@sideboard-ai/core 0.1.52 → 0.1.53

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.
@@ -171,6 +171,33 @@ function localAgentStore() {
171
171
  (0, import_node_fs3.mkdirSync)(root, { recursive: true });
172
172
  return new import_sdk.JsonlLocalAgentStore(root);
173
173
  }
174
+ function isAgentBusyError(err) {
175
+ if (err instanceof import_sdk.AgentBusyError) return true;
176
+ const message = err instanceof Error ? err.message : String(err);
177
+ return /already has active run/i.test(message);
178
+ }
179
+ async function cancelStaleLocalRuns(agentId, opts) {
180
+ const listed = await import_sdk.Agent.listRuns(agentId, {
181
+ runtime: "local",
182
+ cwd: opts.cwd,
183
+ store: opts.store,
184
+ limit: 20
185
+ });
186
+ let cancelled = 0;
187
+ for (const run of listed.items) {
188
+ if (run.status !== "running") continue;
189
+ try {
190
+ await import_sdk.Agent.cancelRun(run.id, {
191
+ runtime: "local",
192
+ cwd: opts.cwd,
193
+ store: opts.store
194
+ });
195
+ cancelled += 1;
196
+ } catch {
197
+ }
198
+ }
199
+ return cancelled;
200
+ }
174
201
  async function readStdinJson() {
175
202
  const rl = (0, import_node_readline.createInterface)({ input: process.stdin, crlfDelay: Infinity });
176
203
  const chunks = [];
@@ -242,10 +269,22 @@ async function main() {
242
269
  }
243
270
  try {
244
271
  emit({ type: "session_id", data: agent.agentId });
245
- const run = await agent.send(
246
- req.prompt,
247
- mcpServers ? { mcpServers } : void 0
248
- );
272
+ const sendOpts = mcpServers ? { mcpServers } : void 0;
273
+ let run;
274
+ try {
275
+ run = await agent.send(req.prompt, sendOpts);
276
+ } catch (err) {
277
+ if (!isAgentBusyError(err)) throw err;
278
+ const n = await cancelStaleLocalRuns(agent.agentId, {
279
+ cwd: req.cwd,
280
+ store
281
+ });
282
+ emit({
283
+ type: "stderr",
284
+ data: n > 0 ? `Cursor agent had ${n} stale active run(s) \u2014 cancelled and retrying` : "Cursor agent busy \u2014 retrying send"
285
+ });
286
+ run = await agent.send(req.prompt, sendOpts);
287
+ }
249
288
  for await (const msg of run.stream()) {
250
289
  for (const event of cursorSdkMessageToEvents(msg)) {
251
290
  emit(event);
@@ -2,13 +2,13 @@
2
2
  import {
3
3
  cursorSdkMessageToEvents,
4
4
  formatUnknownDetail
5
- } from "../chunk-J5JTEJ5O.js";
5
+ } from "../chunk-VG22SETP.js";
6
6
  import {
7
7
  appDataDir
8
8
  } from "../chunk-M37RITA6.js";
9
9
 
10
10
  // src/agents/cursor-runner.ts
11
- import { Agent, CursorAgentError, JsonlLocalAgentStore } from "@cursor/sdk";
11
+ import { Agent, AgentBusyError, CursorAgentError, JsonlLocalAgentStore } from "@cursor/sdk";
12
12
  import { mkdirSync } from "fs";
13
13
  import { join } from "path";
14
14
  import { createInterface } from "readline";
@@ -35,6 +35,33 @@ function localAgentStore() {
35
35
  mkdirSync(root, { recursive: true });
36
36
  return new JsonlLocalAgentStore(root);
37
37
  }
38
+ function isAgentBusyError(err) {
39
+ if (err instanceof AgentBusyError) return true;
40
+ const message = err instanceof Error ? err.message : String(err);
41
+ return /already has active run/i.test(message);
42
+ }
43
+ async function cancelStaleLocalRuns(agentId, opts) {
44
+ const listed = await Agent.listRuns(agentId, {
45
+ runtime: "local",
46
+ cwd: opts.cwd,
47
+ store: opts.store,
48
+ limit: 20
49
+ });
50
+ let cancelled = 0;
51
+ for (const run of listed.items) {
52
+ if (run.status !== "running") continue;
53
+ try {
54
+ await Agent.cancelRun(run.id, {
55
+ runtime: "local",
56
+ cwd: opts.cwd,
57
+ store: opts.store
58
+ });
59
+ cancelled += 1;
60
+ } catch {
61
+ }
62
+ }
63
+ return cancelled;
64
+ }
38
65
  async function readStdinJson() {
39
66
  const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
40
67
  const chunks = [];
@@ -106,10 +133,22 @@ async function main() {
106
133
  }
107
134
  try {
108
135
  emit({ type: "session_id", data: agent.agentId });
109
- const run = await agent.send(
110
- req.prompt,
111
- mcpServers ? { mcpServers } : void 0
112
- );
136
+ const sendOpts = mcpServers ? { mcpServers } : void 0;
137
+ let run;
138
+ try {
139
+ run = await agent.send(req.prompt, sendOpts);
140
+ } catch (err) {
141
+ if (!isAgentBusyError(err)) throw err;
142
+ const n = await cancelStaleLocalRuns(agent.agentId, {
143
+ cwd: req.cwd,
144
+ store
145
+ });
146
+ emit({
147
+ type: "stderr",
148
+ data: n > 0 ? `Cursor agent had ${n} stale active run(s) \u2014 cancelled and retrying` : "Cursor agent busy \u2014 retrying send"
149
+ });
150
+ run = await agent.send(req.prompt, sendOpts);
151
+ }
113
152
  for await (const msg of run.stream()) {
114
153
  for (const event of cursorSdkMessageToEvents(msg)) {
115
154
  emit(event);
@@ -30,7 +30,7 @@ import {
30
30
  permissionMode,
31
31
  resolveCursorModelId,
32
32
  resolveQuotaFallbackAgent
33
- } from "./chunk-6QTZVJ7A.js";
33
+ } from "./chunk-BZST4HMJ.js";
34
34
  import "./chunk-H6GGDLYS.js";
35
35
  import "./chunk-HBJSHRY2.js";
36
36
  import {
@@ -26,7 +26,7 @@ import {
26
26
  permissionMode,
27
27
  resolveCursorModelId,
28
28
  resolveQuotaFallbackAgent
29
- } from "./chunk-D3METLRW.js";
29
+ } from "./chunk-A6HVEMIB.js";
30
30
  import {
31
31
  ORCHESTRATOR_AGENT_KINDS,
32
32
  assertOrchestratorCapableAgent,
@@ -37,7 +37,7 @@ import "./chunk-BXJ76RHF.js";
37
37
  import {
38
38
  cursorSdkMessageToEvents,
39
39
  parseCursorRunnerLine
40
- } from "./chunk-J5JTEJ5O.js";
40
+ } from "./chunk-VG22SETP.js";
41
41
  import "./chunk-T5QQVXK3.js";
42
42
  import "./chunk-77WWLBCI.js";
43
43
  import "./chunk-M37RITA6.js";
@@ -10,7 +10,7 @@ import {
10
10
  formatUnknownDetail,
11
11
  looksLikeAgentFailureMessage,
12
12
  parseCursorRunnerLine
13
- } from "./chunk-J5JTEJ5O.js";
13
+ } from "./chunk-VG22SETP.js";
14
14
  import {
15
15
  claudeChromeEnabled,
16
16
  loadAppSettings,
@@ -1609,6 +1609,7 @@ var opencodeAdapter = {
1609
1609
  }
1610
1610
  },
1611
1611
  async resolveSessionId(worktreePath, cached) {
1612
+ const cachedId = cached?.trim() || null;
1612
1613
  const listed = await run(
1613
1614
  "opencode",
1614
1615
  ["session", "list", "--format", "json"],
@@ -1620,15 +1621,21 @@ var opencodeAdapter = {
1620
1621
  if (Array.isArray(sessions) && sessions.length > 0) {
1621
1622
  const norm = (p) => p.replace(/\/+$/, "");
1622
1623
  const wt = norm(worktreePath);
1623
- const match = sessions.find(
1624
+ const forWorktree = sessions.filter(
1624
1625
  (s) => s.directory && norm(s.directory) === wt || s.path && norm(s.path) === wt
1625
1626
  );
1626
- if (match?.id) return match.id;
1627
+ if (cachedId && forWorktree.some((s) => s.id === cachedId)) {
1628
+ return cachedId;
1629
+ }
1630
+ if (cachedId && sessions.some((s) => s.id === cachedId)) {
1631
+ return cachedId;
1632
+ }
1633
+ return null;
1627
1634
  }
1628
1635
  } catch {
1629
1636
  }
1630
1637
  }
1631
- return cached;
1638
+ return cachedId;
1632
1639
  },
1633
1640
  async buildAttach(thread) {
1634
1641
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
@@ -109,6 +109,11 @@ function summarizeTurnStderr(tail, maxChars = 500) {
109
109
  if (joined.length <= maxChars) return joined;
110
110
  return joined.slice(joined.length - maxChars);
111
111
  }
112
+ function looksLikeInvalidAgentSession(text) {
113
+ const lower = text.trim().toLowerCase();
114
+ if (!lower) return false;
115
+ return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower);
116
+ }
112
117
  function looksLikeAgentFailureMessage(text) {
113
118
  const lower = text.trim().toLowerCase();
114
119
  if (!lower) return false;
@@ -1766,6 +1771,7 @@ var opencodeAdapter = {
1766
1771
  }
1767
1772
  },
1768
1773
  async resolveSessionId(worktreePath, cached) {
1774
+ const cachedId = cached?.trim() || null;
1769
1775
  const listed = await run(
1770
1776
  "opencode",
1771
1777
  ["session", "list", "--format", "json"],
@@ -1777,15 +1783,21 @@ var opencodeAdapter = {
1777
1783
  if (Array.isArray(sessions) && sessions.length > 0) {
1778
1784
  const norm = (p) => p.replace(/\/+$/, "");
1779
1785
  const wt = norm(worktreePath);
1780
- const match = sessions.find(
1786
+ const forWorktree = sessions.filter(
1781
1787
  (s) => s.directory && norm(s.directory) === wt || s.path && norm(s.path) === wt
1782
1788
  );
1783
- if (match?.id) return match.id;
1789
+ if (cachedId && forWorktree.some((s) => s.id === cachedId)) {
1790
+ return cachedId;
1791
+ }
1792
+ if (cachedId && sessions.some((s) => s.id === cachedId)) {
1793
+ return cachedId;
1794
+ }
1795
+ return null;
1784
1796
  }
1785
1797
  } catch {
1786
1798
  }
1787
1799
  }
1788
- return cached;
1800
+ return cachedId;
1789
1801
  },
1790
1802
  async buildAttach(thread) {
1791
1803
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
@@ -2197,6 +2209,7 @@ function allAdapters() {
2197
2209
  export {
2198
2210
  pushTurnStderr,
2199
2211
  summarizeTurnStderr,
2212
+ looksLikeInvalidAgentSession,
2200
2213
  looksLikeAgentFailureMessage,
2201
2214
  fallbackTurnFailDetail,
2202
2215
  humanizeAgentFailDetail,
@@ -60,6 +60,11 @@ function summarizeTurnStderr(tail, maxChars = 500) {
60
60
  if (joined.length <= maxChars) return joined;
61
61
  return joined.slice(joined.length - maxChars);
62
62
  }
63
+ function looksLikeInvalidAgentSession(text) {
64
+ const lower = text.trim().toLowerCase();
65
+ if (!lower) return false;
66
+ return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower);
67
+ }
63
68
  function looksLikeAgentFailureMessage(text) {
64
69
  const lower = text.trim().toLowerCase();
65
70
  if (!lower) return false;
@@ -212,6 +217,7 @@ export {
212
217
  extractJsonErrorMessage,
213
218
  pushTurnStderr,
214
219
  summarizeTurnStderr,
220
+ looksLikeInvalidAgentSession,
215
221
  looksLikeAgentFailureMessage,
216
222
  fallbackTurnFailDetail,
217
223
  humanizeAgentFailDetail,
package/dist/index.cjs CHANGED
@@ -4316,6 +4316,11 @@ function summarizeTurnStderr(tail, maxChars = 500) {
4316
4316
  if (joined.length <= maxChars) return joined;
4317
4317
  return joined.slice(joined.length - maxChars);
4318
4318
  }
4319
+ function looksLikeInvalidAgentSession(text) {
4320
+ const lower = text.trim().toLowerCase();
4321
+ if (!lower) return false;
4322
+ return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower);
4323
+ }
4319
4324
  function looksLikeAgentFailureMessage(text) {
4320
4325
  const lower = text.trim().toLowerCase();
4321
4326
  if (!lower) return false;
@@ -6124,6 +6129,7 @@ var init_opencode = __esm({
6124
6129
  }
6125
6130
  },
6126
6131
  async resolveSessionId(worktreePath, cached) {
6132
+ const cachedId = cached?.trim() || null;
6127
6133
  const listed = await run(
6128
6134
  "opencode",
6129
6135
  ["session", "list", "--format", "json"],
@@ -6135,15 +6141,21 @@ var init_opencode = __esm({
6135
6141
  if (Array.isArray(sessions) && sessions.length > 0) {
6136
6142
  const norm = (p) => p.replace(/\/+$/, "");
6137
6143
  const wt = norm(worktreePath);
6138
- const match = sessions.find(
6144
+ const forWorktree = sessions.filter(
6139
6145
  (s) => s.directory && norm(s.directory) === wt || s.path && norm(s.path) === wt
6140
6146
  );
6141
- if (match?.id) return match.id;
6147
+ if (cachedId && forWorktree.some((s) => s.id === cachedId)) {
6148
+ return cachedId;
6149
+ }
6150
+ if (cachedId && sessions.some((s) => s.id === cachedId)) {
6151
+ return cachedId;
6152
+ }
6153
+ return null;
6142
6154
  }
6143
6155
  } catch {
6144
6156
  }
6145
6157
  }
6146
- return cached;
6158
+ return cachedId;
6147
6159
  },
6148
6160
  async buildAttach(thread) {
6149
6161
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
@@ -11570,21 +11582,30 @@ var Orchestrator = class {
11570
11582
  return findThreadByRef(idOrRef) ?? readThread(idOrRef);
11571
11583
  }
11572
11584
  async createThread(input) {
11573
- let thread = await createThread(input);
11585
+ const thread = await createThread(input);
11574
11586
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
11575
- await this.runSetupAfterCreate(thread.id);
11587
+ void this.finishCreateThread(thread.id, input.prompt?.trim() || void 0);
11588
+ return thread;
11589
+ }
11590
+ async finishCreateThread(threadId, prompt) {
11591
+ await this.runSetupAfterCreate(threadId);
11576
11592
  const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
11577
11593
  if (autoRunAfterSetupEnabled2()) {
11578
11594
  try {
11579
- await this.startDev(thread.id);
11595
+ await this.startDev(threadId);
11580
11596
  } catch {
11581
11597
  }
11582
11598
  }
11583
- const prompt = input.prompt?.trim();
11584
11599
  if (prompt) {
11585
- thread = await this.send(thread.id, prompt);
11600
+ try {
11601
+ await this.send(threadId, prompt);
11602
+ } catch (err) {
11603
+ const message = err instanceof Error ? err.message : String(err);
11604
+ updateThread(threadId, {
11605
+ lastError: `First prompt failed: ${message}`
11606
+ });
11607
+ }
11586
11608
  }
11587
- return thread;
11588
11609
  }
11589
11610
  /** Run workspace setup after a new worktree is created (no-op if none configured). */
11590
11611
  async runSetupAfterCreate(threadId) {
@@ -11930,8 +11951,73 @@ var Orchestrator = class {
11930
11951
  }
11931
11952
  }
11932
11953
  }
11933
- const lastStderr = summarizeTurnStderr(stderrTail);
11934
- const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
11954
+ let lastStderr = summarizeTurnStderr(stderrTail);
11955
+ let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
11956
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && looksLikeInvalidAgentSession(detail) && this.requireThread(threadId).sessionId && this.requireThread(threadId).agent !== "cursor" && this.requireThread(threadId).agent !== "brightsy") {
11957
+ updateThread(threadId, { sessionId: null });
11958
+ pushTurnStderr(
11959
+ stderrTail,
11960
+ "Agent session missing \u2014 starting a fresh session"
11961
+ );
11962
+ this.emit({
11963
+ type: "turn_output",
11964
+ threadId,
11965
+ event: {
11966
+ type: "stderr",
11967
+ data: "Agent session missing \u2014 starting a fresh session"
11968
+ }
11969
+ });
11970
+ const retryThread = this.requireThread(threadId);
11971
+ const prior = retryThread.messages.slice(0, -1);
11972
+ const retrySeed = buildSessionSeed(prior);
11973
+ const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
11974
+ loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
11975
+ );
11976
+ const retryPrefix = [
11977
+ coordinatorDirective,
11978
+ worktreeDirective,
11979
+ artifactDirective,
11980
+ renameBranchDirective,
11981
+ retryInstructions,
11982
+ retrySeed
11983
+ ].filter(Boolean).join("\n\n---\n\n");
11984
+ const retryHandle = await spawnAgentTurn(
11985
+ retryThread,
11986
+ { cachedPrefix: retryPrefix, prompt: agentPrompt },
11987
+ (event) => {
11988
+ this.emit({ type: "turn_output", threadId, event });
11989
+ if (event.type === "session_id") {
11990
+ updateThread(threadId, { sessionId: event.data });
11991
+ }
11992
+ if (event.type === "stderr" && typeof event.data === "string") {
11993
+ pushTurnStderr(stderrTail, event.data);
11994
+ }
11995
+ }
11996
+ );
11997
+ this.activeTurns.set(threadId, retryHandle);
11998
+ if (typeof retryHandle.pid === "number" && retryHandle.pid > 0) {
11999
+ updateThread(threadId, { agentPid: retryHandle.pid });
12000
+ }
12001
+ this.processes.set(`${threadId}:agent`, {
12002
+ kind: "agent",
12003
+ pid: retryHandle.pid,
12004
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
12005
+ kill: retryHandle.kill
12006
+ });
12007
+ if (this.stoppedTurns.has(threadId)) {
12008
+ retryHandle.kill();
12009
+ }
12010
+ const retryResult = await retryHandle.done;
12011
+ if (retryResult.sessionId) {
12012
+ updateThread(threadId, { sessionId: retryResult.sessionId });
12013
+ }
12014
+ assistantText = retryResult.assistantText.trim();
12015
+ parts = retryResult.parts;
12016
+ usage = retryResult.usage ?? void 0;
12017
+ exitCode = retryResult.exitCode;
12018
+ lastStderr = summarizeTurnStderr(stderrTail);
12019
+ detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
12020
+ }
11935
12021
  let chatText = assistantText;
11936
12022
  if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
11937
12023
  chatText = humanizeAgentFailDetail(detail);
package/dist/index.d.cts CHANGED
@@ -2223,6 +2223,7 @@ declare class Orchestrator {
2223
2223
  getThreads(includeArchived?: boolean): Thread[];
2224
2224
  getThread(idOrRef: string): Thread | null;
2225
2225
  createThread(input: CreateThreadInput): Promise<Thread>;
2226
+ private finishCreateThread;
2226
2227
  /** Run workspace setup after a new worktree is created (no-op if none configured). */
2227
2228
  private runSetupAfterCreate;
2228
2229
  listWorkspaces(): Workspace[];
package/dist/index.d.ts CHANGED
@@ -2223,6 +2223,7 @@ declare class Orchestrator {
2223
2223
  getThreads(includeArchived?: boolean): Thread[];
2224
2224
  getThread(idOrRef: string): Thread | null;
2225
2225
  createThread(input: CreateThreadInput): Promise<Thread>;
2226
+ private finishCreateThread;
2226
2227
  /** Run workspace setup after a new worktree is created (no-op if none configured). */
2227
2228
  private runSetupAfterCreate;
2228
2229
  listWorkspaces(): Workspace[];
package/dist/index.js CHANGED
@@ -70,7 +70,7 @@ import {
70
70
  resolveQuotaFallbackAgent,
71
71
  sanitizeMcpServerName,
72
72
  writeInjectedMcpConfig
73
- } from "./chunk-D3METLRW.js";
73
+ } from "./chunk-A6HVEMIB.js";
74
74
  import {
75
75
  ORCHESTRATOR_AGENT_KINDS,
76
76
  assertOrchestratorCapableAgent,
@@ -106,10 +106,11 @@ import {
106
106
  formatTurnExitError,
107
107
  humanizeAgentFailDetail,
108
108
  looksLikeAgentFailureMessage,
109
+ looksLikeInvalidAgentSession,
109
110
  parseCursorRunnerLine,
110
111
  pushTurnStderr,
111
112
  summarizeTurnStderr
112
- } from "./chunk-J5JTEJ5O.js";
113
+ } from "./chunk-VG22SETP.js";
113
114
  import {
114
115
  HARNESS_ENV_KEYS,
115
116
  appSettingsPath,
@@ -2941,7 +2942,7 @@ async function createThread(input, _onSetupLine) {
2941
2942
  return readThread(thread.id) ?? thread;
2942
2943
  }
2943
2944
  async function listLinearIssues(agent, repoPath) {
2944
- const { getAdapter: getAdapter2 } = await import("./agents-YKSS6VBO.js");
2945
+ const { getAdapter: getAdapter2 } = await import("./agents-KYACODJ3.js");
2945
2946
  await requireAgent(agent, { requireLinear: true });
2946
2947
  const adapter = getAdapter2(agent);
2947
2948
  if (!adapter.listLinearIssues) {
@@ -4544,21 +4545,30 @@ var Orchestrator = class {
4544
4545
  return findThreadByRef(idOrRef) ?? readThread(idOrRef);
4545
4546
  }
4546
4547
  async createThread(input) {
4547
- let thread = await createThread(input);
4548
+ const thread = await createThread(input);
4548
4549
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
4549
- await this.runSetupAfterCreate(thread.id);
4550
+ void this.finishCreateThread(thread.id, input.prompt?.trim() || void 0);
4551
+ return thread;
4552
+ }
4553
+ async finishCreateThread(threadId, prompt) {
4554
+ await this.runSetupAfterCreate(threadId);
4550
4555
  const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-LZP632KI.js");
4551
4556
  if (autoRunAfterSetupEnabled2()) {
4552
4557
  try {
4553
- await this.startDev(thread.id);
4558
+ await this.startDev(threadId);
4554
4559
  } catch {
4555
4560
  }
4556
4561
  }
4557
- const prompt = input.prompt?.trim();
4558
4562
  if (prompt) {
4559
- thread = await this.send(thread.id, prompt);
4563
+ try {
4564
+ await this.send(threadId, prompt);
4565
+ } catch (err) {
4566
+ const message = err instanceof Error ? err.message : String(err);
4567
+ updateThread(threadId, {
4568
+ lastError: `First prompt failed: ${message}`
4569
+ });
4570
+ }
4560
4571
  }
4561
- return thread;
4562
4572
  }
4563
4573
  /** Run workspace setup after a new worktree is created (no-op if none configured). */
4564
4574
  async runSetupAfterCreate(threadId) {
@@ -4904,8 +4914,73 @@ var Orchestrator = class {
4904
4914
  }
4905
4915
  }
4906
4916
  }
4907
- const lastStderr = summarizeTurnStderr(stderrTail);
4908
- const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
4917
+ let lastStderr = summarizeTurnStderr(stderrTail);
4918
+ let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
4919
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && looksLikeInvalidAgentSession(detail) && this.requireThread(threadId).sessionId && this.requireThread(threadId).agent !== "cursor" && this.requireThread(threadId).agent !== "brightsy") {
4920
+ updateThread(threadId, { sessionId: null });
4921
+ pushTurnStderr(
4922
+ stderrTail,
4923
+ "Agent session missing \u2014 starting a fresh session"
4924
+ );
4925
+ this.emit({
4926
+ type: "turn_output",
4927
+ threadId,
4928
+ event: {
4929
+ type: "stderr",
4930
+ data: "Agent session missing \u2014 starting a fresh session"
4931
+ }
4932
+ });
4933
+ const retryThread = this.requireThread(threadId);
4934
+ const prior = retryThread.messages.slice(0, -1);
4935
+ const retrySeed = buildSessionSeed(prior);
4936
+ const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
4937
+ loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
4938
+ );
4939
+ const retryPrefix = [
4940
+ coordinatorDirective,
4941
+ worktreeDirective,
4942
+ artifactDirective,
4943
+ renameBranchDirective,
4944
+ retryInstructions,
4945
+ retrySeed
4946
+ ].filter(Boolean).join("\n\n---\n\n");
4947
+ const retryHandle = await spawnAgentTurn(
4948
+ retryThread,
4949
+ { cachedPrefix: retryPrefix, prompt: agentPrompt },
4950
+ (event) => {
4951
+ this.emit({ type: "turn_output", threadId, event });
4952
+ if (event.type === "session_id") {
4953
+ updateThread(threadId, { sessionId: event.data });
4954
+ }
4955
+ if (event.type === "stderr" && typeof event.data === "string") {
4956
+ pushTurnStderr(stderrTail, event.data);
4957
+ }
4958
+ }
4959
+ );
4960
+ this.activeTurns.set(threadId, retryHandle);
4961
+ if (typeof retryHandle.pid === "number" && retryHandle.pid > 0) {
4962
+ updateThread(threadId, { agentPid: retryHandle.pid });
4963
+ }
4964
+ this.processes.set(`${threadId}:agent`, {
4965
+ kind: "agent",
4966
+ pid: retryHandle.pid,
4967
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
4968
+ kill: retryHandle.kill
4969
+ });
4970
+ if (this.stoppedTurns.has(threadId)) {
4971
+ retryHandle.kill();
4972
+ }
4973
+ const retryResult = await retryHandle.done;
4974
+ if (retryResult.sessionId) {
4975
+ updateThread(threadId, { sessionId: retryResult.sessionId });
4976
+ }
4977
+ assistantText = retryResult.assistantText.trim();
4978
+ parts = retryResult.parts;
4979
+ usage = retryResult.usage ?? void 0;
4980
+ exitCode = retryResult.exitCode;
4981
+ lastStderr = summarizeTurnStderr(stderrTail);
4982
+ detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
4983
+ }
4909
4984
  let chatText = assistantText;
4910
4985
  if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
4911
4986
  chatText = humanizeAgentFailDetail(detail);
@@ -92,6 +92,11 @@ function summarizeTurnStderr(tail, maxChars = 500) {
92
92
  if (joined.length <= maxChars) return joined;
93
93
  return joined.slice(joined.length - maxChars);
94
94
  }
95
+ function looksLikeInvalidAgentSession(text) {
96
+ const lower = text.trim().toLowerCase();
97
+ if (!lower) return false;
98
+ return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower);
99
+ }
95
100
  function looksLikeAgentFailureMessage(text) {
96
101
  const lower = text.trim().toLowerCase();
97
102
  if (!lower) return false;
@@ -5710,6 +5715,7 @@ var init_opencode = __esm({
5710
5715
  }
5711
5716
  },
5712
5717
  async resolveSessionId(worktreePath, cached) {
5718
+ const cachedId = cached?.trim() || null;
5713
5719
  const listed = await run(
5714
5720
  "opencode",
5715
5721
  ["session", "list", "--format", "json"],
@@ -5721,15 +5727,21 @@ var init_opencode = __esm({
5721
5727
  if (Array.isArray(sessions) && sessions.length > 0) {
5722
5728
  const norm = (p) => p.replace(/\/+$/, "");
5723
5729
  const wt = norm(worktreePath);
5724
- const match = sessions.find(
5730
+ const forWorktree = sessions.filter(
5725
5731
  (s) => s.directory && norm(s.directory) === wt || s.path && norm(s.path) === wt
5726
5732
  );
5727
- if (match?.id) return match.id;
5733
+ if (cachedId && forWorktree.some((s) => s.id === cachedId)) {
5734
+ return cachedId;
5735
+ }
5736
+ if (cachedId && sessions.some((s) => s.id === cachedId)) {
5737
+ return cachedId;
5738
+ }
5739
+ return null;
5728
5740
  }
5729
5741
  } catch {
5730
5742
  }
5731
5743
  }
5732
- return cached;
5744
+ return cachedId;
5733
5745
  },
5734
5746
  async buildAttach(thread) {
5735
5747
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
@@ -10479,21 +10491,30 @@ var Orchestrator = class {
10479
10491
  return findThreadByRef(idOrRef) ?? readThread(idOrRef);
10480
10492
  }
10481
10493
  async createThread(input) {
10482
- let thread = await createThread(input);
10494
+ const thread = await createThread(input);
10483
10495
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
10484
- await this.runSetupAfterCreate(thread.id);
10496
+ void this.finishCreateThread(thread.id, input.prompt?.trim() || void 0);
10497
+ return thread;
10498
+ }
10499
+ async finishCreateThread(threadId, prompt) {
10500
+ await this.runSetupAfterCreate(threadId);
10485
10501
  const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
10486
10502
  if (autoRunAfterSetupEnabled2()) {
10487
10503
  try {
10488
- await this.startDev(thread.id);
10504
+ await this.startDev(threadId);
10489
10505
  } catch {
10490
10506
  }
10491
10507
  }
10492
- const prompt = input.prompt?.trim();
10493
10508
  if (prompt) {
10494
- thread = await this.send(thread.id, prompt);
10509
+ try {
10510
+ await this.send(threadId, prompt);
10511
+ } catch (err) {
10512
+ const message = err instanceof Error ? err.message : String(err);
10513
+ updateThread(threadId, {
10514
+ lastError: `First prompt failed: ${message}`
10515
+ });
10516
+ }
10495
10517
  }
10496
- return thread;
10497
10518
  }
10498
10519
  /** Run workspace setup after a new worktree is created (no-op if none configured). */
10499
10520
  async runSetupAfterCreate(threadId) {
@@ -10839,8 +10860,73 @@ var Orchestrator = class {
10839
10860
  }
10840
10861
  }
10841
10862
  }
10842
- const lastStderr = summarizeTurnStderr(stderrTail);
10843
- const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
10863
+ let lastStderr = summarizeTurnStderr(stderrTail);
10864
+ let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
10865
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && looksLikeInvalidAgentSession(detail) && this.requireThread(threadId).sessionId && this.requireThread(threadId).agent !== "cursor" && this.requireThread(threadId).agent !== "brightsy") {
10866
+ updateThread(threadId, { sessionId: null });
10867
+ pushTurnStderr(
10868
+ stderrTail,
10869
+ "Agent session missing \u2014 starting a fresh session"
10870
+ );
10871
+ this.emit({
10872
+ type: "turn_output",
10873
+ threadId,
10874
+ event: {
10875
+ type: "stderr",
10876
+ data: "Agent session missing \u2014 starting a fresh session"
10877
+ }
10878
+ });
10879
+ const retryThread = this.requireThread(threadId);
10880
+ const prior = retryThread.messages.slice(0, -1);
10881
+ const retrySeed = buildSessionSeed(prior);
10882
+ const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
10883
+ loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
10884
+ );
10885
+ const retryPrefix = [
10886
+ coordinatorDirective,
10887
+ worktreeDirective,
10888
+ artifactDirective,
10889
+ renameBranchDirective,
10890
+ retryInstructions,
10891
+ retrySeed
10892
+ ].filter(Boolean).join("\n\n---\n\n");
10893
+ const retryHandle = await spawnAgentTurn(
10894
+ retryThread,
10895
+ { cachedPrefix: retryPrefix, prompt: agentPrompt },
10896
+ (event) => {
10897
+ this.emit({ type: "turn_output", threadId, event });
10898
+ if (event.type === "session_id") {
10899
+ updateThread(threadId, { sessionId: event.data });
10900
+ }
10901
+ if (event.type === "stderr" && typeof event.data === "string") {
10902
+ pushTurnStderr(stderrTail, event.data);
10903
+ }
10904
+ }
10905
+ );
10906
+ this.activeTurns.set(threadId, retryHandle);
10907
+ if (typeof retryHandle.pid === "number" && retryHandle.pid > 0) {
10908
+ updateThread(threadId, { agentPid: retryHandle.pid });
10909
+ }
10910
+ this.processes.set(`${threadId}:agent`, {
10911
+ kind: "agent",
10912
+ pid: retryHandle.pid,
10913
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
10914
+ kill: retryHandle.kill
10915
+ });
10916
+ if (this.stoppedTurns.has(threadId)) {
10917
+ retryHandle.kill();
10918
+ }
10919
+ const retryResult = await retryHandle.done;
10920
+ if (retryResult.sessionId) {
10921
+ updateThread(threadId, { sessionId: retryResult.sessionId });
10922
+ }
10923
+ assistantText = retryResult.assistantText.trim();
10924
+ parts = retryResult.parts;
10925
+ usage = retryResult.usage ?? void 0;
10926
+ exitCode = retryResult.exitCode;
10927
+ lastStderr = summarizeTurnStderr(stderrTail);
10928
+ detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
10929
+ }
10844
10930
  let chatText = assistantText;
10845
10931
  if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
10846
10932
  chatText = humanizeAgentFailDetail(detail);
@@ -10,12 +10,13 @@ import {
10
10
  isSessionQuotaLimit,
11
11
  listModelsForAgent,
12
12
  looksLikeAgentFailureMessage,
13
+ looksLikeInvalidAgentSession,
13
14
  parseBrightsyCliLine,
14
15
  parseSessionQuotaResetAt,
15
16
  pushTurnStderr,
16
17
  resolveQuotaFallbackAgent,
17
18
  summarizeTurnStderr
18
- } from "../chunk-6QTZVJ7A.js";
19
+ } from "../chunk-BZST4HMJ.js";
19
20
  import "../chunk-H6GGDLYS.js";
20
21
  import {
21
22
  addWorkspace,
@@ -1162,7 +1163,7 @@ async function createThread(input, _onSetupLine) {
1162
1163
  return readThread(thread.id) ?? thread;
1163
1164
  }
1164
1165
  async function listLinearIssues(agent, repoPath) {
1165
- const { getAdapter: getAdapter2 } = await import("../agents-ON6RKKND.js");
1166
+ const { getAdapter: getAdapter2 } = await import("../agents-KP7UJEHJ.js");
1166
1167
  await requireAgent(agent, { requireLinear: true });
1167
1168
  const adapter = getAdapter2(agent);
1168
1169
  if (!adapter.listLinearIssues) {
@@ -4037,21 +4038,30 @@ var Orchestrator = class {
4037
4038
  return findThreadByRef(idOrRef) ?? readThread(idOrRef);
4038
4039
  }
4039
4040
  async createThread(input) {
4040
- let thread = await createThread(input);
4041
+ const thread = await createThread(input);
4041
4042
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
4042
- await this.runSetupAfterCreate(thread.id);
4043
+ void this.finishCreateThread(thread.id, input.prompt?.trim() || void 0);
4044
+ return thread;
4045
+ }
4046
+ async finishCreateThread(threadId, prompt) {
4047
+ await this.runSetupAfterCreate(threadId);
4043
4048
  const { autoRunAfterSetupEnabled } = await import("../app-settings-7XVDQJ7F.js");
4044
4049
  if (autoRunAfterSetupEnabled()) {
4045
4050
  try {
4046
- await this.startDev(thread.id);
4051
+ await this.startDev(threadId);
4047
4052
  } catch {
4048
4053
  }
4049
4054
  }
4050
- const prompt = input.prompt?.trim();
4051
4055
  if (prompt) {
4052
- thread = await this.send(thread.id, prompt);
4056
+ try {
4057
+ await this.send(threadId, prompt);
4058
+ } catch (err) {
4059
+ const message = err instanceof Error ? err.message : String(err);
4060
+ updateThread(threadId, {
4061
+ lastError: `First prompt failed: ${message}`
4062
+ });
4063
+ }
4053
4064
  }
4054
- return thread;
4055
4065
  }
4056
4066
  /** Run workspace setup after a new worktree is created (no-op if none configured). */
4057
4067
  async runSetupAfterCreate(threadId) {
@@ -4397,8 +4407,73 @@ var Orchestrator = class {
4397
4407
  }
4398
4408
  }
4399
4409
  }
4400
- const lastStderr = summarizeTurnStderr(stderrTail);
4401
- const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
4410
+ let lastStderr = summarizeTurnStderr(stderrTail);
4411
+ let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
4412
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && looksLikeInvalidAgentSession(detail) && this.requireThread(threadId).sessionId && this.requireThread(threadId).agent !== "cursor" && this.requireThread(threadId).agent !== "brightsy") {
4413
+ updateThread(threadId, { sessionId: null });
4414
+ pushTurnStderr(
4415
+ stderrTail,
4416
+ "Agent session missing \u2014 starting a fresh session"
4417
+ );
4418
+ this.emit({
4419
+ type: "turn_output",
4420
+ threadId,
4421
+ event: {
4422
+ type: "stderr",
4423
+ data: "Agent session missing \u2014 starting a fresh session"
4424
+ }
4425
+ });
4426
+ const retryThread = this.requireThread(threadId);
4427
+ const prior = retryThread.messages.slice(0, -1);
4428
+ const retrySeed = buildSessionSeed(prior);
4429
+ const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
4430
+ loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
4431
+ );
4432
+ const retryPrefix = [
4433
+ coordinatorDirective,
4434
+ worktreeDirective,
4435
+ artifactDirective,
4436
+ renameBranchDirective,
4437
+ retryInstructions,
4438
+ retrySeed
4439
+ ].filter(Boolean).join("\n\n---\n\n");
4440
+ const retryHandle = await spawnAgentTurn(
4441
+ retryThread,
4442
+ { cachedPrefix: retryPrefix, prompt: agentPrompt },
4443
+ (event) => {
4444
+ this.emit({ type: "turn_output", threadId, event });
4445
+ if (event.type === "session_id") {
4446
+ updateThread(threadId, { sessionId: event.data });
4447
+ }
4448
+ if (event.type === "stderr" && typeof event.data === "string") {
4449
+ pushTurnStderr(stderrTail, event.data);
4450
+ }
4451
+ }
4452
+ );
4453
+ this.activeTurns.set(threadId, retryHandle);
4454
+ if (typeof retryHandle.pid === "number" && retryHandle.pid > 0) {
4455
+ updateThread(threadId, { agentPid: retryHandle.pid });
4456
+ }
4457
+ this.processes.set(`${threadId}:agent`, {
4458
+ kind: "agent",
4459
+ pid: retryHandle.pid,
4460
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
4461
+ kill: retryHandle.kill
4462
+ });
4463
+ if (this.stoppedTurns.has(threadId)) {
4464
+ retryHandle.kill();
4465
+ }
4466
+ const retryResult = await retryHandle.done;
4467
+ if (retryResult.sessionId) {
4468
+ updateThread(threadId, { sessionId: retryResult.sessionId });
4469
+ }
4470
+ assistantText = retryResult.assistantText.trim();
4471
+ parts = retryResult.parts;
4472
+ usage = retryResult.usage ?? void 0;
4473
+ exitCode = retryResult.exitCode;
4474
+ lastStderr = summarizeTurnStderr(stderrTail);
4475
+ detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
4476
+ }
4402
4477
  let chatText = assistantText;
4403
4478
  if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
4404
4479
  chatText = humanizeAgentFailDetail(detail);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
4
4
  "description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",