@sideboard-ai/core 0.1.9

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.
@@ -0,0 +1,198 @@
1
+ import {
2
+ ensureGlobalCoordinatorCwd
3
+ } from "./chunk-2R5VV4BA.js";
4
+ import {
5
+ allocateTeamName,
6
+ takenSlugsFromThread,
7
+ teamSlugFromName
8
+ } from "./chunk-LL7DTZ5B.js";
9
+ import {
10
+ createEmptyThread,
11
+ listThreads,
12
+ updateThread,
13
+ writeThread
14
+ } from "./chunk-HYRHI3QU.js";
15
+ import {
16
+ globalAgentCwd
17
+ } from "./chunk-M37RITA6.js";
18
+
19
+ // src/brightsy/cloud-connect-constants.ts
20
+ var CLOUD_ORCHESTRATOR_GOAL = "Cloud-connected Sideboard orchestrator";
21
+ var SIDEBOARD_FORCE_STOP = "SIDEBOARD_FORCE_STOP";
22
+ var CLOUD_COORDINATOR_BUSY_REPLY = [
23
+ "Sideboard is busy with an in-progress client/desktop tool turn on the global coordinator.",
24
+ "I did not start a new orchestration turn.",
25
+ `To force-stop the in-progress turn, send another desktop request whose first line is exactly ${SIDEBOARD_FORCE_STOP}`,
26
+ "(optional follow-up request on later lines).",
27
+ "What do you want me to do? (retry later, wait until idle, force-stop, rephrase, or cancel)"
28
+ ].join(" ");
29
+ var CLOUD_COORDINATOR_STOPPED_REPLY = [
30
+ "Sideboard force-stopped the in-progress global coordinator turn.",
31
+ "No new orchestration request was started."
32
+ ].join(" ");
33
+ var CLOUD_COORDINATOR_TIMEOUT_REPLY = [
34
+ "Sideboard global coordinator timed out waiting for the local agent turn to finish.",
35
+ "I did not produce a full orchestration result.",
36
+ `To force-stop the in-progress turn, send another desktop request whose first line is exactly ${SIDEBOARD_FORCE_STOP}`,
37
+ "(optional follow-up request on later lines).",
38
+ "What do you want me to do? (retry later, wait, force-stop, rephrase, or cancel)"
39
+ ].join(" ");
40
+ function parseForceStopMessage(message) {
41
+ const normalized = message.replace(/\r\n/g, "\n");
42
+ const nl = normalized.indexOf("\n");
43
+ const firstLine = (nl === -1 ? normalized : normalized.slice(0, nl)).trim();
44
+ const remainder = (nl === -1 ? "" : normalized.slice(nl + 1)).trim();
45
+ const forceStop = firstLine.toLowerCase() === SIDEBOARD_FORCE_STOP.toLowerCase();
46
+ return { forceStop, remainder: forceStop ? remainder : message };
47
+ }
48
+
49
+ // src/store/global-workspace.ts
50
+ var GLOBAL_WORKSPACE_ID = "__global__";
51
+ function isGlobalThread(thread) {
52
+ return Boolean(thread && thread.repoPath === GLOBAL_WORKSPACE_ID);
53
+ }
54
+ function isGlobalRepoPath(repoPath) {
55
+ return repoPath === GLOBAL_WORKSPACE_ID;
56
+ }
57
+ function isOrchestratorThread(thread) {
58
+ return Boolean(
59
+ thread && (thread.sourceType === "orchestration" || isGlobalThread(thread))
60
+ );
61
+ }
62
+ function orchestratorSessionPoisonedByBuiltins(thread) {
63
+ if (!thread?.messages?.length) return false;
64
+ let usedBuiltin = false;
65
+ let usedSideboardMcp = false;
66
+ for (const msg of thread.messages) {
67
+ for (const part of msg.parts ?? []) {
68
+ if (part.type !== "tool") continue;
69
+ if (part.name.startsWith("mcp__sideboard")) {
70
+ usedSideboardMcp = true;
71
+ } else if (part.name === "Bash" || part.name === "Edit" || part.name === "Write" || part.name === "Read" || part.name === "Glob" || part.name === "Grep") {
72
+ usedBuiltin = true;
73
+ }
74
+ }
75
+ }
76
+ return usedBuiltin && !usedSideboardMcp;
77
+ }
78
+ function isCloudCoordinatorThread(thread) {
79
+ if (thread.sourceRef === CLOUD_ORCHESTRATOR_GOAL) {
80
+ return isGlobalThread(thread) || thread.sourceType === "orchestration";
81
+ }
82
+ if (thread.title === CLOUD_ORCHESTRATOR_GOAL) {
83
+ return isGlobalThread(thread) || thread.sourceType === "orchestration";
84
+ }
85
+ return false;
86
+ }
87
+ function orchestrationTitleNeedsSoccerNickname(thread) {
88
+ if (!isOrchestratorThread(thread)) return false;
89
+ const title = thread.title?.trim() ?? "";
90
+ if (teamSlugFromName(title)) return false;
91
+ if (title === CLOUD_ORCHESTRATOR_GOAL) return true;
92
+ if (!title || title === "Untitled") return true;
93
+ if (thread.userSetTitle) return false;
94
+ if (thread.sourceRef?.trim() && title === thread.sourceRef.trim()) return true;
95
+ return false;
96
+ }
97
+ function takenTeamSlugsForOrchestration() {
98
+ const taken = /* @__PURE__ */ new Set(["global"]);
99
+ for (const thread of listThreads({ includeArchived: true })) {
100
+ if (thread.status === "archived") continue;
101
+ for (const slug of takenSlugsFromThread(thread)) {
102
+ taken.add(slug);
103
+ }
104
+ }
105
+ return [...taken];
106
+ }
107
+ function createGlobalChat(opts) {
108
+ ensureGlobalCoordinatorCwd();
109
+ const isCloud = opts.sourceRef === CLOUD_ORCHESTRATOR_GOAL || opts.title?.trim() === CLOUD_ORCHESTRATOR_GOAL;
110
+ const explicit = opts.title?.trim();
111
+ const title = explicit && explicit !== CLOUD_ORCHESTRATOR_GOAL ? explicit : allocateTeamName(takenTeamSlugsForOrchestration()).name;
112
+ const sourceRef = opts.sourceRef?.trim() || (isCloud ? CLOUD_ORCHESTRATOR_GOAL : title);
113
+ const thread = createEmptyThread({
114
+ title,
115
+ // Stick nicknames the same way chat tabs do (avoid later sync overwrites).
116
+ userSetTitle: true,
117
+ sourceType: "orchestration",
118
+ sourceRef,
119
+ branchName: "global",
120
+ worktreePath: globalAgentCwd(),
121
+ repoPath: GLOBAL_WORKSPACE_ID,
122
+ agent: opts.agent,
123
+ autonomy: opts.autonomy ?? "default",
124
+ model: opts.model ?? null,
125
+ fast: Boolean(opts.fast),
126
+ planMode: Boolean(opts.planMode),
127
+ attachments: opts.attachments ?? [],
128
+ parentThreadId: opts.parentThreadId ?? null,
129
+ status: "idle"
130
+ });
131
+ writeThread(thread);
132
+ return thread;
133
+ }
134
+ function healOrchestrationSoccerTitles() {
135
+ let healed = 0;
136
+ const taken = new Set(takenTeamSlugsForOrchestration());
137
+ for (const thread of listThreads({ includeArchived: true })) {
138
+ if (thread.status === "archived") continue;
139
+ if (!orchestrationTitleNeedsSoccerNickname(thread)) continue;
140
+ const team = allocateTeamName(taken);
141
+ taken.add(team.slug);
142
+ updateThread(thread.id, { title: team.name, userSetTitle: true });
143
+ healed += 1;
144
+ }
145
+ return healed;
146
+ }
147
+ function listGlobalThreads(includeArchived = false) {
148
+ return listThreads({ includeArchived }).filter(
149
+ (t) => t.repoPath === GLOBAL_WORKSPACE_ID
150
+ );
151
+ }
152
+ function findCloudCoordinator() {
153
+ return listThreads({ includeArchived: true }).find(
154
+ (t) => t.status !== "archived" && isCloudCoordinatorThread(t)
155
+ );
156
+ }
157
+ function ensureCloudCoordinator(agent) {
158
+ const existing = findCloudCoordinator();
159
+ if (existing) {
160
+ if (existing.repoPath !== GLOBAL_WORKSPACE_ID) {
161
+ return updateThread(existing.id, {
162
+ repoPath: GLOBAL_WORKSPACE_ID,
163
+ worktreePath: globalAgentCwd(),
164
+ branchName: "global"
165
+ });
166
+ }
167
+ return existing;
168
+ }
169
+ const created = createGlobalChat({
170
+ sourceRef: CLOUD_ORCHESTRATOR_GOAL,
171
+ agent
172
+ });
173
+ const all = listThreads({ includeArchived: true }).filter(
174
+ (t) => t.status !== "archived" && isCloudCoordinatorThread(t)
175
+ ).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
176
+ return all[0] ?? created;
177
+ }
178
+
179
+ export {
180
+ CLOUD_ORCHESTRATOR_GOAL,
181
+ SIDEBOARD_FORCE_STOP,
182
+ CLOUD_COORDINATOR_BUSY_REPLY,
183
+ CLOUD_COORDINATOR_STOPPED_REPLY,
184
+ CLOUD_COORDINATOR_TIMEOUT_REPLY,
185
+ parseForceStopMessage,
186
+ GLOBAL_WORKSPACE_ID,
187
+ isGlobalThread,
188
+ isGlobalRepoPath,
189
+ isOrchestratorThread,
190
+ orchestratorSessionPoisonedByBuiltins,
191
+ isCloudCoordinatorThread,
192
+ orchestrationTitleNeedsSoccerNickname,
193
+ takenTeamSlugsForOrchestration,
194
+ createGlobalChat,
195
+ healOrchestrationSoccerTitles,
196
+ listGlobalThreads,
197
+ ensureCloudCoordinator
198
+ };
@@ -0,0 +1,143 @@
1
+ import {
2
+ resolveGithubRepoSlug
3
+ } from "./chunk-LL7DTZ5B.js";
4
+ import {
5
+ globalAgentCwd,
6
+ sideboardReposDir
7
+ } from "./chunk-M37RITA6.js";
8
+
9
+ // src/orchestrator/coordinator-prompt.ts
10
+ import { mkdirSync, writeFileSync } from "fs";
11
+ import { join } from "path";
12
+ function formatWorkspaceInventory(workspaces) {
13
+ if (workspaces.length === 0) return "(no registered workspaces)";
14
+ return workspaces.map((w) => {
15
+ const slug = w.githubSlug?.trim() ? ` github:${w.githubSlug.trim()}` : "";
16
+ return `- ${w.name}: ${w.path}${slug}`;
17
+ }).join("\n");
18
+ }
19
+ async function enrichWorkspacesWithGithub(workspaces) {
20
+ return Promise.all(
21
+ workspaces.map(async (w) => {
22
+ const githubSlug = await resolveGithubRepoSlug(w.path).catch(() => null);
23
+ return { ...w, githubSlug };
24
+ })
25
+ );
26
+ }
27
+ function coordinatorGreenfieldPlaybook(reposDir) {
28
+ return [
29
+ "Greenfield (new app / new GitHub repo) \u2014 use Bash + MCP:",
30
+ `- Create or clone under \`${reposDir}/<name>\` (never inside this synthetic home cwd).`,
31
+ "- Examples:",
32
+ ` - Clone: \`git clone <url> ${reposDir}/<name>\``,
33
+ ` - New GitHub repo: \`gh repo create <owner>/<name> --private --clone -- ${reposDir}/<name>\` (or mkdir + git init + gh repo create + remote add + push)`,
34
+ "- Then: add_workspace with that absolute path \u2192 create_thread (repoPath + parentThreadId) \u2192 send_to_thread (build) \u2192 wait_for_turn \u2192 send_to_thread (`gh pr create --draft`) or create_draft_pr.",
35
+ "- Do coding work in the child worktree thread, not by editing files in this home cwd."
36
+ ].join("\n");
37
+ }
38
+ var COORDINATOR_TOOL_PLAYBOOK = [
39
+ "Role: you oversee Sideboard worktree agents across registered repos. You do not live inside one of those worktrees.",
40
+ "Sideboard MCP (fleet control \u2014 prefer these for status and orchestration):",
41
+ "Discover:",
42
+ "- list_workspaces \u2014 registered repos (path + github slug when known)",
43
+ "- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
44
+ "- list_threads / get_thread \u2014 fleet status (what is going on)",
45
+ "Workspaces:",
46
+ "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
47
+ "Worktree threads (chats):",
48
+ "- create_thread \u2014 create a worktree + chat from branch | pr | ticket; pass repoPath + parentThreadId",
49
+ "- 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",
50
+ "- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply",
51
+ "- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
52
+ "- archive_thread / restore_thread \u2014 archive (tears down worktree when last tab) or restore",
53
+ "Setup / run:",
54
+ "- run_setup \u2014 re-run worktree setup",
55
+ "- list_run_scripts / run_dev_script / stop_dev_script \u2014 start/stop named run scripts",
56
+ "Inspect / PRs:",
57
+ "- get_diff \u2014 compact diff summary",
58
+ "- preview_land \u2014 preview push+PR (does not push)",
59
+ "- Prefer asking the worktree agent via send_to_thread to open a draft PR (`gh pr create --draft`) so it owns title/body from the diff.",
60
+ "- create_draft_pr \u2014 fallback: commit (if dirty), push, open/update a DRAFT PR from the orchestrator",
61
+ "Human-only (do not attempt): ready-for-review confirm_land, purge_thread.",
62
+ "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.",
63
+ "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."
64
+ ].join("\n");
65
+ function coordinatorTurnReminder(opts) {
66
+ const goal = opts.goal?.trim();
67
+ return [
68
+ "Sideboard Orchestration (mandatory):",
69
+ "- You oversee Sideboard worktree agents. You are not yourself checked out in a project worktree.",
70
+ "- This cwd is a synthetic empty home (not a git repo). Emptiness here is expected \u2014 it is not a problem to fix.",
71
+ "- Registered workspaces / child threads are the fleet you manage via Sideboard MCP.",
72
+ `- Parent thread id (pass as parentThreadId when creating children): ${opts.parentId}`,
73
+ goal ? `- Goal / title: ${goal}` : null,
74
+ `- 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.`,
75
+ "- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn.",
76
+ "- 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 draft PR.",
77
+ "- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`."
78
+ ].filter(Boolean).join("\n");
79
+ }
80
+ function ensureGlobalCoordinatorCwd() {
81
+ const dir = globalAgentCwd();
82
+ mkdirSync(dir, { recursive: true });
83
+ const reposDir = sideboardReposDir();
84
+ const body = [
85
+ "# Sideboard Orchestration",
86
+ "",
87
+ "You are the Sideboard **Orchestration** agent \u2014 you oversee worktree agents in the Sideboard app.",
88
+ "You are **not** connected to a single project workspace. This directory is a synthetic empty cwd (not a git worktree).",
89
+ "It being empty / not a git repo is **normal**. Do not initialize git here or ask the user to point you at a repo for *your* checkout.",
90
+ "Repos from `list_workspaces` and threads from `list_threads` are the fleet you orchestrate.",
91
+ 'For status questions ("what\'s going on?"), use `list_threads` / `list_workspaces` \u2014 never diagnose this synthetic home as a broken worktree.',
92
+ "Bash is fine for inspecting **child worktree** / registered-repo paths, and for greenfield repo setup under the Sideboard repos directory \u2014 not for treating this home as the project.",
93
+ "",
94
+ COORDINATOR_TOOL_PLAYBOOK,
95
+ "",
96
+ coordinatorGreenfieldPlaybook(reposDir),
97
+ "",
98
+ "When creating threads, pass `repoPath` from `list_workspaces` (or the path you just registered) and `parentThreadId` for children.",
99
+ "Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn.",
100
+ "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 draft PR.",
101
+ "Prefer asking worktree agents to open draft PRs; use create_draft_pr as fallback."
102
+ ].join("\n");
103
+ writeFileSync(join(dir, "CLAUDE.md"), `${body}
104
+ `, "utf8");
105
+ writeFileSync(join(dir, "AGENTS.md"), `${body}
106
+ `, "utf8");
107
+ return dir;
108
+ }
109
+ function coordinatorSystemPrompt(opts) {
110
+ const audience = opts.audience ?? "cloud";
111
+ const reposDir = sideboardReposDir();
112
+ const intro = audience === "cloud" ? [
113
+ "You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Slack, Discord, Teams, or other chat).",
114
+ "Your reply will be sent back to that cloud agent \u2014 be concise and actionable."
115
+ ] : [
116
+ "You are a Sideboard orchestration agent: you oversee worktree agents across registered workspaces in the Sideboard app.",
117
+ "Stay concise and actionable; prefer Sideboard MCP for fleet status; use Bash for greenfield repo setup and inspecting target worktree paths."
118
+ ];
119
+ return [
120
+ ...intro,
121
+ "You operate across ALL registered workspaces below.",
122
+ "You have no project git home \u2014 this process cwd is synthetic and empty on purpose.",
123
+ COORDINATOR_TOOL_PLAYBOOK,
124
+ coordinatorGreenfieldPlaybook(reposDir),
125
+ "When creating threads, pass the correct repoPath for the target workspace and parentThreadId for children.",
126
+ "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 send_to_thread (ask for `gh pr create --draft`) \u2192 wait_for_turn. Use create_draft_pr only if the child cannot open the PR.",
127
+ "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 draft PR.",
128
+ `Goal: ${opts.goal}`,
129
+ `Parent thread id (pass as parentThreadId when creating children): ${opts.parentId}`,
130
+ "Registered workspaces:",
131
+ formatWorkspaceInventory(opts.workspaces)
132
+ ].join("\n");
133
+ }
134
+
135
+ export {
136
+ formatWorkspaceInventory,
137
+ enrichWorkspacesWithGithub,
138
+ coordinatorGreenfieldPlaybook,
139
+ COORDINATOR_TOOL_PLAYBOOK,
140
+ coordinatorTurnReminder,
141
+ ensureGlobalCoordinatorCwd,
142
+ coordinatorSystemPrompt
143
+ };
@@ -0,0 +1,92 @@
1
+ // src/agents/cursor-events.ts
2
+ function usageFromCursor(usage) {
3
+ if (!usage) return null;
4
+ const inputTokens = Number(usage.inputTokens ?? 0);
5
+ const outputTokens = Number(usage.outputTokens ?? 0);
6
+ if (!inputTokens && !outputTokens) return null;
7
+ return {
8
+ inputTokens,
9
+ outputTokens,
10
+ cacheReadTokens: usage.cacheReadTokens ? Number(usage.cacheReadTokens) : void 0,
11
+ cacheWriteTokens: usage.cacheWriteTokens ? Number(usage.cacheWriteTokens) : void 0
12
+ };
13
+ }
14
+ function cursorSdkMessageToEvents(msg) {
15
+ if (!msg?.type) return [];
16
+ if (msg.type === "system" && msg.agent_id) {
17
+ return [{ type: "session_id", data: msg.agent_id }];
18
+ }
19
+ if (msg.type === "thinking" && msg.text) {
20
+ return [{ type: "thinking", data: msg.text }];
21
+ }
22
+ if (msg.type === "assistant" && msg.message?.content?.length) {
23
+ const out = [];
24
+ for (const block of msg.message.content) {
25
+ if (block?.type === "text" && block.text) {
26
+ out.push({ type: "stdout", data: block.text });
27
+ } else if (block?.type === "tool_use" && block.id && block.name) {
28
+ out.push({
29
+ type: "tool_use",
30
+ id: block.id,
31
+ name: block.name,
32
+ input: block.input && typeof block.input === "object" ? block.input : void 0
33
+ });
34
+ }
35
+ }
36
+ return out;
37
+ }
38
+ if (msg.type === "tool_call" && msg.call_id && msg.name) {
39
+ if (msg.status === "running") {
40
+ return [
41
+ {
42
+ type: "tool_use",
43
+ id: msg.call_id,
44
+ name: msg.name,
45
+ input: msg.args && typeof msg.args === "object" ? msg.args : void 0
46
+ }
47
+ ];
48
+ }
49
+ if (msg.status === "completed" || msg.status === "error") {
50
+ const content = typeof msg.result === "string" ? msg.result : msg.result != null ? JSON.stringify(msg.result) : void 0;
51
+ return [
52
+ {
53
+ type: "tool_result",
54
+ id: msg.call_id,
55
+ content,
56
+ isError: msg.status === "error"
57
+ }
58
+ ];
59
+ }
60
+ }
61
+ if (msg.type === "usage") {
62
+ const usage = usageFromCursor(msg.usage);
63
+ if (usage) return [{ type: "usage", data: usage }];
64
+ }
65
+ if (msg.type === "status" && msg.status === "ERROR") {
66
+ const detail = msg.message || msg.text || "Cursor run entered ERROR status";
67
+ return [{ type: "stderr", data: detail }];
68
+ }
69
+ return [];
70
+ }
71
+ function parseCursorRunnerLine(line) {
72
+ const trimmed = line.trim();
73
+ if (!trimmed) return null;
74
+ try {
75
+ const obj = JSON.parse(trimmed);
76
+ if (Array.isArray(obj)) return obj;
77
+ if (obj && typeof obj === "object" && "events" in obj && Array.isArray(obj.events)) {
78
+ return obj.events;
79
+ }
80
+ if (obj && typeof obj === "object" && "type" in obj) {
81
+ return obj;
82
+ }
83
+ return null;
84
+ } catch {
85
+ return { type: "stdout", data: line };
86
+ }
87
+ }
88
+
89
+ export {
90
+ cursorSdkMessageToEvents,
91
+ parseCursorRunnerLine
92
+ };