@sideboard-ai/core 0.1.44 → 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-PP3URTSF.js → agents-JSHCAZUZ.js} +15 -5
- package/dist/{app-settings-ZKVZHJPQ.js → app-settings-LYGVDGZY.js} +5 -1
- package/dist/{chunk-JM2TVGNW.js → chunk-6NAPN2N5.js} +4 -1
- package/dist/{chunk-XX26NCB6.js → chunk-ANZ566Z5.js} +184 -3
- package/dist/{chunk-7PCTK4WO.js → chunk-FV6FN6V5.js} +1 -1
- package/dist/{chunk-V3S4NF5F.js → chunk-I6QGZOOS.js} +392 -29
- package/dist/{chunk-PU27NUO4.js → chunk-J5JTEJ5O.js} +7 -2
- package/dist/{chunk-ENSD62HW.js → chunk-O6W3P7V3.js} +3 -1
- package/dist/{chunk-TXFJEXFB.js → chunk-U3EQKJHA.js} +2 -2
- package/dist/{chunk-FSIK442J.js → chunk-WYY3J7GR.js} +21 -0
- package/dist/{chunk-I3PKMLFW.js → chunk-ZNSM2DDD.js} +3 -3
- package/dist/{coordinator-prompt-I5YNS27B.js → coordinator-prompt-2XFYG3C5.js} +3 -3
- package/dist/{global-workspace-FU6UIPDY.js → global-workspace-YFOQUGWD.js} +4 -4
- package/dist/index.cjs +619 -13
- package/dist/index.d.cts +76 -2
- package/dist/index.d.ts +76 -2
- package/dist/index.js +23 -9
- package/dist/mcp/run-stdio.cjs +615 -23
- package/dist/mcp/run-stdio.js +9 -9
- package/dist/{thread-store-EHROA3VZ.js → thread-store-OV2X6PYO.js} +1 -1
- package/dist/{workspaces-RSEDTBGJ.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)",
|
|
@@ -5322,6 +5352,199 @@ var init_opencode = __esm({
|
|
|
5322
5352
|
}
|
|
5323
5353
|
});
|
|
5324
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
|
+
|
|
5325
5548
|
// src/agents/install.ts
|
|
5326
5549
|
function getAgentSetupInfo(agent) {
|
|
5327
5550
|
return SETUP[agent];
|
|
@@ -5502,6 +5725,7 @@ var init_install = __esm({
|
|
|
5502
5725
|
// src/agents/index.ts
|
|
5503
5726
|
var agents_exports = {};
|
|
5504
5727
|
__export(agents_exports, {
|
|
5728
|
+
CLAUDE_MODEL_CATALOG: () => CLAUDE_MODEL_CATALOG,
|
|
5505
5729
|
PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
|
|
5506
5730
|
allAdapters: () => allAdapters,
|
|
5507
5731
|
brightsyAdapter: () => brightsyAdapter,
|
|
@@ -5516,17 +5740,21 @@ __export(agents_exports, {
|
|
|
5516
5740
|
getAgentSetupInfo: () => getAgentSetupInfo,
|
|
5517
5741
|
installAgent: () => installAgent,
|
|
5518
5742
|
isCursorAutoModel: () => isCursorAutoModel,
|
|
5743
|
+
isSessionQuotaLimit: () => isSessionQuotaLimit,
|
|
5519
5744
|
listAgentSetupInfo: () => listAgentSetupInfo,
|
|
5520
5745
|
listBrightsyChatTargets: () => listBrightsyChatTargets,
|
|
5521
5746
|
listCodexModels: () => listCodexModels,
|
|
5522
5747
|
listCursorModels: () => listCursorModels,
|
|
5748
|
+
listModelsForAgent: () => listModelsForAgent,
|
|
5523
5749
|
listOpencodeModels: () => listOpencodeModels,
|
|
5524
5750
|
loginAgent: () => loginAgent,
|
|
5525
5751
|
openInSystemTerminal: () => openInSystemTerminal,
|
|
5526
5752
|
opencodeAdapter: () => opencodeAdapter,
|
|
5527
5753
|
parseCursorRunnerLine: () => parseCursorRunnerLine,
|
|
5754
|
+
parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
|
|
5528
5755
|
permissionMode: () => permissionMode,
|
|
5529
|
-
resolveCursorModelId: () => resolveCursorModelId
|
|
5756
|
+
resolveCursorModelId: () => resolveCursorModelId,
|
|
5757
|
+
resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent
|
|
5530
5758
|
});
|
|
5531
5759
|
function getAdapter(kind) {
|
|
5532
5760
|
return adapters[kind];
|
|
@@ -5552,6 +5780,8 @@ var init_agents = __esm({
|
|
|
5552
5780
|
init_cursor_events();
|
|
5553
5781
|
init_cursor();
|
|
5554
5782
|
init_opencode();
|
|
5783
|
+
init_list_models();
|
|
5784
|
+
init_session_quota();
|
|
5555
5785
|
init_path();
|
|
5556
5786
|
init_install();
|
|
5557
5787
|
adapters = {
|
|
@@ -7175,12 +7405,19 @@ function forkChatTab(input) {
|
|
|
7175
7405
|
const from = requireThread(input.threadId);
|
|
7176
7406
|
const slice = forkMessageSlice(from, input.throughIndex);
|
|
7177
7407
|
const attachment = buildForkTranscriptAttachment(from.title || "Chat", slice);
|
|
7178
|
-
|
|
7408
|
+
const tab = createChatTab({
|
|
7179
7409
|
fromThreadId: input.threadId,
|
|
7180
7410
|
agent: input.agent ?? from.agent,
|
|
7411
|
+
model: input.model,
|
|
7181
7412
|
title: input.title?.trim() || void 0,
|
|
7182
7413
|
attachments: [attachment]
|
|
7183
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;
|
|
7184
7421
|
}
|
|
7185
7422
|
|
|
7186
7423
|
// src/review/request-review.ts
|
|
@@ -7237,6 +7474,7 @@ async function requestReview(threadRef, send) {
|
|
|
7237
7474
|
|
|
7238
7475
|
// src/threads/fork-worktree.ts
|
|
7239
7476
|
init_thread_store();
|
|
7477
|
+
init_global_workspace();
|
|
7240
7478
|
function requireThread2(idOrRef) {
|
|
7241
7479
|
const thread = findThreadByRef(idOrRef) ?? null;
|
|
7242
7480
|
if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
|
|
@@ -7244,16 +7482,28 @@ function requireThread2(idOrRef) {
|
|
|
7244
7482
|
}
|
|
7245
7483
|
async function forkThreadWorktree(input, onSetupLine) {
|
|
7246
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
|
+
}
|
|
7247
7495
|
const slice = forkMessageSlice(from, input.throughIndex);
|
|
7248
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;
|
|
7249
7499
|
const thread = await createThread(
|
|
7250
7500
|
{
|
|
7251
7501
|
sourceType: "branch",
|
|
7252
7502
|
sourceRef: from.branchName,
|
|
7253
7503
|
repoPath: from.repoPath,
|
|
7254
|
-
agent:
|
|
7504
|
+
agent: nextAgent,
|
|
7255
7505
|
autonomy: from.autonomy,
|
|
7256
|
-
model:
|
|
7506
|
+
model: nextModel,
|
|
7257
7507
|
effort: from.effort,
|
|
7258
7508
|
fast: from.fast,
|
|
7259
7509
|
planMode: from.planMode,
|
|
@@ -7266,6 +7516,115 @@ async function forkThreadWorktree(input, onSetupLine) {
|
|
|
7266
7516
|
return thread;
|
|
7267
7517
|
}
|
|
7268
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
|
+
|
|
7269
7628
|
// src/threads/adopt.ts
|
|
7270
7629
|
var import_node_child_process = require("child_process");
|
|
7271
7630
|
var import_node_fs20 = require("fs");
|
|
@@ -8469,7 +8828,7 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
|
|
|
8469
8828
|
// src/composer/stage-files.ts
|
|
8470
8829
|
var import_node_fs23 = require("fs");
|
|
8471
8830
|
var import_node_path22 = require("path");
|
|
8472
|
-
var
|
|
8831
|
+
var import_node_crypto5 = require("crypto");
|
|
8473
8832
|
var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
8474
8833
|
"png",
|
|
8475
8834
|
"jpg",
|
|
@@ -8525,7 +8884,7 @@ function uniqueAttachmentName(dir, originalName) {
|
|
|
8525
8884
|
const candidate = `${stem}-${i}${ext}`;
|
|
8526
8885
|
if (!(0, import_node_fs23.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
|
|
8527
8886
|
}
|
|
8528
|
-
return `${stem}-${(0,
|
|
8887
|
+
return `${stem}-${(0, import_node_crypto5.randomUUID)()}${ext}`;
|
|
8529
8888
|
}
|
|
8530
8889
|
function previewDataUrlFromBuf(filePath, buf) {
|
|
8531
8890
|
if (!isImageFilePath(filePath)) return void 0;
|
|
@@ -8537,7 +8896,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8537
8896
|
if (isImageFilePath(name)) {
|
|
8538
8897
|
const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
|
|
8539
8898
|
return {
|
|
8540
|
-
id: (0,
|
|
8899
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8541
8900
|
name,
|
|
8542
8901
|
kind: "file",
|
|
8543
8902
|
path: opts.path,
|
|
@@ -8550,7 +8909,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8550
8909
|
}
|
|
8551
8910
|
if (buf.length > MAX_INLINE_BYTES) {
|
|
8552
8911
|
return {
|
|
8553
|
-
id: (0,
|
|
8912
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8554
8913
|
name,
|
|
8555
8914
|
kind: "file",
|
|
8556
8915
|
path: opts.path,
|
|
@@ -8559,7 +8918,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8559
8918
|
}
|
|
8560
8919
|
if (buf.includes(0)) {
|
|
8561
8920
|
return {
|
|
8562
|
-
id: (0,
|
|
8921
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8563
8922
|
name,
|
|
8564
8923
|
kind: "file",
|
|
8565
8924
|
path: opts.path,
|
|
@@ -8567,7 +8926,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8567
8926
|
};
|
|
8568
8927
|
}
|
|
8569
8928
|
return {
|
|
8570
|
-
id: (0,
|
|
8929
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8571
8930
|
name,
|
|
8572
8931
|
kind: "file",
|
|
8573
8932
|
path: opts.path,
|
|
@@ -8591,7 +8950,7 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
8591
8950
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
8592
8951
|
} catch (err) {
|
|
8593
8952
|
out.push({
|
|
8594
|
-
id: (0,
|
|
8953
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8595
8954
|
name: originalName,
|
|
8596
8955
|
kind: "file",
|
|
8597
8956
|
content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -8615,7 +8974,7 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
|
|
|
8615
8974
|
out.push(attachmentFromBuffer(name, buf, { path: rel }));
|
|
8616
8975
|
} catch (err) {
|
|
8617
8976
|
out.push({
|
|
8618
|
-
id: (0,
|
|
8977
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8619
8978
|
name: originalName,
|
|
8620
8979
|
kind: "file",
|
|
8621
8980
|
content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -8629,7 +8988,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
8629
8988
|
for (const rel of relativePaths) {
|
|
8630
8989
|
if (!rel || rel.includes("..") || rel.startsWith("/")) {
|
|
8631
8990
|
out.push({
|
|
8632
|
-
id: (0,
|
|
8991
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8633
8992
|
name: (0, import_node_path22.basename)(rel) || "file",
|
|
8634
8993
|
kind: "file",
|
|
8635
8994
|
content: `(invalid path: ${rel})`
|
|
@@ -8645,7 +9004,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
8645
9004
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
8646
9005
|
} catch (err) {
|
|
8647
9006
|
out.push({
|
|
8648
|
-
id: (0,
|
|
9007
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8649
9008
|
name,
|
|
8650
9009
|
kind: "file",
|
|
8651
9010
|
content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -8901,6 +9260,8 @@ var Orchestrator = class {
|
|
|
8901
9260
|
haltDrain = /* @__PURE__ */ new Set();
|
|
8902
9261
|
/** WIP snapshot SHA at the start of the latest agent turn (per thread). */
|
|
8903
9262
|
turnBaselines = /* @__PURE__ */ new Map();
|
|
9263
|
+
/** Timers for orchestration session-quota auto-resume. */
|
|
9264
|
+
quotaResumeTimers = /* @__PURE__ */ new Map();
|
|
8904
9265
|
maxConcurrent;
|
|
8905
9266
|
runningCount = 0;
|
|
8906
9267
|
constructor(opts) {
|
|
@@ -8980,6 +9341,113 @@ var Orchestrator = class {
|
|
|
8980
9341
|
void this.drainQueue(thread.id);
|
|
8981
9342
|
}
|
|
8982
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
|
+
}
|
|
8983
9451
|
}
|
|
8984
9452
|
getThreads(includeArchived = false) {
|
|
8985
9453
|
return listThreads({ includeArchived });
|
|
@@ -9338,11 +9806,16 @@ var Orchestrator = class {
|
|
|
9338
9806
|
}
|
|
9339
9807
|
}
|
|
9340
9808
|
}
|
|
9341
|
-
const
|
|
9342
|
-
|
|
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) {
|
|
9343
9816
|
appendMessage(threadId, {
|
|
9344
9817
|
role: "agent",
|
|
9345
|
-
text:
|
|
9818
|
+
text: chatText,
|
|
9346
9819
|
parts: parts.length > 0 ? parts : void 0,
|
|
9347
9820
|
durationMs: Math.max(0, Date.now() - turnStartedAt),
|
|
9348
9821
|
usage,
|
|
@@ -9361,13 +9834,12 @@ var Orchestrator = class {
|
|
|
9361
9834
|
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
9362
9835
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
9363
9836
|
} else {
|
|
9364
|
-
const lastStderr = summarizeTurnStderr(stderrTail);
|
|
9365
|
-
const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
9366
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()));
|
|
9367
9839
|
setStatus(
|
|
9368
9840
|
threadId,
|
|
9369
9841
|
exitCode === 0 ? "idle" : "error",
|
|
9370
|
-
exitCode === 0 ? null : failDetail
|
|
9842
|
+
exitCode === 0 || explainedInChat ? null : failDetail
|
|
9371
9843
|
);
|
|
9372
9844
|
this.emit({
|
|
9373
9845
|
type: "status_changed",
|
|
@@ -9375,6 +9847,10 @@ var Orchestrator = class {
|
|
|
9375
9847
|
status: exitCode === 0 ? "idle" : "error"
|
|
9376
9848
|
});
|
|
9377
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
|
+
}
|
|
9378
9854
|
}
|
|
9379
9855
|
} catch (err) {
|
|
9380
9856
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -9388,6 +9864,7 @@ var Orchestrator = class {
|
|
|
9388
9864
|
this.emit({ type: "error", threadId, message });
|
|
9389
9865
|
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
9390
9866
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
9867
|
+
void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
|
|
9391
9868
|
}
|
|
9392
9869
|
} finally {
|
|
9393
9870
|
this.startingTurns.delete(threadId);
|
|
@@ -10154,6 +10631,7 @@ async function listIssues(repoPath) {
|
|
|
10154
10631
|
|
|
10155
10632
|
// src/mcp/server.ts
|
|
10156
10633
|
init_global_workspace();
|
|
10634
|
+
init_list_models();
|
|
10157
10635
|
|
|
10158
10636
|
// src/mcp/archive-guard.ts
|
|
10159
10637
|
init_global_workspace();
|
|
@@ -10570,6 +11048,120 @@ async function startMcpServer() {
|
|
|
10570
11048
|
}
|
|
10571
11049
|
}
|
|
10572
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
|
+
);
|
|
10573
11165
|
server.tool(
|
|
10574
11166
|
"run_dev_script",
|
|
10575
11167
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|