cascade-ai 0.2.12 → 0.4.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
@@ -130,7 +130,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
130
130
  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;
131
131
  var init_constants = __esm({
132
132
  "src/constants.ts"() {
133
- CASCADE_VERSION = "0.2.12";
133
+ CASCADE_VERSION = "0.4.0";
134
134
  CASCADE_CONFIG_FILE = ".cascade/config.json";
135
135
  CASCADE_DB_FILE = ".cascade/memory.db";
136
136
  CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
@@ -949,19 +949,21 @@ var init_azure = __esm({
949
949
  init_openai();
950
950
  AzureOpenAIProvider = class extends OpenAIProvider {
951
951
  constructor(config, model) {
952
- const baseUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
952
+ const rawUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
953
+ const endpoint = rawUrl.replace(/\/+$/, "");
953
954
  super(
954
955
  {
955
956
  ...config,
956
- baseUrl: `${baseUrl}/openai/deployments/${config.deploymentName ?? model.id}`
957
+ baseUrl: endpoint
958
+ // Kept for superclass compatibility if it reads it
957
959
  },
958
960
  model
959
961
  );
960
- this.client = new OpenAI__default.default({
962
+ this.client = new OpenAI.AzureOpenAI({
961
963
  apiKey: config.apiKey,
962
- baseURL: `${baseUrl}/openai/deployments/${config.deploymentName ?? model.id}`,
963
- defaultQuery: { "api-version": config.apiVersion ?? "2024-08-01-preview" },
964
- defaultHeaders: { "api-key": config.apiKey ?? "" }
964
+ endpoint,
965
+ deployment: config.deploymentName ?? model.id,
966
+ apiVersion: config.apiVersion ?? "2024-08-01-preview"
965
967
  });
966
968
  }
967
969
  async listModels() {
@@ -1922,8 +1924,8 @@ Original error: ${err.message}`
1922
1924
  upsertRuntimeNode(node) {
1923
1925
  this.enqueueWrite(() => {
1924
1926
  this.db.prepare(`
1925
- INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global)
1926
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1927
+ INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global, output)
1928
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1927
1929
  ON CONFLICT(tier_id) DO UPDATE SET
1928
1930
  session_id = excluded.session_id,
1929
1931
  parent_id = excluded.parent_id,
@@ -1934,7 +1936,8 @@ Original error: ${err.message}`
1934
1936
  progress_pct = excluded.progress_pct,
1935
1937
  updated_at = excluded.updated_at,
1936
1938
  workspace_path = excluded.workspace_path,
1937
- is_global = excluded.is_global
1939
+ is_global = excluded.is_global,
1940
+ output = excluded.output
1938
1941
  `).run(
1939
1942
  node.tierId,
1940
1943
  node.sessionId,
@@ -1946,7 +1949,8 @@ Original error: ${err.message}`
1946
1949
  node.progressPct ?? null,
1947
1950
  node.updatedAt,
1948
1951
  node.workspacePath ?? null,
1949
- node.isGlobal ? 1 : 0
1952
+ node.isGlobal ? 1 : 0,
1953
+ node.output ?? null
1950
1954
  );
1951
1955
  });
1952
1956
  }
@@ -1967,14 +1971,15 @@ Original error: ${err.message}`
1967
1971
  progressPct: row.progress_pct ?? void 0,
1968
1972
  updatedAt: row.updated_at,
1969
1973
  workspacePath: row.workspace_path ?? void 0,
1970
- isGlobal: row.is_global === 1
1974
+ isGlobal: row.is_global === 1,
1975
+ output: row.output ?? void 0
1971
1976
  }));
1972
1977
  }
1973
1978
  addRuntimeNodeLog(log) {
1974
1979
  this.enqueueWrite(() => {
1975
1980
  this.db.prepare(`
1976
- INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global)
1977
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1981
+ INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global, output)
1982
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1978
1983
  `).run(
1979
1984
  log.id,
1980
1985
  log.sessionId,
@@ -1986,7 +1991,8 @@ Original error: ${err.message}`
1986
1991
  log.progressPct ?? null,
1987
1992
  log.timestamp,
1988
1993
  log.workspacePath ?? null,
1989
- log.isGlobal ? 1 : 0
1994
+ log.isGlobal ? 1 : 0,
1995
+ log.output ?? null
1990
1996
  );
1991
1997
  this.db.prepare(`
1992
1998
  DELETE FROM runtime_node_logs
@@ -2029,7 +2035,8 @@ Original error: ${err.message}`
2029
2035
  progressPct: row.progress_pct ?? void 0,
2030
2036
  timestamp: row.timestamp,
2031
2037
  workspacePath: row.workspace_path ?? void 0,
2032
- isGlobal: row.is_global === 1
2038
+ isGlobal: row.is_global === 1,
2039
+ output: row.output ?? void 0
2033
2040
  }));
2034
2041
  }
2035
2042
  // ── Messages ──────────────────────────────────
@@ -2356,7 +2363,8 @@ Original error: ${err.message}`
2356
2363
  progress_pct INTEGER,
2357
2364
  updated_at TEXT NOT NULL,
2358
2365
  workspace_path TEXT,
2359
- is_global INTEGER NOT NULL DEFAULT 0
2366
+ is_global INTEGER NOT NULL DEFAULT 0,
2367
+ output TEXT
2360
2368
  );
2361
2369
 
2362
2370
  CREATE INDEX IF NOT EXISTS idx_runtime_nodes_session ON runtime_nodes(session_id);
@@ -2373,7 +2381,8 @@ Original error: ${err.message}`
2373
2381
  progress_pct INTEGER,
2374
2382
  timestamp TEXT NOT NULL,
2375
2383
  workspace_path TEXT,
2376
- is_global INTEGER NOT NULL DEFAULT 0
2384
+ is_global INTEGER NOT NULL DEFAULT 0,
2385
+ output TEXT
2377
2386
  );
2378
2387
 
2379
2388
  CREATE TABLE IF NOT EXISTS model_cache (
@@ -2402,6 +2411,14 @@ Original error: ${err.message}`
2402
2411
 
2403
2412
  CREATE INDEX IF NOT EXISTS idx_file_snapshots_session ON file_snapshots(session_id);
2404
2413
  `);
2414
+ try {
2415
+ this.db.exec("ALTER TABLE runtime_nodes ADD COLUMN output TEXT");
2416
+ } catch {
2417
+ }
2418
+ try {
2419
+ this.db.exec("ALTER TABLE runtime_node_logs ADD COLUMN output TEXT");
2420
+ } catch {
2421
+ }
2405
2422
  }
2406
2423
  // ── Deserializers ─────────────────────────────
2407
2424
  deserializeSession(row, messages) {
@@ -3280,7 +3297,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
3280
3297
  if (!model) {
3281
3298
  throw new Error(`Configured model "${override}" for ${tier} could not be loaded. Check provider availability and exact model name.`);
3282
3299
  }
3283
- if (model.id !== override) {
3300
+ if (model.id !== override && `${model.provider}:${model.id}` !== override) {
3284
3301
  throw new Error(`Configured model "${override}" for ${tier} resolved to "${model.id}". Use the exact provider model ID or prefix the provider (e.g. gemini:${override}).`);
3285
3302
  }
3286
3303
  this.tierModels.set(tier, model);
@@ -3630,7 +3647,7 @@ var BaseTier = class extends EventEmitter__default.default {
3630
3647
  getStatus() {
3631
3648
  return this.status;
3632
3649
  }
3633
- setStatus(status) {
3650
+ setStatus(status, output) {
3634
3651
  this.status = status;
3635
3652
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3636
3653
  const event = {
@@ -3639,7 +3656,8 @@ var BaseTier = class extends EventEmitter__default.default {
3639
3656
  parentId: this.parentId,
3640
3657
  label: this.label,
3641
3658
  status,
3642
- timestamp
3659
+ timestamp,
3660
+ output
3643
3661
  };
3644
3662
  this.emit("status", event);
3645
3663
  this.emit("tier:status", event);
@@ -3665,7 +3683,8 @@ var BaseTier = class extends EventEmitter__default.default {
3665
3683
  status: this.status,
3666
3684
  currentAction: update.currentAction,
3667
3685
  progressPct: update.progressPct,
3668
- timestamp
3686
+ timestamp,
3687
+ output: update.output
3669
3688
  });
3670
3689
  }
3671
3690
  buildMessage(type, to, payload) {
@@ -4046,16 +4065,17 @@ Now execute your subtask using this context where relevant.`
4046
4065
  return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
4047
4066
  }
4048
4067
  }
4049
- this.setStatus("COMPLETED");
4050
- this.sendStatusUpdate({ progressPct: 100, currentAction: "Subtask complete", status: "IN_PROGRESS" });
4068
+ this.setStatus("COMPLETED", output);
4069
+ this.sendStatusUpdate({ progressPct: 100, currentAction: "Subtask complete", status: "IN_PROGRESS", output });
4051
4070
  this.peerBus?.publish(this.id, assignment.subtaskId, output, "COMPLETED");
4052
4071
  return this.buildResult("COMPLETED", output, { checksRun, passed, failed }, issues, correctionAttempts);
4053
4072
  } catch (err) {
4054
4073
  const errMsg = err instanceof Error ? err.message : String(err);
4055
4074
  issues.push(`Execution error: ${errMsg}`);
4056
- this.setStatus("FAILED");
4057
- this.peerBus?.publish(this.id, assignment.subtaskId, errMsg, "FAILED");
4058
- return this.buildResult("ESCALATED", output || errMsg, { checksRun, passed, failed }, issues, correctionAttempts);
4075
+ const finalOutput = output || errMsg;
4076
+ this.setStatus("FAILED", finalOutput);
4077
+ this.peerBus?.publish(this.id, assignment.subtaskId, finalOutput, "FAILED");
4078
+ return this.buildResult("ESCALATED", finalOutput, { checksRun, passed, failed }, issues, correctionAttempts);
4059
4079
  }
4060
4080
  }
4061
4081
  sendToPeer(toId, content) {
@@ -4109,6 +4129,10 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
4109
4129
  await this.context.addMessage({ role: "assistant", content: result.content, toolCalls: result.toolCalls });
4110
4130
  if (!result.toolCalls?.length) {
4111
4131
  if (requiresArtifact) {
4132
+ const artifactCheck = await this.verifyArtifacts(this.assignment);
4133
+ if (artifactCheck.ok) {
4134
+ return { output: result.content, toolCalls: allToolCalls };
4135
+ }
4112
4136
  stalledArtifactIterations += 1;
4113
4137
  if (stalledArtifactIterations >= 2) {
4114
4138
  if (stalledArtifactIterations === 2) {
@@ -4118,15 +4142,22 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
4118
4142
  }
4119
4143
  await this.context.addMessage({
4120
4144
  role: "user",
4121
- content: "You have not yet created and verified the required artifact. Use tools to create the file in the workspace, verify it exists, and inspect the result before concluding."
4145
+ content: `You have not yet created and verified the required artifact. Issues: ${artifactCheck.issues.join("; ")}. Use tools to create the file in the workspace, verify it exists, and inspect the result before concluding.`
4122
4146
  });
4123
4147
  continue;
4124
4148
  }
4125
4149
  return { output: result.content, toolCalls: allToolCalls };
4126
4150
  }
4127
4151
  stalledArtifactIterations = 0;
4128
- if (result.finishReason === "stop" && !requiresArtifact) {
4129
- return { output: result.content, toolCalls: allToolCalls };
4152
+ if (result.finishReason === "stop") {
4153
+ if (requiresArtifact) {
4154
+ const artifactCheck = await this.verifyArtifacts(this.assignment);
4155
+ if (artifactCheck.ok) {
4156
+ return { output: result.content, toolCalls: allToolCalls };
4157
+ }
4158
+ } else {
4159
+ return { output: result.content, toolCalls: allToolCalls };
4160
+ }
4130
4161
  }
4131
4162
  for (const tc of result.toolCalls) {
4132
4163
  allToolCalls.push(tc);
@@ -4738,8 +4769,9 @@ var T2Manager = class extends BaseTier {
4738
4769
  const summary = await this.aggregateResults(assignment, t3Results);
4739
4770
  const issues = t3Results.filter((r) => r.status !== "COMPLETED").flatMap((r) => r.issues);
4740
4771
  const overallStatus = this.determineStatus(t3Results);
4741
- this.setStatus(overallStatus === "COMPLETED" ? "COMPLETED" : "FAILED");
4742
- this.sendStatusUpdate({ progressPct: 100, currentAction: "Section complete", status: "IN_PROGRESS" });
4772
+ const isOk = overallStatus === "COMPLETED" || overallStatus === "PARTIAL";
4773
+ this.setStatus(isOk ? "COMPLETED" : "FAILED", summary);
4774
+ this.sendStatusUpdate({ progressPct: 100, currentAction: "Section complete", status: "IN_PROGRESS", output: summary });
4743
4775
  const result = {
4744
4776
  sectionId: assignment.sectionId,
4745
4777
  sectionTitle: assignment.sectionTitle,
@@ -4752,7 +4784,7 @@ var T2Manager = class extends BaseTier {
4752
4784
  return result;
4753
4785
  } catch (err) {
4754
4786
  const errMsg = err instanceof Error ? err.message : String(err);
4755
- this.setStatus("FAILED");
4787
+ this.setStatus("FAILED", errMsg);
4756
4788
  const failedResult = {
4757
4789
  sectionId: assignment.sectionId,
4758
4790
  sectionTitle: assignment.sectionTitle,
@@ -5317,8 +5349,8 @@ Create a CORRECTION PLAN that contains only the new sections needed to fix the i
5317
5349
  status: "IN_PROGRESS"
5318
5350
  });
5319
5351
  const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
5320
- this.setStatus("COMPLETED");
5321
- this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
5352
+ this.setStatus("COMPLETED", output);
5353
+ this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS", output });
5322
5354
  return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
5323
5355
  }
5324
5356
  getEscalations() {
@@ -7596,10 +7628,17 @@ var Cascade = class extends EventEmitter__default.default {
7596
7628
  throw err;
7597
7629
  }
7598
7630
  }
7631
+ isCasualGreeting(prompt) {
7632
+ const casual = /^(hi|hello|hey|greetings|thanks|thank you|thx|bye|goodbye|cya)$/i.test(prompt.trim().replace(/[!?.]+$/, ""));
7633
+ return casual;
7634
+ }
7599
7635
  looksLikeSimpleArtifactTask(prompt) {
7600
7636
  return /create .*\.(txt|md|json|csv)\b/i.test(prompt) && !/(research|compare|thorough|pdf|report|analy[sz]e|architecture|multi-agent)/i.test(prompt);
7601
7637
  }
7602
7638
  async determineComplexity(prompt, workspacePath, conversationHistory = []) {
7639
+ if (this.isCasualGreeting(prompt)) {
7640
+ return "Simple";
7641
+ }
7603
7642
  if (this.looksLikeSimpleArtifactTask(prompt)) {
7604
7643
  return "Simple";
7605
7644
  }
@@ -8275,35 +8314,69 @@ function AgentTree({ root, theme }) {
8275
8314
  }
8276
8315
  function T2Row({ node, theme, isLast }) {
8277
8316
  const connector = isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
8278
- const t3Active = countByRoleAndStatus(node, "T3", "ACTIVE");
8279
- const t3Total = countByRole(node, "T3");
8280
- const workerSuffix = t3Total > 0 ? ` T3\xD7${t3Total}` : "";
8317
+ const t3Nodes = (node.children ?? []).filter((c) => c.role === "T3");
8318
+ const t3ActiveCount = t3Nodes.filter((c) => c.status === "ACTIVE").length;
8281
8319
  const label = stripRolePrefix(node.label, node.role);
8282
8320
  const action = node.currentAction ? ` ${node.currentAction.slice(0, 38)}` : "";
8321
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
8322
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { children: [
8323
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.muted, children: [
8324
+ " ",
8325
+ connector
8326
+ ] }),
8327
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.t2Color, bold: true, children: "[T2]" }),
8328
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.foreground, children: [
8329
+ " ",
8330
+ label
8331
+ ] }),
8332
+ node.status === "ACTIVE" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8333
+ action ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, children: action }) : null,
8334
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }),
8335
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner__default.default, { type: "dots" }),
8336
+ t3ActiveCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.muted, children: [
8337
+ " (",
8338
+ t3ActiveCount,
8339
+ " running)"
8340
+ ] }) : null
8341
+ ] }),
8342
+ node.status === "COMPLETED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.success, children: " \u2714" }),
8343
+ node.status === "FAILED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.error, children: " \u2718" }),
8344
+ node.status === "ESCALATED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.warning, children: " \u25B2" })
8345
+ ] }),
8346
+ t3Nodes.map((t3, idx) => /* @__PURE__ */ jsxRuntime.jsx(
8347
+ T3Row,
8348
+ {
8349
+ node: t3,
8350
+ theme,
8351
+ isLast: idx === t3Nodes.length - 1,
8352
+ parentIsLast: isLast
8353
+ },
8354
+ t3.id
8355
+ ))
8356
+ ] });
8357
+ }
8358
+ function T3Row({ node, theme, isLast, parentIsLast }) {
8359
+ const indent = parentIsLast ? " " : " \u2502 ";
8360
+ const connector = isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
8361
+ const label = stripRolePrefix(node.label, node.role);
8362
+ const action = node.currentAction ? ` ${node.currentAction.slice(0, 42)}` : "";
8283
8363
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { children: [
8284
8364
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.muted, children: [
8285
- " ",
8365
+ indent,
8286
8366
  connector
8287
8367
  ] }),
8288
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.t2Color, bold: true, children: "[T2]" }),
8289
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.foreground, children: [
8368
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.t3Color, children: "[T3]" }),
8369
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.muted, children: [
8290
8370
  " ",
8291
8371
  label
8292
8372
  ] }),
