cascade-ai 0.12.21 → 0.12.23

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.21";
222455
+ var CASCADE_VERSION = "0.12.23";
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,18 +298057,18 @@ 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);
298064
298067
  cascade.on("stream:token", (e3) => {
298065
298068
  this.socket.emitToSocket(socketId, "stream:token", { sessionId, tierId: e3.tierId, text: e3.text });
298066
- this.socket.broadcast("stream:token", { sessionId, tierId: e3.tierId, text: e3.text });
298067
298069
  });
298068
298070
  cascade.on("tier:status", (e3) => {
298069
298071
  this.socket.emitToSocket(socketId, "tier:status", { sessionId, ...e3 });
298070
- this.socket.broadcast("tier:status", { sessionId, ...e3 });
298071
298072
  });
298072
298073
  cascade.on("permission:user-required", (e3) => {
298073
298074
  this.socket.emitToSocket(socketId, "permission:user-required", { sessionId, ...e3 });
@@ -298076,7 +298077,8 @@ var DashboardServer = class {
298076
298077
  this.socket.emitPeerMessage(e3);
298077
298078
  });
298078
298079
  try {
298079
- const result = await cascade.run({ prompt });
298080
+ const result = await cascade.run({ prompt: runPrompt });
298081
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
298080
298082
  this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
298081
298083
  this.socket.broadcast("cost:update", {
298082
298084
  sessionId,
@@ -298085,6 +298087,7 @@ var DashboardServer = class {
298085
298087
  });
298086
298088
  this.throttledBroadcast("workspace");
298087
298089
  } catch (err) {
298090
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
298088
298091
  this.socket.emitToSocket(socketId, "session:error", {
298089
298092
  sessionId,
298090
298093
  error: err instanceof Error ? err.message : String(err)
@@ -298195,6 +298198,86 @@ var DashboardServer = class {
298195
298198
  }
298196
298199
  return this.globalStore;
298197
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
+ }
298198
298281
  throttledBroadcast(scope) {
298199
298282
  if (this.broadcastTimer) return;
298200
298283
  this.broadcastTimer = setTimeout(() => {
@@ -298556,17 +298639,19 @@ var DashboardServer = class {
298556
298639
  res.status(400).json({ error: "prompt is required" });
298557
298640
  return;
298558
298641
  }
298559
- 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();
298560
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);
298561
298648
  void (async () => {
298562
298649
  const cascade = new Cascade(this.config, this.workspacePath, this.store);
298563
298650
  this.activeSessions.set(sessionId, cascade);
298564
298651
  cascade.on("stream:token", (e3) => {
298565
- this.socket.broadcast("stream:token", { sessionId, tierId: e3.tierId, text: e3.text });
298566
298652
  this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, tierId: e3.tierId, text: e3.text });
298567
298653
  });
298568
298654
  cascade.on("tier:status", (e3) => {
298569
- this.socket.broadcast("tier:status", { sessionId, ...e3 });
298570
298655
  this.socket.broadcastToRoom(`session:${sessionId}`, "tier:status", { sessionId, ...e3 });
298571
298656
  });
298572
298657
  cascade.on("permission:user-required", (e3) => {
@@ -298576,7 +298661,8 @@ var DashboardServer = class {
298576
298661
  this.socket.emitPeerMessage(e3);
298577
298662
  });
298578
298663
  try {
298579
- 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");
298580
298666
  this.socket.broadcast("cost:update", {
298581
298667
  sessionId,
298582
298668
  totalTokens: result.usage.totalTokens,
@@ -298585,6 +298671,7 @@ var DashboardServer = class {
298585
298671
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
298586
298672
  this.throttledBroadcast("workspace");
298587
298673
  } catch (err) {
298674
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
298588
298675
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:error", {
298589
298676
  sessionId,
298590
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.21";
88
+ var CASCADE_VERSION = "0.12.23";
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,18 +10827,18 @@ 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);
10834
10837
  cascade.on("stream:token", (e) => {
10835
10838
  this.socket.emitToSocket(socketId, "stream:token", { sessionId, tierId: e.tierId, text: e.text });
10836
- this.socket.broadcast("stream:token", { sessionId, tierId: e.tierId, text: e.text });
10837
10839
  });
10838
10840
  cascade.on("tier:status", (e) => {
10839
10841
  this.socket.emitToSocket(socketId, "tier:status", { sessionId, ...e });
10840
- this.socket.broadcast("tier:status", { sessionId, ...e });
10841
10842
  });
10842
10843
  cascade.on("permission:user-required", (e) => {
10843
10844
  this.socket.emitToSocket(socketId, "permission:user-required", { sessionId, ...e });
@@ -10846,7 +10847,8 @@ var DashboardServer = class {
10846
10847
  this.socket.emitPeerMessage(e);
10847
10848
  });
10848
10849
  try {
10849
- const result = await cascade.run({ prompt });
10850
+ const result = await cascade.run({ prompt: runPrompt });
10851
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
10850
10852
  this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
10851
10853
  this.socket.broadcast("cost:update", {
10852
10854
  sessionId,
@@ -10855,6 +10857,7 @@ var DashboardServer = class {
10855
10857
  });
10856
10858
  this.throttledBroadcast("workspace");
10857
10859
  } catch (err) {
10860
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
10858
10861
  this.socket.emitToSocket(socketId, "session:error", {
10859
10862
  sessionId,
10860
10863
  error: err instanceof Error ? err.message : String(err)
@@ -10965,6 +10968,86 @@ var DashboardServer = class {
10965
10968
  }
10966
10969
  return this.globalStore;
10967
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
+ }
10968
11051
  throttledBroadcast(scope) {
10969
11052
  if (this.broadcastTimer) return;
10970
11053
  this.broadcastTimer = setTimeout(() => {
@@ -11326,17 +11409,19 @@ var DashboardServer = class {
11326
11409
  res.status(400).json({ error: "prompt is required" });
11327
11410
  return;
11328
11411
  }
11329
- 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();
11330
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);
11331
11418
  void (async () => {
11332
11419
  const cascade = new Cascade(this.config, this.workspacePath, this.store);
11333
11420
  this.activeSessions.set(sessionId, cascade);
11334
11421
  cascade.on("stream:token", (e) => {
11335
- this.socket.broadcast("stream:token", { sessionId, tierId: e.tierId, text: e.text });
11336
11422
  this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, tierId: e.tierId, text: e.text });
11337
11423
  });
11338
11424
  cascade.on("tier:status", (e) => {
11339
- this.socket.broadcast("tier:status", { sessionId, ...e });
11340
11425
  this.socket.broadcastToRoom(`session:${sessionId}`, "tier:status", { sessionId, ...e });
11341
11426
  });
11342
11427
  cascade.on("permission:user-required", (e) => {
@@ -11346,7 +11431,8 @@ var DashboardServer = class {
11346
11431
  this.socket.emitPeerMessage(e);
11347
11432
  });
11348
11433
  try {
11349
- 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");
11350
11436
  this.socket.broadcast("cost:update", {
11351
11437
  sessionId,
11352
11438
  totalTokens: result.usage.totalTokens,
@@ -11355,6 +11441,7 @@ var DashboardServer = class {
11355
11441
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
11356
11442
  this.throttledBroadcast("workspace");
11357
11443
  } catch (err) {
11444
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
11358
11445
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:error", {
11359
11446
  sessionId,
11360
11447
  error: err instanceof Error ? err.message : String(err)