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.
package/dist/index.d.cts CHANGED
@@ -2036,7 +2036,7 @@ declare class DashboardSocket {
2036
2036
  }) => void): void;
2037
2037
  emitToSocket(socketId: string, event: string, data: unknown): void;
2038
2038
  onConfigGet(callback: (socketId: string) => void): void;
2039
- onCascadeRun(callback: (prompt: string, model: string, socketId: string) => void): void;
2039
+ onCascadeRun(callback: (prompt: string, model: string, socketId: string, sessionId?: string) => void): void;
2040
2040
  close(): void;
2041
2041
  }
2042
2042
 
@@ -2078,6 +2078,18 @@ declare class DashboardServer {
2078
2078
  */
2079
2079
  private resolvePasswordHash;
2080
2080
  private getGlobalStore;
2081
+ /** Record run start: session row (if new), the user message, and an ACTIVE runtime row. Returns the session title. */
2082
+ private persistRunStart;
2083
+ /** Record run end: the assistant reply (when there is one) and the runtime row's final status. */
2084
+ private persistRunEnd;
2085
+ private persistRuntimeRow;
2086
+ /**
2087
+ * Continuing an existing session: the Cascade orchestrator is per-run and
2088
+ * stateless across runs, so prepend a compact transcript of the stored
2089
+ * conversation to the new prompt. Called BEFORE persistRunStart so the new
2090
+ * prompt isn't duplicated into its own context.
2091
+ */
2092
+ private buildContinuationPrompt;
2081
2093
  private throttledBroadcast;
2082
2094
  private broadcastRuntime;
2083
2095
  private broadcastSessionDetails;
package/dist/index.d.ts CHANGED
@@ -2036,7 +2036,7 @@ declare class DashboardSocket {
2036
2036
  }) => void): void;
2037
2037
  emitToSocket(socketId: string, event: string, data: unknown): void;
2038
2038
  onConfigGet(callback: (socketId: string) => void): void;
2039
- onCascadeRun(callback: (prompt: string, model: string, socketId: string) => void): void;
2039
+ onCascadeRun(callback: (prompt: string, model: string, socketId: string, sessionId?: string) => void): void;
2040
2040
  close(): void;
2041
2041
  }
2042
2042
 
@@ -2078,6 +2078,18 @@ declare class DashboardServer {
2078
2078
  */
2079
2079
  private resolvePasswordHash;
2080
2080
  private getGlobalStore;
2081
+ /** Record run start: session row (if new), the user message, and an ACTIVE runtime row. Returns the session title. */
2082
+ private persistRunStart;
2083
+ /** Record run end: the assistant reply (when there is one) and the runtime row's final status. */
2084
+ private persistRunEnd;
2085
+ private persistRuntimeRow;
2086
+ /**
2087
+ * Continuing an existing session: the Cascade orchestrator is per-run and
2088
+ * stateless across runs, so prepend a compact transcript of the stored
2089
+ * conversation to the new prompt. Called BEFORE persistRunStart so the new
2090
+ * prompt isn't duplicated into its own context.
2091
+ */
2092
+ private buildContinuationPrompt;
2081
2093
  private throttledBroadcast;
2082
2094
  private broadcastRuntime;
2083
2095
  private broadcastSessionDetails;
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ import cron from 'node-cron';
39
39
 
40
40
 
41
41
  // src/constants.ts
42
- var CASCADE_VERSION = "0.12.21";
42
+ var CASCADE_VERSION = "0.12.23";
43
43
  var CASCADE_CONFIG_DIR = ".cascade";
44
44
  var CASCADE_MD_FILE = "CASCADE.md";
45
45
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -10702,7 +10702,8 @@ var DashboardSocket = class {
10702
10702
  this.io.on("connection", (socket) => {
10703
10703
  socket.on("cascade:run", (payload) => {
10704
10704
  if (typeof payload?.prompt === "string" && payload.prompt.trim()) {
10705
- callback(payload.prompt.trim(), payload.model ?? "auto", socket.id);
10705
+ const sessionId = typeof payload.sessionId === "string" && payload.sessionId.trim() ? payload.sessionId.trim() : void 0;
10706
+ callback(payload.prompt.trim(), payload.model ?? "auto", socket.id, sessionId);
10706
10707
  }
10707
10708
  });
10708
10709
  });
@@ -10780,18 +10781,18 @@ var DashboardServer = class {
10780
10781
  }
10781
10782
  this.persistConfig();
10782
10783
  });
