@sideboard-ai/core 0.1.287 → 0.1.289
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/{abletime-I4RODUKT.js → abletime-4WJUVE64.js} +4 -4
- package/dist/{abletime-4CH2IOX5.js → abletime-VFKYNSJT.js} +4 -4
- package/dist/{abletime-oauth-7OZZ3ABX.js → abletime-oauth-FW24YHYX.js} +3 -3
- package/dist/{abletime-oauth-C2HN2Q6F.js → abletime-oauth-ICMB2XV2.js} +3 -3
- package/dist/{abletime-rest-E4LBRFLM.js → abletime-rest-HFMRTUDT.js} +3 -3
- package/dist/{abletime-rest-WFI2HWBU.js → abletime-rest-OGWC7BC6.js} +3 -3
- package/dist/agents/cursor-runner.js +3 -3
- package/dist/{agents-D3PU4UCP.js → agents-4BBCKITL.js} +20 -18
- package/dist/{agents-KU4QPB3Y.js → agents-XAKSP653.js} +18 -16
- package/dist/{app-settings-EQQZQNYD.js → app-settings-EHMXHNG5.js} +1 -1
- package/dist/{app-settings-NI4X5HW3.js → app-settings-MR2U6AXN.js} +1 -1
- package/dist/{chunk-K5XSTHHP.js → chunk-2IXKTAIT.js} +2 -2
- package/dist/{chunk-YNTXWFXT.js → chunk-36J3IZBS.js} +12 -12
- package/dist/{chunk-IJMUYHPT.js → chunk-3E4NOIXN.js} +3 -3
- package/dist/chunk-4BXRRHDC.js +139 -0
- package/dist/{chunk-5MGLNVNK.js → chunk-5HPMKLJ7.js} +2 -2
- package/dist/{chunk-VOIWX5HA.js → chunk-7UUILVMV.js} +1 -1
- package/dist/chunk-AF2DMY3K.js +295 -0
- package/dist/{chunk-5QOLFOAX.js → chunk-AFLNFGGB.js} +1 -1
- package/dist/{chunk-5T4YWFLD.js → chunk-AHH5USEA.js} +2 -2
- package/dist/{chunk-6CVVNAIU.js → chunk-B3BPLPKQ.js} +2 -2
- package/dist/{chunk-LKVP7JCB.js → chunk-C546EOEF.js} +35 -315
- package/dist/{chunk-QPBR5SAL.js → chunk-EBGJXOXT.js} +27 -129
- package/dist/{chunk-RFPMTOV2.js → chunk-G74TSBJ3.js} +6 -6
- package/dist/{chunk-L65YT2SC.js → chunk-GBAACE3C.js} +7 -7
- package/dist/{chunk-VQEARG5H.js → chunk-GU6DT6NO.js} +5 -5
- package/dist/{chunk-S2OOM7AU.js → chunk-K6IOJI3L.js} +1 -1
- package/dist/{chunk-UFJLNCOF.js → chunk-LUQ7MIVD.js} +8 -298
- package/dist/{chunk-U6QDXT4K.js → chunk-MIVX7QKI.js} +1 -1
- package/dist/{chunk-WRIMAEF2.js → chunk-MVZO7QZ2.js} +12 -12
- package/dist/{chunk-LS4FFCPL.js → chunk-MYJYINTS.js} +3 -3
- package/dist/{chunk-DEJALWKM.js → chunk-NFPWLDCH.js} +5 -5
- package/dist/chunk-NXXQFLNQ.js +300 -0
- package/dist/{chunk-KLAQ4G34.js → chunk-O4324MXE.js} +4 -4
- package/dist/chunk-OH4EN323.js +89 -0
- package/dist/{chunk-ZGAMETNF.js → chunk-V3BSO2O6.js} +4 -4
- package/dist/{chunk-Z3EE3PUZ.js → chunk-VNCPRMIK.js} +10 -300
- package/dist/chunk-W2KZTDET.js +302 -0
- package/dist/{chunk-TMM4FEZ5.js → chunk-Y67TBJD5.js} +3 -3
- package/dist/{chunk-XSDCLM3F.js → chunk-YSLCWCMJ.js} +1 -1
- package/dist/{conductor-FOKYVMUE.js → conductor-5MNGC25D.js} +6 -6
- package/dist/{conductor-KDUL6AMD.js → conductor-UFJXJPTF.js} +7 -7
- package/dist/{coordinator-prompt-GU5PGLG6.js → coordinator-prompt-5Q66Y5KW.js} +4 -4
- package/dist/{coordinator-prompt-YARYX53V.js → coordinator-prompt-BYPEW6DE.js} +4 -4
- package/dist/{global-workspace-SMCPOWE5.js → global-workspace-BTH2YGDZ.js} +4 -4
- package/dist/{global-workspace-C5HMC66E.js → global-workspace-YKOYXSB2.js} +4 -4
- package/dist/index.cjs +618 -323
- package/dist/index.d.cts +42 -10
- package/dist/index.d.ts +42 -10
- package/dist/index.js +494 -281
- package/dist/mcp/run-stdio.cjs +602 -225
- package/dist/mcp/run-stdio.js +334 -188
- package/dist/optional-cli-6SW4OOX3.js +23 -0
- package/dist/optional-cli-FRUZSZ3U.js +98 -0
- package/dist/{orchestrator-2UAVOXTN.js → orchestrator-IG4VU7MT.js} +13 -11
- package/dist/{orchestrator-NWKE56BW.js → orchestrator-MKF3YBNK.js} +10 -8
- package/dist/{thread-store-7XBOUQ5K.js → thread-store-DPKESDFR.js} +1 -1
- package/dist/{thread-store-ZQZDREVH.js → thread-store-X6S2GIR5.js} +1 -1
- package/dist/{workspaces-KHQ5ETSQ.js → workspaces-ELO2YVTU.js} +4 -4
- package/dist/{workspaces-DQMDCRB2.js → workspaces-RFD6I4FM.js} +4 -4
- package/dist/{worktree-NKLNVNXX.js → worktree-ADVQ6X6C.js} +3 -3
- package/dist/{worktree-YTZC3UQN.js → worktree-TN7DZDJZ.js} +3 -3
- package/package.json +1 -1
- /package/dist/{chunk-N5UGX3JL.js → chunk-QB5MDOHL.js} +0 -0
- /package/dist/{chunk-JPH7PYSK.js → chunk-YRXV3E7J.js} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -1160,9 +1160,9 @@ function sumOptional(a, b) {
|
|
|
1160
1160
|
function fromInclusiveInputUsage(opts) {
|
|
1161
1161
|
const totalInput = Number(opts.inputTokens) || 0;
|
|
1162
1162
|
const outputTokens = Number(opts.outputTokens) || 0;
|
|
1163
|
-
const
|
|
1163
|
+
const cached2 = Number(opts.cachedInputTokens) || 0;
|
|
1164
1164
|
if (!totalInput && !outputTokens) return null;
|
|
1165
|
-
const cacheReadTokens =
|
|
1165
|
+
const cacheReadTokens = cached2 > 0 ? Math.min(cached2, totalInput) : 0;
|
|
1166
1166
|
return {
|
|
1167
1167
|
inputTokens: Math.max(0, totalInput - cacheReadTokens),
|
|
1168
1168
|
outputTokens,
|
|
@@ -3247,22 +3247,21 @@ function applyAppEnvironment(target = process.env, settings = loadAppSettings())
|
|
|
3247
3247
|
applyOptionalServiceTokens(target, settings.integrations);
|
|
3248
3248
|
return target;
|
|
3249
3249
|
}
|
|
3250
|
-
function
|
|
3250
|
+
function assignEnv(target, key, value) {
|
|
3251
3251
|
const v = value?.trim();
|
|
3252
3252
|
if (!key || !v) return;
|
|
3253
|
-
|
|
3253
|
+
target[key] = v;
|
|
3254
|
+
}
|
|
3255
|
+
function scrubPosthogAgentEnv(target) {
|
|
3256
|
+
delete target.POSTHOG_PERSONAL_API_KEY;
|
|
3257
|
+
delete target.POSTHOG_HOST;
|
|
3254
3258
|
}
|
|
3255
3259
|
function applyOptionalServiceTokens(target, integrations) {
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
"POSTHOG_HOST",
|
|
3262
|
-
integrations.posthogPersonalApiKey ? integrations.posthogHost || "https://us.posthog.com" : void 0
|
|
3263
|
-
);
|
|
3264
|
-
fillEnvGap(target, "SENTRY_AUTH_TOKEN", integrations.sentryAuthToken);
|
|
3265
|
-
fillEnvGap(
|
|
3260
|
+
assignEnv(target, "VERCEL_TOKEN", integrations.vercelToken);
|
|
3261
|
+
assignEnv(target, "SUPABASE_ACCESS_TOKEN", integrations.supabaseAccessToken);
|
|
3262
|
+
scrubPosthogAgentEnv(target);
|
|
3263
|
+
assignEnv(target, "SENTRY_AUTH_TOKEN", integrations.sentryAuthToken);
|
|
3264
|
+
assignEnv(
|
|
3266
3265
|
target,
|
|
3267
3266
|
"SENTRY_URL",
|
|
3268
3267
|
integrations.sentryAuthToken ? integrations.sentryHost || "https://sentry.io" : void 0
|
|
@@ -3277,6 +3276,7 @@ function childEnvWithAppSettings(extra) {
|
|
|
3277
3276
|
if (v != null) env[k] = v;
|
|
3278
3277
|
}
|
|
3279
3278
|
}
|
|
3279
|
+
scrubPosthogAgentEnv(env);
|
|
3280
3280
|
return env;
|
|
3281
3281
|
}
|
|
3282
3282
|
function harnessEnvKey(harness) {
|
|
@@ -4702,8 +4702,8 @@ function readThread(id) {
|
|
|
4702
4702
|
return null;
|
|
4703
4703
|
}
|
|
4704
4704
|
const cache2 = cacheForDir();
|
|
4705
|
-
const
|
|
4706
|
-
if (
|
|
4705
|
+
const cached2 = cache2.byId.get(id);
|
|
4706
|
+
if (cached2 && cache2.mtimeMs.get(id) === mtimeMs) return cached2;
|
|
4707
4707
|
const raw = (0, import_node_fs12.readFileSync)(path2, "utf8");
|
|
4708
4708
|
const thread = normalizeThread(JSON.parse(raw));
|
|
4709
4709
|
rememberThread(thread, mtimeMs);
|
|
@@ -5081,7 +5081,7 @@ function humanizeAgentFailDetail(detail) {
|
|
|
5081
5081
|
return "Cursor local file search failed (ripgrep / resource_exhausted). Wait a minute and retry; if it keeps happening, check Cursor usage.";
|
|
5082
5082
|
}
|
|
5083
5083
|
if (looksLikeCodexMcpError(raw)) {
|
|
5084
|
-
return `${raw} \u2014 skip that vendor MCP and continue (PostHog: Settings \u2192 Connectors
|
|
5084
|
+
return `${raw} \u2014 skip that vendor MCP and continue (PostHog: Settings \u2192 Connectors and posthog_api).`;
|
|
5085
5085
|
}
|
|
5086
5086
|
if (/invalid user api key|invalid api key|not logged in|not authenticated|unauthorized|authentication|please run.*login|codex login|claude auth|cursor api/.test(
|
|
5087
5087
|
lower
|
|
@@ -6922,15 +6922,15 @@ function loadFromDisk(threadId) {
|
|
|
6922
6922
|
}
|
|
6923
6923
|
}
|
|
6924
6924
|
function memFor(threadId) {
|
|
6925
|
-
const
|
|
6926
|
-
if (
|
|
6925
|
+
const cached2 = memory.get(threadId);
|
|
6926
|
+
if (cached2) return cached2;
|
|
6927
6927
|
const mem = fromSnapshot(loadFromDisk(threadId) ?? emptySetupLog());
|
|
6928
6928
|
memory.set(threadId, mem);
|
|
6929
6929
|
return mem;
|
|
6930
6930
|
}
|
|
6931
6931
|
function readSetupLog(threadId) {
|
|
6932
|
-
const
|
|
6933
|
-
if (
|
|
6932
|
+
const cached2 = memory.get(threadId);
|
|
6933
|
+
if (cached2) return materialize(cached2);
|
|
6934
6934
|
const disk = loadFromDisk(threadId);
|
|
6935
6935
|
if (disk) {
|
|
6936
6936
|
memory.set(threadId, fromSnapshot(disk));
|
|
@@ -7076,15 +7076,15 @@ function loadFromDisk2(key) {
|
|
|
7076
7076
|
}
|
|
7077
7077
|
}
|
|
7078
7078
|
function memFor2(key) {
|
|
7079
|
-
const
|
|
7080
|
-
if (
|
|
7079
|
+
const cached2 = memory2.get(key);
|
|
7080
|
+
if (cached2) return cached2;
|
|
7081
7081
|
const mem = fromSnapshot2(loadFromDisk2(key) ?? emptyRunLog());
|
|
7082
7082
|
memory2.set(key, mem);
|
|
7083
7083
|
return mem;
|
|
7084
7084
|
}
|
|
7085
7085
|
function readRunLog(key) {
|
|
7086
|
-
const
|
|
7087
|
-
if (
|
|
7086
|
+
const cached2 = memory2.get(key);
|
|
7087
|
+
if (cached2) return materialize2(cached2);
|
|
7088
7088
|
const disk = loadFromDisk2(key);
|
|
7089
7089
|
if (disk) {
|
|
7090
7090
|
memory2.set(key, fromSnapshot2(disk));
|
|
@@ -11647,7 +11647,7 @@ var init_coordinator_prompt = __esm({
|
|
|
11647
11647
|
"- linear_* (when Linear is connected) \u2014 list_teams for team key/states; linear_list_projects for project names; get/search include the project a ticket is on; get/create/update/comment with ENG-123. Worktree agents have the same Account tools \u2014 prefer they comment, update status, and create spin-offs (parent=) on their own ticket when they were asked to do the work. When reviewing a PR or ticket, do not comment or update until the user types a next step \u2014 they should work through the feedback in chat first. Do not tell the child to ask_user after the review. Scope errors: reconnect Linear in Account settings. Prefer these over any Linear MCP the CLI may still list.",
|
|
11648
11648
|
"- github_* \u2014 get/comment/update/create GitHub issues via Account `gh` (#123). github_list_projects and get/search include GitHub Projects. projectsError or a missing read:project scope \u2192 `gh auth refresh -s read:project`. Worktree agents have these too. Prefer over any vendor GitHub MCP. Pass parent= for spin-offs. When reviewing a PR or ticket, do not comment, submit `gh pr review`, or update the PR/ticket until the user types a next step.",
|
|
11649
11649
|
"- abletime_* (when AbleTime is connected) \u2014 orientation first; abletime_list_projects lists projects; get/list include the project a task is on; get/comment/update/create (parent= for spin-offs); ensure_task when work has no ticket (or create_workspace from the default branch auto-creates one). Worktree agents have the same Account tools. Same review-write gate as Linear/GitHub.",
|
|
11650
|
-
"- Optional connectors (Vercel, Supabase,
|
|
11650
|
+
"- Optional connectors (Vercel, Supabase, Sentry) in Settings \u2192 Connectors inject tokens into worktree agent env when connected. Prefer official CLIs (`vercel`, `supabase`, `sentry-cli`). PostHog is the Sideboard tool `posthog_api` (GET by default; POST `/api/projects/:id/query/` or `/api/environments/:id/query/` is a read and does not need write=true; other POST and PATCH need write=true only when the user asked to change data). The PostHog key stays in the vault \u2014 do not read it from the environment. If a connector MCP needsAuth or is unauthorized, do not sign in \u2014 use the native CLI or `posthog_api`. Tell any worktree agent (Claude / Cursor / Codex / OpenCode) to write CLI output to `.context/cli/` and read a slice. If a CLI is missing, the user presses Install CLI on that row. Do not add vendor MCPs. Git (`gh`) stays Settings \u2192 Git; issue tracking stays Settings \u2192 Issues. The phone remote is Settings \u2192 Remote \u2014 do not look up Slack users.",
|
|
11651
11651
|
"- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
|
|
11652
11652
|
"- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
|
|
11653
11653
|
"- list_chats / get_chat \u2014 live thread list (parent id + last message preview). get_chat on this orchestration chat lists child worktree agents (status + lastText). blockedReason is why that agent is waiting on a person. Also includes usage / lastTurnUsage.",
|
|
@@ -13982,7 +13982,8 @@ var init_injected_mcp = __esm({
|
|
|
13982
13982
|
"mcp__sideboard__create_schedule",
|
|
13983
13983
|
"mcp__sideboard__update_schedule",
|
|
13984
13984
|
"mcp__sideboard__delete_schedule",
|
|
13985
|
-
"mcp__sideboard__run_schedule"
|
|
13985
|
+
"mcp__sideboard__run_schedule",
|
|
13986
|
+
"mcp__sideboard__posthog_api"
|
|
13986
13987
|
];
|
|
13987
13988
|
SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS = ["mcp__sideboard__github_*"];
|
|
13988
13989
|
SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS = ["mcp__sideboard__linear_*"];
|
|
@@ -15127,8 +15128,8 @@ var init_claude = __esm({
|
|
|
15127
15128
|
return { type: "stdout", data: line };
|
|
15128
15129
|
}
|
|
15129
15130
|
},
|
|
15130
|
-
async resolveSessionId(_worktreePath,
|
|
15131
|
-
return
|
|
15131
|
+
async resolveSessionId(_worktreePath, cached2) {
|
|
15132
|
+
return cached2;
|
|
15132
15133
|
},
|
|
15133
15134
|
async buildAttach(thread) {
|
|
15134
15135
|
const claude = resolveClaudeExecutable();
|
|
@@ -15512,8 +15513,8 @@ var init_codex = __esm({
|
|
|
15512
15513
|
return { type: "stdout", data: line };
|
|
15513
15514
|
}
|
|
15514
15515
|
},
|
|
15515
|
-
async resolveSessionId(_worktreePath,
|
|
15516
|
-
return
|
|
15516
|
+
async resolveSessionId(_worktreePath, cached2) {
|
|
15517
|
+
return cached2;
|
|
15517
15518
|
},
|
|
15518
15519
|
async buildAttach(thread) {
|
|
15519
15520
|
const codex = resolveAgentExecutable("codex");
|
|
@@ -16149,8 +16150,8 @@ var init_cursor = __esm({
|
|
|
16149
16150
|
parseEvent(line) {
|
|
16150
16151
|
return parseCursorRunnerLine(line);
|
|
16151
16152
|
},
|
|
16152
|
-
async resolveSessionId(_worktreePath,
|
|
16153
|
-
return
|
|
16153
|
+
async resolveSessionId(_worktreePath, cached2) {
|
|
16154
|
+
return cached2;
|
|
16154
16155
|
},
|
|
16155
16156
|
async buildAttach(thread) {
|
|
16156
16157
|
const which = await run("which", ["cursor"], { reject: false });
|
|
@@ -16632,8 +16633,8 @@ var init_opencode = __esm({
|
|
|
16632
16633
|
return { type: "stdout", data: line };
|
|
16633
16634
|
}
|
|
16634
16635
|
},
|
|
16635
|
-
async resolveSessionId(worktreePath,
|
|
16636
|
-
const cachedId =
|
|
16636
|
+
async resolveSessionId(worktreePath, cached2) {
|
|
16637
|
+
const cachedId = cached2?.trim() || null;
|
|
16637
16638
|
const listed = await run(
|
|
16638
16639
|
resolveAgentExecutable("opencode"),
|
|
16639
16640
|
["session", "list", "--format", "json"],
|
|
@@ -16960,7 +16961,7 @@ function numberOrUndef(value) {
|
|
|
16960
16961
|
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
16961
16962
|
return value;
|
|
16962
16963
|
}
|
|
16963
|
-
function parseClaudeUsagePayload(raw,
|
|
16964
|
+
function parseClaudeUsagePayload(raw, fetchedAt2 = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
16964
16965
|
if (!raw || typeof raw !== "object") return null;
|
|
16965
16966
|
const obj = raw;
|
|
16966
16967
|
const source = obj.rate_limits && typeof obj.rate_limits === "object" ? obj.rate_limits : obj;
|
|
@@ -16974,7 +16975,7 @@ function parseClaudeUsagePayload(raw, fetchedAt = (/* @__PURE__ */ new Date()).t
|
|
|
16974
16975
|
return {
|
|
16975
16976
|
windows,
|
|
16976
16977
|
...extra ? { extraUsage: extra } : {},
|
|
16977
|
-
fetchedAt
|
|
16978
|
+
fetchedAt: fetchedAt2
|
|
16978
16979
|
};
|
|
16979
16980
|
}
|
|
16980
16981
|
function hottestClaudeWindow(usage) {
|
|
@@ -17017,15 +17018,15 @@ function formatClaudeUsageResetClock(resetsAt) {
|
|
|
17017
17018
|
return at.toISOString();
|
|
17018
17019
|
}
|
|
17019
17020
|
}
|
|
17020
|
-
function formatClaudeUsageFetchedAt(
|
|
17021
|
-
const at = new Date(
|
|
17021
|
+
function formatClaudeUsageFetchedAt(fetchedAt2, now = /* @__PURE__ */ new Date()) {
|
|
17022
|
+
const at = new Date(fetchedAt2);
|
|
17022
17023
|
if (Number.isNaN(at.getTime())) return "";
|
|
17023
17024
|
const minutes = Math.max(0, Math.round((now.getTime() - at.getTime()) / 6e4));
|
|
17024
17025
|
if (minutes < 1) return "Updated just now";
|
|
17025
17026
|
if (minutes < 60) return `Updated ${minutes}m ago`;
|
|
17026
17027
|
const hours = Math.round(minutes / 60);
|
|
17027
17028
|
if (hours < 36) return `Updated ${hours}h ago`;
|
|
17028
|
-
return `Updated ${formatClaudeUsageResetClock(
|
|
17029
|
+
return `Updated ${formatClaudeUsageResetClock(fetchedAt2)}`;
|
|
17029
17030
|
}
|
|
17030
17031
|
function formatClaudeExtraUsageDetail(extra) {
|
|
17031
17032
|
if (!extra.enabled) return "Extra usage off";
|
|
@@ -24869,13 +24870,13 @@ function formatStat(files) {
|
|
|
24869
24870
|
return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
|
|
24870
24871
|
}
|
|
24871
24872
|
function emptyScopeStats() {
|
|
24872
|
-
const
|
|
24873
|
+
const z11 = emptyStat();
|
|
24873
24874
|
return {
|
|
24874
|
-
commits:
|
|
24875
|
-
uncommitted:
|
|
24876
|
-
staged:
|
|
24877
|
-
unstaged:
|
|
24878
|
-
last_turn:
|
|
24875
|
+
commits: z11,
|
|
24876
|
+
uncommitted: z11,
|
|
24877
|
+
staged: z11,
|
|
24878
|
+
unstaged: z11,
|
|
24879
|
+
last_turn: z11
|
|
24879
24880
|
};
|
|
24880
24881
|
}
|
|
24881
24882
|
function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
|
|
@@ -26398,6 +26399,92 @@ var init_instructions = __esm({
|
|
|
26398
26399
|
}
|
|
26399
26400
|
});
|
|
26400
26401
|
|
|
26402
|
+
// src/integrations/optional-cli.ts
|
|
26403
|
+
var optional_cli_exports = {};
|
|
26404
|
+
__export(optional_cli_exports, {
|
|
26405
|
+
clearOptionalCliPresenceCache: () => clearOptionalCliPresenceCache,
|
|
26406
|
+
detectOptionalServiceClis: () => detectOptionalServiceClis,
|
|
26407
|
+
installOptionalServiceCli: () => installOptionalServiceCli,
|
|
26408
|
+
optionalCliPresence: () => optionalCliPresence
|
|
26409
|
+
});
|
|
26410
|
+
async function detectOptionalServiceClis() {
|
|
26411
|
+
enrichPathWithNpmGlobalBin();
|
|
26412
|
+
return Promise.all(
|
|
26413
|
+
OPTIONAL_SERVICES.map(async (spec) => {
|
|
26414
|
+
if (!spec.cli) {
|
|
26415
|
+
return { id: spec.id, cli: null, installed: false, path: null };
|
|
26416
|
+
}
|
|
26417
|
+
const path2 = await whichOnPath(spec.cli);
|
|
26418
|
+
return {
|
|
26419
|
+
id: spec.id,
|
|
26420
|
+
cli: spec.cli,
|
|
26421
|
+
installed: Boolean(path2),
|
|
26422
|
+
path: path2
|
|
26423
|
+
};
|
|
26424
|
+
})
|
|
26425
|
+
);
|
|
26426
|
+
}
|
|
26427
|
+
function clearOptionalCliPresenceCache() {
|
|
26428
|
+
cliPresenceCache.clear();
|
|
26429
|
+
}
|
|
26430
|
+
async function optionalCliPresence(ids) {
|
|
26431
|
+
const presence = {};
|
|
26432
|
+
const now = Date.now();
|
|
26433
|
+
await Promise.all(
|
|
26434
|
+
ids.map(async (id) => {
|
|
26435
|
+
const spec = optionalServiceSpec(id);
|
|
26436
|
+
if (!spec.cli) return;
|
|
26437
|
+
const hit = cliPresenceCache.get(id);
|
|
26438
|
+
if (hit && now - hit.at < CLI_PRESENCE_TTL_MS) {
|
|
26439
|
+
presence[id] = hit.installed;
|
|
26440
|
+
return;
|
|
26441
|
+
}
|
|
26442
|
+
const path2 = await whichOnPath(spec.cli);
|
|
26443
|
+
const installed = Boolean(path2);
|
|
26444
|
+
cliPresenceCache.set(id, { at: now, installed });
|
|
26445
|
+
presence[id] = installed;
|
|
26446
|
+
})
|
|
26447
|
+
);
|
|
26448
|
+
return presence;
|
|
26449
|
+
}
|
|
26450
|
+
async function installOptionalServiceCli(id) {
|
|
26451
|
+
if (!isOptionalServiceId(id)) {
|
|
26452
|
+
return { ok: false, message: "Unknown optional service" };
|
|
26453
|
+
}
|
|
26454
|
+
const spec = optionalServiceSpec(id);
|
|
26455
|
+
if (!spec.cli || !spec.npmPackage) {
|
|
26456
|
+
return {
|
|
26457
|
+
ok: false,
|
|
26458
|
+
message: `${spec.label} has no CLI to install. Agents call posthog_api.`
|
|
26459
|
+
};
|
|
26460
|
+
}
|
|
26461
|
+
enrichPathWithNpmGlobalBin();
|
|
26462
|
+
const existing = await whichOnPath(spec.cli);
|
|
26463
|
+
if (existing) {
|
|
26464
|
+
return {
|
|
26465
|
+
ok: true,
|
|
26466
|
+
command: existing,
|
|
26467
|
+
message: `Already on PATH: ${existing}`
|
|
26468
|
+
};
|
|
26469
|
+
}
|
|
26470
|
+
return installNpmGlobalPackage({
|
|
26471
|
+
npmPackage: spec.npmPackage,
|
|
26472
|
+
installCommand: `npm install -g ${spec.npmPackage}`,
|
|
26473
|
+
cliBin: spec.cli
|
|
26474
|
+
});
|
|
26475
|
+
}
|
|
26476
|
+
var CLI_PRESENCE_TTL_MS, cliPresenceCache;
|
|
26477
|
+
var init_optional_cli = __esm({
|
|
26478
|
+
"src/integrations/optional-cli.ts"() {
|
|
26479
|
+
"use strict";
|
|
26480
|
+
init_install();
|
|
26481
|
+
init_path();
|
|
26482
|
+
init_optional_services();
|
|
26483
|
+
CLI_PRESENCE_TTL_MS = 6e4;
|
|
26484
|
+
cliPresenceCache = /* @__PURE__ */ new Map();
|
|
26485
|
+
}
|
|
26486
|
+
});
|
|
26487
|
+
|
|
26401
26488
|
// src/integrations/optional-services.ts
|
|
26402
26489
|
function optionalServiceSpec(id) {
|
|
26403
26490
|
const spec = OPTIONAL_SERVICES.find((s) => s.id === id);
|
|
@@ -26569,54 +26656,57 @@ function optionalServiceConnected(id, integrations) {
|
|
|
26569
26656
|
function connectedOptionalServices(integrations) {
|
|
26570
26657
|
return OPTIONAL_SERVICES.filter((spec) => optionalServiceConnected(spec.id, integrations));
|
|
26571
26658
|
}
|
|
26572
|
-
function
|
|
26659
|
+
function cliPresencePhrase(spec, cli) {
|
|
26660
|
+
if (!spec.cli || !cli || !(spec.id in cli)) return "";
|
|
26661
|
+
return cli[spec.id] ? " (installed)" : " (not installed \u2014 ask the user to press Install CLI in Settings \u2192 Connectors)";
|
|
26662
|
+
}
|
|
26663
|
+
function connectorDirectiveLine(spec, cli) {
|
|
26664
|
+
const extras = spec.hostEnvKey ? ` / \`${spec.hostEnvKey}\`` : "";
|
|
26665
|
+
if (spec.cli) {
|
|
26666
|
+
return `- ${spec.label}: use \`${spec.cli}\` (\`${spec.envKey}\`${extras})${cliPresencePhrase(spec, cli)}. Do not add a ${spec.label} MCP.`;
|
|
26667
|
+
}
|
|
26668
|
+
return `- ${spec.label}: call \`posthog_api\`. Path must start with \`/api/\`. GET is the default. POST \`/api/projects/:id/query/\` or \`/api/environments/:id/query/\` is a read and does not need \`write=true\`. Other POST and PATCH require \`write=true\` only when the user asked to change data. Do not read the API key from the environment. Do not add a ${spec.label} MCP.`;
|
|
26669
|
+
}
|
|
26670
|
+
function connectorReminderBit(spec, cli) {
|
|
26671
|
+
if (!spec.cli) return `${spec.label}: \`posthog_api\``;
|
|
26672
|
+
const presence = cliPresencePhrase(spec, cli);
|
|
26673
|
+
return `${spec.label}: \`${spec.cli}\`${presence}`;
|
|
26674
|
+
}
|
|
26675
|
+
function formatOptionalServicesDirective(integrations, cli) {
|
|
26573
26676
|
const connected = connectedOptionalServices(integrations);
|
|
26574
26677
|
if (connected.length === 0) return null;
|
|
26678
|
+
const cliConnected = connected.some((spec) => spec.cli);
|
|
26575
26679
|
const lines = [
|
|
26576
|
-
"Optional connectors (connected \u2014 tokens are already in this process env):"
|
|
26680
|
+
cliConnected ? "Optional connectors (connected \u2014 CLI tokens are already in this process env):" : "Optional connectors (connected):",
|
|
26681
|
+
...connected.map((spec) => connectorDirectiveLine(spec, cli)),
|
|
26682
|
+
cliConnected ? "If a connector MCP is listed but needsAuth or returns unauthorized, do not sign in to that MCP. CLI tokens are already in this process. Do not read a PostHog key from the environment." : "If a connector MCP is listed but needsAuth or returns unauthorized, do not sign in to that MCP. Do not read a PostHog key from the environment.",
|
|
26683
|
+
"Do not add a vendor MCP. When a binary is missing, ask the user to press Install CLI in Settings \u2192 Connectors.",
|
|
26684
|
+
"When you shell a connector CLI, write bulky output to `.context/cli/` and read a slice."
|
|
26577
26685
|
];
|
|
26578
|
-
for (const spec of connected) {
|
|
26579
|
-
if (spec.cli) {
|
|
26580
|
-
const extras = spec.hostEnvKey ? ` / \`${spec.hostEnvKey}\`` : "";
|
|
26581
|
-
lines.push(
|
|
26582
|
-
`- ${spec.label}: use the \`${spec.cli}\` CLI (\`${spec.envKey}\`${extras}). Do not add a ${spec.label} MCP.`
|
|
26583
|
-
);
|
|
26584
|
-
} else {
|
|
26585
|
-
const extras = spec.hostEnvKey ? ` / \`${spec.hostEnvKey}\`` : "";
|
|
26586
|
-
lines.push(
|
|
26587
|
-
`- ${spec.label}: no first-class CLI \u2014 call the HTTP API with \`${spec.envKey}\`${extras}. Do not add a ${spec.label} MCP.`
|
|
26588
|
-
);
|
|
26589
|
-
}
|
|
26590
|
-
}
|
|
26591
|
-
lines.push(
|
|
26592
|
-
"If a connector MCP is listed but needsAuth or returns unauthorized, do not sign in to that MCP. Check the native CLI (or the HTTP API when there is no CLI) \u2014 the token is already in this process."
|
|
26593
|
-
);
|
|
26594
|
-
lines.push(
|
|
26595
|
-
"If a CLI is missing, say so or use the HTTP API with the same token. The user can Install CLI from Settings \u2192 Connectors. Do not ask the user to install a vendor MCP."
|
|
26596
|
-
);
|
|
26597
|
-
lines.push(
|
|
26598
|
-
"Safe CLI / HTTP (every connector \u2014 `vercel logs`, Supabase inspect, `sentry-cli`, PostHog API):"
|
|
26599
|
-
);
|
|
26600
|
-
lines.push(
|
|
26601
|
-
'- Never put raw `--json` / `--expand` / huge dumps in the tool result. That crashes any worktree agent mid-turn (Claude / Cursor / Codex / OpenCode). Cursor chat fills with `file://\u2026/cursor-runtime/\u2026/@cursor/sdk/dist/esm/index.js` then minified `importas e from"@bufbuild/protobuf"`.'
|
|
26602
|
-
);
|
|
26603
|
-
lines.push(
|
|
26604
|
-
"- Write output to `.context/cli/` (worktree scratch; not `.context/attachments/`). Then read a slice. Tight window first (`--limit`, recent `--since` / last N events); raise only if empty."
|
|
26605
|
-
);
|
|
26606
|
-
lines.push(
|
|
26607
|
-
"- Filter (`jq`) to the fields you need (timestamp, path, status, message) \u2014 not the full request object."
|
|
26608
|
-
);
|
|
26609
|
-
lines.push(
|
|
26610
|
-
"- One query at a time. After you have the error line, stop fetching. Piping to `head` does not help \u2014 the CLI still buffers until you kill it."
|
|
26611
|
-
);
|
|
26612
|
-
lines.push(
|
|
26613
|
-
"- If a call is still running at ~40s with no useful output, stop waiting on the raw shell; detach (`/long-running`) or `stop_job` if it is hanging / doing the wrong thing."
|
|
26614
|
-
);
|
|
26615
26686
|
return lines.join("\n");
|
|
26616
26687
|
}
|
|
26617
|
-
function formatOptionalServicesReminder(integrations) {
|
|
26618
|
-
|
|
26619
|
-
|
|
26688
|
+
function formatOptionalServicesReminder(integrations, cli) {
|
|
26689
|
+
const connected = connectedOptionalServices(integrations);
|
|
26690
|
+
if (connected.length === 0) return null;
|
|
26691
|
+
const bits = connected.map((spec) => connectorReminderBit(spec, cli)).join(". ");
|
|
26692
|
+
const tokenBit = connected.some((spec) => spec.cli) ? "CLI tokens are already in this process. " : "";
|
|
26693
|
+
return `Settings \u2192 Connectors: ${tokenBit}${bits}. If a connector MCP needsAuth or is unauthorized, ignore it and use the CLI or \`posthog_api\`. Do not add vendor MCPs. Do not read a PostHog key from the environment. Shell dumps from those CLIs go in \`.context/cli/\` \u2014 read a slice.`;
|
|
26694
|
+
}
|
|
26695
|
+
async function connectedCliPresence(integrations) {
|
|
26696
|
+
const ids = connectedOptionalServices(integrations).filter((spec) => spec.cli).map((spec) => spec.id);
|
|
26697
|
+
if (ids.length === 0) return void 0;
|
|
26698
|
+
try {
|
|
26699
|
+
const { optionalCliPresence: optionalCliPresence2 } = await Promise.resolve().then(() => (init_optional_cli(), optional_cli_exports));
|
|
26700
|
+
return await optionalCliPresence2(ids);
|
|
26701
|
+
} catch {
|
|
26702
|
+
return void 0;
|
|
26703
|
+
}
|
|
26704
|
+
}
|
|
26705
|
+
async function formatOptionalServicesDirectiveForTurn(integrations) {
|
|
26706
|
+
return formatOptionalServicesDirective(integrations, await connectedCliPresence(integrations));
|
|
26707
|
+
}
|
|
26708
|
+
async function formatOptionalServicesReminderForTurn(integrations) {
|
|
26709
|
+
return formatOptionalServicesReminder(integrations, await connectedCliPresence(integrations));
|
|
26620
26710
|
}
|
|
26621
26711
|
var OPTIONAL_SERVICE_IDS, OPTIONAL_SERVICES;
|
|
26622
26712
|
var init_optional_services = __esm({
|
|
@@ -26649,7 +26739,7 @@ var init_optional_services = __esm({
|
|
|
26649
26739
|
{
|
|
26650
26740
|
id: "posthog",
|
|
26651
26741
|
label: "PostHog",
|
|
26652
|
-
hint: "Product analytics.
|
|
26742
|
+
hint: "Product analytics. Agents call `posthog_api` on the Sideboard MCP server.",
|
|
26653
26743
|
tokenPlaceholder: "phx_\u2026",
|
|
26654
26744
|
tokenDocs: "https://app.posthog.com/settings/user-api-keys",
|
|
26655
26745
|
envKey: "POSTHOG_PERSONAL_API_KEY",
|
|
@@ -28405,7 +28495,7 @@ var init_orchestrator = __esm({
|
|
|
28405
28495
|
const artifactReminder = thread.agent !== "brightsy" ? formatUiReminder() : null;
|
|
28406
28496
|
const longRunningReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatLongRunningReminder({ activeRuns: worktreeActiveRuns }) : null;
|
|
28407
28497
|
const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
|
|
28408
|
-
const optionalServicesReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ?
|
|
28498
|
+
const optionalServicesReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? await formatOptionalServicesReminderForTurn(loadAppSettings().integrations) : null;
|
|
28409
28499
|
const issueTicket = issueTicketFromThread(thread, resolveEffectiveIssueSource());
|
|
28410
28500
|
const issueToolsReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatIssueToolsReminder({
|
|
28411
28501
|
linear: isLinearConnected(),
|
|
@@ -28476,7 +28566,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
|
|
|
28476
28566
|
});
|
|
28477
28567
|
const artifactDirective = isBrightsy ? null : formatArtifactDirective();
|
|
28478
28568
|
const longRunningDirective = isBrightsy || isOrchestration ? null : formatLongRunningDirective();
|
|
28479
|
-
const optionalServicesDirective = isBrightsy || isOrchestration ? null :
|
|
28569
|
+
const optionalServicesDirective = isBrightsy || isOrchestration ? null : await formatOptionalServicesDirectiveForTurn(loadAppSettings().integrations);
|
|
28480
28570
|
const freshIssueTicket = issueTicketFromThread(fresh, resolveEffectiveIssueSource());
|
|
28481
28571
|
const issueToolsDirective = isBrightsy || isOrchestration ? null : formatIssueToolsDirective({
|
|
28482
28572
|
linear: isLinearConnected(),
|
|
@@ -28653,7 +28743,7 @@ ${formatEnvReminder(projectNameForRepoPath(thread.repoPath || thread.worktreePat
|
|
|
28653
28743
|
}
|
|
28654
28744
|
} catch {
|
|
28655
28745
|
}
|
|
28656
|
-
const retryNote = isolateCodexPluginRetry ? "Codex plugin/MCP failed \u2014 continuing without that vendor plugin (Settings \u2192 Connectors
|
|
28746
|
+
const retryNote = isolateCodexPluginRetry ? "Codex plugin/MCP failed \u2014 continuing without that vendor plugin (PostHog: Settings \u2192 Connectors and posthog_api)" : looksLikeInvalidAgentSession(detail) ? "Agent session missing \u2014 starting a fresh session" : looksLikeV8Oom(detail) ? "Agent ran out of memory \u2014 starting a fresh session" : "Agent runner crashed \u2014 restarting Node once";
|
|
28657
28747
|
pushTurnStderr(stderrTail, retryNote);
|
|
28658
28748
|
this.emit({
|
|
28659
28749
|
type: "turn_output",
|
|
@@ -30572,6 +30662,7 @@ __export(index_exports, {
|
|
|
30572
30662
|
clearAgentDoneCustomSoundFile: () => clearAgentDoneCustomSoundFile,
|
|
30573
30663
|
clearBoardPins: () => clearBoardPins,
|
|
30574
30664
|
clearHomeBoardCache: () => clearHomeBoardCache,
|
|
30665
|
+
clearOptionalCliPresenceCache: () => clearOptionalCliPresenceCache,
|
|
30575
30666
|
clipAgentEventForPaint: () => clipAgentEventForPaint,
|
|
30576
30667
|
clipThinkingForStore: () => clipThinkingForStore,
|
|
30577
30668
|
cloneRepoIntoSideboard: () => cloneRepoIntoSideboard,
|
|
@@ -30738,7 +30829,9 @@ __export(index_exports, {
|
|
|
30738
30829
|
formatMessagesAsTranscript: () => formatMessagesAsTranscript,
|
|
30739
30830
|
formatNotifyOrchestratorPrompt: () => formatNotifyOrchestratorPrompt,
|
|
30740
30831
|
formatOptionalServicesDirective: () => formatOptionalServicesDirective,
|
|
30832
|
+
formatOptionalServicesDirectiveForTurn: () => formatOptionalServicesDirectiveForTurn,
|
|
30741
30833
|
formatOptionalServicesReminder: () => formatOptionalServicesReminder,
|
|
30834
|
+
formatOptionalServicesReminderForTurn: () => formatOptionalServicesReminderForTurn,
|
|
30742
30835
|
formatPlanQuestionAnswers: () => formatPlanQuestionAnswers,
|
|
30743
30836
|
formatPlanQuestionsForChat: () => formatPlanQuestionsForChat,
|
|
30744
30837
|
formatPrGateDirective: () => formatPrGateDirective,
|
|
@@ -31047,6 +31140,7 @@ __export(index_exports, {
|
|
|
31047
31140
|
openPrStackLayers: () => openPrStackLayers,
|
|
31048
31141
|
openStackLayer: () => openStackLayer,
|
|
31049
31142
|
opencodeAdapter: () => opencodeAdapter,
|
|
31143
|
+
optionalCliPresence: () => optionalCliPresence,
|
|
31050
31144
|
optionalServiceConnected: () => optionalServiceConnected,
|
|
31051
31145
|
optionalServiceSpec: () => optionalServiceSpec,
|
|
31052
31146
|
orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
|
|
@@ -33753,56 +33847,7 @@ function commentsFromAbleTimeIssues(tasks, issues, sinceIso) {
|
|
|
33753
33847
|
|
|
33754
33848
|
// src/index.ts
|
|
33755
33849
|
init_optional_services();
|
|
33756
|
-
|
|
33757
|
-
// src/integrations/optional-cli.ts
|
|
33758
|
-
init_install();
|
|
33759
|
-
init_path();
|
|
33760
|
-
init_optional_services();
|
|
33761
|
-
async function detectOptionalServiceClis() {
|
|
33762
|
-
enrichPathWithNpmGlobalBin();
|
|
33763
|
-
return Promise.all(
|
|
33764
|
-
OPTIONAL_SERVICES.map(async (spec) => {
|
|
33765
|
-
if (!spec.cli) {
|
|
33766
|
-
return { id: spec.id, cli: null, installed: false, path: null };
|
|
33767
|
-
}
|
|
33768
|
-
const path2 = await whichOnPath(spec.cli);
|
|
33769
|
-
return {
|
|
33770
|
-
id: spec.id,
|
|
33771
|
-
cli: spec.cli,
|
|
33772
|
-
installed: Boolean(path2),
|
|
33773
|
-
path: path2
|
|
33774
|
-
};
|
|
33775
|
-
})
|
|
33776
|
-
);
|
|
33777
|
-
}
|
|
33778
|
-
async function installOptionalServiceCli(id) {
|
|
33779
|
-
if (!isOptionalServiceId(id)) {
|
|
33780
|
-
return { ok: false, message: "Unknown optional service" };
|
|
33781
|
-
}
|
|
33782
|
-
const spec = optionalServiceSpec(id);
|
|
33783
|
-
if (!spec.cli || !spec.npmPackage) {
|
|
33784
|
-
return {
|
|
33785
|
-
ok: false,
|
|
33786
|
-
message: `${spec.label} has no CLI to install. Use the HTTP API with the stored token.`
|
|
33787
|
-
};
|
|
33788
|
-
}
|
|
33789
|
-
enrichPathWithNpmGlobalBin();
|
|
33790
|
-
const existing = await whichOnPath(spec.cli);
|
|
33791
|
-
if (existing) {
|
|
33792
|
-
return {
|
|
33793
|
-
ok: true,
|
|
33794
|
-
command: existing,
|
|
33795
|
-
message: `Already on PATH: ${existing}`
|
|
33796
|
-
};
|
|
33797
|
-
}
|
|
33798
|
-
return installNpmGlobalPackage({
|
|
33799
|
-
npmPackage: spec.npmPackage,
|
|
33800
|
-
installCommand: `npm install -g ${spec.npmPackage}`,
|
|
33801
|
-
cliBin: spec.cli
|
|
33802
|
-
});
|
|
33803
|
-
}
|
|
33804
|
-
|
|
33805
|
-
// src/index.ts
|
|
33850
|
+
init_optional_cli();
|
|
33806
33851
|
init_fetch();
|
|
33807
33852
|
init_system_ca();
|
|
33808
33853
|
init_agents();
|
|
@@ -34172,7 +34217,7 @@ function createClippedMcpServer() {
|
|
|
34172
34217
|
}
|
|
34173
34218
|
|
|
34174
34219
|
// src/mcp/server.ts
|
|
34175
|
-
var
|
|
34220
|
+
var import_zod10 = require("zod");
|
|
34176
34221
|
var import_node_crypto21 = require("crypto");
|
|
34177
34222
|
var import_node_path66 = require("path");
|
|
34178
34223
|
init_orchestrator();
|
|
@@ -35279,6 +35324,151 @@ function registerConnectedIssueVendorTools(server, settings = loadAppSettings())
|
|
|
35279
35324
|
if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
|
|
35280
35325
|
}
|
|
35281
35326
|
|
|
35327
|
+
// src/mcp/posthog-tools.ts
|
|
35328
|
+
var import_zod8 = require("zod");
|
|
35329
|
+
init_fetch();
|
|
35330
|
+
init_optional_services();
|
|
35331
|
+
init_app_settings();
|
|
35332
|
+
init_issue_list();
|
|
35333
|
+
var POSTHOG_METHODS = ["GET", "POST", "PATCH"];
|
|
35334
|
+
var POSTHOG_RESPONSE_MAX_CHARS = 256e3;
|
|
35335
|
+
var POSTHOG_TIMEOUT_MS = 2e4;
|
|
35336
|
+
var POSTHOG_READ_QUERY = /^\/api\/(?:projects|environments)\/[^/]+\/query$/;
|
|
35337
|
+
function isPosthogReadQuery(method, path2) {
|
|
35338
|
+
if (method !== "POST") return false;
|
|
35339
|
+
return POSTHOG_READ_QUERY.test(path2.trim().replace(/\/+$/, ""));
|
|
35340
|
+
}
|
|
35341
|
+
function withPosthogSlash(method, path2) {
|
|
35342
|
+
const raw = path2.trim();
|
|
35343
|
+
if ((method === "POST" || method === "PATCH") && raw.startsWith("/api/") && !raw.endsWith("/")) {
|
|
35344
|
+
return `${raw}/`;
|
|
35345
|
+
}
|
|
35346
|
+
return raw;
|
|
35347
|
+
}
|
|
35348
|
+
function posthogApiUrl(host, path2, query) {
|
|
35349
|
+
const raw = path2.trim();
|
|
35350
|
+
if (!raw.startsWith("/api/")) {
|
|
35351
|
+
throw new Error("path must start with /api/");
|
|
35352
|
+
}
|
|
35353
|
+
if (raw.includes("://") || raw.startsWith("//") || /[?#\s\\]/.test(raw) || raw.includes("..")) {
|
|
35354
|
+
throw new Error("path must be a PostHog API path, not a URL");
|
|
35355
|
+
}
|
|
35356
|
+
const origin = normalizeServiceOrigin(host, "https://us.posthog.com");
|
|
35357
|
+
const url = new URL(raw, origin);
|
|
35358
|
+
if (url.origin !== new URL(origin).origin || !url.pathname.startsWith("/api/")) {
|
|
35359
|
+
throw new Error("path must stay on the connected PostHog host");
|
|
35360
|
+
}
|
|
35361
|
+
if (query) {
|
|
35362
|
+
for (const [key, value] of Object.entries(query)) {
|
|
35363
|
+
if (!key.trim()) throw new Error("query keys must be non-empty");
|
|
35364
|
+
url.searchParams.set(key, value);
|
|
35365
|
+
}
|
|
35366
|
+
}
|
|
35367
|
+
return url.toString();
|
|
35368
|
+
}
|
|
35369
|
+
async function runPosthogApi(input) {
|
|
35370
|
+
const method = input.method;
|
|
35371
|
+
if (!POSTHOG_METHODS.includes(method)) {
|
|
35372
|
+
return mcpJson({ ok: false, error: "method must be GET, POST, or PATCH" }, true);
|
|
35373
|
+
}
|
|
35374
|
+
if (method !== "GET" && input.write !== true && !isPosthogReadQuery(method, input.path)) {
|
|
35375
|
+
return mcpJson(
|
|
35376
|
+
{
|
|
35377
|
+
ok: false,
|
|
35378
|
+
error: "POST and PATCH require write=true, and only when the user asked to change PostHog data. POST /api/projects/:id/query/ and POST /api/environments/:id/query/ are reads and do not need write=true."
|
|
35379
|
+
},
|
|
35380
|
+
true
|
|
35381
|
+
);
|
|
35382
|
+
}
|
|
35383
|
+
if (method === "GET" && input.body !== void 0) {
|
|
35384
|
+
return mcpJson({ ok: false, error: "GET does not take a body" }, true);
|
|
35385
|
+
}
|
|
35386
|
+
const integrations = loadAppSettings().integrations;
|
|
35387
|
+
const token = integrations.posthogPersonalApiKey?.trim();
|
|
35388
|
+
if (!token) {
|
|
35389
|
+
return mcpJson(
|
|
35390
|
+
{ ok: false, error: "PostHog is not connected (Settings \u2192 Connectors)." },
|
|
35391
|
+
true
|
|
35392
|
+
);
|
|
35393
|
+
}
|
|
35394
|
+
let url;
|
|
35395
|
+
try {
|
|
35396
|
+
url = posthogApiUrl(
|
|
35397
|
+
integrations.posthogHost || "https://us.posthog.com",
|
|
35398
|
+
withPosthogSlash(method, input.path),
|
|
35399
|
+
input.query
|
|
35400
|
+
);
|
|
35401
|
+
} catch (err) {
|
|
35402
|
+
return mcpJson(
|
|
35403
|
+
{ ok: false, error: err instanceof Error ? err.message : String(err) },
|
|
35404
|
+
true
|
|
35405
|
+
);
|
|
35406
|
+
}
|
|
35407
|
+
const headers = {
|
|
35408
|
+
Authorization: `Bearer ${token}`,
|
|
35409
|
+
Accept: "application/json"
|
|
35410
|
+
};
|
|
35411
|
+
let body;
|
|
35412
|
+
if (input.body !== void 0) {
|
|
35413
|
+
headers["Content-Type"] = "application/json";
|
|
35414
|
+
body = typeof input.body === "string" ? input.body : JSON.stringify(input.body);
|
|
35415
|
+
}
|
|
35416
|
+
try {
|
|
35417
|
+
const res = await httpFetch(url, {
|
|
35418
|
+
method,
|
|
35419
|
+
headers,
|
|
35420
|
+
body,
|
|
35421
|
+
signal: AbortSignal.timeout(POSTHOG_TIMEOUT_MS)
|
|
35422
|
+
});
|
|
35423
|
+
const text7 = await res.text();
|
|
35424
|
+
const truncated = text7.length > POSTHOG_RESPONSE_MAX_CHARS;
|
|
35425
|
+
const slice = truncated ? text7.slice(0, POSTHOG_RESPONSE_MAX_CHARS) : text7;
|
|
35426
|
+
let parsed = slice;
|
|
35427
|
+
if (slice.trim()) {
|
|
35428
|
+
try {
|
|
35429
|
+
parsed = JSON.parse(slice);
|
|
35430
|
+
} catch {
|
|
35431
|
+
parsed = slice;
|
|
35432
|
+
}
|
|
35433
|
+
} else {
|
|
35434
|
+
parsed = null;
|
|
35435
|
+
}
|
|
35436
|
+
return mcpJson(
|
|
35437
|
+
{
|
|
35438
|
+
ok: res.ok,
|
|
35439
|
+
status: res.status,
|
|
35440
|
+
...truncated ? { truncated: true } : {},
|
|
35441
|
+
body: parsed
|
|
35442
|
+
},
|
|
35443
|
+
!res.ok
|
|
35444
|
+
);
|
|
35445
|
+
} catch (err) {
|
|
35446
|
+
return mcpJson(
|
|
35447
|
+
{ ok: false, error: err instanceof Error ? err.message : String(err) },
|
|
35448
|
+
true
|
|
35449
|
+
);
|
|
35450
|
+
}
|
|
35451
|
+
}
|
|
35452
|
+
var POSTHOG_API_DESCRIPTION = "Call the connected PostHog project (Settings \u2192 Connectors). path must start with /api/ and is joined to the stored host. GET is the default. POST /api/projects/:id/query/ and POST /api/environments/:id/query/ are reads (HogQL and insights) and do not need write=true. Other POST and PATCH require write=true, and only when the user asked to change PostHog data. The API key is not in the environment. Do not sign in to a PostHog MCP.";
|
|
35453
|
+
function registerPosthogTools(server) {
|
|
35454
|
+
server.tool(
|
|
35455
|
+
"posthog_api",
|
|
35456
|
+
POSTHOG_API_DESCRIPTION,
|
|
35457
|
+
{
|
|
35458
|
+
method: import_zod8.z.enum(POSTHOG_METHODS).describe(
|
|
35459
|
+
"GET, POST /api/projects|environments/:id/query/ for a read, or POST/PATCH when the user asked for a change"
|
|
35460
|
+
),
|
|
35461
|
+
path: import_zod8.z.string().describe("PostHog path starting with /api/, not a full URL"),
|
|
35462
|
+
query: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.string()).optional().describe("Query string parameters"),
|
|
35463
|
+
body: import_zod8.z.unknown().optional().describe("JSON body for POST or PATCH"),
|
|
35464
|
+
write: import_zod8.z.boolean().optional().describe(
|
|
35465
|
+
"Required true for POST and PATCH that change data. Omit for GET and for POST .../query/."
|
|
35466
|
+
)
|
|
35467
|
+
},
|
|
35468
|
+
async (input) => runPosthogApi(input)
|
|
35469
|
+
);
|
|
35470
|
+
}
|
|
35471
|
+
|
|
35282
35472
|
// src/mcp/server.ts
|
|
35283
35473
|
init_viewer_context_tools();
|
|
35284
35474
|
init_issue_list();
|
|
@@ -35323,7 +35513,7 @@ init_list_prs();
|
|
|
35323
35513
|
init_app_settings();
|
|
35324
35514
|
|
|
35325
35515
|
// src/mcp/schedule-tools.ts
|
|
35326
|
-
var
|
|
35516
|
+
var import_zod9 = require("zod");
|
|
35327
35517
|
init_schedules();
|
|
35328
35518
|
init_schedule_runner();
|
|
35329
35519
|
init_issue_list();
|
|
@@ -35366,18 +35556,18 @@ function registerScheduleTools(server) {
|
|
|
35366
35556
|
"create_schedule",
|
|
35367
35557
|
"Create a local schedule that, when due, sends a prompt to an existing chat (chatId) or starts a new Global orchestration chat (omit chatId). Pass chatId=self to continue this chat. If that chat is missing or archived when the job fires, Sideboard starts a new Global chat (recurring jobs then continue that chat). Exactly one of at (ISO datetime), every (15m/1h/6h/1d), or cron (5-field). Recurring jobs without chatId open a new Global chat each run. Creating or enabling a future job turns on Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled so the Mac stays awake until due. Sideboard.app must be running for the job to fire.",
|
|
35368
35558
|
{
|
|
35369
|
-
prompt:
|
|
35370
|
-
name:
|
|
35371
|
-
at:
|
|
35372
|
-
every:
|
|
35373
|
-
cron:
|
|
35374
|
-
tz:
|
|
35375
|
-
chatId:
|
|
35559
|
+
prompt: import_zod9.z.string().describe("User message / goal queued when the schedule fires"),
|
|
35560
|
+
name: import_zod9.z.string().optional(),
|
|
35561
|
+
at: import_zod9.z.string().optional().describe("ISO datetime for a one-shot"),
|
|
35562
|
+
every: import_zod9.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
|
|
35563
|
+
cron: import_zod9.z.string().optional().describe("5-field cron expression"),
|
|
35564
|
+
tz: import_zod9.z.string().optional().describe("IANA timezone for cron (default: system)"),
|
|
35565
|
+
chatId: import_zod9.z.string().optional().describe(
|
|
35376
35566
|
'Existing chat id (orchestration or workspace), or "self" for this chat. Omit to create a new Global chat on fire.'
|
|
35377
35567
|
),
|
|
35378
|
-
threadId:
|
|
35379
|
-
agent:
|
|
35380
|
-
model:
|
|
35568
|
+
threadId: import_zod9.z.string().optional().describe("Legacy alias for chatId."),
|
|
35569
|
+
agent: import_zod9.z.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
|
|
35570
|
+
model: import_zod9.z.string().optional()
|
|
35381
35571
|
},
|
|
35382
35572
|
async (args) => {
|
|
35383
35573
|
try {
|
|
@@ -35413,20 +35603,20 @@ function registerScheduleTools(server) {
|
|
|
35413
35603
|
"update_schedule",
|
|
35414
35604
|
"Update a local schedule (prompt, cadence, target chat, enabled). Pass id from list_schedules.",
|
|
35415
35605
|
{
|
|
35416
|
-
id:
|
|
35417
|
-
prompt:
|
|
35418
|
-
name:
|
|
35419
|
-
at:
|
|
35420
|
-
every:
|
|
35421
|
-
cron:
|
|
35422
|
-
tz:
|
|
35423
|
-
chatId:
|
|
35606
|
+
id: import_zod9.z.string(),
|
|
35607
|
+
prompt: import_zod9.z.string().optional(),
|
|
35608
|
+
name: import_zod9.z.string().optional(),
|
|
35609
|
+
at: import_zod9.z.string().optional(),
|
|
35610
|
+
every: import_zod9.z.string().optional(),
|
|
35611
|
+
cron: import_zod9.z.string().optional(),
|
|
35612
|
+
tz: import_zod9.z.string().optional(),
|
|
35613
|
+
chatId: import_zod9.z.string().optional().describe(
|
|
35424
35614
|
'Existing chat (orchestration or workspace), "self", or empty string to create a new Global chat each run'
|
|
35425
35615
|
),
|
|
35426
|
-
threadId:
|
|
35427
|
-
agent:
|
|
35428
|
-
model:
|
|
35429
|
-
enabled:
|
|
35616
|
+
threadId: import_zod9.z.string().optional().describe("Legacy alias for chatId."),
|
|
35617
|
+
agent: import_zod9.z.enum(["claude", "cursor", "codex", "opencode"]).optional(),
|
|
35618
|
+
model: import_zod9.z.string().optional(),
|
|
35619
|
+
enabled: import_zod9.z.boolean().optional()
|
|
35430
35620
|
},
|
|
35431
35621
|
async (args) => {
|
|
35432
35622
|
try {
|
|
@@ -35467,7 +35657,7 @@ function registerScheduleTools(server) {
|
|
|
35467
35657
|
server.tool(
|
|
35468
35658
|
"delete_schedule",
|
|
35469
35659
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
35470
|
-
{ id:
|
|
35660
|
+
{ id: import_zod9.z.string() },
|
|
35471
35661
|
async ({ id }) => {
|
|
35472
35662
|
try {
|
|
35473
35663
|
deleteSchedule(id);
|
|
@@ -35480,7 +35670,7 @@ function registerScheduleTools(server) {
|
|
|
35480
35670
|
server.tool(
|
|
35481
35671
|
"run_schedule",
|
|
35482
35672
|
"Fire a schedule now (does not wait for nextRunAt). Queues the prompt on the target chat or starts a new Global chat. If Sideboard.app is running, the desktop drains the turn.",
|
|
35483
|
-
{ id:
|
|
35673
|
+
{ id: import_zod9.z.string() },
|
|
35484
35674
|
async ({ id }) => {
|
|
35485
35675
|
try {
|
|
35486
35676
|
if (!getSchedule(id)) {
|
|
@@ -35916,15 +36106,15 @@ async function getHomeBoardInputs(workspaces, opts) {
|
|
|
35916
36106
|
}
|
|
35917
36107
|
const promise = (async () => {
|
|
35918
36108
|
const inputs = await loadHomeBoardInputs(workspaces);
|
|
35919
|
-
const
|
|
36109
|
+
const fetchedAt2 = opts?.now ?? Date.now();
|
|
35920
36110
|
const pins = syncBoardPins(listBoardPins(), inputs.issues, inputs.prs);
|
|
35921
36111
|
replaceBoardPins(pins);
|
|
35922
|
-
const loaded = { ...inputs, fetchedAt, fromCache: false, pins };
|
|
36112
|
+
const loaded = { ...inputs, fetchedAt: fetchedAt2, fromCache: false, pins };
|
|
35923
36113
|
if (shouldCacheHomeBoardInputs(inputs)) {
|
|
35924
36114
|
const entry = {
|
|
35925
36115
|
version: CACHE_VERSION,
|
|
35926
36116
|
workspaceKey: key,
|
|
35927
|
-
fetchedAt,
|
|
36117
|
+
fetchedAt: fetchedAt2,
|
|
35928
36118
|
inputs
|
|
35929
36119
|
};
|
|
35930
36120
|
memory3 = entry;
|
|
@@ -35984,6 +36174,7 @@ async function startMcpServer() {
|
|
|
35984
36174
|
}
|
|
35985
36175
|
};
|
|
35986
36176
|
registerViewerContextTools(server);
|
|
36177
|
+
registerPosthogTools(server);
|
|
35987
36178
|
if (worktreeProfile) {
|
|
35988
36179
|
registerConnectedIssueVendorTools(server);
|
|
35989
36180
|
}
|
|
@@ -36039,16 +36230,16 @@ async function startMcpServer() {
|
|
|
36039
36230
|
"list_board",
|
|
36040
36231
|
"Home Kanban of workspaces (New, Draft, Review, Merged) \u2014 one card per checkout; sibling chats on that card are separate agents. Same cards as desktop Home. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind (ticket/PR/branch source), ownership (mine = your PRs / WIP; reviewing = someone else's PR), column, limit (default 40). create_workspace adds a workspace (and a Home card), or returns the live checkout if that ticket/PR/named branch is already checked out. That reuse is one checkout, not one agent \u2014 fork_chat to add another.",
|
|
36041
36232
|
{
|
|
36042
|
-
query:
|
|
36043
|
-
repoPath:
|
|
36044
|
-
kind:
|
|
36045
|
-
ownership:
|
|
36233
|
+
query: import_zod10.z.string().optional().describe("Case-insensitive token search across title, id, labels, workspace tags, repo"),
|
|
36234
|
+
repoPath: import_zod10.z.string().optional().describe("Limit to one project path from list_projects"),
|
|
36235
|
+
kind: import_zod10.z.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
36236
|
+
ownership: import_zod10.z.enum(["all", "mine", "reviewing"]).optional().describe(
|
|
36046
36237
|
"Mine = PRs you authored or local WIP; reviewing = someone else's PR. Uses the gh login."
|
|
36047
36238
|
),
|
|
36048
|
-
column:
|
|
36239
|
+
column: import_zod10.z.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
36049
36240
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
36050
36241
|
),
|
|
36051
|
-
limit:
|
|
36242
|
+
limit: import_zod10.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
36052
36243
|
},
|
|
36053
36244
|
async ({ query, repoPath, kind, ownership, column, limit }) => {
|
|
36054
36245
|
const workspaces = orch.listWorkspaces();
|
|
@@ -36078,7 +36269,7 @@ async function startMcpServer() {
|
|
|
36078
36269
|
server.tool(
|
|
36079
36270
|
"get_chat",
|
|
36080
36271
|
"Get a compact chat summary by id/ref. Includes last message preview, parentChatId, and child workspace chats (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (billed token + costUsd totals when providers reported cost) and lastTurnUsage. blockedReason is why that agent is waiting on a person (question, plan approval, or a reported block).",
|
|
36081
|
-
{ ref:
|
|
36272
|
+
{ ref: import_zod10.z.string() },
|
|
36082
36273
|
async ({ ref }) => {
|
|
36083
36274
|
const t = orch.getThread(ref);
|
|
36084
36275
|
if (!t) {
|
|
@@ -36125,17 +36316,17 @@ async function startMcpServer() {
|
|
|
36125
36316
|
"present_artifact",
|
|
36126
36317
|
"Show a document or live log in Sideboard\u2019s side column. For html/svg/markdown/react, pass the FULL document and do not also fence that same body in chat. For type=log, pass only NEW lines (same artifact_id appends). Prefer type=log for long-running job output \u2014 do not resend HTML. type=react is a single default-export component (JSX/TSX); only react/react-dom imports.",
|
|
36127
36318
|
{
|
|
36128
|
-
title:
|
|
36129
|
-
type:
|
|
36319
|
+
title: import_zod10.z.string().describe("Short title shown in the artifact pane header"),
|
|
36320
|
+
type: import_zod10.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
36130
36321
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
36131
36322
|
),
|
|
36132
|
-
content:
|
|
36323
|
+
content: import_zod10.z.string().describe(
|
|
36133
36324
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
36134
36325
|
),
|
|
36135
|
-
artifact_id:
|
|
36136
|
-
status:
|
|
36137
|
-
phase:
|
|
36138
|
-
mode:
|
|
36326
|
+
artifact_id: import_zod10.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
36327
|
+
status: import_zod10.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
36328
|
+
phase: import_zod10.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
36329
|
+
mode: import_zod10.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
36139
36330
|
},
|
|
36140
36331
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
36141
36332
|
return mcpJson(
|
|
@@ -36148,15 +36339,15 @@ async function startMcpServer() {
|
|
|
36148
36339
|
"ask_user",
|
|
36149
36340
|
"Ask the user a clarifying multiple-choice question in Sideboard\u2019s composer. Call only when work is blocked on choosing among a few concrete options (approach fork, which API, auth vs cookies). Do not call for greetings, check-ins, \u201Chello\u201D, open-ended how-can-I-help, or to invent a menu of possible next tasks \u2014 reply in chat instead. If one option is the obvious default, proceed without asking. Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. After calling, stop and wait for answers. Not for \u201Cis the plan ready?\u201D.",
|
|
36150
36341
|
{
|
|
36151
|
-
questions:
|
|
36152
|
-
|
|
36153
|
-
question:
|
|
36154
|
-
header:
|
|
36155
|
-
multiSelect:
|
|
36156
|
-
options:
|
|
36157
|
-
|
|
36158
|
-
label:
|
|
36159
|
-
description:
|
|
36342
|
+
questions: import_zod10.z.array(
|
|
36343
|
+
import_zod10.z.object({
|
|
36344
|
+
question: import_zod10.z.string().describe("Full question text ending with ?"),
|
|
36345
|
+
header: import_zod10.z.string().max(24).optional().describe("Short label shown above the question"),
|
|
36346
|
+
multiSelect: import_zod10.z.boolean().optional().describe("Allow selecting multiple options"),
|
|
36347
|
+
options: import_zod10.z.array(
|
|
36348
|
+
import_zod10.z.object({
|
|
36349
|
+
label: import_zod10.z.string(),
|
|
36350
|
+
description: import_zod10.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
36160
36351
|
})
|
|
36161
36352
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
36162
36353
|
})
|
|
@@ -36182,9 +36373,9 @@ async function startMcpServer() {
|
|
|
36182
36373
|
"present_plan",
|
|
36183
36374
|
PRESENT_PLAN_TOOL_DESCRIPTION,
|
|
36184
36375
|
{
|
|
36185
|
-
title:
|
|
36186
|
-
content:
|
|
36187
|
-
thread_id:
|
|
36376
|
+
title: import_zod10.z.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
36377
|
+
content: import_zod10.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
36378
|
+
thread_id: import_zod10.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
36188
36379
|
},
|
|
36189
36380
|
async ({ title, content, thread_id }) => {
|
|
36190
36381
|
const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
|
|
@@ -36213,15 +36404,15 @@ async function startMcpServer() {
|
|
|
36213
36404
|
"present_schema",
|
|
36214
36405
|
"Open Sideboard\u2019s schema-driven side column (filterable table and/or form) when the user needs to filter, edit, publish, or persist records. Do not call this just to re-display rows you already wrote as a markdown table. If the user asks for an editable / interactive table, call this even if chat already showed those rows. Pass JSON Schema + optional schemaUi. Prefer datasource=inline with embedded resource/records. Use datasource=brightsy with resource_id only when the user is logged into Brightsy.",
|
|
36215
36406
|
{
|
|
36216
|
-
title:
|
|
36217
|
-
mode:
|
|
36218
|
-
datasource:
|
|
36219
|
-
resource_id:
|
|
36220
|
-
record_id:
|
|
36221
|
-
resource:
|
|
36222
|
-
record:
|
|
36223
|
-
records:
|
|
36224
|
-
pane_id:
|
|
36407
|
+
title: import_zod10.z.string().describe("Pane title"),
|
|
36408
|
+
mode: import_zod10.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
36409
|
+
datasource: import_zod10.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
36410
|
+
resource_id: import_zod10.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
36411
|
+
record_id: import_zod10.z.string().optional().describe("Record id when opening form mode"),
|
|
36412
|
+
resource: import_zod10.z.record(import_zod10.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
36413
|
+
record: import_zod10.z.record(import_zod10.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
36414
|
+
records: import_zod10.z.array(import_zod10.z.record(import_zod10.z.unknown())).optional().describe("Inline records for table mode"),
|
|
36415
|
+
pane_id: import_zod10.z.string().optional().describe("Stable pane id across updates")
|
|
36225
36416
|
},
|
|
36226
36417
|
async (args) => {
|
|
36227
36418
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -36242,10 +36433,10 @@ async function startMcpServer() {
|
|
|
36242
36433
|
"present_files",
|
|
36243
36434
|
"Open Sideboard\u2019s Files column (CMS-style file manager: browse, upload, pick). Use datasource=brightsy when the user is logged into Brightsy account storage; datasource=memory for a session-local demo store. Prefer this over claiming a file manager UI is unavailable. Pair with present_schema when editing records that need media.",
|
|
36244
36435
|
{
|
|
36245
|
-
title:
|
|
36246
|
-
datasource:
|
|
36247
|
-
path:
|
|
36248
|
-
pane_id:
|
|
36436
|
+
title: import_zod10.z.string().optional().describe("Pane title (default: Files)"),
|
|
36437
|
+
datasource: import_zod10.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
36438
|
+
path: import_zod10.z.string().optional().describe("Initial folder path (e.g. public)"),
|
|
36439
|
+
pane_id: import_zod10.z.string().optional().describe("Stable pane id across updates")
|
|
36249
36440
|
},
|
|
36250
36441
|
async (args) => {
|
|
36251
36442
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -36265,8 +36456,8 @@ async function startMcpServer() {
|
|
|
36265
36456
|
"wait_for_job",
|
|
36266
36457
|
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. Sideboard opens a type=log pane from this result (artifact_id = job id, content = delta). stillRunning is the source of truth \u2014 if true, call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
36267
36458
|
{
|
|
36268
|
-
id:
|
|
36269
|
-
timeoutMs:
|
|
36459
|
+
id: import_zod10.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
36460
|
+
timeoutMs: import_zod10.z.number().optional()
|
|
36270
36461
|
},
|
|
36271
36462
|
async ({ id, timeoutMs }) => {
|
|
36272
36463
|
const result = await waitForDetachedJob(process.cwd(), id, {
|
|
@@ -36279,8 +36470,8 @@ async function startMcpServer() {
|
|
|
36279
36470
|
"stop_job",
|
|
36280
36471
|
"Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. The type=log pane updates to status=failed with the last delta. Then decide the next step.",
|
|
36281
36472
|
{
|
|
36282
|
-
id:
|
|
36283
|
-
reason:
|
|
36473
|
+
id: import_zod10.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
36474
|
+
reason: import_zod10.z.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the answer)")
|
|
36284
36475
|
},
|
|
36285
36476
|
async ({ id, reason }) => {
|
|
36286
36477
|
const result = await stopDetachedJob(process.cwd(), id, { reason });
|
|
@@ -36292,7 +36483,7 @@ async function startMcpServer() {
|
|
|
36292
36483
|
"list_run_scripts",
|
|
36293
36484
|
"List .sideboard/.conductor run scripts for this thread (same menu as the desktop Dev button) and which are active. Each script and active run includes preview: url (open http://localhost:<SIDEBOARD_PORT>) or window (Electron: native window is the app; url is renderer/HMR only). Never guess :3000. Worktree turns may omit ref (uses cwd).",
|
|
36294
36485
|
{
|
|
36295
|
-
ref:
|
|
36486
|
+
ref: import_zod10.z.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd).")
|
|
36296
36487
|
},
|
|
36297
36488
|
async ({ ref }) => {
|
|
36298
36489
|
try {
|
|
@@ -36322,8 +36513,8 @@ async function startMcpServer() {
|
|
|
36322
36513
|
"run_dev_script",
|
|
36323
36514
|
"Start a .sideboard/.conductor run script (same as the desktop Dev / play button). Several named scripts can run at once; each gets its own port, and starting one does not stop the others. Logs and Stop appear in the thread UI \u2014 do not detached-job or shell-spawn the same command. Returns {port, ports, url, preview}. preview=url \u2192 open url (browser, curl, Playwright). preview=window \u2192 Electron: the native window is the app; url is renderer/HMR only (no preload/IPC in a browser). The port is allocated SIDEBOARD_PORT, not 3000. Worktree turns may omit ref (uses cwd). Omit name for the default script.",
|
|
36324
36515
|
{
|
|
36325
|
-
ref:
|
|
36326
|
-
name:
|
|
36516
|
+
ref: import_zod10.z.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd)."),
|
|
36517
|
+
name: import_zod10.z.string().optional().describe("Script name from list_run_scripts (default script if omitted).")
|
|
36327
36518
|
},
|
|
36328
36519
|
async ({ ref, name }) => {
|
|
36329
36520
|
try {
|
|
@@ -36351,8 +36542,8 @@ async function startMcpServer() {
|
|
|
36351
36542
|
"stop_dev_script",
|
|
36352
36543
|
"Stop a run script started with run_dev_script or the desktop Dev button (same as UI Stop). Omit name to stop all scripts on this worktree. Worktree turns may omit ref (uses cwd).",
|
|
36353
36544
|
{
|
|
36354
|
-
ref:
|
|
36355
|
-
name:
|
|
36545
|
+
ref: import_zod10.z.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd)."),
|
|
36546
|
+
name: import_zod10.z.string().optional().describe("Script name to stop; omit to stop all active scripts.")
|
|
36356
36547
|
},
|
|
36357
36548
|
async ({ ref, name }) => {
|
|
36358
36549
|
try {
|
|
@@ -36369,9 +36560,9 @@ async function startMcpServer() {
|
|
|
36369
36560
|
"get_run_log",
|
|
36370
36561
|
"Read the Run-tab terminal for a worktree script (same stdout/stderr as the desktop Dev pane). Default is a tail \u2014 raise `tail` only if the error is missing. Worktree turns may omit ref (uses cwd). Omit name for the default script.",
|
|
36371
36562
|
{
|
|
36372
|
-
ref:
|
|
36373
|
-
name:
|
|
36374
|
-
tail:
|
|
36563
|
+
ref: import_zod10.z.string().optional().describe("Thread id. Omit on a worktree turn (uses cwd)."),
|
|
36564
|
+
name: import_zod10.z.string().optional().describe("Script name from list_run_scripts (default script if omitted)."),
|
|
36565
|
+
tail: import_zod10.z.number().int().min(1).max(MAX_RUN_LOG_CHARS).optional().describe(
|
|
36375
36566
|
`Max characters to return from the end (default ${DEFAULT_RUN_LOG_TAIL_CHARS}). Do not request the full buffer unless the tail is truncated and you still need more.`
|
|
36376
36567
|
)
|
|
36377
36568
|
},
|
|
@@ -36409,8 +36600,8 @@ async function startMcpServer() {
|
|
|
36409
36600
|
"notify_orchestrator",
|
|
36410
36601
|
"Wake the parent Global orchestrator with a short status so it does not have to poll wait_for_turn. Use when you are blocked and the parent may have moved on (missing access, waiting on something the coordinator must handle). The message is shown on the sidebar and board until the next user message in this chat. ask_user already notifies for input-required \u2014 do not call this for the same question. Information only: never a git command. Do not use this to steer other worktrees.",
|
|
36411
36602
|
{
|
|
36412
|
-
message:
|
|
36413
|
-
reason:
|
|
36603
|
+
message: import_zod10.z.string().min(1).max(1500).describe("Why you are blocked / what the parent should know (short)."),
|
|
36604
|
+
reason: import_zod10.z.enum(["input-required", "blocked"]).optional().describe(
|
|
36414
36605
|
"input-required = waiting on the user in this chat; blocked = need the coordinator (default)."
|
|
36415
36606
|
)
|
|
36416
36607
|
},
|
|
@@ -36447,11 +36638,11 @@ async function startMcpServer() {
|
|
|
36447
36638
|
"set_workspace_tags",
|
|
36448
36639
|
worktreeProfile ? "Update tags on this workspace (the checkout you are in). Omit ref to use this chat. mode=replace (default), add, or remove. Short labels \u2014 the same ones the orchestrator used for this task. You cannot retag a different checkout." : "Label a project workspace so related checkouts stay recognizable in the sidebar and on the phone. Pass any chat id on that workspace. Use the same tags on every workspace from one task. mode=replace is the default; add and remove change the set. create_workspace tags= sets them at create time. Not for orchestration chats.",
|
|
36449
36640
|
{
|
|
36450
|
-
ref:
|
|
36641
|
+
ref: import_zod10.z.string().optional().describe(
|
|
36451
36642
|
worktreeProfile ? "Omit for this chat. Another chat id only if it is on this same checkout." : "Chat id on the workspace to label."
|
|
36452
36643
|
),
|
|
36453
|
-
tags:
|
|
36454
|
-
mode:
|
|
36644
|
+
tags: import_zod10.z.union([import_zod10.z.string(), import_zod10.z.array(import_zod10.z.string())]).describe("Labels to set. A string may be comma-separated."),
|
|
36645
|
+
mode: import_zod10.z.enum(["replace", "add", "remove"]).optional()
|
|
36455
36646
|
},
|
|
36456
36647
|
async ({ ref, tags, mode }) => {
|
|
36457
36648
|
try {
|
|
@@ -36485,7 +36676,7 @@ async function startMcpServer() {
|
|
|
36485
36676
|
"set_caffeinate",
|
|
36486
36677
|
"Keep this Mac awake with caffeinate (like Claude Code) across turns \u2014 independent of Settings toggles. Turn ON when the user will be away from the keyboard, is driving work from Slack, or asks you to keep the machine awake. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the Mac awake. Closing or archiving this orchestration chat also releases the hold. macOS only.",
|
|
36487
36678
|
{
|
|
36488
|
-
enabled:
|
|
36679
|
+
enabled: import_zod10.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
36489
36680
|
},
|
|
36490
36681
|
async ({ enabled }) => {
|
|
36491
36682
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
@@ -36511,24 +36702,24 @@ async function startMcpServer() {
|
|
|
36511
36702
|
"create_workspace",
|
|
36512
36703
|
`Create a workspace (isolated git checkout + first chat) from branch, pr, or ticket. A ticket, PR, or named branch has one checkout \u2014 if one already matches, returns it (alreadyStarted=true) instead of a second checkout. That is not one agent: chats lists every agent on that checkout. Add another with fork_chat; send_to_chat only the chat that should do this job. Creating from the default branch still opens a new isolated checkout. Pass repoPath from list_projects. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentChatId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Omit agent/model \u2014 Sideboard applies ${accountDefaultsHint}. Do not pass your own agent or agent=cursor; those are ignored. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy). Pass the same tags on every workspace you open for one task (short labels such as "phone-sync") so they still match in the sidebar the next day. Reusing a checkout adds those tags.`,
|
|
36513
36704
|
{
|
|
36514
|
-
sourceType:
|
|
36515
|
-
sourceRef:
|
|
36516
|
-
agent:
|
|
36705
|
+
sourceType: import_zod10.z.enum(["branch", "pr", "ticket"]),
|
|
36706
|
+
sourceRef: import_zod10.z.string(),
|
|
36707
|
+
agent: import_zod10.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(
|
|
36517
36708
|
`Usually omit. Sideboard uses Account default agent (${accountDefaults.agent}). Passing your own agent or cursor is ignored.`
|
|
36518
36709
|
),
|
|
36519
|
-
model:
|
|
36710
|
+
model: import_zod10.z.string().nullable().optional().describe(
|
|
36520
36711
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
36521
36712
|
),
|
|
36522
|
-
repoPath:
|
|
36523
|
-
title:
|
|
36524
|
-
cowboy:
|
|
36713
|
+
repoPath: import_zod10.z.string(),
|
|
36714
|
+
title: import_zod10.z.string().optional(),
|
|
36715
|
+
cowboy: import_zod10.z.boolean().optional().describe(
|
|
36525
36716
|
"If true, work in the project folder on the default branch (no thread/* worktree). Requires Settings \u2192 Advanced \u2192 Cowboy mode. Land is commit+push to that branch. Archive does not delete the folder."
|
|
36526
36717
|
),
|
|
36527
|
-
parentChatId:
|
|
36718
|
+
parentChatId: import_zod10.z.string().optional().describe(
|
|
36528
36719
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
36529
36720
|
),
|
|
36530
|
-
parentThreadId:
|
|
36531
|
-
tags:
|
|
36721
|
+
parentThreadId: import_zod10.z.string().optional().describe("Legacy alias for parentChatId."),
|
|
36722
|
+
tags: import_zod10.z.union([import_zod10.z.string(), import_zod10.z.array(import_zod10.z.string())]).optional().describe(
|
|
36532
36723
|
"Short labels for this workspace (or a comma-separated string). Use the same tags on every checkout for one task. Added when the checkout already exists."
|
|
36533
36724
|
)
|
|
36534
36725
|
},
|
|
@@ -36544,11 +36735,11 @@ async function startMcpServer() {
|
|
|
36544
36735
|
"start_board_card",
|
|
36545
36736
|
"Same as create_workspace for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Does not create a second checkout when one already matches \u2014 returns that workspace (alreadyStarted) and its chats. A workspace can have many agents. fork_chat to add one; send_to_chat only the chat that should do this job.",
|
|
36546
36737
|
{
|
|
36547
|
-
kind:
|
|
36548
|
-
ref:
|
|
36549
|
-
repoPath:
|
|
36550
|
-
title:
|
|
36551
|
-
tags:
|
|
36738
|
+
kind: import_zod10.z.enum(["ticket", "pr", "branch"]),
|
|
36739
|
+
ref: import_zod10.z.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
36740
|
+
repoPath: import_zod10.z.string().describe("Project path from list_projects / list_board"),
|
|
36741
|
+
title: import_zod10.z.string().optional(),
|
|
36742
|
+
tags: import_zod10.z.union([import_zod10.z.string(), import_zod10.z.array(import_zod10.z.string())]).optional().describe(
|
|
36552
36743
|
"Short labels for this workspace. Same tags on every checkout for one task. Added when the checkout already exists."
|
|
36553
36744
|
)
|
|
36554
36745
|
},
|
|
@@ -36653,9 +36844,9 @@ async function startMcpServer() {
|
|
|
36653
36844
|
"send_to_chat",
|
|
36654
36845
|
'Steer a prompt on a workspace chat (Settings \u2192 Follow-up, default steer: interrupt the in-flight turn and start now, same as the workspace composer). Use after create_workspace to start or continue a conversation. For commit/push/PR, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. force_stop=true kills the in-flight turn and clears the queue before this prompt \u2014 only when the current request is wrong and must be replaced. Do not send_to_chat to check in, resume after a halt notice, or because wait_for_turn returned stillRunning; that interrupts the child mid-thought. Call wait_for_turn again instead.',
|
|
36655
36846
|
{
|
|
36656
|
-
ref:
|
|
36657
|
-
prompt:
|
|
36658
|
-
force_stop:
|
|
36847
|
+
ref: import_zod10.z.string(),
|
|
36848
|
+
prompt: import_zod10.z.string(),
|
|
36849
|
+
force_stop: import_zod10.z.boolean().optional()
|
|
36659
36850
|
},
|
|
36660
36851
|
async ({ ref, prompt, force_stop }) => {
|
|
36661
36852
|
if (force_stop) {
|
|
@@ -36680,8 +36871,8 @@ async function startMcpServer() {
|
|
|
36680
36871
|
"wait_for_turn",
|
|
36681
36872
|
"Wait until the chat finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. taskState is the A2A-style lifecycle: submitted (queued, not started), working, input-required (ask_user), completed, failed, canceled. stillRunning is true only for submitted/working. blockedReason is why the child is waiting on a person (the question, plan approval, or a reported block) \u2014 read it and do not send_to_chat a check-in. If stillRunning, text and usage are empty (they would be the previous turn) \u2014 read progress and call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts). On failed, lastError/text is the failure. On canceled, the child did not finish \u2014 resume with send_to_chat or tell the user. On input-required, wait for the user in that chat. When finished, text is this turn\u2019s assistant reply and usage is that turn\u2019s tokens + costUsd (this turn, not the Claude session total; sessionCostUsd is the provider session total when present).",
|
|
36682
36873
|
{
|
|
36683
|
-
ref:
|
|
36684
|
-
timeoutMs:
|
|
36874
|
+
ref: import_zod10.z.string(),
|
|
36875
|
+
timeoutMs: import_zod10.z.number().optional()
|
|
36685
36876
|
},
|
|
36686
36877
|
async ({ ref, timeoutMs }) => {
|
|
36687
36878
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
@@ -36709,7 +36900,7 @@ async function startMcpServer() {
|
|
|
36709
36900
|
server.tool(
|
|
36710
36901
|
"get_turn_result",
|
|
36711
36902
|
"Assistant message and last-turn usage when the turn finished. While stillRunning (submitted/working), text and usage are empty so a previous reply cannot be mistaken for this turn \u2014 use progress for tools/thinking. Not the full transcript. Includes taskState (A2A lifecycle) and blockedReason (why the agent is waiting on a person).",
|
|
36712
|
-
{ ref:
|
|
36903
|
+
{ ref: import_zod10.z.string() },
|
|
36713
36904
|
async ({ ref }) => {
|
|
36714
36905
|
const result = orch.getTurnResult(ref);
|
|
36715
36906
|
const hint = mcpWaitTaskHint(result.taskState, result.status, result.blockedReason);
|
|
@@ -36725,9 +36916,9 @@ async function startMcpServer() {
|
|
|
36725
36916
|
"stop_chat",
|
|
36726
36917
|
"Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true. Pass reason \u2014 a short note on why this chat is stopping. It is stored on the thread and kept when the turn unwinds. If you omit it, Sideboard still records that the queue was cleared.",
|
|
36727
36918
|
{
|
|
36728
|
-
ref:
|
|
36729
|
-
force:
|
|
36730
|
-
reason:
|
|
36919
|
+
ref: import_zod10.z.string(),
|
|
36920
|
+
force: import_zod10.z.boolean().optional(),
|
|
36921
|
+
reason: import_zod10.z.string().optional().describe("Why this chat is stopping. Stored on the thread for the next session.")
|
|
36731
36922
|
},
|
|
36732
36923
|
async ({ ref, force, reason }) => {
|
|
36733
36924
|
const t = orch.getThread(ref);
|
|
@@ -36751,7 +36942,7 @@ async function startMcpServer() {
|
|
|
36751
36942
|
server.tool(
|
|
36752
36943
|
"archive_chat",
|
|
36753
36944
|
"Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
|
|
36754
|
-
{ ref:
|
|
36945
|
+
{ ref: import_zod10.z.string() },
|
|
36755
36946
|
async ({ ref }) => {
|
|
36756
36947
|
const t = orch.getThread(ref);
|
|
36757
36948
|
if (!t) {
|
|
@@ -36777,7 +36968,7 @@ async function startMcpServer() {
|
|
|
36777
36968
|
server.tool(
|
|
36778
36969
|
"restore_chat",
|
|
36779
36970
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
36780
|
-
{ ref:
|
|
36971
|
+
{ ref: import_zod10.z.string() },
|
|
36781
36972
|
async ({ ref }) => {
|
|
36782
36973
|
try {
|
|
36783
36974
|
const restored = await orch.restore(ref);
|
|
@@ -36796,8 +36987,8 @@ async function startMcpServer() {
|
|
|
36796
36987
|
"get_diff",
|
|
36797
36988
|
"Compact diff summary (capped hunks, paginated)",
|
|
36798
36989
|
{
|
|
36799
|
-
ref:
|
|
36800
|
-
maxFiles:
|
|
36990
|
+
ref: import_zod10.z.string(),
|
|
36991
|
+
maxFiles: import_zod10.z.number().optional()
|
|
36801
36992
|
},
|
|
36802
36993
|
async ({ ref, maxFiles }) => {
|
|
36803
36994
|
const summary = await orch.diffSummary(ref);
|
|
@@ -36810,7 +37001,7 @@ async function startMcpServer() {
|
|
|
36810
37001
|
server.tool(
|
|
36811
37002
|
"get_pr_checks",
|
|
36812
37003
|
"Snapshot of GitHub PR checks for a worktree thread (`gh pr checks` plus merge/review gates). null = no PR. If the user gave a goal (Greptile 5/5, CI green), the worktree agent watches with `gh pr checks --watch` via /long-running \u2014 this tool is a snapshot, not a waiter. Coordinators: do not run gh from the orchestration cwd.",
|
|
36813
|
-
{ ref:
|
|
37004
|
+
{ ref: import_zod10.z.string().describe("Worktree thread id/ref") },
|
|
36814
37005
|
async ({ ref }) => {
|
|
36815
37006
|
try {
|
|
36816
37007
|
const checks = await orch.getPrChecks(ref);
|
|
@@ -36824,7 +37015,7 @@ async function startMcpServer() {
|
|
|
36824
37015
|
server.tool(
|
|
36825
37016
|
"request_review",
|
|
36826
37017
|
'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). If that worktree has a single unused idle agent (no messages), runs the review there; otherwise opens a new Review chat tab. Attaches .claude/skills/review/SKILL.md when present (else copies .sideboard/review.md into .context/review.md, or seeds that file from the stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information in that chat. The review stays there so the user can read it and type next steps \u2014 do not comment on or update the PR or ticket, and do not ask_user after the review, until they ask. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn (loop while stillRunning) / get_turn_result on the returned review thread id.',
|
|
36827
|
-
{ ref:
|
|
37018
|
+
{ ref: import_zod10.z.string().describe("Worktree thread id/ref to review") },
|
|
36828
37019
|
async ({ ref }) => {
|
|
36829
37020
|
try {
|
|
36830
37021
|
const tab = await orch.requestReview(ref);
|
|
@@ -36846,8 +37037,8 @@ async function startMcpServer() {
|
|
|
36846
37037
|
"ask_git",
|
|
36847
37038
|
"Commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge \u2014 same prompts as the desktop git buttons (including Resolve). Always steers the worktree agent; does not push or open the PR locally. Then wait_for_turn (loop while stillRunning). Do not start a checks loop on a plain push \u2014 only if the user gave a goal (Greptile 5/5, CI green). Pass a worktree thread ref (not the orchestrator). action=ready-for-review and action=merge only when the user explicitly asked (or clicked the desktop button). Do not run git or gh from the orchestration cwd.",
|
|
36848
37039
|
{
|
|
36849
|
-
ref:
|
|
36850
|
-
action:
|
|
37040
|
+
ref: import_zod10.z.string().describe("Worktree thread id/ref"),
|
|
37041
|
+
action: import_zod10.z.enum(AGENT_GIT_ACTIONS).describe(
|
|
36851
37042
|
"commit-push | create-draft | create-web | resolve-conflicts | ready-for-review | merge"
|
|
36852
37043
|
)
|
|
36853
37044
|
},
|
|
@@ -36876,7 +37067,7 @@ async function startMcpServer() {
|
|
|
36876
37067
|
}
|
|
36877
37068
|
}
|
|
36878
37069
|
);
|
|
36879
|
-
const agentEnum =
|
|
37070
|
+
const agentEnum = import_zod10.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
36880
37071
|
server.tool(
|
|
36881
37072
|
"list_models",
|
|
36882
37073
|
"List models for an agent. Prefer Auto: do not call this unless you have a reason to pin a specific model (user request, cost/latency, capability). Omit agent to list all.",
|
|
@@ -36897,11 +37088,11 @@ async function startMcpServer() {
|
|
|
36897
37088
|
"fork_workspace",
|
|
36898
37089
|
"Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_chat / wait_for_turn (loop while stillRunning) on the returned id.",
|
|
36899
37090
|
{
|
|
36900
|
-
ref:
|
|
36901
|
-
through_index:
|
|
37091
|
+
ref: import_zod10.z.string().describe("Worktree thread id/ref to fork"),
|
|
37092
|
+
through_index: import_zod10.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
36902
37093
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
36903
|
-
model:
|
|
36904
|
-
title:
|
|
37094
|
+
model: import_zod10.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
37095
|
+
title: import_zod10.z.string().optional()
|
|
36905
37096
|
},
|
|
36906
37097
|
async ({ ref, through_index, agent, model, title }) => {
|
|
36907
37098
|
try {
|
|
@@ -36935,11 +37126,11 @@ async function startMcpServer() {
|
|
|
36935
37126
|
"fork_chat",
|
|
36936
37127
|
"Add another agent chat on the SAME workspace (same files, Run, and git). Also forks a Global orchestration chat into a new orchestration tab. Pass agent when the new chat should use a different harness. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Use this for a second agent on a checkout (review, another harness, parallel work on the same branch). Do not create_workspace again for that \u2014 it returns the existing checkout. Then send_to_chat / wait_for_turn (loop while stillRunning) on the returned id. Use fork_workspace only when the work needs its own git checkout.",
|
|
36937
37128
|
{
|
|
36938
|
-
ref:
|
|
36939
|
-
through_index:
|
|
37129
|
+
ref: import_zod10.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
37130
|
+
through_index: import_zod10.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
36940
37131
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
36941
|
-
model:
|
|
36942
|
-
title:
|
|
37132
|
+
model: import_zod10.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
37133
|
+
title: import_zod10.z.string().optional()
|
|
36943
37134
|
},
|
|
36944
37135
|
async ({ ref, through_index, agent, model, title }) => {
|
|
36945
37136
|
try {
|
|
@@ -36977,7 +37168,7 @@ async function startMcpServer() {
|
|
|
36977
37168
|
server.tool(
|
|
36978
37169
|
"run_setup",
|
|
36979
37170
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
36980
|
-
{ ref:
|
|
37171
|
+
{ ref: import_zod10.z.string() },
|
|
36981
37172
|
async ({ ref }) => {
|
|
36982
37173
|
const result = await orch.runSetup(ref);
|
|
36983
37174
|
return mcpJson(result);
|
|
@@ -36986,7 +37177,7 @@ async function startMcpServer() {
|
|
|
36986
37177
|
server.tool(
|
|
36987
37178
|
"add_project",
|
|
36988
37179
|
"Register a git repo as a Sideboard workspace",
|
|
36989
|
-
{ repoPath:
|
|
37180
|
+
{ repoPath: import_zod10.z.string() },
|
|
36990
37181
|
async ({ repoPath }) => {
|
|
36991
37182
|
const ws = await orch.addWorkspace(repoPath);
|
|
36992
37183
|
return mcpJson(ws);
|
|
@@ -36995,7 +37186,7 @@ async function startMcpServer() {
|
|
|
36995
37186
|
server.tool(
|
|
36996
37187
|
"remove_project",
|
|
36997
37188
|
"Unregister a Sideboard workspace and archive its chats",
|
|
36998
|
-
{ repoPath:
|
|
37189
|
+
{ repoPath: import_zod10.z.string() },
|
|
36999
37190
|
async ({ repoPath }) => {
|
|
37000
37191
|
await orch.removeWorkspace(repoPath);
|
|
37001
37192
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -37005,12 +37196,12 @@ async function startMcpServer() {
|
|
|
37005
37196
|
"fanout",
|
|
37006
37197
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
37007
37198
|
{
|
|
37008
|
-
prompt:
|
|
37009
|
-
agents:
|
|
37010
|
-
repoPath:
|
|
37011
|
-
sourceType:
|
|
37012
|
-
sourceRef:
|
|
37013
|
-
title:
|
|
37199
|
+
prompt: import_zod10.z.string(),
|
|
37200
|
+
agents: import_zod10.z.array(import_zod10.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
37201
|
+
repoPath: import_zod10.z.string(),
|
|
37202
|
+
sourceType: import_zod10.z.enum(["branch", "pr", "ticket"]).optional(),
|
|
37203
|
+
sourceRef: import_zod10.z.string().optional(),
|
|
37204
|
+
title: import_zod10.z.string().optional()
|
|
37014
37205
|
},
|
|
37015
37206
|
async (args) => {
|
|
37016
37207
|
const threads = await orch.bestOfN(args);
|
|
@@ -37028,8 +37219,8 @@ async function startMcpServer() {
|
|
|
37028
37219
|
"list_branches",
|
|
37029
37220
|
"List git branches in a registered workspace. Pass repoPath from list_projects (unmerged into the default branch by default \u2014 for create_workspace sourceType=branch).",
|
|
37030
37221
|
{
|
|
37031
|
-
repoPath:
|
|
37032
|
-
unmergedOnly:
|
|
37222
|
+
repoPath: import_zod10.z.string(),
|
|
37223
|
+
unmergedOnly: import_zod10.z.boolean().optional()
|
|
37033
37224
|
},
|
|
37034
37225
|
async ({ repoPath, unmergedOnly }) => {
|
|
37035
37226
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -37050,19 +37241,19 @@ async function startMcpServer() {
|
|
|
37050
37241
|
"list_prs",
|
|
37051
37242
|
`List GitHub PRs for a registered workspace (the review surface for assigned ticket work \u2014 not the tickets). Pass repoPath from list_projects. "Get me N tickets to review" \u2192 queue=review and limit=N: open non-draft PRs labeled with this project's ready-for-review GitHub label (Settings \u2192 Projects; default eng-review) with no individual user reviewer. Prefer teams that match Settings \u2192 Agents / Projects roles for this repo. A team like engineering-team is not a claim (you are on that team). Also: state (open|closed|merged|all|review), label, reviewer (me|unassigned|login), query, limit (default 40, max 250). Then create_workspace with sourceType=pr.`,
|
|
37052
37243
|
{
|
|
37053
|
-
repoPath:
|
|
37054
|
-
query:
|
|
37055
|
-
queue:
|
|
37244
|
+
repoPath: import_zod10.z.string(),
|
|
37245
|
+
query: import_zod10.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
|
|
37246
|
+
queue: import_zod10.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
|
|
37056
37247
|
"review = unclaimed ready-for-review inbox (Settings \u2192 Projects label, default eng-review). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes."
|
|
37057
37248
|
),
|
|
37058
|
-
state:
|
|
37059
|
-
label:
|
|
37249
|
+
state: import_zod10.z.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
|
|
37250
|
+
label: import_zod10.z.string().optional().describe(
|
|
37060
37251
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
37061
37252
|
),
|
|
37062
|
-
reviewer:
|
|
37253
|
+
reviewer: import_zod10.z.string().optional().describe(
|
|
37063
37254
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
37064
37255
|
),
|
|
37065
|
-
limit:
|
|
37256
|
+
limit: import_zod10.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
37066
37257
|
},
|
|
37067
37258
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
37068
37259
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -37095,7 +37286,7 @@ async function startMcpServer() {
|
|
|
37095
37286
|
server.tool(
|
|
37096
37287
|
"get_pr_stack",
|
|
37097
37288
|
"Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs (and only merge when the user explicitly asked).",
|
|
37098
|
-
{ ref:
|
|
37289
|
+
{ ref: import_zod10.z.string() },
|
|
37099
37290
|
async ({ ref }) => {
|
|
37100
37291
|
const stack = await orch.getPrStack(ref);
|
|
37101
37292
|
return mcpJson(stack);
|
|
@@ -37105,8 +37296,8 @@ async function startMcpServer() {
|
|
|
37105
37296
|
"open_pr_stack_layers",
|
|
37106
37297
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
37107
37298
|
{
|
|
37108
|
-
ref:
|
|
37109
|
-
layer:
|
|
37299
|
+
ref: import_zod10.z.string(),
|
|
37300
|
+
layer: import_zod10.z.number().int().positive().optional()
|
|
37110
37301
|
},
|
|
37111
37302
|
async ({ ref, layer }) => {
|
|
37112
37303
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -37129,9 +37320,9 @@ async function startMcpServer() {
|
|
|
37129
37320
|
"add_stack_layer",
|
|
37130
37321
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
37131
37322
|
{
|
|
37132
|
-
ref:
|
|
37133
|
-
branchName:
|
|
37134
|
-
title:
|
|
37323
|
+
ref: import_zod10.z.string(),
|
|
37324
|
+
branchName: import_zod10.z.string(),
|
|
37325
|
+
title: import_zod10.z.string().optional()
|
|
37135
37326
|
},
|
|
37136
37327
|
async ({ ref, branchName, title }) => {
|
|
37137
37328
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -37149,11 +37340,11 @@ async function startMcpServer() {
|
|
|
37149
37340
|
"create_pr_stack",
|
|
37150
37341
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
37151
37342
|
{
|
|
37152
|
-
repoPath:
|
|
37153
|
-
branches:
|
|
37154
|
-
agent:
|
|
37155
|
-
base:
|
|
37156
|
-
title:
|
|
37343
|
+
repoPath: import_zod10.z.string(),
|
|
37344
|
+
branches: import_zod10.z.array(import_zod10.z.string()).min(1),
|
|
37345
|
+
agent: import_zod10.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
37346
|
+
base: import_zod10.z.string().optional(),
|
|
37347
|
+
title: import_zod10.z.string().optional()
|
|
37157
37348
|
},
|
|
37158
37349
|
async (args) => {
|
|
37159
37350
|
const result = await orch.createPrStack({
|
|
@@ -37181,10 +37372,10 @@ async function startMcpServer() {
|
|
|
37181
37372
|
"list_issues",
|
|
37182
37373
|
"List or search issues (Linear, AbleTime, or GitHub; falls back to GitHub). Use Settings \u2192 Agents / Projects notes and roles to pick tickets relevant to the viewer (query + assignee=me or unassigned as the notes say). Default 40; pass query and/or limit (max 250) when truncated. assignee: me (Linear default), unassigned, all, or a user id. Pass updatedSince for new/updated tickets and new comments (\u201Cany updates since yesterday?\u201D). Then create_workspace with sourceType=ticket.",
|
|
37183
37374
|
{
|
|
37184
|
-
repoPath:
|
|
37185
|
-
query:
|
|
37186
|
-
assignee:
|
|
37187
|
-
limit:
|
|
37375
|
+
repoPath: import_zod10.z.string(),
|
|
37376
|
+
query: import_zod10.z.string().optional().describe("Search title, identifier, or description"),
|
|
37377
|
+
assignee: import_zod10.z.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
|
|
37378
|
+
limit: import_zod10.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true."),
|
|
37188
37379
|
updatedSince: mcpIssueUpdatedSinceSchema
|
|
37189
37380
|
},
|
|
37190
37381
|
async ({ repoPath, query, assignee, limit, updatedSince }) => {
|
|
@@ -39692,14 +39883,46 @@ var RemoteHub = class {
|
|
|
39692
39883
|
this.codes.delete(code);
|
|
39693
39884
|
this.attachPhone(socket, host);
|
|
39694
39885
|
}
|
|
39886
|
+
/** In-memory session, with the live Mac's label when that socket is up. */
|
|
39887
|
+
memorySession(sessionToken) {
|
|
39888
|
+
const cached2 = this.phonesByToken.get(sessionToken);
|
|
39889
|
+
if (!cached2) return null;
|
|
39890
|
+
const live = this.hosts.get(cached2.deviceId);
|
|
39891
|
+
const remembered = this.remembered.get(cached2.deviceId);
|
|
39892
|
+
const current = live?.sessionToken ?? remembered?.sessionToken ?? null;
|
|
39893
|
+
if (current && current !== sessionToken) {
|
|
39894
|
+
this.phonesByToken.delete(sessionToken);
|
|
39895
|
+
return null;
|
|
39896
|
+
}
|
|
39897
|
+
return {
|
|
39898
|
+
deviceId: cached2.deviceId,
|
|
39899
|
+
deviceLabel: live?.deviceLabel || remembered?.deviceLabel || cached2.deviceLabel
|
|
39900
|
+
};
|
|
39901
|
+
}
|
|
39902
|
+
knownSession(sessionToken) {
|
|
39903
|
+
const cached2 = this.memorySession(sessionToken);
|
|
39904
|
+
if (cached2) return cached2;
|
|
39905
|
+
const stored = this.opts.phoneSessions?.find(sessionToken) ?? null;
|
|
39906
|
+
if (!stored) return null;
|
|
39907
|
+
const live = this.hosts.get(stored.deviceId);
|
|
39908
|
+
const remembered = this.remembered.get(stored.deviceId);
|
|
39909
|
+
const current = live?.sessionToken ?? remembered?.sessionToken ?? null;
|
|
39910
|
+
if (current && current !== sessionToken) return null;
|
|
39911
|
+
this.phonesByToken.set(sessionToken, stored);
|
|
39912
|
+
if (remembered && !remembered.sessionToken) remembered.sessionToken = sessionToken;
|
|
39913
|
+
if (live && !live.sessionToken) live.sessionToken = sessionToken;
|
|
39914
|
+
return {
|
|
39915
|
+
deviceId: stored.deviceId,
|
|
39916
|
+
deviceLabel: live?.deviceLabel || remembered?.deviceLabel || stored.deviceLabel
|
|
39917
|
+
};
|
|
39918
|
+
}
|
|
39695
39919
|
resumePhone(socket, sessionToken) {
|
|
39696
|
-
const
|
|
39697
|
-
|
|
39698
|
-
if (!deviceId || !remembered || remembered.sessionToken !== sessionToken) {
|
|
39920
|
+
const known = this.knownSession(sessionToken);
|
|
39921
|
+
if (!known) {
|
|
39699
39922
|
send2(socket, { type: "error", message: "phone session expired \u2014 pair again" });
|
|
39700
39923
|
return;
|
|
39701
39924
|
}
|
|
39702
|
-
const host = this.hosts.get(deviceId);
|
|
39925
|
+
const host = this.hosts.get(known.deviceId);
|
|
39703
39926
|
if (!host) {
|
|
39704
39927
|
send2(socket, { type: "host_offline" });
|
|
39705
39928
|
return;
|
|
@@ -39708,17 +39931,13 @@ var RemoteHub = class {
|
|
|
39708
39931
|
}
|
|
39709
39932
|
listHosts(socket, sessionTokens) {
|
|
39710
39933
|
const hosts = sessionTokens.map((sessionToken) => {
|
|
39711
|
-
const
|
|
39712
|
-
|
|
39713
|
-
const
|
|
39714
|
-
if (!valid || !deviceId || !remembered) {
|
|
39715
|
-
return { sessionToken, deviceId: "", deviceLabel: "", online: false };
|
|
39716
|
-
}
|
|
39717
|
-
const host = this.hosts.get(deviceId);
|
|
39934
|
+
const known = this.knownSession(sessionToken);
|
|
39935
|
+
if (!known) return { sessionToken, deviceId: "", deviceLabel: "", online: false };
|
|
39936
|
+
const host = this.hosts.get(known.deviceId);
|
|
39718
39937
|
return {
|
|
39719
39938
|
sessionToken,
|
|
39720
|
-
deviceId,
|
|
39721
|
-
deviceLabel: host?.deviceLabel ||
|
|
39939
|
+
deviceId: known.deviceId,
|
|
39940
|
+
deviceLabel: host?.deviceLabel || known.deviceLabel,
|
|
39722
39941
|
online: Boolean(host)
|
|
39723
39942
|
};
|
|
39724
39943
|
});
|
|
@@ -39749,18 +39968,20 @@ var RemoteHub = class {
|
|
|
39749
39968
|
}
|
|
39750
39969
|
host.phone = null;
|
|
39751
39970
|
}
|
|
39971
|
+
const created = sessionToken == null;
|
|
39752
39972
|
const token = sessionToken ?? (0, import_node_crypto25.randomBytes)(24).toString("hex");
|
|
39753
39973
|
if (host.sessionToken && host.sessionToken !== token) {
|
|
39754
39974
|
this.phonesByToken.delete(host.sessionToken);
|
|
39755
39975
|
}
|
|
39756
39976
|
host.sessionToken = token;
|
|
39757
39977
|
host.phone = socket;
|
|
39758
|
-
this.phonesByToken.set(token, host.deviceId);
|
|
39978
|
+
this.phonesByToken.set(token, { deviceId: host.deviceId, deviceLabel: host.deviceLabel });
|
|
39759
39979
|
this.remembered.set(host.deviceId, {
|
|
39760
39980
|
hostSecret: host.hostSecret,
|
|
39761
39981
|
sessionToken: token,
|
|
39762
39982
|
deviceLabel: host.deviceLabel
|
|
39763
39983
|
});
|
|
39984
|
+
if (created) this.opts.phoneSessions?.save(host.deviceId, token);
|
|
39764
39985
|
this.bySocket.set(socket, { role: "phone", deviceId: host.deviceId });
|
|
39765
39986
|
this.opts.onAuthorized?.(socket);
|
|
39766
39987
|
send2(socket, {
|
|
@@ -40024,6 +40245,29 @@ function phoneAccountDefaults() {
|
|
|
40024
40245
|
};
|
|
40025
40246
|
}
|
|
40026
40247
|
|
|
40248
|
+
// src/remote/phone-viewer-login.ts
|
|
40249
|
+
var TTL_MS = 5 * 6e4;
|
|
40250
|
+
var cached = "";
|
|
40251
|
+
var fetchedAt = 0;
|
|
40252
|
+
var inflight3 = null;
|
|
40253
|
+
function phoneViewerLogin() {
|
|
40254
|
+
return cached;
|
|
40255
|
+
}
|
|
40256
|
+
function refreshPhoneViewerLogin(force = false) {
|
|
40257
|
+
const now = Date.now();
|
|
40258
|
+
if (!force && fetchedAt && now - fetchedAt < TTL_MS) return Promise.resolve(cached);
|
|
40259
|
+
if (inflight3) return inflight3;
|
|
40260
|
+
const run2 = getGitHubStatus().then((status) => {
|
|
40261
|
+
cached = status.login?.trim() ?? "";
|
|
40262
|
+
fetchedAt = Date.now();
|
|
40263
|
+
return cached;
|
|
40264
|
+
}).catch(() => cached).finally(() => {
|
|
40265
|
+
if (inflight3 === run2) inflight3 = null;
|
|
40266
|
+
});
|
|
40267
|
+
inflight3 = run2;
|
|
40268
|
+
return run2;
|
|
40269
|
+
}
|
|
40270
|
+
|
|
40027
40271
|
// src/remote/phone-transcript.ts
|
|
40028
40272
|
init_turn_live();
|
|
40029
40273
|
|
|
@@ -40328,7 +40572,8 @@ function isProjectPath(path2) {
|
|
|
40328
40572
|
function listPhoneChats() {
|
|
40329
40573
|
return listGlobalThreads().slice().sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).map(summarize);
|
|
40330
40574
|
}
|
|
40331
|
-
function listPhoneSidebar() {
|
|
40575
|
+
function listPhoneSidebar(viewerLogin = phoneViewerLogin()) {
|
|
40576
|
+
const settings = loadAppSettings();
|
|
40332
40577
|
const threads = listThreads();
|
|
40333
40578
|
const byRepo = /* @__PURE__ */ new Map();
|
|
40334
40579
|
for (const thread of threads) {
|
|
@@ -40346,10 +40591,16 @@ function listPhoneSidebar() {
|
|
|
40346
40591
|
path: path2,
|
|
40347
40592
|
worktrees: groupHomeBoardWorktrees(repoThreads).map((group) => {
|
|
40348
40593
|
const tags = workspaceTagsFromGroup(group);
|
|
40594
|
+
const badge = classifyWorktreeListBadges(
|
|
40595
|
+
group,
|
|
40596
|
+
viewerLogin,
|
|
40597
|
+
resolveProjectReviewLabel(settings, path2)
|
|
40598
|
+
)[0];
|
|
40349
40599
|
return {
|
|
40350
40600
|
label: worktreeDisplayLabelForGroup(group),
|
|
40351
40601
|
chats: group.map(summarize),
|
|
40352
|
-
...tags.length > 0 ? { tags } : {}
|
|
40602
|
+
...tags.length > 0 ? { tags } : {},
|
|
40603
|
+
...badge ? { badge: { id: badge.id, label: badge.label, title: badge.title } } : {}
|
|
40353
40604
|
};
|
|
40354
40605
|
})
|
|
40355
40606
|
}));
|
|
@@ -41262,7 +41513,9 @@ function chatKey(row2) {
|
|
|
41262
41513
|
function phoneBoardKey(sidebar) {
|
|
41263
41514
|
const orchestration = sidebar.orchestration.map(chatKey).join("\n");
|
|
41264
41515
|
const projects = sidebar.projects.map((project) => {
|
|
41265
|
-
const trees = project.worktrees.map(
|
|
41516
|
+
const trees = project.worktrees.map(
|
|
41517
|
+
(tree) => `${tree.label} ${tree.badge?.id ?? ""} ${(tree.tags ?? []).join(",")} ${tree.chats.map(chatKey).join("\n")}`
|
|
41518
|
+
).join("\n");
|
|
41266
41519
|
return `${project.path} ${project.name}
|
|
41267
41520
|
${trees}`;
|
|
41268
41521
|
}).join("\n---\n");
|
|
@@ -41340,10 +41593,23 @@ function startPhoneBoardSync(opts) {
|
|
|
41340
41593
|
const unsubscribe = (opts.subscribe ?? subscribePhoneBoard)(schedule);
|
|
41341
41594
|
const offEvents = (opts.events ?? ((listener) => getOrchestrator().on(listener)))(handle);
|
|
41342
41595
|
push();
|
|
41596
|
+
let loginTimer = null;
|
|
41597
|
+
const refreshViewerLogin = opts.refreshViewerLogin;
|
|
41598
|
+
if (refreshViewerLogin) {
|
|
41599
|
+
const afterLogin = () => {
|
|
41600
|
+
void refreshViewerLogin().then(() => {
|
|
41601
|
+
if (!stopped) push();
|
|
41602
|
+
});
|
|
41603
|
+
};
|
|
41604
|
+
afterLogin();
|
|
41605
|
+
loginTimer = setInterval(afterLogin, opts.viewerLoginIntervalMs ?? 5 * 6e4);
|
|
41606
|
+
}
|
|
41343
41607
|
return () => {
|
|
41344
41608
|
stopped = true;
|
|
41345
41609
|
if (timer) clearTimeout(timer);
|
|
41346
41610
|
timer = null;
|
|
41611
|
+
if (loginTimer) clearInterval(loginTimer);
|
|
41612
|
+
loginTimer = null;
|
|
41347
41613
|
unsubscribe();
|
|
41348
41614
|
offEvents();
|
|
41349
41615
|
};
|
|
@@ -41410,7 +41676,8 @@ function runRemoteHost(opts) {
|
|
|
41410
41676
|
},
|
|
41411
41677
|
onRunningChat: (chatId) => {
|
|
41412
41678
|
deliverPhoneTurn({ onOutbound: emit }, chatId);
|
|
41413
|
-
}
|
|
41679
|
+
},
|
|
41680
|
+
refreshViewerLogin: () => refreshPhoneViewerLogin(true)
|
|
41414
41681
|
});
|
|
41415
41682
|
};
|
|
41416
41683
|
const send3 = (msg) => {
|
|
@@ -41673,9 +41940,15 @@ var RelayAccountStore = class {
|
|
|
41673
41940
|
account_id TEXT NOT NULL,
|
|
41674
41941
|
host_secret_hash TEXT NOT NULL,
|
|
41675
41942
|
device_label TEXT NOT NULL,
|
|
41676
|
-
created_at INTEGER NOT NULL
|
|
41943
|
+
created_at INTEGER NOT NULL,
|
|
41944
|
+
session_token_hash TEXT
|
|
41677
41945
|
);
|
|
41678
41946
|
`);
|
|
41947
|
+
const cols = this.db.prepare("PRAGMA table_info(devices)").all();
|
|
41948
|
+
if (!cols.some((col) => col.name === "session_token_hash")) {
|
|
41949
|
+
this.db.exec("ALTER TABLE devices ADD COLUMN session_token_hash TEXT");
|
|
41950
|
+
}
|
|
41951
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS devices_session ON devices(session_token_hash)");
|
|
41679
41952
|
}
|
|
41680
41953
|
close() {
|
|
41681
41954
|
this.db.close();
|
|
@@ -41804,6 +42077,20 @@ var RelayAccountStore = class {
|
|
|
41804
42077
|
throw err;
|
|
41805
42078
|
}
|
|
41806
42079
|
}
|
|
42080
|
+
/** Store a phone pairing when it is created. A later process looks it up on a memory miss. */
|
|
42081
|
+
savePhoneSession(deviceId, sessionToken) {
|
|
42082
|
+
const id = deviceId.trim();
|
|
42083
|
+
const token = sessionToken.trim();
|
|
42084
|
+
if (!id || !token) return;
|
|
42085
|
+
this.db.prepare("UPDATE devices SET session_token_hash = ? WHERE device_id = ?").run(sha256(token), id);
|
|
42086
|
+
}
|
|
42087
|
+
findPhoneSession(sessionToken) {
|
|
42088
|
+
const token = sessionToken.trim();
|
|
42089
|
+
if (!token) return null;
|
|
42090
|
+
const row2 = this.db.prepare("SELECT device_id, device_label FROM devices WHERE session_token_hash = ?").get(sha256(token));
|
|
42091
|
+
if (!row2) return null;
|
|
42092
|
+
return { deviceId: row2.device_id, deviceLabel: row2.device_label };
|
|
42093
|
+
}
|
|
41807
42094
|
requireAccount(accountId) {
|
|
41808
42095
|
const row2 = this.db.prepare("SELECT id FROM accounts WHERE id = ?").get(accountId);
|
|
41809
42096
|
if (!row2) throw new Error("That Sideboard account is no longer on the relay.");
|
|
@@ -42794,6 +43081,10 @@ async function startRelayServer(opts) {
|
|
|
42794
43081
|
const decision = accountStore.authorizeHost(input);
|
|
42795
43082
|
return decision.ok ? { ok: true } : { ok: false, message: decision.message };
|
|
42796
43083
|
} : void 0,
|
|
43084
|
+
phoneSessions: accountStore ? {
|
|
43085
|
+
find: (sessionToken) => accountStore.findPhoneSession(sessionToken),
|
|
43086
|
+
save: (deviceId, sessionToken) => accountStore.savePhoneSession(deviceId, sessionToken)
|
|
43087
|
+
} : void 0,
|
|
42797
43088
|
onAuthorized: (socket) => authMarks.get(socket)?.()
|
|
42798
43089
|
});
|
|
42799
43090
|
const handleCallback = async (reqUrl, res) => {
|
|
@@ -43279,8 +43570,8 @@ function displayName(user) {
|
|
|
43279
43570
|
return fromProfile || user.real_name?.trim() || user.name?.trim() || "";
|
|
43280
43571
|
}
|
|
43281
43572
|
async function resolveUserName(token, userId, fallback, fetchImpl) {
|
|
43282
|
-
const
|
|
43283
|
-
if (
|
|
43573
|
+
const cached2 = nameCache.get(userId);
|
|
43574
|
+
if (cached2) return cached2;
|
|
43284
43575
|
try {
|
|
43285
43576
|
const data = await slackApi(token, "users.info", { user: userId }, fetchImpl);
|
|
43286
43577
|
const name = displayName(data.user ?? {}) || fallback;
|
|
@@ -43697,6 +43988,7 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
43697
43988
|
clearAgentDoneCustomSoundFile,
|
|
43698
43989
|
clearBoardPins,
|
|
43699
43990
|
clearHomeBoardCache,
|
|
43991
|
+
clearOptionalCliPresenceCache,
|
|
43700
43992
|
clipAgentEventForPaint,
|
|
43701
43993
|
clipThinkingForStore,
|
|
43702
43994
|
cloneRepoIntoSideboard,
|
|
@@ -43863,7 +44155,9 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
43863
44155
|
formatMessagesAsTranscript,
|
|
43864
44156
|
formatNotifyOrchestratorPrompt,
|
|
43865
44157
|
formatOptionalServicesDirective,
|
|
44158
|
+
formatOptionalServicesDirectiveForTurn,
|
|
43866
44159
|
formatOptionalServicesReminder,
|
|
44160
|
+
formatOptionalServicesReminderForTurn,
|
|
43867
44161
|
formatPlanQuestionAnswers,
|
|
43868
44162
|
formatPlanQuestionsForChat,
|
|
43869
44163
|
formatPrGateDirective,
|
|
@@ -44172,6 +44466,7 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
44172
44466
|
openPrStackLayers,
|
|
44173
44467
|
openStackLayer,
|
|
44174
44468
|
opencodeAdapter,
|
|
44469
|
+
optionalCliPresence,
|
|
44175
44470
|
optionalServiceConnected,
|
|
44176
44471
|
optionalServiceSpec,
|
|
44177
44472
|
orchestrationQuotaFallbackAgent,
|