cascade-ai 0.15.2 → 0.16.0

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.15.2";
112
+ CASCADE_VERSION = "0.16.0";
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";
@@ -3230,6 +3230,11 @@ function validateConfig(raw) {
3230
3230
 
3231
3231
  // src/config/index.ts
3232
3232
  init_constants();
3233
+ var KEY_OPTIONAL_PROVIDER_TYPES = /* @__PURE__ */ new Set(["ollama", "openai-compatible"]);
3234
+ function hasUsableProvider(providers) {
3235
+ if (!providers?.length) return false;
3236
+ return providers.some((p) => KEY_OPTIONAL_PROVIDER_TYPES.has(p.type) || !!p.apiKey);
3237
+ }
3233
3238
  var ConfigManager = class {
3234
3239
  config;
3235
3240
  keystore;
@@ -7518,6 +7523,10 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
7518
7523
  const worker = new T3Worker(this.router, this.toolRegistry, this.id);
7519
7524
  if (this.store) worker.setStore(this.store, taskId);
7520
7525
  worker.setPeerBus(this.t3PeerBus);
7526
+ if (this.permissionEscalator) worker.setPermissionEscalator(this.permissionEscalator);
7527
+ if (this.toolCreator) worker.setToolCreator(this.toolCreator);
7528
+ worker.on("log", (e) => this.emit("log", e));
7529
+ worker.on("tier:status", (e) => this.emit("tier:status", e));
7521
7530
  worker.on("stream:token", (e) => this.emit("stream:token", e));
7522
7531
  worker.on("tool:approval-request", (e) => this.emit("tool:approval-request", {
7523
7532
  ...e,
@@ -7869,12 +7878,14 @@ Board guidance (must be followed in the plan): ${decision.note}`,
7869
7878
  status: "IN_PROGRESS"
7870
7879
  });
7871
7880
  const okBefore = okCount(allT2Results);
7881
+ const completedSummary = this.summarizeCompletedSections(allT2Results);
7882
+ const correctionContext = [systemContext, completedSummary].filter(Boolean).join("\n\n") || void 0;
7872
7883
  const correctionPlan = await this.decomposeTask(`The previous execution plan failed to fully satisfy the original goal or encountered errors.
7873
7884
  Review reason: ${reviewResult.reason}
7874
7885
 
7875
7886
  Original goal: ${enrichedPrompt}
7876
7887
 
7877
- Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`);
7888
+ Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`, correctionContext);
7878
7889
  const correctionResults = await this.dispatchT2Managers(correctionPlan.sections);
7879
7890
  allT2Results = [...allT2Results, ...correctionResults];
7880
7891
  if (okCount(allT2Results) <= okBefore) {
@@ -7977,6 +7988,15 @@ In 3-5 terse bullets, flag the most important RISKS, GAPS, or over-/under-decomp
7977
7988
  return null;
7978
7989
  }
7979
7990
  }
7991
+ /** Structured, grounded summary of what's already done — used to keep
7992
+ * corrective replan passes from re-emitting completed sections. */
7993
+ summarizeCompletedSections(results) {
7994
+ const done = results.filter((r) => r.status === "COMPLETED" || r.status === "PARTIAL");
7995
+ if (done.length === 0) return "";
7996
+ const lines = done.map((r) => `- "${r.sectionTitle}" [${r.status}]: ${(r.sectionSummary || "(no summary)").slice(0, 300)}`);
7997
+ return `ALREADY COMPLETED SECTIONS (do not redo these \u2014 plan only what's still missing):
7998
+ ${lines.join("\n")}`;
7999
+ }
7980
8000
  async decomposeTask(prompt, systemContext) {
7981
8001
  const db = this.router.getWorldStateDB?.();
7982
8002
  let worldStateContext = "";
@@ -8244,7 +8264,7 @@ Leave dependsOn empty for sections that can run immediately in parallel.`;
8244
8264
  }
8245
8265
  }
8246
8266
  if (readyIds.length === 0) return;
8247
- await Promise.all(readyIds.map(async (id) => {
8267
+ const runOne = async (id) => {
8248
8268
  resultMap.set(id, null);
8249
8269
  const index = sections.findIndex((s) => s.sectionId === id);
8250
8270
  const section = sections[index];
@@ -8284,7 +8304,14 @@ Leave dependsOn empty for sections that can run immediately in parallel.`;
8284
8304
  for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
8285
8305
  inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
8286
8306
  }
8287
- }));
8307
+ };
8308
+ if (this.router.getT3ExecutionMode?.() === "sequential") {
8309
+ for (const id of readyIds) {
8310
+ await runOne(id);
8311
+ }
8312
+ } else {
8313
+ await Promise.all(readyIds.map(runOne));
8314
+ }
8288
8315
  if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
8289
8316
  await executeWave();
