@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
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
assertOrchestratorCapableAgent
|
|
5
|
+
} from "./chunk-S2LL5HA4.js";
|
|
6
|
+
import {
|
|
7
|
+
ensureGlobalCoordinatorCwd
|
|
8
|
+
} from "./chunk-GZXBYHJC.js";
|
|
9
|
+
import {
|
|
10
|
+
allocateTeamName,
|
|
11
|
+
takenSlugsFromThread,
|
|
12
|
+
teamSlugFromName
|
|
13
|
+
} from "./chunk-ZVV5EEQN.js";
|
|
14
|
+
import {
|
|
15
|
+
createEmptyThread,
|
|
16
|
+
listThreads,
|
|
17
|
+
updateThread,
|
|
18
|
+
writeThread
|
|
19
|
+
} from "./chunk-EOYDCKQC.js";
|
|
20
|
+
import {
|
|
21
|
+
globalAgentCwd
|
|
22
|
+
} from "./chunk-7MV3RXSC.js";
|
|
23
|
+
|
|
24
|
+
// src/brightsy/cloud-connect-constants.ts
|
|
25
|
+
var CLOUD_ORCHESTRATOR_GOAL = "Cloud-connected Sideboard orchestrator";
|
|
26
|
+
var SIDEBOARD_FORCE_STOP = "SIDEBOARD_FORCE_STOP";
|
|
27
|
+
var CLOUD_COORDINATOR_BUSY_REPLY = [
|
|
28
|
+
"Sideboard is busy with an in-progress client/desktop tool turn on the global coordinator.",
|
|
29
|
+
"I did not start a new orchestration turn.",
|
|
30
|
+
`To force-stop the in-progress turn, send another desktop request whose first line is exactly ${SIDEBOARD_FORCE_STOP}`,
|
|
31
|
+
"(optional follow-up request on later lines).",
|
|
32
|
+
"What do you want me to do? (retry later, wait until idle, force-stop, rephrase, or cancel)"
|
|
33
|
+
].join(" ");
|
|
34
|
+
var CLOUD_COORDINATOR_STOPPED_REPLY = [
|
|
35
|
+
"Sideboard force-stopped the in-progress global coordinator turn.",
|
|
36
|
+
"No new orchestration request was started."
|
|
37
|
+
].join(" ");
|
|
38
|
+
var CLOUD_COORDINATOR_TIMEOUT_REPLY = [
|
|
39
|
+
"Sideboard global coordinator timed out waiting for the local agent turn to finish.",
|
|
40
|
+
"I did not produce a full orchestration result.",
|
|
41
|
+
`To force-stop the in-progress turn, send another desktop request whose first line is exactly ${SIDEBOARD_FORCE_STOP}`,
|
|
42
|
+
"(optional follow-up request on later lines).",
|
|
43
|
+
"What do you want me to do? (retry later, wait, force-stop, rephrase, or cancel)"
|
|
44
|
+
].join(" ");
|
|
45
|
+
|
|
46
|
+
// src/store/global-workspace.ts
|
|
47
|
+
var GLOBAL_WORKSPACE_ID = "__global__";
|
|
48
|
+
function isGlobalThread(thread) {
|
|
49
|
+
return Boolean(thread && thread.repoPath === GLOBAL_WORKSPACE_ID);
|
|
50
|
+
}
|
|
51
|
+
function isGlobalRepoPath(repoPath) {
|
|
52
|
+
return repoPath === GLOBAL_WORKSPACE_ID;
|
|
53
|
+
}
|
|
54
|
+
function isOrchestratorThread(thread) {
|
|
55
|
+
return Boolean(
|
|
56
|
+
thread && (thread.sourceType === "orchestration" || isGlobalThread(thread))
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function orchestratorSessionPoisonedByBuiltins(thread) {
|
|
60
|
+
if (!thread?.messages?.length) return false;
|
|
61
|
+
let usedBuiltin = false;
|
|
62
|
+
let usedSideboardMcp = false;
|
|
63
|
+
for (const msg of thread.messages) {
|
|
64
|
+
for (const part of msg.parts ?? []) {
|
|
65
|
+
if (part.type !== "tool") continue;
|
|
66
|
+
if (part.name.startsWith("mcp__sideboard")) {
|
|
67
|
+
usedSideboardMcp = true;
|
|
68
|
+
} else if (part.name === "Bash" || part.name === "Edit" || part.name === "Write" || part.name === "Read" || part.name === "Glob" || part.name === "Grep") {
|
|
69
|
+
usedBuiltin = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return usedBuiltin && !usedSideboardMcp;
|
|
74
|
+
}
|
|
75
|
+
function isCloudCoordinatorThread(thread) {
|
|
76
|
+
if (thread.sourceRef === CLOUD_ORCHESTRATOR_GOAL) {
|
|
77
|
+
return isGlobalThread(thread) || thread.sourceType === "orchestration";
|
|
78
|
+
}
|
|
79
|
+
if (thread.title === CLOUD_ORCHESTRATOR_GOAL) {
|
|
80
|
+
return isGlobalThread(thread) || thread.sourceType === "orchestration";
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
function orchestrationTitleNeedsSoccerNickname(thread) {
|
|
85
|
+
if (!isOrchestratorThread(thread)) return false;
|
|
86
|
+
const title = thread.title?.trim() ?? "";
|
|
87
|
+
if (teamSlugFromName(title)) return false;
|
|
88
|
+
if (title === CLOUD_ORCHESTRATOR_GOAL) return true;
|
|
89
|
+
if (!title || title === "Untitled") return true;
|
|
90
|
+
if (thread.userSetTitle) return false;
|
|
91
|
+
if (thread.sourceRef?.trim() && title === thread.sourceRef.trim()) return true;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function takenTeamSlugsForOrchestration() {
|
|
95
|
+
const taken = /* @__PURE__ */ new Set(["global"]);
|
|
96
|
+
for (const thread of listThreads({ includeArchived: true })) {
|
|
97
|
+
if (thread.status === "archived") continue;
|
|
98
|
+
for (const slug of takenSlugsFromThread(thread)) {
|
|
99
|
+
taken.add(slug);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return [...taken];
|
|
103
|
+
}
|
|
104
|
+
function createGlobalChat(opts) {
|
|
105
|
+
ensureGlobalCoordinatorCwd();
|
|
106
|
+
const isCloud = opts.sourceRef === CLOUD_ORCHESTRATOR_GOAL || opts.title?.trim() === CLOUD_ORCHESTRATOR_GOAL;
|
|
107
|
+
const explicit = opts.title?.trim();
|
|
108
|
+
const title = explicit && explicit !== CLOUD_ORCHESTRATOR_GOAL ? explicit : allocateTeamName(takenTeamSlugsForOrchestration()).name;
|
|
109
|
+
const sourceRef = opts.sourceRef?.trim() || (isCloud ? CLOUD_ORCHESTRATOR_GOAL : title);
|
|
110
|
+
const agent = assertOrchestratorCapableAgent(opts.agent);
|
|
111
|
+
const thread = createEmptyThread({
|
|
112
|
+
title,
|
|
113
|
+
// Stick nicknames the same way chat tabs do (avoid later sync overwrites).
|
|
114
|
+
userSetTitle: true,
|
|
115
|
+
sourceType: "orchestration",
|
|
116
|
+
sourceRef,
|
|
117
|
+
branchName: "global",
|
|
118
|
+
worktreePath: globalAgentCwd(),
|
|
119
|
+
repoPath: GLOBAL_WORKSPACE_ID,
|
|
120
|
+
agent,
|
|
121
|
+
autonomy: opts.autonomy ?? "default",
|
|
122
|
+
model: opts.model ?? null,
|
|
123
|
+
effort: opts.effort ?? "high",
|
|
124
|
+
fast: Boolean(opts.fast),
|
|
125
|
+
planMode: Boolean(opts.planMode),
|
|
126
|
+
attachments: opts.attachments ?? [],
|
|
127
|
+
parentThreadId: opts.parentThreadId ?? null,
|
|
128
|
+
status: "idle"
|
|
129
|
+
});
|
|
130
|
+
writeThread(thread);
|
|
131
|
+
return thread;
|
|
132
|
+
}
|
|
133
|
+
function healOrchestrationSoccerTitles() {
|
|
134
|
+
let healed = 0;
|
|
135
|
+
const taken = new Set(takenTeamSlugsForOrchestration());
|
|
136
|
+
for (const thread of listThreads({ includeArchived: true })) {
|
|
137
|
+
if (thread.status === "archived") continue;
|
|
138
|
+
if (!orchestrationTitleNeedsSoccerNickname(thread)) continue;
|
|
139
|
+
const team = allocateTeamName(taken);
|
|
140
|
+
taken.add(team.slug);
|
|
141
|
+
updateThread(thread.id, { title: team.name, userSetTitle: true });
|
|
142
|
+
healed += 1;
|
|
143
|
+
}
|
|
144
|
+
return healed;
|
|
145
|
+
}
|
|
146
|
+
function listGlobalThreads(includeArchived = false) {
|
|
147
|
+
return listThreads({ includeArchived }).filter(
|
|
148
|
+
(t) => t.repoPath === GLOBAL_WORKSPACE_ID
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
function findCloudCoordinator() {
|
|
152
|
+
return listThreads({ includeArchived: true }).find(
|
|
153
|
+
(t) => t.status !== "archived" && isCloudCoordinatorThread(t)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
function ensureCloudCoordinator(agent) {
|
|
157
|
+
const existing = findCloudCoordinator();
|
|
158
|
+
if (existing) {
|
|
159
|
+
if (existing.repoPath !== GLOBAL_WORKSPACE_ID) {
|
|
160
|
+
return updateThread(existing.id, {
|
|
161
|
+
repoPath: GLOBAL_WORKSPACE_ID,
|
|
162
|
+
worktreePath: globalAgentCwd(),
|
|
163
|
+
branchName: "global"
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return existing;
|
|
167
|
+
}
|
|
168
|
+
const created = createGlobalChat({
|
|
169
|
+
sourceRef: CLOUD_ORCHESTRATOR_GOAL,
|
|
170
|
+
agent
|
|
171
|
+
});
|
|
172
|
+
const all = listThreads({ includeArchived: true }).filter(
|
|
173
|
+
(t) => t.status !== "archived" && isCloudCoordinatorThread(t)
|
|
174
|
+
).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
175
|
+
return all[0] ?? created;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export {
|
|
179
|
+
GLOBAL_WORKSPACE_ID,
|
|
180
|
+
isGlobalThread,
|
|
181
|
+
isGlobalRepoPath,
|
|
182
|
+
isOrchestratorThread,
|
|
183
|
+
orchestratorSessionPoisonedByBuiltins,
|
|
184
|
+
isCloudCoordinatorThread,
|
|
185
|
+
orchestrationTitleNeedsSoccerNickname,
|
|
186
|
+
takenTeamSlugsForOrchestration,
|
|
187
|
+
createGlobalChat,
|
|
188
|
+
healOrchestrationSoccerTitles,
|
|
189
|
+
listGlobalThreads,
|
|
190
|
+
ensureCloudCoordinator
|
|
191
|
+
};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
run
|
|
5
|
+
} from "./chunk-ISY4EGF6.js";
|
|
6
|
+
import {
|
|
7
|
+
appDataDir
|
|
8
|
+
} from "./chunk-7MV3RXSC.js";
|
|
9
|
+
|
|
10
|
+
// src/brightsy/connected-teams.ts
|
|
11
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
12
|
+
import { join as join2 } from "path";
|
|
13
|
+
|
|
14
|
+
// src/brightsy/config.ts
|
|
15
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
16
|
+
import { homedir } from "os";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
function brightsyConfigPath() {
|
|
19
|
+
return join(homedir(), ".brightsy", "config.json");
|
|
20
|
+
}
|
|
21
|
+
function loadBrightsyConfig() {
|
|
22
|
+
const path = brightsyConfigPath();
|
|
23
|
+
if (!existsSync(path)) {
|
|
24
|
+
throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
|
|
25
|
+
}
|
|
26
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
27
|
+
if (!raw.access_token || !raw.account_id) {
|
|
28
|
+
throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
|
|
29
|
+
}
|
|
30
|
+
return raw;
|
|
31
|
+
}
|
|
32
|
+
function saveBrightsyConfig(cfg) {
|
|
33
|
+
writeFileSync(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
|
|
34
|
+
`, {
|
|
35
|
+
mode: 384
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/brightsy/accounts.ts
|
|
40
|
+
async function runBrightsyTeamsJson(args) {
|
|
41
|
+
const listed = await run("brightsy", ["teams", ...args, "--json"], {
|
|
42
|
+
reject: false
|
|
43
|
+
});
|
|
44
|
+
if (listed.exitCode !== 0) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
listed.stderr.trim() || listed.stdout.trim() || "brightsy teams failed \u2014 is `@brightsy/cli` installed and logged in?"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const raw = listed.stdout.trim();
|
|
50
|
+
if (!raw) {
|
|
51
|
+
throw new Error("brightsy teams returned empty output");
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(raw);
|
|
55
|
+
} catch {
|
|
56
|
+
throw new Error("brightsy teams returned invalid JSON");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function listBrightsyAccounts() {
|
|
60
|
+
const data = await runBrightsyTeamsJson([]);
|
|
61
|
+
return Array.isArray(data.teams) ? data.teams : [];
|
|
62
|
+
}
|
|
63
|
+
async function performBrightsyAccountSwitch(accountIdOrSlug) {
|
|
64
|
+
const data = await runBrightsyTeamsJson([
|
|
65
|
+
"switch",
|
|
66
|
+
accountIdOrSlug
|
|
67
|
+
]);
|
|
68
|
+
if (!data.active?.id) {
|
|
69
|
+
throw new Error(`Team switch failed for: ${accountIdOrSlug}`);
|
|
70
|
+
}
|
|
71
|
+
const cfg = loadBrightsyConfig();
|
|
72
|
+
if (data.active.slug && data.active.slug !== cfg.account_slug) {
|
|
73
|
+
cfg.account_slug = data.active.slug;
|
|
74
|
+
saveBrightsyConfig(cfg);
|
|
75
|
+
}
|
|
76
|
+
return { cfg, target: data.active };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/brightsy/connected-teams.ts
|
|
80
|
+
function storePath() {
|
|
81
|
+
return join2(appDataDir(), "brightsy-teams.json");
|
|
82
|
+
}
|
|
83
|
+
function readStore() {
|
|
84
|
+
const path = storePath();
|
|
85
|
+
if (!existsSync2(path)) return [];
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
88
|
+
return Array.isArray(parsed.teams) ? parsed.teams : [];
|
|
89
|
+
} catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function writeStore(teams) {
|
|
94
|
+
mkdirSync(appDataDir(), { recursive: true });
|
|
95
|
+
const path = storePath();
|
|
96
|
+
writeFileSync2(path, `${JSON.stringify({ teams }, null, 2)}
|
|
97
|
+
`, {
|
|
98
|
+
mode: 384
|
|
99
|
+
});
|
|
100
|
+
return teams;
|
|
101
|
+
}
|
|
102
|
+
function listConnectedBrightsyTeams() {
|
|
103
|
+
return readStore().map(({ id, slug, name, expires_at }) => ({
|
|
104
|
+
id,
|
|
105
|
+
slug,
|
|
106
|
+
name,
|
|
107
|
+
expires_at
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
function getConnectedBrightsyTeamsRaw() {
|
|
111
|
+
return readStore();
|
|
112
|
+
}
|
|
113
|
+
function applyConnectedTeamToCli(team) {
|
|
114
|
+
let base = {};
|
|
115
|
+
try {
|
|
116
|
+
base = loadBrightsyConfig();
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
saveBrightsyConfig({
|
|
120
|
+
...base,
|
|
121
|
+
access_token: team.access_token,
|
|
122
|
+
refresh_token: team.refresh_token ?? base.refresh_token,
|
|
123
|
+
expires_at: team.expires_at ?? base.expires_at,
|
|
124
|
+
account_id: team.id,
|
|
125
|
+
account_slug: team.slug,
|
|
126
|
+
endpoint: team.endpoint ?? base.endpoint ?? "https://brightsy.ai",
|
|
127
|
+
oauth_client_id: base.oauth_client_id
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async function refreshTeamToken(team) {
|
|
131
|
+
if (!team.refresh_token) return team;
|
|
132
|
+
const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
|
|
133
|
+
const cfg = (() => {
|
|
134
|
+
try {
|
|
135
|
+
return loadBrightsyConfig();
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
})();
|
|
140
|
+
const clientId = cfg?.oauth_client_id || "brightsy-cli";
|
|
141
|
+
const res = await fetch(`${endpoint}/oauth/token`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
144
|
+
body: new URLSearchParams({
|
|
145
|
+
grant_type: "refresh_token",
|
|
146
|
+
refresh_token: team.refresh_token,
|
|
147
|
+
client_id: clientId
|
|
148
|
+
})
|
|
149
|
+
});
|
|
150
|
+
if (!res.ok) return team;
|
|
151
|
+
const data = await res.json();
|
|
152
|
+
if (!data.access_token) return team;
|
|
153
|
+
return {
|
|
154
|
+
...team,
|
|
155
|
+
access_token: data.access_token,
|
|
156
|
+
refresh_token: data.refresh_token || team.refresh_token,
|
|
157
|
+
expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : team.expires_at
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async function ensureConnectedBrightsyTeamTokens() {
|
|
161
|
+
const teams = readStore();
|
|
162
|
+
if (teams.length === 0) return [];
|
|
163
|
+
const next = [];
|
|
164
|
+
let changed = false;
|
|
165
|
+
for (const team of teams) {
|
|
166
|
+
const expired = typeof team.expires_at === "number" && Date.now() >= team.expires_at - 6e4;
|
|
167
|
+
if (!expired) {
|
|
168
|
+
next.push(team);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const refreshed = await refreshTeamToken(team);
|
|
172
|
+
if (refreshed.access_token !== team.access_token) changed = true;
|
|
173
|
+
next.push(refreshed);
|
|
174
|
+
}
|
|
175
|
+
if (changed) writeStore(next);
|
|
176
|
+
try {
|
|
177
|
+
const cfg = loadBrightsyConfig();
|
|
178
|
+
const active = next.find((t) => t.id === cfg.account_id);
|
|
179
|
+
if (active && active.access_token !== cfg.access_token) {
|
|
180
|
+
applyConnectedTeamToCli(active);
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
return next;
|
|
185
|
+
}
|
|
186
|
+
function ensureCliTeamTracked(meta) {
|
|
187
|
+
try {
|
|
188
|
+
const cfg = loadBrightsyConfig();
|
|
189
|
+
const existing = readStore();
|
|
190
|
+
if (existing.some((t) => t.id === cfg.account_id)) {
|
|
191
|
+
return listConnectedBrightsyTeams();
|
|
192
|
+
}
|
|
193
|
+
const team = {
|
|
194
|
+
id: cfg.account_id,
|
|
195
|
+
slug: meta?.slug || cfg.account_slug || cfg.account_id,
|
|
196
|
+
name: meta?.name || cfg.account_slug || cfg.account_id,
|
|
197
|
+
access_token: cfg.access_token,
|
|
198
|
+
refresh_token: cfg.refresh_token,
|
|
199
|
+
expires_at: cfg.expires_at,
|
|
200
|
+
endpoint: cfg.endpoint
|
|
201
|
+
};
|
|
202
|
+
writeStore([...existing, team]);
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
return listConnectedBrightsyTeams();
|
|
206
|
+
}
|
|
207
|
+
async function connectBrightsyTeam(accountIdOrSlug) {
|
|
208
|
+
const accounts = await listBrightsyAccounts();
|
|
209
|
+
const target = accounts.find((a) => a.id === accountIdOrSlug) ?? accounts.find((a) => a.slug === accountIdOrSlug);
|
|
210
|
+
if (!target) {
|
|
211
|
+
throw new Error(`Brightsy team not found: ${accountIdOrSlug}`);
|
|
212
|
+
}
|
|
213
|
+
const existing = readStore();
|
|
214
|
+
const already = existing.find((t) => t.id === target.id);
|
|
215
|
+
if (already) {
|
|
216
|
+
applyConnectedTeamToCli(already);
|
|
217
|
+
return listConnectedBrightsyTeams();
|
|
218
|
+
}
|
|
219
|
+
const { cfg: minted } = await performBrightsyAccountSwitch(target.id);
|
|
220
|
+
const team = {
|
|
221
|
+
id: target.id,
|
|
222
|
+
slug: target.slug,
|
|
223
|
+
name: target.name,
|
|
224
|
+
access_token: minted.access_token,
|
|
225
|
+
refresh_token: minted.refresh_token,
|
|
226
|
+
expires_at: minted.expires_at,
|
|
227
|
+
endpoint: minted.endpoint
|
|
228
|
+
};
|
|
229
|
+
writeStore([...existing, team]);
|
|
230
|
+
return listConnectedBrightsyTeams();
|
|
231
|
+
}
|
|
232
|
+
async function disconnectBrightsyTeam(accountIdOrSlug) {
|
|
233
|
+
const before = readStore();
|
|
234
|
+
const removed = before.find(
|
|
235
|
+
(t) => t.id === accountIdOrSlug || t.slug === accountIdOrSlug
|
|
236
|
+
);
|
|
237
|
+
const teams = before.filter(
|
|
238
|
+
(t) => t.id !== accountIdOrSlug && t.slug !== accountIdOrSlug
|
|
239
|
+
);
|
|
240
|
+
writeStore(teams);
|
|
241
|
+
if (removed) {
|
|
242
|
+
try {
|
|
243
|
+
const cfg = loadBrightsyConfig();
|
|
244
|
+
if (cfg.account_id === removed.id) {
|
|
245
|
+
if (teams[0]) {
|
|
246
|
+
applyConnectedTeamToCli(teams[0]);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return listConnectedBrightsyTeams();
|
|
253
|
+
}
|
|
254
|
+
function brightsyMcpServerName(slug) {
|
|
255
|
+
const cleaned = slug.replace(/[^A-Za-z0-9_-]/g, "_").replace(/^_+|_+$/g, "");
|
|
256
|
+
return `brightsy_${cleaned || "team"}`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export {
|
|
260
|
+
loadBrightsyConfig,
|
|
261
|
+
listConnectedBrightsyTeams,
|
|
262
|
+
getConnectedBrightsyTeamsRaw,
|
|
263
|
+
applyConnectedTeamToCli,
|
|
264
|
+
ensureConnectedBrightsyTeamTokens,
|
|
265
|
+
ensureCliTeamTracked,
|
|
266
|
+
connectBrightsyTeam,
|
|
267
|
+
disconnectBrightsyTeam,
|
|
268
|
+
brightsyMcpServerName
|
|
269
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
normalizeThinkingEffort
|
|
5
|
+
} from "./chunk-KGZBYWZ3.js";
|
|
6
|
+
import {
|
|
7
|
+
threadFilePath,
|
|
8
|
+
threadLockPath,
|
|
9
|
+
threadsDir
|
|
10
|
+
} from "./chunk-7MV3RXSC.js";
|
|
11
|
+
|
|
12
|
+
// src/store/thread-store.ts
|
|
13
|
+
import { randomUUID } from "crypto";
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
readFileSync,
|
|
17
|
+
renameSync,
|
|
18
|
+
unlinkSync,
|
|
19
|
+
writeFileSync,
|
|
20
|
+
readdirSync
|
|
21
|
+
} from "fs";
|
|
22
|
+
import lockfile from "proper-lockfile";
|
|
23
|
+
function nowIso() {
|
|
24
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
25
|
+
}
|
|
26
|
+
function resolveThreadEffort(raw) {
|
|
27
|
+
const fromField = normalizeThinkingEffort(raw.effort);
|
|
28
|
+
if (fromField) return fromField;
|
|
29
|
+
if (raw.fast) return "low";
|
|
30
|
+
return "high";
|
|
31
|
+
}
|
|
32
|
+
function normalizeThread(raw) {
|
|
33
|
+
return {
|
|
34
|
+
...raw,
|
|
35
|
+
model: raw.model ?? null,
|
|
36
|
+
effort: resolveThreadEffort(raw),
|
|
37
|
+
fast: Boolean(raw.fast),
|
|
38
|
+
planMode: Boolean(raw.planMode),
|
|
39
|
+
autonomy: raw.autonomy ?? "default",
|
|
40
|
+
lastError: raw.lastError ?? null,
|
|
41
|
+
agentPid: raw.agentPid ?? null,
|
|
42
|
+
attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
|
|
43
|
+
prTitle: raw.prTitle ?? null,
|
|
44
|
+
userSetTitle: Boolean(raw.userSetTitle),
|
|
45
|
+
activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : [],
|
|
46
|
+
quotaResumeAt: raw.quotaResumeAt ?? null,
|
|
47
|
+
quotaContinuedFromId: raw.quotaContinuedFromId ?? null
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function createEmptyThread(partial) {
|
|
51
|
+
const ts = nowIso();
|
|
52
|
+
return {
|
|
53
|
+
id: randomUUID(),
|
|
54
|
+
sessionId: partial.sessionId ?? null,
|
|
55
|
+
autonomy: partial.autonomy ?? "default",
|
|
56
|
+
model: partial.model ?? null,
|
|
57
|
+
effort: partial.effort ?? "high",
|
|
58
|
+
fast: partial.fast ?? false,
|
|
59
|
+
planMode: partial.planMode ?? false,
|
|
60
|
+
sourceIsFork: partial.sourceIsFork ?? false,
|
|
61
|
+
status: partial.status ?? "idle",
|
|
62
|
+
queue: partial.queue ?? [],
|
|
63
|
+
parentThreadId: partial.parentThreadId ?? null,
|
|
64
|
+
devPort: partial.devPort ?? null,
|
|
65
|
+
activeRuns: partial.activeRuns ?? [],
|
|
66
|
+
prUrl: partial.prUrl ?? null,
|
|
67
|
+
prTitle: partial.prTitle ?? null,
|
|
68
|
+
userSetTitle: partial.userSetTitle ?? false,
|
|
69
|
+
messages: partial.messages ?? [],
|
|
70
|
+
attachments: partial.attachments ?? [],
|
|
71
|
+
createdAt: ts,
|
|
72
|
+
updatedAt: ts,
|
|
73
|
+
title: partial.title,
|
|
74
|
+
sourceType: partial.sourceType,
|
|
75
|
+
sourceRef: partial.sourceRef,
|
|
76
|
+
branchName: partial.branchName,
|
|
77
|
+
worktreePath: partial.worktreePath,
|
|
78
|
+
repoPath: partial.repoPath,
|
|
79
|
+
agent: partial.agent,
|
|
80
|
+
lastError: null,
|
|
81
|
+
agentPid: null
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function withThreadLock(id, fn) {
|
|
85
|
+
const lockPath = threadLockPath(id);
|
|
86
|
+
writeFileSync(lockPath, "", { flag: "a" });
|
|
87
|
+
let release;
|
|
88
|
+
try {
|
|
89
|
+
release = await lockfile.lock(lockPath, {
|
|
90
|
+
retries: { retries: 10, minTimeout: 50, maxTimeout: 200 },
|
|
91
|
+
stale: 6e4
|
|
92
|
+
});
|
|
93
|
+
return await fn();
|
|
94
|
+
} finally {
|
|
95
|
+
if (release) await release();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function readThread(id) {
|
|
99
|
+
const path = threadFilePath(id);
|
|
100
|
+
if (!existsSync(path)) return null;
|
|
101
|
+
const raw = readFileSync(path, "utf8");
|
|
102
|
+
return normalizeThread(JSON.parse(raw));
|
|
103
|
+
}
|
|
104
|
+
function writeThread(thread) {
|
|
105
|
+
const path = threadFilePath(idPath(thread.id));
|
|
106
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
107
|
+
const next = { ...thread, updatedAt: nowIso() };
|
|
108
|
+
writeFileSync(tmp, JSON.stringify(next, null, 2), "utf8");
|
|
109
|
+
renameSync(tmp, path);
|
|
110
|
+
}
|
|
111
|
+
function idPath(id) {
|
|
112
|
+
return id;
|
|
113
|
+
}
|
|
114
|
+
function listThreads(opts) {
|
|
115
|
+
const files = readdirSync(threadsDir()).filter((f) => f.endsWith(".json"));
|
|
116
|
+
const threads = files.map((f) => {
|
|
117
|
+
try {
|
|
118
|
+
return normalizeThread(
|
|
119
|
+
JSON.parse(readFileSync(threadFilePath(f.replace(/\.json$/, "")), "utf8"))
|
|
120
|
+
);
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}).filter((t) => t !== null).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
125
|
+
if (opts?.includeArchived) return threads;
|
|
126
|
+
return threads.filter((t) => t.status !== "archived");
|
|
127
|
+
}
|
|
128
|
+
function deleteThreadRecord(id) {
|
|
129
|
+
const path = threadFilePath(id);
|
|
130
|
+
if (existsSync(path)) unlinkSync(path);
|
|
131
|
+
const lock = threadLockPath(id);
|
|
132
|
+
if (existsSync(lock)) {
|
|
133
|
+
try {
|
|
134
|
+
unlinkSync(lock);
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function updateThread(id, patch) {
|
|
140
|
+
const current = readThread(id);
|
|
141
|
+
if (!current) throw new Error(`Thread not found: ${id}`);
|
|
142
|
+
const next = { ...current, ...patch, id: current.id, updatedAt: nowIso() };
|
|
143
|
+
writeThread(next);
|
|
144
|
+
return next;
|
|
145
|
+
}
|
|
146
|
+
function appendMessage(id, message) {
|
|
147
|
+
const current = readThread(id);
|
|
148
|
+
if (!current) throw new Error(`Thread not found: ${id}`);
|
|
149
|
+
return updateThread(id, { messages: [...current.messages, message] });
|
|
150
|
+
}
|
|
151
|
+
function setStatus(id, status, lastError) {
|
|
152
|
+
return updateThread(id, { status, lastError: lastError ?? null });
|
|
153
|
+
}
|
|
154
|
+
function findThreadByRef(ref) {
|
|
155
|
+
const all = listThreads({ includeArchived: true });
|
|
156
|
+
return all.find((t) => t.id === ref || t.id.startsWith(ref) || t.branchName === ref || t.title === ref) ?? null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export {
|
|
160
|
+
resolveThreadEffort,
|
|
161
|
+
normalizeThread,
|
|
162
|
+
createEmptyThread,
|
|
163
|
+
withThreadLock,
|
|
164
|
+
readThread,
|
|
165
|
+
writeThread,
|
|
166
|
+
listThreads,
|
|
167
|
+
deleteThreadRecord,
|
|
168
|
+
updateThread,
|
|
169
|
+
appendMessage,
|
|
170
|
+
setStatus,
|
|
171
|
+
findThreadByRef
|
|
172
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/agents/orchestrator-capable.ts
|
|
2
|
+
var ORCHESTRATOR_AGENT_KINDS = [
|
|
3
|
+
"claude",
|
|
4
|
+
"codex",
|
|
5
|
+
"opencode",
|
|
6
|
+
"cursor"
|
|
7
|
+
];
|
|
8
|
+
function isOrchestratorCapableAgent(agent) {
|
|
9
|
+
return Boolean(
|
|
10
|
+
agent && ORCHESTRATOR_AGENT_KINDS.includes(agent)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
function assertOrchestratorCapableAgent(agent, context = "orchestration") {
|
|
14
|
+
if (!isOrchestratorCapableAgent(agent)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`${agent} cannot run ${context} \u2014 it does not support Sideboard MCP. Use Claude, Cursor, Codex, or OpenCode.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return agent;
|
|
20
|
+
}
|
|
21
|
+
function coerceOrchestratorAgent(agent, fallback = "claude") {
|
|
22
|
+
return isOrchestratorCapableAgent(agent) ? agent : fallback;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
ORCHESTRATOR_AGENT_KINDS,
|
|
27
|
+
isOrchestratorCapableAgent,
|
|
28
|
+
assertOrchestratorCapableAgent,
|
|
29
|
+
coerceOrchestratorAgent
|
|
30
|
+
};
|