@sideboard-ai/core 0.1.193 → 0.1.195

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.
Files changed (51) hide show
  1. package/dist/{abletime-UX53JTIH.js → abletime-HR6P4GJH.js} +3 -3
  2. package/dist/{abletime-QHKWGTBY.js → abletime-JINF5KJV.js} +3 -3
  3. package/dist/agents/cursor-runner.js +2 -2
  4. package/dist/{agents-MN4GSQTL.js → agents-OXDDPH2O.js} +16 -17
  5. package/dist/{agents-OK5VD25T.js → agents-WQL7SZY6.js} +6 -7
  6. package/dist/{app-settings-LO7UFHQ6.js → app-settings-5KHVKJKO.js} +2 -2
  7. package/dist/{app-settings-QYLNKRK4.js → app-settings-J775CYOO.js} +2 -2
  8. package/dist/{branch-prefix-EVW2KHGQ.js → branch-prefix-7DTIUJT2.js} +1 -1
  9. package/dist/{branch-prefix-7F37FIPX.js → branch-prefix-N3KTCRCE.js} +1 -1
  10. package/dist/{chunk-MQC2VA5U.js → chunk-2X6RGKLI.js} +13 -13
  11. package/dist/{chunk-EBPBGQ4P.js → chunk-6H2XPPPR.js} +9 -9
  12. package/dist/{chunk-HIX2VDRE.js → chunk-6YTI2HBG.js} +6 -8
  13. package/dist/{chunk-KORE4WOF.js → chunk-AH6WIBLY.js} +1 -1
  14. package/dist/{chunk-XPKKCFRT.js → chunk-DP4TKAMZ.js} +864 -7
  15. package/dist/{chunk-O4BMPY5Y.js → chunk-E37JHCO3.js} +1 -1
  16. package/dist/{chunk-JTWKGOFI.js → chunk-FJZWM2ON.js} +1 -1
  17. package/dist/{chunk-3DX2T2KR.js → chunk-GFH3QVAV.js} +188 -95
  18. package/dist/{chunk-EQXXGOMD.js → chunk-HAVI6AY3.js} +197 -104
  19. package/dist/{chunk-4OVCJPVI.js → chunk-HZTHWU6A.js} +6 -8
  20. package/dist/{chunk-4QJRH5DL.js → chunk-KTHRWHA3.js} +1 -1
  21. package/dist/{chunk-BIKCWYRP.js → chunk-S4QCBBXS.js} +873 -16
  22. package/dist/{chunk-BWRS6GAS.js → chunk-S5GLSRLP.js} +1 -1
  23. package/dist/{chunk-PE337IM2.js → chunk-SALM35KD.js} +7 -7
  24. package/dist/{chunk-7SNJMKD6.js → chunk-WDRMZKVB.js} +1 -1
  25. package/dist/{connected-teams-SYQQGCXD.js → connected-teams-WK47ATT7.js} +2 -2
  26. package/dist/{coordinator-prompt-WB65HIZ3.js → coordinator-prompt-IX66YUJ2.js} +5 -6
  27. package/dist/{coordinator-prompt-GXJIPWNA.js → coordinator-prompt-LYKS3YNG.js} +4 -5
  28. package/dist/{global-workspace-MMK5UFUZ.js → global-workspace-4QX6UEOY.js} +5 -6
  29. package/dist/{global-workspace-BZ6V6TEX.js → global-workspace-SNJ2Z6SL.js} +4 -5
  30. package/dist/index.cjs +1051 -971
  31. package/dist/index.d.cts +7 -5
  32. package/dist/index.d.ts +7 -5
  33. package/dist/index.js +95 -54
  34. package/dist/mcp/run-stdio.cjs +218 -140
  35. package/dist/mcp/run-stdio.js +67 -26
  36. package/dist/{orchestrator-4KI6VIFG.js → orchestrator-6ESBGMDN.js} +9 -10
  37. package/dist/{orchestrator-SFOT6Q6F.js → orchestrator-EHRFN3Q6.js} +8 -9
  38. package/dist/{workspaces-KPJS7GLY.js → workspaces-URMUVGAQ.js} +5 -6
  39. package/dist/{workspaces-ACMZMKPD.js → workspaces-UTOYGBYB.js} +4 -5
  40. package/dist/{worktree-6IEVF3GN.js → worktree-R43JOR4V.js} +12 -14
  41. package/dist/{worktree-UW6DNBIE.js → worktree-RM6UWNGZ.js} +17 -19
  42. package/package.json +1 -1
  43. package/dist/chunk-64QSHOKV.js +0 -869
  44. package/dist/chunk-ILWQT2UN.js +0 -55
  45. package/dist/chunk-PBEPPXMY.js +0 -57
  46. package/dist/chunk-RCWHL4TK.js +0 -866
  47. package/dist/github-DWROJOB7.js +0 -11
  48. package/dist/github-IIF7AQNO.js +0 -9
  49. package/dist/worktree-labels-3XDS6O3U.js +0 -22
  50. package/dist/worktree-labels-PEDOJBMD.js +0 -24
  51. package/dist/{chunk-7AJ7OZZF.js → chunk-NLHFAMC2.js} +3 -3
package/dist/index.cjs CHANGED
@@ -975,7 +975,7 @@ function sanitizeGitBranchPrefix(raw) {
975
975
  return slug || null;
976
976
  }
977
977
  function resolveGitBranchPrefix(opts) {
978
- return sanitizeGitBranchPrefix(opts.setting) ?? sanitizeGitBranchPrefix(opts.githubLogin);
978
+ return sanitizeGitBranchPrefix(opts.setting);
979
979
  }
980
980
  function examplePrefixedBranch(prefix, ticket) {
981
981
  const head = sanitizeGitBranchPrefix(prefix);
@@ -2956,762 +2956,10 @@ var init_thread_store = __esm({
2956
2956
  }
2957
2957
  });
2958
2958
 
