@sideboard-ai/core 0.1.79 → 0.1.83
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 +18 -0
- package/dist/agents/cursor-runner.js +4 -0
- package/dist/{agents-EWPHOM6Y.js → agents-3YPUZME7.js} +6 -5
- package/dist/{agents-GRAPUXWW.js → agents-4KRD46KG.js} +5 -5
- package/dist/{app-settings-NILNWNNQ.js → app-settings-GVSOIJLZ.js} +1 -1
- package/dist/{app-settings-6IOQYGBK.js → app-settings-MQXL7OUF.js} +2 -1
- package/dist/{chunk-6T2SKGCS.js → chunk-DJFGX4RT.js} +3 -3
- package/dist/{chunk-HCGEFU3J.js → chunk-E2MIA7DO.js} +13 -14
- package/dist/{chunk-HSLQXL6M.js → chunk-ERJS3ZDP.js} +13 -14
- package/dist/{chunk-YVVXWWCC.js → chunk-GD2FM6FN.js} +81 -49
- package/dist/{chunk-FUUVPN4A.js → chunk-HBBXS2FR.js} +78 -49
- package/dist/chunk-IZ7RPF54.js +36 -0
- package/dist/{chunk-P33ZQ7ZC.js → chunk-JMRJ4F5B.js} +70 -25
- package/dist/{chunk-SHCR6RPU.js → chunk-JW6YFPQE.js} +83 -24
- package/dist/{chunk-PW5YHHP3.js → chunk-LVTNWH7B.js} +2 -2
- package/dist/{chunk-5LXWTU3J.js → chunk-NXXT5SE3.js} +3 -3
- package/dist/{chunk-5VTWBI3I.js → chunk-RS54WYYH.js} +2 -2
- package/dist/{chunk-XKNBSYA7.js → chunk-UHGN4KCL.js} +35 -1
- package/dist/{chunk-IFPN6TER.js → chunk-YO3CYL6B.js} +4 -1
- package/dist/{coordinator-prompt-2OVON2LQ.js → coordinator-prompt-46JE4NQR.js} +4 -3
- package/dist/{coordinator-prompt-K2R34A5T.js → coordinator-prompt-6ICA54JR.js} +3 -3
- package/dist/{global-workspace-VG44RZUH.js → global-workspace-GSLCEB5E.js} +5 -4
- package/dist/{global-workspace-RI367AM6.js → global-workspace-OBRWUPZG.js} +4 -4
- package/dist/index.cjs +268 -151
- package/dist/index.d.cts +52 -11
- package/dist/index.d.ts +52 -11
- package/dist/index.js +87 -87
- package/dist/mcp/run-stdio.cjs +249 -141
- package/dist/mcp/run-stdio.js +66 -76
- package/dist/{workspaces-TYPJNUSM.js → workspaces-7B3PTEF4.js} +6 -5
- package/dist/{workspaces-BQWQEZWE.js → workspaces-RJIWQB6J.js} +5 -5
- package/dist/{worktree-5VLLFI5W.js → worktree-HSN5LWY6.js} +3 -2
- package/dist/{worktree-UNSOCYZU.js → worktree-U3UIID2K.js} +2 -2
- package/package.json +1 -1
|
@@ -7,6 +7,23 @@ var import_node_fs3 = require("fs");
|
|
|
7
7
|
var import_node_path4 = require("path");
|
|
8
8
|
var import_node_readline = require("readline");
|
|
9
9
|
|
|
10
|
+
// src/hook/nested-electron-env.ts
|
|
11
|
+
var NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
|
|
12
|
+
function isNestedElectronEnvKey(key) {
|
|
13
|
+
return NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
14
|
+
}
|
|
15
|
+
function dropNestedElectronEnvFromProcess(env = process.env) {
|
|
16
|
+
for (const key of Object.keys(env)) {
|
|
17
|
+
if (isNestedElectronEnvKey(key)) delete env[key];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
var STRIP_NESTED_ELECTRON_THEN_EXEC = [
|
|
21
|
+
"vars=`printenv | awk -F= '/^(ELECTRON_|CHROME_)/{print $1}'`",
|
|
22
|
+
'[ -n "$vars" ] && unset $vars',
|
|
23
|
+
"export ELECTRON_RUN_AS_NODE=1",
|
|
24
|
+
'exec "$@"'
|
|
25
|
+
].join("; ");
|
|
26
|
+
|
|
10
27
|
// src/store/paths.ts
|
|
11
28
|
var import_node_path2 = require("path");
|
|
12
29
|
var import_node_os2 = require("os");
|
|
@@ -224,6 +241,7 @@ function cursorSdkMessageToEvents(msg) {
|
|
|
224
241
|
}
|
|
225
242
|
|
|
226
243
|
// src/agents/cursor-runner.ts
|
|
244
|
+
dropNestedElectronEnvFromProcess();
|
|
227
245
|
function emit(event) {
|
|
228
246
|
process.stdout.write(`${JSON.stringify(event)}
|
|
229
247
|
`);
|
|
@@ -3,6 +3,9 @@ import {
|
|
|
3
3
|
cursorSdkMessageToEvents,
|
|
4
4
|
formatUnknownDetail
|
|
5
5
|
} from "../chunk-SEOICVGB.js";
|
|
6
|
+
import {
|
|
7
|
+
dropNestedElectronEnvFromProcess
|
|
8
|
+
} from "../chunk-IZ7RPF54.js";
|
|
6
9
|
import {
|
|
7
10
|
appDataDir
|
|
8
11
|
} from "../chunk-M37RITA6.js";
|
|
@@ -12,6 +15,7 @@ import { Agent, AgentBusyError, CursorAgentError, JsonlLocalAgentStore } from "@
|
|
|
12
15
|
import { mkdirSync } from "fs";
|
|
13
16
|
import { join } from "path";
|
|
14
17
|
import { createInterface } from "readline";
|
|
18
|
+
dropNestedElectronEnvFromProcess();
|
|
15
19
|
function emit(event) {
|
|
16
20
|
process.stdout.write(`${JSON.stringify(event)}
|
|
17
21
|
`);
|
|
@@ -28,22 +28,23 @@ import {
|
|
|
28
28
|
resolveCursorModelId,
|
|
29
29
|
resolveLoginCommand,
|
|
30
30
|
resolveQuotaFallbackAgent
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-JW6YFPQE.js";
|
|
32
32
|
import {
|
|
33
33
|
ORCHESTRATOR_AGENT_KINDS,
|
|
34
34
|
assertOrchestratorCapableAgent,
|
|
35
35
|
coerceOrchestratorAgent,
|
|
36
36
|
isOrchestratorCapableAgent
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import "./chunk-
|
|
37
|
+
} from "./chunk-NXXT5SE3.js";
|
|
38
|
+
import "./chunk-E2MIA7DO.js";
|
|
39
39
|
import "./chunk-BTL7EMGT.js";
|
|
40
40
|
import {
|
|
41
41
|
cursorSdkMessageToEvents,
|
|
42
42
|
parseCursorRunnerLine
|
|
43
43
|
} from "./chunk-SEOICVGB.js";
|
|
44
|
-
import "./chunk-
|
|
44
|
+
import "./chunk-GD2FM6FN.js";
|
|
45
45
|
import "./chunk-FKOIHGKV.js";
|
|
46
|
-
import "./chunk-
|
|
46
|
+
import "./chunk-YO3CYL6B.js";
|
|
47
|
+
import "./chunk-IZ7RPF54.js";
|
|
47
48
|
import "./chunk-JT7R45JB.js";
|
|
48
49
|
import "./chunk-FWJYLBO6.js";
|
|
49
50
|
import "./chunk-77WWLBCI.js";
|
|
@@ -32,16 +32,16 @@ import {
|
|
|
32
32
|
resolveCursorModelId,
|
|
33
33
|
resolveLoginCommand,
|
|
34
34
|
resolveQuotaFallbackAgent
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-JMRJ4F5B.js";
|
|
36
36
|
import "./chunk-VROPG6QF.js";
|
|
37
37
|
import {
|
|
38
38
|
ORCHESTRATOR_AGENT_KINDS,
|
|
39
39
|
assertOrchestratorCapableAgent,
|
|
40
40
|
coerceOrchestratorAgent,
|
|
41
41
|
isOrchestratorCapableAgent
|
|
42
|
-
} from "./chunk-
|
|
43
|
-
import "./chunk-
|
|
44
|
-
import "./chunk-
|
|
42
|
+
} from "./chunk-DJFGX4RT.js";
|
|
43
|
+
import "./chunk-ERJS3ZDP.js";
|
|
44
|
+
import "./chunk-HBBXS2FR.js";
|
|
45
45
|
import "./chunk-B3SJXYIJ.js";
|
|
46
46
|
import "./chunk-7FD7COKE.js";
|
|
47
47
|
import {
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
resolveCommandBinarySync,
|
|
54
54
|
withExportedPath
|
|
55
55
|
} from "./chunk-AE5VBOFE.js";
|
|
56
|
-
import "./chunk-
|
|
56
|
+
import "./chunk-UHGN4KCL.js";
|
|
57
57
|
import "./chunk-NSTQ6QKD.js";
|
|
58
58
|
import "./chunk-KGZBYWZ3.js";
|
|
59
59
|
import "./chunk-7MV3RXSC.js";
|
|
@@ -52,7 +52,8 @@ import {
|
|
|
52
52
|
updateDefaultsSettings,
|
|
53
53
|
updateIntegrationsSettings,
|
|
54
54
|
updateOpencodeSettings
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-YO3CYL6B.js";
|
|
56
|
+
import "./chunk-IZ7RPF54.js";
|
|
56
57
|
import "./chunk-JT7R45JB.js";
|
|
57
58
|
import "./chunk-77WWLBCI.js";
|
|
58
59
|
import "./chunk-M37RITA6.js";
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
ensureGlobalCoordinatorCwd
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ERJS3ZDP.js";
|
|
6
6
|
import {
|
|
7
7
|
allocateTeamName,
|
|
8
8
|
takenSlugsFromThread,
|
|
9
9
|
teamSlugFromName
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HBBXS2FR.js";
|
|
11
11
|
import {
|
|
12
12
|
createEmptyThread,
|
|
13
13
|
listThreads,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
resolveNewThreadOptions,
|
|
19
19
|
resolveThreadDefaults
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-UHGN4KCL.js";
|
|
21
21
|
import {
|
|
22
22
|
globalAgentCwd
|
|
23
23
|
} from "./chunk-7MV3RXSC.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveGithubRepoSlug
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GD2FM6FN.js";
|
|
4
4
|
import {
|
|
5
5
|
resolveThreadDefaults
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YO3CYL6B.js";
|
|
7
7
|
import {
|
|
8
8
|
globalAgentCwd,
|
|
9
9
|
sideboardReposDir
|
|
@@ -51,6 +51,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
|
|
|
51
51
|
"- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
|
|
52
52
|
"- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
|
|
53
53
|
"- list_threads / get_thread \u2014 fleet status (what is going on)",
|
|
54
|
+
"- ask_user \u2014 multiple-choice questions in the composer (any mode, not only Plan). Explain options in chat first, include a description on every option, then wait for answers.",
|
|
54
55
|
"- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for Slack / away-from-keyboard work. Turn OFF when the user says they are done, wrapping up, going to sleep, or no longer need the machine awake. Closing this chat also releases it.",
|
|
55
56
|
"Workspaces:",
|
|
56
57
|
"- add_workspace / remove_workspace \u2014 register or unregister a git repo",
|
|
@@ -69,7 +70,12 @@ var COORDINATOR_TOOL_PLAYBOOK = [
|
|
|
69
70
|
"- get_diff \u2014 compact diff summary",
|
|
70
71
|
'- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
|
|
71
72
|
"- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn. Prefer this over paraphrasing.",
|
|
72
|
-
'-
|
|
73
|
+
'- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
|
|
74
|
+
'- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
|
|
75
|
+
"Process guides:",
|
|
76
|
+
"- Recurring multi-item / fan-out: if the child worktree has `.claude/skills/graph-engineering/SKILL.md`, tell the worker to follow it (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch three threads.",
|
|
77
|
+
"- Recurring shapes: have the worktree agent write `.claude/skills/<kebab-name>/SKILL.md` (commit it) so later threads and native Claude Code / attach see it. Do not use `.sideboard/skills` for new guides. Codex/OpenCode: one line in AGENTS.md pointing at that file.",
|
|
78
|
+
"- One-offs: no guide. Same miss across threads: edit the skill or `.sideboard/review.md`, then rerun the batch \u2014 do not patch three threads.",
|
|
73
79
|
"Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
|
|
74
80
|
"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.",
|
|
75
81
|
"Bash / Read / etc: allowed for (1) inspecting target worktrees / registered repo paths from MCP, and (2) greenfield setup under ~/sideboard/repos (git clone, gh repo create, git init+remote). Never git init/clone *inside* this synthetic home cwd \u2014 emptiness here is expected, not a bug."
|
|
@@ -99,7 +105,7 @@ function coordinatorTurnReminder(opts) {
|
|
|
99
105
|
goal ? `- Goal / title: ${goal}` : null,
|
|
100
106
|
accountDefaultsPlaybookLine(),
|
|
101
107
|
`- For "what's going on": call list_threads (and list_workspaces if needed). Summarize fleet status \u2014 do not ls/git-status this synthetic home.`,
|
|
102
|
-
"- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn.
|
|
108
|
+
"- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. Commit/push/draft PR with ask_git on the child, then wait_for_turn \u2014 never git/gh from this cwd. Call ask_git merge only if the user explicitly asked to merge.",
|
|
103
109
|
"- New repo: Bash (clone or gh repo create under ~/sideboard/repos/<name>) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
|
|
104
110
|
"- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
|
|
105
111
|
"- If they will wait on Slack or leave the Mac, call set_caffeinate enabled=true. When they say they are done / wrapping up / going to sleep, call set_caffeinate enabled=false. Closing this chat also turns it off."
|
|
@@ -153,9 +159,9 @@ function ensureGlobalCoordinatorCwd(opts) {
|
|
|
153
159
|
orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
|
|
154
160
|
"Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
|
|
155
161
|
"Never pass `agent=codex` when you yourself are Codex \u2014 nested Codex deadlocks on shared ~/.codex locks. Omit agent (Account default) or use cursor/claude.",
|
|
156
|
-
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn
|
|
162
|
+
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn. Merge only if the user explicitly asked (`ask_git` merge).",
|
|
157
163
|
"Typical flow (new app): Bash create/clone under repos dir \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft.",
|
|
158
|
-
"Always ask worktree agents to commit, push, open draft PRs
|
|
164
|
+
"Always ask worktree agents to commit, push, and open draft PRs (`ask_git` / `send_to_thread`). Tell them to merge only when the user explicitly asked. The worktree agent runs git/gh; never merge from this orchestration cwd."
|
|
159
165
|
].join("\n");
|
|
160
166
|
try {
|
|
161
167
|
writeFileSync(join(dir, "CLAUDE.md"), `${body}
|
|
@@ -168,7 +174,6 @@ function ensureGlobalCoordinatorCwd(opts) {
|
|
|
168
174
|
}
|
|
169
175
|
function coordinatorSystemPrompt(opts) {
|
|
170
176
|
const audience = opts.audience ?? "cloud";
|
|
171
|
-
const reposDir = sideboardReposDir();
|
|
172
177
|
const intro = audience === "cloud" ? [
|
|
173
178
|
"You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Discord, Teams, or other chat).",
|
|
174
179
|
"Your reply will be sent back to that cloud agent \u2014 be concise and actionable."
|
|
@@ -186,14 +191,8 @@ function coordinatorSystemPrompt(opts) {
|
|
|
186
191
|
...intro,
|
|
187
192
|
"You operate across ALL registered workspaces below.",
|
|
188
193
|
"You have no project git home \u2014 this process cwd is synthetic and empty on purpose.",
|
|
189
|
-
|
|
190
|
-
accountDefaultsPlaybookLine(),
|
|
191
|
-
coordinatorGreenfieldPlaybook(reposDir),
|
|
192
|
-
"When creating threads, pass the correct repoPath for the target workspace.",
|
|
194
|
+
"Follow AGENTS.md / CLAUDE.md in this cwd for the fleet playbook (they are the same document).",
|
|
193
195
|
`YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit parentThreadId (Sideboard binds it). Never invent a uuid.`,
|
|
194
|
-
"Omit agent/model on create_thread unless you need to override Account defaults.",
|
|
195
|
-
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn \u2192 (when ready) ask_git merge \u2192 wait_for_turn. Never target upstream. Never git/gh from this orchestration cwd.",
|
|
196
|
-
"Typical flow (new app): Bash under repos dir (clone or gh repo create) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
|
|
197
196
|
`Goal: ${opts.goal}`,
|
|
198
197
|
"Registered workspaces:",
|
|
199
198
|
formatWorkspaceInventory(opts.workspaces)
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
resolveGithubRepoSlug
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HBBXS2FR.js";
|
|
6
6
|
import {
|
|
7
7
|
resolveThreadDefaults
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-UHGN4KCL.js";
|
|
9
9
|
import {
|
|
10
10
|
globalAgentCwd,
|
|
11
11
|
sideboardReposDir
|
|
@@ -53,6 +53,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
|
|
|
53
53
|
"- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
|
|
54
54
|
"- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
|
|
55
55
|
"- list_threads / get_thread \u2014 fleet status (what is going on)",
|
|
56
|
+
"- ask_user \u2014 multiple-choice questions in the composer (any mode, not only Plan). Explain options in chat first, include a description on every option, then wait for answers.",
|
|
56
57
|
"- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for Slack / away-from-keyboard work. Turn OFF when the user says they are done, wrapping up, going to sleep, or no longer need the machine awake. Closing this chat also releases it.",
|
|
57
58
|
"Workspaces:",
|
|
58
59
|
"- add_workspace / remove_workspace \u2014 register or unregister a git repo",
|
|
@@ -71,7 +72,12 @@ var COORDINATOR_TOOL_PLAYBOOK = [
|
|
|
71
72
|
"- get_diff \u2014 compact diff summary",
|
|
72
73
|
'- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
|
|
73
74
|
"- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn. Prefer this over paraphrasing.",
|
|
74
|
-
'-
|
|
75
|
+
'- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
|
|
76
|
+
'- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
|
|
77
|
+
"Process guides:",
|
|
78
|
+
"- Recurring multi-item / fan-out: if the child worktree has `.claude/skills/graph-engineering/SKILL.md`, tell the worker to follow it (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch three threads.",
|
|
79
|
+
"- Recurring shapes: have the worktree agent write `.claude/skills/<kebab-name>/SKILL.md` (commit it) so later threads and native Claude Code / attach see it. Do not use `.sideboard/skills` for new guides. Codex/OpenCode: one line in AGENTS.md pointing at that file.",
|
|
80
|
+
"- One-offs: no guide. Same miss across threads: edit the skill or `.sideboard/review.md`, then rerun the batch \u2014 do not patch three threads.",
|
|
75
81
|
"Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
|
|
76
82
|
"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.",
|
|
77
83
|
"Bash / Read / etc: allowed for (1) inspecting target worktrees / registered repo paths from MCP, and (2) greenfield setup under ~/sideboard/repos (git clone, gh repo create, git init+remote). Never git init/clone *inside* this synthetic home cwd \u2014 emptiness here is expected, not a bug."
|
|
@@ -101,7 +107,7 @@ function coordinatorTurnReminder(opts) {
|
|
|
101
107
|
goal ? `- Goal / title: ${goal}` : null,
|
|
102
108
|
accountDefaultsPlaybookLine(),
|
|
103
109
|
`- For "what's going on": call list_threads (and list_workspaces if needed). Summarize fleet status \u2014 do not ls/git-status this synthetic home.`,
|
|
104
|
-
"- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn.
|
|
110
|
+
"- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn. Commit/push/draft PR with ask_git on the child, then wait_for_turn \u2014 never git/gh from this cwd. Call ask_git merge only if the user explicitly asked to merge.",
|
|
105
111
|
"- New repo: Bash (clone or gh repo create under ~/sideboard/repos/<name>) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
|
|
106
112
|
"- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`.",
|
|
107
113
|
"- If they will wait on Slack or leave the Mac, call set_caffeinate enabled=true. When they say they are done / wrapping up / going to sleep, call set_caffeinate enabled=false. Closing this chat also turns it off."
|
|
@@ -155,9 +161,9 @@ function ensureGlobalCoordinatorCwd(opts) {
|
|
|
155
161
|
orchId ? `Pass parentThreadId="${orchId}" (or omit it). Never invent another parentThreadId.` : "Pass `parentThreadId` for children (this chat's id from the turn reminder).",
|
|
156
162
|
"Omit `agent` / `model` on `create_thread` unless you have a reason to override Account defaults.",
|
|
157
163
|
"Never pass `agent=codex` when you yourself are Codex \u2014 nested Codex deadlocks on shared ~/.codex locks. Omit agent (Account default) or use cursor/claude.",
|
|
158
|
-
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn
|
|
164
|
+
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn. Merge only if the user explicitly asked (`ask_git` merge).",
|
|
159
165
|
"Typical flow (new app): Bash create/clone under repos dir \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft.",
|
|
160
|
-
"Always ask worktree agents to commit, push, open draft PRs
|
|
166
|
+
"Always ask worktree agents to commit, push, and open draft PRs (`ask_git` / `send_to_thread`). Tell them to merge only when the user explicitly asked. The worktree agent runs git/gh; never merge from this orchestration cwd."
|
|
161
167
|
].join("\n");
|
|
162
168
|
try {
|
|
163
169
|
writeFileSync(join(dir, "CLAUDE.md"), `${body}
|
|
@@ -170,7 +176,6 @@ function ensureGlobalCoordinatorCwd(opts) {
|
|
|
170
176
|
}
|
|
171
177
|
function coordinatorSystemPrompt(opts) {
|
|
172
178
|
const audience = opts.audience ?? "cloud";
|
|
173
|
-
const reposDir = sideboardReposDir();
|
|
174
179
|
const intro = audience === "cloud" ? [
|
|
175
180
|
"You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Discord, Teams, or other chat).",
|
|
176
181
|
"Your reply will be sent back to that cloud agent \u2014 be concise and actionable."
|
|
@@ -188,14 +193,8 @@ function coordinatorSystemPrompt(opts) {
|
|
|
188
193
|
...intro,
|
|
189
194
|
"You operate across ALL registered workspaces below.",
|
|
190
195
|
"You have no project git home \u2014 this process cwd is synthetic and empty on purpose.",
|
|
191
|
-
|
|
192
|
-
accountDefaultsPlaybookLine(),
|
|
193
|
-
coordinatorGreenfieldPlaybook(reposDir),
|
|
194
|
-
"When creating threads, pass the correct repoPath for the target workspace.",
|
|
196
|
+
"Follow AGENTS.md / CLAUDE.md in this cwd for the fleet playbook (they are the same document).",
|
|
195
197
|
`YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit parentThreadId (Sideboard binds it). Never invent a uuid.`,
|
|
196
|
-
"Omit agent/model on create_thread unless you need to override Account defaults.",
|
|
197
|
-
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 ask_git create-draft \u2192 wait_for_turn \u2192 (when ready) ask_git merge \u2192 wait_for_turn. Never target upstream. Never git/gh from this orchestration cwd.",
|
|
198
|
-
"Typical flow (new app): Bash under repos dir (clone or gh repo create) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 ask_git create-draft.",
|
|
199
198
|
`Goal: ${opts.goal}`,
|
|
200
199
|
"Registered workspaces:",
|
|
201
200
|
formatWorkspaceInventory(opts.workspaces)
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
getGithubGitAuthMode,
|
|
6
6
|
getGithubPat
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-YO3CYL6B.js";
|
|
8
8
|
import {
|
|
9
9
|
listThreads
|
|
10
10
|
} from "./chunk-FWJYLBO6.js";
|
|
@@ -882,11 +882,25 @@ function formatGhLandError(raw, opts) {
|
|
|
882
882
|
}
|
|
883
883
|
return detail || "Failed to create or update pull request";
|
|
884
884
|
}
|
|
885
|
+
function isPrNotMergeableError(text) {
|
|
886
|
+
return /not mergeable|cannot be cleanly created|cannot merge cleanly|Merge conflict|\bCONFLICTING\b|must be (updated|rebased)|branch is out of date|needs? to be (updated|rebased)|Resolve conflicts or update the branch/i.test(
|
|
887
|
+
text
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
function formatMergePrError(raw) {
|
|
891
|
+
const trimmed = raw.trim();
|
|
892
|
+
if (!trimmed) return "gh pr merge failed";
|
|
893
|
+
if (isPrNotMergeableError(trimmed)) {
|
|
894
|
+
return "This pull request cannot merge cleanly into the base branch. Resolve conflicts or update the branch, then retry.";
|
|
895
|
+
}
|
|
896
|
+
return extractGhErrorDetail(trimmed) || trimmed;
|
|
897
|
+
}
|
|
885
898
|
function formatIpcInvokeError(err) {
|
|
886
899
|
let msg = err instanceof Error ? err.message : String(err);
|
|
887
900
|
msg = msg.replace(/^Error invoking remote method '[^']+':\s*/i, "");
|
|
888
901
|
msg = msg.replace(/^ExecaError:\s*/i, "");
|
|
889
902
|
msg = msg.replace(/^Error:\s*/i, "");
|
|
903
|
+
if (isPrNotMergeableError(msg)) return formatMergePrError(msg);
|
|
890
904
|
return formatGhLandError(msg);
|
|
891
905
|
}
|
|
892
906
|
|
|
@@ -1599,6 +1613,41 @@ ${result.stderr}`;
|
|
|
1599
1613
|
].slice(0, 20) : [];
|
|
1600
1614
|
return { conflicting, base: baseName, files };
|
|
1601
1615
|
}
|
|
1616
|
+
async function probeLocalMergeGate(cwd, gate) {
|
|
1617
|
+
const mergeable = (gate?.mergeable ?? "").toUpperCase();
|
|
1618
|
+
const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
|
|
1619
|
+
const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
|
|
1620
|
+
const inQueue = gate ? prIsInMergeQueue(gate) : false;
|
|
1621
|
+
if (alreadyConflicting || inQueue) return { gate, files: [] };
|
|
1622
|
+
try {
|
|
1623
|
+
const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
|
|
1624
|
+
if (local.conflicting) {
|
|
1625
|
+
return {
|
|
1626
|
+
gate: {
|
|
1627
|
+
mergeable: "CONFLICTING",
|
|
1628
|
+
mergeStateStatus: "DIRTY",
|
|
1629
|
+
reviewDecision: gate?.reviewDecision ?? null,
|
|
1630
|
+
baseRefName: local.base,
|
|
1631
|
+
url: gate?.url ?? null,
|
|
1632
|
+
isInMergeQueue: false
|
|
1633
|
+
},
|
|
1634
|
+
files: local.files
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
|
|
1638
|
+
return {
|
|
1639
|
+
gate: {
|
|
1640
|
+
...gate,
|
|
1641
|
+
mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
|
|
1642
|
+
mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
|
|
1643
|
+
},
|
|
1644
|
+
files: []
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
} catch {
|
|
1648
|
+
}
|
|
1649
|
+
return { gate, files: [] };
|
|
1650
|
+
}
|
|
1602
1651
|
async function getPrChecks(cwd, selector) {
|
|
1603
1652
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
1604
1653
|
const args = [
|
|
@@ -1634,47 +1683,9 @@ async function getPrChecks(cwd, selector) {
|
|
|
1634
1683
|
}
|
|
1635
1684
|
}
|
|
1636
1685
|
let gate = await fetchPrMergeGate(cwd, selector, slug);
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
const
|
|
1640
|
-
const inQueue = gate ? prIsInMergeQueue(gate) : false;
|
|
1641
|
-
const needsLocalProbe = !alreadyConflicting && !inQueue;
|
|
1642
|
-
if (needsLocalProbe) {
|
|
1643
|
-
try {
|
|
1644
|
-
const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
|
|
1645
|
-
if (local.conflicting) {
|
|
1646
|
-
const fileHint = local.files.length > 0 ? ` Conflicting paths: ${local.files.slice(0, 8).join(", ")}${local.files.length > 8 ? "\u2026" : ""}.` : "";
|
|
1647
|
-
gate = {
|
|
1648
|
-
mergeable: "CONFLICTING",
|
|
1649
|
-
mergeStateStatus: "DIRTY",
|
|
1650
|
-
reviewDecision: gate?.reviewDecision ?? null,
|
|
1651
|
-
baseRefName: local.base,
|
|
1652
|
-
url: gate?.url ?? null,
|
|
1653
|
-
isInMergeQueue: false
|
|
1654
|
-
};
|
|
1655
|
-
const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
|
|
1656
|
-
const review2 = (gate.reviewDecision ?? "").toUpperCase();
|
|
1657
|
-
const hasReviewRow2 = review2 === "CHANGES_REQUESTED" || review2 === "REVIEW_REQUIRED";
|
|
1658
|
-
const gateRows2 = buildMergeGateChecks(gate, {
|
|
1659
|
-
suppressGenericBlocked: ciFailed2 || hasReviewRow2
|
|
1660
|
-
}).map(
|
|
1661
|
-
(row) => row.kind === "mergeability" && row.name === "Merge conflicts" ? {
|
|
1662
|
-
...row,
|
|
1663
|
-
description: `${row.description ?? ""}${fileHint}`.trim()
|
|
1664
|
-
} : row
|
|
1665
|
-
);
|
|
1666
|
-
return [...gateRows2, ...ciChecks];
|
|
1667
|
-
}
|
|
1668
|
-
if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
|
|
1669
|
-
gate = {
|
|
1670
|
-
...gate,
|
|
1671
|
-
mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
|
|
1672
|
-
mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
|
|
1673
|
-
};
|
|
1674
|
-
}
|
|
1675
|
-
} catch {
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1686
|
+
const probed = await probeLocalMergeGate(cwd, gate);
|
|
1687
|
+
gate = probed.gate;
|
|
1688
|
+
const fileHint = probed.files.length > 0 ? ` Conflicting paths: ${probed.files.slice(0, 8).join(", ")}${probed.files.length > 8 ? "\u2026" : ""}.` : "";
|
|
1678
1689
|
if (!gate) {
|
|
1679
1690
|
return ciChecks;
|
|
1680
1691
|
}
|
|
@@ -1684,7 +1695,12 @@ async function getPrChecks(cwd, selector) {
|
|
|
1684
1695
|
const gateRows = buildMergeGateChecks(gate, {
|
|
1685
1696
|
// Avoid duplicating "blocked" when CI failures or review rows already explain it.
|
|
1686
1697
|
suppressGenericBlocked: ciFailed || hasReviewRow
|
|
1687
|
-
})
|
|
1698
|
+
}).map(
|
|
1699
|
+
(row) => fileHint && row.kind === "mergeability" && row.name === "Merge conflicts" ? {
|
|
1700
|
+
...row,
|
|
1701
|
+
description: `${row.description ?? ""}${fileHint}`.trim()
|
|
1702
|
+
} : row
|
|
1703
|
+
);
|
|
1688
1704
|
return [...gateRows, ...ciChecks];
|
|
1689
1705
|
}
|
|
1690
1706
|
async function getPrMeta(cwd, selector) {
|
|
@@ -1694,7 +1710,7 @@ async function getPrMeta(cwd, selector) {
|
|
|
1694
1710
|
"view",
|
|
1695
1711
|
selector,
|
|
1696
1712
|
"--json",
|
|
1697
|
-
"number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus"
|
|
1713
|
+
"number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus,mergeable"
|
|
1698
1714
|
];
|
|
1699
1715
|
if (slug) viewArgs.push("--repo", slug);
|
|
1700
1716
|
let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
|
|
@@ -1704,7 +1720,7 @@ async function getPrMeta(cwd, selector) {
|
|
|
1704
1720
|
"view",
|
|
1705
1721
|
selector,
|
|
1706
1722
|
"--json",
|
|
1707
|
-
"number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus"
|
|
1723
|
+
"number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus,mergeable"
|
|
1708
1724
|
];
|
|
1709
1725
|
if (slug) retry.push("--repo", slug);
|
|
1710
1726
|
({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
|
|
@@ -1715,16 +1731,28 @@ async function getPrMeta(cwd, selector) {
|
|
|
1715
1731
|
}
|
|
1716
1732
|
try {
|
|
1717
1733
|
const view = JSON.parse(stdout);
|
|
1734
|
+
const gate = {
|
|
1735
|
+
mergeable: typeof view.mergeable === "string" && view.mergeable ? view.mergeable : null,
|
|
1736
|
+
mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
|
|
1737
|
+
reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
|
|
1738
|
+
baseRefName: String(view.baseRefName ?? ""),
|
|
1739
|
+
url: String(view.url ?? ""),
|
|
1740
|
+
isInMergeQueue: parseInMergeQueue(view)
|
|
1741
|
+
};
|
|
1742
|
+
const probed = await probeLocalMergeGate(cwd, gate);
|
|
1743
|
+
const resolved = probed.gate ?? gate;
|
|
1718
1744
|
return {
|
|
1719
1745
|
number: Number(view.number),
|
|
1720
1746
|
title: String(view.title ?? ""),
|
|
1721
1747
|
url: String(view.url ?? ""),
|
|
1722
1748
|
state: String(view.state ?? ""),
|
|
1723
1749
|
isDraft: Boolean(view.isDraft),
|
|
1724
|
-
reviewDecision:
|
|
1725
|
-
baseRefName: String(view.baseRefName ?? ""),
|
|
1750
|
+
reviewDecision: resolved.reviewDecision,
|
|
1751
|
+
baseRefName: resolved.baseRefName || String(view.baseRefName ?? ""),
|
|
1726
1752
|
headRefName: String(view.headRefName ?? ""),
|
|
1727
|
-
isInMergeQueue:
|
|
1753
|
+
isInMergeQueue: Boolean(resolved.isInMergeQueue),
|
|
1754
|
+
mergeable: resolved.mergeable,
|
|
1755
|
+
mergeStateStatus: resolved.mergeStateStatus
|
|
1728
1756
|
};
|
|
1729
1757
|
} catch {
|
|
1730
1758
|
return null;
|
|
@@ -2192,7 +2220,9 @@ async function mergePr(cwd, selector, opts) {
|
|
|
2192
2220
|
if (slug) args.push("--repo", slug);
|
|
2193
2221
|
const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
|
|
2194
2222
|
if (exitCode !== 0) {
|
|
2195
|
-
throw new Error(
|
|
2223
|
+
throw new Error(
|
|
2224
|
+
formatMergePrError(stderr.trim() || stdout.trim() || "gh pr merge failed")
|
|
2225
|
+
);
|
|
2196
2226
|
}
|
|
2197
2227
|
const after = await gh(viewArgs, cwd, { reject: false });
|
|
2198
2228
|
if (after.exitCode === 0 && after.stdout.trim()) {
|
|
@@ -2353,6 +2383,8 @@ export {
|
|
|
2353
2383
|
formatRateLimitResetHint,
|
|
2354
2384
|
extractGhErrorDetail,
|
|
2355
2385
|
formatGhLandError,
|
|
2386
|
+
isPrNotMergeableError,
|
|
2387
|
+
formatMergePrError,
|
|
2356
2388
|
formatIpcInvokeError,
|
|
2357
2389
|
appendIndexedGitConfig,
|
|
2358
2390
|
githubAgentGitEnv,
|