8293
- workerSuffix ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, children: workerSuffix }) : null,
8294
8373
  node.status === "ACTIVE" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8295
8374
  action ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, children: action }) : null,
8296
8375
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }),
8297
- /* @__PURE__ */ jsxRuntime.jsx(Spinner__default.default, { type: "dots" }),
8298
- t3Active > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.muted, children: [
8299
- " (",
8300
- t3Active,
8301
- " running)"
8302
- ] }) : null
8376
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner__default.default, { type: "dots" })
8303
8377
  ] }),
8304
8378
  node.status === "COMPLETED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.success, children: " \u2714" }),
8305
- node.status === "FAILED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.error, children: " \u2718" }),
8306
- node.status === "ESCALATED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.warning, children: " \u25B2" })
8379
+ node.status === "FAILED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.error, children: " \u2718" })
8307
8380
  ] });
8308
8381
  }
8309
8382
  function hasActiveOrFailed(node) {
@@ -8872,7 +8945,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName })
8872
8945
  if (sel.kind === "auto") {
8873
8946
  delete config.models[tierKey];
8874
8947
  } else {
8875
- config.models[tierKey] = sel.modelId;
8948
+ config.models[tierKey] = `${sel.provider}:${sel.modelId}`;
8876
8949
  }
8877
8950
  try {
8878
8951
  const router = cascadeRef.current?.getRouter();
@@ -8915,7 +8988,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName })
8915
8988
  treeNodesRef.current.set(event.tierId, node);
