cascade-ai 0.2.11 → 0.3.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.11";
133
+ CASCADE_VERSION = "0.3.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";
@@ -569,17 +569,38 @@ var init_anthropic = __esm({
569
569
  messages,
570
570
  tools: tools?.length ? tools : void 0
571
571
  });
572
+ let isThinking = false;
572
573
  for await (const event of stream) {
573
- if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
574
- const text = event.delta.text;
575
- fullContent += text;
576
- onChunk({ text, finishReason: null });
574
+ if (event.type === "content_block_delta") {
575
+ if (event.delta.type === "thinking_delta") {
576
+ if (!isThinking) {
577
+ isThinking = true;
578
+ fullContent += "<think>\n";
579
+ onChunk({ text: "<think>\n", finishReason: null });
580
+ }
581
+ const text = event.delta.thinking;
582
+ fullContent += text;
583
+ onChunk({ text, finishReason: null });
584
+ } else if (event.delta.type === "text_delta") {
585
+ if (isThinking) {
586
+ isThinking = false;
587
+ fullContent += "\n</think>\n\n";
588
+ onChunk({ text: "\n</think>\n\n", finishReason: null });
589
+ }
590
+ const text = event.delta.text;
591
+ fullContent += text;
592
+ onChunk({ text, finishReason: null });
593
+ }
577
594
  } else if (event.type === "message_delta" && event.usage) {
578
595
  outputTokens = event.usage.output_tokens;
579
596
  } else if (event.type === "message_start" && event.message.usage) {
580
597
  inputTokens = event.message.usage.input_tokens;
581
598
  }
582
599
  }
600
+ if (isThinking) {
601
+ fullContent += "\n</think>\n\n";
602
+ onChunk({ text: "\n</think>\n\n", finishReason: null });
603
+ }
583
604
  const finalMessage = await stream.finalMessage();
584
605
  const toolCalls = finalMessage.content.filter((b) => b.type === "tool_use").map((b) => ({
585
606
  id: b.id,
@@ -763,9 +784,25 @@ var init_openai = __esm({
763
784
  }
764
785
  }
765
786
  const toolCallsMap = {};
787
+ let isThinking = false;
766
788
  for await (const chunk of stream) {
767
789
  const delta = chunk.choices[0]?.delta;
790
+ const reasoningContent = delta?.reasoning_content;
791
+ if (reasoningContent) {
792
+ if (!isThinking) {
793
+ isThinking = true;
794
+ fullContent += "<think>\n";
795
+ onChunk({ text: "<think>\n", finishReason: null });
796
+ }
797
+ fullContent += reasoningContent;
798
+ onChunk({ text: reasoningContent, finishReason: null });
799
+ }
768
800
  if (delta?.content) {
801
+ if (isThinking) {
802
+ isThinking = false;
803
+ fullContent += "\n</think>\n\n";
804
+ onChunk({ text: "\n</think>\n\n", finishReason: null });
805
+ }
769
806
  fullContent += delta.content;
770
807
  onChunk({ text: delta.content, finishReason: null });
771
808
  }
@@ -788,6 +825,10 @@ var init_openai = __esm({
788
825
  outputTokens = chunk.usage.completion_tokens;
789
826
  }
790
827
  }
828
+ if (isThinking) {
829
+ fullContent += "\n</think>\n\n";
830
+ onChunk({ text: "\n</think>\n\n", finishReason: null });
831
+ }
791
832
  const toolCalls = Object.values(toolCallsMap).map((tc) => {
792
833
  let input = {};
793
834
  try {
@@ -1881,8 +1922,8 @@ Original error: ${err.message}`
1881
1922
  upsertRuntimeNode(node) {
1882
1923
  this.enqueueWrite(() => {
1883
1924
  this.db.prepare(`
1884
- INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global)
1885
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1925
+ INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global, output)
1926
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1886
1927
  ON CONFLICT(tier_id) DO UPDATE SET
1887
1928
  session_id = excluded.session_id,
1888
1929
  parent_id = excluded.parent_id,
@@ -1893,7 +1934,8 @@ Original error: ${err.message}`
1893
1934
  progress_pct = excluded.progress_pct,
1894
1935
  updated_at = excluded.updated_at,
1895
1936
  workspace_path = excluded.workspace_path,
1896
- is_global = excluded.is_global
1937
+ is_global = excluded.is_global,
1938
+ output = excluded.output
1897
1939
  `).run(
1898
1940
  node.tierId,
1899
1941
  node.sessionId,
@@ -1905,7 +1947,8 @@ Original error: ${err.message}`
1905
1947
  node.progressPct ?? null,
1906
1948
  node.updatedAt,
1907
1949
  node.workspacePath ?? null,
1908
- node.isGlobal ? 1 : 0
1950
+ node.isGlobal ? 1 : 0,
1951
+ node.output ?? null
1909
1952
  );
1910
1953
  });
1911
1954
  }
@@ -1926,14 +1969,15 @@ Original error: ${err.message}`
1926
1969
  progressPct: row.progress_pct ?? void 0,
1927
1970
  updatedAt: row.updated_at,
1928
1971
  workspacePath: row.workspace_path ?? void 0,
1929
- isGlobal: row.is_global === 1
1972
+ isGlobal: row.is_global === 1,
1973
+ output: row.output ?? void 0
1930
1974
  }));
1931
1975
  }
1932
1976
  addRuntimeNodeLog(log) {
1933
1977
  this.enqueueWrite(() => {
1934
1978
  this.db.prepare(`
1935
- INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global)
1936
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1979
+ INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global, output)
1980
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1937
1981
  `).run(
1938
1982
  log.id,
1939
1983
  log.sessionId,
@@ -1945,7 +1989,8 @@ Original error: ${err.message}`
1945
1989
  log.progressPct ?? null,
1946
1990
  log.timestamp,
1947
1991
  log.workspacePath ?? null,
1948
- log.isGlobal ? 1 : 0
1992
+ log.isGlobal ? 1 : 0,
1993
+ log.output ?? null
1949
1994
  );
1950
1995
  this.db.prepare(`
1951
1996
  DELETE FROM runtime_node_logs
@@ -1988,7 +2033,8 @@ Original error: ${err.message}`
1988
2033
  progressPct: row.progress_pct ?? void 0,
1989
2034
  timestamp: row.timestamp,
1990
2035
  workspacePath: row.workspace_path ?? void 0,
1991
- isGlobal: row.is_global === 1
2036
+ isGlobal: row.is_global === 1,
2037
+ output: row.output ?? void 0
1992
2038
  }));
1993
2039
  }
1994
2040
  // ── Messages ──────────────────────────────────
@@ -2315,7 +2361,8 @@ Original error: ${err.message}`
2315
2361
  progress_pct INTEGER,
2316
2362
  updated_at TEXT NOT NULL,
2317
2363
  workspace_path TEXT,
2318
- is_global INTEGER NOT NULL DEFAULT 0
2364
+ is_global INTEGER NOT NULL DEFAULT 0,
2365
+ output TEXT
2319
2366
  );
2320
2367
 
2321
2368
  CREATE INDEX IF NOT EXISTS idx_runtime_nodes_session ON runtime_nodes(session_id);
@@ -2332,7 +2379,8 @@ Original error: ${err.message}`
2332
2379
  progress_pct INTEGER,
2333
2380
  timestamp TEXT NOT NULL,
2334
2381
  workspace_path TEXT,
2335
- is_global INTEGER NOT NULL DEFAULT 0
2382
+ is_global INTEGER NOT NULL DEFAULT 0,
2383
+ output TEXT
2336
2384
  );
2337
2385
 
2338
2386
  CREATE TABLE IF NOT EXISTS model_cache (
@@ -2361,6 +2409,14 @@ Original error: ${err.message}`
2361
2409
 
2362
2410
  CREATE INDEX IF NOT EXISTS idx_file_snapshots_session ON file_snapshots(session_id);
2363
2411
  `);
2412
+ try {
2413
+ this.db.exec("ALTER TABLE runtime_nodes ADD COLUMN output TEXT");
2414
+ } catch {
2415
+ }
2416
+ try {
2417
+ this.db.exec("ALTER TABLE runtime_node_logs ADD COLUMN output TEXT");
2418
+ } catch {
2419
+ }
2364
2420
  }
2365
2421
  // ── Deserializers ─────────────────────────────
2366
2422
  deserializeSession(row, messages) {
@@ -2623,6 +2679,7 @@ var ConfigManager = class {
2623
2679
  }
2624
2680
  }
2625
2681
  async injectEnvKeys() {
2682
+ const isFirstRun = this.config.providers.length === 0;
2626
2683
  const envProviders = [
2627
2684
  { env: "ANTHROPIC_API_KEY", type: "anthropic" },
2628
2685
  { env: "OPENAI_API_KEY", type: "openai" },
@@ -2633,10 +2690,13 @@ var ConfigManager = class {
2633
2690
  const key = process.env[env];
2634
2691
  if (!key) continue;
2635
2692
  const existing = this.config.providers.find((p) => p.type === type);
2636
- if (!existing) this.config.providers.push({ type, apiKey: key });
2637
- else if (!existing.apiKey) existing.apiKey = key;
2693
+ if (!existing && isFirstRun) {
2694
+ this.config.providers.push({ type, apiKey: key });
2695
+ } else if (existing && !existing.apiKey) {
2696
+ existing.apiKey = key;
2697
+ }
2638
2698
  }
2639
- if (!this.config.providers.find((p) => p.type === "ollama")) {
2699
+ if (isFirstRun && !this.config.providers.find((p) => p.type === "ollama")) {
2640
2700
  this.config.providers.push({ type: "ollama" });
2641
2701
  }
2642
2702
  }
@@ -3585,7 +3645,7 @@ var BaseTier = class extends EventEmitter__default.default {
3585
3645
  getStatus() {
3586
3646
  return this.status;
3587
3647
  }
3588
- setStatus(status) {
3648
+ setStatus(status, output) {
3589
3649
  this.status = status;
3590
3650
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3591
3651
  const event = {
@@ -3594,7 +3654,8 @@ var BaseTier = class extends EventEmitter__default.default {
3594
3654
  parentId: this.parentId,
3595
3655
  label: this.label,
3596
3656
  status,
3597
- timestamp
3657
+ timestamp,
3658
+ output
3598
3659
  };
3599
3660
  this.emit("status", event);
3600
3661
  this.emit("tier:status", event);
@@ -3620,7 +3681,8 @@ var BaseTier = class extends EventEmitter__default.default {
3620
3681
  status: this.status,
3621
3682
  currentAction: update.currentAction,
3622
3683
  progressPct: update.progressPct,
3623
- timestamp
3684
+ timestamp,
3685
+ output: update.output
3624
3686
  });
3625
3687
  }
3626
3688
  buildMessage(type, to, payload) {
@@ -4001,16 +4063,17 @@ Now execute your subtask using this context where relevant.`
4001
4063
  return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
4002
4064
  }
4003
4065
  }
4004
- this.setStatus("COMPLETED");
4005
- this.sendStatusUpdate({ progressPct: 100, currentAction: "Subtask complete", status: "IN_PROGRESS" });
4066
+ this.setStatus("COMPLETED", output);
4067
+ this.sendStatusUpdate({ progressPct: 100, currentAction: "Subtask complete", status: "IN_PROGRESS", output });
4006
4068
  this.peerBus?.publish(this.id, assignment.subtaskId, output, "COMPLETED");
4007
4069
  return this.buildResult("COMPLETED", output, { checksRun, passed, failed }, issues, correctionAttempts);
4008
4070
  } catch (err) {
4009
4071
  const errMsg = err instanceof Error ? err.message : String(err);
4010
4072
  issues.push(`Execution error: ${errMsg}`);
4011
- this.setStatus("FAILED");
4012
- this.peerBus?.publish(this.id, assignment.subtaskId, errMsg, "FAILED");
4013
- return this.buildResult("ESCALATED", output || errMsg, { checksRun, passed, failed }, issues, correctionAttempts);
4073
+ const finalOutput = output || errMsg;
4074
+ this.setStatus("FAILED", finalOutput);
4075
+ this.peerBus?.publish(this.id, assignment.subtaskId, finalOutput, "FAILED");
4076
+ return this.buildResult("ESCALATED", finalOutput, { checksRun, passed, failed }, issues, correctionAttempts);
4014
4077
  }
4015
4078
  }
