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/index.d.cts CHANGED
@@ -1574,10 +1574,15 @@ type T1Evaluator = (req: PermissionRequest) => Promise<PermissionDecision | null
1574
1574
  * PermissionEscalator manages the hierarchical permission flow for a single task run.
1575
1575
  *
1576
1576
  * Decision cascade:
1577
- * 1. Check session cache (section-wide key `${t2Id}:${toolName}`) return if hit
1578
- * 2. Ask T2 evaluator if decision returned, cache + return
1579
- * 3. Ask T1 evaluator if decision returned, cache + return
1580
- * 4. Emit `permission:user-required` wait for external decision via `resolveUserDecision()`
1577
+ * 1. Check the task-wide cache (USER/T1 "always" decisions, keyed by `toolName`
1578
+ * alone) → return if hit. These are deliberately NOT scoped to one T2, so a
1579
+ * user's or T1's "Always" covers every sibling worker anywhere in the run.
1580
+ * 2. Check the per-T2 session cache (section-wide key `${t2Id}:${toolName}`)
1581
+ * → return if hit
1582
+ * 3. Ask T2 evaluator → if decision returned, cache (per-T2) + return
1583
+ * 4. Ask T1 evaluator → if decision returned, cache (task-wide) + return
1584
+ * 5. Emit `permission:user-required` → wait for external decision via
1585
+ * `resolveUserDecision()`; an "always" answer caches task-wide.
1581
1586
  */
