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/cli.cjs CHANGED
@@ -109,7 +109,7 @@ var __export = (target, all) => {
109
109
  var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
110
110
  var init_constants = __esm({
111
111
  "src/constants.ts"() {
112
- CASCADE_VERSION = "0.12.21";
112
+ CASCADE_VERSION = "0.12.23";
113
113
  CASCADE_CONFIG_FILE = ".cascade/config.json";
114
114
  CASCADE_DB_FILE = ".cascade/memory.db";
115
115
  CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
@@ -14831,7 +14831,8 @@ var DashboardSocket = class {
14831
14831
  this.io.on("connection", (socket) => {
14832
14832
  socket.on("cascade:run", (payload) => {
14833
14833
  if (typeof payload?.prompt === "string" && payload.prompt.trim()) {
14834
- callback(payload.prompt.trim(), payload.model ?? "auto", socket.id);
14834
+ const sessionId = typeof payload.sessionId === "string" && payload.sessionId.trim() ? payload.sessionId.trim() : void 0;
14835
+ callback(payload.prompt.trim(), payload.model ?? "auto", socket.id, sessionId);
14835
14836
  }
14836
14837
  });
14837
14838
  });
@@ -14912,18 +14913,18 @@ var DashboardServer = class {
14912
14913
  }
14913
14914
  this.persistConfig();
14914
14915
  });
14915
- this.socket.onCascadeRun(async (prompt, model, socketId) => {
14916
- const sessionId = crypto.randomUUID();
14916
+ this.socket.onCascadeRun(async (prompt, model, socketId, requestedSessionId) => {
14917
+ const sessionId = requestedSessionId ?? crypto.randomUUID();
14918
+ const runPrompt = requestedSessionId ? this.buildContinuationPrompt(sessionId, prompt) : prompt;
14919
+ const title = this.persistRunStart(sessionId, prompt);
14917
14920
  const cfg = model !== "auto" ? { ...this.config, models: { ...this.config.models, t1: model } } : this.config;
14918
14921
  const cascade = new Cascade(cfg, this.workspacePath, this.store);
14919
14922
  this.activeSessions.set(sessionId, cascade);
14920
14923
  cascade.on("stream:token", (e) => {
14921
14924
  this.socket.emitToSocket(socketId, "stream:token", { sessionId, tierId: e.tierId, text: e.text });
14922
- this.socket.broadcast("stream:token", { sessionId, tierId: e.tierId, text: e.text });
14923
14925
  });
14924
14926
  cascade.on("tier:status", (e) => {
14925
14927
  this.socket.emitToSocket(socketId, "tier:status", { sessionId, ...e });
14926
- this.socket.broadcast("tier:status", { sessionId, ...e });
14927
14928
  });
14928
14929
  cascade.on("permission:user-required", (e) => {
14929
14930
  this.socket.emitToSocket(socketId, "permission:user-required", { sessionId, ...e });
@@ -14932,7 +14933,8 @@ var DashboardServer = class {
14932
14933
  this.socket.emitPeerMessage(e);
14933
14934
  });
14934
14935
  try {
14935
- const result = await cascade.run({ prompt });
14936
+ const result = await cascade.run({ prompt: runPrompt });
14937
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
14936
14938
  this.socket.emitToSocket(socketId, "session:complete", { sessionId, result });
14937
14939
  this.socket.broadcast("cost:update", {
14938
14940
  sessionId,
@@ -14941,6 +14943,7 @@ var DashboardServer = class {
14941
14943
  });
14942
14944
  this.throttledBroadcast("workspace");
14943
14945
  } catch (err) {
14946
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
14944
14947
  this.socket.emitToSocket(socketId, "session:error", {
14945
14948
  sessionId,
14946
14949
  error: err instanceof Error ? err.message : String(err)
@@ -15051,6 +15054,86 @@ var DashboardServer = class {
15051
15054
  }
15052
15055
  return this.globalStore;
15053
15056
  }
15057
+ // ── Desktop-run session persistence ─────────
15058
+ // Only the CLI REPL used to persist sessions; runs started from the desktop
15059
+ // (socket `cascade:run` and REST /api/run) wrote nothing to the store, so
15060
+ // they never appeared in the session list and couldn't be resumed. These
15061
+ // helpers give both run paths the same persistence the CLI has.
15062
+ /** Record run start: session row (if new), the user message, and an ACTIVE runtime row. Returns the session title. */
15063
+ persistRunStart(sessionId, prompt) {
15064
+ const now = (/* @__PURE__ */ new Date()).toISOString();
15065
+ let title = prompt.replace(/\s+/g, " ").trim().slice(0, 40);
15066
+ try {
15067
+ const existing = this.store.getSession(sessionId);
15068
+ if (existing) {
15069
+ title = existing.title;
15070
+ } else {
15071
+ this.store.createSession({
15072
+ id: sessionId,
15073
+ title,
15074
+ createdAt: now,
15075
+ updatedAt: now,
15076
+ identityId: this.config.defaultIdentityId ?? "default",
15077
+ workspacePath: this.workspacePath,
15078
+ messages: [],
15079
+ metadata: { totalTokens: 0, totalCostUsd: 0, modelsUsed: [], toolsUsed: [], taskCount: 0 }
15080
+ });
15081
+ }
15082
+ this.store.addMessage({ id: crypto.randomUUID(), sessionId, role: "user", content: prompt, timestamp: now });
15083
+ } catch (err) {
15084
+ console.warn("[dashboard] failed to persist run start:", err);
15085
+ }
15086
+ this.persistRuntimeRow(sessionId, title, "ACTIVE", prompt);
15087
+ return title;
15088
+ }
15089
+ /** Record run end: the assistant reply (when there is one) and the runtime row's final status. */
15090
+ persistRunEnd(sessionId, title, latestPrompt, reply, status) {
15091
+ try {
15092
+ if (reply && reply.trim()) {
15093
+ this.store.addMessage({ id: crypto.randomUUID(), sessionId, role: "assistant", content: reply, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
15094
+ }
15095
+ } catch (err) {
15096
+ console.warn("[dashboard] failed to persist run end:", err);
15097
+ }
15098
+ this.persistRuntimeRow(sessionId, title, status, latestPrompt);
15099
+ }
15100
+ persistRuntimeRow(sessionId, title, status, latestPrompt) {
15101
+ const now = (/* @__PURE__ */ new Date()).toISOString();
15102
+ const row = { sessionId, title, workspacePath: this.workspacePath, status, startedAt: now, updatedAt: now, latestPrompt, isGlobal: false };
15103
+ try {
15104
+ this.store.upsertRuntimeSession(row);
15105
+ } catch (err) {
15106
+ console.warn("[dashboard] runtime upsert failed:", err);
15107
+ }
15108
+ try {
15109
+ this.getGlobalStore().upsertRuntimeSession({ ...row, isGlobal: true });
15110
+ } catch {
15111
+ }
15112
+ this.throttledBroadcast("workspace");
15113
+ }
15114
+ /**
15115
+ * Continuing an existing session: the Cascade orchestrator is per-run and
15116
+ * stateless across runs, so prepend a compact transcript of the stored
15117
+ * conversation to the new prompt. Called BEFORE persistRunStart so the new
15118
+ * prompt isn't duplicated into its own context.
15119
+ */
15120
+ buildContinuationPrompt(sessionId, prompt) {
15121
+ try {
15122
+ const history = this.store.getSessionMessages(sessionId);
15123
+ if (history.length === 0) return prompt;
15124
+ const lines = history.slice(-10).map((m) => {
15125
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
15126
+ return `${m.role === "user" ? "User" : "Assistant"}: ${text.slice(0, 2e3)}`;
15127
+ });
15128
+ return `Conversation so far (for context):
15129
+ ${lines.join("\n")}
15130
+
15131
+ User's new message:
15132
+ ${prompt}`;
15133
+ } catch {
15134
+ return prompt;
15135
+ }
15136
+ }
15054
15137
  throttledBroadcast(scope) {
15055
15138
  if (this.broadcastTimer) return;
15056
15139
  this.broadcastTimer = setTimeout(() => {
@@ -15412,17 +15495,19 @@ var DashboardServer = class {
15412
15495
  res.status(400).json({ error: "prompt is required" });
15413
15496
  return;
15414
15497
  }
15415
- const sessionId = crypto.randomUUID();
15498
+ const requestedSessionId = typeof body.sessionId === "string" && body.sessionId.trim() ? body.sessionId.trim() : void 0;
15499
+ const sessionId = requestedSessionId ?? crypto.randomUUID();
15416
15500
  res.json({ sessionId, status: "ACTIVE" });
15501
+ const prompt = body.prompt;
15502
+ const runPrompt = requestedSessionId ? this.buildContinuationPrompt(sessionId, prompt) : prompt;
15503
+ const title = this.persistRunStart(sessionId, prompt);
15417
15504
  void (async () => {
15418
15505
  const cascade = new Cascade(this.config, this.workspacePath, this.store);
15419
15506
  this.activeSessions.set(sessionId, cascade);
15420
15507
  cascade.on("stream:token", (e) => {
15421
- this.socket.broadcast("stream:token", { sessionId, tierId: e.tierId, text: e.text });
15422
15508
  this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, tierId: e.tierId, text: e.text });
15423
15509
  });
15424
15510
  cascade.on("tier:status", (e) => {
15425
- this.socket.broadcast("tier:status", { sessionId, ...e });
15426
15511
  this.socket.broadcastToRoom(`session:${sessionId}`, "tier:status", { sessionId, ...e });
15427
15512
  });
15428
15513
  cascade.on("permission:user-required", (e) => {
@@ -15432,7 +15517,8 @@ var DashboardServer = class {
15432
15517
  this.socket.emitPeerMessage(e);
15433
15518
  });
15434
15519
  try {
15435
- const result = await cascade.run({ prompt: body.prompt, identityId: body.identityId });
15520
+ const result = await cascade.run({ prompt: runPrompt, identityId: body.identityId });
15521
+ this.persistRunEnd(sessionId, title, prompt, result.output, "COMPLETED");
15436
15522
  this.socket.broadcast("cost:update", {
15437
15523
  sessionId,
15438
15524
  totalTokens: result.usage.totalTokens,
@@ -15441,6 +15527,7 @@ var DashboardServer = class {
15441
15527
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
15442
15528
  this.throttledBroadcast("workspace");
15443
15529
  } catch (err) {
15530
+ this.persistRunEnd(sessionId, title, prompt, void 0, "FAILED");
15444
15531
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:error", {
15445
15532
  sessionId,
15446
15533
  error: err instanceof Error ? err.message : String(err)