cascade-ai 0.12.22 → 0.12.24

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.
@@ -222452,7 +222452,7 @@ Anthropic.Models = Models2;
222452
222452
  Anthropic.Beta = Beta;
222453
222453
 
222454
222454
  // src/constants.ts
222455
- var CASCADE_VERSION = "0.12.22";
222455
+ var CASCADE_VERSION = "0.12.24";
222456
222456
  var CASCADE_CONFIG_DIR = ".cascade";
222457
222457
  var CASCADE_MD_FILE = "CASCADE.md";
222458
222458
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -297978,7 +297978,8 @@ var DashboardSocket = class {
297978
297978
  this.io.on("connection", (socket) => {
297979
297979
  socket.on("cascade:run", (payload) => {
297980
297980
  if (typeof payload?.prompt === "string" && payload.prompt.trim()) {
297981
- callback(payload.prompt.trim(), payload.model ?? "auto", socket.id);
297981
+ const sessionId = typeof payload.sessionId === "string" && payload.sessionId.trim() ? payload.sessionId.trim() : void 0;
297982
+ callback(payload.prompt.trim(), payload.model ?? "auto", socket.id, sessionId);
297982
297983
  }
297983
297984
  });
297984
297985
  });
@@ -298056,8 +298057,10 @@ var DashboardServer = class {
298056
298057
  }
298057
298058
  this.persistConfig();
298058
298059
  });