4016
4079
  sendToPeer(toId, content) {
@@ -4693,8 +4756,9 @@ var T2Manager = class extends BaseTier {
4693
4756
  const summary = await this.aggregateResults(assignment, t3Results);
4694
4757
  const issues = t3Results.filter((r) => r.status !== "COMPLETED").flatMap((r) => r.issues);
4695
4758
  const overallStatus = this.determineStatus(t3Results);
4696
- this.setStatus(overallStatus === "COMPLETED" ? "COMPLETED" : "FAILED");
4697
- this.sendStatusUpdate({ progressPct: 100, currentAction: "Section complete", status: "IN_PROGRESS" });
4759
+ const isOk = overallStatus === "COMPLETED" || overallStatus === "PARTIAL";
4760
+ this.setStatus(isOk ? "COMPLETED" : "FAILED", summary);
4761
+ this.sendStatusUpdate({ progressPct: 100, currentAction: "Section complete", status: "IN_PROGRESS", output: summary });
4698
4762
  const result = {
4699
4763
  sectionId: assignment.sectionId,
4700
4764
  sectionTitle: assignment.sectionTitle,
@@ -4707,7 +4771,7 @@ var T2Manager = class extends BaseTier {
4707
4771
  return result;
4708
4772
  } catch (err) {
4709
4773
  const errMsg = err instanceof Error ? err.message : String(err);
4710
- this.setStatus("FAILED");
4774
+ this.setStatus("FAILED", errMsg);
4711
4775
  const failedResult = {
4712
4776
  sectionId: assignment.sectionId,
4713
4777
  sectionTitle: assignment.sectionTitle,
@@ -5272,8 +5336,8 @@ Create a CORRECTION PLAN that contains only the new sections needed to fix the i
5272
5336
  status: "IN_PROGRESS"
5273
5337
  });
5274
5338
  const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
5275
- this.setStatus("COMPLETED");
5276
- this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
5339
+ this.setStatus("COMPLETED", output);
5340
+ this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS", output });
5277
5341
  return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
5278
5342
  }
5279
5343
  getEscalations() {
@@ -8102,11 +8166,6 @@ var SlashCommandRegistry = class {
8102
8166
  description: "Show active models per tier",
8103
8167
  handler: (_args, ctx) => ({ output: ctx.onModelInfo(), handled: true })
8104
8168
  });
8105
- this.register({
8106
- command: "/models",
8107
- description: "Browse available models by provider",
8108
- handler: (_args, ctx) => ({ output: ctx.onModelsInfo(), handled: true })
8109
- });
8110
8169
  this.register({
8111
8170
  command: "/providers",
8112
8171
  description: "Show configured providers",
@@ -8235,35 +8294,69 @@ function AgentTree({ root, theme }) {
8235
8294
  }
8236
8295
  function T2Row({ node, theme, isLast }) {
8237
8296
  const connector = isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
8238
- const t3Active = countByRoleAndStatus(node, "T3", "ACTIVE");
8239
- const t3Total = countByRole(node, "T3");
8240
- const workerSuffix = t3Total > 0 ? ` T3\xD7${t3Total}` : "";
8297
+ const t3Nodes = (node.children ?? []).filter((c) => c.role === "T3");
8298
+ const t3ActiveCount = t3Nodes.filter((c) => c.status === "ACTIVE").length;
8241
8299
  const label = stripRolePrefix(node.label, node.role);
8242
8300
  const action = node.currentAction ? ` ${node.currentAction.slice(0, 38)}` : "";
8301
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
8302
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { children: [
8303
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.muted, children: [
8304
+ " ",
8305
+ connector
8306
+ ] }),
8307
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.t2Color, bold: true, children: "[T2]" }),
8308
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.foreground, children: [
8309
+ " ",
8310
+ label
8311
+ ] }),
8312
+ node.status === "ACTIVE" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8313
+ action ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, children: action }) : null,
8314
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }),
8315
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner__default.default, { type: "dots" }),
8316
+ t3ActiveCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.muted, children: [
8317
+ " (",
8318
+ t3ActiveCount,
8319
+ " running)"
8320
+ ] }) : null
8321
+ ] }),
8322
+ node.status === "COMPLETED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.success, children: " \u2714" }),
8323
+ node.status === "FAILED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.error, children: " \u2718" }),
8324
+ node.status === "ESCALATED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.warning, children: " \u25B2" })
8325
+ ] }),
8326
+ t3Nodes.map((t3, idx) => /* @__PURE__ */ jsxRuntime.jsx(
8327
+ T3Row,
8328
+ {
8329
+ node: t3,
8330
+ theme,
8331
+ isLast: idx === t3Nodes.length - 1,
8332
+ parentIsLast: isLast
8333
+ },
8334
+ t3.id
8335
+ ))
8336
+ ] });
8337
+ }
8338
+ function T3Row({ node, theme, isLast, parentIsLast }) {
8339
+ const indent = parentIsLast ? " " : " \u2502 ";
8340
+ const connector = isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
8341
+ const label = stripRolePrefix(node.label, node.role);
8342
+ const action = node.currentAction ? ` ${node.currentAction.slice(0, 42)}` : "";
8243
8343
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { children: [
8244
8344
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.muted, children: [
8245
- " ",
8345
+ indent,
8246
8346
  connector
8247
8347
  ] }),
8248
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.t2Color, bold: true, children: "[T2]" }),
8249
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.foreground, children: [
8348
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.t3Color, children: "[T3]" }),
8349
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.muted, children: [
8250
8350
  " ",
8251
8351
  label
8252
8352
  ] }),
