@sideboard-ai/core 0.1.45 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/cursor-runner.cjs +11 -4
- package/dist/agents/cursor-runner.js +11 -4
- package/dist/{agents-JSHCAZUZ.js → agents-3P4N6KHC.js} +12 -2
- package/dist/agents-LUB3Q773.js +82 -0
- package/dist/app-settings-7XVDQJ7F.js +80 -0
- package/dist/{app-settings-LYGVDGZY.js → app-settings-LZP632KI.js} +1 -1
- package/dist/{chunk-U3EQKJHA.js → chunk-3NAS4MWH.js} +1 -1
- package/dist/{chunk-ZNSM2DDD.js → chunk-5WYEJ3F3.js} +6 -2
- package/dist/chunk-7MV3RXSC.js +277 -0
- package/dist/chunk-DF5VQQKA.js +191 -0
- package/dist/chunk-E35APBHV.js +269 -0
- package/dist/chunk-EOYDCKQC.js +172 -0
- package/dist/chunk-GI7E5733.js +30 -0
- package/dist/chunk-GZXBYHJC.js +148 -0
- package/dist/chunk-HBJSHRY2.js +494 -0
- package/dist/chunk-ISY4EGF6.js +77 -0
- package/dist/chunk-KGZBYWZ3.js +23 -0
- package/dist/chunk-RQI4TOXK.js +2232 -0
- package/dist/chunk-S2LL5HA4.js +33 -0
- package/dist/{chunk-ANZ566Z5.js → chunk-SS34TVSY.js} +66 -7
- package/dist/{chunk-WYY3J7GR.js → chunk-T5QQVXK3.js} +6 -3
- package/dist/chunk-V4JRXYYU.js +122 -0
- package/dist/{chunk-6NAPN2N5.js → chunk-ZQCQIWIP.js} +1 -1
- package/dist/chunk-ZVV5EEQN.js +1892 -0
- package/dist/connected-teams-UBXHZGO4.js +24 -0
- package/dist/{coordinator-prompt-2XFYG3C5.js → coordinator-prompt-SPGDT7J5.js} +1 -1
- package/dist/coordinator-prompt-VG4BZ5JL.js +25 -0
- package/dist/cursor-recover-NNK7JPQM.js +44 -0
- package/dist/{global-workspace-YFOQUGWD.js → global-workspace-KSR3E63K.js} +3 -2
- package/dist/global-workspace-OJF4ENH4.js +40 -0
- package/dist/index.cjs +377 -30
- package/dist/index.d.cts +70 -11
- package/dist/index.d.ts +70 -11
- package/dist/index.js +5779 -193
- package/dist/mcp/run-stdio.cjs +331 -32
- package/dist/mcp/run-stdio.js +5618 -14
- package/dist/paths-2OFB7UJG.js +28 -0
- package/dist/run-HMRSRG3U.js +12 -0
- package/dist/thread-store-XICUWFNM.js +32 -0
- package/dist/title-4WAKWZRF.js +27 -0
- package/dist/workspaces-OZE7LRDO.js +24 -0
- package/dist/{workspaces-TIKLNDW3.js → workspaces-UJX7JIGH.js} +4 -3
- package/dist/worktree-XG5PCLZY.js +94 -0
- package/package.json +2 -2
- package/dist/chunk-I6QGZOOS.js +0 -5667
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
2
3
|
"use strict";
|
|
3
4
|
var __create = Object.create;
|
|
4
5
|
var __defProp = Object.defineProperty;
|
|
@@ -2753,7 +2754,7 @@ function normalizeAdvanced(raw) {
|
|
|
2753
2754
|
if (source.orchestrationQuotaOnLimit === "switch_agent" || source.orchestrationQuotaOnLimit === "wait_reset") {
|
|
2754
2755
|
out.orchestrationQuotaOnLimit = source.orchestrationQuotaOnLimit;
|
|
2755
2756
|
}
|
|
2756
|
-
if (typeof source.orchestrationQuotaFallbackAgent === "string" && DEFAULT_AGENTS.has(source.orchestrationQuotaFallbackAgent)) {
|
|
2757
|
+
if (typeof source.orchestrationQuotaFallbackAgent === "string" && DEFAULT_AGENTS.has(source.orchestrationQuotaFallbackAgent) && source.orchestrationQuotaFallbackAgent !== "brightsy") {
|
|
2757
2758
|
out.orchestrationQuotaFallbackAgent = source.orchestrationQuotaFallbackAgent;
|
|
2758
2759
|
}
|
|
2759
2760
|
return out;
|
|
@@ -2993,7 +2994,7 @@ function updateAdvancedSettings(patch) {
|
|
|
2993
2994
|
if (patch.orchestrationQuotaOnLimit === "switch_agent" || patch.orchestrationQuotaOnLimit === "wait_reset") {
|
|
2994
2995
|
advanced.orchestrationQuotaOnLimit = patch.orchestrationQuotaOnLimit;
|
|
2995
2996
|
}
|
|
2996
|
-
if (typeof patch.orchestrationQuotaFallbackAgent === "string" && DEFAULT_AGENTS.has(patch.orchestrationQuotaFallbackAgent)) {
|
|
2997
|
+
if (typeof patch.orchestrationQuotaFallbackAgent === "string" && DEFAULT_AGENTS.has(patch.orchestrationQuotaFallbackAgent) && patch.orchestrationQuotaFallbackAgent !== "brightsy") {
|
|
2997
2998
|
advanced.orchestrationQuotaFallbackAgent = patch.orchestrationQuotaFallbackAgent;
|
|
2998
2999
|
}
|
|
2999
3000
|
return saveAppSettings({ ...current, advanced });
|
|
@@ -3021,7 +3022,10 @@ function orchestrationQuotaOnLimit(settings = loadAppSettings()) {
|
|
|
3021
3022
|
}
|
|
3022
3023
|
function orchestrationQuotaFallbackAgent(settings = loadAppSettings()) {
|
|
3023
3024
|
const preferred = settings.advanced.orchestrationQuotaFallbackAgent;
|
|
3024
|
-
|
|
3025
|
+
if (preferred && DEFAULT_AGENTS.has(preferred) && preferred !== "brightsy") {
|
|
3026
|
+
return preferred;
|
|
3027
|
+
}
|
|
3028
|
+
return "cursor";
|
|
3025
3029
|
}
|
|
3026
3030
|
function maxConcurrentAgents(settings = loadAppSettings()) {
|
|
3027
3031
|
const n = settings.advanced.maxConcurrent;
|
|
@@ -3129,6 +3133,36 @@ var init_cloud_connect_constants = __esm({
|
|
|
3129
3133
|
}
|
|
3130
3134
|
});
|
|
3131
3135
|
|
|
3136
|
+
// src/agents/orchestrator-capable.ts
|
|
3137
|
+
function isOrchestratorCapableAgent(agent) {
|
|
3138
|
+
return Boolean(
|
|
3139
|
+
agent && ORCHESTRATOR_AGENT_KINDS.includes(agent)
|
|
3140
|
+
);
|
|
3141
|
+
}
|
|
3142
|
+
function assertOrchestratorCapableAgent(agent, context = "orchestration") {
|
|
3143
|
+
if (!isOrchestratorCapableAgent(agent)) {
|
|
3144
|
+
throw new Error(
|
|
3145
|
+
`${agent} cannot run ${context} \u2014 it does not support Sideboard MCP. Use Claude, Cursor, Codex, or OpenCode.`
|
|
3146
|
+
);
|
|
3147
|
+
}
|
|
3148
|
+
return agent;
|
|
3149
|
+
}
|
|
3150
|
+
function coerceOrchestratorAgent(agent, fallback = "claude") {
|
|
3151
|
+
return isOrchestratorCapableAgent(agent) ? agent : fallback;
|
|
3152
|
+
}
|
|
3153
|
+
var ORCHESTRATOR_AGENT_KINDS;
|
|
3154
|
+
var init_orchestrator_capable = __esm({
|
|
3155
|
+
"src/agents/orchestrator-capable.ts"() {
|
|
3156
|
+
"use strict";
|
|
3157
|
+
ORCHESTRATOR_AGENT_KINDS = [
|
|
3158
|
+
"claude",
|
|
3159
|
+
"codex",
|
|
3160
|
+
"opencode",
|
|
3161
|
+
"cursor"
|
|
3162
|
+
];
|
|
3163
|
+
}
|
|
3164
|
+
});
|
|
3165
|
+
|
|
3132
3166
|
// src/orchestrator/coordinator-prompt.ts
|
|
3133
3167
|
var coordinator_prompt_exports = {};
|
|
3134
3168
|
__export(coordinator_prompt_exports, {
|
|
@@ -3267,7 +3301,7 @@ var init_coordinator_prompt = __esm({
|
|
|
3267
3301
|
"- list_run_scripts / run_dev_script / stop_dev_script \u2014 start/stop named run scripts",
|
|
3268
3302
|
"Inspect / review / PRs:",
|
|
3269
3303
|
"- get_diff \u2014 compact diff summary",
|
|
3270
|
-
|
|
3304
|
+
'- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review."); then wait_for_turn / get_turn_result on the returned id',
|
|
3271
3305
|
"- Ask the worktree agent via send_to_thread to open a draft PR with `gh pr create --draft -R <origin-owner/name>` (workspace `github:` slug / that worktree's origin \u2014 never upstream). Do not open PRs from the orchestrator yourself.",
|
|
3272
3306
|
"Human-only (do not attempt): merge, ready-for-review land, purge_thread.",
|
|
3273
3307
|
"Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
|
|
@@ -3355,6 +3389,7 @@ function createGlobalChat(opts) {
|
|
|
3355
3389
|
const explicit = opts.title?.trim();
|
|
3356
3390
|
const title = explicit && explicit !== CLOUD_ORCHESTRATOR_GOAL ? explicit : allocateTeamName(takenTeamSlugsForOrchestration()).name;
|
|
3357
3391
|
const sourceRef = opts.sourceRef?.trim() || (isCloud ? CLOUD_ORCHESTRATOR_GOAL : title);
|
|
3392
|
+
const agent = assertOrchestratorCapableAgent(opts.agent);
|
|
3358
3393
|
const thread = createEmptyThread({
|
|
3359
3394
|
title,
|
|
3360
3395
|
// Stick nicknames the same way chat tabs do (avoid later sync overwrites).
|
|
@@ -3364,7 +3399,7 @@ function createGlobalChat(opts) {
|
|
|
3364
3399
|
branchName: "global",
|
|
3365
3400
|
worktreePath: globalAgentCwd(),
|
|
3366
3401
|
repoPath: GLOBAL_WORKSPACE_ID,
|
|
3367
|
-
agent
|
|
3402
|
+
agent,
|
|
3368
3403
|
autonomy: opts.autonomy ?? "default",
|
|
3369
3404
|
model: opts.model ?? null,
|
|
3370
3405
|
effort: opts.effort ?? "high",
|
|
@@ -3426,6 +3461,7 @@ var init_global_workspace = __esm({
|
|
|
3426
3461
|
"src/store/global-workspace.ts"() {
|
|
3427
3462
|
"use strict";
|
|
3428
3463
|
init_cloud_connect_constants();
|
|
3464
|
+
init_orchestrator_capable();
|
|
3429
3465
|
init_teams();
|
|
3430
3466
|
init_coordinator_prompt();
|
|
3431
3467
|
init_paths();
|
|
@@ -4158,6 +4194,45 @@ async function buildInjectedMcpServers(opts) {
|
|
|
4158
4194
|
}
|
|
4159
4195
|
return servers;
|
|
4160
4196
|
}
|
|
4197
|
+
function toCursorMcpServers(servers) {
|
|
4198
|
+
const out = {};
|
|
4199
|
+
for (const s of servers) {
|
|
4200
|
+
out[s.name] = {
|
|
4201
|
+
command: s.command,
|
|
4202
|
+
...s.args ? { args: s.args } : {},
|
|
4203
|
+
...s.env ? { env: s.env } : {}
|
|
4204
|
+
};
|
|
4205
|
+
}
|
|
4206
|
+
return out;
|
|
4207
|
+
}
|
|
4208
|
+
function toCodexMcpConfigArgs(servers) {
|
|
4209
|
+
const args = [];
|
|
4210
|
+
for (const s of servers) {
|
|
4211
|
+
const prefix = `mcp_servers.${s.name}`;
|
|
4212
|
+
args.push("-c", `${prefix}.command=${JSON.stringify(s.command)}`);
|
|
4213
|
+
if (s.args?.length) {
|
|
4214
|
+
args.push("-c", `${prefix}.args=${JSON.stringify(s.args)}`);
|
|
4215
|
+
}
|
|
4216
|
+
if (s.env) {
|
|
4217
|
+
for (const [key, value] of Object.entries(s.env)) {
|
|
4218
|
+
args.push("-c", `${prefix}.env.${key}=${JSON.stringify(value)}`);
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
return args;
|
|
4223
|
+
}
|
|
4224
|
+
function toOpencodeMcpConfigContent(servers) {
|
|
4225
|
+
const mcp = {};
|
|
4226
|
+
for (const s of servers) {
|
|
4227
|
+
mcp[s.name] = {
|
|
4228
|
+
type: "local",
|
|
4229
|
+
command: [s.command, ...s.args ?? []],
|
|
4230
|
+
enabled: true,
|
|
4231
|
+
...s.env && Object.keys(s.env).length > 0 ? { environment: s.env } : {}
|
|
4232
|
+
};
|
|
4233
|
+
}
|
|
4234
|
+
return JSON.stringify({ mcp });
|
|
4235
|
+
}
|
|
4161
4236
|
function writeMcpServersConfig(servers) {
|
|
4162
4237
|
if (servers.length === 0) return null;
|
|
4163
4238
|
const mcpServers = {};
|
|
@@ -4658,6 +4733,7 @@ var init_codex = __esm({
|
|
|
4658
4733
|
import_node_path11 = require("path");
|
|
4659
4734
|
init_run();
|
|
4660
4735
|
init_error_detail();
|
|
4736
|
+
init_injected_mcp();
|
|
4661
4737
|
init_turn_input();
|
|
4662
4738
|
init_types();
|
|
4663
4739
|
CODEX_PROMPT_ARG_MAX = 2e5;
|
|
@@ -4716,6 +4792,11 @@ var init_codex = __esm({
|
|
|
4716
4792
|
}
|
|
4717
4793
|
const mode = permissionMode(thread);
|
|
4718
4794
|
const model = thread.model?.trim();
|
|
4795
|
+
const injected = await buildInjectedMcpServers({
|
|
4796
|
+
includeSideboard: true,
|
|
4797
|
+
includeBrightsy: isBrightsyConnected()
|
|
4798
|
+
});
|
|
4799
|
+
const mcpOverrides = toCodexMcpConfigArgs(injected);
|
|
4719
4800
|
const args = [
|
|
4720
4801
|
"exec",
|
|
4721
4802
|
...sessionId ? ["resume", sessionId] : [],
|
|
@@ -4727,7 +4808,8 @@ var init_codex = __esm({
|
|
|
4727
4808
|
mode.codexSandbox,
|
|
4728
4809
|
"--ask-for-approval",
|
|
4729
4810
|
"never",
|
|
4730
|
-
...model ? ["--model", model] : []
|
|
4811
|
+
...model ? ["--model", model] : [],
|
|
4812
|
+
...mcpOverrides
|
|
4731
4813
|
];
|
|
4732
4814
|
return {
|
|
4733
4815
|
file: "codex",
|
|
@@ -5012,6 +5094,7 @@ var init_cursor = __esm({
|
|
|
5012
5094
|
init_run();
|
|
5013
5095
|
init_app_settings();
|
|
5014
5096
|
init_cursor_events();
|
|
5097
|
+
init_injected_mcp();
|
|
5015
5098
|
init_node_launch();
|
|
5016
5099
|
init_turn_input();
|
|
5017
5100
|
init_cursor_events();
|
|
@@ -5062,6 +5145,11 @@ var init_cursor = __esm({
|
|
|
5062
5145
|
const prompt = flattenTurnInput(input);
|
|
5063
5146
|
const agentId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
|
|
5064
5147
|
const apiKey = resolveCursorApiKey() || void 0;
|
|
5148
|
+
const injected = await buildInjectedMcpServers({
|
|
5149
|
+
includeSideboard: true,
|
|
5150
|
+
includeBrightsy: isBrightsyConnected()
|
|
5151
|
+
});
|
|
5152
|
+
const mcpServers = toCursorMcpServers(injected);
|
|
5065
5153
|
const req = {
|
|
5066
5154
|
prompt,
|
|
5067
5155
|
cwd: thread.worktreePath,
|
|
@@ -5070,7 +5158,8 @@ var init_cursor = __esm({
|
|
|
5070
5158
|
effort: thread.effort,
|
|
5071
5159
|
fast: thread.fast,
|
|
5072
5160
|
planMode: thread.planMode,
|
|
5073
|
-
apiKey
|
|
5161
|
+
apiKey,
|
|
5162
|
+
...Object.keys(mcpServers).length > 0 ? { mcpServers } : {}
|
|
5074
5163
|
};
|
|
5075
5164
|
const runner = cursorRunnerPath();
|
|
5076
5165
|
const isTs = runner.endsWith(".ts");
|
|
@@ -5164,6 +5253,7 @@ var init_opencode = __esm({
|
|
|
5164
5253
|
"use strict";
|
|
5165
5254
|
init_run();
|
|
5166
5255
|
init_error_detail();
|
|
5256
|
+
init_injected_mcp();
|
|
5167
5257
|
init_turn_input();
|
|
5168
5258
|
init_types();
|
|
5169
5259
|
FALLBACK_OPENCODE_MODELS = [
|
|
@@ -5224,6 +5314,11 @@ var init_opencode = __esm({
|
|
|
5224
5314
|
if (model) {
|
|
5225
5315
|
args.push("--model", model);
|
|
5226
5316
|
}
|
|
5317
|
+
const injected = await buildInjectedMcpServers({
|
|
5318
|
+
includeSideboard: true,
|
|
5319
|
+
includeBrightsy: isBrightsyConnected()
|
|
5320
|
+
});
|
|
5321
|
+
const mcpContent = injected.length > 0 ? toOpencodeMcpConfigContent(injected) : null;
|
|
5227
5322
|
return {
|
|
5228
5323
|
file: "opencode",
|
|
5229
5324
|
args,
|
|
@@ -5232,7 +5327,8 @@ var init_opencode = __esm({
|
|
|
5232
5327
|
// message is given (see resolveRunInput in opencode's run.ts).
|
|
5233
5328
|
stdin: prompt,
|
|
5234
5329
|
env: {
|
|
5235
|
-
OPENCODE_PERMISSION: mode.opencodePermission
|
|
5330
|
+
OPENCODE_PERMISSION: mode.opencodePermission,
|
|
5331
|
+
...mcpContent ? { OPENCODE_CONFIG_CONTENT: mcpContent } : {}
|
|
5236
5332
|
}
|
|
5237
5333
|
};
|
|
5238
5334
|
},
|
|
@@ -5528,7 +5624,10 @@ function zonedWallTimeToUtc(day, hour, minute, timeZone) {
|
|
|
5528
5624
|
}
|
|
5529
5625
|
}
|
|
5530
5626
|
function resolveQuotaFallbackAgent(current, preferred) {
|
|
5531
|
-
const ordered =
|
|
5627
|
+
const ordered = [
|
|
5628
|
+
...preferred && preferred !== "brightsy" ? [preferred] : [],
|
|
5629
|
+
...FALLBACK_ORDER.filter((a) => a !== preferred)
|
|
5630
|
+
];
|
|
5532
5631
|
return ordered.find((a) => a !== current) ?? (current === "cursor" ? "codex" : "cursor");
|
|
5533
5632
|
}
|
|
5534
5633
|
var FALLBACK_ORDER;
|
|
@@ -5539,7 +5638,6 @@ var init_session_quota = __esm({
|
|
|
5539
5638
|
"cursor",
|
|
5540
5639
|
"codex",
|
|
5541
5640
|
"opencode",
|
|
5542
|
-
"brightsy",
|
|
5543
5641
|
"claude"
|
|
5544
5642
|
];
|
|
5545
5643
|
}
|
|
@@ -5726,11 +5824,14 @@ var init_install = __esm({
|
|
|
5726
5824
|
var agents_exports = {};
|
|
5727
5825
|
__export(agents_exports, {
|
|
5728
5826
|
CLAUDE_MODEL_CATALOG: () => CLAUDE_MODEL_CATALOG,
|
|
5827
|
+
ORCHESTRATOR_AGENT_KINDS: () => ORCHESTRATOR_AGENT_KINDS,
|
|
5729
5828
|
PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
|
|
5730
5829
|
allAdapters: () => allAdapters,
|
|
5830
|
+
assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
|
|
5731
5831
|
brightsyAdapter: () => brightsyAdapter,
|
|
5732
5832
|
claudeAdapter: () => claudeAdapter,
|
|
5733
5833
|
codexAdapter: () => codexAdapter,
|
|
5834
|
+
coerceOrchestratorAgent: () => coerceOrchestratorAgent,
|
|
5734
5835
|
cursorAdapter: () => cursorAdapter,
|
|
5735
5836
|
cursorSdkMessageToEvents: () => cursorSdkMessageToEvents,
|
|
5736
5837
|
decodeBrightsyTarget: () => decodeBrightsyTarget,
|
|
@@ -5740,6 +5841,7 @@ __export(agents_exports, {
|
|
|
5740
5841
|
getAgentSetupInfo: () => getAgentSetupInfo,
|
|
5741
5842
|
installAgent: () => installAgent,
|
|
5742
5843
|
isCursorAutoModel: () => isCursorAutoModel,
|
|
5844
|
+
isOrchestratorCapableAgent: () => isOrchestratorCapableAgent,
|
|
5743
5845
|
isSessionQuotaLimit: () => isSessionQuotaLimit,
|
|
5744
5846
|
listAgentSetupInfo: () => listAgentSetupInfo,
|
|
5745
5847
|
listBrightsyChatTargets: () => listBrightsyChatTargets,
|
|
@@ -5782,6 +5884,7 @@ var init_agents = __esm({
|
|
|
5782
5884
|
init_opencode();
|
|
5783
5885
|
init_list_models();
|
|
5784
5886
|
init_session_quota();
|
|
5887
|
+
init_orchestrator_capable();
|
|
5785
5888
|
init_path();
|
|
5786
5889
|
init_install();
|
|
5787
5890
|
adapters = {
|
|
@@ -5979,6 +6082,7 @@ init_app_settings();
|
|
|
5979
6082
|
init_global_workspace();
|
|
5980
6083
|
init_brightsy();
|
|
5981
6084
|
init_agents();
|
|
6085
|
+
init_orchestrator_capable();
|
|
5982
6086
|
|
|
5983
6087
|
// src/agents/message-parts.ts
|
|
5984
6088
|
function asRecord(input) {
|
|
@@ -6220,6 +6324,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
6220
6324
|
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await Promise.resolve().then(() => (init_coordinator_prompt(), coordinator_prompt_exports));
|
|
6221
6325
|
ensureGlobalCoordinatorCwd2();
|
|
6222
6326
|
}
|
|
6327
|
+
if (isOrchestratorThread(thread)) {
|
|
6328
|
+
assertOrchestratorCapableAgent(thread.agent);
|
|
6329
|
+
}
|
|
6223
6330
|
const adapter = getAdapter(thread.agent);
|
|
6224
6331
|
const cmd = await adapter.buildTurn(thread, input);
|
|
6225
6332
|
if (cmd.cwd !== thread.worktreePath) {
|
|
@@ -7038,6 +7145,9 @@ async function listLinearIssues(agent, repoPath) {
|
|
|
7038
7145
|
return adapter.listLinearIssues(repoPath);
|
|
7039
7146
|
}
|
|
7040
7147
|
|
|
7148
|
+
// src/orchestrator/orchestrator.ts
|
|
7149
|
+
init_orchestrator_capable();
|
|
7150
|
+
|
|
7041
7151
|
// src/threads/chat-tabs.ts
|
|
7042
7152
|
var import_node_crypto2 = require("crypto");
|
|
7043
7153
|
|
|
@@ -7319,6 +7429,7 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
|
|
|
7319
7429
|
init_teams();
|
|
7320
7430
|
init_worktree_labels();
|
|
7321
7431
|
init_global_workspace();
|
|
7432
|
+
init_orchestrator_capable();
|
|
7322
7433
|
init_thread_store();
|
|
7323
7434
|
init_worktree_labels();
|
|
7324
7435
|
function sameWorktreePath(a, b) {
|
|
@@ -7382,6 +7493,10 @@ function createChatTab(input) {
|
|
|
7382
7493
|
const binding = worktreeBindingFrom(from);
|
|
7383
7494
|
const explicitTitle = input.title?.trim();
|
|
7384
7495
|
const title = explicitTitle || allocateTeamName(takenTeamSlugsForChatTab(binding.worktreePath)).name;
|
|
7496
|
+
const nextAgent = input.agent ?? from.agent;
|
|
7497
|
+
if (isOrchestratorThread(from) || binding.sourceType === "orchestration") {
|
|
7498
|
+
assertOrchestratorCapableAgent(nextAgent);
|
|
7499
|
+
}
|
|
7385
7500
|
const thread = createEmptyThread({
|
|
7386
7501
|
title,
|
|
7387
7502
|
// Chat-tab nicknames (soccer team or explicit) must stick. Post-turn
|
|
@@ -7389,7 +7504,7 @@ function createChatTab(input) {
|
|
|
7389
7504
|
// shared worktree folder name (e.g. fork "Arsenal" → "Monaco").
|
|
7390
7505
|
userSetTitle: true,
|
|
7391
7506
|
...binding,
|
|
7392
|
-
agent:
|
|
7507
|
+
agent: nextAgent,
|
|
7393
7508
|
model: input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model,
|
|
7394
7509
|
effort: input.effort !== void 0 ? input.effort : from.effort,
|
|
7395
7510
|
fast: input.fast !== void 0 ? Boolean(input.fast) : from.fast,
|
|
@@ -7426,22 +7541,149 @@ var import_node_fs19 = require("fs");
|
|
|
7426
7541
|
var import_node_path18 = require("path");
|
|
7427
7542
|
init_global_workspace();
|
|
7428
7543
|
init_thread_store();
|
|
7544
|
+
|
|
7545
|
+
// src/review/review-request-template.ts
|
|
7546
|
+
var REVIEW_REQUEST_TEMPLATE = `# Review guidelines:
|
|
7547
|
+
|
|
7548
|
+
You are reviewing a proposed code change so a human can decide whether it is **ready to merge / land**. Findings matter, but the primary deliverable is a clear readiness recommendation \u2014 not a laundry list of style notes.
|
|
7549
|
+
|
|
7550
|
+
## Required outcome
|
|
7551
|
+
|
|
7552
|
+
Start your reply with a **Recommendation** section using exactly one of:
|
|
7553
|
+
|
|
7554
|
+
- **Approve** \u2014 ready to merge as-is (or with only trivial nits the author can ignore).
|
|
7555
|
+
- **Approve with nits** \u2014 ready to merge; list only optional polish that should not block.
|
|
7556
|
+
- **Request changes** \u2014 not ready; blocking issues must be fixed first.
|
|
7557
|
+
- **Needs more information** \u2014 cannot judge readiness yet (missing context, incomplete diff, unclear intent).
|
|
7558
|
+
|
|
7559
|
+
In 1\u20133 sentences, say **why** \u2014 grounded in correctness, risk, test coverage, and scope \u2014 not vibes. If you request changes, name the blockers explicitly.
|
|
7560
|
+
|
|
7561
|
+
People running this review are asking \u201Ccan we ship this?\u201D Treat that as the question you answer first.
|
|
7562
|
+
|
|
7563
|
+
## Findings
|
|
7564
|
+
|
|
7565
|
+
Below are guidelines for determining whether an issue is worth flagging to the original author.
|
|
7566
|
+
|
|
7567
|
+
These are not the final word. More specific guidelines elsewhere (developer message, user message, a file, etc.) override these.
|
|
7568
|
+
|
|
7569
|
+
Flag something as a bug / blocking finding only when:
|
|
7570
|
+
|
|
7571
|
+
1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code.
|
|
7572
|
+
2. The bug is discrete and actionable (not a vague codebase-wide complaint or a bundle of unrelated issues).
|
|
7573
|
+
3. Fixing it does not demand rigor absent from the rest of the codebase.
|
|
7574
|
+
4. The issue was introduced by this change (do not flag pre-existing bugs unless they are newly exposed by this PR).
|
|
7575
|
+
5. The author would likely fix it if made aware.
|
|
7576
|
+
6. It does not rely on unstated assumptions about the codebase or author intent.
|
|
7577
|
+
7. Speculative breakage is not enough \u2014 identify the other code that is provably affected.
|
|
7578
|
+
8. It is clearly not just an intentional change by the author.
|
|
7579
|
+
|
|
7580
|
+
When flagging an issue, include a short accompanying comment:
|
|
7581
|
+
|
|
7582
|
+
1. Clear about why it is a problem.
|
|
7583
|
+
2. Severity must match reality \u2014 do not inflate.
|
|
7584
|
+
3. Brief: at most one paragraph; avoid unnecessary line breaks in prose.
|
|
7585
|
+
4. No code chunks longer than 3 lines; wrap code in inline ticks or a fenced block.
|
|
7586
|
+
5. Call out scenarios / environments / inputs needed to hit the bug when severity depends on them.
|
|
7587
|
+
6. Matter-of-fact tone \u2014 helpful assistant, not accusatory or effusive.
|
|
7588
|
+
7. Skimmable on first read.
|
|
7589
|
+
8. No empty flattery (\u201CGreat job\u2026\u201D, \u201CThanks for\u2026\u201D).
|
|
7590
|
+
|
|
7591
|
+
HOW MANY FINDINGS TO RETURN:
|
|
7592
|
+
|
|
7593
|
+
List every finding the author would fix if they knew about it. If nothing qualifies, say so and still give the Recommendation. Do not stop at the first finding.
|
|
7594
|
+
|
|
7595
|
+
GUIDELINES:
|
|
7596
|
+
|
|
7597
|
+
- Ignore trivial style unless it obscures meaning or violates documented standards.
|
|
7598
|
+
- One comment per distinct issue (or a short multi-line range if needed).
|
|
7599
|
+
- Use \`\`\`suggestion blocks ONLY for concrete replacement code (minimal lines; no commentary inside the block).
|
|
7600
|
+
- In every \`\`\`suggestion block, preserve the exact leading whitespace of the replaced lines (spaces vs tabs, number of spaces).
|
|
7601
|
+
- Do NOT introduce or remove outer indentation levels unless that is the actual fix.
|
|
7602
|
+
- Separate **blocking** findings from **nits**. Only blocking findings should drive Request changes.
|
|
7603
|
+
|
|
7604
|
+
The report appears in chat (and can become Sideboard diff comments). Avoid unnecessary location chatter in the body; keep line ranges as short as possible (prefer \u22645\u201310 lines).
|
|
7605
|
+
|
|
7606
|
+
## Getting the diff
|
|
7607
|
+
|
|
7608
|
+
Use Sideboard's diff for this thread's worktree. Prefer the \`get_diff\` MCP tool (pass this thread's ref) for a compact summary, then read specific files with Read/Glob as needed. In the Sideboard desktop app, the Changes panel shows the same worktree diff.
|
|
7609
|
+
|
|
7610
|
+
If the user asks you to address or read line comments they added in the Changes / file diff UI, those arrive as \`diff-comment\` attachments on the next turn \u2014 follow them precisely.
|
|
7611
|
+
|
|
7612
|
+
## Fallback: if you don't have access to the Sideboard diff tool
|
|
7613
|
+
|
|
7614
|
+
If you don't have access to \`get_diff\`, use the following git commands to get the diff:
|
|
7615
|
+
|
|
7616
|
+
\`\`\`bash
|
|
7617
|
+
# Get the merge base between this branch and the target
|
|
7618
|
+
MERGE_BASE=$(git merge-base origin/main HEAD)
|
|
7619
|
+
|
|
7620
|
+
# Get the committed diff against the merge base
|
|
7621
|
+
git diff $MERGE_BASE HEAD
|
|
7622
|
+
|
|
7623
|
+
# Get any uncommitted changes (staged and unstaged)
|
|
7624
|
+
git diff HEAD
|
|
7625
|
+
\`\`\`
|
|
7626
|
+
|
|
7627
|
+
Review the combination of both outputs: the first shows all committed changes on this branch relative to the target, and the second shows any uncommitted work in progress.
|
|
7628
|
+
|
|
7629
|
+
No need to mention in your report whether or not you used one of the fallback strategies; it's usually irrelevant.
|
|
7630
|
+
|
|
7631
|
+
## Output format
|
|
7632
|
+
|
|
7633
|
+
**1. Recommendation first** (required), then **2. Findings** (may be empty).
|
|
7634
|
+
|
|
7635
|
+
Only report ONE finding per unique issue.
|
|
7636
|
+
|
|
7637
|
+
<example>
|
|
7638
|
+
## Recommendation
|
|
7639
|
+
|
|
7640
|
+
**Request changes** \u2014 The empty-input crash on load will break first-run users; fix that before merge. The unused helper is a nit and can wait.
|
|
7641
|
+
|
|
7642
|
+
## Findings
|
|
7643
|
+
|
|
7644
|
+
### **#1 Empty input causes crash** (blocking)
|
|
7645
|
+
|
|
7646
|
+
If the input field is empty when the page loads, the app will crash.
|
|
7647
|
+
|
|
7648
|
+
File: src/client/frontends/desktop/ui/Input.tsx
|
|
7649
|
+
|
|
7650
|
+
### **#2 Dead code** (nit)
|
|
7651
|
+
|
|
7652
|
+
The getUserData function is now unused. It should be deleted.
|
|
7653
|
+
|
|
7654
|
+
File: src/client/frontends/desktop/core/UserData.ts
|
|
7655
|
+
</example>
|
|
7656
|
+
|
|
7657
|
+
<example>
|
|
7658
|
+
## Recommendation
|
|
7659
|
+
|
|
7660
|
+
**Approve** \u2014 Diff is scoped, behavior looks correct, and there are no blocking issues. Safe to merge.
|
|
7661
|
+
</example>
|
|
7662
|
+
`;
|
|
7663
|
+
|
|
7664
|
+
// src/review/request-review.ts
|
|
7665
|
+
var REPO_REVIEW_PATH = ".sideboard/review.md";
|
|
7666
|
+
var REPO_REVIEW_NAME = "review.md";
|
|
7429
7667
|
var REVIEW_REQUEST_PATH = ".sideboard/attachments/Review request.md";
|
|
7430
7668
|
var REVIEW_REQUEST_NAME = "Review request.md";
|
|
7431
|
-
var REVIEW_REQUEST_PREFILL =
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7669
|
+
var REVIEW_REQUEST_PREFILL = "Review.";
|
|
7670
|
+
var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local only)
|
|
7671
|
+
*
|
|
7672
|
+
!.gitignore
|
|
7673
|
+
`;
|
|
7674
|
+
var LEGACY_REVIEW_TEMPLATE_MARKERS = [
|
|
7675
|
+
"You are acting as a reviewer for a proposed code change made by another engineer.",
|
|
7676
|
+
"HOW MANY FINDINGS TO RETURN:"
|
|
7677
|
+
];
|
|
7678
|
+
function shouldRefreshReviewRequestTemplate(content) {
|
|
7679
|
+
const trimmed = content.trim();
|
|
7680
|
+
if (!trimmed) return true;
|
|
7681
|
+
if (/##\s*Recommendation|ready to merge|Approve with nits|Request changes/i.test(trimmed)) {
|
|
7682
|
+
return false;
|
|
7683
|
+
}
|
|
7684
|
+
return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
|
|
7442
7685
|
}
|
|
7443
|
-
function
|
|
7444
|
-
const abs = (0, import_node_path18.join)(worktreePath, REVIEW_REQUEST_PATH);
|
|
7686
|
+
function readTextIfPresent(abs) {
|
|
7445
7687
|
if (!(0, import_node_fs19.existsSync)(abs)) return null;
|
|
7446
7688
|
try {
|
|
7447
7689
|
const content = (0, import_node_fs19.readFileSync)(abs, "utf8");
|
|
@@ -7450,6 +7692,54 @@ function readExistingReviewRequestFile(worktreePath) {
|
|
|
7450
7692
|
return null;
|
|
7451
7693
|
}
|
|
7452
7694
|
}
|
|
7695
|
+
function ensureAttachmentsGitignore(worktreePath) {
|
|
7696
|
+
const gitignoreAbs = (0, import_node_path18.join)(worktreePath, ".sideboard", "attachments", ".gitignore");
|
|
7697
|
+
if ((0, import_node_fs19.existsSync)(gitignoreAbs)) return;
|
|
7698
|
+
(0, import_node_fs19.mkdirSync)((0, import_node_path18.dirname)(gitignoreAbs), { recursive: true });
|
|
7699
|
+
(0, import_node_fs19.writeFileSync)(gitignoreAbs, ATTACHMENTS_GITIGNORE, "utf8");
|
|
7700
|
+
}
|
|
7701
|
+
function resolveReviewGuidelines(worktreePath) {
|
|
7702
|
+
const repoAbs = (0, import_node_path18.join)(worktreePath, REPO_REVIEW_PATH);
|
|
7703
|
+
const repoContent = readTextIfPresent(repoAbs);
|
|
7704
|
+
if (repoContent) {
|
|
7705
|
+
return {
|
|
7706
|
+
path: REPO_REVIEW_PATH,
|
|
7707
|
+
name: REPO_REVIEW_NAME,
|
|
7708
|
+
content: repoContent,
|
|
7709
|
+
source: "repo"
|
|
7710
|
+
};
|
|
7711
|
+
}
|
|
7712
|
+
const localAbs = (0, import_node_path18.join)(worktreePath, REVIEW_REQUEST_PATH);
|
|
7713
|
+
const localContent = readTextIfPresent(localAbs);
|
|
7714
|
+
if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
|
|
7715
|
+
return {
|
|
7716
|
+
path: REVIEW_REQUEST_PATH,
|
|
7717
|
+
name: REVIEW_REQUEST_NAME,
|
|
7718
|
+
content: localContent,
|
|
7719
|
+
source: "local"
|
|
7720
|
+
};
|
|
7721
|
+
}
|
|
7722
|
+
ensureAttachmentsGitignore(worktreePath);
|
|
7723
|
+
(0, import_node_fs19.mkdirSync)((0, import_node_path18.dirname)(localAbs), { recursive: true });
|
|
7724
|
+
(0, import_node_fs19.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
7725
|
+
return {
|
|
7726
|
+
path: REVIEW_REQUEST_PATH,
|
|
7727
|
+
name: REVIEW_REQUEST_NAME,
|
|
7728
|
+
content: REVIEW_REQUEST_TEMPLATE,
|
|
7729
|
+
source: "stock"
|
|
7730
|
+
};
|
|
7731
|
+
}
|
|
7732
|
+
function buildReviewRequestAttachment(content, opts) {
|
|
7733
|
+
const path = opts?.path ?? REVIEW_REQUEST_PATH;
|
|
7734
|
+
const name = opts?.name ?? (path === REPO_REVIEW_PATH ? REPO_REVIEW_NAME : REVIEW_REQUEST_NAME);
|
|
7735
|
+
return {
|
|
7736
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
7737
|
+
name,
|
|
7738
|
+
kind: "file",
|
|
7739
|
+
path,
|
|
7740
|
+
content
|
|
7741
|
+
};
|
|
7742
|
+
}
|
|
7453
7743
|
async function requestReview(threadRef, send) {
|
|
7454
7744
|
const from = findThreadByRef(threadRef);
|
|
7455
7745
|
if (!from) throw new Error(`Thread not found: ${threadRef}`);
|
|
@@ -7461,12 +7751,16 @@ async function requestReview(threadRef, send) {
|
|
|
7461
7751
|
if (from.status === "archived") {
|
|
7462
7752
|
throw new Error(`Thread is archived: ${from.id}`);
|
|
7463
7753
|
}
|
|
7464
|
-
const
|
|
7465
|
-
const attachments = existing ? [buildReviewRequestAttachment(existing)] : [];
|
|
7754
|
+
const guidelines = resolveReviewGuidelines(from.worktreePath);
|
|
7466
7755
|
const tab = createChatTab({
|
|
7467
7756
|
fromThreadId: from.id,
|
|
7468
7757
|
title: "Review",
|
|
7469
|
-
attachments
|
|
7758
|
+
attachments: [
|
|
7759
|
+
buildReviewRequestAttachment(guidelines.content, {
|
|
7760
|
+
path: guidelines.path,
|
|
7761
|
+
name: guidelines.name
|
|
7762
|
+
})
|
|
7763
|
+
]
|
|
7470
7764
|
});
|
|
7471
7765
|
const started = await send(tab.id, REVIEW_REQUEST_PREFILL);
|
|
7472
7766
|
return { tab: started, from };
|
|
@@ -8850,7 +9144,7 @@ var IMAGE_MIME_BY_EXT = {
|
|
|
8850
9144
|
ico: "image/x-icon"
|
|
8851
9145
|
};
|
|
8852
9146
|
var ATTACHMENTS_DIR = ".sideboard/attachments";
|
|
8853
|
-
var
|
|
9147
|
+
var ATTACHMENTS_GITIGNORE2 = `# Sideboard review / composer attachments (local only)
|
|
8854
9148
|
*
|
|
8855
9149
|
!.gitignore
|
|
8856
9150
|
`;
|
|
@@ -8871,7 +9165,7 @@ function ensureAttachmentsDir(worktreePath) {
|
|
|
8871
9165
|
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
8872
9166
|
const gi = (0, import_node_path22.join)(dir, ".gitignore");
|
|
8873
9167
|
if (!(0, import_node_fs23.existsSync)(gi)) {
|
|
8874
|
-
(0, import_node_fs23.writeFileSync)(gi,
|
|
9168
|
+
(0, import_node_fs23.writeFileSync)(gi, ATTACHMENTS_GITIGNORE2, "utf8");
|
|
8875
9169
|
}
|
|
8876
9170
|
return dir;
|
|
8877
9171
|
}
|
|
@@ -9655,9 +9949,11 @@ var Orchestrator = class {
|
|
|
9655
9949
|
this.emit({ type: "error", threadId, message: `context compact failed: ${message}` });
|
|
9656
9950
|
thread = this.requireThread(threadId);
|
|
9657
9951
|
}
|
|
9952
|
+
const sentAttachments = thread.attachments.length > 0 ? [...thread.attachments] : void 0;
|
|
9658
9953
|
appendMessage(threadId, {
|
|
9659
9954
|
role: "user",
|
|
9660
9955
|
text: prompt,
|
|
9956
|
+
...sentAttachments ? { attachments: sentAttachments } : {},
|
|
9661
9957
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
9662
9958
|
});
|
|
9663
9959
|
thread = this.requireThread(threadId);
|
|
@@ -9665,7 +9961,7 @@ var Orchestrator = class {
|
|
|
9665
9961
|
thread.worktreePath,
|
|
9666
9962
|
prompt,
|
|
9667
9963
|
{
|
|
9668
|
-
attachments: thread.attachments
|
|
9964
|
+
attachments: sentAttachments ?? thread.attachments
|
|
9669
9965
|
}
|
|
9670
9966
|
);
|
|
9671
9967
|
const orchestrationReminder = isOrchestratorThread(thread) ? coordinatorTurnReminder({
|
|
@@ -10349,6 +10645,9 @@ var Orchestrator = class {
|
|
|
10349
10645
|
`Cannot switch agent provider mid-chat (${thread.agent} \u2192 ${patch.agent}). Start a new chat tab instead.`
|
|
10350
10646
|
);
|
|
10351
10647
|
}
|
|
10648
|
+
if (isOrchestratorThread(thread)) {
|
|
10649
|
+
assertOrchestratorCapableAgent(patch.agent);
|
|
10650
|
+
}
|
|
10352
10651
|
next.agent = patch.agent;
|
|
10353
10652
|
if (patch.agent !== "claude" && patch.model === void 0) next.model = null;
|
|
10354
10653
|
next.sessionId = null;
|
|
@@ -11022,7 +11321,7 @@ async function startMcpServer() {
|
|
|
11022
11321
|
);
|
|
11023
11322
|
server.tool(
|
|
11024
11323
|
"request_review",
|
|
11025
|
-
|
|
11324
|
+
'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn / get_turn_result on the returned review tab id.',
|
|
11026
11325
|
{ ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
|
|
11027
11326
|
async ({ ref }) => {
|
|
11028
11327
|
try {
|
|
@@ -11114,7 +11413,7 @@ async function startMcpServer() {
|
|
|
11114
11413
|
);
|
|
11115
11414
|
server.tool(
|
|
11116
11415
|
"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.",
|
|
11416
|
+
"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.",
|
|
11118
11417
|
{
|
|
11119
11418
|
ref: import_zod.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
11120
11419
|
through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|