cascade-ai 0.2.0 → 0.2.2

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.cjs CHANGED
@@ -164,7 +164,7 @@ var require_keytar2 = __commonJS({
164
164
  });
165
165
 
166
166
  // src/constants.ts
167
- var CASCADE_VERSION = "0.1.2";
167
+ var CASCADE_VERSION = "0.2.2";
168
168
  var CASCADE_CONFIG_DIR = ".cascade";
169
169
  var CASCADE_MD_FILE = "CASCADE.md";
170
170
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -1284,6 +1284,15 @@ var ModelSelector = class {
1284
1284
  markProviderUnavailable(provider) {
1285
1285
  this.availableProviders.delete(provider);
1286
1286
  }
1287
+ /**
1288
+ * Re-add a provider to the available set after it has recovered (e.g. after
1289
+ * a failover timeout expires or a successful call confirms recovery). Only
1290
+ * re-enables providers that were originally configured — callers should
1291
+ * guard against enabling providers that were never configured.
1292
+ */
1293
+ markProviderAvailable(provider) {
1294
+ this.availableProviders.add(provider);
1295
+ }
1287
1296
  resolveDynamicModel(overrideModelId) {
1288
1297
  let providerStr = null;
1289
1298
  let actualId = overrideModelId;
@@ -1353,10 +1362,23 @@ var FailoverManager = class {
1353
1362
  if (!failure) return true;
1354
1363
  if (Date.now() - failure.failedAt >= failure.retryAfterMs) {
1355
1364
  this.failures.delete(provider);
1365
+ this.selector.markProviderAvailable(provider);
1356
1366
  return true;
1357
1367
  }
1358
1368
  return false;
1359
1369
  }
1370
+ /**
1371
+ * Call after a successful generation to immediately re-enable a provider
1372
+ * that had previously been marked unavailable. This allows fast recovery
1373
+ * when a transient rate-limit clears before the backoff window expires,
1374
+ * preventing unnecessary routing to more expensive fallback models.
1375
+ */
1376
+ recordSuccess(provider) {
1377
+ if (this.failures.has(provider)) {
1378
+ this.failures.delete(provider);
1379
+ this.selector.markProviderAvailable(provider);
1380
+ }
1381
+ }
1360
1382
  getFallbackModel(currentModel, tier) {
1361
1383
  return this.selector.getNextFallback(currentModel.id, tier);
1362
1384
  }
@@ -1373,6 +1395,7 @@ var FailoverManager = class {
1373
1395
  }
1374
1396
  clearFailure(provider) {
1375
1397
  this.failures.delete(provider);
1398
+ this.selector.markProviderAvailable(provider);
1376
1399
  }
1377
1400
  };
1378
1401
 
@@ -1585,6 +1608,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
1585
1608
  throw new Error(`Provider ${model.provider}:${model.id} returned an invalid generation result.`);
1586
1609
  }
1587
1610
  this.recordStats(tier, model, result.usage);
1611
+ this.failover.recordSuccess(model.provider);
1588
1612
  return result;
1589
1613
  } catch (err) {
1590
1614
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -2283,14 +2307,13 @@ Now execute your subtask using this context where relevant.`
2283
2307
  await this.peerBus.barrier(this.id, barrierName, total);
2284
2308
  }
2285
2309
  receivePeerSync(fromId, content) {
2286
- const existing = this.peerSyncBuffer.find((p) => p.fromId === fromId);
2287
- if (existing) {
2288
- existing.content = content;
2289
- existing.timestamp = (/* @__PURE__ */ new Date()).toISOString();
2290
- } else {
2291
- this.peerSyncBuffer.push({ fromId, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
2292
- }
2310
+ this.peerSyncBuffer.push({ fromId, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
2293
2311
  this.emit("peer-sync-received", { fromId, content });
2312
+ this.context.addMessage({
2313
+ role: "user",
2314
+ content: `[SYSTEM_NOTIFICATION]: You received a new peer message from ${fromId}. Use the "peer_message" tool with action="receive" to read it.`
2315
+ }).catch(() => {
2316
+ });
2294
2317
  }
2295
2318
  // ── Private ──────────────────────────────────
2296
2319
  async runAgentLoop(systemPrompt, tools) {
@@ -2421,7 +2444,11 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
2421
2444
  sendPeerSync: (to, syncType, content) => {
2422
2445
  this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
2423
2446
  },
2424
- getPeerMessages: () => [...this.peerSyncBuffer]
2447
+ getPeerMessages: () => {
2448
+ const msgs = [...this.peerSyncBuffer];
2449
+ this.peerSyncBuffer = [];
2450
+ return msgs;
2451
+ }
2425
2452
  });
2426
2453
  if (this.audit) {
2427
2454
  this.audit.toolCall(this.id, tc.name, tc.input);
@@ -2963,13 +2990,17 @@ var T2Manager = class extends BaseTier {
2963
2990
  }
2964
2991
  // ── Private ──────────────────────────────────
2965
2992
  async decomposeSection(assignment) {
2993
+ const peerPlans = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_PLAN_ANNOUNCEMENT").map((p) => `[Peer ${p.fromId} Plan]: ${p.content.sectionTitle} - ${p.content.subtaskTitles?.join(", ")}`).join("\n");
2966
2994
  const prompt = `Decompose this section into 2-5 concrete subtasks for T3 workers.
2967
2995
 
2968
2996
  Section: ${assignment.sectionTitle}
2969
2997
  Description: ${assignment.description}
2970
2998
  Expected output: ${assignment.expectedOutput}
2971
2999
  Constraints: ${assignment.constraints.join("; ")}
2972
-
3000
+ ${peerPlans ? `
3001
+ Context from sibling T2 plans (use this to align execution and avoid overlaps):
3002
+ ${peerPlans}
3003
+ ` : ""}
2973
3004
  Return a JSON array of subtask objects, each with:
2974
3005
  - subtaskId: string (unique)
2975
3006
  - subtaskTitle: string
@@ -3183,9 +3214,14 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
3183
3214
  const completed = results.filter((r) => r.status === "COMPLETED");
3184
3215
  if (!completed.length) return `Section ${assignment.sectionTitle} failed \u2014 no T3 workers completed.`;
3185
3216
  const outputs = completed.map((r, i) => `[T3-${i + 1}]: ${r.output}`).join("\n\n");
3217
+ const peerOutputs = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_SECTION_OUTPUT").map((p) => `[Peer ${p.fromId} Output]: ${p.content.output}`).join("\n\n");
3186
3218
  const prompt = `Summarize these T3 worker outputs for section "${assignment.sectionTitle}" in 2-3 sentences:
3187
3219
 
3188
- ${outputs}`;
3220
+ ${outputs}
3221
+ ${peerOutputs ? `
3222
+
3223
+ Context from sibling T2 completed sections (use this to ensure your summary aligns with the overall state):
3224
+ ${peerOutputs}` : ""}`;
3189
3225
  const messages = [{ role: "user", content: prompt }];
3190
3226
  try {
3191
3227
  const result = await this.router.generate("T2", {