8253
- workerSuffix ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, children: workerSuffix }) : null,
8254
8353
  node.status === "ACTIVE" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8255
8354
  action ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, children: action }) : null,
8256
8355
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }),
8257
- /* @__PURE__ */ jsxRuntime.jsx(Spinner__default.default, { type: "dots" }),
8258
- t3Active > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: theme.colors.muted, children: [
8259
- " (",
8260
- t3Active,
8261
- " running)"
8262
- ] }) : null
8356
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner__default.default, { type: "dots" })
8263
8357
  ] }),
8264
8358
  node.status === "COMPLETED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.success, children: " \u2714" }),
8265
- node.status === "FAILED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.error, children: " \u2718" }),
8266
- node.status === "ESCALATED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.warning, children: " \u25B2" })
8359
+ node.status === "FAILED" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.error, children: " \u2718" })
8267
8360
  ] });
8268
8361
  }
8269
8362
  function hasActiveOrFailed(node) {
@@ -8875,7 +8968,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName })
8875
8968
  treeNodesRef.current.set(event.tierId, node);
8876
8969
  const store = storeRef.current;
8877
8970
  if (store) {
8878
- 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 };
8971
+ 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 };
8879
8972
  store.upsertRuntimeNode(runtimeNode);
8880
8973
  globalStoreRef.current?.upsertRuntimeNode({ ...runtimeNode, isGlobal: true });