2959
- // src/store/setup-log.ts
2960
- function setupLogPath(threadId) {
2961
- return (0, import_node_path11.join)(threadsDir(), `${threadId}.setup.log.json`);
2962
- }
2963
- function emptySetupLog() {
2964
- return { ...EMPTY };
2965
- }
2966
- function mergeSetupOutput(prev, incoming) {
2967
- if (!prev) return incoming;
2968
- if (!incoming) return prev;
2969
- if (prev === incoming) return prev;
2970
- if (prev.startsWith(incoming) || prev.endsWith(incoming)) return prev;
2971
- if (incoming.startsWith(prev) || incoming.endsWith(prev)) return incoming;
2972
- return incoming.length >= prev.length ? incoming : prev;
2973
- }
2974
- function clone(snap) {
2975
- return { ...snap };
2976
- }
2977
- function persistNow(threadId) {
2978
- const timer = persistTimers.get(threadId);
2979
- if (timer) {
2980
- clearTimeout(timer);
2981
- persistTimers.delete(threadId);
2982
- }
2983
- const snap = memory.get(threadId);
2984
- if (!snap) return;
2985
- try {
2986
- (0, import_node_fs10.writeFileSync)(setupLogPath(threadId), `${JSON.stringify(snap)}
2987
- `, "utf8");
2988
- } catch {
2989
- }
2990
- }
2991
- function schedulePersist(threadId) {
2992
- if (persistTimers.has(threadId)) return;
2993
- persistTimers.set(
2994
- threadId,
2995
- setTimeout(() => {
2996
- persistTimers.delete(threadId);
2997
- persistNow(threadId);
2998
- }, 100)
2999
- );
3000
- }
3001
- function loadFromDisk(threadId) {
3002
- const path2 = setupLogPath(threadId);
3003
- if (!(0, import_node_fs10.existsSync)(path2)) return null;
3004
- try {
3005
- const raw = JSON.parse((0, import_node_fs10.readFileSync)(path2, "utf8"));
3006
- return {
3007
- output: typeof raw.output === "string" ? raw.output : "",
3008
- running: Boolean(raw.running),
3009
- exitCode: typeof raw.exitCode === "number" ? raw.exitCode : null,
3010
- source: typeof raw.source === "string" ? raw.source : null
3011
- };
3012
- } catch {
3013
- return null;
3014
- }
3015
- }
3016
- function readSetupLog(threadId) {
3017
- const cached = memory.get(threadId);
3018
- if (cached) return clone(cached);
3019
- const disk = loadFromDisk(threadId);
3020
- if (disk) {
3021
- memory.set(threadId, disk);
3022
- return clone(disk);
3023
- }
3024
- return emptySetupLog();
3025
- }
3026
- function beginSetupLog(threadId) {
3027
- const snap = {
3028
- output: "",
3029
- running: true,
3030
- exitCode: null,
3031
- source: null
3032
- };
3033
- memory.set(threadId, snap);
3034
- persistNow(threadId);
3035
- return clone(snap);
3036
- }
3037
- function appendSetupLog(threadId, line) {
3038
- const current = memory.get(threadId) ?? readSetupLog(threadId);
3039
- const snap = {
3040
- ...current,
3041
- output: current.output ? `${current.output}
3042
- ${line}` : line,
3043
- running: true
3044
- };
3045
- memory.set(threadId, snap);
3046
- schedulePersist(threadId);
3047
- return clone(snap);
3048
- }
3049
- function finishSetupLog(threadId, exitCode, source) {
3050
- const current = memory.get(threadId) ?? readSetupLog(threadId);
3051
- const snap = {
3052
- ...current,
3053
- running: false,
3054
- exitCode,
3055
- source: source ?? current.source
3056
- };
3057
- memory.set(threadId, snap);
3058
- persistNow(threadId);
3059
- return clone(snap);
3060
- }
3061
- var import_node_fs10, import_node_path11, EMPTY, memory, persistTimers;
3062
- var init_setup_log = __esm({
3063
- "src/store/setup-log.ts"() {
3064
- "use strict";
3065
- import_node_fs10 = require("fs");
3066
- import_node_path11 = require("path");
3067
- init_paths();
3068
- EMPTY = {
3069
- output: "",
3070
- running: false,
3071
- exitCode: null,
3072
- source: null
3073
- };
3074
- memory = /* @__PURE__ */ new Map();
3075
- persistTimers = /* @__PURE__ */ new Map();
3076
- }
3077
- });
3078
-
3079
- // src/store/schedules.ts
3080
- function schedulesPath() {
3081
- return (0, import_node_path12.join)(appDataDir(), "schedules.json");
3082
- }
3083
- function parseDurationMs(every) {
3084
- const m = every.trim().match(DURATION_RE);
3085
- if (!m) return null;
3086
- const n = Number.parseInt(m[1], 10);
3087
- if (!Number.isFinite(n) || n <= 0) return null;
3088
- const unit = m[2].toLowerCase();
3089
- return n * UNIT_MS[unit];
3090
- }
3091
- function defaultScheduleName(prompt) {
3092
- const line = prompt.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
3093
- if (!line) return "Untitled schedule";
3094
- return line.length > 60 ? `${line.slice(0, 57).trimEnd()}\u2026` : line;
3095
- }
3096
- function resolveScheduleThreadId(raw, env = process.env) {
3097
- const value = raw?.trim() || "";
3098
- if (!value) return null;
3099
- if (value.toLowerCase() === "self") {
3100
- return env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
3101
- }
3102
- return value;
3103
- }
3104
- function computeNextRunAt(when, from = /* @__PURE__ */ new Date()) {
3105
- if (when.kind === "once") {
3106
- const at = new Date(when.at);
3107
- if (Number.isNaN(at.getTime())) {
3108
- throw new Error(`Invalid once-at datetime: ${when.at}`);
3109
- }
3110
- return at;
3111
- }
3112
- if (when.kind === "every") {
3113
- const ms = parseDurationMs(when.every);
3114
- if (!ms) {
3115
- throw new Error(`Invalid interval: ${when.every} (use 15m, 1h, 6h, 1d)`);
3116
- }
3117
- return new Date(from.getTime() + ms);
3118
- }
3119
- try {
3120
- const job = new import_croner.Cron(when.expr, {
3121
- timezone: when.tz?.trim() || void 0,
3122
- paused: true
3123
- });
3124
- const next = job.nextRun(from);
3125
- if (!next) {
3126
- throw new Error(`Cron expression has no next run: ${when.expr}`);
3127
- }
3128
- return next;
3129
- } catch (err) {
3130
- const message = err instanceof Error ? err.message : String(err);
3131
- throw new Error(`Invalid cron: ${message}`);
3132
- }
3133
- }
3134
- function formatScheduleWhen(when) {
3135
- if (when.kind === "once") return `once at ${when.at}`;
3136
- if (when.kind === "every") return `every ${when.every}`;
3137
- const tz = when.tz?.trim();
3138
- return tz ? `cron ${when.expr} (${tz})` : `cron ${when.expr}`;
3139
- }
3140
- function nowIso2(d = /* @__PURE__ */ new Date()) {
3141
- return d.toISOString();
3142
- }
3143
- function normalizeWhen(raw) {
3144
- if (!raw || typeof raw !== "object") {
3145
- throw new Error("Schedule is missing `when`");
3146
- }
3147
- const rec = raw;
3148
- const kind = rec.kind;
3149
- if (kind === "once" && typeof rec.at === "string") {
3150
- return { kind: "once", at: rec.at };
3151
- }
3152
- if (kind === "every" && typeof rec.every === "string") {
3153
- return { kind: "every", every: rec.every };
3154
- }
3155
- if (kind === "cron" && typeof rec.expr === "string") {
3156
- return {
3157
- kind: "cron",
3158
- expr: rec.expr,
3159
- tz: typeof rec.tz === "string" && rec.tz.trim() ? rec.tz.trim() : void 0
3160
- };
3161
- }
3162
- throw new Error("Schedule `when` must be once, every, or cron");
3163
- }
3164
- function normalizeTask(raw) {
3165
- const when = normalizeWhen(raw.when);
3166
- const agent = raw.agent && isOrchestratorCapableAgent(raw.agent) ? raw.agent : null;
3167
- return {
3168
- id: raw.id,
3169
- name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : defaultScheduleName(raw.prompt),
3170
- prompt: String(raw.prompt ?? ""),
3171
- enabled: Boolean(raw.enabled),
3172
- when,
3173
- threadId: raw.threadId?.trim() || null,
3174
- agent,
3175
- model: raw.model?.trim() || null,
3176
- nextRunAt: raw.nextRunAt,
3177
- lastRunAt: raw.lastRunAt ?? null,
3178
- lastThreadId: raw.lastThreadId ?? null,
3179
- lastError: raw.lastError ?? null,
3180
- createdBy: raw.createdBy === "mcp" || raw.createdBy === "cli" || raw.createdBy === "ui" ? raw.createdBy : "ui",
3181
- createdAt: raw.createdAt,
3182
- updatedAt: raw.updatedAt
3183
- };
3184
- }
3185
- function readAll() {
3186
- const path2 = schedulesPath();
3187
- if (!(0, import_node_fs11.existsSync)(path2)) return [];
3188
- try {
3189
- const parsed = JSON.parse((0, import_node_fs11.readFileSync)(path2, "utf8"));
3190
- if (!Array.isArray(parsed)) return [];
3191
- return parsed.map((row) => {
3192
- try {
3193
- return normalizeTask(row);
3194
- } catch {
3195
- return null;
3196
- }
3197
- }).filter((row) => Boolean(row?.id && row.prompt));
3198
- } catch {
3199
- return [];
3200
- }
3201
- }
3202
- function writeAll(rows) {
3203
- writePrivateFile(schedulesPath(), `${JSON.stringify(rows, null, 2)}
3204
- `);
3205
- }
3206
- function buildTask(input, now) {
3207
- const prompt = input.prompt.trim();
3208
- if (!prompt) throw new Error("Schedule prompt is required");
3209
- const when = normalizeWhen(input.when);
3210
- const nextRunAt = computeNextRunAt(when, now).toISOString();
3211
- const ts = nowIso2(now);
3212
- const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
3213
- return {
3214
- id: (0, import_node_crypto4.randomUUID)(),
3215
- name: input.name?.trim() || defaultScheduleName(prompt),
3216
- prompt,
3217
- enabled: input.enabled !== false,
3218
- when,
3219
- threadId: input.threadId?.trim() || null,
3220
- agent,
3221
- model: input.model?.trim() || null,
3222
- nextRunAt,
3223
- lastRunAt: null,
3224
- lastThreadId: null,
3225
- lastError: null,
3226
- createdBy: input.createdBy,
3227
- createdAt: ts,
3228
- updatedAt: ts
3229
- };
3230
- }
3231
- function listSchedules() {
3232
- return readAll().sort((a, b) => a.nextRunAt.localeCompare(b.nextRunAt));
3233
- }
3234
- function hasEnabledSchedules() {
3235
- return listSchedules().some((row) => row.enabled);
3236
- }
3237
- function getSchedule(id) {
3238
- const key = id.trim();
3239
- if (!key) return null;
3240
- return readAll().find(
3241
- (row) => row.id === key || row.id.startsWith(key) && key.length >= 8
3242
- ) ?? null;
3243
- }
3244
- function createSchedule(input) {
3245
- const task = buildTask(input, /* @__PURE__ */ new Date());
3246
- const rows = readAll();
3247
- rows.push(task);
3248
- writeAll(rows);
3249
- return task;
3250
- }
3251
- function updateSchedule(id, patch) {
3252
- const rows = readAll();
3253
- const idx = rows.findIndex((row) => row.id === id || id.length >= 8 && row.id.startsWith(id));
3254
- if (idx < 0) throw new Error(`Schedule not found: ${id}`);
3255
- const prev = rows[idx];
3256
- const when = patch.when ? normalizeWhen(patch.when) : prev.when;
3257
- const prompt = patch.prompt !== void 0 ? patch.prompt.trim() : prev.prompt;
3258
- if (!prompt) throw new Error("Schedule prompt is required");
3259
- const whenChanged = patch.when !== void 0;
3260
- const next = {
3261
- ...prev,
3262
- name: patch.name !== void 0 ? patch.name.trim() || defaultScheduleName(prompt) : prev.name,
3263
- prompt,
3264
- enabled: patch.enabled ?? prev.enabled,
3265
- when,
3266
- threadId: patch.threadId !== void 0 ? patch.threadId?.trim() || null : prev.threadId,
3267
- agent: patch.agent !== void 0 ? patch.agent ? coerceOrchestratorAgent(patch.agent) : null : prev.agent,
3268
- model: patch.model !== void 0 ? patch.model?.trim() || null : prev.model,
3269
- nextRunAt: whenChanged ? computeNextRunAt(when).toISOString() : prev.nextRunAt,
3270
- lastError: whenChanged ? null : prev.lastError,
3271
- updatedAt: nowIso2()
3272
- };
3273
- rows[idx] = next;
3274
- writeAll(rows);
3275
- return next;
3276
- }
3277
- function deleteSchedule(id) {
3278
- const rows = readAll();
3279
- const next = rows.filter(
3280
- (row) => row.id !== id && !(id.length >= 8 && row.id.startsWith(id))
3281
- );
3282
- if (next.length === rows.length) throw new Error(`Schedule not found: ${id}`);
3283
- writeAll(next);
3284
- }
3285
- function recordScheduleRun(id, result) {
3286
- const rows = readAll();
3287
- const idx = rows.findIndex((row) => row.id === id);
3288
- if (idx < 0) throw new Error(`Schedule not found: ${id}`);
3289
- const prev = rows[idx];
3290
- const firedAt = result.firedAt ?? /* @__PURE__ */ new Date();
3291
- const next = {
3292
- ...prev,
3293
- enabled: prev.when.kind === "once" ? false : prev.enabled,
3294
- nextRunAt: prev.when.kind === "once" ? prev.nextRunAt : computeNextRunAt(prev.when, firedAt).toISOString(),
3295
- lastRunAt: nowIso2(firedAt),
3296
- lastThreadId: result.lastThreadId !== void 0 ? result.lastThreadId : prev.lastThreadId,
3297
- lastError: result.lastError ?? null,
3298
- updatedAt: nowIso2(firedAt)
3299
- };
3300
- rows[idx] = next;
3301
- writeAll(rows);
3302
- return next;
3303
- }
3304
- var import_node_crypto4, import_node_fs11, import_node_path12, import_croner, DURATION_RE, UNIT_MS;
3305
- var init_schedules = __esm({
3306
- "src/store/schedules.ts"() {
3307
- "use strict";
3308
- import_node_crypto4 = require("crypto");
3309
- import_node_fs11 = require("fs");
3310
- import_node_path12 = require("path");
3311
- import_croner = require("croner");
3312
- init_orchestrator_capable();
3313
- init_paths();
3314
- init_private_file();
3315
- DURATION_RE = /^(\d+)(s|m|h|d)$/i;
3316
- UNIT_MS = {
3317
- s: 1e3,
3318
- m: 6e4,
3319
- h: 36e5,
3320
- d: 864e5
3321
- };
3322
- }
3323
- });
3324
-
3325
- // src/store/desktop-host.ts
3326
- function desktopHostPidPath() {
3327
- return (0, import_node_path13.join)(appDataDir(), "desktop-host.pid");
3328
- }
3329
- function claimDesktopHost(pid = process.pid) {
3330
- (0, import_node_fs12.writeFileSync)(desktopHostPidPath(), `${pid}
3331
- `, "utf8");
3332
- }
3333
- function releaseDesktopHost(pid = process.pid) {
3334
- if (readDesktopHostPid() !== pid) return;
3335
- try {
3336
- (0, import_node_fs12.unlinkSync)(desktopHostPidPath());
3337
- } catch {
3338
- }
3339
- }
3340
- function readDesktopHostPid() {
3341
- try {
3342
- const pid = Number.parseInt((0, import_node_fs12.readFileSync)(desktopHostPidPath(), "utf8").trim(), 10);
3343
- if (!Number.isFinite(pid) || pid <= 0) return null;
3344
- return pid;
3345
- } catch {
3346
- return null;
3347
- }
3348
- }
3349
- function pidAlive(pid) {
3350
- try {
3351
- process.kill(pid, 0);
3352
- return true;
3353
- } catch {
3354
- return false;
3355
- }
3356
- }
3357
- function isDesktopHostAlive() {
3358
- const pid = readDesktopHostPid();
3359
- return pid != null && pidAlive(pid);
3360
- }
3361
- function isThisProcessDesktopHost() {
3362
- return readDesktopHostPid() === process.pid && pidAlive(process.pid);
3363
- }
3364
- function thisProcessShouldDrainAgentQueues() {
3365
- if (isThisProcessDesktopHost()) return true;
3366
- return !isDesktopHostAlive();
3367
- }
3368
- var import_node_fs12, import_node_path13;
3369
- var init_desktop_host = __esm({
3370
- "src/store/desktop-host.ts"() {
3371
- "use strict";
3372
- import_node_fs12 = require("fs");
3373
- import_node_path13 = require("path");
3374
- init_paths();
3375
- }
3376
- });
3377
-
3378
- // src/brightsy/cloud-connect-constants.ts
3379
- function parseForceStopMessage(message) {
3380
- const normalized = message.replace(/\r\n/g, "\n");
3381
- const nl = normalized.indexOf("\n");
3382
- const firstLine = (nl === -1 ? normalized : normalized.slice(0, nl)).trim();
3383
- const remainder = (nl === -1 ? "" : normalized.slice(nl + 1)).trim();
3384
- const forceStop = firstLine.toLowerCase() === SIDEBOARD_FORCE_STOP.toLowerCase();
3385
- return { forceStop, remainder: forceStop ? remainder : message };
3386
- }
3387
- var CLOUD_ORCHESTRATOR_GOAL, SIDEBOARD_FORCE_STOP, CLOUD_COORDINATOR_BUSY_REPLY, CLOUD_COORDINATOR_STOPPED_REPLY, CLOUD_COORDINATOR_TIMEOUT_REPLY;
3388
- var init_cloud_connect_constants = __esm({
3389
- "src/brightsy/cloud-connect-constants.ts"() {
3390
- "use strict";
3391
- CLOUD_ORCHESTRATOR_GOAL = "Cloud-connected Sideboard orchestrator";
3392
- SIDEBOARD_FORCE_STOP = "SIDEBOARD_FORCE_STOP";
3393
- CLOUD_COORDINATOR_BUSY_REPLY = [
3394
- "Sideboard is busy with an in-progress client/desktop tool turn on the global coordinator.",
3395
- "I did not start a new orchestration turn.",
3396
- `To force-stop the in-progress turn, send another desktop request whose first line is exactly ${SIDEBOARD_FORCE_STOP}`,
3397
- "(optional follow-up request on later lines).",
3398
- "What do you want me to do? (retry later, wait until idle, force-stop, rephrase, or cancel)"
3399
- ].join(" ");
3400
- CLOUD_COORDINATOR_STOPPED_REPLY = [
3401
- "Sideboard force-stopped the in-progress global coordinator turn.",
3402
- "No new orchestration request was started."
3403
- ].join(" ");
3404
- CLOUD_COORDINATOR_TIMEOUT_REPLY = [
3405
- "Sideboard global coordinator timed out waiting for the local agent turn to finish.",
3406
- "I did not produce a full orchestration result.",
3407
- `To force-stop the in-progress turn, send another desktop request whose first line is exactly ${SIDEBOARD_FORCE_STOP}`,
3408
- "(optional follow-up request on later lines).",
3409
- "What do you want me to do? (retry later, wait, force-stop, rephrase, or cancel)"
3410
- ].join(" ");
3411
- }
3412
- });
3413
-
3414
- // src/paths/workspace-scratch.ts
3415
- function attachmentsGitignoreBody() {
3416
- return ATTACHMENTS_GITIGNORE;
3417
- }
3418
- function isWorkspaceScratchPath(relativePath) {
3419
- const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
3420
- return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === CLI_DIR || p.startsWith(`${CLI_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".sideboard" || p.startsWith(".sideboard/") || p === ".context" || p.startsWith(".context/");
3421
- }
3422
- var ATTACHMENTS_DIR, CLI_DIR, DETACHED_JOBS_DIR, LEGACY_ATTACHMENTS_DIR, LEGACY_DETACHED_JOBS_DIR, ATTACHMENTS_GITIGNORE;
3423
- var init_workspace_scratch = __esm({
3424
- "src/paths/workspace-scratch.ts"() {
3425
- "use strict";
3426
- ATTACHMENTS_DIR = ".context/attachments";
3427
- CLI_DIR = ".context/cli";
3428
- DETACHED_JOBS_DIR = ".context/.sideboard/detached-jobs";
3429
- LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
3430
- LEGACY_DETACHED_JOBS_DIR = ".sideboard/detached-jobs";
3431
- ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
3432
- *
3433
- !.gitignore
3434
- `;
3435
- }
3436
- });
3437
-
3438
- // src/composer/stage-files.ts
3439
- function fileExtension(filePath) {
3440
- const base = (0, import_node_path14.basename)(filePath).toLowerCase();
3441
- return base.includes(".") ? base.split(".").pop() || "" : "";
3442
- }
3443
- function isImageFilePath(filePath) {
3444
- return IMAGE_EXTENSIONS.has(fileExtension(filePath));
3445
- }
3446
- function imageMimeType(filePath) {
3447
- return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
3448
- }
3449
- function ensureAttachmentsDir(worktreePath) {
3450
- const dir = (0, import_node_path14.join)(worktreePath, ATTACHMENTS_DIR);
3451
- (0, import_node_fs13.mkdirSync)(dir, { recursive: true });
3452
- const gi = (0, import_node_path14.join)(dir, ".gitignore");
3453
- if (!(0, import_node_fs13.existsSync)(gi)) {
3454
- (0, import_node_fs13.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
3455
- }
3456
- return dir;
3457
- }
3458
- function uniqueAttachmentName(dir, originalName) {
3459
- const safe = originalName.replace(/[/\\]/g, "_") || "file";
3460
- if (!(0, import_node_fs13.existsSync)((0, import_node_path14.join)(dir, safe))) return safe;
3461
- const ext = (0, import_node_path14.extname)(safe);
3462
- const stem = ext ? safe.slice(0, -ext.length) : safe;
3463
- for (let i = 1; i < 1e4; i++) {
3464
- const candidate = `${stem}-${i}${ext}`;
3465
- if (!(0, import_node_fs13.existsSync)((0, import_node_path14.join)(dir, candidate))) return candidate;
3466
- }
3467
- return `${stem}-${(0, import_node_crypto5.randomUUID)()}${ext}`;
3468
- }
3469
- function previewDataUrlFromBuf(filePath, buf) {
3470
- if (!isImageFilePath(filePath)) return void 0;
3471
- if (buf.length > MAX_PREVIEW_BYTES) return void 0;
3472
- return `data:${imageMimeType(filePath)};base64,${buf.toString("base64")}`;
3473
- }
3474
- function attachmentFromBuffer(name, buf, opts) {
3475
- const previewDataUrl = previewDataUrlFromBuf(name, buf);
3476
- if (isImageFilePath(name)) {
3477
- const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
3478
- return {
3479
- id: (0, import_node_crypto5.randomUUID)(),
3480
- name,
3481
- kind: "file",
3482
- path: opts.path,
3483
- previewDataUrl,
3484
- content: [
3485
- `Image attached: ${pathHint}`,
3486
- opts.path ? `Use the Read tool on \`${opts.path}\` to view this image.` : "The image is shown in the composer; copy it into the worktree if you need to inspect pixels."
3487
- ].join("\n")
3488
- };
3489
- }
3490
- if (buf.length > MAX_INLINE_BYTES) {
3491
- return {
3492
- id: (0, import_node_crypto5.randomUUID)(),
3493
- name,
3494
- kind: "file",
3495
- path: opts.path,
3496
- content: opts.path ? `(file too large to attach inline: \`${opts.path}\`, ${buf.length} bytes \u2014 use the Read tool)` : `(file too large to attach inline: ${opts.sourceLabel || name}, ${buf.length} bytes)`
3497
- };
3498
- }
3499
- if (buf.includes(0)) {
3500
- return {
3501
- id: (0, import_node_crypto5.randomUUID)(),
3502
- name,
3503
- kind: "file",
3504
- path: opts.path,
3505
- content: opts.path ? `(binary file at \`${opts.path}\` \u2014 use tools to inspect)` : `(binary file attached by path only: ${opts.sourceLabel || name})`
3506
- };
3507
- }
3508
- return {
3509
- id: (0, import_node_crypto5.randomUUID)(),
3510
- name,
3511
- kind: "file",
3512
- path: opts.path,
3513
- content: buf.toString("utf8")
3514
- };
3515
- }
3516
- function attachmentFromAbsolutePath(absolutePath) {
3517
- const name = (0, import_node_path14.basename)(absolutePath);
3518
- try {
3519
- const st = (0, import_node_fs13.statSync)(absolutePath);
3520
- if (!st.isFile()) {
3521
- return {
3522
- id: (0, import_node_crypto5.randomUUID)(),
3523
- name,
3524
- kind: "file",
3525
- content: `(not a file: ${absolutePath})`
3526
- };
3527
- }
3528
- const buf = (0, import_node_fs13.readFileSync)(absolutePath);
3529
- return attachmentFromBuffer(name, buf, { sourceLabel: absolutePath });
3530
- } catch (err) {
3531
- return {
3532
- id: (0, import_node_crypto5.randomUUID)(),
3533
- name,
3534
- kind: "file",
3535
- content: `(could not read ${absolutePath}: ${err instanceof Error ? err.message : String(err)})`
3536
- };
3537
- }
3538
- }
3539
- function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
3540
- if (absolutePaths.length === 0) return [];
3541
- const dir = ensureAttachmentsDir(worktreePath);
3542
- const out = [];
3543
- for (const abs of absolutePaths) {
3544
- const originalName = (0, import_node_path14.basename)(abs);
3545
- try {
3546
- const st = (0, import_node_fs13.statSync)(abs);
3547
- if (!st.isFile()) continue;
3548
- const name = uniqueAttachmentName(dir, originalName);
3549
- const destAbs = (0, import_node_path14.join)(dir, name);
3550
- (0, import_node_fs13.copyFileSync)(abs, destAbs);
3551
- const rel = `${ATTACHMENTS_DIR}/${name}`;
3552
- const buf = (0, import_node_fs13.readFileSync)(destAbs);
3553
- out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
3554
- } catch (err) {
3555
- out.push({
3556
- id: (0, import_node_crypto5.randomUUID)(),
3557
- name: originalName,
3558
- kind: "file",
3559
- content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
3560
- });
3561
- }
3562
- }
3563
- return out;
3564
- }
3565
- function stageBuffersAsAttachments(worktreePath, buffers2) {
3566
- if (buffers2.length === 0) return [];
3567
- const dir = ensureAttachmentsDir(worktreePath);
3568
- const out = [];
3569
- for (const item of buffers2) {
3570
- const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
3571
- try {
3572
- const buf = Buffer.from(item.dataBase64, "base64");
3573
- const name = uniqueAttachmentName(dir, originalName);
3574
- const destAbs = (0, import_node_path14.join)(dir, name);
3575
- (0, import_node_fs13.writeFileSync)(destAbs, buf);
3576
- const rel = `${ATTACHMENTS_DIR}/${name}`;
3577
- out.push(attachmentFromBuffer(name, buf, { path: rel }));
3578
- } catch (err) {
3579
- out.push({
3580
- id: (0, import_node_crypto5.randomUUID)(),
3581
- name: originalName,
3582
- kind: "file",
3583
- content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
3584
- });
3585
- }
3586
- }
3587
- return out;
3588
- }
3589
- function attachmentsFromBuffers(buffers2) {
3590
- return buffers2.map((item) => {
3591
- const name = (item.name || "file").replace(/[/\\]/g, "_") || "file";
3592
- try {
3593
- const buf = Buffer.from(item.dataBase64, "base64");
3594
- return attachmentFromBuffer(name, buf, { sourceLabel: name });
3595
- } catch (err) {
3596
- return {
3597
- id: (0, import_node_crypto5.randomUUID)(),
3598
- name,
3599
- kind: "file",
3600
- content: `(could not attach ${name}: ${err instanceof Error ? err.message : String(err)})`
3601
- };
3602
- }
3603
- });
3604
- }
3605
- function isWorktreeRelativePath(p) {
3606
- if (!p || p.includes("..")) return false;
3607
- if (p.startsWith("/")) return false;
3608
- if (/^[A-Za-z]:[\\/]/.test(p)) return false;
3609
- return true;
3610
- }
3611
- function dataUrlToBase64(url) {
3612
- if (!url) return null;
3613
- const m = /^data:[^;]+;base64,(.+)$/s.exec(url);
3614
- return m?.[1] ?? null;
3615
- }
3616
- function persistPendingFileAttachments(worktreePath, attachments) {
3617
- if (attachments.length === 0) return attachments;
3618
- const keep = [];
3619
- const buffers2 = [];
3620
- for (const att of attachments) {
3621
- if (att.kind !== "file") {
3622
- keep.push(att);
3623
- continue;
3624
- }
3625
- if (att.path && isWorktreeRelativePath(att.path)) {
3626
- keep.push(att);
3627
- continue;
3628
- }
3629
- const fromPreview = dataUrlToBase64(att.previewDataUrl);
3630
- if (fromPreview) {
3631
- buffers2.push({ name: att.name, dataBase64: fromPreview });
3632
- continue;
3633
- }
3634
- if (att.content && !IMAGE_HINT_RE.test(att.content) && !PLACEHOLDER_CONTENT_RE.test(att.content)) {
3635
- buffers2.push({
3636
- name: att.name,
3637
- dataBase64: Buffer.from(att.content, "utf8").toString("base64")
3638
- });
3639
- continue;
3640
- }
3641
- keep.push(att);
3642
- }
3643
- if (buffers2.length === 0) return attachments;
3644
- return [...keep, ...stageBuffersAsAttachments(worktreePath, buffers2)];
3645
- }
3646
- function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
3647
- const out = [];
3648
- for (const rel of relativePaths) {
3649
- if (!rel || rel.includes("..") || rel.startsWith("/")) {
3650
- out.push({
3651
- id: (0, import_node_crypto5.randomUUID)(),
3652
- name: (0, import_node_path14.basename)(rel) || "file",
3653
- kind: "file",
3654
- content: `(invalid path: ${rel})`
3655
- });
3656
- continue;
3657
- }
3658
- const name = (0, import_node_path14.basename)(rel);
3659
- try {
3660
- const abs = (0, import_node_path14.join)(worktreePath, rel);
3661
- const st = (0, import_node_fs13.statSync)(abs);
3662
- if (!st.isFile()) continue;
3663
- const buf = (0, import_node_fs13.readFileSync)(abs);
3664
- out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
3665
- } catch (err) {
3666
- out.push({
3667
- id: (0, import_node_crypto5.randomUUID)(),
3668
- name,
3669
- kind: "file",
3670
- content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
3671
- });
3672
- }
3673
- }
3674
- return out;
3675
- }
3676
- var import_node_fs13, import_node_path14, import_node_crypto5, IMAGE_EXTENSIONS, IMAGE_MIME_BY_EXT, MAX_INLINE_BYTES, MAX_PREVIEW_BYTES, IMAGE_HINT_RE, PLACEHOLDER_CONTENT_RE;
3677
- var init_stage_files = __esm({
3678
- "src/composer/stage-files.ts"() {
3679
- "use strict";
3680
- import_node_fs13 = require("fs");
3681
- import_node_path14 = require("path");
3682
- import_node_crypto5 = require("crypto");
3683
- init_workspace_scratch();
3684
- IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
3685
- "png",
3686
- "jpg",
3687
- "jpeg",
3688
- "gif",
3689
- "webp",
3690
- "svg",
3691
- "bmp",
3692
- "ico"
3693
- ]);
3694
- IMAGE_MIME_BY_EXT = {
3695
- png: "image/png",
3696
- jpg: "image/jpeg",
3697
- jpeg: "image/jpeg",
3698
- gif: "image/gif",
3699
- webp: "image/webp",
3700
- svg: "image/svg+xml",
3701
- bmp: "image/bmp",
3702
- ico: "image/x-icon"
3703
- };
3704
- MAX_INLINE_BYTES = 4e5;
3705
- MAX_PREVIEW_BYTES = 5e6;
3706
- IMAGE_HINT_RE = /^Image attached:/;
3707
- PLACEHOLDER_CONTENT_RE = /^\((could not |file too large|binary file|not a file|invalid path)/;
3708
- }
3709
- });
3710
-
3711
- // src/git/team-meta.ts
3712
- var SOCCER_TEAM_META;
3713
- var init_team_meta = __esm({
3714
- "src/git/team-meta.ts"() {
2959
+ // src/git/team-meta.ts
2960
+ var SOCCER_TEAM_META;
2961
+ var init_team_meta = __esm({
2962
+ "src/git/team-meta.ts"() {
3715
2963
  "use strict";
3716
2964
  SOCCER_TEAM_META = {
3717
2965
  "aalborg": { location: "Aalborg, Denmark", league: "Danish Superliga" },
@@ -4502,89 +3750,835 @@ var init_teams = __esm({
4502
3750
  }
4503
3751
  });
4504
3752
 
4505
- // src/git/worktree-labels.ts
4506
- var worktree_labels_exports = {};
4507
- __export(worktree_labels_exports, {
4508
- branchDisplayLabel: () => branchDisplayLabel,
4509
- isPlaceholderBranch: () => isPlaceholderBranch,
4510
- normalizeWorktreePath: () => normalizeWorktreePath,
4511
- threadDisplayLabel: () => threadDisplayLabel,
4512
- ticketSlugForBranch: () => ticketSlugForBranch,
4513
- worktreeDisplayLabel: () => worktreeDisplayLabel,
4514
- worktreeDisplayLabelForGroup: () => worktreeDisplayLabelForGroup,
4515
- worktreeNameFromPath: () => worktreeNameFromPath,
4516
- worktreeSlugForTicket: () => worktreeSlugForTicket
4517
- });
4518
- function normalizeWorktreePath(worktreePath) {
4519
- const trimmed = (worktreePath ?? "").replace(/\/+$/, "") || "/";
4520
- const absolute = trimmed.startsWith("/");
4521
- const parts = trimmed.split("/").filter((p) => p && p !== ".");
4522
- const out = [];
4523
- for (const p of parts) {
4524
- if (p === "..") {
4525
- out.pop();
4526
- continue;
4527
- }
4528
- out.push(p);
3753
+ // src/git/worktree-labels.ts
3754
+ function normalizeWorktreePath(worktreePath) {
3755
+ const trimmed = (worktreePath ?? "").replace(/\/+$/, "") || "/";
3756
+ const absolute = trimmed.startsWith("/");
3757
+ const parts = trimmed.split("/").filter((p) => p && p !== ".");
3758
+ const out = [];
3759
+ for (const p of parts) {
3760
+ if (p === "..") {
3761
+ out.pop();
3762
+ continue;
3763
+ }
3764
+ out.push(p);
3765
+ }
3766
+ return (absolute ? "/" : "") + out.join("/");
3767
+ }
3768
+ function worktreeNameFromPath(worktreePath) {
3769
+ const normalized = worktreePath.replace(/\/+$/, "");
3770
+ const parts = normalized.split("/");
3771
+ return parts[parts.length - 1] || worktreePath;
3772
+ }
3773
+ function ticketSlugForBranch(sourceRef) {
3774
+ const raw = sourceRef.trim();
3775
+ if (!raw) return null;
3776
+ const linear = raw.match(/\b([A-Za-z]{2,8}-\d{1,6})\b/);
3777
+ if (linear) return linear[1].toLowerCase();
3778
+ const hashed = raw.match(/^#(\d{1,8})$/);
3779
+ if (hashed) return hashed[1];
3780
+ const digits = raw.match(/^(\d{1,8})$/);
3781
+ if (digits) return digits[1];
3782
+ const slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
3783
+ return slug || null;
3784
+ }
3785
+ function worktreeSlugForTicket(ticketRef, teamSlug) {
3786
+ const ticket = ticketSlugForBranch(ticketRef);
3787
+ const team = teamSlug.trim().replace(/^thread\//, "");
3788
+ if (!ticket) return team;
3789
+ if (!team) return ticket;
3790
+ return `${ticket}-${team}`;
3791
+ }
3792
+ function isPlaceholderBranch(branchName, worktreePath) {
3793
+ const branch = branchName.trim();
3794
+ if (!branch || branch === "HEAD") return true;
3795
+ if (branch.startsWith("thread/")) return true;
3796
+ const dir = worktreeNameFromPath(worktreePath).toLowerCase();
3797
+ return branch.toLowerCase() === dir;
3798
+ }
3799
+ function branchDisplayLabel(branchName, worktreePath) {
3800
+ const dir = worktreeNameFromPath(worktreePath);
3801
+ if (isPlaceholderBranch(branchName, worktreePath)) {
3802
+ const slug = branchName.replace(/^thread\//, "") || dir;
3803
+ return teamNameFromSlug(slug);
3804
+ }
3805
+ return branchName.trim();
3806
+ }
3807
+ function threadDisplayLabel(thread) {
3808
+ if (thread.userSetTitle && thread.title?.trim()) return thread.title.trim();
3809
+ if (thread.prTitle?.trim()) return thread.prTitle.trim();
3810
+ return branchDisplayLabel(thread.branchName, thread.worktreePath);
3811
+ }
3812
+ function worktreeDisplayLabel(thread) {
3813
+ return threadDisplayLabel(thread);
3814
+ }
3815
+ function worktreeDisplayLabelForGroup(threads) {
3816
+ if (threads.length === 0) return "Worktree";
3817
+ const canonical = [...threads].sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
3818
+ return threadDisplayLabel(canonical);
3819
+ }
3820
+ var init_worktree_labels = __esm({
3821
+ "src/git/worktree-labels.ts"() {
3822
+ "use strict";
3823
+ init_teams();
3824
+ }
3825
+ });
3826
+
3827
+ // src/store/setup-log.ts
3828
+ function setupLogPath(threadId) {
3829
+ return (0, import_node_path11.join)(threadsDir(), `${threadId}.setup.log.json`);
3830
+ }
3831
+ function setupLogKeyForWorktree(worktreePath) {
3832
+ const norm = normalizeWorktreePath(worktreePath);
3833
+ return `wt-${(0, import_node_crypto4.createHash)("sha1").update(norm).digest("hex").slice(0, 16)}`;
3834
+ }
3835
+ function emptySetupLog() {
3836
+ return { ...EMPTY };
3837
+ }
3838
+ function mergeSetupOutput(prev, incoming) {
3839
+ if (!prev) return incoming;
3840
+ if (!incoming) return prev;
3841
+ if (prev === incoming) return prev;
3842
+ if (prev.startsWith(incoming) || prev.endsWith(incoming)) return prev;
3843
+ if (incoming.startsWith(prev) || incoming.endsWith(prev)) return incoming;
3844
+ return incoming.length >= prev.length ? incoming : prev;
3845
+ }
3846
+ function clone(snap) {
3847
+ return { ...snap };
3848
+ }
3849
+ function persistNow(threadId) {
3850
+ const timer = persistTimers.get(threadId);
3851
+ if (timer) {
3852
+ clearTimeout(timer);
3853
+ persistTimers.delete(threadId);
3854
+ }
3855
+ const snap = memory.get(threadId);
3856
+ if (!snap) return;
3857
+ try {
3858
+ (0, import_node_fs10.writeFileSync)(setupLogPath(threadId), `${JSON.stringify(snap)}
3859
+ `, "utf8");
3860
+ } catch {
3861
+ }
3862
+ }
3863
+ function schedulePersist(threadId) {
3864
+ if (persistTimers.has(threadId)) return;
3865
+ persistTimers.set(
3866
+ threadId,
3867
+ setTimeout(() => {
3868
+ persistTimers.delete(threadId);
3869
+ persistNow(threadId);
3870
+ }, 100)
3871
+ );
3872
+ }
3873
+ function loadFromDisk(threadId) {
3874
+ const path2 = setupLogPath(threadId);
3875
+ if (!(0, import_node_fs10.existsSync)(path2)) return null;
3876
+ try {
3877
+ const raw = JSON.parse((0, import_node_fs10.readFileSync)(path2, "utf8"));
3878
+ return {
3879
+ output: typeof raw.output === "string" ? raw.output : "",
3880
+ running: Boolean(raw.running),
3881
+ exitCode: typeof raw.exitCode === "number" ? raw.exitCode : null,
3882
+ source: typeof raw.source === "string" ? raw.source : null
3883
+ };
3884
+ } catch {
3885
+ return null;
3886
+ }
3887
+ }
3888
+ function readSetupLog(threadId) {
3889
+ const cached = memory.get(threadId);
3890
+ if (cached) return clone(cached);
3891
+ const disk = loadFromDisk(threadId);
3892
+ if (disk) {
3893
+ memory.set(threadId, disk);
3894
+ return clone(disk);
3895
+ }
3896
+ return emptySetupLog();
3897
+ }
3898
+ function beginSetupLog(threadId) {
3899
+ const snap = {
3900
+ output: "",
3901
+ running: true,
3902
+ exitCode: null,
3903
+ source: null
3904
+ };
3905
+ memory.set(threadId, snap);
3906
+ persistNow(threadId);
3907
+ return clone(snap);
3908
+ }
3909
+ function appendSetupLog(threadId, line) {
3910
+ const current = memory.get(threadId) ?? readSetupLog(threadId);
3911
+ const snap = {
3912
+ ...current,
3913
+ output: current.output ? `${current.output}
3914
+ ${line}` : line,
3915
+ running: true
3916
+ };
3917
+ memory.set(threadId, snap);
3918
+ schedulePersist(threadId);
3919
+ return clone(snap);
3920
+ }
3921
+ function finishSetupLog(threadId, exitCode, source) {
3922
+ const current = memory.get(threadId) ?? readSetupLog(threadId);
3923
+ const snap = {
3924
+ ...current,
3925
+ running: false,
3926
+ exitCode,
3927
+ source: source ?? current.source
3928
+ };
3929
+ memory.set(threadId, snap);
3930
+ persistNow(threadId);
3931
+ return clone(snap);
3932
+ }
3933
+ var import_node_crypto4, import_node_fs10, import_node_path11, EMPTY, memory, persistTimers;
3934
+ var init_setup_log = __esm({
3935
+ "src/store/setup-log.ts"() {
3936
+ "use strict";
3937
+ import_node_crypto4 = require("crypto");
3938
+ import_node_fs10 = require("fs");
3939
+ import_node_path11 = require("path");
3940
+ init_worktree_labels();
3941
+ init_paths();
3942
+ EMPTY = {
3943
+ output: "",
3944
+ running: false,
3945
+ exitCode: null,
3946
+ source: null
3947
+ };
3948
+ memory = /* @__PURE__ */ new Map();
3949
+ persistTimers = /* @__PURE__ */ new Map();
3950
+ }
3951
+ });
3952
+
3953
+ // src/store/schedules.ts
3954
+ function schedulesPath() {
3955
+ return (0, import_node_path12.join)(appDataDir(), "schedules.json");
3956
+ }
3957
+ function parseDurationMs(every) {
3958
+ const m = every.trim().match(DURATION_RE);
3959
+ if (!m) return null;
3960
+ const n = Number.parseInt(m[1], 10);
3961
+ if (!Number.isFinite(n) || n <= 0) return null;
3962
+ const unit = m[2].toLowerCase();
3963
+ return n * UNIT_MS[unit];
3964
+ }
3965
+ function defaultScheduleName(prompt) {
3966
+ const line = prompt.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
3967
+ if (!line) return "Untitled schedule";
3968
+ return line.length > 60 ? `${line.slice(0, 57).trimEnd()}\u2026` : line;
3969
+ }
3970
+ function resolveScheduleThreadId(raw, env = process.env) {
3971
+ const value = raw?.trim() || "";
3972
+ if (!value) return null;
3973
+ if (value.toLowerCase() === "self") {
3974
+ return env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
3975
+ }
3976
+ return value;
3977
+ }
3978
+ function computeNextRunAt(when, from = /* @__PURE__ */ new Date()) {
3979
+ if (when.kind === "once") {
3980
+ const at = new Date(when.at);
3981
+ if (Number.isNaN(at.getTime())) {
3982
+ throw new Error(`Invalid once-at datetime: ${when.at}`);
3983
+ }
3984
+ return at;
3985
+ }
3986
+ if (when.kind === "every") {
3987
+ const ms = parseDurationMs(when.every);
3988
+ if (!ms) {
3989
+ throw new Error(`Invalid interval: ${when.every} (use 15m, 1h, 6h, 1d)`);
3990
+ }
3991
+ return new Date(from.getTime() + ms);
3992
+ }
3993
+ try {
3994
+ const job = new import_croner.Cron(when.expr, {
3995
+ timezone: when.tz?.trim() || void 0,
3996
+ paused: true
3997
+ });
3998
+ const next = job.nextRun(from);
3999
+ if (!next) {
4000
+ throw new Error(`Cron expression has no next run: ${when.expr}`);
4001
+ }
4002
+ return next;
4003
+ } catch (err) {
4004
+ const message = err instanceof Error ? err.message : String(err);
4005
+ throw new Error(`Invalid cron: ${message}`);
4006
+ }
4007
+ }
4008
+ function formatScheduleWhen(when) {
4009
+ if (when.kind === "once") return `once at ${when.at}`;
4010
+ if (when.kind === "every") return `every ${when.every}`;
4011
+ const tz = when.tz?.trim();
4012
+ return tz ? `cron ${when.expr} (${tz})` : `cron ${when.expr}`;
4013
+ }
4014
+ function nowIso2(d = /* @__PURE__ */ new Date()) {
4015
+ return d.toISOString();
4016
+ }
4017
+ function normalizeWhen(raw) {
4018
+ if (!raw || typeof raw !== "object") {
4019
+ throw new Error("Schedule is missing `when`");
4020
+ }
4021
+ const rec = raw;
4022
+ const kind = rec.kind;
4023
+ if (kind === "once" && typeof rec.at === "string") {
4024
+ return { kind: "once", at: rec.at };
4025
+ }
4026
+ if (kind === "every" && typeof rec.every === "string") {
4027
+ return { kind: "every", every: rec.every };
4028
+ }
4029
+ if (kind === "cron" && typeof rec.expr === "string") {
4030
+ return {
4031
+ kind: "cron",
4032
+ expr: rec.expr,
4033
+ tz: typeof rec.tz === "string" && rec.tz.trim() ? rec.tz.trim() : void 0
4034
+ };
4035
+ }
4036
+ throw new Error("Schedule `when` must be once, every, or cron");
4037
+ }
4038
+ function normalizeTask(raw) {
4039
+ const when = normalizeWhen(raw.when);
4040
+ const agent = raw.agent && isOrchestratorCapableAgent(raw.agent) ? raw.agent : null;
4041
+ return {
4042
+ id: raw.id,
4043
+ name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : defaultScheduleName(raw.prompt),
4044
+ prompt: String(raw.prompt ?? ""),
4045
+ enabled: Boolean(raw.enabled),
4046
+ when,
4047
+ threadId: raw.threadId?.trim() || null,
4048
+ agent,
4049
+ model: raw.model?.trim() || null,
4050
+ nextRunAt: raw.nextRunAt,
4051
+ lastRunAt: raw.lastRunAt ?? null,
4052
+ lastThreadId: raw.lastThreadId ?? null,
4053
+ lastError: raw.lastError ?? null,
4054
+ createdBy: raw.createdBy === "mcp" || raw.createdBy === "cli" || raw.createdBy === "ui" ? raw.createdBy : "ui",
4055
+ createdAt: raw.createdAt,
4056
+ updatedAt: raw.updatedAt
4057
+ };
4058
+ }
4059
+ function readAll() {
4060
+ const path2 = schedulesPath();
4061
+ if (!(0, import_node_fs11.existsSync)(path2)) return [];
4062
+ try {
4063
+ const parsed = JSON.parse((0, import_node_fs11.readFileSync)(path2, "utf8"));
4064
+ if (!Array.isArray(parsed)) return [];
4065
+ return parsed.map((row) => {
4066
+ try {
4067
+ return normalizeTask(row);
4068
+ } catch {
4069
+ return null;
4070
+ }
4071
+ }).filter((row) => Boolean(row?.id && row.prompt));
4072
+ } catch {
4073
+ return [];
4074
+ }
4075
+ }
4076
+ function writeAll(rows) {
4077
+ writePrivateFile(schedulesPath(), `${JSON.stringify(rows, null, 2)}
4078
+ `);
4079
+ }
4080
+ function buildTask(input, now) {
4081
+ const prompt = input.prompt.trim();
4082
+ if (!prompt) throw new Error("Schedule prompt is required");
4083
+ const when = normalizeWhen(input.when);
4084
+ const nextRunAt = computeNextRunAt(when, now).toISOString();
4085
+ const ts = nowIso2(now);
4086
+ const agent = input.agent ? coerceOrchestratorAgent(input.agent) : null;
4087
+ return {
4088
+ id: (0, import_node_crypto5.randomUUID)(),
4089
+ name: input.name?.trim() || defaultScheduleName(prompt),
4090
+ prompt,
4091
+ enabled: input.enabled !== false,
4092
+ when,
4093
+ threadId: input.threadId?.trim() || null,
4094
+ agent,
4095
+ model: input.model?.trim() || null,
4096
+ nextRunAt,
4097
+ lastRunAt: null,
4098
+ lastThreadId: null,
4099
+ lastError: null,
4100
+ createdBy: input.createdBy,
4101
+ createdAt: ts,
4102
+ updatedAt: ts
4103
+ };
4104
+ }
4105
+ function listSchedules() {
4106
+ return readAll().sort((a, b) => a.nextRunAt.localeCompare(b.nextRunAt));
4107
+ }
4108
+ function hasEnabledSchedules() {
4109
+ return listSchedules().some((row) => row.enabled);
4110
+ }
4111
+ function getSchedule(id) {
4112
+ const key = id.trim();
4113
+ if (!key) return null;
4114
+ return readAll().find(
4115
+ (row) => row.id === key || row.id.startsWith(key) && key.length >= 8
4116
+ ) ?? null;
4117
+ }
4118
+ function createSchedule(input) {
4119
+ const task = buildTask(input, /* @__PURE__ */ new Date());
4120
+ const rows = readAll();
4121
+ rows.push(task);
4122
+ writeAll(rows);
4123
+ return task;
4124
+ }
4125
+ function updateSchedule(id, patch) {
4126
+ const rows = readAll();
4127
+ const idx = rows.findIndex((row) => row.id === id || id.length >= 8 && row.id.startsWith(id));
4128
+ if (idx < 0) throw new Error(`Schedule not found: ${id}`);
4129
+ const prev = rows[idx];
4130
+ const when = patch.when ? normalizeWhen(patch.when) : prev.when;
4131
+ const prompt = patch.prompt !== void 0 ? patch.prompt.trim() : prev.prompt;
4132
+ if (!prompt) throw new Error("Schedule prompt is required");
4133
+ const whenChanged = patch.when !== void 0;
4134
+ const next = {
4135
+ ...prev,
4136
+ name: patch.name !== void 0 ? patch.name.trim() || defaultScheduleName(prompt) : prev.name,
4137
+ prompt,
4138
+ enabled: patch.enabled ?? prev.enabled,
4139
+ when,
4140
+ threadId: patch.threadId !== void 0 ? patch.threadId?.trim() || null : prev.threadId,
4141
+ agent: patch.agent !== void 0 ? patch.agent ? coerceOrchestratorAgent(patch.agent) : null : prev.agent,
4142
+ model: patch.model !== void 0 ? patch.model?.trim() || null : prev.model,
4143
+ nextRunAt: whenChanged ? computeNextRunAt(when).toISOString() : prev.nextRunAt,
4144
+ lastError: whenChanged ? null : prev.lastError,
4145
+ updatedAt: nowIso2()
4146
+ };
4147
+ rows[idx] = next;
4148
+ writeAll(rows);
4149
+ return next;
4150
+ }
4151
+ function deleteSchedule(id) {
4152
+ const rows = readAll();
4153
+ const next = rows.filter(
4154
+ (row) => row.id !== id && !(id.length >= 8 && row.id.startsWith(id))
4155
+ );
4156
+ if (next.length === rows.length) throw new Error(`Schedule not found: ${id}`);
4157
+ writeAll(next);
4158
+ }
4159
+ function recordScheduleRun(id, result) {
4160
+ const rows = readAll();
4161
+ const idx = rows.findIndex((row) => row.id === id);
4162
+ if (idx < 0) throw new Error(`Schedule not found: ${id}`);
4163
+ const prev = rows[idx];
4164
+ const firedAt = result.firedAt ?? /* @__PURE__ */ new Date();
4165
+ const next = {
4166
+ ...prev,
4167
+ enabled: prev.when.kind === "once" ? false : prev.enabled,
4168
+ nextRunAt: prev.when.kind === "once" ? prev.nextRunAt : computeNextRunAt(prev.when, firedAt).toISOString(),
4169
+ lastRunAt: nowIso2(firedAt),
4170
+ lastThreadId: result.lastThreadId !== void 0 ? result.lastThreadId : prev.lastThreadId,
4171
+ lastError: result.lastError ?? null,
4172
+ updatedAt: nowIso2(firedAt)
4173
+ };
4174
+ rows[idx] = next;
4175
+ writeAll(rows);
4176
+ return next;
4177
+ }
4178
+ var import_node_crypto5, import_node_fs11, import_node_path12, import_croner, DURATION_RE, UNIT_MS;
4179
+ var init_schedules = __esm({
4180
+ "src/store/schedules.ts"() {
4181
+ "use strict";
4182
+ import_node_crypto5 = require("crypto");
4183
+ import_node_fs11 = require("fs");
4184
+ import_node_path12 = require("path");
4185
+ import_croner = require("croner");
4186
+ init_orchestrator_capable();
4187
+ init_paths();
4188
+ init_private_file();
4189
+ DURATION_RE = /^(\d+)(s|m|h|d)$/i;
4190
+ UNIT_MS = {
4191
+ s: 1e3,
4192
+ m: 6e4,
4193
+ h: 36e5,
4194
+ d: 864e5
4195
+ };
4196
+ }
4197
+ });
4198
+
4199
+ // src/store/desktop-host.ts
4200
+ function desktopHostPidPath() {
4201
+ return (0, import_node_path13.join)(appDataDir(), "desktop-host.pid");
4202
+ }
4203
+ function claimDesktopHost(pid = process.pid) {
4204
+ (0, import_node_fs12.writeFileSync)(desktopHostPidPath(), `${pid}
4205
+ `, "utf8");
4206
+ }
4207
+ function releaseDesktopHost(pid = process.pid) {
4208
+ if (readDesktopHostPid() !== pid) return;
4209
+ try {
4210
+ (0, import_node_fs12.unlinkSync)(desktopHostPidPath());
4211
+ } catch {
4212
+ }
4213
+ }
4214
+ function readDesktopHostPid() {
4215
+ try {
4216
+ const pid = Number.parseInt((0, import_node_fs12.readFileSync)(desktopHostPidPath(), "utf8").trim(), 10);
4217
+ if (!Number.isFinite(pid) || pid <= 0) return null;
4218
+ return pid;
4219
+ } catch {
4220
+ return null;
4221
+ }
4222
+ }
4223
+ function pidAlive(pid) {
4224
+ try {
4225
+ process.kill(pid, 0);
4226
+ return true;
4227
+ } catch {
4228
+ return false;
4229
+ }
4230
+ }
4231
+ function isDesktopHostAlive() {
4232
+ const pid = readDesktopHostPid();
4233
+ return pid != null && pidAlive(pid);
4234
+ }
4235
+ function isThisProcessDesktopHost() {
4236
+ return readDesktopHostPid() === process.pid && pidAlive(process.pid);
4237
+ }
4238
+ function thisProcessShouldDrainAgentQueues() {
4239
+ if (isThisProcessDesktopHost()) return true;
4240
+ return !isDesktopHostAlive();
4241
+ }
4242
+ var import_node_fs12, import_node_path13;
4243
+ var init_desktop_host = __esm({
4244
+ "src/store/desktop-host.ts"() {
4245
+ "use strict";
4246
+ import_node_fs12 = require("fs");
4247
+ import_node_path13 = require("path");
4248
+ init_paths();
4249
+ }
4250
+ });
4251
+
4252
+ // src/brightsy/cloud-connect-constants.ts
4253
+ function parseForceStopMessage(message) {
4254
+ const normalized = message.replace(/\r\n/g, "\n");
4255
+ const nl = normalized.indexOf("\n");
4256
+ const firstLine = (nl === -1 ? normalized : normalized.slice(0, nl)).trim();
4257
+ const remainder = (nl === -1 ? "" : normalized.slice(nl + 1)).trim();
4258
+ const forceStop = firstLine.toLowerCase() === SIDEBOARD_FORCE_STOP.toLowerCase();
4259
+ return { forceStop, remainder: forceStop ? remainder : message };
4260
+ }
4261
+ var CLOUD_ORCHESTRATOR_GOAL, SIDEBOARD_FORCE_STOP, CLOUD_COORDINATOR_BUSY_REPLY, CLOUD_COORDINATOR_STOPPED_REPLY, CLOUD_COORDINATOR_TIMEOUT_REPLY;
4262
+ var init_cloud_connect_constants = __esm({
4263
+ "src/brightsy/cloud-connect-constants.ts"() {
4264
+ "use strict";
4265
+ CLOUD_ORCHESTRATOR_GOAL = "Cloud-connected Sideboard orchestrator";
4266
+ SIDEBOARD_FORCE_STOP = "SIDEBOARD_FORCE_STOP";
4267
+ CLOUD_COORDINATOR_BUSY_REPLY = [
4268
+ "Sideboard is busy with an in-progress client/desktop tool turn on the global coordinator.",
4269
+ "I did not start a new orchestration turn.",
4270
+ `To force-stop the in-progress turn, send another desktop request whose first line is exactly ${SIDEBOARD_FORCE_STOP}`,
4271
+ "(optional follow-up request on later lines).",
4272
+ "What do you want me to do? (retry later, wait until idle, force-stop, rephrase, or cancel)"
4273
+ ].join(" ");
4274
+ CLOUD_COORDINATOR_STOPPED_REPLY = [
4275
+ "Sideboard force-stopped the in-progress global coordinator turn.",
4276
+ "No new orchestration request was started."
4277
+ ].join(" ");
4278
+ CLOUD_COORDINATOR_TIMEOUT_REPLY = [
4279
+ "Sideboard global coordinator timed out waiting for the local agent turn to finish.",
4280
+ "I did not produce a full orchestration result.",
4281
+ `To force-stop the in-progress turn, send another desktop request whose first line is exactly ${SIDEBOARD_FORCE_STOP}`,
4282
+ "(optional follow-up request on later lines).",
4283
+ "What do you want me to do? (retry later, wait, force-stop, rephrase, or cancel)"
4284
+ ].join(" ");
4285
+ }
4286
+ });
4287
+
4288
+ // src/paths/workspace-scratch.ts
4289
+ function attachmentsGitignoreBody() {
4290
+ return ATTACHMENTS_GITIGNORE;
4291
+ }
4292
+ function isWorkspaceScratchPath(relativePath) {
4293
+ const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
4294
+ return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === CLI_DIR || p.startsWith(`${CLI_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".sideboard" || p.startsWith(".sideboard/") || p === ".context" || p.startsWith(".context/");
4295
+ }
4296
+ var ATTACHMENTS_DIR, CLI_DIR, DETACHED_JOBS_DIR, LEGACY_ATTACHMENTS_DIR, LEGACY_DETACHED_JOBS_DIR, ATTACHMENTS_GITIGNORE;
4297
+ var init_workspace_scratch = __esm({
4298
+ "src/paths/workspace-scratch.ts"() {
4299
+ "use strict";
4300
+ ATTACHMENTS_DIR = ".context/attachments";
4301
+ CLI_DIR = ".context/cli";
4302
+ DETACHED_JOBS_DIR = ".context/.sideboard/detached-jobs";
4303
+ LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
4304
+ LEGACY_DETACHED_JOBS_DIR = ".sideboard/detached-jobs";
4305
+ ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
4306
+ *
4307
+ !.gitignore
4308
+ `;
4309
+ }
4310
+ });
4311
+
4312
+ // src/composer/stage-files.ts
4313
+ function fileExtension(filePath) {
4314
+ const base = (0, import_node_path14.basename)(filePath).toLowerCase();
4315
+ return base.includes(".") ? base.split(".").pop() || "" : "";
4316
+ }
4317
+ function isImageFilePath(filePath) {
4318
+ return IMAGE_EXTENSIONS.has(fileExtension(filePath));
4319
+ }
4320
+ function imageMimeType(filePath) {
4321
+ return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
4322
+ }
4323
+ function ensureAttachmentsDir(worktreePath) {
4324
+ const dir = (0, import_node_path14.join)(worktreePath, ATTACHMENTS_DIR);
4325
+ (0, import_node_fs13.mkdirSync)(dir, { recursive: true });
4326
+ const gi = (0, import_node_path14.join)(dir, ".gitignore");
4327
+ if (!(0, import_node_fs13.existsSync)(gi)) {
4328
+ (0, import_node_fs13.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
4529
4329
  }
4530
- return (absolute ? "/" : "") + out.join("/");
4330
+ return dir;
4531
4331
  }
4532
- function worktreeNameFromPath(worktreePath) {
4533
- const normalized = worktreePath.replace(/\/+$/, "");
4534
- const parts = normalized.split("/");
4535
- return parts[parts.length - 1] || worktreePath;
4332
+ function uniqueAttachmentName(dir, originalName) {
4333
+ const safe = originalName.replace(/[/\\]/g, "_") || "file";
4334
+ if (!(0, import_node_fs13.existsSync)((0, import_node_path14.join)(dir, safe))) return safe;
4335
+ const ext = (0, import_node_path14.extname)(safe);
4336
+ const stem = ext ? safe.slice(0, -ext.length) : safe;
4337
+ for (let i = 1; i < 1e4; i++) {
4338
+ const candidate = `${stem}-${i}${ext}`;
4339
+ if (!(0, import_node_fs13.existsSync)((0, import_node_path14.join)(dir, candidate))) return candidate;
4340
+ }
4341
+ return `${stem}-${(0, import_node_crypto6.randomUUID)()}${ext}`;
4536
4342
  }
4537
- function ticketSlugForBranch(sourceRef) {
4538
- const raw = sourceRef.trim();
4539
- if (!raw) return null;
4540
- const linear = raw.match(/\b([A-Za-z]{2,8}-\d{1,6})\b/);
4541
- if (linear) return linear[1].toLowerCase();
4542
- const hashed = raw.match(/^#(\d{1,8})$/);
4543
- if (hashed) return hashed[1];
4544
- const digits = raw.match(/^(\d{1,8})$/);
4545
- if (digits) return digits[1];
4546
- const slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
4547
- return slug || null;
4343
+ function previewDataUrlFromBuf(filePath, buf) {
4344
+ if (!isImageFilePath(filePath)) return void 0;
4345
+ if (buf.length > MAX_PREVIEW_BYTES) return void 0;
4346
+ return `data:${imageMimeType(filePath)};base64,${buf.toString("base64")}`;
4548
4347
  }
4549
- function worktreeSlugForTicket(ticketRef, teamSlug) {
4550
- const ticket = ticketSlugForBranch(ticketRef);
4551
- const team = teamSlug.trim().replace(/^thread\//, "");
4552
- if (!ticket) return team;
4553
- if (!team) return ticket;
4554
- return `${ticket}-${team}`;
4348
+ function attachmentFromBuffer(name, buf, opts) {
4349
+ const previewDataUrl = previewDataUrlFromBuf(name, buf);
4350
+ if (isImageFilePath(name)) {
4351
+ const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
4352
+ return {
4353
+ id: (0, import_node_crypto6.randomUUID)(),
4354
+ name,
4355
+ kind: "file",
4356
+ path: opts.path,
4357
+ previewDataUrl,
4358
+ content: [
4359
+ `Image attached: ${pathHint}`,
4360
+ opts.path ? `Use the Read tool on \`${opts.path}\` to view this image.` : "The image is shown in the composer; copy it into the worktree if you need to inspect pixels."
4361
+ ].join("\n")
4362
+ };
4363
+ }
4364
+ if (buf.length > MAX_INLINE_BYTES) {
4365
+ return {
4366
+ id: (0, import_node_crypto6.randomUUID)(),
4367
+ name,
4368
+ kind: "file",
4369
+ path: opts.path,
4370
+ content: opts.path ? `(file too large to attach inline: \`${opts.path}\`, ${buf.length} bytes \u2014 use the Read tool)` : `(file too large to attach inline: ${opts.sourceLabel || name}, ${buf.length} bytes)`
4371
+ };
4372
+ }
4373
+ if (buf.includes(0)) {
4374
+ return {
4375
+ id: (0, import_node_crypto6.randomUUID)(),
4376
+ name,
4377
+ kind: "file",
4378
+ path: opts.path,
4379
+ content: opts.path ? `(binary file at \`${opts.path}\` \u2014 use tools to inspect)` : `(binary file attached by path only: ${opts.sourceLabel || name})`
4380
+ };
4381
+ }
4382
+ return {
4383
+ id: (0, import_node_crypto6.randomUUID)(),
4384
+ name,
4385
+ kind: "file",
4386
+ path: opts.path,
4387
+ content: buf.toString("utf8")
4388
+ };
4555
4389
  }
4556
- function isPlaceholderBranch(branchName, worktreePath) {
4557
- const branch = branchName.trim();
4558
- if (!branch || branch === "HEAD") return true;
4559
- if (branch.startsWith("thread/")) return true;
4560
- const dir = worktreeNameFromPath(worktreePath).toLowerCase();
4561
- return branch.toLowerCase() === dir;
4390
+ function attachmentFromAbsolutePath(absolutePath) {
4391
+ const name = (0, import_node_path14.basename)(absolutePath);
4392
+ try {
4393
+ const st = (0, import_node_fs13.statSync)(absolutePath);
4394
+ if (!st.isFile()) {
4395
+ return {
4396
+ id: (0, import_node_crypto6.randomUUID)(),
4397
+ name,
4398
+ kind: "file",
4399
+ content: `(not a file: ${absolutePath})`
4400
+ };
4401
+ }
4402
+ const buf = (0, import_node_fs13.readFileSync)(absolutePath);
4403
+ return attachmentFromBuffer(name, buf, { sourceLabel: absolutePath });
4404
+ } catch (err) {
4405
+ return {
4406
+ id: (0, import_node_crypto6.randomUUID)(),
4407
+ name,
4408
+ kind: "file",
4409
+ content: `(could not read ${absolutePath}: ${err instanceof Error ? err.message : String(err)})`
4410
+ };
4411
+ }
4562
4412
  }
4563
- function branchDisplayLabel(branchName, worktreePath) {
4564
- const dir = worktreeNameFromPath(worktreePath);
4565
- if (isPlaceholderBranch(branchName, worktreePath)) {
4566
- const slug = branchName.replace(/^thread\//, "") || dir;
4567
- return teamNameFromSlug(slug);
4413
+ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
4414
+ if (absolutePaths.length === 0) return [];
4415
+ const dir = ensureAttachmentsDir(worktreePath);
4416
+ const out = [];
4417
+ for (const abs of absolutePaths) {
4418
+ const originalName = (0, import_node_path14.basename)(abs);
4419
+ try {
4420
+ const st = (0, import_node_fs13.statSync)(abs);
4421
+ if (!st.isFile()) continue;
4422
+ const name = uniqueAttachmentName(dir, originalName);
4423
+ const destAbs = (0, import_node_path14.join)(dir, name);
4424
+ (0, import_node_fs13.copyFileSync)(abs, destAbs);
4425
+ const rel = `${ATTACHMENTS_DIR}/${name}`;
4426
+ const buf = (0, import_node_fs13.readFileSync)(destAbs);
4427
+ out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
4428
+ } catch (err) {
4429
+ out.push({
4430
+ id: (0, import_node_crypto6.randomUUID)(),
4431
+ name: originalName,
4432
+ kind: "file",
4433
+ content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
4434
+ });
4435
+ }
4568
4436
  }
4569
- return branchName.trim();
4437
+ return out;
4570
4438
  }
4571
- function threadDisplayLabel(thread) {
4572
- if (thread.userSetTitle && thread.title?.trim()) return thread.title.trim();
4573
- if (thread.prTitle?.trim()) return thread.prTitle.trim();
4574
- return branchDisplayLabel(thread.branchName, thread.worktreePath);
4439
+ function stageBuffersAsAttachments(worktreePath, buffers2) {
4440
+ if (buffers2.length === 0) return [];
4441
+ const dir = ensureAttachmentsDir(worktreePath);
4442
+ const out = [];
4443
+ for (const item of buffers2) {
4444
+ const originalName = (item.name || "file").replace(/[/\\]/g, "_") || "file";
4445
+ try {
4446
+ const buf = Buffer.from(item.dataBase64, "base64");
4447
+ const name = uniqueAttachmentName(dir, originalName);
4448
+ const destAbs = (0, import_node_path14.join)(dir, name);
4449
+ (0, import_node_fs13.writeFileSync)(destAbs, buf);
4450
+ const rel = `${ATTACHMENTS_DIR}/${name}`;
4451
+ out.push(attachmentFromBuffer(name, buf, { path: rel }));
4452
+ } catch (err) {
4453
+ out.push({
4454
+ id: (0, import_node_crypto6.randomUUID)(),
4455
+ name: originalName,
4456
+ kind: "file",
4457
+ content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
4458
+ });
4459
+ }
4460
+ }
4461
+ return out;
4575
4462
  }
4576
- function worktreeDisplayLabel(thread) {
4577
- return threadDisplayLabel(thread);
4463
+ function attachmentsFromBuffers(buffers2) {
4464
+ return buffers2.map((item) => {
4465
+ const name = (item.name || "file").replace(/[/\\]/g, "_") || "file";
4466
+ try {
4467
+ const buf = Buffer.from(item.dataBase64, "base64");
4468
+ return attachmentFromBuffer(name, buf, { sourceLabel: name });
4469
+ } catch (err) {
4470
+ return {
4471
+ id: (0, import_node_crypto6.randomUUID)(),
4472
+ name,
4473
+ kind: "file",
4474
+ content: `(could not attach ${name}: ${err instanceof Error ? err.message : String(err)})`
4475
+ };
4476
+ }
4477
+ });
4478
+ }
4479
+ function isWorktreeRelativePath(p) {
4480
+ if (!p || p.includes("..")) return false;
4481
+ if (p.startsWith("/")) return false;
4482
+ if (/^[A-Za-z]:[\\/]/.test(p)) return false;
4483
+ return true;
4484
+ }
4485
+ function dataUrlToBase64(url) {
4486
+ if (!url) return null;
4487
+ const m = /^data:[^;]+;base64,(.+)$/s.exec(url);
4488
+ return m?.[1] ?? null;
4489
+ }
4490
+ function persistPendingFileAttachments(worktreePath, attachments) {
4491
+ if (attachments.length === 0) return attachments;
4492
+ const keep = [];
4493
+ const buffers2 = [];
4494
+ for (const att of attachments) {
4495
+ if (att.kind !== "file") {
4496
+ keep.push(att);
4497
+ continue;
4498
+ }
4499
+ if (att.path && isWorktreeRelativePath(att.path)) {
4500
+ keep.push(att);
4501
+ continue;
4502
+ }
4503
+ const fromPreview = dataUrlToBase64(att.previewDataUrl);
4504
+ if (fromPreview) {
4505
+ buffers2.push({ name: att.name, dataBase64: fromPreview });
4506
+ continue;
4507
+ }
4508
+ if (att.content && !IMAGE_HINT_RE.test(att.content) && !PLACEHOLDER_CONTENT_RE.test(att.content)) {
4509
+ buffers2.push({
4510
+ name: att.name,
4511
+ dataBase64: Buffer.from(att.content, "utf8").toString("base64")
4512
+ });
4513
+ continue;
4514
+ }
4515
+ keep.push(att);
4516
+ }
4517
+ if (buffers2.length === 0) return attachments;
4518
+ return [...keep, ...stageBuffersAsAttachments(worktreePath, buffers2)];
4578
4519
  }
4579
- function worktreeDisplayLabelForGroup(threads) {
4580
- if (threads.length === 0) return "Worktree";
4581
- const canonical = [...threads].sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
4582
- return threadDisplayLabel(canonical);
4520
+ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
4521
+ const out = [];
4522
+ for (const rel of relativePaths) {
4523
+ if (!rel || rel.includes("..") || rel.startsWith("/")) {
4524
+ out.push({
4525
+ id: (0, import_node_crypto6.randomUUID)(),
4526
+ name: (0, import_node_path14.basename)(rel) || "file",
4527
+ kind: "file",
4528
+ content: `(invalid path: ${rel})`
4529
+ });
4530
+ continue;
4531
+ }
4532
+ const name = (0, import_node_path14.basename)(rel);
4533
+ try {
4534
+ const abs = (0, import_node_path14.join)(worktreePath, rel);
4535
+ const st = (0, import_node_fs13.statSync)(abs);
4536
+ if (!st.isFile()) continue;
4537
+ const buf = (0, import_node_fs13.readFileSync)(abs);
4538
+ out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
4539
+ } catch (err) {
4540
+ out.push({
4541
+ id: (0, import_node_crypto6.randomUUID)(),
4542
+ name,
4543
+ kind: "file",
4544
+ content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
4545
+ });
4546
+ }
4547
+ }
4548
+ return out;
4583
4549
  }
4584
- var init_worktree_labels = __esm({
4585
- "src/git/worktree-labels.ts"() {
4550
+ var import_node_fs13, import_node_path14, import_node_crypto6, IMAGE_EXTENSIONS, IMAGE_MIME_BY_EXT, MAX_INLINE_BYTES, MAX_PREVIEW_BYTES, IMAGE_HINT_RE, PLACEHOLDER_CONTENT_RE;
4551
+ var init_stage_files = __esm({
4552
+ "src/composer/stage-files.ts"() {
4586
4553
  "use strict";
4587
- init_teams();
4554
+ import_node_fs13 = require("fs");
4555
+ import_node_path14 = require("path");
4556
+ import_node_crypto6 = require("crypto");
4557
+ init_workspace_scratch();
4558
+ IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
4559
+ "png",
4560
+ "jpg",
4561
+ "jpeg",
4562
+ "gif",
4563
+ "webp",
4564
+ "svg",
4565
+ "bmp",
4566
+ "ico"
4567
+ ]);
4568
+ IMAGE_MIME_BY_EXT = {
4569
+ png: "image/png",
4570
+ jpg: "image/jpeg",
4571
+ jpeg: "image/jpeg",
4572
+ gif: "image/gif",
4573
+ webp: "image/webp",
4574
+ svg: "image/svg+xml",
4575
+ bmp: "image/bmp",
4576
+ ico: "image/x-icon"
4577
+ };
4578
+ MAX_INLINE_BYTES = 4e5;
4579
+ MAX_PREVIEW_BYTES = 5e6;
4580
+ IMAGE_HINT_RE = /^Image attached:/;
4581
+ PLACEHOLDER_CONTENT_RE = /^\((could not |file too large|binary file|not a file|invalid path)/;
4588
4582
  }
4589
4583
  });
4590
4584
 
@@ -7294,7 +7288,7 @@ async function createOrUpdatePr(worktreePath, opts) {
7294
7288
  const bodyText = clampGithubPrBody(opts.body ?? opts.title);
7295
7289
  const bodyFile = (0, import_node_path20.join)(
7296
7290
  (0, import_node_os6.tmpdir)(),
7297
- `sideboard-pr-body-${process.pid}-${(0, import_node_crypto6.randomBytes)(6).toString("hex")}.md`
7291
+ `sideboard-pr-body-${process.pid}-${(0, import_node_crypto7.randomBytes)(6).toString("hex")}.md`
7298
7292
  );
7299
7293
  (0, import_node_fs18.writeFileSync)(bodyFile, bodyText.endsWith("\n") ? bodyText : `${bodyText}
7300
7294
  `, "utf8");
@@ -7435,11 +7429,11 @@ function allocateTeamSlug(repoPath) {
7435
7429
  }
7436
7430
  throw new Error("No available soccer team worktree directories left");
7437
7431
  }
7438
- var import_node_crypto6, import_node_fs18, import_node_os6, import_node_path20, PR_DETAILS_JSON_FIELDS;
7432
+ var import_node_crypto7, import_node_fs18, import_node_os6, import_node_path20, PR_DETAILS_JSON_FIELDS;
7439
7433
  var init_worktree = __esm({
7440
7434
  "src/git/worktree.ts"() {
7441
7435
  "use strict";
7442
- import_node_crypto6 = require("crypto");
7436
+ import_node_crypto7 = require("crypto");
7443
7437
  import_node_fs18 = require("fs");
7444
7438
  import_node_os6 = require("os");
7445
7439
  import_node_path20 = require("path");
@@ -14805,6 +14799,59 @@ var init_context_compact = __esm({
14805
14799
  }
14806
14800
  });
14807
14801
 
14802
+ // src/threads/worktree-runtime.ts
14803
+ function worktreeProcessPrefix(worktreePath) {
14804
+ return `wt:${normalizeWorktreePath(worktreePath)}`;
14805
+ }
14806
+ function worktreeSetupProcessKey(worktreePath) {
14807
+ return `${worktreeProcessPrefix(worktreePath)}:setup`;
14808
+ }
14809
+ function worktreeRunProcessKey(worktreePath, scriptName) {
14810
+ return `${worktreeProcessPrefix(worktreePath)}:run:${scriptName}`;
14811
+ }
14812
+ function worktreeDevProcessKey(worktreePath) {
14813
+ return `${worktreeProcessPrefix(worktreePath)}:dev`;
14814
+ }
14815
+ function isWorktreeRunProcessKey(key, worktreePath) {
14816
+ const prefix = worktreeProcessPrefix(worktreePath);
14817
+ return key.startsWith(`${prefix}:run:`) || key === `${prefix}:dev`;
14818
+ }
14819
+ function pickRichestSetupLog(snaps) {
14820
+ let best;
14821
+ for (const snap of snaps) {
14822
+ if (!snap.output && !snap.running && snap.exitCode == null) continue;
14823
+ if (!best) {
14824
+ best = snap;
14825
+ continue;
14826
+ }
14827
+ if (snap.running && !best.running) {
14828
+ best = snap;
14829
+ continue;
14830
+ }
14831
+ if (snap.running === best.running && snap.output.length > best.output.length) {
14832
+ best = snap;
14833
+ }
14834
+ }
14835
+ return best;
14836
+ }
14837
+ function mergeWorktreeActiveRuns(threads) {
14838
+ const byName = /* @__PURE__ */ new Map();
14839
+ let devPort = null;
14840
+ for (const thread of threads) {
14841
+ if (thread.devPort != null && devPort == null) devPort = thread.devPort;
14842
+ for (const run2 of thread.activeRuns ?? []) {
14843
+ if (!byName.has(run2.scriptName)) byName.set(run2.scriptName, run2);
14844
+ }
14845
+ }
14846
+ return { activeRuns: [...byName.values()], devPort };
14847
+ }
14848
+ var init_worktree_runtime = __esm({
14849
+ "src/threads/worktree-runtime.ts"() {
14850
+ "use strict";
14851
+ init_worktree_labels();
14852
+ }
14853
+ });
14854
+
14808
14855
  // src/threads/chat-tabs.ts
14809
14856
  function sameWorktreePath(a, b) {
14810
14857
  return normalizeWorktreePath(a) === normalizeWorktreePath(b);
@@ -14867,7 +14914,7 @@ function forkMessageSlice(from, throughIndex) {
14867
14914
  function buildForkTranscriptAttachment(baseTitle, messages) {
14868
14915
  const title = baseTitle || "Chat";
14869
14916
  return {
14870
- id: (0, import_node_crypto7.randomUUID)(),
14917
+ id: (0, import_node_crypto8.randomUUID)(),
14871
14918
  name: `Transcript of ${title}.md`,
14872
14919
  kind: "transcript",
14873
14920
  content: formatTranscriptMarkdown(title, messages)
@@ -14901,7 +14948,8 @@ function createChatTab(input) {
14901
14948
  planMode: from.planMode,
14902
14949
  autonomy: input.autonomy ?? from.autonomy,
14903
14950
  attachments: input.attachments ?? [],
14904
- status: "idle"
14951
+ status: "idle",
14952
+ ...mergeWorktreeActiveRuns(threadsSharingWorktree(binding.worktreePath))
14905
14953
  });
14906
14954
  writeThread(thread);
14907
14955
  return thread;
@@ -14924,17 +14972,18 @@ function forkChatTab(input) {
14924
14972
  }
14925
14973
  return tab;
14926
14974
  }
14927
- var import_node_crypto7;
14975
+ var import_node_crypto8;
14928
14976
  var init_chat_tabs = __esm({
14929
14977
  "src/threads/chat-tabs.ts"() {
14930
14978
  "use strict";
14931
- import_node_crypto7 = require("crypto");
14979
+ import_node_crypto8 = require("crypto");
14932
14980
  init_context_compact();
14933
14981
  init_teams();
14934
14982
  init_worktree_labels();
14935
14983
  init_global_workspace();
14936
14984
  init_orchestrator_capable();
14937
14985
  init_thread_store();
14986
+ init_worktree_runtime();
14938
14987
  init_worktree_labels();
14939
14988
  }
14940
14989
  });
@@ -15194,7 +15243,7 @@ function buildReviewRequestAttachment(content, opts) {
15194
15243
  const path2 = opts?.path ?? REVIEW_SKILL_PATH;
15195
15244
  const name = opts?.name ?? (path2 === REVIEW_SKILL_PATH ? REVIEW_SKILL_NAME : path2 === REPO_REVIEW_PATH || path2 === CONTEXT_REVIEW_PATH ? REPO_REVIEW_NAME : REVIEW_REQUEST_NAME);
15196
15245
  return {
15197
- id: (0, import_node_crypto8.randomUUID)(),
15246
+ id: (0, import_node_crypto9.randomUUID)(),
15198
15247
  name,
15199
15248
  kind: "file",
15200
15249
  path: path2,
@@ -15229,11 +15278,11 @@ async function requestReview(threadRef, send2) {
15229
15278
  const started = await send2(tab.id, REVIEW_REQUEST_PREFILL);
15230
15279
  return { tab: started, from };
15231
15280
  }
15232
- var import_node_crypto8, import_node_fs38, import_node_path38, REPO_REVIEW_PATH, REPO_REVIEW_NAME, CONTEXT_REVIEW_PATH, REVIEW_REQUEST_PATH, LEGACY_REVIEW_REQUEST_PATH, REVIEW_REQUEST_NAME, REVIEW_REQUEST_PREFILL, LEGACY_REVIEW_TEMPLATE_MARKERS;
15281
+ var import_node_crypto9, import_node_fs38, import_node_path38, REPO_REVIEW_PATH, REPO_REVIEW_NAME, CONTEXT_REVIEW_PATH, REVIEW_REQUEST_PATH, LEGACY_REVIEW_REQUEST_PATH, REVIEW_REQUEST_NAME, REVIEW_REQUEST_PREFILL, LEGACY_REVIEW_TEMPLATE_MARKERS;
15233
15282
  var init_request_review = __esm({
15234
15283
  "src/review/request-review.ts"() {
15235
15284
  "use strict";
15236
- import_node_crypto8 = require("crypto");
15285
+ import_node_crypto9 = require("crypto");
15237
15286
  import_node_fs38 = require("fs");
15238
15287
  import_node_path38 = require("path");
15239
15288
  init_global_workspace();
@@ -18104,7 +18153,7 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
18104
18153
  `- Do not wait on the limited ${from.agent} account; keep going on ${fallbackAgent}.`
18105
18154
  ].join("\n");
18106
18155
  return {
18107
- id: (0, import_node_crypto9.randomUUID)(),
18156
+ id: (0, import_node_crypto10.randomUUID)(),
18108
18157
  name: "Orchestration quota handoff.md",
18109
18158
  kind: "transcript",
18110
18159
  content: body
@@ -18125,11 +18174,11 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
18125
18174
  sourceType: "orchestration"
18126
18175
  });
18127
18176
  }
18128
- var import_node_crypto9, QUOTA_CONTINUE_PROMPT, QUOTA_RESUME_PROMPT;
18177
+ var import_node_crypto10, QUOTA_CONTINUE_PROMPT, QUOTA_RESUME_PROMPT;
18129
18178
  var init_quota_failover = __esm({
18130
18179
  "src/orchestrator/quota-failover.ts"() {
18131
18180
  "use strict";
18132
- import_node_crypto9 = require("crypto");
18181
+ import_node_crypto10 = require("crypto");
18133
18182
  init_session_quota();
18134
18183
  init_app_settings();
18135
18184
  init_global_workspace();
@@ -20785,64 +20834,6 @@ var init_sync_branch = __esm({
20785
20834
  }
20786
20835
  });
20787
20836
 
20788
- // src/integrations/github.ts
20789
- var github_exports = {};
20790
- __export(github_exports, {
20791
- getGitHubStatus: () => getGitHubStatus,
20792
- refreshGitHubAuth: () => refreshGitHubAuth
20793
- });
20794
- async function getGitHubStatus() {
20795
- const which = await run("which", ["gh"], { reject: false });
20796
- if (which.exitCode !== 0 || !which.stdout.trim()) {
20797
- return {
20798
- connected: false,
20799
- login: null,
20800
- hosts: [],
20801
- reason: "gh CLI not found on PATH"
20802
- };
20803
- }
20804
- const loginResult = await run(
20805
- "gh",
20806
- ["api", "user", "--jq", ".login"],
20807
- { reject: false }
20808
- );
20809
- if (loginResult.exitCode === 0 && loginResult.stdout.trim()) {
20810
- return {
20811
- connected: true,
20812
- login: loginResult.stdout.trim(),
20813
- hosts: ["github.com"],
20814
- reason: null
20815
- };
20816
- }
20817
- const status = await run("gh", ["auth", "status"], { reject: false });
20818
- const text7 = `${status.stdout}
20819
- ${status.stderr}`;
20820
- const loginMatch = text7.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text7.match(/Logged in to ([^\s]+) as (\S+)/i);
20821
- if (loginMatch) {
20822
- return {
20823
- connected: true,
20824
- login: loginMatch[2].replace(/['"]/g, ""),
20825
- hosts: [loginMatch[1]],
20826
- reason: null
20827
- };
20828
- }
20829
- return {
20830
- connected: false,
20831
- login: null,
20832
- hosts: [],
20833
- reason: status.stderr.trim() || status.stdout.trim() || "Not logged in to GitHub (run: gh auth login)"
20834
- };
20835
- }
20836
- async function refreshGitHubAuth() {
20837
- return getGitHubStatus();
20838
- }
20839
- var init_github = __esm({
20840
- "src/integrations/github.ts"() {
20841
- "use strict";
20842
- init_run();
20843
- }
20844
- });
20845
-
20846
20837
  // src/agents/cursor-store.ts
20847
20838
  function cursorSdkStoreDir(threadId) {
20848
20839
  const root = (0, import_node_path49.join)(appDataDir(), CURSOR_SDK_STORE_DIR);
@@ -21109,6 +21100,7 @@ var init_orchestrator = __esm({
21109
21100
  init_setup_last_error();
21110
21101
  init_request_review();
21111
21102
  init_fork_worktree();
21103
+ init_worktree_runtime();
21112
21104
  init_quota_failover();
21113
21105
  init_adopt();
21114
21106
  init_stack_layers();
@@ -21907,21 +21899,11 @@ var init_orchestrator = __esm({
21907
21899
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
21908
21900
  let renameBranchDirective = null;
21909
21901
  if (!isBrightsy && !isOrchestration && autoRenameBranchEnabled2()) {
21910
- const { isPlaceholderBranch: isPlaceholderBranch2 } = await Promise.resolve().then(() => (init_worktree_labels(), worktree_labels_exports));
21911
- let githubLogin = null;
21912
- if (isPlaceholderBranch2(fresh.branchName, fresh.worktreePath) && !gitBranchPrefixSetting2()) {
21913
- try {
21914
- const { getGitHubStatus: getGitHubStatus2 } = await Promise.resolve().then(() => (init_github(), github_exports));
21915
- githubLogin = (await getGitHubStatus2()).login;
21916
- } catch {
21917
- }
21918
- }
21919
21902
  const { resolveGitBranchPrefix: resolveGitBranchPrefix2 } = await Promise.resolve().then(() => (init_branch_prefix(), branch_prefix_exports));
21920
21903
  renameBranchDirective = formatRenameBranchDirective(fresh, {
21921
21904
  customPrompt: settings?.prompts?.renameBranch,
21922
21905
  branchPrefix: resolveGitBranchPrefix2({
21923
- setting: gitBranchPrefixSetting2(),
21924
- githubLogin
21906
+ setting: gitBranchPrefixSetting2()
21925
21907
  })
21926
21908
  });
21927
21909
  }
@@ -22284,22 +22266,25 @@ var init_orchestrator = __esm({
22284
22266
  "No run script found for this worktree. Add [scripts.run.*] in the worktree\u2019s .sideboard/settings.toml or .conductor/settings.toml (or settings.local.toml on the main repo)."
22285
22267
  );
22286
22268
  }
22287
- const runKey = `${thread.id}:run:${resolvedName}`;
22288
- const existing = this.processes.get(runKey);
22289
- const active = (thread.activeRuns ?? []).find((r) => r.scriptName === resolvedName);
22269
+ const siblings = threadsSharingWorktree(thread.worktreePath);
22270
+ const shared = mergeWorktreeActiveRuns(siblings);
22271
+ const runKey = worktreeRunProcessKey(thread.worktreePath, resolvedName);
22272
+ const existing = this.processes.get(runKey) ?? siblings.map((t) => this.processes.get(`${t.id}:run:${resolvedName}`)).find(Boolean);
22273
+ const active = shared.activeRuns.find((r) => r.scriptName === resolvedName);
22290
22274
  if (existing && active) {
22291
22275
  return { port: active.port, scriptName: resolvedName, ports: active.ports };
22292
22276
  }
22293
22277
  if (!scriptName) {
22294
- const legacy = this.processes.get(`${thread.id}:dev`);
22295
- if (legacy && thread.devPort) {
22296
- return { port: thread.devPort, scriptName: resolvedName, ports: [thread.devPort] };
22278
+ const legacy = this.processes.get(worktreeDevProcessKey(thread.worktreePath)) ?? siblings.map((t) => this.processes.get(`${t.id}:dev`)).find(Boolean);
22279
+ if (legacy && shared.devPort) {
22280
+ return { port: shared.devPort, scriptName: resolvedName, ports: [shared.devPort] };
22297
22281
  }
22298
22282
  }
22299
22283
  const mode = getRunMode(thread.worktreePath, thread.repoPath);
22300
22284
  if (mode === "nonconcurrent") {
22285
+ const here = normalizeWorktreePath(thread.worktreePath);
22301
22286
  for (const t of listThreads()) {
22302
- if (t.id === thread.id) continue;
22287
+ if (normalizeWorktreePath(t.worktreePath) === here) continue;
22303
22288
  const runs = t.activeRuns ?? [];
22304
22289
  if (runs.length > 0 || t.devPort) {
22305
22290
  throw new Error(
@@ -22334,7 +22319,7 @@ var init_orchestrator = __esm({
22334
22319
  });
22335
22320
  const isDefault = scripts.find((s) => s.default)?.name === resolvedName || !scripts.some((s) => s.default) && (resolvedName === "dev" || resolvedName === scripts[0]?.name);
22336
22321
  if (isDefault) {
22337
- this.processes.set(`${thread.id}:dev`, {
22322
+ this.processes.set(worktreeDevProcessKey(thread.worktreePath), {
22338
22323
  kind: "dev",
22339
22324
  pid: handle.pid,
22340
22325
  startedAt,
@@ -22349,13 +22334,14 @@ var init_orchestrator = __esm({
22349
22334
  startedAt
22350
22335
  };
22351
22336
  const nextRuns = [
22352
- ...(thread.activeRuns ?? []).filter((r) => r.scriptName !== resolvedName),
22337
+ ...shared.activeRuns.filter((r) => r.scriptName !== resolvedName),
22353
22338
  run2
22354
22339
  ];
22355
- updateThread(thread.id, {
22356
- activeRuns: nextRuns,
22357
- devPort: isDefault ? handle.port : thread.devPort
22358
- });
22340
+ this.syncWorktreeRuns(
22341
+ thread.worktreePath,
22342
+ nextRuns,
22343
+ isDefault ? handle.port : shared.devPort
22344
+ );
22359
22345
  this.emit({
22360
22346
  type: "dev_server_started",
22361
22347
  threadId: thread.id,
@@ -22364,15 +22350,14 @@ var init_orchestrator = __esm({
22364
22350
  });
22365
22351
  void handle.done.then(() => {
22366
22352
  this.processes.delete(runKey);
22367
- if (isDefault) this.processes.delete(`${thread.id}:dev`);
22368
- const latest = readThread(thread.id);
22369
- const remaining = (latest?.activeRuns ?? []).filter(
22370
- (r) => r.scriptName !== resolvedName
22353
+ if (isDefault) this.processes.delete(worktreeDevProcessKey(thread.worktreePath));
22354
+ const latest = mergeWorktreeActiveRuns(threadsSharingWorktree(thread.worktreePath));
22355
+ const remaining = latest.activeRuns.filter((r) => r.scriptName !== resolvedName);
22356
+ this.syncWorktreeRuns(
22357
+ thread.worktreePath,
22358
+ remaining,
22359
+ isDefault ? null : latest.devPort
22371
22360
  );
22372
- updateThread(thread.id, {
22373
- activeRuns: remaining,
22374
- devPort: isDefault ? null : latest?.devPort ?? null
22375
- });
22376
22361
  this.emit({
22377
22362
  type: "dev_server_stopped",
22378
22363
  threadId: thread.id,
@@ -22383,51 +22368,82 @@ var init_orchestrator = __esm({
22383
22368
  }
22384
22369
  stopDev(threadRef, scriptName) {
22385
22370
  const thread = this.requireThread(threadRef);
22371
+ const siblings = threadsSharingWorktree(thread.worktreePath);
22372
+ const shared = mergeWorktreeActiveRuns(siblings);
22386
22373
  if (scriptName) {
22387
- const runKey = `${thread.id}:run:${scriptName}`;
22388
- const proc = this.processes.get(runKey);
22389
- if (proc) proc.kill();
22390
- this.processes.delete(runKey);
22391
- const remaining = (thread.activeRuns ?? []).filter((r) => r.scriptName !== scriptName);
22392
- const isPrimary = thread.devPort != null && thread.activeRuns?.find((r) => r.scriptName === scriptName)?.port === thread.devPort;
22393
- updateThread(thread.id, {
22394
- activeRuns: remaining,
22395
- devPort: isPrimary ? null : thread.devPort
22396
- });
22374
+ const keys = [
22375
+ worktreeRunProcessKey(thread.worktreePath, scriptName),
22376
+ ...siblings.map((t) => `${t.id}:run:${scriptName}`)
22377
+ ];
22378
+ for (const key of keys) {
22379
+ const proc = this.processes.get(key);
22380
+ if (proc) proc.kill();
22381
+ this.processes.delete(key);
22382
+ }
22383
+ const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
22384
+ const isPrimary = shared.devPort != null && shared.activeRuns.find((r) => r.scriptName === scriptName)?.port === shared.devPort;
22385
+ this.syncWorktreeRuns(
22386
+ thread.worktreePath,
22387
+ remaining,
22388
+ isPrimary ? null : shared.devPort
22389
+ );
22397
22390
  this.emit({ type: "dev_server_stopped", threadId: thread.id, scriptName });
22398
22391
  return;
22399
22392
  }
22400
22393
  for (const [key, proc] of [...this.processes.entries()]) {
22401
- if (key.startsWith(`${thread.id}:run:`) || key === `${thread.id}:dev`) {
22394
+ if (isWorktreeRunProcessKey(key, thread.worktreePath) || siblings.some((t) => key.startsWith(`${t.id}:run:`) || key === `${t.id}:dev`)) {
22402
22395
  proc.kill();
22403
22396
  this.processes.delete(key);
22404
22397
  }
22405
22398
  }
22406
- updateThread(thread.id, { activeRuns: [], devPort: null });
22399
+ this.syncWorktreeRuns(thread.worktreePath, [], null);
22407
22400
  this.emit({ type: "dev_server_stopped", threadId: thread.id });
22408
22401
  }
22402
+ syncWorktreeRuns(worktreePath, activeRuns, devPort) {
22403
+ for (const sibling of threadsSharingWorktree(worktreePath)) {
22404
+ updateThread(sibling.id, { activeRuns, devPort });
22405
+ }
22406
+ }
22409
22407
  listThreadRunScripts(threadRef) {
22410
22408
  const thread = this.requireThread(threadRef);
22411
22409
  return listRunScripts(thread.worktreePath, thread.repoPath);
22412
22410
  }
22413
22411
  getActiveRuns(threadRef) {
22414
- return this.requireThread(threadRef).activeRuns ?? [];
22412
+ const thread = this.requireThread(threadRef);
22413
+ return mergeWorktreeActiveRuns(threadsSharingWorktree(thread.worktreePath)).activeRuns;
22415
22414
  }
22416
22415
  getSetupLog(threadRef) {
22417
22416
  const thread = this.getThread(threadRef);
22418
22417
  if (!thread) return readSetupLog(threadRef);
22419
- const snap = readSetupLog(thread.id);
22420
- if (snap.running && !this.processes.has(`${thread.id}:setup`)) {
22418
+ const siblingIds = [
22419
+ thread.id,
22420
+ ...threadsSharingWorktree(thread.worktreePath).map((t) => t.id)
22421
+ ];
22422
+ const snap = pickRichestSetupLog([
22423
+ readSetupLog(setupLogKeyForWorktree(thread.worktreePath)),
22424
+ ...[...new Set(siblingIds)].map((id) => readSetupLog(id))
22425
+ ]) ?? readSetupLog(thread.id);
22426
+ if (snap.running && !this.hasWorktreeSetupProcess(thread.worktreePath)) {
22421
22427
  return { ...snap, running: false };
22422
22428
  }
22423
22429
  return snap;
22424
22430
  }
22431
+ hasWorktreeSetupProcess(worktreePath) {
22432
+ if (this.processes.has(worktreeSetupProcessKey(worktreePath))) return true;
22433
+ return threadsSharingWorktree(worktreePath).some(
22434
+ (t) => this.processes.has(`${t.id}:setup`)
22435
+ );
22436
+ }
22437
+ writeSetupLog(threadId, worktreePath, fn) {
22438
+ fn(setupLogKeyForWorktree(worktreePath));
22439
+ fn(threadId);
22440
+ }
22425
22441
  async runSetup(threadRef, opts) {
22426
22442
  const thread = this.requireThread(threadRef);
22427
22443
  this.assertNotGlobal(thread, "Setup");
22428
- const key = `${thread.id}:setup`;
22429
- if (this.processes.has(key)) {
22430
- throw new Error("Setup already running for this thread");
22444
+ const key = worktreeSetupProcessKey(thread.worktreePath);
22445
+ if (this.hasWorktreeSetupProcess(thread.worktreePath)) {
22446
+ throw new Error("Setup already running for this worktree");
22431
22447
  }
22432
22448
  const abort = new AbortController();
22433
22449
  this.processes.set(key, {
@@ -22435,13 +22451,17 @@ var init_orchestrator = __esm({
22435
22451
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
22436
22452
  kill: () => abort.abort()
22437
22453
  });
22438
- beginSetupLog(thread.id);
22454
+ this.writeSetupLog(thread.id, thread.worktreePath, beginSetupLog);
22439
22455
  this.emit({ type: "setup_started", threadId: thread.id });
22440
22456
  let finished = false;
22441
22457
  const finish = (exitCode, source) => {
22442
22458
  if (finished) return;
22443
22459
  finished = true;
22444
- finishSetupLog(thread.id, exitCode, source);
22460
+ this.writeSetupLog(
22461
+ thread.id,
22462
+ thread.worktreePath,
22463
+ (logKey) => finishSetupLog(logKey, exitCode, source)
22464
+ );
22445
22465
  this.emit({ type: "setup_finished", threadId: thread.id, exitCode });
22446
22466
  };
22447
22467
  try {
@@ -22449,14 +22469,22 @@ var init_orchestrator = __esm({
22449
22469
  thread.repoPath,
22450
22470
  thread.worktreePath,
22451
22471
  (line) => {
22452
- appendSetupLog(thread.id, line);
22472
+ this.writeSetupLog(
22473
+ thread.id,
22474
+ thread.worktreePath,
22475
+ (logKey) => appendSetupLog(logKey, line)
22476
+ );
22453
22477
  this.emit({ type: "setup_output", threadId: thread.id, line });
22454
22478
  },
22455
22479
  { signal: abort.signal }
22456
22480
  );
22457
22481
  if (!setup.ran) {
22458
22482
  const line = "No setup script in .sideboard/settings.toml, .conductor/settings.toml, .cursor/worktrees.json, or script/setup (bin/setup, scripts/setup)";
22459
- appendSetupLog(thread.id, line);
22483
+ this.writeSetupLog(
22484
+ thread.id,
22485
+ thread.worktreePath,
22486
+ (logKey) => appendSetupLog(logKey, line)
22487
+ );
22460
22488
  this.emit({ type: "setup_output", threadId: thread.id, line });
22461
22489
  finish(null, null);
22462
22490
  throw new Error(line);
@@ -22484,8 +22512,14 @@ var init_orchestrator = __esm({
22484
22512
  }
22485
22513
  cancelSetup(threadRef) {
22486
22514
  const thread = this.requireThread(threadRef);
22487
- const proc = this.processes.get(`${thread.id}:setup`);
22488
- if (proc) proc.kill();
22515
+ const keys = [
22516
+ worktreeSetupProcessKey(thread.worktreePath),
22517
+ ...threadsSharingWorktree(thread.worktreePath).map((t) => `${t.id}:setup`)
22518
+ ];
22519
+ for (const key of [...new Set(keys)]) {
22520
+ const proc = this.processes.get(key);
22521
+ if (proc) proc.kill();
22522
+ }
22489
22523
  }
22490
22524
  async applyIntoMain(threadRef, opts) {
22491
22525
  const thread = this.requireThread(threadRef);
@@ -24162,7 +24196,54 @@ init_worktree();
24162
24196
  init_stack();
24163
24197
  init_branch_prefix();
24164
24198
  init_agent_git_actions();
24165
- init_github();
24199
+
24200
+ // src/integrations/github.ts
24201
+ init_run();
24202
+ async function getGitHubStatus() {
24203
+ const which = await run("which", ["gh"], { reject: false });
24204
+ if (which.exitCode !== 0 || !which.stdout.trim()) {
24205
+ return {
24206
+ connected: false,
24207
+ login: null,
24208
+ hosts: [],
24209
+ reason: "gh CLI not found on PATH"
24210
+ };
24211
+ }
24212
+ const loginResult = await run(
24213
+ "gh",
24214
+ ["api", "user", "--jq", ".login"],
24215
+ { reject: false }
24216
+ );
24217
+ if (loginResult.exitCode === 0 && loginResult.stdout.trim()) {
24218
+ return {
24219
+ connected: true,
24220
+ login: loginResult.stdout.trim(),
24221
+ hosts: ["github.com"],
24222
+ reason: null
24223
+ };
24224
+ }
24225
+ const status = await run("gh", ["auth", "status"], { reject: false });
24226
+ const text7 = `${status.stdout}
24227
+ ${status.stderr}`;
24228
+ const loginMatch = text7.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text7.match(/Logged in to ([^\s]+) as (\S+)/i);
24229
+ if (loginMatch) {
24230
+ return {
24231
+ connected: true,
24232
+ login: loginMatch[2].replace(/['"]/g, ""),
24233
+ hosts: [loginMatch[1]],
24234
+ reason: null
24235
+ };
24236
+ }
24237
+ return {
24238
+ connected: false,
24239
+ login: null,
24240
+ hosts: [],
24241
+ reason: status.stderr.trim() || status.stdout.trim() || "Not logged in to GitHub (run: gh auth login)"
24242
+ };
24243
+ }
24244
+ async function refreshGitHubAuth() {
24245
+ return getGitHubStatus();
24246
+ }
24166
24247
 
24167
24248
  // src/integrations/linear.ts
24168
24249
  init_fetch();
@@ -24238,7 +24319,7 @@ function issueActivityKind(issue, sinceIso) {
24238
24319
  }
24239
24320
 
24240
24321
  // src/integrations/linear-oauth.ts
24241
- var import_node_crypto10 = require("crypto");
24322
+ var import_node_crypto11 = require("crypto");
24242
24323
  var import_node_http = require("http");
24243
24324
  init_app_settings();
24244
24325
  init_fetch();
@@ -24270,8 +24351,8 @@ function linearOAuthCredentials() {
24270
24351
  return { clientId, clientSecret };
24271
24352
  }
24272
24353
  function createLinearPkce() {
24273
- const verifier = (0, import_node_crypto10.randomBytes)(32).toString("base64url");
24274
- const challenge = (0, import_node_crypto10.createHash)("sha256").update(verifier).digest("base64url");
24354
+ const verifier = (0, import_node_crypto11.randomBytes)(32).toString("base64url");
24355
+ const challenge = (0, import_node_crypto11.createHash)("sha256").update(verifier).digest("base64url");
24275
24356
  return { verifier, challenge };
24276
24357
  }
24277
24358
  function linearOAuthAuthorizeUrl(clientId, state, codeChallenge) {
@@ -24402,7 +24483,7 @@ async function startLinearOAuth(opts) {
24402
24483
  throw new LinearOAuthCancelledError();
24403
24484
  }
24404
24485
  const { clientId, clientSecret } = linearOAuthCredentials();
24405
- const state = (0, import_node_crypto10.randomBytes)(16).toString("hex");
24486
+ const state = (0, import_node_crypto11.randomBytes)(16).toString("hex");
24406
24487
  const pkce = createLinearPkce();
24407
24488
  const authorizeUrl = linearOAuthAuthorizeUrl(clientId, state, pkce.challenge);
24408
24489
  const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
@@ -26255,7 +26336,7 @@ function buildPathRefAttachment(input) {
26255
26336
  init_stage_files();
26256
26337
 
26257
26338
  // src/composer/pasted-text.ts
26258
- var import_node_crypto11 = require("crypto");
26339
+ var import_node_crypto12 = require("crypto");
26259
26340
  var PASTE_ATTACH_MIN_CHARS = 1200;
26260
26341
  var PASTE_ATTACH_MIN_LINES = 15;
26261
26342
  var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
@@ -26282,7 +26363,7 @@ function nextPastedTextName(existing) {
26282
26363
  }
26283
26364
  function buildPastedTextAttachment(text7, opts) {
26284
26365
  return {
26285
- id: opts?.id ?? (0, import_node_crypto11.randomUUID)(),
26366
+ id: opts?.id ?? (0, import_node_crypto12.randomUUID)(),
26286
26367
  name: opts?.name ?? "Pasted text #1.txt",
26287
26368
  kind: "file",
26288
26369
  path: opts?.path,
@@ -26436,7 +26517,7 @@ init_orphan_cleanup();
26436
26517
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
26437
26518
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
26438
26519
  var import_zod9 = require("zod");
26439
- var import_node_crypto13 = require("crypto");
26520
+ var import_node_crypto14 = require("crypto");
26440
26521
  var import_node_path52 = require("path");
26441
26522
  init_orchestrator();
26442
26523
  init_worktree();
@@ -28163,7 +28244,7 @@ init_worktree();
28163
28244
  init_paths();
28164
28245
 
28165
28246
  // src/board/board-pins.ts
28166
- var import_node_crypto12 = require("crypto");
28247
+ var import_node_crypto13 = require("crypto");
28167
28248
  var import_node_fs54 = require("fs");
28168
28249
  var import_node_path50 = require("path");
28169
28250
  init_paths();
@@ -28195,7 +28276,7 @@ function addBoardPin(input) {
28195
28276
  const ref = input.ref.trim();
28196
28277
  if (!ref) throw new Error("Board item needs a ref");
28197
28278
  const pin = {
28198
- id: (0, import_node_crypto12.randomUUID)(),
28279
+ id: (0, import_node_crypto13.randomUUID)(),
28199
28280
  kind: input.kind,
28200
28281
  ref,
28201
28282
  repoPath: input.repoPath.trim(),
@@ -28440,7 +28521,6 @@ async function getHomeBoardInputs(workspaces, opts) {
28440
28521
 
28441
28522
  // src/mcp/server.ts
28442
28523
  init_home_board();
28443
- init_github();
28444
28524
 
28445
28525
  // src/mcp/create-thread-agent.ts
28446
28526
  init_cursor();
@@ -29030,7 +29110,7 @@ async function startMcpServer() {
29030
29110
  const ident = issue?.identifier ?? ref.trim();
29031
29111
  const attachments = issue ? [
29032
29112
  {
29033
- id: (0, import_node_crypto13.randomUUID)(),
29113
+ id: (0, import_node_crypto14.randomUUID)(),
29034
29114
  name: issue.identifier,
29035
29115
  kind: "issue",
29036
29116
  content: [
@@ -29985,7 +30065,7 @@ init_orch_mcp_isolation();
29985
30065
  init_workspaces2();
29986
30066
 
29987
30067
  // src/slack/oauth.ts
29988
- var import_node_crypto14 = require("crypto");
30068
+ var import_node_crypto15 = require("crypto");
29989
30069
  init_app_settings();
29990
30070
  init_workspaces2();
29991
30071
 
@@ -30209,7 +30289,7 @@ async function startSlackOAuth(opts) {
30209
30289
  throw new SlackOAuthCancelledError();
30210
30290
  }
30211
30291
  const { clientId } = slackOAuthCredentials();
30212
- const state = (0, import_node_crypto14.randomBytes)(16).toString("hex");
30292
+ const state = (0, import_node_crypto15.randomBytes)(16).toString("hex");
30213
30293
  const authorizeUrl = slackOAuthAuthorizeUrl(clientId, state);
30214
30294
  const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
30215
30295
  const pollIntervalMs = opts?.pollIntervalMs ?? 400;
@@ -30491,7 +30571,7 @@ init_workspaces2();
30491
30571
  init_reply_target();
30492
30572
 
30493
30573
  // src/slack/relay-protocol.ts
30494
- var import_node_crypto15 = require("crypto");
30574
+ var import_node_crypto16 = require("crypto");
30495
30575
  var SLACK_RELAY_PING_INTERVAL_MS = 2e4;
30496
30576
  var SLACK_RELAY_PONG_TIMEOUT_MS = 15e3;
30497
30577
  function slackRelaySessionKey(teamId, userId, deviceId) {
@@ -30501,7 +30581,7 @@ function slackRelayUserKey(teamId, userId) {
30501
30581
  return `${teamId.trim()}:${userId.trim()}`;
30502
30582
  }
30503
30583
  function newSlackRelayEventId() {
30504
- return (0, import_node_crypto15.randomBytes)(12).toString("hex");
30584
+ return (0, import_node_crypto16.randomBytes)(12).toString("hex");
30505
30585
  }
30506
30586
  function parseSlackRelayClientMessage(raw) {
30507
30587
  try {