@sideboard-ai/core 0.1.194 → 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.
@@ -471,7 +471,7 @@ async function continueSourceThread(threadId, prompt) {
471
471
  await continueOnReply(threadId, prompt);
472
472
  return;
473
473
  }
474
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-S3M2E7O6.js");
474
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-6ESBGMDN.js");
475
475
  await getOrchestrator2().send(threadId, prompt);
476
476
  } catch {
477
477
  }
@@ -1475,6 +1475,53 @@ async function maybeCompactContext(thread, thresholds = {}, summarize = summariz
1475
1475
  };
1476
1476
  }
1477
1477
 
1478
+ // src/threads/worktree-runtime.ts
1479
+ function worktreeProcessPrefix(worktreePath) {
1480
+ return `wt:${normalizeWorktreePath(worktreePath)}`;
1481
+ }
1482
+ function worktreeSetupProcessKey(worktreePath) {
1483
+ return `${worktreeProcessPrefix(worktreePath)}:setup`;
1484
+ }
1485
+ function worktreeRunProcessKey(worktreePath, scriptName) {
1486
+ return `${worktreeProcessPrefix(worktreePath)}:run:${scriptName}`;
1487
+ }
1488
+ function worktreeDevProcessKey(worktreePath) {
1489
+ return `${worktreeProcessPrefix(worktreePath)}:dev`;
1490
+ }
1491
+ function isWorktreeRunProcessKey(key, worktreePath) {
1492
+ const prefix = worktreeProcessPrefix(worktreePath);
1493
+ return key.startsWith(`${prefix}:run:`) || key === `${prefix}:dev`;
1494
+ }
1495
+ function pickRichestSetupLog(snaps) {
1496
+ let best;
1497
+ for (const snap of snaps) {
1498
+ if (!snap.output && !snap.running && snap.exitCode == null) continue;
1499
+ if (!best) {
1500
+ best = snap;
1501
+ continue;
1502
+ }
1503
+ if (snap.running && !best.running) {
1504
+ best = snap;
1505
+ continue;
1506
+ }
1507
+ if (snap.running === best.running && snap.output.length > best.output.length) {
1508
+ best = snap;
1509
+ }
1510
+ }
1511
+ return best;
1512
+ }
1513
+ function mergeWorktreeActiveRuns(threads) {
1514
+ const byName = /* @__PURE__ */ new Map();
1515
+ let devPort = null;
1516
+ for (const thread of threads) {
1517
+ if (thread.devPort != null && devPort == null) devPort = thread.devPort;
1518
+ for (const run2 of thread.activeRuns ?? []) {
1519
+ if (!byName.has(run2.scriptName)) byName.set(run2.scriptName, run2);
1520
+ }
1521
+ }
1522
+ return { activeRuns: [...byName.values()], devPort };
1523
+ }
1524
+
1478
1525
  // src/threads/chat-tabs.ts
1479
1526
  function sameWorktreePath(a, b) {
1480
1527
  return normalizeWorktreePath(a) === normalizeWorktreePath(b);
@@ -1571,7 +1618,8 @@ function createChatTab(input) {
1571
1618
  planMode: from.planMode,
1572
1619
  autonomy: input.autonomy ?? from.autonomy,
1573
1620
  attachments: input.attachments ?? [],
1574
- status: "idle"
1621
+ status: "idle",
1622
+ ...mergeWorktreeActiveRuns(threadsSharingWorktree(binding.worktreePath))
1575
1623
  });
1576
1624
  writeThread(thread);
1577
1625
  return thread;
@@ -2244,6 +2292,7 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
2244
2292
  }
2245
2293
 
2246
2294
  // src/store/setup-log.ts
2295
+ import { createHash } from "crypto";
2247
2296
  import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
2248
2297
  import { join as join7 } from "path";