8881
8974
  }
@@ -8886,7 +8979,6 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName })
8886
8979
  React5.useEffect(() => {
8887
8980
  const originalWarn = console.warn;
8888
8981
  const originalLog = console.log;
8889
- process.stdout.write("\x1Bc");
8890
8982
  console.warn = (...args) => {
8891
8983
  const msg = args.join(" ");
8892
8984
  if (msg.includes("non-text parts") || msg.includes("functionCall")) return;
@@ -9403,8 +9495,8 @@ Use /identity <name|id> to switch.`;
9403
9495
  const statusHeight = agentTreeHeight + timelineHeight;
9404
9496
  const costHeight = state.showCost ? 6 : 0;
9405
9497
  const approvalHeight = state.approvalRequest ? 12 : 0;
9406
- const slashVisibleCount = Math.min(SLASH_PAGE_SIZE, slashEntries.length);
9407
- const slashHeight = slashVisibleCount > 0 ? slashVisibleCount + 2 : 0;
9498
+ Math.min(SLASH_PAGE_SIZE, slashEntries.length);
9499
+ const slashHeight = isTypingCommand ? SLASH_PAGE_SIZE + 2 : 0;
9408
9500
  const chromeHeight = statusHeight + costHeight + approvalHeight + slashHeight + 7;
9409
9501
  const availableHeight = Math.max(4, height - chromeHeight);
9410
9502
  const allLines = formatToLines(
@@ -9816,8 +9908,21 @@ function wizardReducer(state, action) {
9816
9908
  addingAnotherCompat: false
9817
9909
  };
9818
9910
  }
9911
+ case "ADD_OLLAMA": {
9912
+ const newEntry = {
9913
+ id: crypto.randomUUID(),
9914
+ type: "ollama",
9915
+ label: `Ollama endpoint ${state.entries.filter((e) => e.type === "ollama").length + 1}`
9916
+ };
9917
+ return {
9918
+ ...state,
9919
+ entries: [...state.entries, newEntry],
9920
+ currentEntryIdx: state.entries.length,
9921
+ addingAnotherOllama: false
9922
+ };
9923
+ }
9819
9924
  case "SKIP_MORE":
9820
- return { ...state, addingAnotherAzure: false, addingAnotherCompat: false, step: "FETCH_MODELS", currentEntryIdx: 0 };
9925
+ return { ...state, addingAnotherAzure: false, addingAnotherCompat: false, addingAnotherOllama: false, step: "FETCH_MODELS", currentEntryIdx: 0 };
9821
9926
  case "GO_FETCH":
9822
9927
  return { ...state, step: "FETCH_MODELS", fetchLog: [], fetchedModels: [] };
9823
9928
  case "SET_FETCH_LOG":
@@ -9850,6 +9955,7 @@ function SetupWizard({ workspacePath, onComplete }) {
9850
9955
  currentEntryIdx: 0,
9851
9956
  addingAnotherAzure: false,
9852
9957
  addingAnotherCompat: false,
9958
+ addingAnotherOllama: false,
9853
9959
  fetchedModels: [],
9854
9960
  fetchLog: [],
9855
9961
  tierT1: "auto",
@@ -9949,13 +10055,16 @@ function SetupWizard({ workspacePath, onComplete }) {
9949
10055
  }, [state.step, state.entries, state.tierT1, state.tierT2, state.tierT3, workspacePath, onComplete, exit]);
9950
10056
  ink.useInput((_input, key) => {
9951
10057
  if (state.step === "PROVIDER_SELECT") {
9952
- if (key.upArrow) setProviderCursor((p) => Math.max(0, p - 1));
9953
- if (key.downArrow) setProviderCursor((p) => Math.min(providerOrder.length - 1, p + 1));
10058
+ if (key.upArrow) setProviderCursor((p) => p <= 0 ? providerOrder.length - 1 : p - 1);
10059
+ if (key.downArrow) setProviderCursor((p) => p >= providerOrder.length - 1 ? 0 : p + 1);
9954
10060
  if (_input === " ") dispatch({ type: "TOGGLE_PROVIDER", provider: providerOrder[providerCursor] });
9955
10061
  if (_input === "a") dispatch({ type: "TOGGLE_ALL" });
9956
10062
  if (_input === "i") dispatch({ type: "INVERT_SELECTION" });
9957
10063
  if (key.return) {
9958
- if (state.selectedTypes.size === 0) return;
10064
+ if (state.selectedTypes.size === 0) {
10065
+ dispatch({ type: "SET_ERROR", error: "Please select at least one provider using <space> before pressing <enter>." });
10066
+ return;
10067
+ }
9959
10068
  dispatch({ type: "CONFIRM_PROVIDERS" });
9960
10069
  const firstType = [...state.selectedTypes][0];
9961
10070
  setFieldStage(firstType === "azure" ? "deploymentName" : firstType === "openai-compatible" ? "label" : firstType === "ollama" ? "baseUrl" : "apiKey");
@@ -10012,11 +10121,7 @@ function SetupWizard({ workspacePath, onComplete }) {
10012
10121
  } else if (currentEntry.type === "ollama") {
10013
10122
  dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val || "http://localhost:11434" });
10014
10123
  setFieldBuffer("");
10015
- const nextEntry = state.entries[state.currentEntryIdx + 1];
10016
- if (nextEntry) {
10017
- setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
10018
- }
10019
- dispatch({ type: "NEXT_ENTRY" });
10124
+ setFieldStage("askMore");
10020
10125
  } else {
10021
10126
  dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
10022
10127
  setFieldBuffer("");
@@ -10057,7 +10162,7 @@ function SetupWizard({ workspacePath, onComplete }) {
10057
10162
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
10058
10163
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 1, children: [
10059
10164
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "magenta", bold: true, children: "? " }),
10060
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: isAzure ? "Add another Azure deployment? (y/n)" : "Add another custom endpoint? (y/n)" })
10165
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: isAzure ? "Add another Azure deployment? (y/n)" : isOllama ? "Add another Ollama endpoint? (y/n)" : "Add another custom endpoint? (y/n)" })
10061
10166
  ] }),
10062
10167
  /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(
10063
10168
  SelectInput__default.default,
@@ -10071,8 +10176,9 @@ function SetupWizard({ workspacePath, onComplete }) {
10071
10176
  onSelect: (item) => {
10072
10177
  if (item.value === "yes") {
10073
10178
  if (isAzure) dispatch({ type: "ADD_AZURE" });
10179
+ else if (isOllama) dispatch({ type: "ADD_OLLAMA" });
10074
10180
  else dispatch({ type: "ADD_COMPAT" });
10075
- setFieldStage(isAzure ? "deploymentName" : "label");
10181
+ setFieldStage(isAzure ? "deploymentName" : isOllama ? "baseUrl" : "label");
10076
10182
  setFieldBuffer("");
10077
10183
  } else {
10078
10184
  const nextEntry = state.entries[state.currentEntryIdx + 1];
@@ -10088,7 +10194,7 @@ function SetupWizard({ workspacePath, onComplete }) {
10088
10194
  ] });
10089
10195
  }
10090
10196
  const prompt = isAzure && fieldStage === "deploymentName" ? `Azure deployment name (${currentEntry.label})` : isAzure && fieldStage === "baseUrl" ? `Azure endpoint URL` : isCompat && fieldStage === "label" ? `Name for this endpoint (e.g. Groq)` : isCompat && fieldStage === "baseUrl" ? `Base URL (e.g. https://api.groq.com/openai/v1)` : isOllama ? `Ollama URL (Enter for http://localhost:11434)` : `${currentEntry.label} API Key`;
10091
- const isMasked = fieldStage === "apiKey";
10197
+ const isMasked = fieldStage === "apiKey" && !isOllama;
10092
10198
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
10093
10199
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 1, children: [
10094
10200
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "magenta", bold: true, children: "? " }),
@@ -10982,8 +11088,8 @@ var DashboardServer = class {
10982
11088
  void (async () => {
10983
11089
  const cascade = new Cascade(this.config, this.workspacePath, this.store);
10984
11090
  cascade.on("stream:token", (e) => {
10985
- this.socket.broadcast("stream:token", { sessionId, token: e.text });
10986
- this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, token: e.text });
11091
+ this.socket.broadcast("stream:token", { sessionId, tierId: e.tierId, text: e.text });
11092
+ this.socket.broadcastToRoom(`session:${sessionId}`, "stream:token", { sessionId, tierId: e.tierId, text: e.text });
10987
11093
  });
10988
11094
  cascade.on("tier:status", (e) => {
10989
11095
  this.socket.broadcast("tier:status", { sessionId, ...e });
@@ -11419,6 +11525,9 @@ async function startRepl(options) {
11419
11525
  await cm.load();
11420
11526
  config = cm.getConfig();
11421
11527
  }
11528
+ if (process.stdout.isTTY) {
11529
+ process.stdout.write("\x1B[2J\x1B[H");
11530
+ }
11422
11531
  const { waitUntilExit } = ink.render(
11423
11532
  React5__default.default.createElement(Repl, {
11424
11533
  config,