8916
8989
  const store = storeRef.current;
8917
8990
  if (store) {
8918
- const runtimeNode = { tierId: node.id, sessionId: sessionIdRef.current, parentId: node.parentId, role: node.role, label: node.label, status: node.status, currentAction: node.currentAction, progressPct: node.progressPct, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), workspacePath, isGlobal: false };
8991
+ const runtimeNode = { tierId: node.id, sessionId: sessionIdRef.current, parentId: node.parentId, role: node.role, label: node.label, status: node.status, currentAction: node.currentAction, progressPct: node.progressPct, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), workspacePath, isGlobal: false, output: event.output };
8919
8992
  store.upsertRuntimeNode(runtimeNode);
8920
8993
  globalStoreRef.current?.upsertRuntimeNode({ ...runtimeNode, isGlobal: true });
8921
8994
  }
@@ -8926,7 +8999,6 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName })
8926
8999
  React5.useEffect(() => {
8927
9000
  const originalWarn = console.warn;
8928
9001
  const originalLog = console.log;
8929
- process.stdout.write("\x1Bc");
8930
9002
  console.warn = (...args) => {
8931
9003
  const msg = args.join(" ");
8932
9004
  if (msg.includes("non-text parts") || msg.includes("functionCall")) return;
@@ -9431,9 +9503,15 @@ Use /identity <name|id> to switch.`;
9431
9503
  let agentTreeHeight = 0;
9432
9504
  if (state.agentTree && hasActiveOrFailed2(state.agentTree)) {
9433
9505
  agentTreeHeight = 1;
9434
- const childrenCount = state.agentTree.children?.length ?? 0;
9435
- agentTreeHeight += Math.min(childrenCount, 6);
9436
- if (childrenCount > 6) agentTreeHeight += 1;
9506
+ const visibleT2s = state.agentTree.children?.slice(0, 6) ?? [];
9507
+ agentTreeHeight += visibleT2s.length;
9508
+ for (const t2 of visibleT2s) {
9509
+ const t3Count = (t2.children ?? []).filter((c) => c.role === "T3").length;
9510
+ agentTreeHeight += t3Count;
9511
+ }
9512
+ if ((state.agentTree.children?.length ?? 0) > 6) {
9513
+ agentTreeHeight += 1;
9514
+ }
9437
9515
  }
9438
9516
  let timelineHeight = 0;
9439
9517
  if (state.showDetails && treeNodesRef.current.size > 0) {
@@ -9622,6 +9700,13 @@ async function validateConfiguredModels(config) {
9622
9700
  return problems.length ? `Model warnings: ${problems.join(", ")}` : null;
9623
9701
  }
9624
9702
  function inferProviderFromModelId(id, providers) {
9703
+ if (id.includes(":")) {
9704
+ const prefix = id.split(":")[0].toLowerCase();
9705
+ const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
9706
+ if (validProviders.includes(prefix)) {
9707
+ return prefix;
9708
+ }
9709
+ }
9625
9710
  const lower = id.toLowerCase();
9626
9711
  if (lower.includes("gpt")) return "openai";
9627
9712
  if (lower.includes("claude")) return "anthropic";
@@ -11036,8 +11121,8 @@ var DashboardServer = class {
11036
11121
  void (async () => {
11037
11122
  const cascade = new Cascade(this.config, this.workspacePath, this.store);
11038
11123
  cascade.on("stream:token", (e) => {
11039
- this.socket.broadcast("stream:token", { sessionId, token: e.text });
11040
- this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, token: e.text });
11124
+ this.socket.broadcast("stream:token", { sessionId, tierId: e.tierId, text: e.text });
11125
+ this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, tierId: e.tierId, text: e.text });
11041
11126
  });
11042
11127
  cascade.on("tier:status", (e) => {
11043
11128
  this.socket.broadcast("tier:status", { sessionId, ...e });
@@ -11473,6 +11558,9 @@ async function startRepl(options) {
11473
11558
  await cm.load();
11474
11559
  config = cm.getConfig();
11475
11560
  }
11561
+ if (process.stdout.isTTY) {
11562
+ process.stdout.write("\x1B[2J\x1B[H");
11563
+ }
11476
11564
  const { waitUntilExit } = ink.render(
11477
11565
  React5__default.default.createElement(Repl, {
11478
11566
  config,