8290
8317
  }
@@ -10088,6 +10115,13 @@ var PermissionEscalator = class extends EventEmitter__default.default {
10088
10115
  * All T3 workers under the same T2 share cached decisions for the same tool.
10089
10116
  */
10090
10117
  sessionCache = /* @__PURE__ */ new Map();
10118
+ /**
10119
+ * Task-wide cache keyed by `toolName` alone, for USER- and T1-level
10120
+ * "always" decisions — these are meant to cover every sibling T2/T3 in the
10121
+ * run, not just the one that happened to ask first (see PermissionDecision
10122
+ * doc comment: "task-wide for T1").
10123
+ */
10124
+ taskWideCache = /* @__PURE__ */ new Map();
10091
10125
  t2Evaluator;
10092
10126
  t1Evaluator;
10093
10127
  /** Pending user-decision resolvers keyed by request ID */
@@ -10116,6 +10150,15 @@ var PermissionEscalator = class extends EventEmitter__default.default {
10116
10150
  * Returns a PermissionDecision from whichever tier was able to decide.
10117
10151
  */
10118
10152
  async requestPermission(req) {
10153
+ if (!req.forceReprompt && this.taskWideCache.has(req.toolName)) {
10154
+ return {
10155
+ requestId: req.id,
10156
+ approved: this.taskWideCache.get(req.toolName),
10157
+ always: true,
10158
+ decidedBy: "T1",
10159
+ reasoning: "Cached from a previous task-wide decision in this session"
10160
+ };
10161
+ }
10119
10162
  const cacheKey = `${req.parentT2Id}:${req.toolName}`;
10120
10163
  if (!req.forceReprompt && this.sessionCache.has(cacheKey)) {
10121
10164
  return {
@@ -10160,7 +10203,7 @@ var PermissionEscalator = class extends EventEmitter__default.default {
10160
10203
  try {
10161
10204
  const t1Decision = await this.t1Evaluator(req);
10162
10205
  if (t1Decision !== null) {
10163
- if (t1Decision.always) this.sessionCache.set(cacheKey, t1Decision.approved);
10206
+ if (t1Decision.always) this.taskWideCache.set(req.toolName, t1Decision.approved);
10164
10207
  return t1Decision;
10165
10208
  }
10166
10209
  } catch {
@@ -10190,7 +10233,7 @@ var PermissionEscalator = class extends EventEmitter__default.default {
10190
10233
  const wrappedResolver = (decision) => {
10191
10234
  if (timer) clearTimeout(timer);
10192
10235
  if (decision.always) {
10193
- this.sessionCache.set(`${req.parentT2Id}:${req.toolName}`, decision.approved);
10236
+ this.taskWideCache.set(req.toolName, decision.approved);
10194
10237
  }
10195
10238
  resolve(decision);
10196
10239
  };
@@ -15269,11 +15312,18 @@ function SetupWizard({ workspacePath, onComplete }) {
15269
15312
  }
15270
15313
  });
15271
15314
  const currentEntry = state.entries[state.currentEntryIdx];
15315
+ const rejectIfBlank = React2.useCallback((val, message) => {
15316
+ if (val.trim()) return false;
15317
+ dispatch({ type: "SET_ERROR", error: message });
15318
+ return true;
15319
+ }, []);
15272
15320
  const handleFieldSubmit = React2.useCallback((val) => {
15273
15321
  if (!currentEntry) return;
15274
15322
  if (currentEntry.type === "azure") {
15275
15323
  if (fieldStage === "deploymentName") {
15324
+ if (rejectIfBlank(val, "Deployment name is required.")) return;
15276
15325
  dispatch({ type: "SET_ENTRY_FIELD", field: "deploymentName", value: val });
15326
+ dispatch({ type: "SET_ERROR", error: "" });
15277
15327
  setFieldBuffer("");
15278
15328
  if (currentEntry.baseUrl && currentEntry.apiKey && currentEntry.apiVersion) {
15279
15329
  setFieldStage("askMore");
@@ -15281,38 +15331,50 @@ function SetupWizard({ workspacePath, onComplete }) {
15281
15331
  setFieldStage("baseUrl");
15282
15332
  }
15283
15333
  } else if (fieldStage === "baseUrl") {
15334
+ if (rejectIfBlank(val, "Azure endpoint URL is required.")) return;
15284
15335
  dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val });
15336
+ dispatch({ type: "SET_ERROR", error: "" });
15285
15337
  setFieldBuffer("");
15286
15338
  setFieldStage("apiKey");
15287
15339
  } else if (fieldStage === "apiKey") {
15340
+ if (rejectIfBlank(val, "API key is required.")) return;
15288
15341
  dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
15342
+ dispatch({ type: "SET_ERROR", error: "" });
15289
15343
  setFieldBuffer("");
15290
15344
  setFieldStage("apiVersion");
15291
15345
  } else if (fieldStage === "apiVersion") {
15292
15346
  dispatch({ type: "SET_ENTRY_FIELD", field: "apiVersion", value: val || "2024-08-01-preview" });
15347
+ dispatch({ type: "SET_ERROR", error: "" });
15293
15348
  setFieldBuffer("");
15294
15349
  setFieldStage("askMore");
15295
15350
  }
15296
15351
  } else if (currentEntry.type === "openai-compatible") {
15297
15352
  if (fieldStage === "label") {
15298
15353
  dispatch({ type: "SET_ENTRY_FIELD", field: "label", value: val || currentEntry.label });
15354
+ dispatch({ type: "SET_ERROR", error: "" });
15299
15355
  setFieldBuffer("");
15300
15356
  setFieldStage("baseUrl");
15301
15357
  } else if (fieldStage === "baseUrl") {
15358
+ if (rejectIfBlank(val, "Base URL is required.")) return;
15302
15359
  dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val });
15360
+ dispatch({ type: "SET_ERROR", error: "" });
15303
15361
  setFieldBuffer("");
15304
15362
  setFieldStage("apiKey");
15305
15363
  } else if (fieldStage === "apiKey") {
15306
15364
  dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
15365
+ dispatch({ type: "SET_ERROR", error: "" });
15307
15366
  setFieldBuffer("");
15308
15367
  setFieldStage("askMore");
15309
15368
  }
15310
15369
  } else if (currentEntry.type === "ollama") {
15311
15370
  dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val || "http://localhost:11434" });
15371
+ dispatch({ type: "SET_ERROR", error: "" });
15312
15372
  setFieldBuffer("");
15313
15373
  setFieldStage("askMore");
15314
15374
  } else {
15375
+ if (rejectIfBlank(val, "API key is required.")) return;
15315
15376
  dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
15377
+ dispatch({ type: "SET_ERROR", error: "" });
15316
15378
  setFieldBuffer("");
15317
15379
  const nextEntry = state.entries[state.currentEntryIdx + 1];
15318
15380
  if (nextEntry) {
@@ -15320,7 +15382,7 @@ function SetupWizard({ workspacePath, onComplete }) {
15320
15382
  }
15321
15383
  dispatch({ type: "NEXT_ENTRY" });
15322
15384
  }
15323
- }, [currentEntry, fieldStage]);
15385
+ }, [currentEntry, fieldStage, rejectIfBlank, state.entries, state.currentEntryIdx]);
15324
15386
  if (state.step === "PROVIDER_SELECT") {
15325
15387
  return /* @__PURE__ */ jsxRuntime.jsxs(Frame, { theme, phase: "keys", children: [
15326
15388
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 1, children: [
@@ -16255,6 +16317,16 @@ var DashboardServer = class {
16255
16317
  getSocket() {
16256
16318
  return this.socket;
16257
16319
  }
16320
+ /**
16321
+ * Rebind the workspace tasks execute in — e.g. the desktop app's Code view
16322
+ * opening a different project folder — without tearing down the socket
16323
+ * server (which would drop the port/auth token/connection mid-session).
16324
+ * The next `cascade:run` picks this up immediately since `this.workspacePath`
16325
+ * is read live per-run (see onCascadeRun below).
16326
+ */
16327
+ setWorkspacePath(workspacePath) {
16328
+ this.workspacePath = workspacePath;
16329
+ }
16258
16330
  /**
16259
16331
  * Write the in-memory config back to the workspace config file so mutations
16260
16332
  * made over the socket (Settings → Save) persist across restarts. Best-effort:
@@ -17558,7 +17630,7 @@ async function startRepl(options) {
17558
17630
  process.exit(1);
17559
17631
  }
17560
17632
  let config = cm.getConfig();
17561
- const needsSetup = !config.providers?.length || config.providers.every((p) => p.type !== "ollama" && !p.apiKey);
17633
+ const needsSetup = !hasUsableProvider(config.providers);
17562
17634
  if (needsSetup) {
17563
17635
  console.log(chalk9__default.default.magenta(" \u25C8 No providers configured \u2014 launching setup wizard\u2026"));
17564
17636
  console.log();
@@ -17599,7 +17671,7 @@ async function runHeadless(prompt, options) {
17599
17671
  process.exit(1);
17600
17672
  }
17601
17673
  const config = cm.getConfig();
17602
- const needsSetup = !config.providers?.length || config.providers.every((p) => p.type !== "ollama" && !p.apiKey);
17674
+ const needsSetup = !hasUsableProvider(config.providers);
17603
17675
  if (needsSetup) {
17604
17676
  console.error(chalk9__default.default.red("No providers configured. Run `cascade init` first."));
17605
17677
  process.exit(1);