2249
2298
  var EMPTY = {
@@ -2257,6 +2306,10 @@ var persistTimers = /* @__PURE__ */ new Map();
2257
2306
  function setupLogPath(threadId) {
2258
2307
  return join7(threadsDir(), `${threadId}.setup.log.json`);
2259
2308
  }
2309
+ function setupLogKeyForWorktree(worktreePath) {
2310
+ const norm = normalizeWorktreePath(worktreePath);
2311
+ return `wt-${createHash("sha1").update(norm).digest("hex").slice(0, 16)}`;
2312
+ }
2260
2313
  function emptySetupLog() {
2261
2314
  return { ...EMPTY };
2262
2315
  }
@@ -6823,7 +6876,7 @@ function formatScheduledPrompt(name, prompt) {
6823
6876
  ${prompt}`;
6824
6877
  }
6825
6878
  async function defaultDeps() {
6826
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-S3M2E7O6.js");
6879
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-6ESBGMDN.js");
6827
6880
  const orch = getOrchestrator2();
6828
6881
  return {
6829
6882
  findThread: (id) => findThreadByRef(id),
@@ -8105,22 +8158,25 @@ var Orchestrator = class {
8105
8158
  "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)."
8106
8159
  );
8107
8160
  }
8108
- const runKey = `${thread.id}:run:${resolvedName}`;
8109
- const existing = this.processes.get(runKey);
8110
- const active = (thread.activeRuns ?? []).find((r) => r.scriptName === resolvedName);
8161
+ const siblings = threadsSharingWorktree(thread.worktreePath);
8162
+ const shared = mergeWorktreeActiveRuns(siblings);
8163
+ const runKey = worktreeRunProcessKey(thread.worktreePath, resolvedName);
8164
+ const existing = this.processes.get(runKey) ?? siblings.map((t) => this.processes.get(`${t.id}:run:${resolvedName}`)).find(Boolean);
8165
+ const active = shared.activeRuns.find((r) => r.scriptName === resolvedName);
8111
8166
  if (existing && active) {
8112
8167
  return { port: active.port, scriptName: resolvedName, ports: active.ports };
8113
8168
  }
8114
8169
  if (!scriptName) {
8115
- const legacy = this.processes.get(`${thread.id}:dev`);
8116
- if (legacy && thread.devPort) {
8117
- return { port: thread.devPort, scriptName: resolvedName, ports: [thread.devPort] };
8170
+ const legacy = this.processes.get(worktreeDevProcessKey(thread.worktreePath)) ?? siblings.map((t) => this.processes.get(`${t.id}:dev`)).find(Boolean);
8171
+ if (legacy && shared.devPort) {
8172
+ return { port: shared.devPort, scriptName: resolvedName, ports: [shared.devPort] };
8118
8173
  }
8119
8174
  }
8120
8175
  const mode = getRunMode(thread.worktreePath, thread.repoPath);
8121
8176
  if (mode === "nonconcurrent") {
8177
+ const here = normalizeWorktreePath(thread.worktreePath);
8122
8178
  for (const t of listThreads()) {
8123
- if (t.id === thread.id) continue;
8179
+ if (normalizeWorktreePath(t.worktreePath) === here) continue;
8124
8180
  const runs = t.activeRuns ?? [];
8125
8181
  if (runs.length > 0 || t.devPort) {
8126
8182
  throw new Error(
@@ -8155,7 +8211,7 @@ var Orchestrator = class {
8155
8211
  });
8156
8212
  const isDefault = scripts.find((s) => s.default)?.name === resolvedName || !scripts.some((s) => s.default) && (resolvedName === "dev" || resolvedName === scripts[0]?.name);
8157
8213
  if (isDefault) {
8158
- this.processes.set(`${thread.id}:dev`, {
8214
+ this.processes.set(worktreeDevProcessKey(thread.worktreePath), {
8159
8215
  kind: "dev",
8160
8216
  pid: handle.pid,
8161
8217
  startedAt,
@@ -8170,13 +8226,14 @@ var Orchestrator = class {
8170
8226
  startedAt
8171
8227
  };
8172
8228
  const nextRuns = [
8173
- ...(thread.activeRuns ?? []).filter((r) => r.scriptName !== resolvedName),
8229
+ ...shared.activeRuns.filter((r) => r.scriptName !== resolvedName),
8174
8230
  run2
8175
8231
  ];
8176
- updateThread(thread.id, {
8177
- activeRuns: nextRuns,
8178
- devPort: isDefault ? handle.port : thread.devPort
8179
- });
8232
+ this.syncWorktreeRuns(
8233
+ thread.worktreePath,
8234
+ nextRuns,
8235
+ isDefault ? handle.port : shared.devPort
8236
+ );
8180
8237
  this.emit({
8181
8238
  type: "dev_server_started",
8182
8239
  threadId: thread.id,
@@ -8185,15 +8242,14 @@ var Orchestrator = class {
8185
8242
  });
8186
8243
  void handle.done.then(() => {
8187
8244
  this.processes.delete(runKey);
8188
- if (isDefault) this.processes.delete(`${thread.id}:dev`);
8189
- const latest = readThread(thread.id);
8190
- const remaining = (latest?.activeRuns ?? []).filter(
8191
- (r) => r.scriptName !== resolvedName
8245
+ if (isDefault) this.processes.delete(worktreeDevProcessKey(thread.worktreePath));
8246
+ const latest = mergeWorktreeActiveRuns(threadsSharingWorktree(thread.worktreePath));
8247
+ const remaining = latest.activeRuns.filter((r) => r.scriptName !== resolvedName);
8248
+ this.syncWorktreeRuns(
8249
+ thread.worktreePath,
8250
+ remaining,
8251
+ isDefault ? null : latest.devPort
8192
8252
  );
8193
- updateThread(thread.id, {
8194
- activeRuns: remaining,
8195
- devPort: isDefault ? null : latest?.devPort ?? null
8196
- });
8197
8253
  this.emit({
8198
8254
  type: "dev_server_stopped",
8199
8255
  threadId: thread.id,
@@ -8204,51 +8260,82 @@ var Orchestrator = class {
8204
8260
  }
8205
8261
  stopDev(threadRef, scriptName) {
8206
8262
  const thread = this.requireThread(threadRef);
8263
+ const siblings = threadsSharingWorktree(thread.worktreePath);
8264
+ const shared = mergeWorktreeActiveRuns(siblings);
8207
8265
  if (scriptName) {
8208
- const runKey = `${thread.id}:run:${scriptName}`;
8209
- const proc = this.processes.get(runKey);
8210
- if (proc) proc.kill();
8211
- this.processes.delete(runKey);
8212
- const remaining = (thread.activeRuns ?? []).filter((r) => r.scriptName !== scriptName);
8213
- const isPrimary = thread.devPort != null && thread.activeRuns?.find((r) => r.scriptName === scriptName)?.port === thread.devPort;
8214
- updateThread(thread.id, {
8215
- activeRuns: remaining,
8216
- devPort: isPrimary ? null : thread.devPort
8217
- });
8266
+ const keys = [
8267
+ worktreeRunProcessKey(thread.worktreePath, scriptName),
8268
+ ...siblings.map((t) => `${t.id}:run:${scriptName}`)
8269
+ ];
8270
+ for (const key of keys) {
8271
+ const proc = this.processes.get(key);
8272
+ if (proc) proc.kill();
8273
+ this.processes.delete(key);
8274
+ }
8275
+ const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
8276
+ const isPrimary = shared.devPort != null && shared.activeRuns.find((r) => r.scriptName === scriptName)?.port === shared.devPort;
8277
+ this.syncWorktreeRuns(
8278
+ thread.worktreePath,
8279
+ remaining,
8280
+ isPrimary ? null : shared.devPort
8281
+ );
8218
8282
  this.emit({ type: "dev_server_stopped", threadId: thread.id, scriptName });
8219
8283
  return;
8220
8284
  }
8221
8285
  for (const [key, proc] of [...this.processes.entries()]) {
8222
- if (key.startsWith(`${thread.id}:run:`) || key === `${thread.id}:dev`) {
8286
+ if (isWorktreeRunProcessKey(key, thread.worktreePath) || siblings.some((t) => key.startsWith(`${t.id}:run:`) || key === `${t.id}:dev`)) {
8223
8287
  proc.kill();
8224
8288
  this.processes.delete(key);
8225
8289
  }
8226
8290
  }
8227
- updateThread(thread.id, { activeRuns: [], devPort: null });
8291
+ this.syncWorktreeRuns(thread.worktreePath, [], null);
8228
8292
  this.emit({ type: "dev_server_stopped", threadId: thread.id });
8229
8293
  }
8294
+ syncWorktreeRuns(worktreePath, activeRuns, devPort) {
8295
+ for (const sibling of threadsSharingWorktree(worktreePath)) {
8296
+ updateThread(sibling.id, { activeRuns, devPort });
8297
+ }
8298
+ }
8230
8299
  listThreadRunScripts(threadRef) {
8231
8300
  const thread = this.requireThread(threadRef);
8232
8301
  return listRunScripts(thread.worktreePath, thread.repoPath);
8233
8302
  }
8234
8303
  getActiveRuns(threadRef) {
8235
- return this.requireThread(threadRef).activeRuns ?? [];
8304
+ const thread = this.requireThread(threadRef);
8305
+ return mergeWorktreeActiveRuns(threadsSharingWorktree(thread.worktreePath)).activeRuns;
8236
8306
  }
8237
8307
  getSetupLog(threadRef) {
8238
8308
  const thread = this.getThread(threadRef);
8239
8309
  if (!thread) return readSetupLog(threadRef);
8240
- const snap = readSetupLog(thread.id);
8241
- if (snap.running && !this.processes.has(`${thread.id}:setup`)) {
8310
+ const siblingIds = [
8311
+ thread.id,
8312
+ ...threadsSharingWorktree(thread.worktreePath).map((t) => t.id)
8313
+ ];
8314
+ const snap = pickRichestSetupLog([
8315
+ readSetupLog(setupLogKeyForWorktree(thread.worktreePath)),
8316
+ ...[...new Set(siblingIds)].map((id) => readSetupLog(id))
8317
+ ]) ?? readSetupLog(thread.id);
8318
+ if (snap.running && !this.hasWorktreeSetupProcess(thread.worktreePath)) {
8242
8319
  return { ...snap, running: false };
8243
8320
  }
8244
8321
  return snap;
8245
8322
  }
8323
+ hasWorktreeSetupProcess(worktreePath) {
8324
+ if (this.processes.has(worktreeSetupProcessKey(worktreePath))) return true;
8325
+ return threadsSharingWorktree(worktreePath).some(
8326
+ (t) => this.processes.has(`${t.id}:setup`)
8327
+ );
8328
+ }
8329
+ writeSetupLog(threadId, worktreePath, fn) {
8330
+ fn(setupLogKeyForWorktree(worktreePath));
8331
+ fn(threadId);
8332
+ }
8246
8333
  async runSetup(threadRef, opts) {
8247
8334
  const thread = this.requireThread(threadRef);
8248
8335
  this.assertNotGlobal(thread, "Setup");
8249
- const key = `${thread.id}:setup`;
8250
- if (this.processes.has(key)) {
8251
- throw new Error("Setup already running for this thread");
8336
+ const key = worktreeSetupProcessKey(thread.worktreePath);
8337
+ if (this.hasWorktreeSetupProcess(thread.worktreePath)) {
8338
+ throw new Error("Setup already running for this worktree");
8252
8339
  }
8253
8340
  const abort = new AbortController();
8254
8341
  this.processes.set(key, {
@@ -8256,13 +8343,17 @@ var Orchestrator = class {
8256
8343
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
8257
8344
  kill: () => abort.abort()
8258
8345
  });
8259
- beginSetupLog(thread.id);
8346
+ this.writeSetupLog(thread.id, thread.worktreePath, beginSetupLog);
8260
8347
  this.emit({ type: "setup_started", threadId: thread.id });
8261
8348
  let finished = false;
8262
8349
  const finish = (exitCode, source) => {
8263
8350
  if (finished) return;
8264
8351
  finished = true;
8265
- finishSetupLog(thread.id, exitCode, source);
8352
+ this.writeSetupLog(
8353
+ thread.id,
8354
+ thread.worktreePath,
8355
+ (logKey) => finishSetupLog(logKey, exitCode, source)
8356
+ );
8266
8357
  this.emit({ type: "setup_finished", threadId: thread.id, exitCode });
8267
8358
  };
8268
8359
  try {
@@ -8270,14 +8361,22 @@ var Orchestrator = class {
8270
8361
  thread.repoPath,
8271
8362
  thread.worktreePath,
8272
8363
  (line) => {
8273
- appendSetupLog(thread.id, line);
8364
+ this.writeSetupLog(
8365
+ thread.id,
8366
+ thread.worktreePath,
8367
+ (logKey) => appendSetupLog(logKey, line)
8368
+ );
8274
8369
  this.emit({ type: "setup_output", threadId: thread.id, line });
8275
8370
  },
8276
8371
  { signal: abort.signal }
8277
8372
  );
8278
8373
  if (!setup.ran) {
8279
8374
  const line = "No setup script in .sideboard/settings.toml, .conductor/settings.toml, .cursor/worktrees.json, or script/setup (bin/setup, scripts/setup)";
8280
- appendSetupLog(thread.id, line);
8375
+ this.writeSetupLog(
8376
+ thread.id,
8377
+ thread.worktreePath,
8378
+ (logKey) => appendSetupLog(logKey, line)
8379
+ );
8281
8380
  this.emit({ type: "setup_output", threadId: thread.id, line });
8282
8381
  finish(null, null);
8283
8382
  throw new Error(line);
@@ -8305,8 +8404,14 @@ var Orchestrator = class {
8305
8404
  }
8306
8405
  cancelSetup(threadRef) {
8307
8406
  const thread = this.requireThread(threadRef);
8308
- const proc = this.processes.get(`${thread.id}:setup`);
8309
- if (proc) proc.kill();
8407
+ const keys = [
8408
+ worktreeSetupProcessKey(thread.worktreePath),
8409
+ ...threadsSharingWorktree(thread.worktreePath).map((t) => `${t.id}:setup`)
8410
+ ];
8411
+ for (const key of [...new Set(keys)]) {
8412
+ const proc = this.processes.get(key);
8413
+ if (proc) proc.kill();
8414
+ }
8310
8415
  }
8311
8416
  async applyIntoMain(threadRef, opts) {
8312
8417
  const thread = this.requireThread(threadRef);