10783
- this.socket.onCascadeRun(async (prompt, model, socketId) => {
10784
- const sessionId = randomUUID();
10784
+ this.socket.onCascadeRun(async (prompt, model, socketId, requestedSessionId) => {
10785
+ const sessionId = requestedSessionId ?? randomUUID();
10786
+ const runPrompt = requestedSessionId ? this.buildContinuationPrompt(sessionId, prompt) : prompt;
10787
+ const title = this.persistRunStart(sessionId, prompt);
10785
10788
  const cfg = model !== "auto" ? { ...this.config, models: { ...this.config.models, t1: model } } : this.config;
10786
10789
  const cascade = new Cascade(cfg, this.workspacePath, this.store);
10787
10790
  this.activeSessions.set(sessionId, cascade);
10788
10791
  cascade.on("stream:token", (e) => {
10789
10792
  this.socket.emitToSocket(socketId, "stream:token", { sessionId, tierId: e.tierId, text: e.text });
10790
- this.socket.broadcast("stream:token", { sessionId, tierId: e.tierId, text: e.text });
10791
10793
  });
10792
10794
  cascade.on("tier:status", (e) => {
10793
10795
  this.socket.emitToSocket(socketId, "tier:status", { sessionId, ...e });
10794
- this.socket.broadcast("tier:status", { sessionId, ...e });
10795
10796
  });
10796
10797
  cascade.on("permission:user-required", (e) => {
10797
10798
  this.socket.emitToSocket(socketId, "permission:user-required", { sessionId, ...e });
@@ -10800,7 +10801,8 @@ var DashboardServer = class {
10800
10801
  this.socket.emitPeerMessage(e);
10801
10802
  });
10802
10803
  try {
10803
- const result = await cascade.run({ prompt });
10804
+ const result = await cascade.run({ prompt: runPrompt });
10805
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
10804
10806
  this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
10805
10807
  this.socket.broadcast("cost:update", {
10806
10808
  sessionId,
@@ -10809,6 +10811,7 @@ var DashboardServer = class {
10809
10811
  });
10810
10812
  this.throttledBroadcast("workspace");
10811
10813
  } catch (err) {
10814
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
10812
10815
  this.socket.emitToSocket(socketId, "session:error", {
10813
10816
  sessionId,
10814
10817
  error: err instanceof Error ? err.message : String(err)
@@ -10919,6 +10922,86 @@ var DashboardServer = class {
10919
10922
  }
10920
10923
  return this.globalStore;
10921
10924
  }
10925
+ // ── Desktop-run session persistence ─────────
10926
+ // Only the CLI REPL used to persist sessions; runs started from the desktop
10927
+ // (socket `cascade:run` and REST /api/run) wrote nothing to the store, so
10928
+ // they never appeared in the session list and couldn't be resumed. These
10929
+ // helpers give both run paths the same persistence the CLI has.
10930
+ /** Record run start: session row (if new), the user message, and an ACTIVE runtime row. Returns the session title. */
10931
+ persistRunStart(sessionId, prompt) {
10932
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10933
+ let title = prompt.replace(/\s+/g, " ").trim().slice(0, 40);
10934
+ try {
10935
+ const existing = this.store.getSession(sessionId);
10936
+ if (existing) {
10937
+ title = existing.title;
10938
+ } else {
10939
+ this.store.createSession({
10940
+ id: sessionId,
10941
+ title,
10942
+ createdAt: now,
10943
+ updatedAt: now,
10944
+ identityId: this.config.defaultIdentityId ?? "default",
10945
+ workspacePath: this.workspacePath,
10946
+ messages: [],
10947
+ metadata: { totalTokens: 0, totalCostUsd: 0, modelsUsed: [], toolsUsed: [], taskCount: 0 }
10948
+ });
10949
+ }
10950
+ this.store.addMessage({ id: randomUUID(), sessionId, role: "user", content: prompt, timestamp: now });
10951
+ } catch (err) {
10952
+ console.warn("[dashboard] failed to persist run start:", err);
10953
+ }
10954
+ this.persistRuntimeRow(sessionId, title, "ACTIVE", prompt);
10955
+ return title;
10956
+ }
10957
+ /** Record run end: the assistant reply (when there is one) and the runtime row's final status. */
10958
+ persistRunEnd(sessionId, title, latestPrompt, reply, status) {
10959
+ try {
10960
+ if (reply && reply.trim()) {
10961
+ this.store.addMessage({ id: randomUUID(), sessionId, role: "assistant", content: reply, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
10962
+ }
10963
+ } catch (err) {
10964
+ console.warn("[dashboard] failed to persist run end:", err);
10965
+ }
10966
+ this.persistRuntimeRow(sessionId, title, status, latestPrompt);
10967
+ }
10968
+ persistRuntimeRow(sessionId, title, status, latestPrompt) {
10969
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10970
+ const row = { sessionId, title, workspacePath: this.workspacePath, status, startedAt: now, updatedAt: now, latestPrompt, isGlobal: false };
10971
+ try {
10972
+ this.store.upsertRuntimeSession(row);
10973
+ } catch (err) {
10974
+ console.warn("[dashboard] runtime upsert failed:", err);
10975
+ }
10976
+ try {
10977
+ this.getGlobalStore().upsertRuntimeSession({ ...row, isGlobal: true });
10978
+ } catch {
10979
+ }
10980
+ this.throttledBroadcast("workspace");
10981
+ }
10982
+ /**
10983
+ * Continuing an existing session: the Cascade orchestrator is per-run and
10984
+ * stateless across runs, so prepend a compact transcript of the stored
10985
+ * conversation to the new prompt. Called BEFORE persistRunStart so the new
10986
+ * prompt isn't duplicated into its own context.
10987
+ */
10988
+ buildContinuationPrompt(sessionId, prompt) {
10989
+ try {
10990
+ const history = this.store.getSessionMessages(sessionId);
10991
+ if (history.length === 0) return prompt;
10992
+ const lines = history.slice(-10).map((m) => {
10993
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
10994
+ return `${m.role === "user" ? "User" : "Assistant"}: ${text.slice(0, 2e3)}`;
10995
+ });
10996
+ return `Conversation so far (for context):
10997
+ ${lines.join("\n")}
10998
+
10999
+ User's new message:
11000
+ ${prompt}`;
11001
+ } catch {
11002
+ return prompt;
11003
+ }
11004
+ }
10922
11005
  throttledBroadcast(scope) {
10923
11006
  if (this.broadcastTimer) return;
10924
11007
  this.broadcastTimer = setTimeout(() => {
@@ -11280,17 +11363,19 @@ var DashboardServer = class {
11280
11363
  res.status(400).json({ error: "prompt is required" });
11281
11364
  return;
11282
11365
  }
11283
- const sessionId = randomUUID();
11366
+ const requestedSessionId = typeof body.sessionId === "string" && body.sessionId.trim() ? body.sessionId.trim() : void 0;
11367
+ const sessionId = requestedSessionId ?? randomUUID();
11284
11368
  res.json({ sessionId, status: "ACTIVE" });
11369
+ const prompt = body.prompt;
11370
+ const runPrompt = requestedSessionId ? this.buildContinuationPrompt(sessionId, prompt) : prompt;
11371
+ const title = this.persistRunStart(sessionId, prompt);
11285
11372
  void (async () => {
11286
11373
  const cascade = new Cascade(this.config, this.workspacePath, this.store);
11287
11374
  this.activeSessions.set(sessionId, cascade);
11288
11375
  cascade.on("stream:token", (e) => {
11289
- this.socket.broadcast("stream:token", { sessionId, tierId: e.tierId, text: e.text });
11290
11376
  this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, tierId: e.tierId, text: e.text });
11291
11377
  });
11292
11378
  cascade.on("tier:status", (e) => {
11293
- this.socket.broadcast("tier:status", { sessionId, ...e });
11294
11379
  this.socket.broadcastToRoom(`session:${sessionId}`, "tier:status", { sessionId, ...e });
11295
11380
  });
11296
11381
  cascade.on("permission:user-required", (e) => {
@@ -11300,7 +11385,8 @@ var DashboardServer = class {
11300
11385
  this.socket.emitPeerMessage(e);
11301
11386
  });
11302
11387
  try {
11303
- const result = await cascade.run({ prompt: body.prompt, identityId: body.identityId });
11388
+ const result = await cascade.run({ prompt: runPrompt, identityId: body.identityId });
11389
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
11304
11390
  this.socket.broadcast("cost:update", {
11305
11391
  sessionId,
11306
11392
  totalTokens: result.usage.totalTokens,
@@ -11309,6 +11395,7 @@ var DashboardServer = class {
11309
11395
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
11310
11396
  this.throttledBroadcast("workspace");
11311
11397
  } catch (err) {
11398
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
11312
11399
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:error", {
11313
11400
  sessionId,
11314
11401
  error: err instanceof Error ? err.message : String(err)