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.
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.22";
42
+ var CASCADE_VERSION = "0.12.24";
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,8 +10781,10 @@ 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);
@@ -10798,7 +10801,8 @@ var DashboardServer = class {
10798
10801
  this.socket.emitPeerMessage(e);
10799
10802
  });
10800
10803
  try {
10801
- const result = await cascade.run({ prompt });
10804
+ const result = await cascade.run({ prompt: runPrompt });
10805
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
10802
10806
  this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
10803
10807
  this.socket.broadcast("cost:update", {
10804
10808
  sessionId,
@@ -10807,6 +10811,7 @@ var DashboardServer = class {
10807
10811
  });
10808
10812
  this.throttledBroadcast("workspace");
10809
10813
  } catch (err) {
10814
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
10810
10815
  this.socket.emitToSocket(socketId, "session:error", {
10811
10816
  sessionId,
10812
10817
  error: err instanceof Error ? err.message : String(err)
@@ -10917,6 +10922,86 @@ var DashboardServer = class {
10917
10922
  }
10918
10923
  return this.globalStore;
10919
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
+ }
10920
11005
  throttledBroadcast(scope) {
10921
11006
  if (this.broadcastTimer) return;
10922
11007
  this.broadcastTimer = setTimeout(() => {
@@ -11278,8 +11363,12 @@ var DashboardServer = class {
11278
11363
  res.status(400).json({ error: "prompt is required" });
11279
11364
  return;
11280
11365
  }
11281
- const sessionId = randomUUID();
11366
+ const requestedSessionId = typeof body.sessionId === "string" && body.sessionId.trim() ? body.sessionId.trim() : void 0;
11367
+ const sessionId = requestedSessionId ?? randomUUID();
11282
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);
11283
11372
  void (async () => {
11284
11373
  const cascade = new Cascade(this.config, this.workspacePath, this.store);
11285
11374
  this.activeSessions.set(sessionId, cascade);
@@ -11296,7 +11385,8 @@ var DashboardServer = class {
11296
11385
  this.socket.emitPeerMessage(e);
11297
11386
  });
11298
11387
  try {
11299
- 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");
11300
11390
  this.socket.broadcast("cost:update", {
11301
11391
  sessionId,
11302
11392
  totalTokens: result.usage.totalTokens,
@@ -11305,6 +11395,7 @@ var DashboardServer = class {
11305
11395
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
11306
11396
  this.throttledBroadcast("workspace");
11307
11397
  } catch (err) {
11398
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
11308
11399
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:error", {
11309
11400
  sessionId,
11310
11401
  error: err instanceof Error ? err.message : String(err)