@sideboard-ai/core 0.1.44 → 0.1.46
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.cjs +11 -4
- package/dist/agents/cursor-runner.js +12 -5
- package/dist/{agents-PP3URTSF.js → agents-6L6VENDT.js} +25 -5
- package/dist/{app-settings-ZKVZHJPQ.js → app-settings-LZP632KI.js} +5 -1
- package/dist/{chunk-JM2TVGNW.js → chunk-6NAPN2N5.js} +4 -1
- package/dist/{chunk-V3S4NF5F.js → chunk-7DTJFP4D.js} +406 -30
- package/dist/{chunk-7PCTK4WO.js → chunk-FV6FN6V5.js} +1 -1
- package/dist/chunk-GI7E5733.js +30 -0
- package/dist/{chunk-PU27NUO4.js → chunk-J5JTEJ5O.js} +7 -2
- package/dist/{chunk-ENSD62HW.js → chunk-O6W3P7V3.js} +3 -1
- package/dist/{chunk-I3PKMLFW.js → chunk-SPCU3MFY.js} +8 -4
- package/dist/{chunk-TXFJEXFB.js → chunk-SZAKJ4TR.js} +2 -2
- package/dist/{chunk-FSIK442J.js → chunk-T5QQVXK3.js} +24 -0
- package/dist/{chunk-XX26NCB6.js → chunk-TQK2OYPU.js} +246 -6
- package/dist/{coordinator-prompt-I5YNS27B.js → coordinator-prompt-2XFYG3C5.js} +3 -3
- package/dist/{global-workspace-FU6UIPDY.js → global-workspace-XFOXEQCP.js} +5 -4
- package/dist/index.cjs +747 -18
- package/dist/index.d.cts +96 -2
- package/dist/index.d.ts +96 -2
- package/dist/index.js +33 -9
- package/dist/mcp/run-stdio.cjs +737 -28
- package/dist/mcp/run-stdio.js +10 -9
- package/dist/{thread-store-EHROA3VZ.js → thread-store-OV2X6PYO.js} +1 -1
- package/dist/{workspaces-RSEDTBGJ.js → workspaces-SRL2HZTB.js} +6 -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) && source.orchestrationQuotaFallbackAgent !== "brightsy") {
|
|
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) && patch.orchestrationQuotaFallbackAgent !== "brightsy") {
|
|
2997
|
+
advanced.orchestrationQuotaFallbackAgent = patch.orchestrationQuotaFallbackAgent;
|
|
2998
|
+
}
|
|
2979
2999
|
return saveAppSettings({ ...current, advanced });
|
|
2980
3000
|
}
|
|
2981
3001
|
function autoRenameBranchEnabled(settings = loadAppSettings()) {
|
|
@@ -2996,6 +3016,16 @@ 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
|
+
if (preferred && DEFAULT_AGENTS.has(preferred) && preferred !== "brightsy") {
|
|
3025
|
+
return preferred;
|
|
3026
|
+
}
|
|
3027
|
+
return "cursor";
|
|
3028
|
+
}
|
|
2999
3029
|
function maxConcurrentAgents(settings = loadAppSettings()) {
|
|
3000
3030
|
const n = settings.advanced.maxConcurrent;
|
|
3001
3031
|
if (typeof n === "number" && Number.isFinite(n)) {
|
|
@@ -3102,6 +3132,36 @@ var init_cloud_connect_constants = __esm({
|
|
|
3102
3132
|
}
|
|
3103
3133
|
});
|
|
3104
3134
|
|
|
3135
|
+
// src/agents/orchestrator-capable.ts
|
|
3136
|
+
function isOrchestratorCapableAgent(agent) {
|
|
3137
|
+
return Boolean(
|
|
3138
|
+
agent && ORCHESTRATOR_AGENT_KINDS.includes(agent)
|
|
3139
|
+
);
|
|
3140
|
+
}
|
|
3141
|
+
function assertOrchestratorCapableAgent(agent, context = "orchestration") {
|
|
3142
|
+
if (!isOrchestratorCapableAgent(agent)) {
|
|
3143
|
+
throw new Error(
|
|
3144
|
+
`${agent} cannot run ${context} \u2014 it does not support Sideboard MCP. Use Claude, Cursor, Codex, or OpenCode.`
|
|
3145
|
+
);
|
|
3146
|
+
}
|
|
3147
|
+
return agent;
|
|
3148
|
+
}
|
|
3149
|
+
function coerceOrchestratorAgent(agent, fallback = "claude") {
|
|
3150
|
+
return isOrchestratorCapableAgent(agent) ? agent : fallback;
|
|
3151
|
+
}
|
|
3152
|
+
var ORCHESTRATOR_AGENT_KINDS;
|
|
3153
|
+
var init_orchestrator_capable = __esm({
|
|
3154
|
+
"src/agents/orchestrator-capable.ts"() {
|
|
3155
|
+
"use strict";
|
|
3156
|
+
ORCHESTRATOR_AGENT_KINDS = [
|
|
3157
|
+
"claude",
|
|
3158
|
+
"codex",
|
|
3159
|
+
"opencode",
|
|
3160
|
+
"cursor"
|
|
3161
|
+
];
|
|
3162
|
+
}
|
|
3163
|
+
});
|
|
3164
|
+
|
|
3105
3165
|
// src/orchestrator/coordinator-prompt.ts
|
|
3106
3166
|
var coordinator_prompt_exports = {};
|
|
3107
3167
|
__export(coordinator_prompt_exports, {
|
|
@@ -3223,11 +3283,14 @@ var init_coordinator_prompt = __esm({
|
|
|
3223
3283
|
"Discover:",
|
|
3224
3284
|
"- list_workspaces \u2014 registered repos (path + github slug when known)",
|
|
3225
3285
|
"- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
|
|
3286
|
+
"- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
|
|
3226
3287
|
"- list_threads / get_thread \u2014 fleet status (what is going on)",
|
|
3227
3288
|
"Workspaces:",
|
|
3228
3289
|
"- add_workspace / remove_workspace \u2014 register or unregister a git repo",
|
|
3229
3290
|
"Worktree threads (chats):",
|
|
3230
3291
|
"- create_thread \u2014 create a worktree + chat from branch | pr | ticket; pass repoPath + parentThreadId",
|
|
3292
|
+
"- 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.",
|
|
3293
|
+
"- 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
3294
|
"- 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
3295
|
"- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply",
|
|
3233
3296
|
"- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
|
|
@@ -3325,6 +3388,7 @@ function createGlobalChat(opts) {
|
|
|
3325
3388
|
const explicit = opts.title?.trim();
|
|
3326
3389
|
const title = explicit && explicit !== CLOUD_ORCHESTRATOR_GOAL ? explicit : allocateTeamName(takenTeamSlugsForOrchestration()).name;
|
|
3327
3390
|
const sourceRef = opts.sourceRef?.trim() || (isCloud ? CLOUD_ORCHESTRATOR_GOAL : title);
|
|
3391
|
+
const agent = assertOrchestratorCapableAgent(opts.agent);
|
|
3328
3392
|
const thread = createEmptyThread({
|
|
3329
3393
|
title,
|
|
3330
3394
|
// Stick nicknames the same way chat tabs do (avoid later sync overwrites).
|
|
@@ -3334,7 +3398,7 @@ function createGlobalChat(opts) {
|
|
|
3334
3398
|
branchName: "global",
|
|
3335
3399
|
worktreePath: globalAgentCwd(),
|
|
3336
3400
|
repoPath: GLOBAL_WORKSPACE_ID,
|
|
3337
|
-
agent
|
|
3401
|
+
agent,
|
|
3338
3402
|
autonomy: opts.autonomy ?? "default",
|
|
3339
3403
|
model: opts.model ?? null,
|
|
3340
3404
|
effort: opts.effort ?? "high",
|
|
@@ -3396,6 +3460,7 @@ var init_global_workspace = __esm({
|
|
|
3396
3460
|
"src/store/global-workspace.ts"() {
|
|
3397
3461
|
"use strict";
|
|
3398
3462
|
init_cloud_connect_constants();
|
|
3463
|
+
init_orchestrator_capable();
|
|
3399
3464
|
init_teams();
|
|
3400
3465
|
init_coordinator_prompt();
|
|
3401
3466
|
init_paths();
|
|
@@ -4128,6 +4193,45 @@ async function buildInjectedMcpServers(opts) {
|
|
|
4128
4193
|
}
|
|
4129
4194
|
return servers;
|
|
4130
4195
|
}
|
|
4196
|
+
function toCursorMcpServers(servers) {
|
|
4197
|
+
const out = {};
|
|
4198
|
+
for (const s of servers) {
|
|
4199
|
+
out[s.name] = {
|
|
4200
|
+
command: s.command,
|
|
4201
|
+
...s.args ? { args: s.args } : {},
|
|
4202
|
+
...s.env ? { env: s.env } : {}
|
|
4203
|
+
};
|
|
4204
|
+
}
|
|
4205
|
+
return out;
|
|
4206
|
+
}
|
|
4207
|
+
function toCodexMcpConfigArgs(servers) {
|
|
4208
|
+
const args = [];
|
|
4209
|
+
for (const s of servers) {
|
|
4210
|
+
const prefix = `mcp_servers.${s.name}`;
|
|
4211
|
+
args.push("-c", `${prefix}.command=${JSON.stringify(s.command)}`);
|
|
4212
|
+
if (s.args?.length) {
|
|
4213
|
+
args.push("-c", `${prefix}.args=${JSON.stringify(s.args)}`);
|
|
4214
|
+
}
|
|
4215
|
+
if (s.env) {
|
|
4216
|
+
for (const [key, value] of Object.entries(s.env)) {
|
|
4217
|
+
args.push("-c", `${prefix}.env.${key}=${JSON.stringify(value)}`);
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
return args;
|
|
4222
|
+
}
|
|
4223
|
+
function toOpencodeMcpConfigContent(servers) {
|
|
4224
|
+
const mcp = {};
|
|
4225
|
+
for (const s of servers) {
|
|
4226
|
+
mcp[s.name] = {
|
|
4227
|
+
type: "local",
|
|
4228
|
+
command: [s.command, ...s.args ?? []],
|
|
4229
|
+
enabled: true,
|
|
4230
|
+
...s.env && Object.keys(s.env).length > 0 ? { environment: s.env } : {}
|
|
4231
|
+
};
|
|
4232
|
+
}
|
|
4233
|
+
return JSON.stringify({ mcp });
|
|
4234
|
+
}
|
|
4131
4235
|
function writeMcpServersConfig(servers) {
|
|
4132
4236
|
if (servers.length === 0) return null;
|
|
4133
4237
|
const mcpServers = {};
|
|
@@ -4628,6 +4732,7 @@ var init_codex = __esm({
|
|
|
4628
4732
|
import_node_path11 = require("path");
|
|
4629
4733
|
init_run();
|
|
4630
4734
|
init_error_detail();
|
|
4735
|
+
init_injected_mcp();
|
|
4631
4736
|
init_turn_input();
|
|
4632
4737
|
init_types();
|
|
4633
4738
|
CODEX_PROMPT_ARG_MAX = 2e5;
|
|
@@ -4686,6 +4791,11 @@ var init_codex = __esm({
|
|
|
4686
4791
|
}
|
|
4687
4792
|
const mode = permissionMode(thread);
|
|
4688
4793
|
const model = thread.model?.trim();
|
|
4794
|
+
const injected = await buildInjectedMcpServers({
|
|
4795
|
+
includeSideboard: true,
|
|
4796
|
+
includeBrightsy: isBrightsyConnected()
|
|
4797
|
+
});
|
|
4798
|
+
const mcpOverrides = toCodexMcpConfigArgs(injected);
|
|
4689
4799
|
const args = [
|
|
4690
4800
|
"exec",
|
|
4691
4801
|
...sessionId ? ["resume", sessionId] : [],
|
|
@@ -4697,7 +4807,8 @@ var init_codex = __esm({
|
|
|
4697
4807
|
mode.codexSandbox,
|
|
4698
4808
|
"--ask-for-approval",
|
|
4699
4809
|
"never",
|
|
4700
|
-
...model ? ["--model", model] : []
|
|
4810
|
+
...model ? ["--model", model] : [],
|
|
4811
|
+
...mcpOverrides
|
|
4701
4812
|
];
|
|
4702
4813
|
return {
|
|
4703
4814
|
file: "codex",
|
|
@@ -4982,6 +5093,7 @@ var init_cursor = __esm({
|
|
|
4982
5093
|
init_run();
|
|
4983
5094
|
init_app_settings();
|
|
4984
5095
|
init_cursor_events();
|
|
5096
|
+
init_injected_mcp();
|
|
4985
5097
|
init_node_launch();
|
|
4986
5098
|
init_turn_input();
|
|
4987
5099
|
init_cursor_events();
|
|
@@ -5032,6 +5144,11 @@ var init_cursor = __esm({
|
|
|
5032
5144
|
const prompt = flattenTurnInput(input);
|
|
5033
5145
|
const agentId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
|
|
5034
5146
|
const apiKey = resolveCursorApiKey() || void 0;
|
|
5147
|
+
const injected = await buildInjectedMcpServers({
|
|
5148
|
+
includeSideboard: true,
|
|
5149
|
+
includeBrightsy: isBrightsyConnected()
|
|
5150
|
+
});
|
|
5151
|
+
const mcpServers = toCursorMcpServers(injected);
|
|
5035
5152
|
const req = {
|
|
5036
5153
|
prompt,
|
|
5037
5154
|
cwd: thread.worktreePath,
|
|
@@ -5040,7 +5157,8 @@ var init_cursor = __esm({
|
|
|
5040
5157
|
effort: thread.effort,
|
|
5041
5158
|
fast: thread.fast,
|
|
5042
5159
|
planMode: thread.planMode,
|
|
5043
|
-
apiKey
|
|
5160
|
+
apiKey,
|
|
5161
|
+
...Object.keys(mcpServers).length > 0 ? { mcpServers } : {}
|
|
5044
5162
|
};
|
|
5045
5163
|
const runner = cursorRunnerPath();
|
|
5046
5164
|
const isTs = runner.endsWith(".ts");
|
|
@@ -5134,6 +5252,7 @@ var init_opencode = __esm({
|
|
|
5134
5252
|
"use strict";
|
|
5135
5253
|
init_run();
|
|
5136
5254
|
init_error_detail();
|
|
5255
|
+
init_injected_mcp();
|
|
5137
5256
|
init_turn_input();
|
|
5138
5257
|
init_types();
|
|
5139
5258
|
FALLBACK_OPENCODE_MODELS = [
|
|
@@ -5194,6 +5313,11 @@ var init_opencode = __esm({
|
|
|
5194
5313
|
if (model) {
|
|
5195
5314
|
args.push("--model", model);
|
|
5196
5315
|
}
|
|
5316
|
+
const injected = await buildInjectedMcpServers({
|
|
5317
|
+
includeSideboard: true,
|
|
5318
|
+
includeBrightsy: isBrightsyConnected()
|
|
5319
|
+
});
|
|
5320
|
+
const mcpContent = injected.length > 0 ? toOpencodeMcpConfigContent(injected) : null;
|
|
5197
5321
|
return {
|
|
5198
5322
|
file: "opencode",
|
|
5199
5323
|
args,
|
|
@@ -5202,7 +5326,8 @@ var init_opencode = __esm({
|
|
|
5202
5326
|
// message is given (see resolveRunInput in opencode's run.ts).
|
|
5203
5327
|
stdin: prompt,
|
|
5204
5328
|
env: {
|
|
5205
|
-
OPENCODE_PERMISSION: mode.opencodePermission
|
|
5329
|
+
OPENCODE_PERMISSION: mode.opencodePermission,
|
|
5330
|
+
...mcpContent ? { OPENCODE_CONFIG_CONTENT: mcpContent } : {}
|
|
5206
5331
|
}
|
|
5207
5332
|
};
|
|
5208
5333
|
},
|
|
@@ -5322,6 +5447,201 @@ var init_opencode = __esm({
|
|
|
5322
5447
|
}
|
|
5323
5448
|
});
|
|
5324
5449
|
|
|
5450
|
+
// src/agents/list-models.ts
|
|
5451
|
+
async function listBrightsyModels() {
|
|
5452
|
+
try {
|
|
5453
|
+
const targets = await listBrightsyChatTargets();
|
|
5454
|
+
const accountId = targets.activeAccountId;
|
|
5455
|
+
const models = (targets.models ?? []).map((m) => ({
|
|
5456
|
+
id: encodeBrightsyTarget("model", m.id, accountId),
|
|
5457
|
+
displayName: m.name || m.id,
|
|
5458
|
+
description: m.description ?? void 0
|
|
5459
|
+
}));
|
|
5460
|
+
const agents = (targets.agents ?? []).map((a) => ({
|
|
5461
|
+
id: encodeBrightsyTarget("agent", a.id, accountId),
|
|
5462
|
+
displayName: a.name || a.id,
|
|
5463
|
+
description: a.description ?? "Brightsy agent target"
|
|
5464
|
+
}));
|
|
5465
|
+
return [...models, ...agents].slice(0, 80);
|
|
5466
|
+
} catch {
|
|
5467
|
+
return [];
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
async function listModelsForAgent(agent) {
|
|
5471
|
+
const kinds = agent ? [agent] : ["claude", "codex", "opencode", "cursor", "brightsy"];
|
|
5472
|
+
const out = [];
|
|
5473
|
+
for (const kind of kinds) {
|
|
5474
|
+
if (kind === "claude") {
|
|
5475
|
+
out.push({
|
|
5476
|
+
agent: kind,
|
|
5477
|
+
auto: true,
|
|
5478
|
+
models: CLAUDE_MODEL_CATALOG,
|
|
5479
|
+
note: "Default Auto \u2014 only pass a model id when you have a reason."
|
|
5480
|
+
});
|
|
5481
|
+
continue;
|
|
5482
|
+
}
|
|
5483
|
+
if (kind === "codex") {
|
|
5484
|
+
out.push({
|
|
5485
|
+
agent: kind,
|
|
5486
|
+
auto: true,
|
|
5487
|
+
models: await listCodexModels(),
|
|
5488
|
+
note: "Default Auto \u2014 only pass a model slug when you have a reason."
|
|
5489
|
+
});
|
|
5490
|
+
continue;
|
|
5491
|
+
}
|
|
5492
|
+
if (kind === "opencode") {
|
|
5493
|
+
out.push({
|
|
5494
|
+
agent: kind,
|
|
5495
|
+
auto: true,
|
|
5496
|
+
models: await listOpencodeModels(),
|
|
5497
|
+
note: "Default Auto \u2014 only pass a provider/model id when you have a reason."
|
|
5498
|
+
});
|
|
5499
|
+
continue;
|
|
5500
|
+
}
|
|
5501
|
+
if (kind === "cursor") {
|
|
5502
|
+
out.push({
|
|
5503
|
+
agent: kind,
|
|
5504
|
+
auto: true,
|
|
5505
|
+
models: await listCursorModels(),
|
|
5506
|
+
note: 'Default Auto \u2014 only pass a model id when you have a reason (or use "default").'
|
|
5507
|
+
});
|
|
5508
|
+
continue;
|
|
5509
|
+
}
|
|
5510
|
+
if (kind === "brightsy") {
|
|
5511
|
+
const models = await listBrightsyModels();
|
|
5512
|
+
out.push({
|
|
5513
|
+
agent: kind,
|
|
5514
|
+
auto: true,
|
|
5515
|
+
models,
|
|
5516
|
+
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."
|
|
5517
|
+
});
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5520
|
+
return out;
|
|
5521
|
+
}
|
|
5522
|
+
var CLAUDE_MODEL_CATALOG;
|
|
5523
|
+
var init_list_models = __esm({
|
|
5524
|
+
"src/agents/list-models.ts"() {
|
|
5525
|
+
"use strict";
|
|
5526
|
+
init_brightsy();
|
|
5527
|
+
init_brightsy_targets();
|
|
5528
|
+
init_codex();
|
|
5529
|
+
init_cursor();
|
|
5530
|
+
init_opencode();
|
|
5531
|
+
CLAUDE_MODEL_CATALOG = [
|
|
5532
|
+
{ id: "fable", displayName: "Fable" },
|
|
5533
|
+
{ id: "opus", displayName: "Opus" },
|
|
5534
|
+
{ id: "sonnet", displayName: "Sonnet" },
|
|
5535
|
+
{ id: "haiku", displayName: "Haiku" }
|
|
5536
|
+
];
|
|
5537
|
+
}
|
|
5538
|
+
});
|
|
5539
|
+
|
|
5540
|
+
// src/agents/session-quota.ts
|
|
5541
|
+
function isSessionQuotaLimit(text) {
|
|
5542
|
+
const lower = text.trim().toLowerCase();
|
|
5543
|
+
if (!lower) return false;
|
|
5544
|
+
if (/credit balance is too low|out of credits|insufficient.?quota|billing/.test(lower)) {
|
|
5545
|
+
return false;
|
|
5546
|
+
}
|
|
5547
|
+
if (/prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower)) {
|
|
5548
|
+
return false;
|
|
5549
|
+
}
|
|
5550
|
+
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);
|
|
5551
|
+
}
|
|
5552
|
+
function parseSessionQuotaResetAt(text, now = /* @__PURE__ */ new Date()) {
|
|
5553
|
+
const absolute = text.match(
|
|
5554
|
+
/resets\s+(?:at\s+)?(\d{1,2}):(\d{2})\s*(am|pm)(?:\s*\(([^)]+)\))?/i
|
|
5555
|
+
);
|
|
5556
|
+
if (absolute) {
|
|
5557
|
+
const hour12 = Number(absolute[1]);
|
|
5558
|
+
const minute = Number(absolute[2]);
|
|
5559
|
+
const ampm = absolute[3].toLowerCase();
|
|
5560
|
+
const timeZone = absolute[4]?.trim() || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
5561
|
+
let hour = hour12 % 12;
|
|
5562
|
+
if (ampm === "pm") hour += 12;
|
|
5563
|
+
const at = zonedWallTimeToUtc(now, hour, minute, timeZone);
|
|
5564
|
+
if (!at) return null;
|
|
5565
|
+
if (at.getTime() <= now.getTime() + 3e4) {
|
|
5566
|
+
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1e3);
|
|
5567
|
+
return zonedWallTimeToUtc(tomorrow, hour, minute, timeZone);
|
|
5568
|
+
}
|
|
5569
|
+
return at;
|
|
5570
|
+
}
|
|
5571
|
+
const relative = text.match(
|
|
5572
|
+
/resets\s+in\s+(\d+)\s*(minutes?|hours?|days?)/i
|
|
5573
|
+
);
|
|
5574
|
+
if (relative) {
|
|
5575
|
+
const n = Number(relative[1]);
|
|
5576
|
+
const unit = relative[2].toLowerCase();
|
|
5577
|
+
const ms = unit.startsWith("day") ? n * 24 * 60 * 60 * 1e3 : unit.startsWith("hour") ? n * 60 * 60 * 1e3 : n * 60 * 1e3;
|
|
5578
|
+
return new Date(now.getTime() + ms);
|
|
5579
|
+
}
|
|
5580
|
+
return null;
|
|
5581
|
+
}
|
|
5582
|
+
function zonedWallTimeToUtc(day, hour, minute, timeZone) {
|
|
5583
|
+
try {
|
|
5584
|
+
const cal = new Intl.DateTimeFormat("en-US", {
|
|
5585
|
+
timeZone,
|
|
5586
|
+
year: "numeric",
|
|
5587
|
+
month: "2-digit",
|
|
5588
|
+
day: "2-digit"
|
|
5589
|
+
});
|
|
5590
|
+
const parts = Object.fromEntries(
|
|
5591
|
+
cal.formatToParts(day).filter((p) => p.type !== "literal").map((p) => [p.type, p.value])
|
|
5592
|
+
);
|
|
5593
|
+
const year = Number(parts.year);
|
|
5594
|
+
const month = Number(parts.month);
|
|
5595
|
+
const date = Number(parts.day);
|
|
5596
|
+
if (![year, month, date].every((n) => Number.isFinite(n))) return null;
|
|
5597
|
+
const utcGuess = Date.UTC(year, month - 1, date, hour, minute, 0);
|
|
5598
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
5599
|
+
timeZone,
|
|
5600
|
+
year: "numeric",
|
|
5601
|
+
month: "2-digit",
|
|
5602
|
+
day: "2-digit",
|
|
5603
|
+
hour: "2-digit",
|
|
5604
|
+
minute: "2-digit",
|
|
5605
|
+
second: "2-digit",
|
|
5606
|
+
hourCycle: "h23"
|
|
5607
|
+
});
|
|
5608
|
+
const asParts = Object.fromEntries(
|
|
5609
|
+
dtf.formatToParts(new Date(utcGuess)).filter((p) => p.type !== "literal").map((p) => [p.type, p.value])
|
|
5610
|
+
);
|
|
5611
|
+
const asUtc = Date.UTC(
|
|
5612
|
+
Number(asParts.year),
|
|
5613
|
+
Number(asParts.month) - 1,
|
|
5614
|
+
Number(asParts.day),
|
|
5615
|
+
Number(asParts.hour),
|
|
5616
|
+
Number(asParts.minute),
|
|
5617
|
+
Number(asParts.second || "0")
|
|
5618
|
+
);
|
|
5619
|
+
const offset = asUtc - utcGuess;
|
|
5620
|
+
return new Date(utcGuess - offset);
|
|
5621
|
+
} catch {
|
|
5622
|
+
return null;
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5625
|
+
function resolveQuotaFallbackAgent(current, preferred) {
|
|
5626
|
+
const ordered = [
|
|
5627
|
+
...preferred && preferred !== "brightsy" ? [preferred] : [],
|
|
5628
|
+
...FALLBACK_ORDER.filter((a) => a !== preferred)
|
|
5629
|
+
];
|
|
5630
|
+
return ordered.find((a) => a !== current) ?? (current === "cursor" ? "codex" : "cursor");
|
|
5631
|
+
}
|
|
5632
|
+
var FALLBACK_ORDER;
|
|
5633
|
+
var init_session_quota = __esm({
|
|
5634
|
+
"src/agents/session-quota.ts"() {
|
|
5635
|
+
"use strict";
|
|
5636
|
+
FALLBACK_ORDER = [
|
|
5637
|
+
"cursor",
|
|
5638
|
+
"codex",
|
|
5639
|
+
"opencode",
|
|
5640
|
+
"claude"
|
|
5641
|
+
];
|
|
5642
|
+
}
|
|
5643
|
+
});
|
|
5644
|
+
|
|
5325
5645
|
// src/agents/install.ts
|
|
5326
5646
|
function getAgentSetupInfo(agent) {
|
|
5327
5647
|
return SETUP[agent];
|
|
@@ -5502,11 +5822,15 @@ var init_install = __esm({
|
|
|
5502
5822
|
// src/agents/index.ts
|
|
5503
5823
|
var agents_exports = {};
|
|
5504
5824
|
__export(agents_exports, {
|
|
5825
|
+
CLAUDE_MODEL_CATALOG: () => CLAUDE_MODEL_CATALOG,
|
|
5826
|
+
ORCHESTRATOR_AGENT_KINDS: () => ORCHESTRATOR_AGENT_KINDS,
|
|
5505
5827
|
PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
|
|
5506
5828
|
allAdapters: () => allAdapters,
|
|
5829
|
+
assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
|
|
5507
5830
|
brightsyAdapter: () => brightsyAdapter,
|
|
5508
5831
|
claudeAdapter: () => claudeAdapter,
|
|
5509
5832
|
codexAdapter: () => codexAdapter,
|
|
5833
|
+
coerceOrchestratorAgent: () => coerceOrchestratorAgent,
|
|
5510
5834
|
cursorAdapter: () => cursorAdapter,
|
|
5511
5835
|
cursorSdkMessageToEvents: () => cursorSdkMessageToEvents,
|
|
5512
5836
|
decodeBrightsyTarget: () => decodeBrightsyTarget,
|
|
@@ -5516,17 +5840,22 @@ __export(agents_exports, {
|
|
|
5516
5840
|
getAgentSetupInfo: () => getAgentSetupInfo,
|
|
5517
5841
|
installAgent: () => installAgent,
|
|
5518
5842
|
isCursorAutoModel: () => isCursorAutoModel,
|
|
5843
|
+
isOrchestratorCapableAgent: () => isOrchestratorCapableAgent,
|
|
5844
|
+
isSessionQuotaLimit: () => isSessionQuotaLimit,
|
|
5519
5845
|
listAgentSetupInfo: () => listAgentSetupInfo,
|
|
5520
5846
|
listBrightsyChatTargets: () => listBrightsyChatTargets,
|
|
5521
5847
|
listCodexModels: () => listCodexModels,
|
|
5522
5848
|
listCursorModels: () => listCursorModels,
|
|
5849
|
+
listModelsForAgent: () => listModelsForAgent,
|
|
5523
5850
|
listOpencodeModels: () => listOpencodeModels,
|
|
5524
5851
|
loginAgent: () => loginAgent,
|
|
5525
5852
|
openInSystemTerminal: () => openInSystemTerminal,
|
|
5526
5853
|
opencodeAdapter: () => opencodeAdapter,
|
|
5527
5854
|
parseCursorRunnerLine: () => parseCursorRunnerLine,
|
|
5855
|
+
parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
|
|
5528
5856
|
permissionMode: () => permissionMode,
|
|
5529
|
-
resolveCursorModelId: () => resolveCursorModelId
|
|
5857
|
+
resolveCursorModelId: () => resolveCursorModelId,
|
|
5858
|
+
resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent
|
|
5530
5859
|
});
|
|
5531
5860
|
function getAdapter(kind) {
|
|
5532
5861
|
return adapters[kind];
|
|
@@ -5552,6 +5881,9 @@ var init_agents = __esm({
|
|
|
5552
5881
|
init_cursor_events();
|
|
5553
5882
|
init_cursor();
|
|
5554
5883
|
init_opencode();
|
|
5884
|
+
init_list_models();
|
|
5885
|
+
init_session_quota();
|
|
5886
|
+
init_orchestrator_capable();
|
|
5555
5887
|
init_path();
|
|
5556
5888
|
init_install();
|
|
5557
5889
|
adapters = {
|
|
@@ -5749,6 +6081,7 @@ init_app_settings();
|
|
|
5749
6081
|
init_global_workspace();
|
|
5750
6082
|
init_brightsy();
|
|
5751
6083
|
init_agents();
|
|
6084
|
+
init_orchestrator_capable();
|
|
5752
6085
|
|
|
5753
6086
|
// src/agents/message-parts.ts
|
|
5754
6087
|
function asRecord(input) {
|
|
@@ -5990,6 +6323,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
5990
6323
|
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await Promise.resolve().then(() => (init_coordinator_prompt(), coordinator_prompt_exports));
|
|
5991
6324
|
ensureGlobalCoordinatorCwd2();
|
|
5992
6325
|
}
|
|
6326
|
+
if (isOrchestratorThread(thread)) {
|
|
6327
|
+
assertOrchestratorCapableAgent(thread.agent);
|
|
6328
|
+
}
|
|
5993
6329
|
const adapter = getAdapter(thread.agent);
|
|
5994
6330
|
const cmd = await adapter.buildTurn(thread, input);
|
|
5995
6331
|
if (cmd.cwd !== thread.worktreePath) {
|
|
@@ -6808,6 +7144,9 @@ async function listLinearIssues(agent, repoPath) {
|
|
|
6808
7144
|
return adapter.listLinearIssues(repoPath);
|
|
6809
7145
|
}
|
|
6810
7146
|
|
|
7147
|
+
// src/orchestrator/orchestrator.ts
|
|
7148
|
+
init_orchestrator_capable();
|
|
7149
|
+
|
|
6811
7150
|
// src/threads/chat-tabs.ts
|
|
6812
7151
|
var import_node_crypto2 = require("crypto");
|
|
6813
7152
|
|
|
@@ -7089,6 +7428,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
7089
7428
|
init_teams();
|
|
7090
7429
|
init_worktree_labels();
|
|
7091
7430
|
init_global_workspace();
|
|
7431
|
+
init_orchestrator_capable();
|
|
7092
7432
|
init_thread_store();
|
|
7093
7433
|
init_worktree_labels();
|
|
7094
7434
|
function sameWorktreePath(a, b) {
|
|
@@ -7152,6 +7492,10 @@ function createChatTab(input) {
|
|
|
7152
7492
|
const binding = worktreeBindingFrom(from);
|
|
7153
7493
|
const explicitTitle = input.title?.trim();
|
|
7154
7494
|
const title = explicitTitle || allocateTeamName(takenTeamSlugsForChatTab(binding.worktreePath)).name;
|
|
7495
|
+
const nextAgent = input.agent ?? from.agent;
|
|
7496
|
+
if (isOrchestratorThread(from) || binding.sourceType === "orchestration") {
|
|
7497
|
+
assertOrchestratorCapableAgent(nextAgent);
|
|
7498
|
+
}
|
|
7155
7499
|
const thread = createEmptyThread({
|
|
7156
7500
|
title,
|
|
7157
7501
|
// Chat-tab nicknames (soccer team or explicit) must stick. Post-turn
|
|
@@ -7159,7 +7503,7 @@ function createChatTab(input) {
|
|
|
7159
7503
|
// shared worktree folder name (e.g. fork "Arsenal" → "Monaco").
|
|
7160
7504
|
userSetTitle: true,
|
|
7161
7505
|
...binding,
|
|
7162
|
-
agent:
|
|
7506
|
+
agent: nextAgent,
|
|
7163
7507
|
model: input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model,
|
|
7164
7508
|
effort: input.effort !== void 0 ? input.effort : from.effort,
|
|
7165
7509
|
fast: input.fast !== void 0 ? Boolean(input.fast) : from.fast,
|
|
@@ -7175,12 +7519,19 @@ function forkChatTab(input) {
|
|
|
7175
7519
|
const from = requireThread(input.threadId);
|
|
7176
7520
|
const slice = forkMessageSlice(from, input.throughIndex);
|
|
7177
7521
|
const attachment = buildForkTranscriptAttachment(from.title || "Chat", slice);
|
|
7178
|
-
|
|
7522
|
+
const tab = createChatTab({
|
|
7179
7523
|
fromThreadId: input.threadId,
|
|
7180
7524
|
agent: input.agent ?? from.agent,
|
|
7525
|
+
model: input.model,
|
|
7181
7526
|
title: input.title?.trim() || void 0,
|
|
7182
7527
|
attachments: [attachment]
|
|
7183
7528
|
});
|
|
7529
|
+
if (isOrchestratorThread(from) && tab.parentThreadId !== from.id) {
|
|
7530
|
+
const next = { ...tab, parentThreadId: from.id };
|
|
7531
|
+
writeThread(next);
|
|
7532
|
+
return next;
|
|
7533
|
+
}
|
|
7534
|
+
return tab;
|
|
7184
7535
|
}
|
|
7185
7536
|
|
|
7186
7537
|
// src/review/request-review.ts
|
|
@@ -7237,6 +7588,7 @@ async function requestReview(threadRef, send) {
|
|
|
7237
7588
|
|
|
7238
7589
|
// src/threads/fork-worktree.ts
|
|
7239
7590
|
init_thread_store();
|
|
7591
|
+
init_global_workspace();
|
|
7240
7592
|
function requireThread2(idOrRef) {
|
|
7241
7593
|
const thread = findThreadByRef(idOrRef) ?? null;
|
|
7242
7594
|
if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
|
|
@@ -7244,16 +7596,28 @@ function requireThread2(idOrRef) {
|
|
|
7244
7596
|
}
|
|
7245
7597
|
async function forkThreadWorktree(input, onSetupLine) {
|
|
7246
7598
|
const from = requireThread2(input.threadId);
|
|
7599
|
+
if (isOrchestratorThread(from)) {
|
|
7600
|
+
throw new Error(
|
|
7601
|
+
"fork_worktree targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
|
|
7602
|
+
);
|
|
7603
|
+
}
|
|
7604
|
+
if (!from.branchName?.trim() || !from.repoPath?.trim()) {
|
|
7605
|
+
throw new Error(
|
|
7606
|
+
`Cannot fork worktree: thread ${from.id} has no branch/repo (need a real worktree chat).`
|
|
7607
|
+
);
|
|
7608
|
+
}
|
|
7247
7609
|
const slice = forkMessageSlice(from, input.throughIndex);
|
|
7248
7610
|
const attachment = buildForkTranscriptAttachment(from.title || "Chat", slice);
|
|
7611
|
+
const nextAgent = input.agent ?? from.agent;
|
|
7612
|
+
const nextModel = input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model;
|
|
7249
7613
|
const thread = await createThread(
|
|
7250
7614
|
{
|
|
7251
7615
|
sourceType: "branch",
|
|
7252
7616
|
sourceRef: from.branchName,
|
|
7253
7617
|
repoPath: from.repoPath,
|
|
7254
|
-
agent:
|
|
7618
|
+
agent: nextAgent,
|
|
7255
7619
|
autonomy: from.autonomy,
|
|
7256
|
-
model:
|
|
7620
|
+
model: nextModel,
|
|
7257
7621
|
effort: from.effort,
|
|
7258
7622
|
fast: from.fast,
|
|
7259
7623
|
planMode: from.planMode,
|
|
@@ -7266,6 +7630,115 @@ async function forkThreadWorktree(input, onSetupLine) {
|
|
|
7266
7630
|
return thread;
|
|
7267
7631
|
}
|
|
7268
7632
|
|
|
7633
|
+
// src/orchestrator/quota-failover.ts
|
|
7634
|
+
var import_node_crypto4 = require("crypto");
|
|
7635
|
+
init_session_quota();
|
|
7636
|
+
init_app_settings();
|
|
7637
|
+
init_global_workspace();
|
|
7638
|
+
init_thread_store();
|
|
7639
|
+
function planOrchestrationQuotaFailover(thread, limitText, opts) {
|
|
7640
|
+
if (!isOrchestratorThread(thread)) return null;
|
|
7641
|
+
if (!isSessionQuotaLimit(limitText)) return null;
|
|
7642
|
+
const onLimit = opts?.onLimit ?? orchestrationQuotaOnLimit();
|
|
7643
|
+
const resumeAt = parseSessionQuotaResetAt(limitText, opts?.now);
|
|
7644
|
+
if (thread.quotaContinuedFromId) {
|
|
7645
|
+
if (resumeAt) {
|
|
7646
|
+
return {
|
|
7647
|
+
action: "wait_reset",
|
|
7648
|
+
reason: "Already continued once; waiting for quota reset instead.",
|
|
7649
|
+
limitText,
|
|
7650
|
+
resumeAt
|
|
7651
|
+
};
|
|
7652
|
+
}
|
|
7653
|
+
return {
|
|
7654
|
+
action: "none",
|
|
7655
|
+
reason: "Already continued once; no parseable reset time.",
|
|
7656
|
+
limitText
|
|
7657
|
+
};
|
|
7658
|
+
}
|
|
7659
|
+
if (onLimit === "wait_reset") {
|
|
7660
|
+
if (!resumeAt) {
|
|
7661
|
+
return {
|
|
7662
|
+
action: "none",
|
|
7663
|
+
reason: "wait_reset configured but reset time could not be parsed.",
|
|
7664
|
+
limitText
|
|
7665
|
+
};
|
|
7666
|
+
}
|
|
7667
|
+
return {
|
|
7668
|
+
action: "wait_reset",
|
|
7669
|
+
reason: "Settings: wait for quota reset.",
|
|
7670
|
+
limitText,
|
|
7671
|
+
resumeAt
|
|
7672
|
+
};
|
|
7673
|
+
}
|
|
7674
|
+
const preferred = opts?.fallbackAgent ?? orchestrationQuotaFallbackAgent();
|
|
7675
|
+
const fallbackAgent = resolveQuotaFallbackAgent(thread.agent, preferred);
|
|
7676
|
+
return {
|
|
7677
|
+
action: "switch_agent",
|
|
7678
|
+
reason: `Continue on ${fallbackAgent} (Auto) after ${thread.agent} session limit.`,
|
|
7679
|
+
limitText,
|
|
7680
|
+
fallbackAgent
|
|
7681
|
+
};
|
|
7682
|
+
}
|
|
7683
|
+
function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
7684
|
+
const children = listThreads({ includeArchived: false }).filter((t) => t.parentThreadId === from.id && t.status !== "archived").slice(0, 40).map(
|
|
7685
|
+
(t) => `- ${t.title} \xB7 ${t.status} \xB7 ${t.agent} \xB7 sideboard://thread/${t.id}`
|
|
7686
|
+
);
|
|
7687
|
+
const recent = from.messages.slice(-8).map((m) => {
|
|
7688
|
+
const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
|
|
7689
|
+
const text = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
|
|
7690
|
+
return text ? `- ${role}: ${text}` : null;
|
|
7691
|
+
}).filter(Boolean);
|
|
7692
|
+
const body = [
|
|
7693
|
+
`# Orchestration handoff`,
|
|
7694
|
+
"",
|
|
7695
|
+
`Previous chat: ${from.title} (\`${from.id}\`) on **${from.agent}** hit a session/usage limit.`,
|
|
7696
|
+
`Limit: ${limitText.trim()}`,
|
|
7697
|
+
`Continuing on **${fallbackAgent}** with Auto model.`,
|
|
7698
|
+
"",
|
|
7699
|
+
`## Goal`,
|
|
7700
|
+
from.sourceRef?.trim() || "(none)",
|
|
7701
|
+
"",
|
|
7702
|
+
`## Child threads`,
|
|
7703
|
+
children.length ? children.join("\n") : "(none listed \u2014 call list_threads)",
|
|
7704
|
+
"",
|
|
7705
|
+
`## Recent turns (truncated)`,
|
|
7706
|
+
recent.length ? recent.join("\n") : "(none)",
|
|
7707
|
+
"",
|
|
7708
|
+
`## Instructions`,
|
|
7709
|
+
`- Continue fleet orchestration from this handoff.`,
|
|
7710
|
+
`- Prefer Sideboard MCP (list_threads, get_thread, send_to_thread, \u2026) for live status.`,
|
|
7711
|
+
`- Leave model Auto unless there is a specific reason to pin one.`,
|
|
7712
|
+
`- Do not wait on the limited ${from.agent} account; keep going on ${fallbackAgent}.`
|
|
7713
|
+
].join("\n");
|
|
7714
|
+
return {
|
|
7715
|
+
id: (0, import_node_crypto4.randomUUID)(),
|
|
7716
|
+
name: "Orchestration quota handoff.md",
|
|
7717
|
+
kind: "transcript",
|
|
7718
|
+
content: body
|
|
7719
|
+
};
|
|
7720
|
+
}
|
|
7721
|
+
var QUOTA_CONTINUE_PROMPT = (fromAgent, fallback) => [
|
|
7722
|
+
`${fromAgent} hit a session/usage limit. Continue this orchestration on ${fallback} using the attached handoff.`,
|
|
7723
|
+
"Call list_threads for live fleet status, then proceed with the goal. Leave model Auto unless needed."
|
|
7724
|
+
].join(" ");
|
|
7725
|
+
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.";
|
|
7726
|
+
function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
7727
|
+
const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
|
|
7728
|
+
const tab = createChatTab({
|
|
7729
|
+
fromThreadId: from.id,
|
|
7730
|
+
agent: fallbackAgent,
|
|
7731
|
+
model: null,
|
|
7732
|
+
attachments: [handoff]
|
|
7733
|
+
});
|
|
7734
|
+
return updateThread(tab.id, {
|
|
7735
|
+
parentThreadId: from.id,
|
|
7736
|
+
quotaContinuedFromId: from.id,
|
|
7737
|
+
sourceRef: from.sourceRef,
|
|
7738
|
+
sourceType: "orchestration"
|
|
7739
|
+
});
|
|
7740
|
+
}
|
|
7741
|
+
|
|
7269
7742
|
// src/threads/adopt.ts
|
|
7270
7743
|
var import_node_child_process = require("child_process");
|
|
7271
7744
|
var import_node_fs20 = require("fs");
|
|
@@ -8469,7 +8942,7 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
|
|
|
8469
8942
|
// src/composer/stage-files.ts
|
|
8470
8943
|
var import_node_fs23 = require("fs");
|
|
8471
8944
|
var import_node_path22 = require("path");
|
|
8472
|
-
var
|
|
8945
|
+
var import_node_crypto5 = require("crypto");
|
|
8473
8946
|
var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
8474
8947
|
"png",
|
|
8475
8948
|
"jpg",
|
|
@@ -8525,7 +8998,7 @@ function uniqueAttachmentName(dir, originalName) {
|
|
|
8525
8998
|
const candidate = `${stem}-${i}${ext}`;
|
|
8526
8999
|
if (!(0, import_node_fs23.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
|
|
8527
9000
|
}
|
|
8528
|
-
return `${stem}-${(0,
|
|
9001
|
+
return `${stem}-${(0, import_node_crypto5.randomUUID)()}${ext}`;
|
|
8529
9002
|
}
|
|
8530
9003
|
function previewDataUrlFromBuf(filePath, buf) {
|
|
8531
9004
|
if (!isImageFilePath(filePath)) return void 0;
|
|
@@ -8537,7 +9010,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8537
9010
|
if (isImageFilePath(name)) {
|
|
8538
9011
|
const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
|
|
8539
9012
|
return {
|
|
8540
|
-
id: (0,
|
|
9013
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8541
9014
|
name,
|
|
8542
9015
|
kind: "file",
|
|
8543
9016
|
path: opts.path,
|
|
@@ -8550,7 +9023,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8550
9023
|
}
|
|
8551
9024
|
if (buf.length > MAX_INLINE_BYTES) {
|
|
8552
9025
|
return {
|
|
8553
|
-
id: (0,
|
|
9026
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8554
9027
|
name,
|
|
8555
9028
|
kind: "file",
|
|
8556
9029
|
path: opts.path,
|
|
@@ -8559,7 +9032,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8559
9032
|
}
|
|
8560
9033
|
if (buf.includes(0)) {
|
|
8561
9034
|
return {
|
|
8562
|
-
id: (0,
|
|
9035
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8563
9036
|
name,
|
|
8564
9037
|
kind: "file",
|
|
8565
9038
|
path: opts.path,
|
|
@@ -8567,7 +9040,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8567
9040
|
};
|
|
8568
9041
|
}
|
|
8569
9042
|
return {
|
|
8570
|
-
id: (0,
|
|
9043
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8571
9044
|
name,
|
|
8572
9045
|
kind: "file",
|
|
8573
9046
|
path: opts.path,
|
|
@@ -8591,7 +9064,7 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
8591
9064
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
8592
9065
|
} catch (err) {
|
|
8593
9066
|
out.push({
|
|
8594
|
-
id: (0,
|
|
9067
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8595
9068
|
name: originalName,
|
|
8596
9069
|
kind: "file",
|
|
8597
9070
|
content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -8615,7 +9088,7 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
|
|
|
8615
9088
|
out.push(attachmentFromBuffer(name, buf, { path: rel }));
|
|
8616
9089
|
} catch (err) {
|
|
8617
9090
|
out.push({
|
|
8618
|
-
id: (0,
|
|
9091
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8619
9092
|
name: originalName,
|
|
8620
9093
|
kind: "file",
|
|
8621
9094
|
content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -8629,7 +9102,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
8629
9102
|
for (const rel of relativePaths) {
|
|
8630
9103
|
if (!rel || rel.includes("..") || rel.startsWith("/")) {
|
|
8631
9104
|
out.push({
|
|
8632
|
-
id: (0,
|
|
9105
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8633
9106
|
name: (0, import_node_path22.basename)(rel) || "file",
|
|
8634
9107
|
kind: "file",
|
|
8635
9108
|
content: `(invalid path: ${rel})`
|
|
@@ -8645,7 +9118,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
8645
9118
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
8646
9119
|
} catch (err) {
|
|
8647
9120
|
out.push({
|
|
8648
|
-
id: (0,
|
|
9121
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8649
9122
|
name,
|
|
8650
9123
|
kind: "file",
|
|
8651
9124
|
content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -8901,6 +9374,8 @@ var Orchestrator = class {
|
|
|
8901
9374
|
haltDrain = /* @__PURE__ */ new Set();
|
|
8902
9375
|
/** WIP snapshot SHA at the start of the latest agent turn (per thread). */
|
|
8903
9376
|
turnBaselines = /* @__PURE__ */ new Map();
|
|
9377
|
+
/** Timers for orchestration session-quota auto-resume. */
|
|
9378
|
+
quotaResumeTimers = /* @__PURE__ */ new Map();
|
|
8904
9379
|
maxConcurrent;
|
|
8905
9380
|
runningCount = 0;
|
|
8906
9381
|
constructor(opts) {
|
|
@@ -8980,6 +9455,113 @@ var Orchestrator = class {
|
|
|
8980
9455
|
void this.drainQueue(thread.id);
|
|
8981
9456
|
}
|
|
8982
9457
|
}
|
|
9458
|
+
this.schedulePendingQuotaResumes();
|
|
9459
|
+
}
|
|
9460
|
+
clearQuotaResumeTimer(threadId) {
|
|
9461
|
+
const timer = this.quotaResumeTimers.get(threadId);
|
|
9462
|
+
if (timer) clearTimeout(timer);
|
|
9463
|
+
this.quotaResumeTimers.delete(threadId);
|
|
9464
|
+
}
|
|
9465
|
+
/** Schedule (or fire) auto-retry after a provider session/usage limit reset. */
|
|
9466
|
+
scheduleQuotaResume(threadId, resumeAt) {
|
|
9467
|
+
this.clearQuotaResumeTimer(threadId);
|
|
9468
|
+
updateThread(threadId, { quotaResumeAt: resumeAt.toISOString() });
|
|
9469
|
+
const delay = Math.max(5e3, resumeAt.getTime() - Date.now());
|
|
9470
|
+
const capped = Math.min(delay, 2147483647);
|
|
9471
|
+
const timer = setTimeout(() => {
|
|
9472
|
+
this.quotaResumeTimers.delete(threadId);
|
|
9473
|
+
void this.resumeAfterQuotaWait(threadId);
|
|
9474
|
+
}, capped);
|
|
9475
|
+
this.quotaResumeTimers.set(threadId, timer);
|
|
9476
|
+
}
|
|
9477
|
+
schedulePendingQuotaResumes() {
|
|
9478
|
+
for (const thread of listThreads({ includeArchived: false })) {
|
|
9479
|
+
if (!thread.quotaResumeAt) continue;
|
|
9480
|
+
const at = new Date(thread.quotaResumeAt);
|
|
9481
|
+
if (Number.isNaN(at.getTime())) continue;
|
|
9482
|
+
if (at.getTime() <= Date.now()) {
|
|
9483
|
+
void this.resumeAfterQuotaWait(thread.id);
|
|
9484
|
+
} else if (!this.quotaResumeTimers.has(thread.id)) {
|
|
9485
|
+
this.scheduleQuotaResume(thread.id, at);
|
|
9486
|
+
}
|
|
9487
|
+
}
|
|
9488
|
+
}
|
|
9489
|
+
async resumeAfterQuotaWait(threadId) {
|
|
9490
|
+
const thread = findThreadByRef(threadId);
|
|
9491
|
+
if (!thread || thread.status === "archived") return;
|
|
9492
|
+
this.clearQuotaResumeTimer(threadId);
|
|
9493
|
+
try {
|
|
9494
|
+
updateThread(threadId, { quotaResumeAt: null });
|
|
9495
|
+
} catch {
|
|
9496
|
+
return;
|
|
9497
|
+
}
|
|
9498
|
+
if (thread.status === "running" || this.activeTurns.has(threadId) || this.startingTurns.has(threadId)) {
|
|
9499
|
+
return;
|
|
9500
|
+
}
|
|
9501
|
+
await this.send(threadId, QUOTA_RESUME_PROMPT);
|
|
9502
|
+
}
|
|
9503
|
+
/**
|
|
9504
|
+
* Host-side continue when an orchestration chat hits a provider session/usage
|
|
9505
|
+
* limit (not context size): switch agent (Auto) or wait until reset.
|
|
9506
|
+
*/
|
|
9507
|
+
async maybeHandleOrchestrationQuotaFailover(threadId, limitText) {
|
|
9508
|
+
const thread = findThreadByRef(threadId);
|
|
9509
|
+
if (!thread) return;
|
|
9510
|
+
const plan = planOrchestrationQuotaFailover(thread, limitText);
|
|
9511
|
+
if (!plan || plan.action === "none") return;
|
|
9512
|
+
if (plan.action === "wait_reset" && plan.resumeAt) {
|
|
9513
|
+
this.haltDrain.add(threadId);
|
|
9514
|
+
this.scheduleQuotaResume(threadId, plan.resumeAt);
|
|
9515
|
+
setStatus(threadId, "idle", null);
|
|
9516
|
+
appendMessage(threadId, {
|
|
9517
|
+
role: "agent",
|
|
9518
|
+
text: `Sideboard will auto-retry this orchestration around ${plan.resumeAt.toLocaleString()} when the session limit resets.`,
|
|
9519
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
9520
|
+
});
|
|
9521
|
+
this.emit({
|
|
9522
|
+
type: "quota_failover",
|
|
9523
|
+
threadId,
|
|
9524
|
+
action: "wait_reset",
|
|
9525
|
+
message: plan.reason,
|
|
9526
|
+
resumeAt: plan.resumeAt.toISOString()
|
|
9527
|
+
});
|
|
9528
|
+
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
9529
|
+
return;
|
|
9530
|
+
}
|
|
9531
|
+
if (plan.action === "switch_agent" && plan.fallbackAgent) {
|
|
9532
|
+
this.haltDrain.add(threadId);
|
|
9533
|
+
const next = createQuotaFailoverChat(
|
|
9534
|
+
thread,
|
|
9535
|
+
plan.fallbackAgent,
|
|
9536
|
+
plan.limitText
|
|
9537
|
+
);
|
|
9538
|
+
this.clearQuotaResumeTimer(threadId);
|
|
9539
|
+
try {
|
|
9540
|
+
updateThread(threadId, { quotaResumeAt: null });
|
|
9541
|
+
} catch {
|
|
9542
|
+
}
|
|
9543
|
+
appendMessage(threadId, {
|
|
9544
|
+
role: "agent",
|
|
9545
|
+
text: `Session limit on ${thread.agent}. Sideboard continued on ${plan.fallbackAgent} (Auto) in [${next.title}](sideboard://thread/${next.id}).`,
|
|
9546
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
9547
|
+
});
|
|
9548
|
+
this.emit({
|
|
9549
|
+
type: "quota_failover",
|
|
9550
|
+
threadId,
|
|
9551
|
+
action: "switch_agent",
|
|
9552
|
+
toThreadId: next.id,
|
|
9553
|
+
message: plan.reason
|
|
9554
|
+
});
|
|
9555
|
+
this.emit({
|
|
9556
|
+
type: "status_changed",
|
|
9557
|
+
threadId: next.id,
|
|
9558
|
+
status: next.status
|
|
9559
|
+
});
|
|
9560
|
+
await this.send(
|
|
9561
|
+
next.id,
|
|
9562
|
+
QUOTA_CONTINUE_PROMPT(thread.agent, plan.fallbackAgent)
|
|
9563
|
+
);
|
|
9564
|
+
}
|
|
8983
9565
|
}
|
|
8984
9566
|
getThreads(includeArchived = false) {
|
|
8985
9567
|
return listThreads({ includeArchived });
|
|
@@ -9338,11 +9920,16 @@ var Orchestrator = class {
|
|
|
9338
9920
|
}
|
|
9339
9921
|
}
|
|
9340
9922
|
}
|
|
9341
|
-
const
|
|
9342
|
-
|
|
9923
|
+
const lastStderr = summarizeTurnStderr(stderrTail);
|
|
9924
|
+
const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
9925
|
+
let chatText = assistantText;
|
|
9926
|
+
if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
|
|
9927
|
+
chatText = humanizeAgentFailDetail(detail);
|
|
9928
|
+
}
|
|
9929
|
+
if (chatText || parts.length > 0) {
|
|
9343
9930
|
appendMessage(threadId, {
|
|
9344
9931
|
role: "agent",
|
|
9345
|
-
text:
|
|
9932
|
+
text: chatText,
|
|
9346
9933
|
parts: parts.length > 0 ? parts : void 0,
|
|
9347
9934
|
durationMs: Math.max(0, Date.now() - turnStartedAt),
|
|
9348
9935
|
usage,
|
|
@@ -9361,13 +9948,12 @@ var Orchestrator = class {
|
|
|
9361
9948
|
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
9362
9949
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
9363
9950
|
} else {
|
|
9364
|
-
const lastStderr = summarizeTurnStderr(stderrTail);
|
|
9365
|
-
const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
9366
9951
|
const failDetail = formatTurnExitError(exitCode, detail);
|
|
9952
|
+
const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
|
|
9367
9953
|
setStatus(
|
|
9368
9954
|
threadId,
|
|
9369
9955
|
exitCode === 0 ? "idle" : "error",
|
|
9370
|
-
exitCode === 0 ? null : failDetail
|
|
9956
|
+
exitCode === 0 || explainedInChat ? null : failDetail
|
|
9371
9957
|
);
|
|
9372
9958
|
this.emit({
|
|
9373
9959
|
type: "status_changed",
|
|
@@ -9375,6 +9961,10 @@ var Orchestrator = class {
|
|
|
9375
9961
|
status: exitCode === 0 ? "idle" : "error"
|
|
9376
9962
|
});
|
|
9377
9963
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
9964
|
+
if (exitCode !== 0) {
|
|
9965
|
+
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
9966
|
+
void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
|
|
9967
|
+
}
|
|
9378
9968
|
}
|
|
9379
9969
|
} catch (err) {
|
|
9380
9970
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -9388,6 +9978,7 @@ var Orchestrator = class {
|
|
|
9388
9978
|
this.emit({ type: "error", threadId, message });
|
|
9389
9979
|
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
9390
9980
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
9981
|
+
void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
|
|
9391
9982
|
}
|
|
9392
9983
|
} finally {
|
|
9393
9984
|
this.startingTurns.delete(threadId);
|
|
@@ -9872,6 +10463,9 @@ var Orchestrator = class {
|
|
|
9872
10463
|
`Cannot switch agent provider mid-chat (${thread.agent} \u2192 ${patch.agent}). Start a new chat tab instead.`
|
|
9873
10464
|
);
|
|
9874
10465
|
}
|
|
10466
|
+
if (isOrchestratorThread(thread)) {
|
|
10467
|
+
assertOrchestratorCapableAgent(patch.agent);
|
|
10468
|
+
}
|
|
9875
10469
|
next.agent = patch.agent;
|
|
9876
10470
|
if (patch.agent !== "claude" && patch.model === void 0) next.model = null;
|
|
9877
10471
|
next.sessionId = null;
|
|
@@ -10154,6 +10748,7 @@ async function listIssues(repoPath) {
|
|
|
10154
10748
|
|
|
10155
10749
|
// src/mcp/server.ts
|
|
10156
10750
|
init_global_workspace();
|
|
10751
|
+
init_list_models();
|
|
10157
10752
|
|
|
10158
10753
|
// src/mcp/archive-guard.ts
|
|
10159
10754
|
init_global_workspace();
|
|
@@ -10570,6 +11165,120 @@ async function startMcpServer() {
|
|
|
10570
11165
|
}
|
|
10571
11166
|
}
|
|
10572
11167
|
);
|
|
11168
|
+
const agentEnum = import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
11169
|
+
server.tool(
|
|
11170
|
+
"list_models",
|
|
11171
|
+
"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.",
|
|
11172
|
+
{
|
|
11173
|
+
agent: agentEnum.optional().describe("Limit to one agent; omit for all")
|
|
11174
|
+
},
|
|
11175
|
+
async ({ agent }) => {
|
|
11176
|
+
try {
|
|
11177
|
+
const catalogs = await listModelsForAgent(agent);
|
|
11178
|
+
return {
|
|
11179
|
+
content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
|
|
11180
|
+
};
|
|
11181
|
+
} catch (err) {
|
|
11182
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11183
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
11184
|
+
}
|
|
11185
|
+
}
|
|
11186
|
+
);
|
|
11187
|
+
server.tool(
|
|
11188
|
+
"fork_worktree",
|
|
11189
|
+
"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.",
|
|
11190
|
+
{
|
|
11191
|
+
ref: import_zod.z.string().describe("Worktree thread id/ref to fork"),
|
|
11192
|
+
through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
11193
|
+
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
11194
|
+
model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
11195
|
+
title: import_zod.z.string().optional()
|
|
11196
|
+
},
|
|
11197
|
+
async ({ ref, through_index, agent, model, title }) => {
|
|
11198
|
+
try {
|
|
11199
|
+
const source = orch.getThread(ref);
|
|
11200
|
+
if (source) await orch.reconcile(source.repoPath);
|
|
11201
|
+
const thread = await orch.forkThreadWorktree({
|
|
11202
|
+
threadId: ref,
|
|
11203
|
+
throughIndex: through_index,
|
|
11204
|
+
agent,
|
|
11205
|
+
model,
|
|
11206
|
+
title
|
|
11207
|
+
});
|
|
11208
|
+
return {
|
|
11209
|
+
content: [
|
|
11210
|
+
{
|
|
11211
|
+
type: "text",
|
|
11212
|
+
text: JSON.stringify({
|
|
11213
|
+
id: thread.id,
|
|
11214
|
+
title: thread.title,
|
|
11215
|
+
status: thread.status,
|
|
11216
|
+
agent: thread.agent,
|
|
11217
|
+
model: thread.model,
|
|
11218
|
+
branchName: thread.branchName,
|
|
11219
|
+
worktreePath: thread.worktreePath,
|
|
11220
|
+
fromThreadId: source?.id ?? ref,
|
|
11221
|
+
link: `sideboard://thread/${thread.id}`
|
|
11222
|
+
})
|
|
11223
|
+
}
|
|
11224
|
+
]
|
|
11225
|
+
};
|
|
11226
|
+
} catch (err) {
|
|
11227
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11228
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
11229
|
+
}
|
|
11230
|
+
}
|
|
11231
|
+
);
|
|
11232
|
+
server.tool(
|
|
11233
|
+
"fork_chat",
|
|
11234
|
+
"Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). 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.",
|
|
11235
|
+
{
|
|
11236
|
+
ref: import_zod.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
11237
|
+
through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
11238
|
+
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
11239
|
+
model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
11240
|
+
title: import_zod.z.string().optional()
|
|
11241
|
+
},
|
|
11242
|
+
async ({ ref, through_index, agent, model, title }) => {
|
|
11243
|
+
try {
|
|
11244
|
+
const source = orch.getThread(ref);
|
|
11245
|
+
if (!source) {
|
|
11246
|
+
return {
|
|
11247
|
+
content: [{ type: "text", text: `Thread not found: ${ref}` }],
|
|
11248
|
+
isError: true
|
|
11249
|
+
};
|
|
11250
|
+
}
|
|
11251
|
+
const tab = orch.forkChatTab({
|
|
11252
|
+
threadId: source.id,
|
|
11253
|
+
throughIndex: through_index,
|
|
11254
|
+
agent,
|
|
11255
|
+
model,
|
|
11256
|
+
title
|
|
11257
|
+
});
|
|
11258
|
+
return {
|
|
11259
|
+
content: [
|
|
11260
|
+
{
|
|
11261
|
+
type: "text",
|
|
11262
|
+
text: JSON.stringify({
|
|
11263
|
+
id: tab.id,
|
|
11264
|
+
title: tab.title,
|
|
11265
|
+
status: tab.status,
|
|
11266
|
+
agent: tab.agent,
|
|
11267
|
+
model: tab.model,
|
|
11268
|
+
sourceType: tab.sourceType,
|
|
11269
|
+
worktreePath: tab.worktreePath,
|
|
11270
|
+
fromThreadId: source.id,
|
|
11271
|
+
link: `sideboard://thread/${tab.id}`
|
|
11272
|
+
})
|
|
11273
|
+
}
|
|
11274
|
+
]
|
|
11275
|
+
};
|
|
11276
|
+
} catch (err) {
|
|
11277
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11278
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
11279
|
+
}
|
|
11280
|
+
}
|
|
11281
|
+
);
|
|
10573
11282
|
server.tool(
|
|
10574
11283
|
"run_dev_script",
|
|
10575
11284
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|