@sideboard-ai/core 0.1.95 → 0.1.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -11694,23 +11694,24 @@ async function countUnpushedCommits(worktreePath) {
11694
11694
  reject: false
11695
11695
  });
11696
11696
  const branch = head.stdout.trim();
11697
+ if (branch && branch !== "HEAD") {
11698
+ const remote = await git(
11699
+ ["rev-list", "--count", `origin/${branch}..HEAD`],
11700
+ worktreePath,
11701
+ { reject: false }
11702
+ );
11703
+ if (remote.exitCode === 0) {
11704
+ const n2 = Number(remote.stdout.trim());
11705
+ return Number.isFinite(n2) ? n2 : 0;
11706
+ }
11707
+ }
11697
11708
  const upstream = await git(
11698
11709
  ["rev-list", "--count", "@{upstream}..HEAD"],
11699
11710
  worktreePath,
11700
11711
  { reject: false }
11701
11712
  );
11702
- if (upstream.exitCode === 0) {
11703
- const n2 = Number(upstream.stdout.trim());
11704
- return Number.isFinite(n2) ? n2 : 0;
11705
- }
11706
- if (!branch || branch === "HEAD") return 0;
11707
- const remote = await git(
11708
- ["rev-list", "--count", `origin/${branch}..HEAD`],
11709
- worktreePath,
11710
- { reject: false }
11711
- );
11712
- if (remote.exitCode !== 0) return 0;
11713
- const n = Number(remote.stdout.trim());
11713
+ if (upstream.exitCode !== 0) return 0;
11714
+ const n = Number(upstream.stdout.trim());
11714
11715
  return Number.isFinite(n) ? n : 0;
11715
11716
  }
11716
11717
  function splitCombinedDiff(stdout) {
@@ -15418,6 +15419,11 @@ function isPidAlive(pid) {
15418
15419
  return false;
15419
15420
  }
15420
15421
  }
15422
+ function writeLiveStatus(threadId, status, lastError) {
15423
+ const latest = readThread(threadId);
15424
+ if (!latest || latest.status === "archived") return latest;
15425
+ return setStatus(threadId, status, lastError);
15426
+ }
15421
15427
  var Orchestrator = class {
15422
15428
  events = new import_node_events.EventEmitter();
15423
15429
  processes = /* @__PURE__ */ new Map();
@@ -15723,6 +15729,9 @@ var Orchestrator = class {
15723
15729
  }
15724
15730
  async send(threadRef, prompt) {
15725
15731
  const thread = this.requireThread(threadRef);
15732
+ if (thread.status === "archived") {
15733
+ throw new Error(`Thread is archived: ${thread.id}`);
15734
+ }
15726
15735
  return withThreadLock(thread.id, async () => {
15727
15736
  const current = this.requireThread(thread.id);
15728
15737
  const queue = [...current.queue, prompt];
@@ -15816,7 +15825,7 @@ var Orchestrator = class {
15816
15825
  break;
15817
15826
  }
15818
15827
  const thread = readThread(threadId);
15819
- if (!thread || thread.queue.length === 0) {
15828
+ if (!thread || thread.status === "archived" || thread.queue.length === 0) {
15820
15829
  if (thread && thread.status === "queued") {
15821
15830
  setStatus(threadId, "idle");
15822
15831
  this.emit({ type: "status_changed", threadId, status: "idle" });
@@ -15847,14 +15856,17 @@ var Orchestrator = class {
15847
15856
  }
15848
15857
  }
15849
15858
  async runTurn(threadId, prompt) {
15850
- let thread = this.requireThread(threadId);
15859
+ const existing = readThread(threadId);
15860
+ if (!existing || existing.status === "archived") return;
15861
+ let thread = existing;
15851
15862
  if (isGlobalThread(thread) && thread.sessionId && orchestratorSessionPoisonedByBuiltins(thread)) {
15852
15863
  thread = updateThread(threadId, { sessionId: null });
15853
15864
  }
15865
+ const running = writeLiveStatus(threadId, "running");
15866
+ if (!running || running.status !== "running") return;
15854
15867
  this.runningCount += 1;
15855
15868
  const turnStartedAt = Date.now();
15856
15869
  this.startingTurns.add(threadId);
15857
- setStatus(threadId, "running");
15858
15870
  this.emit({ type: "status_changed", threadId, status: "running" });
15859
15871
  this.emit({ type: "turn_started", threadId, prompt });
15860
15872
  try {
@@ -16125,8 +16137,8 @@ var Orchestrator = class {
16125
16137
  ts: (/* @__PURE__ */ new Date()).toISOString()
16126
16138
  });
16127
16139
  }
16128
- const afterTurn = this.requireThread(threadId);
16129
- if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
16140
+ const afterTurn = readThread(threadId);
16141
+ if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.worktreePath?.trim()) {
16130
16142
  const presented = extractPresentedPlan(parts);
16131
16143
  const exited = parts.some(
16132
16144
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
@@ -16139,29 +16151,34 @@ var Orchestrator = class {
16139
16151
  }
16140
16152
  }
16141
16153
  }
16142
- if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
16154
+ if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
16143
16155
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
16144
16156
  )) {
16145
16157
  updateThread(threadId, { sessionId: null });
16146
16158
  }
16147
16159
  await syncThreadBranchFromGit(threadId);
16148
16160
  if (this.stoppedTurns.has(threadId)) {
16149
- setStatus(threadId, "stopped");
16150
- this.emit({ type: "status_changed", threadId, status: "stopped" });
16161
+ const stopped = writeLiveStatus(threadId, "stopped");
16162
+ if (stopped?.status === "stopped") {
16163
+ this.emit({ type: "status_changed", threadId, status: "stopped" });
16164
+ }
16151
16165
  this.emit({ type: "turn_finished", threadId, exitCode });
16152
16166
  } else {
16153
16167
  const failDetail = formatTurnExitError(exitCode, detail);
16154
16168
  const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
16155
- setStatus(
16169
+ const nextStatus = exitCode === 0 ? "idle" : "error";
16170
+ const written = writeLiveStatus(
16156
16171
  threadId,
16157
- exitCode === 0 ? "idle" : "error",
16172
+ nextStatus,
16158
16173
  exitCode === 0 || explainedInChat ? null : failDetail
16159
16174
  );
16160
- this.emit({
16161
- type: "status_changed",
16162
- threadId,
16163
- status: exitCode === 0 ? "idle" : "error"
16164
- });
16175
+ if (written && written.status !== "archived") {
16176
+ this.emit({
16177
+ type: "status_changed",
16178
+ threadId,
16179
+ status: written.status
16180
+ });
16181
+ }
16165
16182
  this.emit({ type: "turn_finished", threadId, exitCode });
16166
16183
  if (exitCode !== 0) {
16167
16184
  const blob = [chatText, detail].filter(Boolean).join("\n");
@@ -16172,13 +16189,17 @@ var Orchestrator = class {
16172
16189
  const message = err instanceof Error ? err.message : String(err);
16173
16190
  await syncThreadBranchFromGit(threadId).catch(() => void 0);
16174
16191
  if (this.stoppedTurns.has(threadId)) {
16175
- setStatus(threadId, "stopped");
16176
- this.emit({ type: "status_changed", threadId, status: "stopped" });
16192
+ const stopped = writeLiveStatus(threadId, "stopped");
16193
+ if (stopped?.status === "stopped") {
16194
+ this.emit({ type: "status_changed", threadId, status: "stopped" });
16195
+ }
16177
16196
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
16178
16197
  } else {
16179
- setStatus(threadId, "error", message);
16180
- this.emit({ type: "error", threadId, message });
16181
- this.emit({ type: "status_changed", threadId, status: "error" });
16198
+ const written = writeLiveStatus(threadId, "error", message);
16199
+ if (written?.status === "error") {
16200
+ this.emit({ type: "error", threadId, message });
16201
+ this.emit({ type: "status_changed", threadId, status: "error" });
16202
+ }
16182
16203
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
16183
16204
  void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
16184
16205
  }
@@ -16225,9 +16246,11 @@ var Orchestrator = class {
16225
16246
  if (handle) handle.kill();
16226
16247
  const proc = this.processes.get(`${thread.id}:agent`);
16227
16248
  if (proc) proc.kill();
16228
- setStatus(thread.id, "stopped");
16229
- this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
16230
- return this.requireThread(thread.id);
16249
+ const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
16250
+ if (stopped.status === "stopped") {
16251
+ this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
16252
+ }
16253
+ return stopped;
16231
16254
  }
16232
16255
  async startDev(threadRef, scriptName) {
16233
16256
  const thread = this.requireThread(threadRef);
@@ -16463,11 +16486,21 @@ var Orchestrator = class {
16463
16486
  const off = this.on((event) => {
16464
16487
  if (event.type === "turn_finished" && event.threadId === thread.id) {
16465
16488
  off();
16466
- resolve(this.requireThread(thread.id));
16489
+ const latest = readThread(thread.id);
16490
+ if (!latest) {
16491
+ reject(new Error(`Thread not found: ${thread.id}`));
16492
+ return;
16493
+ }
16494
+ resolve(latest);
16467
16495
  }
16468
16496
  if (event.type === "error" && event.threadId === thread.id) {
16469
16497
  off();
16470
- resolve(this.requireThread(thread.id));
16498
+ const latest = readThread(thread.id);
16499
+ if (!latest) {
16500
+ reject(new Error(`Thread not found: ${thread.id}`));
16501
+ return;
16502
+ }
16503
+ resolve(latest);
16471
16504
  }
16472
16505
  });
16473
16506
  const timer = setInterval(() => {
@@ -16477,7 +16510,13 @@ var Orchestrator = class {
16477
16510
  reject(new Error("wait_for_turn timed out"));
16478
16511
  }
16479
16512
  const current = readThread(thread.id);
16480
- if (current && !["running", "queued"].includes(current.status)) {
16513
+ if (!current) {
16514
+ clearInterval(timer);
16515
+ off();
16516
+ reject(new Error(`Thread not found: ${thread.id}`));
16517
+ return;
16518
+ }
16519
+ if (!["running", "queued"].includes(current.status)) {
16481
16520
  clearInterval(timer);
16482
16521
  off();
16483
16522
  resolve(current);
@@ -20094,10 +20133,10 @@ function slackInboundSuperseded(opts) {
20094
20133
  function interruptSlackCoordinatorForInbound(msg, agent, log = () => void 0) {
20095
20134
  const userId = msg.userId?.trim();
20096
20135
  if (!userId) return false;
20097
- const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
20098
- const fresh = readThread(coordinator.id) ?? coordinator;
20099
- if (fresh.status !== "running" && fresh.status !== "queued") return false;
20100
20136
  try {
20137
+ const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
20138
+ const fresh = readThread(coordinator.id) ?? coordinator;
20139
+ if (fresh.status !== "running" && fresh.status !== "queued") return false;
20101
20140
  getOrchestrator().stop(fresh.id, { clearQueue: true });
20102
20141
  log(
20103
20142
  `interrupt ${msg.kind} ${msg.ts} \u2192 coordinator ${fresh.id.slice(0, 8)} (${fresh.status})`
@@ -20137,7 +20176,7 @@ function slackReplyThreadTs(msg) {
20137
20176
  if (msg.kind === "mention") return msg.ts;
20138
20177
  return void 0;
20139
20178
  }
20140
- var SLACK_SEEN_REACTION = "+1";
20179
+ var SLACK_SEEN_REACTION = "eyes";
20141
20180
  function writeTokenForTeam(teamId) {
20142
20181
  const workspaces = listSlackWorkspacesRaw();
20143
20182
  const ws = workspaces.find((w) => w.team_id === teamId);
@@ -20303,7 +20342,7 @@ async function handleSlackInbound(msg, opts) {
20303
20342
  return;
20304
20343
  }
20305
20344
  const after = readThread(fresh.id);
20306
- if (after?.status === "stopped") {
20345
+ if (!after || after.status === "stopped" || after.status === "archived") {
20307
20346
  log(`turn finished ${msg.ts} (interrupted, skip post)`);
20308
20347
  return;
20309
20348
  }
@@ -20314,6 +20353,11 @@ async function handleSlackInbound(msg, opts) {
20314
20353
  return;
20315
20354
  }
20316
20355
  const errMsg = err instanceof Error ? err.message : String(err);
20356
+ const gone = /thread not found/i.test(errMsg);
20357
+ if (gone) {
20358
+ log(`turn finished ${msg.ts} (thread gone, skip post)`);
20359
+ return;
20360
+ }
20317
20361
  const timedOut = /timed out|timeout/i.test(errMsg);
20318
20362
  const errorReply = timedOut ? SLACK_LISTEN_TIMEOUT_REPLY : `Sideboard coordinator error: ${errMsg}`;
20319
20363
  await postSlackReply(msg, errorReply, opts).catch((postErr) => {
package/dist/index.d.cts CHANGED
@@ -4262,7 +4262,7 @@ interface SlackListenOptions {
4262
4262
  */
4263
4263
  inboundGeneration?: number;
4264
4264
  currentInboundGeneration?: () => number;
4265
- /** Listen already acked; skip the thumbs-up in handleSlackInbound. */
4265
+ /** Listen already acked; skip the eyes reaction in handleSlackInbound. */
4266
4266
  skipAck?: boolean;
4267
4267
  }
4268
4268
  /**
@@ -4276,9 +4276,9 @@ declare function formatSlackInboundPrompt(msg: SlackInboundMessage): string;
4276
4276
  * more than one device can see who answered and address follow-ups the same way.
4277
4277
  */
4278
4278
  declare function formatSlackSignedReply(deviceLabel: string, text: string): string;
4279
- /** Slack emoji short name for “seen / got it”. */
4280
- declare const SLACK_SEEN_REACTION = "+1";
4281
- /** Thumbs-up the inbound message so Slack shows the bot saw it. */
4279
+ /** Slack emoji short name for “seen / looking at this”. */
4280
+ declare const SLACK_SEEN_REACTION = "eyes";
4281
+ /** Eyes-react the inbound message so Slack shows the bot saw it. */
4282
4282
  declare function ackSlackInboundSeen(msg: SlackInboundMessage, opts: Pick<SlackListenOptions, 'addReaction' | 'fetchImpl' | 'onLog'>): Promise<void>;
4283
4283
  declare function handleSlackInbound(msg: SlackInboundMessage, opts: SlackListenOptions): Promise<void>;
4284
4284
  /**
package/dist/index.d.ts CHANGED
@@ -4262,7 +4262,7 @@ interface SlackListenOptions {
4262
4262
  */
4263
4263
  inboundGeneration?: number;
4264
4264
  currentInboundGeneration?: () => number;
4265
- /** Listen already acked; skip the thumbs-up in handleSlackInbound. */
4265
+ /** Listen already acked; skip the eyes reaction in handleSlackInbound. */
4266
4266
  skipAck?: boolean;
4267
4267
  }
4268
4268
  /**
@@ -4276,9 +4276,9 @@ declare function formatSlackInboundPrompt(msg: SlackInboundMessage): string;
4276
4276
  * more than one device can see who answered and address follow-ups the same way.
4277
4277
  */
4278
4278
  declare function formatSlackSignedReply(deviceLabel: string, text: string): string;
4279
- /** Slack emoji short name for “seen / got it”. */
4280
- declare const SLACK_SEEN_REACTION = "+1";
4281
- /** Thumbs-up the inbound message so Slack shows the bot saw it. */
4279
+ /** Slack emoji short name for “seen / looking at this”. */
4280
+ declare const SLACK_SEEN_REACTION = "eyes";
4281
+ /** Eyes-react the inbound message so Slack shows the bot saw it. */
4282
4282
  declare function ackSlackInboundSeen(msg: SlackInboundMessage, opts: Pick<SlackListenOptions, 'addReaction' | 'fetchImpl' | 'onLog'>): Promise<void>;
4283
4283
  declare function handleSlackInbound(msg: SlackInboundMessage, opts: SlackListenOptions): Promise<void>;
4284
4284
  /**
package/dist/index.js CHANGED
@@ -2151,23 +2151,24 @@ async function countUnpushedCommits(worktreePath) {
2151
2151
  reject: false
2152
2152
  });
2153
2153
  const branch = head.stdout.trim();
2154
+ if (branch && branch !== "HEAD") {
2155
+ const remote = await git(
2156
+ ["rev-list", "--count", `origin/${branch}..HEAD`],
2157
+ worktreePath,
2158
+ { reject: false }
2159
+ );
2160
+ if (remote.exitCode === 0) {
2161
+ const n2 = Number(remote.stdout.trim());
2162
+ return Number.isFinite(n2) ? n2 : 0;
2163
+ }
2164
+ }
2154
2165
  const upstream = await git(
2155
2166
  ["rev-list", "--count", "@{upstream}..HEAD"],
2156
2167
  worktreePath,
2157
2168
  { reject: false }
2158
2169
  );
2159
- if (upstream.exitCode === 0) {
2160
- const n2 = Number(upstream.stdout.trim());
2161
- return Number.isFinite(n2) ? n2 : 0;
2162
- }
2163
- if (!branch || branch === "HEAD") return 0;
2164
- const remote = await git(
2165
- ["rev-list", "--count", `origin/${branch}..HEAD`],
2166
- worktreePath,
2167
- { reject: false }
2168
- );
2169
- if (remote.exitCode !== 0) return 0;
2170
- const n = Number(remote.stdout.trim());
2170
+ if (upstream.exitCode !== 0) return 0;
2171
+ const n = Number(upstream.stdout.trim());
2171
2172
  return Number.isFinite(n) ? n : 0;
2172
2173
  }
2173
2174
  function splitCombinedDiff(stdout) {
@@ -5810,6 +5811,11 @@ function isPidAlive(pid) {
5810
5811
  return false;
5811
5812
  }
5812
5813
  }
5814
+ function writeLiveStatus(threadId, status, lastError) {
5815
+ const latest = readThread(threadId);
5816
+ if (!latest || latest.status === "archived") return latest;
5817
+ return setStatus(threadId, status, lastError);
5818
+ }
5813
5819
  var Orchestrator = class {
5814
5820
  events = new EventEmitter();
5815
5821
  processes = /* @__PURE__ */ new Map();
@@ -6115,6 +6121,9 @@ var Orchestrator = class {
6115
6121
  }
6116
6122
  async send(threadRef, prompt) {
6117
6123
  const thread = this.requireThread(threadRef);
6124
+ if (thread.status === "archived") {
6125
+ throw new Error(`Thread is archived: ${thread.id}`);
6126
+ }
6118
6127
  return withThreadLock(thread.id, async () => {
6119
6128
  const current = this.requireThread(thread.id);
6120
6129
  const queue = [...current.queue, prompt];
@@ -6208,7 +6217,7 @@ var Orchestrator = class {
6208
6217
  break;
6209
6218
  }
6210
6219
  const thread = readThread(threadId);
6211
- if (!thread || thread.queue.length === 0) {
6220
+ if (!thread || thread.status === "archived" || thread.queue.length === 0) {
6212
6221
  if (thread && thread.status === "queued") {
6213
6222
  setStatus(threadId, "idle");
6214
6223
  this.emit({ type: "status_changed", threadId, status: "idle" });
@@ -6239,14 +6248,17 @@ var Orchestrator = class {
6239
6248
  }
6240
6249
  }
6241
6250
  async runTurn(threadId, prompt) {
6242
- let thread = this.requireThread(threadId);
6251
+ const existing = readThread(threadId);
6252
+ if (!existing || existing.status === "archived") return;
6253
+ let thread = existing;
6243
6254
  if (isGlobalThread(thread) && thread.sessionId && orchestratorSessionPoisonedByBuiltins(thread)) {
6244
6255
  thread = updateThread(threadId, { sessionId: null });
6245
6256
  }
6257
+ const running = writeLiveStatus(threadId, "running");
6258
+ if (!running || running.status !== "running") return;
6246
6259
  this.runningCount += 1;
6247
6260
  const turnStartedAt = Date.now();
6248
6261
  this.startingTurns.add(threadId);
6249
- setStatus(threadId, "running");
6250
6262
  this.emit({ type: "status_changed", threadId, status: "running" });
6251
6263
  this.emit({ type: "turn_started", threadId, prompt });
6252
6264
  try {
@@ -6517,8 +6529,8 @@ var Orchestrator = class {
6517
6529
  ts: (/* @__PURE__ */ new Date()).toISOString()
6518
6530
  });
6519
6531
  }
6520
- const afterTurn = this.requireThread(threadId);
6521
- if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
6532
+ const afterTurn = readThread(threadId);
6533
+ if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.worktreePath?.trim()) {
6522
6534
  const presented = extractPresentedPlan(parts);
6523
6535
  const exited = parts.some(
6524
6536
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
@@ -6531,29 +6543,34 @@ var Orchestrator = class {
6531
6543
  }
6532
6544
  }
6533
6545
  }
6534
- if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
6546
+ if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
6535
6547
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
6536
6548
  )) {
6537
6549
  updateThread(threadId, { sessionId: null });
6538
6550
  }
6539
6551
  await syncThreadBranchFromGit(threadId);
6540
6552
  if (this.stoppedTurns.has(threadId)) {
6541
- setStatus(threadId, "stopped");
6542
- this.emit({ type: "status_changed", threadId, status: "stopped" });
6553
+ const stopped = writeLiveStatus(threadId, "stopped");
6554
+ if (stopped?.status === "stopped") {
6555
+ this.emit({ type: "status_changed", threadId, status: "stopped" });
6556
+ }
6543
6557
  this.emit({ type: "turn_finished", threadId, exitCode });
6544
6558
  } else {
6545
6559
  const failDetail = formatTurnExitError(exitCode, detail);
6546
6560
  const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
6547
- setStatus(
6561
+ const nextStatus = exitCode === 0 ? "idle" : "error";
6562
+ const written = writeLiveStatus(
6548
6563
  threadId,
6549
- exitCode === 0 ? "idle" : "error",
6564
+ nextStatus,
6550
6565
  exitCode === 0 || explainedInChat ? null : failDetail
6551
6566
  );
6552
- this.emit({
6553
- type: "status_changed",
6554
- threadId,
6555
- status: exitCode === 0 ? "idle" : "error"
6556
- });
6567
+ if (written && written.status !== "archived") {
6568
+ this.emit({
6569
+ type: "status_changed",
6570
+ threadId,
6571
+ status: written.status
6572
+ });
6573
+ }
6557
6574
  this.emit({ type: "turn_finished", threadId, exitCode });
6558
6575
  if (exitCode !== 0) {
6559
6576
  const blob = [chatText, detail].filter(Boolean).join("\n");
@@ -6564,13 +6581,17 @@ var Orchestrator = class {
6564
6581
  const message = err instanceof Error ? err.message : String(err);
6565
6582
  await syncThreadBranchFromGit(threadId).catch(() => void 0);
6566
6583
  if (this.stoppedTurns.has(threadId)) {
6567
- setStatus(threadId, "stopped");
6568
- this.emit({ type: "status_changed", threadId, status: "stopped" });
6584
+ const stopped = writeLiveStatus(threadId, "stopped");
6585
+ if (stopped?.status === "stopped") {
6586
+ this.emit({ type: "status_changed", threadId, status: "stopped" });
6587
+ }
6569
6588
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
6570
6589
  } else {
6571
- setStatus(threadId, "error", message);
6572
- this.emit({ type: "error", threadId, message });
6573
- this.emit({ type: "status_changed", threadId, status: "error" });
6590
+ const written = writeLiveStatus(threadId, "error", message);
6591
+ if (written?.status === "error") {
6592
+ this.emit({ type: "error", threadId, message });
6593
+ this.emit({ type: "status_changed", threadId, status: "error" });
6594
+ }
6574
6595
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
6575
6596
  void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
6576
6597
  }
@@ -6617,9 +6638,11 @@ var Orchestrator = class {
6617
6638
  if (handle) handle.kill();
6618
6639
  const proc = this.processes.get(`${thread.id}:agent`);
6619
6640
  if (proc) proc.kill();
6620
- setStatus(thread.id, "stopped");
6621
- this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
6622
- return this.requireThread(thread.id);
6641
+ const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
6642
+ if (stopped.status === "stopped") {
6643
+ this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
6644
+ }
6645
+ return stopped;
6623
6646
  }
6624
6647
  async startDev(threadRef, scriptName) {
6625
6648
  const thread = this.requireThread(threadRef);
@@ -6855,11 +6878,21 @@ var Orchestrator = class {
6855
6878
  const off = this.on((event) => {
6856
6879
  if (event.type === "turn_finished" && event.threadId === thread.id) {
6857
6880
  off();
6858
- resolve(this.requireThread(thread.id));
6881
+ const latest = readThread(thread.id);
6882
+ if (!latest) {
6883
+ reject(new Error(`Thread not found: ${thread.id}`));
6884
+ return;
6885
+ }
6886
+ resolve(latest);
6859
6887
  }
6860
6888
  if (event.type === "error" && event.threadId === thread.id) {
6861
6889
  off();
6862
- resolve(this.requireThread(thread.id));
6890
+ const latest = readThread(thread.id);
6891
+ if (!latest) {
6892
+ reject(new Error(`Thread not found: ${thread.id}`));
6893
+ return;
6894
+ }
6895
+ resolve(latest);
6863
6896
  }
6864
6897
  });
6865
6898
  const timer = setInterval(() => {
@@ -6869,7 +6902,13 @@ var Orchestrator = class {
6869
6902
  reject(new Error("wait_for_turn timed out"));
6870
6903
  }
6871
6904
  const current = readThread(thread.id);
6872
- if (current && !["running", "queued"].includes(current.status)) {
6905
+ if (!current) {
6906
+ clearInterval(timer);
6907
+ off();
6908
+ reject(new Error(`Thread not found: ${thread.id}`));
6909
+ return;
6910
+ }
6911
+ if (!["running", "queued"].includes(current.status)) {
6873
6912
  clearInterval(timer);
6874
6913
  off();
6875
6914
  resolve(current);
@@ -10444,10 +10483,10 @@ function slackInboundSuperseded(opts) {
10444
10483
  function interruptSlackCoordinatorForInbound(msg, agent, log = () => void 0) {
10445
10484
  const userId = msg.userId?.trim();
10446
10485
  if (!userId) return false;
10447
- const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
10448
- const fresh = readThread(coordinator.id) ?? coordinator;
10449
- if (fresh.status !== "running" && fresh.status !== "queued") return false;
10450
10486
  try {
10487
+ const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
10488
+ const fresh = readThread(coordinator.id) ?? coordinator;
10489
+ if (fresh.status !== "running" && fresh.status !== "queued") return false;
10451
10490
  getOrchestrator().stop(fresh.id, { clearQueue: true });
10452
10491
  log(
10453
10492
  `interrupt ${msg.kind} ${msg.ts} \u2192 coordinator ${fresh.id.slice(0, 8)} (${fresh.status})`
@@ -10487,7 +10526,7 @@ function slackReplyThreadTs(msg) {
10487
10526
  if (msg.kind === "mention") return msg.ts;
10488
10527
  return void 0;
10489
10528
  }
10490
- var SLACK_SEEN_REACTION = "+1";
10529
+ var SLACK_SEEN_REACTION = "eyes";
10491
10530
  function writeTokenForTeam(teamId) {
10492
10531
  const workspaces = listSlackWorkspacesRaw();
10493
10532
  const ws = workspaces.find((w) => w.team_id === teamId);
@@ -10653,7 +10692,7 @@ async function handleSlackInbound(msg, opts) {
10653
10692
  return;
10654
10693
  }
10655
10694
  const after = readThread(fresh.id);
10656
- if (after?.status === "stopped") {
10695
+ if (!after || after.status === "stopped" || after.status === "archived") {
10657
10696
  log(`turn finished ${msg.ts} (interrupted, skip post)`);
10658
10697
  return;
10659
10698
  }
@@ -10664,6 +10703,11 @@ async function handleSlackInbound(msg, opts) {
10664
10703
  return;
10665
10704
  }
10666
10705
  const errMsg = err instanceof Error ? err.message : String(err);
10706
+ const gone = /thread not found/i.test(errMsg);
10707
+ if (gone) {
10708
+ log(`turn finished ${msg.ts} (thread gone, skip post)`);
10709
+ return;
10710
+ }
10667
10711
  const timedOut = /timed out|timeout/i.test(errMsg);
10668
10712
  const errorReply = timedOut ? SLACK_LISTEN_TIMEOUT_REPLY : `Sideboard coordinator error: ${errMsg}`;
10669
10713
  await postSlackReply(msg, errorReply, opts).catch((postErr) => {
@@ -12124,23 +12124,24 @@ async function countUnpushedCommits(worktreePath) {
12124
12124
  reject: false
12125
12125
  });
12126
12126
  const branch = head.stdout.trim();
12127
+ if (branch && branch !== "HEAD") {
12128
+ const remote = await git(
12129
+ ["rev-list", "--count", `origin/${branch}..HEAD`],
12130
+ worktreePath,
12131
+ { reject: false }
12132
+ );
12133
+ if (remote.exitCode === 0) {
12134
+ const n2 = Number(remote.stdout.trim());
12135
+ return Number.isFinite(n2) ? n2 : 0;
12136
+ }
12137
+ }
12127
12138
  const upstream = await git(
12128
12139
  ["rev-list", "--count", "@{upstream}..HEAD"],
12129
12140
  worktreePath,
12130
12141
  { reject: false }
12131
12142
  );
12132
- if (upstream.exitCode === 0) {
12133
- const n2 = Number(upstream.stdout.trim());
12134
- return Number.isFinite(n2) ? n2 : 0;
12135
- }
12136
- if (!branch || branch === "HEAD") return 0;
12137
- const remote = await git(
12138
- ["rev-list", "--count", `origin/${branch}..HEAD`],
12139
- worktreePath,
12140
- { reject: false }
12141
- );
12142
- if (remote.exitCode !== 0) return 0;
12143
- const n = Number(remote.stdout.trim());
12143
+ if (upstream.exitCode !== 0) return 0;
12144
+ const n = Number(upstream.stdout.trim());
12144
12145
  return Number.isFinite(n) ? n : 0;
12145
12146
  }
12146
12147
  function splitCombinedDiff(stdout) {
@@ -13466,6 +13467,11 @@ function isPidAlive(pid) {
13466
13467
  return false;
13467
13468
  }
13468
13469
  }
13470
+ function writeLiveStatus(threadId, status, lastError) {
13471
+ const latest = readThread(threadId);
13472
+ if (!latest || latest.status === "archived") return latest;
13473
+ return setStatus(threadId, status, lastError);
13474
+ }
13469
13475
  var Orchestrator = class {
13470
13476
  events = new import_node_events.EventEmitter();
13471
13477
  processes = /* @__PURE__ */ new Map();
@@ -13771,6 +13777,9 @@ var Orchestrator = class {
13771
13777
  }
13772
13778
  async send(threadRef, prompt) {
13773
13779
  const thread = this.requireThread(threadRef);
13780
+ if (thread.status === "archived") {
13781
+ throw new Error(`Thread is archived: ${thread.id}`);
13782
+ }
13774
13783
  return withThreadLock(thread.id, async () => {
13775
13784
  const current = this.requireThread(thread.id);
13776
13785
  const queue = [...current.queue, prompt];
@@ -13864,7 +13873,7 @@ var Orchestrator = class {
13864
13873
  break;
13865
13874
  }
13866
13875
  const thread = readThread(threadId);
13867
- if (!thread || thread.queue.length === 0) {
13876
+ if (!thread || thread.status === "archived" || thread.queue.length === 0) {
13868
13877
  if (thread && thread.status === "queued") {
13869
13878
  setStatus(threadId, "idle");
13870
13879
  this.emit({ type: "status_changed", threadId, status: "idle" });
@@ -13895,14 +13904,17 @@ var Orchestrator = class {
13895
13904
  }
13896
13905
  }
13897
13906
  async runTurn(threadId, prompt) {
13898
- let thread = this.requireThread(threadId);
13907
+ const existing = readThread(threadId);
13908
+ if (!existing || existing.status === "archived") return;
13909
+ let thread = existing;
13899
13910
  if (isGlobalThread(thread) && thread.sessionId && orchestratorSessionPoisonedByBuiltins(thread)) {
13900
13911
  thread = updateThread(threadId, { sessionId: null });
13901
13912
  }
13913
+ const running = writeLiveStatus(threadId, "running");
13914
+ if (!running || running.status !== "running") return;
13902
13915
  this.runningCount += 1;
13903
13916
  const turnStartedAt = Date.now();
13904
13917
  this.startingTurns.add(threadId);
13905
- setStatus(threadId, "running");
13906
13918
  this.emit({ type: "status_changed", threadId, status: "running" });
13907
13919
  this.emit({ type: "turn_started", threadId, prompt });
13908
13920
  try {
@@ -14173,8 +14185,8 @@ var Orchestrator = class {
14173
14185
  ts: (/* @__PURE__ */ new Date()).toISOString()
14174
14186
  });
14175
14187
  }
14176
- const afterTurn = this.requireThread(threadId);
14177
- if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
14188
+ const afterTurn = readThread(threadId);
14189
+ if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.worktreePath?.trim()) {
14178
14190
  const presented = extractPresentedPlan(parts);
14179
14191
  const exited = parts.some(
14180
14192
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
@@ -14187,29 +14199,34 @@ var Orchestrator = class {
14187
14199
  }
14188
14200
  }
14189
14201
  }
14190
- if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
14202
+ if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
14191
14203
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
14192
14204
  )) {
14193
14205
  updateThread(threadId, { sessionId: null });
14194
14206
  }
14195
14207
  await syncThreadBranchFromGit(threadId);
14196
14208
  if (this.stoppedTurns.has(threadId)) {
14197
- setStatus(threadId, "stopped");
14198
- this.emit({ type: "status_changed", threadId, status: "stopped" });
14209
+ const stopped = writeLiveStatus(threadId, "stopped");
14210
+ if (stopped?.status === "stopped") {
14211
+ this.emit({ type: "status_changed", threadId, status: "stopped" });
14212
+ }
14199
14213
  this.emit({ type: "turn_finished", threadId, exitCode });
14200
14214
  } else {
14201
14215
  const failDetail = formatTurnExitError(exitCode, detail);
14202
14216
  const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
14203
- setStatus(
14217
+ const nextStatus = exitCode === 0 ? "idle" : "error";
14218
+ const written = writeLiveStatus(
14204
14219
  threadId,
14205
- exitCode === 0 ? "idle" : "error",
14220
+ nextStatus,
14206
14221
  exitCode === 0 || explainedInChat ? null : failDetail
14207
14222
  );
14208
- this.emit({
14209
- type: "status_changed",
14210
- threadId,
14211
- status: exitCode === 0 ? "idle" : "error"
14212
- });
14223
+ if (written && written.status !== "archived") {
14224
+ this.emit({
14225
+ type: "status_changed",
14226
+ threadId,
14227
+ status: written.status
14228
+ });
14229
+ }
14213
14230
  this.emit({ type: "turn_finished", threadId, exitCode });
14214
14231
  if (exitCode !== 0) {
14215
14232
  const blob = [chatText, detail].filter(Boolean).join("\n");
@@ -14220,13 +14237,17 @@ var Orchestrator = class {
14220
14237
  const message = err instanceof Error ? err.message : String(err);
14221
14238
  await syncThreadBranchFromGit(threadId).catch(() => void 0);
14222
14239
  if (this.stoppedTurns.has(threadId)) {
14223
- setStatus(threadId, "stopped");
14224
- this.emit({ type: "status_changed", threadId, status: "stopped" });
14240
+ const stopped = writeLiveStatus(threadId, "stopped");
14241
+ if (stopped?.status === "stopped") {
14242
+ this.emit({ type: "status_changed", threadId, status: "stopped" });
14243
+ }
14225
14244
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
14226
14245
  } else {
14227
- setStatus(threadId, "error", message);
14228
- this.emit({ type: "error", threadId, message });
14229
- this.emit({ type: "status_changed", threadId, status: "error" });
14246
+ const written = writeLiveStatus(threadId, "error", message);
14247
+ if (written?.status === "error") {
14248
+ this.emit({ type: "error", threadId, message });
14249
+ this.emit({ type: "status_changed", threadId, status: "error" });
14250
+ }
14230
14251
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
14231
14252
  void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
14232
14253
  }
@@ -14273,9 +14294,11 @@ var Orchestrator = class {
14273
14294
  if (handle) handle.kill();
14274
14295
  const proc = this.processes.get(`${thread.id}:agent`);
14275
14296
  if (proc) proc.kill();
14276
- setStatus(thread.id, "stopped");
14277
- this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
14278
- return this.requireThread(thread.id);
14297
+ const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
14298
+ if (stopped.status === "stopped") {
14299
+ this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
14300
+ }
14301
+ return stopped;
14279
14302
  }
14280
14303
  async startDev(threadRef, scriptName) {
14281
14304
  const thread = this.requireThread(threadRef);
@@ -14511,11 +14534,21 @@ var Orchestrator = class {
14511
14534
  const off = this.on((event) => {
14512
14535
  if (event.type === "turn_finished" && event.threadId === thread.id) {
14513
14536
  off();
14514
- resolve(this.requireThread(thread.id));
14537
+ const latest = readThread(thread.id);
14538
+ if (!latest) {
14539
+ reject(new Error(`Thread not found: ${thread.id}`));
14540
+ return;
14541
+ }
14542
+ resolve(latest);
14515
14543
  }
14516
14544
  if (event.type === "error" && event.threadId === thread.id) {
14517
14545
  off();
14518
- resolve(this.requireThread(thread.id));
14546
+ const latest = readThread(thread.id);
14547
+ if (!latest) {
14548
+ reject(new Error(`Thread not found: ${thread.id}`));
14549
+ return;
14550
+ }
14551
+ resolve(latest);
14519
14552
  }
14520
14553
  });
14521
14554
  const timer = setInterval(() => {
@@ -14525,7 +14558,13 @@ var Orchestrator = class {
14525
14558
  reject(new Error("wait_for_turn timed out"));
14526
14559
  }
14527
14560
  const current = readThread(thread.id);
14528
- if (current && !["running", "queued"].includes(current.status)) {
14561
+ if (!current) {
14562
+ clearInterval(timer);
14563
+ off();
14564
+ reject(new Error(`Thread not found: ${thread.id}`));
14565
+ return;
14566
+ }
14567
+ if (!["running", "queued"].includes(current.status)) {
14529
14568
  clearInterval(timer);
14530
14569
  off();
14531
14570
  resolve(current);
@@ -3300,23 +3300,24 @@ async function countUnpushedCommits(worktreePath) {
3300
3300
  reject: false
3301
3301
  });
3302
3302
  const branch = head.stdout.trim();
3303
+ if (branch && branch !== "HEAD") {
3304
+ const remote = await git(
3305
+ ["rev-list", "--count", `origin/${branch}..HEAD`],
3306
+ worktreePath,
3307
+ { reject: false }
3308
+ );
3309
+ if (remote.exitCode === 0) {
3310
+ const n2 = Number(remote.stdout.trim());
3311
+ return Number.isFinite(n2) ? n2 : 0;
3312
+ }
3313
+ }
3303
3314
  const upstream = await git(
3304
3315
  ["rev-list", "--count", "@{upstream}..HEAD"],
3305
3316
  worktreePath,
3306
3317
  { reject: false }
3307
3318
  );
3308
- if (upstream.exitCode === 0) {
3309
- const n2 = Number(upstream.stdout.trim());
3310
- return Number.isFinite(n2) ? n2 : 0;
3311
- }
3312
- if (!branch || branch === "HEAD") return 0;
3313
- const remote = await git(
3314
- ["rev-list", "--count", `origin/${branch}..HEAD`],
3315
- worktreePath,
3316
- { reject: false }
3317
- );
3318
- if (remote.exitCode !== 0) return 0;
3319
- const n = Number(remote.stdout.trim());
3319
+ if (upstream.exitCode !== 0) return 0;
3320
+ const n = Number(upstream.stdout.trim());
3320
3321
  return Number.isFinite(n) ? n : 0;
3321
3322
  }
3322
3323
  function splitCombinedDiff(stdout) {
@@ -4626,6 +4627,11 @@ function isPidAlive(pid) {
4626
4627
  return false;
4627
4628
  }
4628
4629
  }
4630
+ function writeLiveStatus(threadId, status, lastError) {
4631
+ const latest = readThread(threadId);
4632
+ if (!latest || latest.status === "archived") return latest;
4633
+ return setStatus(threadId, status, lastError);
4634
+ }
4629
4635
  var Orchestrator = class {
4630
4636
  events = new EventEmitter();
4631
4637
  processes = /* @__PURE__ */ new Map();
@@ -4931,6 +4937,9 @@ var Orchestrator = class {
4931
4937
  }
4932
4938
  async send(threadRef, prompt) {
4933
4939
  const thread = this.requireThread(threadRef);
4940
+ if (thread.status === "archived") {
4941
+ throw new Error(`Thread is archived: ${thread.id}`);
4942
+ }
4934
4943
  return withThreadLock(thread.id, async () => {
4935
4944
  const current = this.requireThread(thread.id);
4936
4945
  const queue = [...current.queue, prompt];
@@ -5024,7 +5033,7 @@ var Orchestrator = class {
5024
5033
  break;
5025
5034
  }
5026
5035
  const thread = readThread(threadId);
5027
- if (!thread || thread.queue.length === 0) {
5036
+ if (!thread || thread.status === "archived" || thread.queue.length === 0) {
5028
5037
  if (thread && thread.status === "queued") {
5029
5038
  setStatus(threadId, "idle");
5030
5039
  this.emit({ type: "status_changed", threadId, status: "idle" });
@@ -5055,14 +5064,17 @@ var Orchestrator = class {
5055
5064
  }
5056
5065
  }
5057
5066
  async runTurn(threadId, prompt) {
5058
- let thread = this.requireThread(threadId);
5067
+ const existing = readThread(threadId);
5068
+ if (!existing || existing.status === "archived") return;
5069
+ let thread = existing;
5059
5070
  if (isGlobalThread(thread) && thread.sessionId && orchestratorSessionPoisonedByBuiltins(thread)) {
5060
5071
  thread = updateThread(threadId, { sessionId: null });
5061
5072
  }
5073
+ const running = writeLiveStatus(threadId, "running");
5074
+ if (!running || running.status !== "running") return;
5062
5075
  this.runningCount += 1;
5063
5076
  const turnStartedAt = Date.now();
5064
5077
  this.startingTurns.add(threadId);
5065
- setStatus(threadId, "running");
5066
5078
  this.emit({ type: "status_changed", threadId, status: "running" });
5067
5079
  this.emit({ type: "turn_started", threadId, prompt });
5068
5080
  try {
@@ -5333,8 +5345,8 @@ var Orchestrator = class {
5333
5345
  ts: (/* @__PURE__ */ new Date()).toISOString()
5334
5346
  });
5335
5347
  }
5336
- const afterTurn = this.requireThread(threadId);
5337
- if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
5348
+ const afterTurn = readThread(threadId);
5349
+ if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.worktreePath?.trim()) {
5338
5350
  const presented = extractPresentedPlan(parts);
5339
5351
  const exited = parts.some(
5340
5352
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
@@ -5347,29 +5359,34 @@ var Orchestrator = class {
5347
5359
  }
5348
5360
  }
5349
5361
  }
5350
- if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
5362
+ if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
5351
5363
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
5352
5364
  )) {
5353
5365
  updateThread(threadId, { sessionId: null });
5354
5366
  }
5355
5367
  await syncThreadBranchFromGit(threadId);
5356
5368
  if (this.stoppedTurns.has(threadId)) {
5357
- setStatus(threadId, "stopped");
5358
- this.emit({ type: "status_changed", threadId, status: "stopped" });
5369
+ const stopped = writeLiveStatus(threadId, "stopped");
5370
+ if (stopped?.status === "stopped") {
5371
+ this.emit({ type: "status_changed", threadId, status: "stopped" });
5372
+ }
5359
5373
  this.emit({ type: "turn_finished", threadId, exitCode });
5360
5374
  } else {
5361
5375
  const failDetail = formatTurnExitError(exitCode, detail);
5362
5376
  const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
5363
- setStatus(
5377
+ const nextStatus = exitCode === 0 ? "idle" : "error";
5378
+ const written = writeLiveStatus(
5364
5379
  threadId,
5365
- exitCode === 0 ? "idle" : "error",
5380
+ nextStatus,
5366
5381
  exitCode === 0 || explainedInChat ? null : failDetail
5367
5382
  );
5368
- this.emit({
5369
- type: "status_changed",
5370
- threadId,
5371
- status: exitCode === 0 ? "idle" : "error"
5372
- });
5383
+ if (written && written.status !== "archived") {
5384
+ this.emit({
5385
+ type: "status_changed",
5386
+ threadId,
5387
+ status: written.status
5388
+ });
5389
+ }
5373
5390
  this.emit({ type: "turn_finished", threadId, exitCode });
5374
5391
  if (exitCode !== 0) {
5375
5392
  const blob = [chatText, detail].filter(Boolean).join("\n");
@@ -5380,13 +5397,17 @@ var Orchestrator = class {
5380
5397
  const message = err instanceof Error ? err.message : String(err);
5381
5398
  await syncThreadBranchFromGit(threadId).catch(() => void 0);
5382
5399
  if (this.stoppedTurns.has(threadId)) {
5383
- setStatus(threadId, "stopped");
5384
- this.emit({ type: "status_changed", threadId, status: "stopped" });
5400
+ const stopped = writeLiveStatus(threadId, "stopped");
5401
+ if (stopped?.status === "stopped") {
5402
+ this.emit({ type: "status_changed", threadId, status: "stopped" });
5403
+ }
5385
5404
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
5386
5405
  } else {
5387
- setStatus(threadId, "error", message);
5388
- this.emit({ type: "error", threadId, message });
5389
- this.emit({ type: "status_changed", threadId, status: "error" });
5406
+ const written = writeLiveStatus(threadId, "error", message);
5407
+ if (written?.status === "error") {
5408
+ this.emit({ type: "error", threadId, message });
5409
+ this.emit({ type: "status_changed", threadId, status: "error" });
5410
+ }
5390
5411
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
5391
5412
  void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
5392
5413
  }
@@ -5433,9 +5454,11 @@ var Orchestrator = class {
5433
5454
  if (handle) handle.kill();
5434
5455
  const proc = this.processes.get(`${thread.id}:agent`);
5435
5456
  if (proc) proc.kill();
5436
- setStatus(thread.id, "stopped");
5437
- this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
5438
- return this.requireThread(thread.id);
5457
+ const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
5458
+ if (stopped.status === "stopped") {
5459
+ this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
5460
+ }
5461
+ return stopped;
5439
5462
  }
5440
5463
  async startDev(threadRef, scriptName) {
5441
5464
  const thread = this.requireThread(threadRef);
@@ -5671,11 +5694,21 @@ var Orchestrator = class {
5671
5694
  const off = this.on((event) => {
5672
5695
  if (event.type === "turn_finished" && event.threadId === thread.id) {
5673
5696
  off();
5674
- resolve(this.requireThread(thread.id));
5697
+ const latest = readThread(thread.id);
5698
+ if (!latest) {
5699
+ reject(new Error(`Thread not found: ${thread.id}`));
5700
+ return;
5701
+ }
5702
+ resolve(latest);
5675
5703
  }
5676
5704
  if (event.type === "error" && event.threadId === thread.id) {
5677
5705
  off();
5678
- resolve(this.requireThread(thread.id));
5706
+ const latest = readThread(thread.id);
5707
+ if (!latest) {
5708
+ reject(new Error(`Thread not found: ${thread.id}`));
5709
+ return;
5710
+ }
5711
+ resolve(latest);
5679
5712
  }
5680
5713
  });
5681
5714
  const timer = setInterval(() => {
@@ -5685,7 +5718,13 @@ var Orchestrator = class {
5685
5718
  reject(new Error("wait_for_turn timed out"));
5686
5719
  }
5687
5720
  const current = readThread(thread.id);
5688
- if (current && !["running", "queued"].includes(current.status)) {
5721
+ if (!current) {
5722
+ clearInterval(timer);
5723
+ off();
5724
+ reject(new Error(`Thread not found: ${thread.id}`));
5725
+ return;
5726
+ }
5727
+ if (!["running", "queued"].includes(current.status)) {
5689
5728
  clearInterval(timer);
5690
5729
  off();
5691
5730
  resolve(current);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.95",
3
+ "version": "0.1.97",
4
4
  "description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",