1582
1587
  declare class PermissionEscalator extends EventEmitter {
1583
1588
  /**
@@ -1585,6 +1590,13 @@ declare class PermissionEscalator extends EventEmitter {
1585
1590
  * All T3 workers under the same T2 share cached decisions for the same tool.
1586
1591
  */
1587
1592
  private sessionCache;
1593
+ /**
1594
+ * Task-wide cache keyed by `toolName` alone, for USER- and T1-level
1595
+ * "always" decisions — these are meant to cover every sibling T2/T3 in the
1596
+ * run, not just the one that happened to ask first (see PermissionDecision
1597
+ * doc comment: "task-wide for T1").
1598
+ */
1599
+ private taskWideCache;
1588
1600
  private t2Evaluator?;
1589
1601
  private t1Evaluator?;
1590
1602
  /** Pending user-decision resolvers keyed by request ID */
@@ -1742,6 +1754,9 @@ declare class T1Administrator extends BaseTier {
1742
1754
  * so it never blocks the approval gate.
1743
1755
  */
1744
1756
  private reviewPlan;
1757
+ /** Structured, grounded summary of what's already done — used to keep
1758
+ * corrective replan passes from re-emitting completed sections. */
1759
+ private summarizeCompletedSections;
1745
1760
  private decomposeTask;
1746
1761
  private validatePlan;
1747
1762
  private dispatchT2Managers;
@@ -2392,6 +2407,14 @@ declare class DashboardServer {
2392
2407
  start(): Promise<void>;
2393
2408
  stop(): Promise<void>;
2394
2409
  getSocket(): DashboardSocket;
2410
+ /**
2411
+ * Rebind the workspace tasks execute in — e.g. the desktop app's Code view
2412
+ * opening a different project folder — without tearing down the socket
2413
+ * server (which would drop the port/auth token/connection mid-session).
2414
+ * The next `cascade:run` picks this up immediately since `this.workspacePath`
2415
+ * is read live per-run (see onCascadeRun below).
2416
+ */
2417
+ setWorkspacePath(workspacePath: string): void;
2395
2418
  /**
2396
2419
  * Write the in-memory config back to the workspace config file so mutations
2397
2420
  * made over the socket (Settings → Save) persist across restarts. Best-effort:
package/dist/index.d.ts CHANGED
@@ -1574,10 +1574,15 @@ type T1Evaluator = (req: PermissionRequest) => Promise<PermissionDecision | null
1574
1574
  * PermissionEscalator manages the hierarchical permission flow for a single task run.
1575
1575
  *
1576
1576
  * Decision cascade:
1577
- * 1. Check session cache (section-wide key `${t2Id}:${toolName}`) return if hit
1578
- * 2. Ask T2 evaluator if decision returned, cache + return
1579
- * 3. Ask T1 evaluator if decision returned, cache + return
1580
- * 4. Emit `permission:user-required` wait for external decision via `resolveUserDecision()`
1577
+ * 1. Check the task-wide cache (USER/T1 "always" decisions, keyed by `toolName`
1578
+ * alone) → return if hit. These are deliberately NOT scoped to one T2, so a
1579
+ * user's or T1's "Always" covers every sibling worker anywhere in the run.
1580
+ * 2. Check the per-T2 session cache (section-wide key `${t2Id}:${toolName}`)
1581
+ * → return if hit
1582
+ * 3. Ask T2 evaluator → if decision returned, cache (per-T2) + return
1583
+ * 4. Ask T1 evaluator → if decision returned, cache (task-wide) + return
1584
+ * 5. Emit `permission:user-required` → wait for external decision via
1585
+ * `resolveUserDecision()`; an "always" answer caches task-wide.
1581
1586
  */
1582
1587
  declare class PermissionEscalator extends EventEmitter {
1583
1588
  /**
@@ -1585,6 +1590,13 @@ declare class PermissionEscalator extends EventEmitter {
1585
1590
  * All T3 workers under the same T2 share cached decisions for the same tool.
1586
1591
  */
1587
1592
  private sessionCache;
1593
+ /**
1594
+ * Task-wide cache keyed by `toolName` alone, for USER- and T1-level
1595
+ * "always" decisions — these are meant to cover every sibling T2/T3 in the
1596
+ * run, not just the one that happened to ask first (see PermissionDecision
1597
+ * doc comment: "task-wide for T1").
1598
+ */
1599
+ private taskWideCache;
1588
1600
  private t2Evaluator?;
1589
1601
  private t1Evaluator?;
1590
1602
  /** Pending user-decision resolvers keyed by request ID */
@@ -1742,6 +1754,9 @@ declare class T1Administrator extends BaseTier {
1742
1754
  * so it never blocks the approval gate.
1743
1755
  */
1744
1756
  private reviewPlan;
1757
+ /** Structured, grounded summary of what's already done — used to keep
1758
+ * corrective replan passes from re-emitting completed sections. */
1759
+ private summarizeCompletedSections;
1745
1760
  private decomposeTask;
1746
1761
  private validatePlan;
1747
1762
  private dispatchT2Managers;
@@ -2392,6 +2407,14 @@ declare class DashboardServer {
2392
2407
  start(): Promise<void>;
2393
2408
  stop(): Promise<void>;
2394
2409
  getSocket(): DashboardSocket;
2410
+ /**
2411
+ * Rebind the workspace tasks execute in — e.g. the desktop app's Code view
2412
+ * opening a different project folder — without tearing down the socket
2413
+ * server (which would drop the port/auth token/connection mid-session).
2414
+ * The next `cascade:run` picks this up immediately since `this.workspacePath`
2415
+ * is read live per-run (see onCascadeRun below).
2416
+ */
2417
+ setWorkspacePath(workspacePath: string): void;
2395
2418
  /**
2396
2419
  * Write the in-memory config back to the workspace config file so mutations
2397
2420
  * made over the socket (Settings → Save) persist across restarts. Best-effort:
package/dist/index.js CHANGED
@@ -190,7 +190,7 @@ var init_audit_logger = __esm({
190
190
  });
191
191
 
192
192
  // src/constants.ts
193
- var CASCADE_VERSION = "0.15.2";
193
+ var CASCADE_VERSION = "0.16.0";
194
194
  var CASCADE_CONFIG_DIR = ".cascade";
195
195
  var CASCADE_MD_FILE = "CASCADE.md";
196
196
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -5506,6 +5506,10 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
5506
5506
  const worker = new T3Worker(this.router, this.toolRegistry, this.id);
5507
5507
  if (this.store) worker.setStore(this.store, taskId);
5508
5508
  worker.setPeerBus(this.t3PeerBus);
5509
+ if (this.permissionEscalator) worker.setPermissionEscalator(this.permissionEscalator);
5510
+ if (this.toolCreator) worker.setToolCreator(this.toolCreator);
5511
+ worker.on("log", (e) => this.emit("log", e));
5512
+ worker.on("tier:status", (e) => this.emit("tier:status", e));
5509
5513
  worker.on("stream:token", (e) => this.emit("stream:token", e));
5510
5514
  worker.on("tool:approval-request", (e) => this.emit("tool:approval-request", {
5511
5515
  ...e,
@@ -5854,12 +5858,14 @@ Board guidance (must be followed in the plan): ${decision.note}`,
5854
5858
  status: "IN_PROGRESS"
5855
5859
  });
5856
5860
  const okBefore = okCount(allT2Results);
5861
+ const completedSummary = this.summarizeCompletedSections(allT2Results);
5862
+ const correctionContext = [systemContext, completedSummary].filter(Boolean).join("\n\n") || void 0;
5857
5863
  const correctionPlan = await this.decomposeTask(`The previous execution plan failed to fully satisfy the original goal or encountered errors.
5858
5864
  Review reason: ${reviewResult.reason}
5859
5865
 
5860
5866
  Original goal: ${enrichedPrompt}
5861
5867
 
5862
- Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`);
5868
+ Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`, correctionContext);
5863
5869
  const correctionResults = await this.dispatchT2Managers(correctionPlan.sections);
5864
5870
  allT2Results = [...allT2Results, ...correctionResults];
5865
5871
  if (okCount(allT2Results) <= okBefore) {
@@ -5962,6 +5968,15 @@ In 3-5 terse bullets, flag the most important RISKS, GAPS, or over-/under-decomp
5962
5968
  return null;
5963
5969
  }
5964
5970
  }
5971
+ /** Structured, grounded summary of what's already done — used to keep
5972
+ * corrective replan passes from re-emitting completed sections. */
5973
+ summarizeCompletedSections(results) {
5974
+ const done = results.filter((r) => r.status === "COMPLETED" || r.status === "PARTIAL");
5975
+ if (done.length === 0) return "";
5976
+ const lines = done.map((r) => `- "${r.sectionTitle}" [${r.status}]: ${(r.sectionSummary || "(no summary)").slice(0, 300)}`);
5977
+ return `ALREADY COMPLETED SECTIONS (do not redo these \u2014 plan only what's still missing):
5978
+ ${lines.join("\n")}`;
5979
+ }
5965
5980
  async decomposeTask(prompt, systemContext) {
5966
5981
  const db = this.router.getWorldStateDB?.();
5967
5982
  let worldStateContext = "";
@@ -6229,7 +6244,7 @@ Leave dependsOn empty for sections that can run immediately in parallel.`;
6229
6244
  }
6230
6245
  }
6231
6246
  if (readyIds.length === 0) return;
6232
- await Promise.all(readyIds.map(async (id) => {
6247
+ const runOne = async (id) => {
6233
6248
  resultMap.set(id, null);
6234
6249
  const index = sections.findIndex((s) => s.sectionId === id);
6235
6250
  const section = sections[index];
@@ -6269,7 +6284,14 @@ Leave dependsOn empty for sections that can run immediately in parallel.`;
6269
6284
  for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
6270
6285
  inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
6271
6286
  }
6272
- }));
6287
+ };
6288
+ if (this.router.getT3ExecutionMode?.() === "sequential") {
6289
+ for (const id of readyIds) {
6290
+ await runOne(id);
6291
+ }
6292
+ } else {
6293
+ await Promise.all(readyIds.map(runOne));
6294
+ }
6273
6295
  if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
6274
6296
  await executeWave();
6275
6297
  }
@@ -8067,6 +8089,13 @@ var PermissionEscalator = class extends EventEmitter {
8067
8089
  * All T3 workers under the same T2 share cached decisions for the same tool.
8068
8090
  */
8069
8091
  sessionCache = /* @__PURE__ */ new Map();
8092
+ /**
8093
+ * Task-wide cache keyed by `toolName` alone, for USER- and T1-level
8094
+ * "always" decisions — these are meant to cover every sibling T2/T3 in the
8095
+ * run, not just the one that happened to ask first (see PermissionDecision
8096
+ * doc comment: "task-wide for T1").
8097
+ */
8098
+ taskWideCache = /* @__PURE__ */ new Map();
8070
8099
  t2Evaluator;
8071
8100
  t1Evaluator;
8072
8101
  /** Pending user-decision resolvers keyed by request ID */
@@ -8095,6 +8124,15 @@ var PermissionEscalator = class extends EventEmitter {
8095
8124
  * Returns a PermissionDecision from whichever tier was able to decide.
8096
8125
  */
8097
8126
  async requestPermission(req) {
8127
+ if (!req.forceReprompt && this.taskWideCache.has(req.toolName)) {
8128
+ return {
8129
+ requestId: req.id,
8130
+ approved: this.taskWideCache.get(req.toolName),
8131
+ always: true,
8132
+ decidedBy: "T1",
8133
+ reasoning: "Cached from a previous task-wide decision in this session"
8134
+ };
8135
+ }
8098
8136
  const cacheKey = `${req.parentT2Id}:${req.toolName}`;
8099
8137
  if (!req.forceReprompt && this.sessionCache.has(cacheKey)) {
8100
8138
  return {
@@ -8139,7 +8177,7 @@ var PermissionEscalator = class extends EventEmitter {
8139
8177
  try {
8140
8178
  const t1Decision = await this.t1Evaluator(req);
8141
8179
  if (t1Decision !== null) {
8142
- if (t1Decision.always) this.sessionCache.set(cacheKey, t1Decision.approved);
8180
+ if (t1Decision.always) this.taskWideCache.set(req.toolName, t1Decision.approved);
8143
8181
  return t1Decision;
8144
8182
  }
8145
8183
  } catch {
@@ -8169,7 +8207,7 @@ var PermissionEscalator = class extends EventEmitter {
8169
8207
  const wrappedResolver = (decision) => {
8170
8208
  if (timer) clearTimeout(timer);
8171
8209
  if (decision.always) {
8172
- this.sessionCache.set(`${req.parentT2Id}:${req.toolName}`, decision.approved);
8210
+ this.taskWideCache.set(req.toolName, decision.approved);
8173
8211
  }
8174
8212
  resolve(decision);
8175
8213
  };
@@ -12088,6 +12126,16 @@ var DashboardServer = class {
12088
12126
  getSocket() {
12089
12127
  return this.socket;
12090
12128
  }
12129
+ /**
12130
+ * Rebind the workspace tasks execute in — e.g. the desktop app's Code view
12131
+ * opening a different project folder — without tearing down the socket
12132
+ * server (which would drop the port/auth token/connection mid-session).
12133
+ * The next `cascade:run` picks this up immediately since `this.workspacePath`
12134
+ * is read live per-run (see onCascadeRun below).
12135
+ */
12136
+ setWorkspacePath(workspacePath) {
12137
+ this.workspacePath = workspacePath;
12138
+ }
12091
12139
  /**
12092
12140
  * Write the in-memory config back to the workspace config file so mutations
12093
12141
  * made over the socket (Settings → Save) persist across restarts. Best-effort: