@sideboard-ai/core 0.1.139 → 0.1.143
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-VTP4JR63.js → abletime-DI55AJX3.js} +2 -2
- package/dist/{abletime-DDTV7WO3.js → abletime-QEVAE67D.js} +2 -2
- package/dist/agents/cursor-runner.cjs +0 -0
- package/dist/agents/cursor-runner.js +1 -1
- package/dist/{agents-665S7Z3R.js → agents-E5AAMHDY.js} +11 -7
- package/dist/{agents-OC3XM7UE.js → agents-OEEOIKUB.js} +10 -6
- package/dist/{app-settings-U5RZKUXG.js → app-settings-WJDCJFSY.js} +3 -1
- package/dist/{app-settings-YQ7YX2CC.js → app-settings-ZCN632OJ.js} +3 -1
- package/dist/caffeinate-hold-NBRUWYXO.js +0 -0
- package/dist/{chunk-HLIHBGVO.js → chunk-4D4PEBGB.js} +2 -2
- package/dist/{chunk-DJJ3DTT4.js → chunk-4Q45TLZ5.js} +7 -7
- package/dist/chunk-6GN5WPYZ.js +0 -0
- package/dist/chunk-B3SJXYIJ.js +0 -0
- package/dist/chunk-BD2ICC4D.js +0 -0
- package/dist/{chunk-GKKHXZCT.js → chunk-BGQBELGJ.js} +201 -2
- package/dist/{chunk-2XXAAIN3.js → chunk-BOD5V52W.js} +1 -1
- package/dist/{chunk-23KCPND2.js → chunk-CG25RYQQ.js} +47 -40
- package/dist/{chunk-YZQEJOAU.js → chunk-EAP4SMR3.js} +342 -36
- package/dist/chunk-ED4UPEJX.js +0 -0
- package/dist/chunk-EUXOHTUK.js +0 -0
- package/dist/{chunk-5XH5M6RA.js → chunk-FADNFPZO.js} +3 -3
- package/dist/{chunk-SSBM4GZX.js → chunk-HC5N3BDL.js} +2 -2
- package/dist/{chunk-GJBHOJCZ.js → chunk-I77RYPOH.js} +201 -2
- package/dist/chunk-KGZBYWZ3.js +0 -0
- package/dist/chunk-KLBOWJ74.js +0 -0
- package/dist/{chunk-ZCLHAOFR.js → chunk-KP4OJUPH.js} +551 -39
- package/dist/chunk-KPIYENTF.js +0 -0
- package/dist/chunk-N5PM7HGQ.js +0 -0
- package/dist/chunk-NSTQ6QKD.js +0 -0
- package/dist/{chunk-DKXZCIB2.js → chunk-O3JEC2UJ.js} +7 -7
- package/dist/{chunk-RTX3AY42.js → chunk-OHWN4JEL.js} +5 -3
- package/dist/{chunk-E2RE7P2S.js → chunk-R2W7A2UO.js} +3 -3
- package/dist/chunk-S42XV45P.js +0 -0
- package/dist/{chunk-7SYFWPOZ.js → chunk-UG5N7ET3.js} +5 -3
- package/dist/{chunk-CDJISVKN.js → chunk-VHTIHOHE.js} +47 -41
- package/dist/{chunk-AT346U23.js → chunk-VZJVKQHH.js} +1 -1
- package/dist/{chunk-FSCQXMGI.js → chunk-WT5HSFOU.js} +1 -0
- package/dist/connected-teams-IJUJQ7R6.js +0 -0
- package/dist/{coordinator-prompt-43O6EPA2.js → coordinator-prompt-2LD3C74O.js} +3 -3
- package/dist/{coordinator-prompt-4TXU6Q3R.js → coordinator-prompt-FBX7Y4EM.js} +3 -3
- package/dist/cursor-recover-NOQYOFRF.js +0 -0
- package/dist/{global-workspace-ZDYKHQ4O.js → global-workspace-NG7SU3GV.js} +4 -4
- package/dist/{global-workspace-VIU57E3Y.js → global-workspace-OCV77UYF.js} +4 -4
- package/dist/index.cjs +945 -129
- package/dist/index.d.cts +249 -62
- package/dist/index.d.ts +249 -62
- package/dist/index.js +110 -14
- package/dist/mcp/run-stdio.cjs +646 -112
- package/dist/mcp/run-stdio.js +12 -12
- package/dist/oauth-YJBAURIJ.js +0 -0
- package/dist/{orchestrator-ZCMYSDTC.js → orchestrator-TDJZKQPT.js} +7 -7
- package/dist/{orchestrator-WEFXUHFX.js → orchestrator-VIFXOHF4.js} +8 -8
- package/dist/paths-QCI3MTO5.js +0 -0
- package/dist/plan-file-PHVKUAEE.js +0 -0
- package/dist/run-USF6FRKV.js +0 -0
- package/dist/thread-store-57ZLHR3A.js +0 -0
- package/dist/title-4WAKWZRF.js +0 -0
- package/dist/{workspaces-4HYGNH4B.js → workspaces-4FUQW5LD.js} +5 -5
- package/dist/{workspaces-ALCTB65T.js → workspaces-PU5YHQ7Z.js} +5 -5
- package/dist/{worktree-GXD2NGOZ.js → worktree-GKLPPNWR.js} +2 -2
- package/dist/{worktree-NSNZODAM.js → worktree-Z22HTSCU.js} +2 -2
- package/package.json +9 -8
- package/LICENSE +0 -190
|
@@ -18,13 +18,13 @@ import {
|
|
|
18
18
|
stripBrightsyNdjsonNoise,
|
|
19
19
|
sumUsageList,
|
|
20
20
|
toolDescription
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-VHTIHOHE.js";
|
|
22
22
|
import {
|
|
23
23
|
addWorkspace,
|
|
24
24
|
ensureWorkspace,
|
|
25
25
|
removeWorkspace,
|
|
26
26
|
syncWorkspacesFromThreads
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-HC5N3BDL.js";
|
|
28
28
|
import {
|
|
29
29
|
assertOrchestratorCapableAgent,
|
|
30
30
|
attachmentsFromWorktreePaths,
|
|
@@ -41,14 +41,17 @@ import {
|
|
|
41
41
|
persistPendingFileAttachments,
|
|
42
42
|
stageAbsolutePathsAsAttachments,
|
|
43
43
|
stageBuffersAsAttachments
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-R2W7A2UO.js";
|
|
45
45
|
import {
|
|
46
46
|
SLACK_REPLY_FORMATTING,
|
|
47
47
|
coordinatorSystemPrompt,
|
|
48
48
|
coordinatorTurnReminder,
|
|
49
49
|
enrichWorkspacesWithGithub,
|
|
50
50
|
ensureGlobalCoordinatorCwd
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-UG5N7ET3.js";
|
|
52
|
+
import {
|
|
53
|
+
httpFetch
|
|
54
|
+
} from "./chunk-ELBYOGVA.js";
|
|
52
55
|
import {
|
|
53
56
|
extractPresentedPlan,
|
|
54
57
|
readPlanFile,
|
|
@@ -65,12 +68,13 @@ import {
|
|
|
65
68
|
looksLikeAgentFailureMessage,
|
|
66
69
|
looksLikeInvalidAgentSession,
|
|
67
70
|
looksLikeV8Oom,
|
|
71
|
+
packagedMcpDir,
|
|
68
72
|
pushTurnStderr,
|
|
69
73
|
shouldFeedErrorBackToAgent,
|
|
70
74
|
shouldRetryFailedAgentTurn,
|
|
71
75
|
summarizeTurnStderr,
|
|
72
76
|
turnFailChatText
|
|
73
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-WT5HSFOU.js";
|
|
74
78
|
import {
|
|
75
79
|
releaseCaffeinateHoldForThread
|
|
76
80
|
} from "./chunk-DCOZABNT.js";
|
|
@@ -118,7 +122,7 @@ import {
|
|
|
118
122
|
withRepoGitLock,
|
|
119
123
|
worktreeDisplayLabelForGroup,
|
|
120
124
|
worktreeNameFromPath
|
|
121
|
-
} from "./chunk-
|
|
125
|
+
} from "./chunk-O3JEC2UJ.js";
|
|
122
126
|
import {
|
|
123
127
|
ATTACHMENTS_DIR,
|
|
124
128
|
LEGACY_ATTACHMENTS_DIR
|
|
@@ -135,9 +139,10 @@ import {
|
|
|
135
139
|
resolveNewThreadOptions,
|
|
136
140
|
resolveThreadDefaults,
|
|
137
141
|
resolveVaultKey,
|
|
142
|
+
saveAppSettings,
|
|
138
143
|
updateAdvancedSettings,
|
|
139
144
|
writeSecureJson
|
|
140
|
-
} from "./chunk-
|
|
145
|
+
} from "./chunk-I77RYPOH.js";
|
|
141
146
|
import {
|
|
142
147
|
stripNestedElectronEnv
|
|
143
148
|
} from "./chunk-4TR3HZFT.js";
|
|
@@ -339,7 +344,7 @@ function slackTokenFor(ws, kind = "read") {
|
|
|
339
344
|
const token2 = ws.user_token?.trim();
|
|
340
345
|
if (!token2) {
|
|
341
346
|
throw new Error(
|
|
342
|
-
`Slack search needs a user token for ${ws.team_name}. Reconnect via
|
|
347
|
+
`Slack search needs a user token for ${ws.team_name}. Reconnect via Settings \u2192 Remote \u2192 Slack (browser) or paste an xoxp- token.`
|
|
343
348
|
);
|
|
344
349
|
}
|
|
345
350
|
return token2;
|
|
@@ -354,7 +359,7 @@ function requireSlackWorkspace(teamId) {
|
|
|
354
359
|
const ws = getSlackWorkspace(teamId);
|
|
355
360
|
if (!ws) {
|
|
356
361
|
const connected = listSlackWorkspaces();
|
|
357
|
-
const hint = connected.length === 0 ? "Connect a workspace in
|
|
362
|
+
const hint = connected.length === 0 ? "Connect a workspace in Settings \u2192 Remote \u2192 Slack." : `Connected: ${connected.map((t) => `${t.team_name} (${t.team_id})`).join(", ")}`;
|
|
358
363
|
throw new Error(`Unknown Slack team_id "${teamId}". ${hint}`);
|
|
359
364
|
}
|
|
360
365
|
return ws;
|
|
@@ -448,7 +453,7 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
448
453
|
await continueOnReply(threadId, prompt);
|
|
449
454
|
return;
|
|
450
455
|
}
|
|
451
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
456
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-VIFXOHF4.js");
|
|
452
457
|
await getOrchestrator2().send(threadId, prompt);
|
|
453
458
|
} catch {
|
|
454
459
|
}
|
|
@@ -708,7 +713,7 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
708
713
|
}
|
|
709
714
|
|
|
710
715
|
// src/orchestrator/orchestrator.ts
|
|
711
|
-
import { existsSync as
|
|
716
|
+
import { existsSync as existsSync16 } from "fs";
|
|
712
717
|
|
|
713
718
|
// src/agents/spawn.ts
|
|
714
719
|
import { createInterface } from "readline";
|
|
@@ -728,9 +733,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
728
733
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
729
734
|
);
|
|
730
735
|
}
|
|
731
|
-
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-
|
|
736
|
+
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-NG7SU3GV.js");
|
|
732
737
|
if (isGlobalThread2(thread)) {
|
|
733
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-
|
|
738
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-2LD3C74O.js");
|
|
734
739
|
ensureGlobalCoordinatorCwd2(
|
|
735
740
|
isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
|
|
736
741
|
);
|
|
@@ -1008,6 +1013,15 @@ function estimateOccupancyTokens(messages) {
|
|
|
1008
1013
|
function threadHasCompactedContext(messages) {
|
|
1009
1014
|
return messages.some((m) => m.role === "summary");
|
|
1010
1015
|
}
|
|
1016
|
+
function forwardOccupancyTokens(messages, usage, windowTokens = 1e6) {
|
|
1017
|
+
const estimated = estimateOccupancyTokens(messages);
|
|
1018
|
+
if (threadHasCompactedContext(messages) && estimated > 0) return estimated;
|
|
1019
|
+
if (usage) {
|
|
1020
|
+
const last = contextTokens(usage);
|
|
1021
|
+
if (last > 0 && last <= windowTokens) return last;
|
|
1022
|
+
}
|
|
1023
|
+
return estimated;
|
|
1024
|
+
}
|
|
1011
1025
|
function forwardContextUsage(usage, messages) {
|
|
1012
1026
|
if (!usage) return null;
|
|
1013
1027
|
if (!threadHasCompactedContext(messages)) return usage;
|
|
@@ -1243,6 +1257,71 @@ function buildSessionSeed(messages, opts) {
|
|
|
1243
1257
|
"Continue from this context. Do not repeat the summary unless asked."
|
|
1244
1258
|
].join("\n");
|
|
1245
1259
|
}
|
|
1260
|
+
var BRIGHTSY_SUMMARIZE_CONTEXT_TOOL = "summarize_context";
|
|
1261
|
+
function extractBrightsyContextSummary(result) {
|
|
1262
|
+
if (!result?.trim()) return null;
|
|
1263
|
+
const trimmed = result.trim();
|
|
1264
|
+
const lower = trimmed.toLowerCase();
|
|
1265
|
+
if (lower.startsWith("context summarization failed") || lower.startsWith("nothing to summarize") || lower.startsWith("no messages found") || lower.startsWith("messages are required") || lower.startsWith("agent id is required")) {
|
|
1266
|
+
return null;
|
|
1267
|
+
}
|
|
1268
|
+
try {
|
|
1269
|
+
const parsed = JSON.parse(trimmed);
|
|
1270
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1271
|
+
if (parsed.error != null && typeof parsed.context_summary !== "string") {
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1274
|
+
if (typeof parsed.context_summary === "string" && parsed.context_summary.trim()) {
|
|
1275
|
+
return parsed.context_summary.trim();
|
|
1276
|
+
}
|
|
1277
|
+
return null;
|
|
1278
|
+
}
|
|
1279
|
+
} catch {
|
|
1280
|
+
}
|
|
1281
|
+
if (trimmed.includes('"error"') && !trimmed.includes("context_summary")) {
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
return trimmed;
|
|
1285
|
+
}
|
|
1286
|
+
function findLastBrightsyContextSummary(messages) {
|
|
1287
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1288
|
+
const message = messages[i];
|
|
1289
|
+
if (message?.role !== "agent") continue;
|
|
1290
|
+
for (const part of message.parts ?? []) {
|
|
1291
|
+
if (part.type !== "tool" || part.name !== BRIGHTSY_SUMMARIZE_CONTEXT_TOOL) {
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
if (part.status === "error") continue;
|
|
1295
|
+
const text = extractBrightsyContextSummary(part.result);
|
|
1296
|
+
if (text) return { index: i, text };
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
function messagesSinceLastBrightsyContextSummary(messages) {
|
|
1302
|
+
const match = findLastBrightsyContextSummary(messages);
|
|
1303
|
+
if (!match) return messages;
|
|
1304
|
+
return messages.slice(match.index + 1);
|
|
1305
|
+
}
|
|
1306
|
+
function buildBrightsySessionSeed(messages) {
|
|
1307
|
+
const match = findLastBrightsyContextSummary(messages);
|
|
1308
|
+
const tail = match ? messages.slice(match.index + 1) : messages;
|
|
1309
|
+
const body = formatMessagesAsTranscript(tail, { tools: "none" });
|
|
1310
|
+
if (!match && !body.trim()) return null;
|
|
1311
|
+
const blocks = [
|
|
1312
|
+
"Sideboard conversation context (restored after compaction or a new session):",
|
|
1313
|
+
""
|
|
1314
|
+
];
|
|
1315
|
+
if (match) {
|
|
1316
|
+
blocks.push(`## Prior summary
|
|
1317
|
+
${match.text}`, "");
|
|
1318
|
+
}
|
|
1319
|
+
if (body.trim()) {
|
|
1320
|
+
blocks.push(body, "");
|
|
1321
|
+
}
|
|
1322
|
+
blocks.push("Continue from this context. Do not repeat the summary unless asked.");
|
|
1323
|
+
return blocks.join("\n");
|
|
1324
|
+
}
|
|
1246
1325
|
function applyCompaction(messages, summaryText, thresholds = {}) {
|
|
1247
1326
|
const { older, recent } = splitForCompaction(messages, thresholds);
|
|
1248
1327
|
if (older.length === 0) return messages;
|
|
@@ -2860,7 +2939,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
2860
2939
|
});
|
|
2861
2940
|
await requireAgent(resolved.agent);
|
|
2862
2941
|
if (input.cowboy) {
|
|
2863
|
-
const { cowboyModeEnabled } = await import("./app-settings-
|
|
2942
|
+
const { cowboyModeEnabled } = await import("./app-settings-WJDCJFSY.js");
|
|
2864
2943
|
if (!cowboyModeEnabled()) {
|
|
2865
2944
|
throw new Error(
|
|
2866
2945
|
"Cowboy mode is off. Enable it in Settings \u2192 Advanced."
|
|
@@ -2916,7 +2995,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
2916
2995
|
let sourceIsFork = false;
|
|
2917
2996
|
if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
|
|
2918
2997
|
if (isAbleTimeConnected() && getIssueSource() === "abletime") {
|
|
2919
|
-
const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-
|
|
2998
|
+
const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-QEVAE67D.js");
|
|
2920
2999
|
const task = await ensureAbleTimeTask({
|
|
2921
3000
|
title: title?.trim() || "Untitled work",
|
|
2922
3001
|
description: input.prompt?.trim() || void 0
|
|
@@ -2989,7 +3068,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
2989
3068
|
return readThread(thread.id) ?? thread;
|
|
2990
3069
|
}
|
|
2991
3070
|
async function listLinearIssues(agent, repoPath) {
|
|
2992
|
-
const { getAdapter: getAdapter2 } = await import("./agents-
|
|
3071
|
+
const { getAdapter: getAdapter2 } = await import("./agents-E5AAMHDY.js");
|
|
2993
3072
|
await requireAgent(agent, { requireLinear: true });
|
|
2994
3073
|
const adapter = getAdapter2(agent);
|
|
2995
3074
|
if (!adapter.listLinearIssues) {
|
|
@@ -3386,7 +3465,7 @@ async function adoptThread(input) {
|
|
|
3386
3465
|
messages: input.messages ?? []
|
|
3387
3466
|
});
|
|
3388
3467
|
writeThread(thread);
|
|
3389
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
3468
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-PU5YHQ7Z.js");
|
|
3390
3469
|
await ensureWorkspace2(repoPath);
|
|
3391
3470
|
return thread;
|
|
3392
3471
|
}
|
|
@@ -4729,6 +4808,93 @@ async function confirmLand(thread, opts) {
|
|
|
4729
4808
|
import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
|
|
4730
4809
|
import { homedir } from "os";
|
|
4731
4810
|
import { join as join11 } from "path";
|
|
4811
|
+
|
|
4812
|
+
// src/skills/bundled/long-running.ts
|
|
4813
|
+
var LONG_RUNNING_SKILL_COMMAND = "long-running";
|
|
4814
|
+
var LONG_RUNNING_SKILL_NAME = "long-running";
|
|
4815
|
+
var BUNDLED_LONG_RUNNING_PATH = "bundled:long-running";
|
|
4816
|
+
var LONG_RUNNING_SKILL_DESCRIPTION = "Detach and wait on any long worktree job (pack, test, deploy) so a new chat turn does not SIGTERM it, and the human does not have to poll. Use when a command may run more than ~30s, or the user mentions wait / status / aborted builds.";
|
|
4817
|
+
var LONG_RUNNING_SKILL_BODY = `# Long-running jobs
|
|
4818
|
+
|
|
4819
|
+
A Sideboard or Cursor **worktree turn** SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. \`block_until_ms: 0\` is not enough \u2014 the child stays in that group.
|
|
4820
|
+
|
|
4821
|
+
Do **not** ask the human to check back. Detach, then **wait** in 45s slices (same idea as MCP \`wait_for_turn\`) until \`stillRunning\` is false.
|
|
4822
|
+
|
|
4823
|
+
## Tool
|
|
4824
|
+
|
|
4825
|
+
Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" start\` path injected each turn. If this worktree has \`scripts/detached-job.js\`, that is the same tool.
|
|
4826
|
+
|
|
4827
|
+
\`\`\`bash
|
|
4828
|
+
# Start (exits in ~1s; job survives this turn)
|
|
4829
|
+
node <detached-job.js> start <id> -- <command> [args...]
|
|
4830
|
+
|
|
4831
|
+
# Wait \u2014 returns within ~45s even if the job is still going
|
|
4832
|
+
node <detached-job.js> wait <id>
|
|
4833
|
+
|
|
4834
|
+
# Block until the process exits (humans / a turn that will not be interrupted)
|
|
4835
|
+
node <detached-job.js> wait <id> --until-done
|
|
4836
|
+
|
|
4837
|
+
node <detached-job.js> status <id>
|
|
4838
|
+
\`\`\`
|
|
4839
|
+
|
|
4840
|
+
\`<id>\` is kebab-case (\`mac-release\`, \`core-test\`, \`fly-deploy\`). State is \`.sideboard/detached-jobs/<id>/\` (local scratch).
|
|
4841
|
+
|
|
4842
|
+
Wait JSON:
|
|
4843
|
+
|
|
4844
|
+
- \`stillRunning: true\` \u2192 exit 2 \u2192 **call wait again**. Progress is in \`progress\` / \`phase\`. Do not start a second job. Do not ping the user.
|
|
4845
|
+
- \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
|
|
4846
|
+
- \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
|
|
4847
|
+
|
|
4848
|
+
## Sideboard UI (append-only log)
|
|
4849
|
+
|
|
4850
|
+
The side column **is** the live view. Use \`present_artifact\` **\`type=log\`** with a stable \`artifact_id\` (the job id). Each call **appends** \u2014 send only \`delta\` from wait JSON, not the full log and not HTML.
|
|
4851
|
+
|
|
4852
|
+
\`\`\`
|
|
4853
|
+
present_artifact
|
|
4854
|
+
title: <short title>
|
|
4855
|
+
type: log
|
|
4856
|
+
artifact_id: <id>
|
|
4857
|
+
content: <wait.delta> # new lines only; empty is ok
|
|
4858
|
+
status: running | ok | failed # from wait.status
|
|
4859
|
+
phase: <wait.phase>
|
|
4860
|
+
\`\`\`
|
|
4861
|
+
|
|
4862
|
+
After **start**, present once (\`status=running\`, empty or first lines). After **every wait**, present the same id with \`content=delta\`. Do **not** dump the log in chat. Do **not** also fence HTML.
|
|
4863
|
+
|
|
4864
|
+
Ad-hoc pid/log (legacy or another tool\u2019s files):
|
|
4865
|
+
|
|
4866
|
+
\`\`\`bash
|
|
4867
|
+
node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
4868
|
+
\`\`\`
|
|
4869
|
+
|
|
4870
|
+
## Agent loop
|
|
4871
|
+
|
|
4872
|
+
1. \`start\` once. If JSON says \`already-running\`, do not start again.
|
|
4873
|
+
2. Immediately \`present_artifact\` \`type=log\` (same \`artifact_id\`, \`status=running\`) \u2014 the human should see **working** in the side column, not a \u201Ccheck back later\u201D message.
|
|
4874
|
+
3. Loop \`wait\` (use \`--timeout-ms 15000\` for a livelier pane). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
|
|
4875
|
+
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
4876
|
+
|
|
4877
|
+
Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
|
|
4878
|
+
`;
|
|
4879
|
+
|
|
4880
|
+
// src/skills/discover.ts
|
|
4881
|
+
var BUNDLED_SKILL_PREFIX = "bundled:";
|
|
4882
|
+
function bundledSkills() {
|
|
4883
|
+
return [
|
|
4884
|
+
{
|
|
4885
|
+
id: BUNDLED_LONG_RUNNING_PATH,
|
|
4886
|
+
name: LONG_RUNNING_SKILL_NAME,
|
|
4887
|
+
command: LONG_RUNNING_SKILL_COMMAND,
|
|
4888
|
+
description: LONG_RUNNING_SKILL_DESCRIPTION.slice(0, 240),
|
|
4889
|
+
path: BUNDLED_LONG_RUNNING_PATH,
|
|
4890
|
+
source: "bundled"
|
|
4891
|
+
}
|
|
4892
|
+
];
|
|
4893
|
+
}
|
|
4894
|
+
function bundledSkillBody(id) {
|
|
4895
|
+
if (id === LONG_RUNNING_SKILL_COMMAND) return LONG_RUNNING_SKILL_BODY;
|
|
4896
|
+
return null;
|
|
4897
|
+
}
|
|
4732
4898
|
function toCommand(name) {
|
|
4733
4899
|
return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4734
4900
|
}
|
|
@@ -4846,7 +5012,13 @@ function discoverSkills(worktreePath) {
|
|
|
4846
5012
|
scanSkillsDir(abs, "user", collected);
|
|
4847
5013
|
}
|
|
4848
5014
|
scanClaudePluginSkills(join11(home, ".claude/plugins"), collected);
|
|
4849
|
-
|
|
5015
|
+
collected.push(...bundledSkills());
|
|
5016
|
+
const rank = {
|
|
5017
|
+
workspace: 0,
|
|
5018
|
+
user: 1,
|
|
5019
|
+
cli: 2,
|
|
5020
|
+
bundled: 3
|
|
5021
|
+
};
|
|
4850
5022
|
const byCommand = /* @__PURE__ */ new Map();
|
|
4851
5023
|
for (const skill of collected) {
|
|
4852
5024
|
const prev = byCommand.get(skill.command);
|
|
@@ -4857,6 +5029,14 @@ function discoverSkills(worktreePath) {
|
|
|
4857
5029
|
return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
|
|
4858
5030
|
}
|
|
4859
5031
|
function readSkillBody(skillPath, maxChars = 12e3) {
|
|
5032
|
+
if (skillPath.startsWith(BUNDLED_SKILL_PREFIX)) {
|
|
5033
|
+
const body = bundledSkillBody(skillPath.slice(BUNDLED_SKILL_PREFIX.length));
|
|
5034
|
+
if (body) {
|
|
5035
|
+
return body.length > maxChars ? `${body.slice(0, maxChars)}
|
|
5036
|
+
|
|
5037
|
+
\u2026(truncated)` : body;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
4860
5040
|
const raw = readFileSync9(skillPath, "utf8");
|
|
4861
5041
|
if (raw.startsWith("---")) {
|
|
4862
5042
|
const end = raw.indexOf("\n---", 3);
|
|
@@ -4950,8 +5130,39 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
|
|
|
4950
5130
|
}
|
|
4951
5131
|
|
|
4952
5132
|
// src/agents/instructions.ts
|
|
4953
|
-
import { existsSync as
|
|
4954
|
-
import { join as
|
|
5133
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
|
|
5134
|
+
import { join as join13 } from "path";
|
|
5135
|
+
|
|
5136
|
+
// src/skills/detached-job-path.ts
|
|
5137
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5138
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
5139
|
+
import { fileURLToPath } from "url";
|
|
5140
|
+
function packagedDetachedJobPath() {
|
|
5141
|
+
const dir = packagedMcpDir();
|
|
5142
|
+
if (!dir) return null;
|
|
5143
|
+
const script = join12(dir, "scripts", "detached-job.js");
|
|
5144
|
+
return existsSync13(script) ? script : null;
|
|
5145
|
+
}
|
|
5146
|
+
function resolveDetachedJobScript() {
|
|
5147
|
+
const packaged = packagedDetachedJobPath();
|
|
5148
|
+
if (packaged) return packaged;
|
|
5149
|
+
let dir = dirname4(fileURLToPath(import.meta.url));
|
|
5150
|
+
for (let i = 0; i < 8; i++) {
|
|
5151
|
+
const candidate = join12(dir, "scripts", "detached-job.js");
|
|
5152
|
+
if (existsSync13(candidate)) return candidate;
|
|
5153
|
+
const parent = dirname4(dir);
|
|
5154
|
+
if (parent === dir) break;
|
|
5155
|
+
dir = parent;
|
|
5156
|
+
}
|
|
5157
|
+
return null;
|
|
5158
|
+
}
|
|
5159
|
+
function formatDetachedJobInvoke(scriptPath) {
|
|
5160
|
+
const resolved = scriptPath === void 0 ? resolveDetachedJobScript() : scriptPath;
|
|
5161
|
+
if (resolved) return `node ${JSON.stringify(resolved)}`;
|
|
5162
|
+
return "node scripts/detached-job.js";
|
|
5163
|
+
}
|
|
5164
|
+
|
|
5165
|
+
// src/agents/instructions.ts
|
|
4955
5166
|
function normPath(p) {
|
|
4956
5167
|
return p.replace(/\/+$/, "");
|
|
4957
5168
|
}
|
|
@@ -5059,6 +5270,7 @@ function formatWorktreeReminder() {
|
|
|
5059
5270
|
function formatProcessGuideDirective() {
|
|
5060
5271
|
return [
|
|
5061
5272
|
"Process guides (recurring work only):",
|
|
5273
|
+
"- Long jobs (pack, test, deploy, anything that may run more than ~30s): Sideboard always provides `/long-running`. Detach and wait \u2014 do not ask the human to poll.",
|
|
5062
5274
|
"- If `.claude/skills/graph-engineering/SKILL.md` exists, follow it (`/graph-engineering`) for migrations, ports, batch fixes, and other fan-out. Judge first; state on disk; grow the rulebook; do not patch around it.",
|
|
5063
5275
|
"- If this same shape of work will happen again, write `.claude/skills/<kebab-name>/SKILL.md` in this worktree (Claude Code project skill). Sideboard `/name`, Claude Code, and `attach` all load that path. Do not leave the method only in chat.",
|
|
5064
5276
|
"- Merge-readiness notes: create or edit `.claude/skills/review/SKILL.md` and commit it. That file is allowed. Do not use `.sideboard/review.md` for new notes (legacy; setup copies it into the skill).",
|
|
@@ -5066,6 +5278,23 @@ function formatProcessGuideDirective() {
|
|
|
5066
5278
|
"- Skip a guide for a one-off. If a matching skill exists, follow it. Same miss twice \u2192 edit the skill, do not patch around it."
|
|
5067
5279
|
].join("\n");
|
|
5068
5280
|
}
|
|
5281
|
+
function formatLongRunningDirective(opts) {
|
|
5282
|
+
const invoke = formatDetachedJobInvoke(opts?.scriptPath);
|
|
5283
|
+
return [
|
|
5284
|
+
"Long-running jobs (mandatory when a command may run more than ~30s):",
|
|
5285
|
+
"A Sideboard worktree turn SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. `block_until_ms: 0` is not enough. Do not ask the human to poll.",
|
|
5286
|
+
`Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
|
|
5287
|
+
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
|
|
5288
|
+
"- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
|
|
5289
|
+
`- Loop \`${invoke} wait <id>\` (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait again. Do not resend the full log or HTML.`,
|
|
5290
|
+
"- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
|
|
5291
|
+
"State: `.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
|
|
5292
|
+
].join("\n");
|
|
5293
|
+
}
|
|
5294
|
+
function formatLongRunningReminder(opts) {
|
|
5295
|
+
const invoke = formatDetachedJobInvoke(opts?.scriptPath);
|
|
5296
|
+
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not ask the human to poll.`;
|
|
5297
|
+
}
|
|
5069
5298
|
function formatArtifactDirective() {
|
|
5070
5299
|
return [
|
|
5071
5300
|
"Sideboard side column (desktop UI):",
|
|
@@ -5115,8 +5344,8 @@ function loadAgentInstructions(worktreePath, agent) {
|
|
|
5115
5344
|
const out = [];
|
|
5116
5345
|
for (const rel of candidates) {
|
|
5117
5346
|
if (seenPaths.has(rel)) continue;
|
|
5118
|
-
const abs =
|
|
5119
|
-
if (!
|
|
5347
|
+
const abs = join13(worktreePath, rel);
|
|
5348
|
+
if (!existsSync14(abs)) continue;
|
|
5120
5349
|
try {
|
|
5121
5350
|
if (!statSync4(abs).isFile()) continue;
|
|
5122
5351
|
let content = readFileSync10(abs, "utf8");
|
|
@@ -5160,6 +5389,253 @@ function withAgentInstructions(prompt, files) {
|
|
|
5160
5389
|
${prompt}`;
|
|
5161
5390
|
}
|
|
5162
5391
|
|
|
5392
|
+
// src/integrations/optional-services.ts
|
|
5393
|
+
var OPTIONAL_SERVICE_IDS = ["vercel", "supabase", "posthog", "sentry"];
|
|
5394
|
+
var OPTIONAL_SERVICES = [
|
|
5395
|
+
{
|
|
5396
|
+
id: "vercel",
|
|
5397
|
+
label: "Vercel",
|
|
5398
|
+
hint: "Deploys and project settings. Agents use the `vercel` CLI (`VERCEL_TOKEN`).",
|
|
5399
|
+
tokenPlaceholder: "token from vercel.com/account/tokens",
|
|
5400
|
+
tokenDocs: "https://vercel.com/account/tokens",
|
|
5401
|
+
envKey: "VERCEL_TOKEN",
|
|
5402
|
+
cli: "vercel",
|
|
5403
|
+
npmPackage: "vercel"
|
|
5404
|
+
},
|
|
5405
|
+
{
|
|
5406
|
+
id: "supabase",
|
|
5407
|
+
label: "Supabase",
|
|
5408
|
+
hint: "Database and project management. Agents use the `supabase` CLI (`SUPABASE_ACCESS_TOKEN`).",
|
|
5409
|
+
tokenPlaceholder: "sbp_\u2026",
|
|
5410
|
+
tokenDocs: "https://supabase.com/dashboard/account/tokens",
|
|
5411
|
+
envKey: "SUPABASE_ACCESS_TOKEN",
|
|
5412
|
+
cli: "supabase",
|
|
5413
|
+
npmPackage: "supabase"
|
|
5414
|
+
},
|
|
5415
|
+
{
|
|
5416
|
+
id: "posthog",
|
|
5417
|
+
label: "PostHog",
|
|
5418
|
+
hint: "Product analytics. No first-class CLI \u2014 agents use the HTTP API (`POSTHOG_PERSONAL_API_KEY`).",
|
|
5419
|
+
tokenPlaceholder: "phx_\u2026",
|
|
5420
|
+
tokenDocs: "https://app.posthog.com/settings/user-api-keys",
|
|
5421
|
+
envKey: "POSTHOG_PERSONAL_API_KEY",
|
|
5422
|
+
hostEnvKey: "POSTHOG_HOST",
|
|
5423
|
+
defaultHost: "https://us.posthog.com",
|
|
5424
|
+
hostPlaceholder: "https://us.posthog.com"
|
|
5425
|
+
},
|
|
5426
|
+
{
|
|
5427
|
+
id: "sentry",
|
|
5428
|
+
label: "Sentry",
|
|
5429
|
+
hint: "Error tracking. Agents use `sentry-cli` (`SENTRY_AUTH_TOKEN`).",
|
|
5430
|
+
tokenPlaceholder: "sntrys_\u2026 or auth token",
|
|
5431
|
+
tokenDocs: "https://sentry.io/settings/account/api/auth-tokens/",
|
|
5432
|
+
envKey: "SENTRY_AUTH_TOKEN",
|
|
5433
|
+
cli: "sentry-cli",
|
|
5434
|
+
npmPackage: "@sentry/cli",
|
|
5435
|
+
hostEnvKey: "SENTRY_URL",
|
|
5436
|
+
defaultHost: "https://sentry.io",
|
|
5437
|
+
hostPlaceholder: "https://sentry.io"
|
|
5438
|
+
}
|
|
5439
|
+
];
|
|
5440
|
+
function optionalServiceSpec(id) {
|
|
5441
|
+
const spec = OPTIONAL_SERVICES.find((s) => s.id === id);
|
|
5442
|
+
if (!spec) throw new Error(`Unknown optional service: ${id}`);
|
|
5443
|
+
return spec;
|
|
5444
|
+
}
|
|
5445
|
+
function isOptionalServiceId(value) {
|
|
5446
|
+
return typeof value === "string" && OPTIONAL_SERVICE_IDS.includes(value);
|
|
5447
|
+
}
|
|
5448
|
+
function normalizeServiceOrigin(raw, fallback) {
|
|
5449
|
+
const trimmed = raw?.trim() || fallback;
|
|
5450
|
+
try {
|
|
5451
|
+
const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
|
|
5452
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return fallback;
|
|
5453
|
+
return `${url.protocol}//${url.host}`;
|
|
5454
|
+
} catch {
|
|
5455
|
+
return fallback;
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
function asRecord(value) {
|
|
5459
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
5460
|
+
}
|
|
5461
|
+
function asString(value) {
|
|
5462
|
+
return typeof value === "string" ? value.trim() : "";
|
|
5463
|
+
}
|
|
5464
|
+
function firstString(record, keys) {
|
|
5465
|
+
if (!record) return "";
|
|
5466
|
+
for (const key of keys) {
|
|
5467
|
+
const value = asString(record[key]);
|
|
5468
|
+
if (value) return value;
|
|
5469
|
+
}
|
|
5470
|
+
return "";
|
|
5471
|
+
}
|
|
5472
|
+
async function readJson(res) {
|
|
5473
|
+
const text = await res.text();
|
|
5474
|
+
if (!text.trim()) return null;
|
|
5475
|
+
try {
|
|
5476
|
+
return JSON.parse(text);
|
|
5477
|
+
} catch {
|
|
5478
|
+
return text;
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
async function authorizedGet(url, token) {
|
|
5482
|
+
const res = await httpFetch(url, {
|
|
5483
|
+
method: "GET",
|
|
5484
|
+
headers: {
|
|
5485
|
+
Authorization: `Bearer ${token}`,
|
|
5486
|
+
Accept: "application/json"
|
|
5487
|
+
}
|
|
5488
|
+
});
|
|
5489
|
+
const body = await readJson(res);
|
|
5490
|
+
if (!res.ok) {
|
|
5491
|
+
const record = asRecord(body);
|
|
5492
|
+
const message = firstString(record, ["message", "error", "error_description", "detail"]) || (typeof body === "string" ? body.trim().slice(0, 160) : "");
|
|
5493
|
+
throw new Error(
|
|
5494
|
+
message ? `${res.status}: ${message}` : `Request failed (${res.status})`
|
|
5495
|
+
);
|
|
5496
|
+
}
|
|
5497
|
+
return body;
|
|
5498
|
+
}
|
|
5499
|
+
async function verifyOptionalService(input) {
|
|
5500
|
+
const token = input.token.trim();
|
|
5501
|
+
if (!token) throw new Error("A token is required");
|
|
5502
|
+
const spec = optionalServiceSpec(input.id);
|
|
5503
|
+
try {
|
|
5504
|
+
if (input.id === "vercel") {
|
|
5505
|
+
const body2 = asRecord(await authorizedGet("https://api.vercel.com/v2/user", token));
|
|
5506
|
+
const user = asRecord(body2?.user) ?? body2;
|
|
5507
|
+
const name2 = firstString(user, ["username", "name", "email"]) || "Vercel user";
|
|
5508
|
+
return { viewerName: name2 };
|
|
5509
|
+
}
|
|
5510
|
+
if (input.id === "supabase") {
|
|
5511
|
+
const body2 = await authorizedGet("https://api.supabase.com/v1/organizations", token);
|
|
5512
|
+
const list2 = Array.isArray(body2) ? body2 : [];
|
|
5513
|
+
const first2 = asRecord(list2[0]);
|
|
5514
|
+
const name2 = firstString(first2, ["name", "slug"]) || "Supabase";
|
|
5515
|
+
return { viewerName: list2.length > 1 ? `${name2} +${list2.length - 1}` : name2 };
|
|
5516
|
+
}
|
|
5517
|
+
if (input.id === "posthog") {
|
|
5518
|
+
const host2 = normalizeServiceOrigin(input.host, spec.defaultHost ?? "https://us.posthog.com");
|
|
5519
|
+
const body2 = asRecord(await authorizedGet(`${host2}/api/users/@me/`, token));
|
|
5520
|
+
const name2 = firstString(body2, ["email", "distinct_id"]) || [firstString(body2, ["first_name"]), firstString(body2, ["last_name"])].filter(Boolean).join(" ") || "PostHog user";
|
|
5521
|
+
return { viewerName: name2, host: host2 };
|
|
5522
|
+
}
|
|
5523
|
+
const host = normalizeServiceOrigin(input.host, spec.defaultHost ?? "https://sentry.io");
|
|
5524
|
+
const body = await authorizedGet(`${host}/api/0/organizations/`, token);
|
|
5525
|
+
const list = Array.isArray(body) ? body : [];
|
|
5526
|
+
const first = asRecord(list[0]);
|
|
5527
|
+
const name = firstString(first, ["slug", "name"]) || "Sentry";
|
|
5528
|
+
return { viewerName: list.length > 1 ? `${name} +${list.length - 1}` : name, host };
|
|
5529
|
+
} catch (err) {
|
|
5530
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
5531
|
+
throw new Error(
|
|
5532
|
+
`${spec.label} rejected this token. ${detail} Create a token at ${spec.tokenDocs}`
|
|
5533
|
+
);
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
function applyConnection(integrations, id, input) {
|
|
5537
|
+
if (id === "vercel") {
|
|
5538
|
+
integrations.vercelToken = input.token;
|
|
5539
|
+
if (input.viewerName) integrations.vercelViewerName = input.viewerName.slice(0, 120);
|
|
5540
|
+
return;
|
|
5541
|
+
}
|
|
5542
|
+
if (id === "supabase") {
|
|
5543
|
+
integrations.supabaseAccessToken = input.token;
|
|
5544
|
+
if (input.viewerName) integrations.supabaseViewerName = input.viewerName.slice(0, 120);
|
|
5545
|
+
return;
|
|
5546
|
+
}
|
|
5547
|
+
if (id === "posthog") {
|
|
5548
|
+
integrations.posthogPersonalApiKey = input.token;
|
|
5549
|
+
if (input.host) integrations.posthogHost = input.host;
|
|
5550
|
+
if (input.viewerName) integrations.posthogViewerName = input.viewerName.slice(0, 120);
|
|
5551
|
+
return;
|
|
5552
|
+
}
|
|
5553
|
+
integrations.sentryAuthToken = input.token;
|
|
5554
|
+
if (input.host) integrations.sentryHost = input.host;
|
|
5555
|
+
if (input.viewerName) integrations.sentryViewerName = input.viewerName.slice(0, 120);
|
|
5556
|
+
}
|
|
5557
|
+
function clearConnection(integrations, id) {
|
|
5558
|
+
if (id === "vercel") {
|
|
5559
|
+
delete integrations.vercelToken;
|
|
5560
|
+
delete integrations.vercelViewerName;
|
|
5561
|
+
return;
|
|
5562
|
+
}
|
|
5563
|
+
if (id === "supabase") {
|
|
5564
|
+
delete integrations.supabaseAccessToken;
|
|
5565
|
+
delete integrations.supabaseViewerName;
|
|
5566
|
+
return;
|
|
5567
|
+
}
|
|
5568
|
+
if (id === "posthog") {
|
|
5569
|
+
delete integrations.posthogPersonalApiKey;
|
|
5570
|
+
delete integrations.posthogViewerName;
|
|
5571
|
+
delete integrations.posthogHost;
|
|
5572
|
+
return;
|
|
5573
|
+
}
|
|
5574
|
+
delete integrations.sentryAuthToken;
|
|
5575
|
+
delete integrations.sentryViewerName;
|
|
5576
|
+
delete integrations.sentryHost;
|
|
5577
|
+
}
|
|
5578
|
+
async function connectOptionalService(input) {
|
|
5579
|
+
if (!isOptionalServiceId(input.id)) {
|
|
5580
|
+
throw new Error("Unknown optional service");
|
|
5581
|
+
}
|
|
5582
|
+
const verified = await verifyOptionalService(input);
|
|
5583
|
+
const current = loadAppSettings();
|
|
5584
|
+
const integrations = { ...current.integrations };
|
|
5585
|
+
applyConnection(integrations, input.id, {
|
|
5586
|
+
token: input.token.trim(),
|
|
5587
|
+
host: verified.host,
|
|
5588
|
+
viewerName: verified.viewerName
|
|
5589
|
+
});
|
|
5590
|
+
return saveAppSettings({ ...current, integrations });
|
|
5591
|
+
}
|
|
5592
|
+
function disconnectOptionalService(id) {
|
|
5593
|
+
if (!isOptionalServiceId(id)) {
|
|
5594
|
+
throw new Error("Unknown optional service");
|
|
5595
|
+
}
|
|
5596
|
+
const current = loadAppSettings();
|
|
5597
|
+
const integrations = { ...current.integrations };
|
|
5598
|
+
clearConnection(integrations, id);
|
|
5599
|
+
return saveAppSettings({ ...current, integrations });
|
|
5600
|
+
}
|
|
5601
|
+
function optionalServiceConnected(id, integrations) {
|
|
5602
|
+
if (id === "vercel") return Boolean(integrations.vercelToken?.trim());
|
|
5603
|
+
if (id === "supabase") return Boolean(integrations.supabaseAccessToken?.trim());
|
|
5604
|
+
if (id === "posthog") return Boolean(integrations.posthogPersonalApiKey?.trim());
|
|
5605
|
+
return Boolean(integrations.sentryAuthToken?.trim());
|
|
5606
|
+
}
|
|
5607
|
+
function connectedOptionalServices(integrations) {
|
|
5608
|
+
return OPTIONAL_SERVICES.filter((spec) => optionalServiceConnected(spec.id, integrations));
|
|
5609
|
+
}
|
|
5610
|
+
function formatOptionalServicesDirective(integrations) {
|
|
5611
|
+
const connected = connectedOptionalServices(integrations);
|
|
5612
|
+
if (connected.length === 0) return null;
|
|
5613
|
+
const lines = [
|
|
5614
|
+
"Optional connectors (connected \u2014 tokens are already in this process env):"
|
|
5615
|
+
];
|
|
5616
|
+
for (const spec of connected) {
|
|
5617
|
+
if (spec.cli) {
|
|
5618
|
+
const extras = spec.hostEnvKey ? ` / \`${spec.hostEnvKey}\`` : "";
|
|
5619
|
+
lines.push(
|
|
5620
|
+
`- ${spec.label}: use the \`${spec.cli}\` CLI (\`${spec.envKey}\`${extras}). Do not add a ${spec.label} MCP.`
|
|
5621
|
+
);
|
|
5622
|
+
} else {
|
|
5623
|
+
const extras = spec.hostEnvKey ? ` / \`${spec.hostEnvKey}\`` : "";
|
|
5624
|
+
lines.push(
|
|
5625
|
+
`- ${spec.label}: no first-class CLI \u2014 call the HTTP API with \`${spec.envKey}\`${extras}. Do not add a ${spec.label} MCP.`
|
|
5626
|
+
);
|
|
5627
|
+
}
|
|
5628
|
+
}
|
|
5629
|
+
lines.push(
|
|
5630
|
+
"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."
|
|
5631
|
+
);
|
|
5632
|
+
return lines.join("\n");
|
|
5633
|
+
}
|
|
5634
|
+
function formatOptionalServicesReminder(integrations) {
|
|
5635
|
+
if (connectedOptionalServices(integrations).length === 0) return null;
|
|
5636
|
+
return "Settings \u2192 Connectors: prefer official CLIs (`vercel`, `supabase`, `sentry-cli`) or the PostHog HTTP API with injected env. Do not add vendor MCPs.";
|
|
5637
|
+
}
|
|
5638
|
+
|
|
5163
5639
|
// src/threads/sync-branch.ts
|
|
5164
5640
|
async function syncThreadBranchFromGit(threadId) {
|
|
5165
5641
|
const thread = readThread(threadId);
|
|
@@ -5192,8 +5668,8 @@ async function syncThreadBranchFromGit(threadId) {
|
|
|
5192
5668
|
|
|
5193
5669
|
// src/store/schedules.ts
|
|
5194
5670
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5195
|
-
import { existsSync as
|
|
5196
|
-
import { join as
|
|
5671
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
|
|
5672
|
+
import { join as join14 } from "path";
|
|
5197
5673
|
import { Cron } from "croner";
|
|
5198
5674
|
var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
|
|
5199
5675
|
var UNIT_MS = {
|
|
@@ -5203,7 +5679,7 @@ var UNIT_MS = {
|
|
|
5203
5679
|
d: 864e5
|
|
5204
5680
|
};
|
|
5205
5681
|
function schedulesPath() {
|
|
5206
|
-
return
|
|
5682
|
+
return join14(appDataDir(), "schedules.json");
|
|
5207
5683
|
}
|
|
5208
5684
|
function parseDurationMs(every) {
|
|
5209
5685
|
const m = every.trim().match(DURATION_RE);
|
|
@@ -5309,7 +5785,7 @@ function normalizeTask(raw) {
|
|
|
5309
5785
|
}
|
|
5310
5786
|
function readAll() {
|
|
5311
5787
|
const path = schedulesPath();
|
|
5312
|
-
if (!
|
|
5788
|
+
if (!existsSync15(path)) return [];
|
|
5313
5789
|
try {
|
|
5314
5790
|
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
5315
5791
|
if (!Array.isArray(parsed)) return [];
|
|
@@ -5437,7 +5913,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
5437
5913
|
${prompt}`;
|
|
5438
5914
|
}
|
|
5439
5915
|
async function defaultDeps() {
|
|
5440
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
5916
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-VIFXOHF4.js");
|
|
5441
5917
|
const orch = getOrchestrator2();
|
|
5442
5918
|
return {
|
|
5443
5919
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -5643,7 +6119,7 @@ var Orchestrator = class {
|
|
|
5643
6119
|
}
|
|
5644
6120
|
continue;
|
|
5645
6121
|
}
|
|
5646
|
-
if (!
|
|
6122
|
+
if (!existsSync16(thread.worktreePath)) {
|
|
5647
6123
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
5648
6124
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
5649
6125
|
continue;
|
|
@@ -5662,7 +6138,7 @@ var Orchestrator = class {
|
|
|
5662
6138
|
orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
|
|
5663
6139
|
});
|
|
5664
6140
|
}
|
|
5665
|
-
const { autoCleanupOrphansEnabled } = await import("./app-settings-
|
|
6141
|
+
const { autoCleanupOrphansEnabled } = await import("./app-settings-WJDCJFSY.js");
|
|
5666
6142
|
if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
|
|
5667
6143
|
await cleanupOrphanWorktrees({ repoPaths });
|
|
5668
6144
|
}
|
|
@@ -5902,7 +6378,7 @@ var Orchestrator = class {
|
|
|
5902
6378
|
}
|
|
5903
6379
|
await setup;
|
|
5904
6380
|
if (skipSetup) return;
|
|
5905
|
-
const { autoRunAfterSetupEnabled } = await import("./app-settings-
|
|
6381
|
+
const { autoRunAfterSetupEnabled } = await import("./app-settings-WJDCJFSY.js");
|
|
5906
6382
|
if (autoRunAfterSetupEnabled()) {
|
|
5907
6383
|
try {
|
|
5908
6384
|
await this.startDev(threadId);
|
|
@@ -6164,7 +6640,9 @@ var Orchestrator = class {
|
|
|
6164
6640
|
].join("\n") : null
|
|
6165
6641
|
].filter(Boolean).join("\n") : null;
|
|
6166
6642
|
const artifactReminder = thread.agent !== "brightsy" ? formatUiReminder() : null;
|
|
6643
|
+
const longRunningReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatLongRunningReminder() : null;
|
|
6167
6644
|
const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
|
|
6645
|
+
const optionalServicesReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatOptionalServicesReminder(loadAppSettings().integrations) : null;
|
|
6168
6646
|
const slackReplyContext = formatSlackRepliesForTurn(
|
|
6169
6647
|
pendingSlackExternalReplies(thread.messages)
|
|
6170
6648
|
);
|
|
@@ -6172,7 +6650,9 @@ var Orchestrator = class {
|
|
|
6172
6650
|
thread.planMode ? PLAN_MODE_INSTRUCTION : null,
|
|
6173
6651
|
orchestrationReminder,
|
|
6174
6652
|
worktreeReminder,
|
|
6653
|
+
optionalServicesReminder,
|
|
6175
6654
|
artifactReminder,
|
|
6655
|
+
longRunningReminder,
|
|
6176
6656
|
slackReplyContext,
|
|
6177
6657
|
expandedPrompt
|
|
6178
6658
|
].filter(Boolean).join("\n\n");
|
|
@@ -6192,7 +6672,7 @@ var Orchestrator = class {
|
|
|
6192
6672
|
}
|
|
6193
6673
|
const isBrightsy = fresh.agent === "brightsy";
|
|
6194
6674
|
const isOrchestration = isOrchestratorThread(fresh);
|
|
6195
|
-
const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-
|
|
6675
|
+
const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-WJDCJFSY.js");
|
|
6196
6676
|
const gitAuthMode = getGithubGitAuthMode();
|
|
6197
6677
|
const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
|
|
6198
6678
|
githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
|
|
@@ -6201,6 +6681,8 @@ var Orchestrator = class {
|
|
|
6201
6681
|
gitAuthMode
|
|
6202
6682
|
});
|
|
6203
6683
|
const artifactDirective = isBrightsy ? null : formatArtifactDirective();
|
|
6684
|
+
const longRunningDirective = isBrightsy || isOrchestration ? null : formatLongRunningDirective();
|
|
6685
|
+
const optionalServicesDirective = isBrightsy || isOrchestration ? null : formatOptionalServicesDirective(loadAppSettings().integrations);
|
|
6204
6686
|
const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
|
|
6205
6687
|
const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled() ? formatRenameBranchDirective(fresh, {
|
|
6206
6688
|
customPrompt: settings?.prompts?.renameBranch
|
|
@@ -6208,7 +6690,7 @@ var Orchestrator = class {
|
|
|
6208
6690
|
let seed = null;
|
|
6209
6691
|
if (!fresh.sessionId) {
|
|
6210
6692
|
const prior = fresh.messages.slice(0, -1);
|
|
6211
|
-
seed = isBrightsy ?
|
|
6693
|
+
seed = isBrightsy ? buildBrightsySessionSeed(prior) : buildSessionSeed(prior);
|
|
6212
6694
|
}
|
|
6213
6695
|
let coordinatorDirective = null;
|
|
6214
6696
|
if (isOrchestration) {
|
|
@@ -6230,7 +6712,9 @@ var Orchestrator = class {
|
|
|
6230
6712
|
const cachedPrefix = fresh.sessionId ? "" : [
|
|
6231
6713
|
coordinatorDirective,
|
|
6232
6714
|
worktreeDirective,
|
|
6715
|
+
optionalServicesDirective,
|
|
6233
6716
|
artifactDirective,
|
|
6717
|
+
longRunningDirective,
|
|
6234
6718
|
renameBranchDirective,
|
|
6235
6719
|
seed
|
|
6236
6720
|
].filter(Boolean).join("\n\n---\n\n");
|
|
@@ -6333,11 +6817,13 @@ var Orchestrator = class {
|
|
|
6333
6817
|
});
|
|
6334
6818
|
const retryThread = this.requireThread(threadId);
|
|
6335
6819
|
const prior = retryThread.messages.slice(0, -1);
|
|
6336
|
-
const retrySeed = buildSessionSeed(prior);
|
|
6820
|
+
const retrySeed = isBrightsy ? buildBrightsySessionSeed(prior) : buildSessionSeed(prior);
|
|
6337
6821
|
const retryPrefix = [
|
|
6338
6822
|
coordinatorDirective,
|
|
6339
6823
|
worktreeDirective,
|
|
6824
|
+
optionalServicesDirective,
|
|
6340
6825
|
artifactDirective,
|
|
6826
|
+
longRunningDirective,
|
|
6341
6827
|
renameBranchDirective,
|
|
6342
6828
|
retrySeed
|
|
6343
6829
|
].filter(Boolean).join("\n\n---\n\n");
|
|
@@ -7014,7 +7500,7 @@ var Orchestrator = class {
|
|
|
7014
7500
|
updateThread(thread.id, { title: meta.title });
|
|
7015
7501
|
}
|
|
7016
7502
|
}
|
|
7017
|
-
const { autoArchiveOnMergeEnabled } = await import("./app-settings-
|
|
7503
|
+
const { autoArchiveOnMergeEnabled } = await import("./app-settings-WJDCJFSY.js");
|
|
7018
7504
|
const latest = this.requireThread(thread.id);
|
|
7019
7505
|
if (!shouldAutoArchiveOnPrMerge({
|
|
7020
7506
|
previousPrState: prevState || null,
|
|
@@ -7324,7 +7810,7 @@ var Orchestrator = class {
|
|
|
7324
7810
|
this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
|
|
7325
7811
|
if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
|
|
7326
7812
|
try {
|
|
7327
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
7813
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-PU5YHQ7Z.js");
|
|
7328
7814
|
await ensureWorkspace2(thread.repoPath);
|
|
7329
7815
|
} catch {
|
|
7330
7816
|
}
|
|
@@ -7351,7 +7837,7 @@ var Orchestrator = class {
|
|
|
7351
7837
|
await runArchiveScript(thread.repoPath, thread.worktreePath);
|
|
7352
7838
|
} catch {
|
|
7353
7839
|
}
|
|
7354
|
-
const { deleteBranchOnPurgeEnabled } = await import("./app-settings-
|
|
7840
|
+
const { deleteBranchOnPurgeEnabled } = await import("./app-settings-WJDCJFSY.js");
|
|
7355
7841
|
const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
|
|
7356
7842
|
await removeWorktree(thread.repoPath, thread.worktreePath, {
|
|
7357
7843
|
deleteBranch: deleteBranch ? thread.branchName : void 0
|
|
@@ -7372,13 +7858,13 @@ var Orchestrator = class {
|
|
|
7372
7858
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
7373
7859
|
return restored2;
|
|
7374
7860
|
}
|
|
7375
|
-
if (!
|
|
7861
|
+
if (!existsSync16(thread.worktreePath)) {
|
|
7376
7862
|
if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
|
|
7377
7863
|
throw new Error(
|
|
7378
7864
|
`Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
|
|
7379
7865
|
);
|
|
7380
7866
|
}
|
|
7381
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-
|
|
7867
|
+
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-GKLPPNWR.js");
|
|
7382
7868
|
const slug = thread.worktreePath.split("/").pop();
|
|
7383
7869
|
const dest = thread.worktreePath;
|
|
7384
7870
|
await withRepoGitLock(thread.repoPath, async () => {
|
|
@@ -7486,7 +7972,7 @@ async function startOrchestration(opts) {
|
|
|
7486
7972
|
sourceRef: "default",
|
|
7487
7973
|
...createOpts
|
|
7488
7974
|
}).catch(async () => {
|
|
7489
|
-
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-
|
|
7975
|
+
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-GKLPPNWR.js");
|
|
7490
7976
|
const repo = await resolveRepoRoot2(repoPath);
|
|
7491
7977
|
const def = await resolveDefaultBranch2(repo);
|
|
7492
7978
|
return orch.createThread({
|
|
@@ -7557,6 +8043,7 @@ export {
|
|
|
7557
8043
|
estimateThreadChars,
|
|
7558
8044
|
estimateOccupancyTokens,
|
|
7559
8045
|
threadHasCompactedContext,
|
|
8046
|
+
forwardOccupancyTokens,
|
|
7560
8047
|
forwardContextUsage,
|
|
7561
8048
|
applyForwardOccupancy,
|
|
7562
8049
|
summarizeConversation,
|
|
@@ -7570,6 +8057,11 @@ export {
|
|
|
7570
8057
|
splitForCompaction,
|
|
7571
8058
|
formatMessagesAsTranscript,
|
|
7572
8059
|
buildSessionSeed,
|
|
8060
|
+
BRIGHTSY_SUMMARIZE_CONTEXT_TOOL,
|
|
8061
|
+
extractBrightsyContextSummary,
|
|
8062
|
+
findLastBrightsyContextSummary,
|
|
8063
|
+
messagesSinceLastBrightsyContextSummary,
|
|
8064
|
+
buildBrightsySessionSeed,
|
|
7573
8065
|
applyCompaction,
|
|
7574
8066
|
lastRequestOccupancy,
|
|
7575
8067
|
shouldResetSessionForOccupancy,
|
|
@@ -7675,18 +8167,38 @@ export {
|
|
|
7675
8167
|
getDiffSummary,
|
|
7676
8168
|
previewLand,
|
|
7677
8169
|
confirmLand,
|
|
8170
|
+
LONG_RUNNING_SKILL_COMMAND,
|
|
8171
|
+
BUNDLED_LONG_RUNNING_PATH,
|
|
8172
|
+
BUNDLED_SKILL_PREFIX,
|
|
7678
8173
|
discoverSkills,
|
|
7679
8174
|
readSkillBody,
|
|
7680
8175
|
expandComposerPrompt,
|
|
8176
|
+
packagedDetachedJobPath,
|
|
8177
|
+
resolveDetachedJobScript,
|
|
8178
|
+
formatDetachedJobInvoke,
|
|
7681
8179
|
formatRenameBranchDirective,
|
|
7682
8180
|
formatWorktreeDirective,
|
|
7683
8181
|
formatWorktreeReminder,
|
|
7684
8182
|
formatProcessGuideDirective,
|
|
8183
|
+
formatLongRunningDirective,
|
|
8184
|
+
formatLongRunningReminder,
|
|
7685
8185
|
formatArtifactDirective,
|
|
7686
8186
|
formatUiReminder,
|
|
7687
8187
|
loadAgentInstructions,
|
|
7688
8188
|
formatAgentInstructions,
|
|
7689
8189
|
withAgentInstructions,
|
|
8190
|
+
OPTIONAL_SERVICE_IDS,
|
|
8191
|
+
OPTIONAL_SERVICES,
|
|
8192
|
+
optionalServiceSpec,
|
|
8193
|
+
isOptionalServiceId,
|
|
8194
|
+
normalizeServiceOrigin,
|
|
8195
|
+
verifyOptionalService,
|
|
8196
|
+
connectOptionalService,
|
|
8197
|
+
disconnectOptionalService,
|
|
8198
|
+
optionalServiceConnected,
|
|
8199
|
+
connectedOptionalServices,
|
|
8200
|
+
formatOptionalServicesDirective,
|
|
8201
|
+
formatOptionalServicesReminder,
|
|
7690
8202
|
isPidAlive,
|
|
7691
8203
|
waitForPidExit,
|
|
7692
8204
|
Orchestrator,
|