@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,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
resolveGithubRepoSlug
|
|
5
|
+
} from "./chunk-ZVV5EEQN.js";
|
|
6
|
+
import {
|
|
7
|
+
globalAgentCwd,
|
|
8
|
+
sideboardReposDir
|
|
9
|
+
} from "./chunk-7MV3RXSC.js";
|
|
10
|
+
|
|
11
|
+
// src/orchestrator/coordinator-prompt.ts
|
|
12
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
function formatWorkspaceInventory(workspaces) {
|
|
15
|
+
if (workspaces.length === 0) return "(no registered workspaces)";
|
|
16
|
+
return workspaces.map((w) => {
|
|
17
|
+
const slug = w.githubSlug?.trim() ? ` github:${w.githubSlug.trim()}` : "";
|
|
18
|
+
return `- ${w.name}: ${w.path}${slug}`;
|
|
19
|
+
}).join("\n");
|
|
20
|
+
}
|
|
21
|
+
async function enrichWorkspacesWithGithub(workspaces) {
|
|
22
|
+
return Promise.all(
|
|
23
|
+
workspaces.map(async (w) => {
|
|
24
|
+
const githubSlug = await resolveGithubRepoSlug(w.path).catch(() => null);
|
|
25
|
+
return { ...w, githubSlug };
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
function coordinatorGreenfieldPlaybook(reposDir) {
|
|
30
|
+
return [
|
|
31
|
+
"Greenfield (new app / new GitHub repo) \u2014 use Bash + MCP:",
|
|
32
|
+
`- Create or clone under \`${reposDir}/<name>\` (never inside this synthetic home cwd).`,
|
|
33
|
+
"- Examples:",
|
|
34
|
+
` - Clone: \`git clone <url> ${reposDir}/<name>\``,
|
|
35
|
+
` - New GitHub repo: \`gh repo create <owner>/<name> --private --clone -- ${reposDir}/<name>\` (or mkdir + git init + gh repo create + remote add + push)`,
|
|
36
|
+
"- 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 -R <origin-owner/name>`).",
|
|
37
|
+
"- Always target the child worktree's **origin** (`github:` slug from list_workspaces / `git remote get-url origin` in that worktree). Never open PRs against `upstream`.",
|
|
38
|
+
"- Do coding work in the child worktree thread, not by editing files in this home cwd."
|
|
39
|
+
].join("\n");
|
|
40
|
+
}
|
|
41
|
+
var COORDINATOR_TOOL_PLAYBOOK = [
|
|
42
|
+
"Role: you oversee Sideboard worktree agents across registered repos. You do not live inside one of those worktrees.",
|
|
43
|
+
"Sideboard MCP (fleet control \u2014 prefer these for status and orchestration):",
|
|
44
|
+
"Discover:",
|
|
45
|
+
"- list_workspaces \u2014 registered repos (path + github slug when known)",
|
|
46
|
+
"- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
|
|
47
|
+
"- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
|
|
48
|
+
"- list_threads / get_thread \u2014 fleet status (what is going on)",
|
|
49
|
+
"Workspaces:",
|
|
50
|
+
"- add_workspace / remove_workspace \u2014 register or unregister a git repo",
|
|
51
|
+
"Worktree threads (chats):",
|
|
52
|
+
"- create_thread \u2014 create a worktree + chat from branch | pr | ticket; pass repoPath + parentThreadId",
|
|
53
|
+
"- fork_worktree \u2014 fork a worktree chat into a NEW git worktree + chat (transcript attached); optional agent; leave model unset (Auto) unless you have a reason. Not for orchestration chats.",
|
|
54
|
+
"- fork_chat \u2014 fork a worktree chat (same worktree tab) OR a Global orchestration chat (new orchestration tab); optional agent; leave model unset (Auto) unless you have a reason. Remote coordinators: use this to continue another orchestration chat on a different agent after session limits.",
|
|
55
|
+
"- 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",
|
|
56
|
+
"- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply",
|
|
57
|
+
"- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
|
|
58
|
+
"- archive_thread / restore_thread \u2014 archive (tears down worktree when last tab) or restore",
|
|
59
|
+
"Setup / run:",
|
|
60
|
+
"- run_setup \u2014 re-run worktree setup",
|
|
61
|
+
"- list_run_scripts / run_dev_script / stop_dev_script \u2014 start/stop named run scripts",
|
|
62
|
+
"Inspect / review / PRs:",
|
|
63
|
+
"- get_diff \u2014 compact diff summary",
|
|
64
|
+
'- 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',
|
|
65
|
+
"- 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.",
|
|
66
|
+
"Human-only (do not attempt): merge, ready-for-review land, purge_thread.",
|
|
67
|
+
"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.",
|
|
68
|
+
"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."
|
|
69
|
+
].join("\n");
|
|
70
|
+
function coordinatorTurnReminder(opts) {
|
|
71
|
+
const goal = opts.goal?.trim();
|
|
72
|
+
return [
|
|
73
|
+
"Sideboard Orchestration (mandatory):",
|
|
74
|
+
"- You oversee Sideboard worktree agents. You are not yourself checked out in a project worktree.",
|
|
75
|
+
"- This cwd is a synthetic empty home (not a git repo). Emptiness here is expected \u2014 it is not a problem to fix.",
|
|
76
|
+
"- Registered workspaces / child threads are the fleet you manage via Sideboard MCP.",
|
|
77
|
+
`- Parent thread id (pass as parentThreadId when creating children): ${opts.parentId}`,
|
|
78
|
+
goal ? `- Goal / title: ${goal}` : null,
|
|
79
|
+
`- 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.`,
|
|
80
|
+
"- Existing repo: create_thread on a repoPath \u2192 send_to_thread \u2192 wait_for_turn.",
|
|
81
|
+
"- 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.",
|
|
82
|
+
"- When naming threads for the user, link them as `[Title](sideboard://thread/<id>)`."
|
|
83
|
+
].filter(Boolean).join("\n");
|
|
84
|
+
}
|
|
85
|
+
function ensureGlobalCoordinatorCwd() {
|
|
86
|
+
const dir = globalAgentCwd();
|
|
87
|
+
mkdirSync(dir, { recursive: true });
|
|
88
|
+
const reposDir = sideboardReposDir();
|
|
89
|
+
const body = [
|
|
90
|
+
"# Sideboard Orchestration",
|
|
91
|
+
"",
|
|
92
|
+
"You are the Sideboard **Orchestration** agent \u2014 you oversee worktree agents in the Sideboard app.",
|
|
93
|
+
"You are **not** connected to a single project workspace. This directory is a synthetic empty cwd (not a git worktree).",
|
|
94
|
+
"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.",
|
|
95
|
+
"Repos from `list_workspaces` and threads from `list_threads` are the fleet you orchestrate.",
|
|
96
|
+
'For status questions ("what\'s going on?"), use `list_threads` / `list_workspaces` \u2014 never diagnose this synthetic home as a broken worktree.',
|
|
97
|
+
"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.",
|
|
98
|
+
"",
|
|
99
|
+
COORDINATOR_TOOL_PLAYBOOK,
|
|
100
|
+
"",
|
|
101
|
+
coordinatorGreenfieldPlaybook(reposDir),
|
|
102
|
+
"",
|
|
103
|
+
"When creating threads, pass `repoPath` from `list_workspaces` (or the path you just registered) and `parentThreadId` for children.",
|
|
104
|
+
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn.",
|
|
105
|
+
"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 via worktree agent.",
|
|
106
|
+
"Always ask worktree agents to open draft PRs (`send_to_thread` + `gh pr create --draft -R <origin>`); never open PRs from the orchestrator."
|
|
107
|
+
].join("\n");
|
|
108
|
+
writeFileSync(join(dir, "CLAUDE.md"), `${body}
|
|
109
|
+
`, "utf8");
|
|
110
|
+
writeFileSync(join(dir, "AGENTS.md"), `${body}
|
|
111
|
+
`, "utf8");
|
|
112
|
+
return dir;
|
|
113
|
+
}
|
|
114
|
+
function coordinatorSystemPrompt(opts) {
|
|
115
|
+
const audience = opts.audience ?? "cloud";
|
|
116
|
+
const reposDir = sideboardReposDir();
|
|
117
|
+
const intro = audience === "cloud" ? [
|
|
118
|
+
"You are a Sideboard coordinator responding to a request from a Brightsy cloud agent (Slack, Discord, Teams, or other chat).",
|
|
119
|
+
"Your reply will be sent back to that cloud agent \u2014 be concise and actionable."
|
|
120
|
+
] : [
|
|
121
|
+
"You are a Sideboard orchestration agent: you oversee worktree agents across registered workspaces in the Sideboard app.",
|
|
122
|
+
"Stay concise and actionable; prefer Sideboard MCP for fleet status; use Bash for greenfield repo setup and inspecting target worktree paths."
|
|
123
|
+
];
|
|
124
|
+
return [
|
|
125
|
+
...intro,
|
|
126
|
+
"You operate across ALL registered workspaces below.",
|
|
127
|
+
"You have no project git home \u2014 this process cwd is synthetic and empty on purpose.",
|
|
128
|
+
COORDINATOR_TOOL_PLAYBOOK,
|
|
129
|
+
coordinatorGreenfieldPlaybook(reposDir),
|
|
130
|
+
"When creating threads, pass the correct repoPath for the target workspace and parentThreadId for children.",
|
|
131
|
+
"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 -R <origin-owner/name>` using the workspace github slug) \u2192 wait_for_turn. Never target upstream. Never open PRs from the orchestrator.",
|
|
132
|
+
"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 via worktree agent.",
|
|
133
|
+
`Goal: ${opts.goal}`,
|
|
134
|
+
`Parent thread id (pass as parentThreadId when creating children): ${opts.parentId}`,
|
|
135
|
+
"Registered workspaces:",
|
|
136
|
+
formatWorkspaceInventory(opts.workspaces)
|
|
137
|
+
].join("\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
formatWorkspaceInventory,
|
|
142
|
+
enrichWorkspacesWithGithub,
|
|
143
|
+
coordinatorGreenfieldPlaybook,
|
|
144
|
+
COORDINATOR_TOOL_PLAYBOOK,
|
|
145
|
+
coordinatorTurnReminder,
|
|
146
|
+
ensureGlobalCoordinatorCwd,
|
|
147
|
+
coordinatorSystemPrompt
|
|
148
|
+
};
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
normalizeThinkingEffort
|
|
5
|
+
} from "./chunk-KGZBYWZ3.js";
|
|
6
|
+
import {
|
|
7
|
+
appDataDir
|
|
8
|
+
} from "./chunk-7MV3RXSC.js";
|
|
9
|
+
|
|
10
|
+
// src/store/app-settings.ts
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
var HARNESS_ENV_KEYS = {
|
|
15
|
+
claude: "ANTHROPIC_API_KEY",
|
|
16
|
+
codex: "CODEX_API_KEY",
|
|
17
|
+
cursor: "CURSOR_API_KEY",
|
|
18
|
+
opencode: null,
|
|
19
|
+
brightsy: null
|
|
20
|
+
};
|
|
21
|
+
var DEFAULT_AGENTS = /* @__PURE__ */ new Set([
|
|
22
|
+
"claude",
|
|
23
|
+
"codex",
|
|
24
|
+
"opencode",
|
|
25
|
+
"brightsy",
|
|
26
|
+
"cursor"
|
|
27
|
+
]);
|
|
28
|
+
function appSettingsPath() {
|
|
29
|
+
return join(appDataDir(), "settings.json");
|
|
30
|
+
}
|
|
31
|
+
function claudeUserSettingsPath() {
|
|
32
|
+
return join(homedir(), ".claude", "settings.json");
|
|
33
|
+
}
|
|
34
|
+
function normalizeClaude(raw) {
|
|
35
|
+
if (!raw || typeof raw !== "object") return {};
|
|
36
|
+
const source = raw;
|
|
37
|
+
const out = {};
|
|
38
|
+
if (typeof source.executablePath === "string") {
|
|
39
|
+
const path = source.executablePath.trim();
|
|
40
|
+
if (path) out.executablePath = path;
|
|
41
|
+
}
|
|
42
|
+
if (typeof source.chromeEnabled === "boolean") {
|
|
43
|
+
out.chromeEnabled = source.chromeEnabled;
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
var CLOUD_CONNECT_AGENTS = /* @__PURE__ */ new Set([
|
|
48
|
+
"claude",
|
|
49
|
+
"codex",
|
|
50
|
+
"opencode",
|
|
51
|
+
"cursor"
|
|
52
|
+
]);
|
|
53
|
+
function normalizeBrightsy(raw) {
|
|
54
|
+
if (!raw || typeof raw !== "object") return {};
|
|
55
|
+
const source = raw;
|
|
56
|
+
const out = {};
|
|
57
|
+
if (typeof source.cloudConnectEnabled === "boolean") {
|
|
58
|
+
out.cloudConnectEnabled = source.cloudConnectEnabled;
|
|
59
|
+
}
|
|
60
|
+
if (typeof source.cloudConnectAgent === "string" && CLOUD_CONNECT_AGENTS.has(source.cloudConnectAgent)) {
|
|
61
|
+
out.cloudConnectAgent = source.cloudConnectAgent;
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
var ISSUE_SOURCES = /* @__PURE__ */ new Set(["linear", "github"]);
|
|
66
|
+
function normalizeIntegrations(raw) {
|
|
67
|
+
if (!raw || typeof raw !== "object") return {};
|
|
68
|
+
const source = raw;
|
|
69
|
+
const out = {};
|
|
70
|
+
if (typeof source.linearApiKey === "string") {
|
|
71
|
+
const key = source.linearApiKey.trim();
|
|
72
|
+
if (key) out.linearApiKey = key;
|
|
73
|
+
}
|
|
74
|
+
if (typeof source.issueSource === "string" && ISSUE_SOURCES.has(source.issueSource)) {
|
|
75
|
+
out.issueSource = source.issueSource;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
function normalizeDefaults(raw) {
|
|
80
|
+
if (!raw || typeof raw !== "object") return {};
|
|
81
|
+
const source = raw;
|
|
82
|
+
const out = {};
|
|
83
|
+
if (typeof source.agent === "string" && DEFAULT_AGENTS.has(source.agent)) {
|
|
84
|
+
out.agent = source.agent;
|
|
85
|
+
}
|
|
86
|
+
if (typeof source.model === "string") {
|
|
87
|
+
const model = source.model.trim();
|
|
88
|
+
if (model) out.model = model;
|
|
89
|
+
}
|
|
90
|
+
if (normalizeThinkingEffort(source.effort)) {
|
|
91
|
+
out.effort = normalizeThinkingEffort(source.effort);
|
|
92
|
+
}
|
|
93
|
+
if (typeof source.fast === "boolean") {
|
|
94
|
+
out.fast = source.fast;
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function normalizeAdvanced(raw) {
|
|
99
|
+
if (!raw || typeof raw !== "object") return {};
|
|
100
|
+
const source = raw;
|
|
101
|
+
const out = {};
|
|
102
|
+
if (typeof source.autoRenameBranch === "boolean") {
|
|
103
|
+
out.autoRenameBranch = source.autoRenameBranch;
|
|
104
|
+
}
|
|
105
|
+
if (typeof source.autoRunAfterSetup === "boolean") {
|
|
106
|
+
out.autoRunAfterSetup = source.autoRunAfterSetup;
|
|
107
|
+
}
|
|
108
|
+
if (typeof source.caffeinateWhileRunning === "boolean") {
|
|
109
|
+
out.caffeinateWhileRunning = source.caffeinateWhileRunning;
|
|
110
|
+
}
|
|
111
|
+
if (typeof source.caffeinateWhileCloudConnect === "boolean") {
|
|
112
|
+
out.caffeinateWhileCloudConnect = source.caffeinateWhileCloudConnect;
|
|
113
|
+
}
|
|
114
|
+
if (typeof source.deleteBranchOnPurge === "boolean") {
|
|
115
|
+
out.deleteBranchOnPurge = source.deleteBranchOnPurge;
|
|
116
|
+
}
|
|
117
|
+
if (typeof source.maxConcurrent === "number" && Number.isFinite(source.maxConcurrent)) {
|
|
118
|
+
out.maxConcurrent = Math.max(1, Math.min(32, Math.floor(source.maxConcurrent)));
|
|
119
|
+
}
|
|
120
|
+
if (typeof source.worktreeMaxCount === "number" && Number.isFinite(source.worktreeMaxCount)) {
|
|
121
|
+
out.worktreeMaxCount = Math.max(1, Math.min(500, Math.floor(source.worktreeMaxCount)));
|
|
122
|
+
}
|
|
123
|
+
if (typeof source.worktreeCleanupIntervalHours === "number" && Number.isFinite(source.worktreeCleanupIntervalHours)) {
|
|
124
|
+
out.worktreeCleanupIntervalHours = Math.max(
|
|
125
|
+
1,
|
|
126
|
+
Math.min(168, Math.floor(source.worktreeCleanupIntervalHours))
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (typeof source.worktreeLastCleanupAt === "string" && source.worktreeLastCleanupAt.trim()) {
|
|
130
|
+
out.worktreeLastCleanupAt = source.worktreeLastCleanupAt.trim();
|
|
131
|
+
}
|
|
132
|
+
if (typeof source.autoCleanupOrphans === "boolean") {
|
|
133
|
+
out.autoCleanupOrphans = source.autoCleanupOrphans;
|
|
134
|
+
}
|
|
135
|
+
if (source.orchestrationQuotaOnLimit === "switch_agent" || source.orchestrationQuotaOnLimit === "wait_reset") {
|
|
136
|
+
out.orchestrationQuotaOnLimit = source.orchestrationQuotaOnLimit;
|
|
137
|
+
}
|
|
138
|
+
if (typeof source.orchestrationQuotaFallbackAgent === "string" && DEFAULT_AGENTS.has(source.orchestrationQuotaFallbackAgent) && source.orchestrationQuotaFallbackAgent !== "brightsy") {
|
|
139
|
+
out.orchestrationQuotaFallbackAgent = source.orchestrationQuotaFallbackAgent;
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
function normalizeSettings(raw) {
|
|
144
|
+
const env = {};
|
|
145
|
+
let claude = {};
|
|
146
|
+
let brightsy = {};
|
|
147
|
+
let integrations = {};
|
|
148
|
+
let defaults = {};
|
|
149
|
+
let advanced = {};
|
|
150
|
+
if (raw && typeof raw === "object") {
|
|
151
|
+
if ("environment" in raw) {
|
|
152
|
+
const source = raw.environment;
|
|
153
|
+
if (source && typeof source === "object") {
|
|
154
|
+
for (const [k, v] of Object.entries(source)) {
|
|
155
|
+
const key = k.trim();
|
|
156
|
+
if (!key) continue;
|
|
157
|
+
if (typeof v === "string" && v.length > 0) env[key] = v;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if ("claude" in raw) {
|
|
162
|
+
claude = normalizeClaude(raw.claude);
|
|
163
|
+
}
|
|
164
|
+
if ("brightsy" in raw) {
|
|
165
|
+
brightsy = normalizeBrightsy(raw.brightsy);
|
|
166
|
+
}
|
|
167
|
+
if ("integrations" in raw) {
|
|
168
|
+
integrations = normalizeIntegrations(
|
|
169
|
+
raw.integrations
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if ("defaults" in raw) {
|
|
173
|
+
defaults = normalizeDefaults(raw.defaults);
|
|
174
|
+
}
|
|
175
|
+
if ("advanced" in raw) {
|
|
176
|
+
advanced = normalizeAdvanced(raw.advanced);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return { environment: env, claude, brightsy, integrations, defaults, advanced };
|
|
180
|
+
}
|
|
181
|
+
var EMPTY_SETTINGS = {
|
|
182
|
+
environment: {},
|
|
183
|
+
claude: {},
|
|
184
|
+
brightsy: {},
|
|
185
|
+
integrations: {},
|
|
186
|
+
defaults: {},
|
|
187
|
+
advanced: {}
|
|
188
|
+
};
|
|
189
|
+
function loadAppSettings() {
|
|
190
|
+
const path = appSettingsPath();
|
|
191
|
+
if (!existsSync(path)) {
|
|
192
|
+
return { ...EMPTY_SETTINGS };
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
196
|
+
return normalizeSettings(parsed);
|
|
197
|
+
} catch {
|
|
198
|
+
return { ...EMPTY_SETTINGS };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function saveAppSettings(settings) {
|
|
202
|
+
const normalized = normalizeSettings(settings);
|
|
203
|
+
mkdirSync(appDataDir(), { recursive: true });
|
|
204
|
+
writeFileSync(appSettingsPath(), `${JSON.stringify(normalized, null, 2)}
|
|
205
|
+
`, "utf8");
|
|
206
|
+
return normalized;
|
|
207
|
+
}
|
|
208
|
+
function updateAppEnvironment(patch) {
|
|
209
|
+
const current = loadAppSettings();
|
|
210
|
+
const environment = { ...current.environment };
|
|
211
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
212
|
+
const k = key.trim();
|
|
213
|
+
if (!k) continue;
|
|
214
|
+
if (value == null || value === "") {
|
|
215
|
+
delete environment[k];
|
|
216
|
+
} else {
|
|
217
|
+
environment[k] = value;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return saveAppSettings({ ...current, environment });
|
|
221
|
+
}
|
|
222
|
+
function updateClaudeSettings(patch) {
|
|
223
|
+
const current = loadAppSettings();
|
|
224
|
+
const claude = { ...current.claude };
|
|
225
|
+
if ("executablePath" in patch) {
|
|
226
|
+
if (patch.executablePath == null || patch.executablePath.trim() === "") {
|
|
227
|
+
delete claude.executablePath;
|
|
228
|
+
} else {
|
|
229
|
+
claude.executablePath = patch.executablePath.trim();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (typeof patch.chromeEnabled === "boolean") {
|
|
233
|
+
claude.chromeEnabled = patch.chromeEnabled;
|
|
234
|
+
}
|
|
235
|
+
return saveAppSettings({ ...current, claude });
|
|
236
|
+
}
|
|
237
|
+
function updateBrightsySettings(patch) {
|
|
238
|
+
const current = loadAppSettings();
|
|
239
|
+
const brightsy = { ...current.brightsy };
|
|
240
|
+
if (typeof patch.cloudConnectEnabled === "boolean") {
|
|
241
|
+
brightsy.cloudConnectEnabled = patch.cloudConnectEnabled;
|
|
242
|
+
}
|
|
243
|
+
if ("cloudConnectAgent" in patch) {
|
|
244
|
+
if (patch.cloudConnectAgent == null) {
|
|
245
|
+
delete brightsy.cloudConnectAgent;
|
|
246
|
+
} else if (CLOUD_CONNECT_AGENTS.has(patch.cloudConnectAgent)) {
|
|
247
|
+
brightsy.cloudConnectAgent = patch.cloudConnectAgent;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return saveAppSettings({ ...current, brightsy });
|
|
251
|
+
}
|
|
252
|
+
function updateIntegrationsSettings(patch) {
|
|
253
|
+
const current = loadAppSettings();
|
|
254
|
+
const integrations = { ...current.integrations };
|
|
255
|
+
if ("linearApiKey" in patch) {
|
|
256
|
+
if (patch.linearApiKey == null || patch.linearApiKey.trim() === "") {
|
|
257
|
+
delete integrations.linearApiKey;
|
|
258
|
+
} else {
|
|
259
|
+
integrations.linearApiKey = patch.linearApiKey.trim();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if ("issueSource" in patch) {
|
|
263
|
+
if (patch.issueSource == null) {
|
|
264
|
+
delete integrations.issueSource;
|
|
265
|
+
} else if (ISSUE_SOURCES.has(patch.issueSource)) {
|
|
266
|
+
integrations.issueSource = patch.issueSource;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return saveAppSettings({ ...current, integrations });
|
|
270
|
+
}
|
|
271
|
+
function updateDefaultsSettings(patch) {
|
|
272
|
+
const current = loadAppSettings();
|
|
273
|
+
const defaults = { ...current.defaults };
|
|
274
|
+
if ("agent" in patch) {
|
|
275
|
+
if (patch.agent == null) {
|
|
276
|
+
delete defaults.agent;
|
|
277
|
+
} else if (DEFAULT_AGENTS.has(patch.agent)) {
|
|
278
|
+
defaults.agent = patch.agent;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if ("model" in patch) {
|
|
282
|
+
if (patch.model == null || patch.model.trim() === "") {
|
|
283
|
+
delete defaults.model;
|
|
284
|
+
} else {
|
|
285
|
+
defaults.model = patch.model.trim();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if ("effort" in patch) {
|
|
289
|
+
if (patch.effort == null) {
|
|
290
|
+
delete defaults.effort;
|
|
291
|
+
} else {
|
|
292
|
+
const effort = normalizeThinkingEffort(patch.effort);
|
|
293
|
+
if (effort) defaults.effort = effort;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if ("fast" in patch) {
|
|
297
|
+
if (patch.fast == null) {
|
|
298
|
+
delete defaults.fast;
|
|
299
|
+
} else {
|
|
300
|
+
defaults.fast = Boolean(patch.fast);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return saveAppSettings({ ...current, defaults });
|
|
304
|
+
}
|
|
305
|
+
function getDefaultAgent(settings = loadAppSettings()) {
|
|
306
|
+
return settings.defaults.agent ?? "claude";
|
|
307
|
+
}
|
|
308
|
+
function getDefaultModel(settings = loadAppSettings()) {
|
|
309
|
+
const model = settings.defaults.model?.trim();
|
|
310
|
+
return model || null;
|
|
311
|
+
}
|
|
312
|
+
function getDefaultEffort(settings = loadAppSettings()) {
|
|
313
|
+
return normalizeThinkingEffort(settings.defaults.effort) ?? "high";
|
|
314
|
+
}
|
|
315
|
+
function getDefaultFast(settings = loadAppSettings()) {
|
|
316
|
+
return settings.defaults.fast === true;
|
|
317
|
+
}
|
|
318
|
+
function resolveThreadDefaults(settings = loadAppSettings()) {
|
|
319
|
+
return {
|
|
320
|
+
agent: getDefaultAgent(settings),
|
|
321
|
+
model: getDefaultModel(settings),
|
|
322
|
+
effort: getDefaultEffort(settings),
|
|
323
|
+
fast: getDefaultFast(settings)
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function isLinearConnected(settings = loadAppSettings()) {
|
|
327
|
+
return Boolean(settings.integrations.linearApiKey?.trim());
|
|
328
|
+
}
|
|
329
|
+
function getIssueSource(settings = loadAppSettings()) {
|
|
330
|
+
return settings.integrations.issueSource ?? "github";
|
|
331
|
+
}
|
|
332
|
+
function resolveEffectiveIssueSource(settings = loadAppSettings()) {
|
|
333
|
+
const preferred = getIssueSource(settings);
|
|
334
|
+
if (preferred === "linear" && !isLinearConnected(settings)) return "github";
|
|
335
|
+
return preferred;
|
|
336
|
+
}
|
|
337
|
+
function getLinearApiKey(settings = loadAppSettings()) {
|
|
338
|
+
const key = settings.integrations.linearApiKey?.trim();
|
|
339
|
+
return key || null;
|
|
340
|
+
}
|
|
341
|
+
function brightsyCloudConnectEnabled(settings = loadAppSettings()) {
|
|
342
|
+
return Boolean(settings.brightsy.cloudConnectEnabled);
|
|
343
|
+
}
|
|
344
|
+
function brightsyCloudConnectAgent(settings = loadAppSettings()) {
|
|
345
|
+
return settings.brightsy.cloudConnectAgent ?? "claude";
|
|
346
|
+
}
|
|
347
|
+
function updateAdvancedSettings(patch) {
|
|
348
|
+
const current = loadAppSettings();
|
|
349
|
+
const advanced = { ...current.advanced };
|
|
350
|
+
if (typeof patch.autoRenameBranch === "boolean") {
|
|
351
|
+
advanced.autoRenameBranch = patch.autoRenameBranch;
|
|
352
|
+
}
|
|
353
|
+
if (typeof patch.autoRunAfterSetup === "boolean") {
|
|
354
|
+
advanced.autoRunAfterSetup = patch.autoRunAfterSetup;
|
|
355
|
+
}
|
|
356
|
+
if (typeof patch.caffeinateWhileRunning === "boolean") {
|
|
357
|
+
advanced.caffeinateWhileRunning = patch.caffeinateWhileRunning;
|
|
358
|
+
}
|
|
359
|
+
if (typeof patch.caffeinateWhileCloudConnect === "boolean") {
|
|
360
|
+
advanced.caffeinateWhileCloudConnect = patch.caffeinateWhileCloudConnect;
|
|
361
|
+
}
|
|
362
|
+
if (typeof patch.deleteBranchOnPurge === "boolean") {
|
|
363
|
+
advanced.deleteBranchOnPurge = patch.deleteBranchOnPurge;
|
|
364
|
+
}
|
|
365
|
+
if (typeof patch.maxConcurrent === "number" && Number.isFinite(patch.maxConcurrent)) {
|
|
366
|
+
advanced.maxConcurrent = Math.max(1, Math.min(32, Math.floor(patch.maxConcurrent)));
|
|
367
|
+
}
|
|
368
|
+
if (typeof patch.worktreeMaxCount === "number" && Number.isFinite(patch.worktreeMaxCount)) {
|
|
369
|
+
advanced.worktreeMaxCount = Math.max(1, Math.min(500, Math.floor(patch.worktreeMaxCount)));
|
|
370
|
+
}
|
|
371
|
+
if (typeof patch.worktreeCleanupIntervalHours === "number" && Number.isFinite(patch.worktreeCleanupIntervalHours)) {
|
|
372
|
+
advanced.worktreeCleanupIntervalHours = Math.max(
|
|
373
|
+
1,
|
|
374
|
+
Math.min(168, Math.floor(patch.worktreeCleanupIntervalHours))
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (typeof patch.worktreeLastCleanupAt === "string") {
|
|
378
|
+
advanced.worktreeLastCleanupAt = patch.worktreeLastCleanupAt;
|
|
379
|
+
}
|
|
380
|
+
if (typeof patch.autoCleanupOrphans === "boolean") {
|
|
381
|
+
advanced.autoCleanupOrphans = patch.autoCleanupOrphans;
|
|
382
|
+
}
|
|
383
|
+
if (patch.orchestrationQuotaOnLimit === "switch_agent" || patch.orchestrationQuotaOnLimit === "wait_reset") {
|
|
384
|
+
advanced.orchestrationQuotaOnLimit = patch.orchestrationQuotaOnLimit;
|
|
385
|
+
}
|
|
386
|
+
if (typeof patch.orchestrationQuotaFallbackAgent === "string" && DEFAULT_AGENTS.has(patch.orchestrationQuotaFallbackAgent) && patch.orchestrationQuotaFallbackAgent !== "brightsy") {
|
|
387
|
+
advanced.orchestrationQuotaFallbackAgent = patch.orchestrationQuotaFallbackAgent;
|
|
388
|
+
}
|
|
389
|
+
return saveAppSettings({ ...current, advanced });
|
|
390
|
+
}
|
|
391
|
+
function autoRenameBranchEnabled(settings = loadAppSettings()) {
|
|
392
|
+
return settings.advanced.autoRenameBranch !== false;
|
|
393
|
+
}
|
|
394
|
+
function autoRunAfterSetupEnabled(settings = loadAppSettings()) {
|
|
395
|
+
return Boolean(settings.advanced.autoRunAfterSetup);
|
|
396
|
+
}
|
|
397
|
+
function caffeinateWhileRunningEnabled(settings = loadAppSettings()) {
|
|
398
|
+
return Boolean(settings.advanced.caffeinateWhileRunning);
|
|
399
|
+
}
|
|
400
|
+
function caffeinateWhileCloudConnectEnabled(settings = loadAppSettings()) {
|
|
401
|
+
return Boolean(settings.advanced.caffeinateWhileCloudConnect);
|
|
402
|
+
}
|
|
403
|
+
function deleteBranchOnPurgeEnabled(settings = loadAppSettings()) {
|
|
404
|
+
return Boolean(settings.advanced.deleteBranchOnPurge);
|
|
405
|
+
}
|
|
406
|
+
function autoCleanupOrphansEnabled(settings = loadAppSettings()) {
|
|
407
|
+
return Boolean(settings.advanced.autoCleanupOrphans);
|
|
408
|
+
}
|
|
409
|
+
function orchestrationQuotaOnLimit(settings = loadAppSettings()) {
|
|
410
|
+
return settings.advanced.orchestrationQuotaOnLimit ?? "switch_agent";
|
|
411
|
+
}
|
|
412
|
+
function orchestrationQuotaFallbackAgent(settings = loadAppSettings()) {
|
|
413
|
+
const preferred = settings.advanced.orchestrationQuotaFallbackAgent;
|
|
414
|
+
if (preferred && DEFAULT_AGENTS.has(preferred) && preferred !== "brightsy") {
|
|
415
|
+
return preferred;
|
|
416
|
+
}
|
|
417
|
+
return "cursor";
|
|
418
|
+
}
|
|
419
|
+
function maxConcurrentAgents(settings = loadAppSettings()) {
|
|
420
|
+
const n = settings.advanced.maxConcurrent;
|
|
421
|
+
if (typeof n === "number" && Number.isFinite(n)) {
|
|
422
|
+
return Math.max(1, Math.min(32, Math.floor(n)));
|
|
423
|
+
}
|
|
424
|
+
return 3;
|
|
425
|
+
}
|
|
426
|
+
function resolveClaudeExecutable(settings = loadAppSettings()) {
|
|
427
|
+
const override = settings.claude.executablePath?.trim();
|
|
428
|
+
return override || "claude";
|
|
429
|
+
}
|
|
430
|
+
function claudeChromeEnabled(settings = loadAppSettings()) {
|
|
431
|
+
return Boolean(settings.claude.chromeEnabled);
|
|
432
|
+
}
|
|
433
|
+
function applyAppEnvironment(target = process.env, settings = loadAppSettings()) {
|
|
434
|
+
for (const [key, value] of Object.entries(settings.environment)) {
|
|
435
|
+
if (!key || value == null || value === "") continue;
|
|
436
|
+
if (target[key] == null || target[key] === "") {
|
|
437
|
+
target[key] = value;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return target;
|
|
441
|
+
}
|
|
442
|
+
function childEnvWithAppSettings(extra) {
|
|
443
|
+
const settings = loadAppSettings();
|
|
444
|
+
const env = { ...process.env };
|
|
445
|
+
applyAppEnvironment(env, settings);
|
|
446
|
+
if (extra) {
|
|
447
|
+
for (const [k, v] of Object.entries(extra)) {
|
|
448
|
+
if (v != null) env[k] = v;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return env;
|
|
452
|
+
}
|
|
453
|
+
function harnessEnvKey(harness) {
|
|
454
|
+
return HARNESS_ENV_KEYS[harness];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export {
|
|
458
|
+
HARNESS_ENV_KEYS,
|
|
459
|
+
appSettingsPath,
|
|
460
|
+
claudeUserSettingsPath,
|
|
461
|
+
loadAppSettings,
|
|
462
|
+
saveAppSettings,
|
|
463
|
+
updateAppEnvironment,
|
|
464
|
+
updateClaudeSettings,
|
|
465
|
+
updateBrightsySettings,
|
|
466
|
+
updateIntegrationsSettings,
|
|
467
|
+
updateDefaultsSettings,
|
|
468
|
+
getDefaultAgent,
|
|
469
|
+
getDefaultModel,
|
|
470
|
+
getDefaultEffort,
|
|
471
|
+
getDefaultFast,
|
|
472
|
+
resolveThreadDefaults,
|
|
473
|
+
isLinearConnected,
|
|
474
|
+
getIssueSource,
|
|
475
|
+
resolveEffectiveIssueSource,
|
|
476
|
+
getLinearApiKey,
|
|
477
|
+
brightsyCloudConnectEnabled,
|
|
478
|
+
brightsyCloudConnectAgent,
|
|
479
|
+
updateAdvancedSettings,
|
|
480
|
+
autoRenameBranchEnabled,
|
|
481
|
+
autoRunAfterSetupEnabled,
|
|
482
|
+
caffeinateWhileRunningEnabled,
|
|
483
|
+
caffeinateWhileCloudConnectEnabled,
|
|
484
|
+
deleteBranchOnPurgeEnabled,
|
|
485
|
+
autoCleanupOrphansEnabled,
|
|
486
|
+
orchestrationQuotaOnLimit,
|
|
487
|
+
orchestrationQuotaFallbackAgent,
|
|
488
|
+
maxConcurrentAgents,
|
|
489
|
+
resolveClaudeExecutable,
|
|
490
|
+
claudeChromeEnabled,
|
|
491
|
+
applyAppEnvironment,
|
|
492
|
+
childEnvWithAppSettings,
|
|
493
|
+
harnessEnvKey
|
|
494
|
+
};
|