298059
- this.socket.onCascadeRun(async (prompt, model, socketId) => {
298060
- const sessionId = crypto2.randomUUID();
298060
+ this.socket.onCascadeRun(async (prompt, model, socketId, requestedSessionId) => {
298061
+ const sessionId = requestedSessionId ?? crypto2.randomUUID();
298062
+ const runPrompt = requestedSessionId ? this.buildContinuationPrompt(sessionId, prompt) : prompt;
298063
+ const title = this.persistRunStart(sessionId, prompt);
298061
298064
  const cfg = model !== "auto" ? { ...this.config, models: { ...this.config.models, t1: model } } : this.config;
298062
298065
  const cascade = new Cascade(cfg, this.workspacePath, this.store);
298063
298066
  this.activeSessions.set(sessionId, cascade);
@@ -298074,7 +298077,8 @@ var DashboardServer = class {
298074
298077
  this.socket.emitPeerMessage(e3);
298075
298078
  });
298076
298079
  try {
298077
- const result = await cascade.run({ prompt });
298080
+ const result = await cascade.run({ prompt: runPrompt });
298081
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
298078
298082
  this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
298079
298083
  this.socket.broadcast("cost:update", {
298080
298084
  sessionId,
@@ -298083,6 +298087,7 @@ var DashboardServer = class {
298083
298087
  });
298084
298088
  this.throttledBroadcast("workspace");
298085
298089
  } catch (err) {
298090
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
298086
298091
  this.socket.emitToSocket(socketId, "session:error", {
298087
298092
  sessionId,
298088
298093
  error: err instanceof Error ? err.message : String(err)
@@ -298193,6 +298198,86 @@ var DashboardServer = class {
298193
298198
  }
298194
298199
  return this.globalStore;
298195
298200
  }
298201
+ // ── Desktop-run session persistence ─────────
298202
+ // Only the CLI REPL used to persist sessions; runs started from the desktop
298203
+ // (socket `cascade:run` and REST /api/run) wrote nothing to the store, so
298204
+ // they never appeared in the session list and couldn't be resumed. These
298205
+ // helpers give both run paths the same persistence the CLI has.
298206
+ /** Record run start: session row (if new), the user message, and an ACTIVE runtime row. Returns the session title. */
298207
+ persistRunStart(sessionId, prompt) {
298208
+ const now = (/* @__PURE__ */ new Date()).toISOString();
298209
+ let title = prompt.replace(/\s+/g, " ").trim().slice(0, 40);
298210
+ try {
298211
+ const existing = this.store.getSession(sessionId);
298212
+ if (existing) {
298213
+ title = existing.title;
298214
+ } else {
298215
+ this.store.createSession({
298216
+ id: sessionId,
298217
+ title,
298218
+ createdAt: now,
298219
+ updatedAt: now,
298220
+ identityId: this.config.defaultIdentityId ?? "default",
298221
+ workspacePath: this.workspacePath,
298222
+ messages: [],
298223
+ metadata: { totalTokens: 0, totalCostUsd: 0, modelsUsed: [], toolsUsed: [], taskCount: 0 }
298224
+ });
298225
+ }
298226
+ this.store.addMessage({ id: crypto2.randomUUID(), sessionId, role: "user", content: prompt, timestamp: now });
298227
+ } catch (err) {
298228
+ console.warn("[dashboard] failed to persist run start:", err);
298229
+ }
298230
+ this.persistRuntimeRow(sessionId, title, "ACTIVE", prompt);
298231
+ return title;
298232
+ }
298233
+ /** Record run end: the assistant reply (when there is one) and the runtime row's final status. */
298234
+ persistRunEnd(sessionId, title, latestPrompt, reply, status) {
298235
+ try {
298236
+ if (reply && reply.trim()) {
298237
+ this.store.addMessage({ id: crypto2.randomUUID(), sessionId, role: "assistant", content: reply, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
298238
+ }
298239
+ } catch (err) {
298240
+ console.warn("[dashboard] failed to persist run end:", err);
298241
+ }
298242
+ this.persistRuntimeRow(sessionId, title, status, latestPrompt);
298243
+ }
298244
+ persistRuntimeRow(sessionId, title, status, latestPrompt) {
298245
+ const now = (/* @__PURE__ */ new Date()).toISOString();
298246
+ const row = { sessionId, title, workspacePath: this.workspacePath, status, startedAt: now, updatedAt: now, latestPrompt, isGlobal: false };
298247
+ try {
298248
+ this.store.upsertRuntimeSession(row);
298249
+ } catch (err) {
298250
+ console.warn("[dashboard] runtime upsert failed:", err);
298251
+ }
298252
+ try {
298253
+ this.getGlobalStore().upsertRuntimeSession({ ...row, isGlobal: true });
298254
+ } catch {
298255
+ }
298256
+ this.throttledBroadcast("workspace");
298257
+ }
298258
+ /**
298259
+ * Continuing an existing session: the Cascade orchestrator is per-run and
298260
+ * stateless across runs, so prepend a compact transcript of the stored
298261
+ * conversation to the new prompt. Called BEFORE persistRunStart so the new
298262
+ * prompt isn't duplicated into its own context.
298263
+ */
298264
+ buildContinuationPrompt(sessionId, prompt) {
298265
+ try {
298266
+ const history = this.store.getSessionMessages(sessionId);
298267
+ if (history.length === 0) return prompt;
298268
+ const lines = history.slice(-10).map((m3) => {
298269
+ const text = typeof m3.content === "string" ? m3.content : JSON.stringify(m3.content);
298270
+ return `${m3.role === "user" ? "User" : "Assistant"}: ${text.slice(0, 2e3)}`;
298271
+ });
298272
+ return `Conversation so far (for context):
298273
+ ${lines.join("\n")}
298274
+
298275
+ User's new message:
298276
+ ${prompt}`;
298277
+ } catch {
298278
+ return prompt;
298279
+ }
298280
+ }
298196
298281
  throttledBroadcast(scope) {
298197
298282
  if (this.broadcastTimer) return;
298198
298283
  this.broadcastTimer = setTimeout(() => {
@@ -298554,8 +298639,12 @@ var DashboardServer = class {
298554
298639
  res.status(400).json({ error: "prompt is required" });
298555
298640
  return;
298556
298641
  }
298557
- const sessionId = crypto2.randomUUID();
298642
+ const requestedSessionId = typeof body.sessionId === "string" && body.sessionId.trim() ? body.sessionId.trim() : void 0;
298643
+ const sessionId = requestedSessionId ?? crypto2.randomUUID();
298558
298644
  res.json({ sessionId, status: "ACTIVE" });
298645
+ const prompt = body.prompt;
298646
+ const runPrompt = requestedSessionId ? this.buildContinuationPrompt(sessionId, prompt) : prompt;
298647
+ const title = this.persistRunStart(sessionId, prompt);
298559
298648
  void (async () => {
298560
298649
  const cascade = new Cascade(this.config, this.workspacePath, this.store);
298561
298650
  this.activeSessions.set(sessionId, cascade);
@@ -298572,7 +298661,8 @@ var DashboardServer = class {
298572
298661
  this.socket.emitPeerMessage(e3);
298573
298662
  });
298574
298663
  try {
298575
- const result = await cascade.run({ prompt: body.prompt, identityId: body.identityId });
298664
+ const result = await cascade.run({ prompt: runPrompt, identityId: body.identityId });
298665
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
298576
298666
  this.socket.broadcast("cost:update", {
298577
298667
  sessionId,
298578
298668
  totalTokens: result.usage.totalTokens,
@@ -298581,6 +298671,7 @@ var DashboardServer = class {
298581
298671
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
298582
298672
  this.throttledBroadcast("workspace");
298583
298673
  } catch (err) {
298674
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
298584
298675
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:error", {
298585
298676
  sessionId,
298586
298677
  error: err instanceof Error ? err.message : String(err)
package/dist/index.cjs CHANGED
@@ -85,7 +85,7 @@ var cron__default = /*#__PURE__*/_interopDefault(cron);
85
85
 
86
86
 
87
87
  // src/constants.ts
88
- var CASCADE_VERSION = "0.12.22";
88
+ var CASCADE_VERSION = "0.12.24";
89
89
  var CASCADE_CONFIG_DIR = ".cascade";
90
90
  var CASCADE_MD_FILE = "CASCADE.md";
91
91
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -10748,7 +10748,8 @@ var DashboardSocket = class {
10748
10748
  this.io.on("connection", (socket) => {
10749
10749
  socket.on("cascade:run", (payload) => {
10750
10750
  if (typeof payload?.prompt === "string" && payload.prompt.trim()) {
10751
- callback(payload.prompt.trim(), payload.model ?? "auto", socket.id);
10751
+ const sessionId = typeof payload.sessionId === "string" && payload.sessionId.trim() ? payload.sessionId.trim() : void 0;
10752
+ callback(payload.prompt.trim(), payload.model ?? "auto", socket.id, sessionId);
10752
10753
  }
10753
10754
  });
10754
10755
  });
@@ -10826,8 +10827,10 @@ var DashboardServer = class {
10826
10827
  }
10827
10828
  this.persistConfig();
10828
10829
  });
10829
- this.socket.onCascadeRun(async (prompt, model, socketId) => {
10830
- const sessionId = crypto.randomUUID();
10830
+ this.socket.onCascadeRun(async (prompt, model, socketId, requestedSessionId) => {
10831
+ const sessionId = requestedSessionId ?? crypto.randomUUID();
10832
+ const runPrompt = requestedSessionId ? this.buildContinuationPrompt(sessionId, prompt) : prompt;
10833
+ const title = this.persistRunStart(sessionId, prompt);
10831
10834
  const cfg = model !== "auto" ? { ...this.config, models: { ...this.config.models, t1: model } } : this.config;
10832
10835
  const cascade = new Cascade(cfg, this.workspacePath, this.store);
10833
10836
  this.activeSessions.set(sessionId, cascade);
@@ -10844,7 +10847,8 @@ var DashboardServer = class {
10844
10847
  this.socket.emitPeerMessage(e);
10845
10848
  });
10846
10849
  try {
10847
- const result = await cascade.run({ prompt });
10850
+ const result = await cascade.run({ prompt: runPrompt });
10851
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
10848
10852
  this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
10849
10853
  this.socket.broadcast("cost:update", {
10850
10854
  sessionId,
@@ -10853,6 +10857,7 @@ var DashboardServer = class {
10853
10857
  });
10854
10858
  this.throttledBroadcast("workspace");
10855
10859
  } catch (err) {
10860
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
10856
10861
  this.socket.emitToSocket(socketId, "session:error", {
10857
10862
  sessionId,
10858
10863
  error: err instanceof Error ? err.message : String(err)
@@ -10963,6 +10968,86 @@ var DashboardServer = class {
10963
10968
  }
10964
10969
  return this.globalStore;
10965
10970
  }
10971
+ // ── Desktop-run session persistence ─────────
10972
+ // Only the CLI REPL used to persist sessions; runs started from the desktop
10973
+ // (socket `cascade:run` and REST /api/run) wrote nothing to the store, so
10974
+ // they never appeared in the session list and couldn't be resumed. These
10975
+ // helpers give both run paths the same persistence the CLI has.
10976
+ /** Record run start: session row (if new), the user message, and an ACTIVE runtime row. Returns the session title. */
10977
+ persistRunStart(sessionId, prompt) {
10978
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10979
+ let title = prompt.replace(/\s+/g, " ").trim().slice(0, 40);
10980
+ try {
10981
+ const existing = this.store.getSession(sessionId);
10982
+ if (existing) {
10983
+ title = existing.title;
10984
+ } else {
10985
+ this.store.createSession({
10986
+ id: sessionId,
10987
+ title,
10988
+ createdAt: now,
10989
+ updatedAt: now,
10990
+ identityId: this.config.defaultIdentityId ?? "default",
10991
+ workspacePath: this.workspacePath,
10992
+ messages: [],
10993
+ metadata: { totalTokens: 0, totalCostUsd: 0, modelsUsed: [], toolsUsed: [], taskCount: 0 }
10994
+ });
10995
+ }
10996
+ this.store.addMessage({ id: crypto.randomUUID(), sessionId, role: "user", content: prompt, timestamp: now });
10997
+ } catch (err) {
10998
+ console.warn("[dashboard] failed to persist run start:", err);
10999
+ }
11000
+ this.persistRuntimeRow(sessionId, title, "ACTIVE", prompt);
11001
+ return title;
11002
+ }
11003
+ /** Record run end: the assistant reply (when there is one) and the runtime row's final status. */
11004
+ persistRunEnd(sessionId, title, latestPrompt, reply, status) {
11005
+ try {
11006
+ if (reply && reply.trim()) {
11007
+ this.store.addMessage({ id: crypto.randomUUID(), sessionId, role: "assistant", content: reply, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
11008
+ }
11009
+ } catch (err) {
11010
+ console.warn("[dashboard] failed to persist run end:", err);
11011
+ }
11012
+ this.persistRuntimeRow(sessionId, title, status, latestPrompt);
11013
+ }
11014
+ persistRuntimeRow(sessionId, title, status, latestPrompt) {
11015
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11016
+ const row = { sessionId, title, workspacePath: this.workspacePath, status, startedAt: now, updatedAt: now, latestPrompt, isGlobal: false };
11017
+ try {
11018
+ this.store.upsertRuntimeSession(row);
11019
+ } catch (err) {
11020
+ console.warn("[dashboard] runtime upsert failed:", err);
11021
+ }
11022
+ try {
11023
+ this.getGlobalStore().upsertRuntimeSession({ ...row, isGlobal: true });
11024
+ } catch {
11025
+ }
11026
+ this.throttledBroadcast("workspace");
11027
+ }
11028
+ /**
11029
+ * Continuing an existing session: the Cascade orchestrator is per-run and
11030
+ * stateless across runs, so prepend a compact transcript of the stored
11031
+ * conversation to the new prompt. Called BEFORE persistRunStart so the new
11032
+ * prompt isn't duplicated into its own context.
11033
+ */
11034
+ buildContinuationPrompt(sessionId, prompt) {
11035
+ try {
11036
+ const history = this.store.getSessionMessages(sessionId);
11037
+ if (history.length === 0) return prompt;
11038
+ const lines = history.slice(-10).map((m) => {
11039
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
11040
+ return `${m.role === "user" ? "User" : "Assistant"}: ${text.slice(0, 2e3)}`;
11041
+ });
11042
+ return `Conversation so far (for context):
11043
+ ${lines.join("\n")}
11044
+
11045
+ User's new message:
11046
+ ${prompt}`;
11047
+ } catch {
11048
+ return prompt;
11049
+ }
11050
+ }
10966
11051
  throttledBroadcast(scope) {
10967
11052
  if (this.broadcastTimer) return;
10968
11053
  this.broadcastTimer = setTimeout(() => {
@@ -11324,8 +11409,12 @@ var DashboardServer = class {
11324
11409
  res.status(400).json({ error: "prompt is required" });
11325
11410
  return;
11326
11411
  }
11327
- const sessionId = crypto.randomUUID();
11412
+ const requestedSessionId = typeof body.sessionId === "string" && body.sessionId.trim() ? body.sessionId.trim() : void 0;
11413
+ const sessionId = requestedSessionId ?? crypto.randomUUID();
11328
11414
  res.json({ sessionId, status: "ACTIVE" });
11415
+ const prompt = body.prompt;
11416
+ const runPrompt = requestedSessionId ? this.buildContinuationPrompt(sessionId, prompt) : prompt;
11417
+ const title = this.persistRunStart(sessionId, prompt);
11329
11418
  void (async () => {
11330
11419
  const cascade = new Cascade(this.config, this.workspacePath, this.store);
11331
11420
  this.activeSessions.set(sessionId, cascade);
@@ -11342,7 +11431,8 @@ var DashboardServer = class {
11342
11431
  this.socket.emitPeerMessage(e);
11343
11432
  });
11344
11433
  try {
11345
- const result = await cascade.run({ prompt: body.prompt, identityId: body.identityId });
11434
+ const result = await cascade.run({ prompt: runPrompt, identityId: body.identityId });
11435
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
11346
11436
  this.socket.broadcast("cost:update", {
11347
11437
  sessionId,
11348
11438
  totalTokens: result.usage.totalTokens,
@@ -11351,6 +11441,7 @@ var DashboardServer = class {
11351
11441
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
11352
11442
  this.throttledBroadcast("workspace");
11353
11443
  } catch (err) {
11444
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
11354
11445
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:error", {
11355
11446
  sessionId,
11356
11447
  error: err instanceof Error ? err.message : String(err)