cascade-ai 0.2.2 → 0.2.12
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/README.md +33 -0
- package/dist/cli.cjs +1222 -375
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1222 -375
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1100 -332
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -5
- package/dist/index.d.ts +72 -5
- package/dist/index.js +1099 -333
- package/dist/index.js.map +1 -1
- package/package.json +9 -6
package/dist/cli.js
CHANGED
|
@@ -9,16 +9,17 @@ import chalk8 from 'chalk';
|
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
10
|
import fs7 from 'fs/promises';
|
|
11
11
|
import path17 from 'path';
|
|
12
|
-
import
|
|
12
|
+
import os3 from 'os';
|
|
13
13
|
import crypto, { randomUUID, timingSafeEqual } from 'crypto';
|
|
14
14
|
import fs14 from 'fs';
|
|
15
15
|
import * as _ignoreModule from 'ignore';
|
|
16
16
|
import _ignoreModule__default from 'ignore';
|
|
17
17
|
import Database from 'better-sqlite3';
|
|
18
18
|
import { z } from 'zod';
|
|
19
|
-
import { exec, spawnSync } from 'child_process';
|
|
19
|
+
import { exec, execSync, spawnSync } from 'child_process';
|
|
20
20
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
21
21
|
import EventEmitter from 'events';
|
|
22
|
+
import { glob } from 'glob';
|
|
22
23
|
import { promisify } from 'util';
|
|
23
24
|
import { simpleGit } from 'simple-git';
|
|
24
25
|
import PDFDocument from 'pdfkit';
|
|
@@ -82,7 +83,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
82
83
|
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;
|
|
83
84
|
var init_constants = __esm({
|
|
84
85
|
"src/constants.ts"() {
|
|
85
|
-
CASCADE_VERSION = "0.2.
|
|
86
|
+
CASCADE_VERSION = "0.2.12";
|
|
86
87
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
87
88
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
88
89
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -377,7 +378,8 @@ var init_constants = __esm({
|
|
|
377
378
|
IMAGE_ANALYZE: "image_analyze",
|
|
378
379
|
PDF_CREATE: "pdf_create",
|
|
379
380
|
RUN_CODE: "run_code",
|
|
380
|
-
PEER_MESSAGE: "peer_message"
|
|
381
|
+
PEER_MESSAGE: "peer_message",
|
|
382
|
+
WEB_SEARCH: "web_search"
|
|
381
383
|
};
|
|
382
384
|
DEFAULT_APPROVAL_REQUIRED = [
|
|
383
385
|
TOOL_NAMES.SHELL,
|
|
@@ -520,17 +522,38 @@ var init_anthropic = __esm({
|
|
|
520
522
|
messages,
|
|
521
523
|
tools: tools?.length ? tools : void 0
|
|
522
524
|
});
|
|
525
|
+
let isThinking = false;
|
|
523
526
|
for await (const event of stream) {
|
|
524
|
-
if (event.type === "content_block_delta"
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
527
|
+
if (event.type === "content_block_delta") {
|
|
528
|
+
if (event.delta.type === "thinking_delta") {
|
|
529
|
+
if (!isThinking) {
|
|
530
|
+
isThinking = true;
|
|
531
|
+
fullContent += "<think>\n";
|
|
532
|
+
onChunk({ text: "<think>\n", finishReason: null });
|
|
533
|
+
}
|
|
534
|
+
const text = event.delta.thinking;
|
|
535
|
+
fullContent += text;
|
|
536
|
+
onChunk({ text, finishReason: null });
|
|
537
|
+
} else if (event.delta.type === "text_delta") {
|
|
538
|
+
if (isThinking) {
|
|
539
|
+
isThinking = false;
|
|
540
|
+
fullContent += "\n</think>\n\n";
|
|
541
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
542
|
+
}
|
|
543
|
+
const text = event.delta.text;
|
|
544
|
+
fullContent += text;
|
|
545
|
+
onChunk({ text, finishReason: null });
|
|
546
|
+
}
|
|
528
547
|
} else if (event.type === "message_delta" && event.usage) {
|
|
529
548
|
outputTokens = event.usage.output_tokens;
|
|
530
549
|
} else if (event.type === "message_start" && event.message.usage) {
|
|
531
550
|
inputTokens = event.message.usage.input_tokens;
|
|
532
551
|
}
|
|
533
552
|
}
|
|
553
|
+
if (isThinking) {
|
|
554
|
+
fullContent += "\n</think>\n\n";
|
|
555
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
556
|
+
}
|
|
534
557
|
const finalMessage = await stream.finalMessage();
|
|
535
558
|
const toolCalls = finalMessage.content.filter((b) => b.type === "tool_use").map((b) => ({
|
|
536
559
|
id: b.id,
|
|
@@ -591,33 +614,61 @@ var init_anthropic = __esm({
|
|
|
591
614
|
}
|
|
592
615
|
}
|
|
593
616
|
convertMessages(messages) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
617
|
+
const result = [];
|
|
618
|
+
for (const m of messages) {
|
|
619
|
+
if (m.role === "system") continue;
|
|
620
|
+
if (m.role === "tool") {
|
|
621
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
622
|
+
result.push({
|
|
623
|
+
role: "user",
|
|
624
|
+
content: [{
|
|
625
|
+
type: "tool_result",
|
|
626
|
+
tool_use_id: m.toolCallId ?? "",
|
|
627
|
+
content: toolContent
|
|
628
|
+
}]
|
|
629
|
+
});
|
|
630
|
+
continue;
|
|
597
631
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
632
|
+
if (m.role === "assistant") {
|
|
633
|
+
const content = [];
|
|
634
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
635
|
+
if (text) content.push({ type: "text", text });
|
|
636
|
+
for (const tc of m.toolCalls ?? []) {
|
|
637
|
+
content.push({
|
|
638
|
+
type: "tool_use",
|
|
639
|
+
id: tc.id,
|
|
640
|
+
name: tc.name,
|
|
641
|
+
input: tc.input
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
if (content.length > 0) {
|
|
645
|
+
result.push({ role: "assistant", content });
|
|
646
|
+
}
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (m.role === "user") {
|
|
650
|
+
if (typeof m.content === "string") {
|
|
651
|
+
result.push({ role: "user", content: m.content });
|
|
652
|
+
} else {
|
|
653
|
+
const content = m.content.map((block) => {
|
|
654
|
+
if (block.type === "text") return { type: "text", text: block.text };
|
|
655
|
+
if (block.type === "image") {
|
|
656
|
+
const img = block.image;
|
|
657
|
+
if (img.type === "base64") {
|
|
658
|
+
return {
|
|
659
|
+
type: "image",
|
|
660
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
661
|
+
};
|
|
609
662
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
};
|
|
663
|
+
return { type: "image", source: { type: "url", url: img.data } };
|
|
664
|
+
}
|
|
665
|
+
return { type: "text", text: "" };
|
|
666
|
+
});
|
|
667
|
+
result.push({ role: "user", content });
|
|
616
668
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return result;
|
|
621
672
|
}
|
|
622
673
|
};
|
|
623
674
|
}
|
|
@@ -686,9 +737,25 @@ var init_openai = __esm({
|
|
|
686
737
|
}
|
|
687
738
|
}
|
|
688
739
|
const toolCallsMap = {};
|
|
740
|
+
let isThinking = false;
|
|
689
741
|
for await (const chunk of stream) {
|
|
690
742
|
const delta = chunk.choices[0]?.delta;
|
|
743
|
+
const reasoningContent = delta?.reasoning_content;
|
|
744
|
+
if (reasoningContent) {
|
|
745
|
+
if (!isThinking) {
|
|
746
|
+
isThinking = true;
|
|
747
|
+
fullContent += "<think>\n";
|
|
748
|
+
onChunk({ text: "<think>\n", finishReason: null });
|
|
749
|
+
}
|
|
750
|
+
fullContent += reasoningContent;
|
|
751
|
+
onChunk({ text: reasoningContent, finishReason: null });
|
|
752
|
+
}
|
|
691
753
|
if (delta?.content) {
|
|
754
|
+
if (isThinking) {
|
|
755
|
+
isThinking = false;
|
|
756
|
+
fullContent += "\n</think>\n\n";
|
|
757
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
758
|
+
}
|
|
692
759
|
fullContent += delta.content;
|
|
693
760
|
onChunk({ text: delta.content, finishReason: null });
|
|
694
761
|
}
|
|
@@ -711,6 +778,10 @@ var init_openai = __esm({
|
|
|
711
778
|
outputTokens = chunk.usage.completion_tokens;
|
|
712
779
|
}
|
|
713
780
|
}
|
|
781
|
+
if (isThinking) {
|
|
782
|
+
fullContent += "\n</think>\n\n";
|
|
783
|
+
onChunk({ text: "\n</think>\n\n", finishReason: null });
|
|
784
|
+
}
|
|
714
785
|
const toolCalls = Object.values(toolCallsMap).map((tc) => {
|
|
715
786
|
let input = {};
|
|
716
787
|
try {
|
|
@@ -915,7 +986,7 @@ var init_gemini = __esm({
|
|
|
915
986
|
for (const part of candidate?.content?.parts ?? []) {
|
|
916
987
|
if (part.functionCall) {
|
|
917
988
|
toolCalls.push({
|
|
918
|
-
id:
|
|
989
|
+
id: part.functionCall.name,
|
|
919
990
|
name: part.functionCall.name,
|
|
920
991
|
input: part.functionCall.args ?? {}
|
|
921
992
|
});
|
|
@@ -1003,10 +1074,70 @@ var init_gemini = __esm({
|
|
|
1003
1074
|
}
|
|
1004
1075
|
// ── Private ──────────────────────────────────
|
|
1005
1076
|
buildContents(messages, extraImages) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1077
|
+
const contents = [];
|
|
1078
|
+
for (const m of messages) {
|
|
1079
|
+
if (m.role === "system") {
|
|
1080
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
1081
|
+
if (!text.trim()) continue;
|
|
1082
|
+
const prev = contents[contents.length - 1];
|
|
1083
|
+
if (prev?.role === "user") {
|
|
1084
|
+
prev.parts.unshift({ text: `[System context]: ${text}
|
|
1085
|
+
|
|
1086
|
+
` });
|
|
1087
|
+
} else {
|
|
1088
|
+
contents.push({ role: "user", parts: [{ text: `[System context]: ${text}` }] });
|
|
1089
|
+
}
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
if (m.role === "tool") {
|
|
1093
|
+
const toolContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
1094
|
+
const functionName = m.toolCallId ?? "unknown_function";
|
|
1095
|
+
contents.push({
|
|
1096
|
+
role: "user",
|
|
1097
|
+
parts: [{
|
|
1098
|
+
functionResponse: {
|
|
1099
|
+
name: functionName,
|
|
1100
|
+
response: { output: toolContent }
|
|
1101
|
+
}
|
|
1102
|
+
}]
|
|
1103
|
+
});
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
if (m.role === "assistant") {
|
|
1107
|
+
const parts = [];
|
|
1108
|
+
const textContent = typeof m.content === "string" ? m.content : "";
|
|
1109
|
+
if (textContent) parts.push({ text: textContent });
|
|
1110
|
+
for (const tc of m.toolCalls ?? []) {
|
|
1111
|
+
parts.push({
|
|
1112
|
+
functionCall: {
|
|
1113
|
+
name: tc.name,
|
|
1114
|
+
args: tc.input
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
if (parts.length > 0) {
|
|
1119
|
+
contents.push({ role: "model", parts });
|
|
1120
|
+
}
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
if (m.role === "user") {
|
|
1124
|
+
const parts = this.convertMessageContent(m, contents.length === 0 ? extraImages : void 0);
|
|
1125
|
+
if (extraImages?.length && contents.length > 0) {
|
|
1126
|
+
const isLastUser = !messages.slice(messages.indexOf(m) + 1).some((x) => x.role === "user");
|
|
1127
|
+
if (isLastUser) {
|
|
1128
|
+
for (const img of extraImages) {
|
|
1129
|
+
if (img.type === "base64") {
|
|
1130
|
+
parts.push({ inlineData: { mimeType: img.mimeType, data: img.data } });
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (parts.length > 0) {
|
|
1136
|
+
contents.push({ role: "user", parts });
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
return contents;
|
|
1010
1141
|
}
|
|
1011
1142
|
convertMessageContent(msg, extraImages) {
|
|
1012
1143
|
const parts = [];
|
|
@@ -1568,9 +1699,10 @@ var MemoryStore = class _MemoryStore {
|
|
|
1568
1699
|
constructor(dbPath) {
|
|
1569
1700
|
fs14.mkdirSync(path17.dirname(dbPath), { recursive: true });
|
|
1570
1701
|
try {
|
|
1571
|
-
this.db = new Database(dbPath);
|
|
1702
|
+
this.db = new Database(dbPath, { timeout: 5e3 });
|
|
1572
1703
|
this.db.pragma("journal_mode = WAL");
|
|
1573
1704
|
this.db.pragma("foreign_keys = ON");
|
|
1705
|
+
this.db.pragma("synchronous = NORMAL");
|
|
1574
1706
|
this.migrate();
|
|
1575
1707
|
} catch (err) {
|
|
1576
1708
|
if (err instanceof Error && err.message.includes("Could not locate the bindings file")) {
|
|
@@ -1584,6 +1716,38 @@ Original error: ${err.message}`
|
|
|
1584
1716
|
throw err;
|
|
1585
1717
|
}
|
|
1586
1718
|
}
|
|
1719
|
+
// ── Async Write Queue ─────────────────────────
|
|
1720
|
+
writeQueue = [];
|
|
1721
|
+
isProcessingQueue = false;
|
|
1722
|
+
async processQueue() {
|
|
1723
|
+
if (this.isProcessingQueue) return;
|
|
1724
|
+
this.isProcessingQueue = true;
|
|
1725
|
+
while (this.writeQueue.length > 0) {
|
|
1726
|
+
const op = this.writeQueue.shift();
|
|
1727
|
+
if (op) {
|
|
1728
|
+
let attempts = 0;
|
|
1729
|
+
while (attempts < 5) {
|
|
1730
|
+
try {
|
|
1731
|
+
op();
|
|
1732
|
+
break;
|
|
1733
|
+
} catch (err) {
|
|
1734
|
+
if (err instanceof Error && err.code === "SQLITE_BUSY") {
|
|
1735
|
+
attempts++;
|
|
1736
|
+
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, attempts)));
|
|
1737
|
+
} else {
|
|
1738
|
+
console.error("Cascade AI: DB Write Error:", err);
|
|
1739
|
+
break;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
this.isProcessingQueue = false;
|
|
1746
|
+
}
|
|
1747
|
+
enqueueWrite(op) {
|
|
1748
|
+
this.writeQueue.push(op);
|
|
1749
|
+
this.processQueue().catch(console.error);
|
|
1750
|
+
}
|
|
1587
1751
|
// ── Sessions ──────────────────────────────────
|
|
1588
1752
|
createSession(session) {
|
|
1589
1753
|
this.db.prepare(`
|
|
@@ -1670,26 +1834,28 @@ Original error: ${err.message}`
|
|
|
1670
1834
|
}
|
|
1671
1835
|
// ── Runtime Sessions / Nodes ─────────────────
|
|
1672
1836
|
upsertRuntimeSession(session) {
|
|
1673
|
-
this.
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1837
|
+
this.enqueueWrite(() => {
|
|
1838
|
+
this.db.prepare(`
|
|
1839
|
+
INSERT INTO runtime_sessions (session_id, title, workspace_path, status, started_at, updated_at, latest_prompt, is_global)
|
|
1840
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1841
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
1842
|
+
title = excluded.title,
|
|
1843
|
+
workspace_path = excluded.workspace_path,
|
|
1844
|
+
status = excluded.status,
|
|
1845
|
+
updated_at = excluded.updated_at,
|
|
1846
|
+
latest_prompt = excluded.latest_prompt,
|
|
1847
|
+
is_global = excluded.is_global
|
|
1848
|
+
`).run(
|
|
1849
|
+
session.sessionId,
|
|
1850
|
+
session.title,
|
|
1851
|
+
session.workspacePath,
|
|
1852
|
+
session.status,
|
|
1853
|
+
session.startedAt,
|
|
1854
|
+
session.updatedAt,
|
|
1855
|
+
session.latestPrompt ?? null,
|
|
1856
|
+
session.isGlobal ? 1 : 0
|
|
1857
|
+
);
|
|
1858
|
+
});
|
|
1693
1859
|
}
|
|
1694
1860
|
listRuntimeSessions(limit = 100) {
|
|
1695
1861
|
const rows = this.db.prepare(`
|
|
@@ -1707,33 +1873,35 @@ Original error: ${err.message}`
|
|
|
1707
1873
|
}));
|
|
1708
1874
|
}
|
|
1709
1875
|
upsertRuntimeNode(node) {
|
|
1710
|
-
this.
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1876
|
+
this.enqueueWrite(() => {
|
|
1877
|
+
this.db.prepare(`
|
|
1878
|
+
INSERT INTO runtime_nodes (tier_id, session_id, parent_id, role, label, status, current_action, progress_pct, updated_at, workspace_path, is_global)
|
|
1879
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1880
|
+
ON CONFLICT(tier_id) DO UPDATE SET
|
|
1881
|
+
session_id = excluded.session_id,
|
|
1882
|
+
parent_id = excluded.parent_id,
|
|
1883
|
+
role = excluded.role,
|
|
1884
|
+
label = excluded.label,
|
|
1885
|
+
status = excluded.status,
|
|
1886
|
+
current_action = excluded.current_action,
|
|
1887
|
+
progress_pct = excluded.progress_pct,
|
|
1888
|
+
updated_at = excluded.updated_at,
|
|
1889
|
+
workspace_path = excluded.workspace_path,
|
|
1890
|
+
is_global = excluded.is_global
|
|
1891
|
+
`).run(
|
|
1892
|
+
node.tierId,
|
|
1893
|
+
node.sessionId,
|
|
1894
|
+
node.parentId ?? null,
|
|
1895
|
+
node.role,
|
|
1896
|
+
node.label,
|
|
1897
|
+
node.status,
|
|
1898
|
+
node.currentAction ?? null,
|
|
1899
|
+
node.progressPct ?? null,
|
|
1900
|
+
node.updatedAt,
|
|
1901
|
+
node.workspacePath ?? null,
|
|
1902
|
+
node.isGlobal ? 1 : 0
|
|
1903
|
+
);
|
|
1904
|
+
});
|
|
1737
1905
|
}
|
|
1738
1906
|
listRuntimeNodes(sessionId, limit = 500) {
|
|
1739
1907
|
const rows = sessionId ? this.db.prepare(`
|
|
@@ -1756,30 +1924,32 @@ Original error: ${err.message}`
|
|
|
1756
1924
|
}));
|
|
1757
1925
|
}
|
|
1758
1926
|
addRuntimeNodeLog(log) {
|
|
1759
|
-
this.
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1927
|
+
this.enqueueWrite(() => {
|
|
1928
|
+
this.db.prepare(`
|
|
1929
|
+
INSERT INTO runtime_node_logs (id, session_id, tier_id, role, label, status, current_action, progress_pct, timestamp, workspace_path, is_global)
|
|
1930
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1931
|
+
`).run(
|
|
1932
|
+
log.id,
|
|
1933
|
+
log.sessionId,
|
|
1934
|
+
log.tierId,
|
|
1935
|
+
log.role,
|
|
1936
|
+
log.label,
|
|
1937
|
+
log.status,
|
|
1938
|
+
log.currentAction ?? null,
|
|
1939
|
+
log.progressPct ?? null,
|
|
1940
|
+
log.timestamp,
|
|
1941
|
+
log.workspacePath ?? null,
|
|
1942
|
+
log.isGlobal ? 1 : 0
|
|
1943
|
+
);
|
|
1944
|
+
this.db.prepare(`
|
|
1945
|
+
DELETE FROM runtime_node_logs
|
|
1946
|
+
WHERE id NOT IN (
|
|
1947
|
+
SELECT id FROM runtime_node_logs
|
|
1948
|
+
ORDER BY timestamp DESC
|
|
1949
|
+
LIMIT 2000
|
|
1950
|
+
)
|
|
1951
|
+
`).run();
|
|
1952
|
+
});
|
|
1783
1953
|
}
|
|
1784
1954
|
listRuntimeNodeLogs(sessionId, tierId, limit = 200) {
|
|
1785
1955
|
let rows;
|
|
@@ -1817,19 +1987,21 @@ Original error: ${err.message}`
|
|
|
1817
1987
|
}
|
|
1818
1988
|
// ── Messages ──────────────────────────────────
|
|
1819
1989
|
addMessage(message) {
|
|
1820
|
-
this.
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1990
|
+
this.enqueueWrite(() => {
|
|
1991
|
+
this.db.prepare(`
|
|
1992
|
+
INSERT INTO messages (id, session_id, role, content, timestamp, tokens, agent_messages)
|
|
1993
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1994
|
+
`).run(
|
|
1995
|
+
message.id,
|
|
1996
|
+
message.sessionId,
|
|
1997
|
+
message.role,
|
|
1998
|
+
typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
1999
|
+
message.timestamp,
|
|
2000
|
+
message.tokens ? JSON.stringify(message.tokens) : null,
|
|
2001
|
+
message.agentMessages ? JSON.stringify(message.agentMessages) : null
|
|
2002
|
+
);
|
|
2003
|
+
this.db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(message.timestamp, message.sessionId);
|
|
2004
|
+
});
|
|
1833
2005
|
}
|
|
1834
2006
|
getSessionMessages(sessionId) {
|
|
1835
2007
|
const rows = this.db.prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
@@ -1926,10 +2098,12 @@ Original error: ${err.message}`
|
|
|
1926
2098
|
}
|
|
1927
2099
|
// ── Audit Log ─────────────────────────────────
|
|
1928
2100
|
addAuditEntry(entry) {
|
|
1929
|
-
this.
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2101
|
+
this.enqueueWrite(() => {
|
|
2102
|
+
this.db.prepare(`
|
|
2103
|
+
INSERT INTO audit_log (id, session_id, timestamp, tier_id, action, details)
|
|
2104
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2105
|
+
`).run(entry.id, entry.sessionId, entry.timestamp, entry.tierId, entry.action, JSON.stringify(entry.details));
|
|
2106
|
+
});
|
|
1933
2107
|
}
|
|
1934
2108
|
getAuditLog(sessionId, limit = 100) {
|
|
1935
2109
|
const rows = this.db.prepare("SELECT * FROM audit_log WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
@@ -1944,10 +2118,12 @@ Original error: ${err.message}`
|
|
|
1944
2118
|
}
|
|
1945
2119
|
// ── File Snapshots ────────────────────────────
|
|
1946
2120
|
addFileSnapshot(sessionId, filePath, content) {
|
|
1947
|
-
this.
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2121
|
+
this.enqueueWrite(() => {
|
|
2122
|
+
this.db.prepare(`
|
|
2123
|
+
INSERT INTO file_snapshots (id, session_id, file_path, content, timestamp)
|
|
2124
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2125
|
+
`).run(randomUUID(), sessionId, filePath, content, (/* @__PURE__ */ new Date()).toISOString());
|
|
2126
|
+
});
|
|
1951
2127
|
}
|
|
1952
2128
|
getLatestFileSnapshots(sessionId) {
|
|
1953
2129
|
const rows = this.db.prepare(`
|
|
@@ -2251,12 +2427,24 @@ var McpServerConfigSchema = z.object({
|
|
|
2251
2427
|
args: z.array(z.string()).optional(),
|
|
2252
2428
|
env: z.record(z.string()).optional()
|
|
2253
2429
|
});
|
|
2430
|
+
var WebSearchConfigSchema = z.object({
|
|
2431
|
+
/** Base URL of your SearXNG instance (e.g. http://localhost:8080) */
|
|
2432
|
+
searxngUrl: z.string().optional(),
|
|
2433
|
+
/** Brave Search API key — get one at https://api.search.brave.com */
|
|
2434
|
+
braveApiKey: z.string().optional(),
|
|
2435
|
+
/** Tavily API key — get one at https://tavily.com */
|
|
2436
|
+
tavilyApiKey: z.string().optional(),
|
|
2437
|
+
/** Max results per search (default 5) */
|
|
2438
|
+
maxResults: z.number().default(5)
|
|
2439
|
+
});
|
|
2254
2440
|
var ToolsConfigSchema = z.object({
|
|
2255
2441
|
shellAllowlist: z.array(z.string()).default([]),
|
|
2256
2442
|
shellBlocklist: z.array(z.string()).default(["rm -rf", "sudo rm", "format", "mkfs"]),
|
|
2257
2443
|
requireApprovalFor: z.array(z.string()).default([]),
|
|
2258
2444
|
browserEnabled: z.boolean().default(false),
|
|
2259
|
-
mcpServers: z.array(McpServerConfigSchema).optional()
|
|
2445
|
+
mcpServers: z.array(McpServerConfigSchema).optional(),
|
|
2446
|
+
/** Web search backends — at least one should be configured for best results */
|
|
2447
|
+
webSearch: WebSearchConfigSchema.optional()
|
|
2260
2448
|
});
|
|
2261
2449
|
var HookDefinitionSchema = z.object({
|
|
2262
2450
|
command: z.string(),
|
|
@@ -2361,7 +2549,7 @@ var ConfigManager = class {
|
|
|
2361
2549
|
globalDir;
|
|
2362
2550
|
constructor(workspacePath = process.cwd()) {
|
|
2363
2551
|
this.workspacePath = workspacePath;
|
|
2364
|
-
this.globalDir = path17.join(
|
|
2552
|
+
this.globalDir = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR);
|
|
2365
2553
|
}
|
|
2366
2554
|
async load() {
|
|
2367
2555
|
this.config = await this.loadConfig();
|
|
@@ -2429,6 +2617,7 @@ var ConfigManager = class {
|
|
|
2429
2617
|
}
|
|
2430
2618
|
}
|
|
2431
2619
|
async injectEnvKeys() {
|
|
2620
|
+
const isFirstRun = this.config.providers.length === 0;
|
|
2432
2621
|
const envProviders = [
|
|
2433
2622
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
2434
2623
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
@@ -2439,10 +2628,13 @@ var ConfigManager = class {
|
|
|
2439
2628
|
const key = process.env[env];
|
|
2440
2629
|
if (!key) continue;
|
|
2441
2630
|
const existing = this.config.providers.find((p) => p.type === type);
|
|
2442
|
-
if (!existing)
|
|
2443
|
-
|
|
2631
|
+
if (!existing && isFirstRun) {
|
|
2632
|
+
this.config.providers.push({ type, apiKey: key });
|
|
2633
|
+
} else if (existing && !existing.apiKey) {
|
|
2634
|
+
existing.apiKey = key;
|
|
2635
|
+
}
|
|
2444
2636
|
}
|
|
2445
|
-
if (!this.config.providers.find((p) => p.type === "ollama")) {
|
|
2637
|
+
if (isFirstRun && !this.config.providers.find((p) => p.type === "ollama")) {
|
|
2446
2638
|
this.config.providers.push({ type: "ollama" });
|
|
2447
2639
|
}
|
|
2448
2640
|
}
|
|
@@ -3360,6 +3552,16 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
3360
3552
|
return /rate.?limit|429|too.?many.?requests|quota/i.test(msg);
|
|
3361
3553
|
}
|
|
3362
3554
|
};
|
|
3555
|
+
|
|
3556
|
+
// src/utils/retry.ts
|
|
3557
|
+
var CascadeCancelledError = class extends Error {
|
|
3558
|
+
constructor(reason) {
|
|
3559
|
+
super(reason ?? "Run was cancelled via AbortSignal");
|
|
3560
|
+
this.name = "CascadeCancelledError";
|
|
3561
|
+
}
|
|
3562
|
+
};
|
|
3563
|
+
|
|
3564
|
+
// src/core/tiers/base.ts
|
|
3363
3565
|
var BaseTier = class extends EventEmitter {
|
|
3364
3566
|
id;
|
|
3365
3567
|
role;
|
|
@@ -3369,6 +3571,8 @@ var BaseTier = class extends EventEmitter {
|
|
|
3369
3571
|
label;
|
|
3370
3572
|
systemPromptOverride = "";
|
|
3371
3573
|
hierarchyContext = "";
|
|
3574
|
+
/** Propagated AbortSignal — set by the tier's `execute()` before work begins. */
|
|
3575
|
+
signal;
|
|
3372
3576
|
constructor(role, id, parentId) {
|
|
3373
3577
|
super();
|
|
3374
3578
|
this.role = role;
|
|
@@ -3431,6 +3635,18 @@ var BaseTier = class extends EventEmitter {
|
|
|
3431
3635
|
log(message, data) {
|
|
3432
3636
|
this.emit("log", { tierId: this.id, role: this.role, message, data, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3433
3637
|
}
|
|
3638
|
+
/**
|
|
3639
|
+
* Throws `CascadeCancelledError` if the run's `AbortSignal` has fired.
|
|
3640
|
+
* Call this at safe checkpoints (before LLM calls, between T3 dispatches)
|
|
3641
|
+
* to provide a fast, clean cancellation path.
|
|
3642
|
+
*/
|
|
3643
|
+
throwIfCancelled() {
|
|
3644
|
+
if (this.signal?.aborted) {
|
|
3645
|
+
throw new CascadeCancelledError(
|
|
3646
|
+
typeof this.signal.reason === "string" ? this.signal.reason : "Run cancelled by caller"
|
|
3647
|
+
);
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3434
3650
|
};
|
|
3435
3651
|
|
|
3436
3652
|
// src/core/context/manager.ts
|
|
@@ -3632,6 +3848,7 @@ Rules:
|
|
|
3632
3848
|
- Execute the subtask completely \u2014 do not stop partway through.
|
|
3633
3849
|
- Use tools when needed. Ask for approval only when the tool registry requires it.
|
|
3634
3850
|
- If the task asks for a file or artifact, you must actually create it in the workspace, verify that it exists, and inspect it before claiming success.
|
|
3851
|
+
- Use the "web_search" tool to find current information, documentation, news, or general web data.
|
|
3635
3852
|
- Use the "pdf_create" tool for PDF requests.
|
|
3636
3853
|
- Use the "run_code" tool for any file types (Excel, Zip, csv, etc.) or complex processing not covered by other tools. Always cleanup after code execution.
|
|
3637
3854
|
- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.
|
|
@@ -3675,7 +3892,8 @@ var T3Worker = class extends BaseTier {
|
|
|
3675
3892
|
this.store = store;
|
|
3676
3893
|
this.audit = new AuditLogger(store, sessionId);
|
|
3677
3894
|
}
|
|
3678
|
-
async execute(assignment, taskId) {
|
|
3895
|
+
async execute(assignment, taskId, signal) {
|
|
3896
|
+
this.signal = signal;
|
|
3679
3897
|
this.assignment = assignment;
|
|
3680
3898
|
this.taskId = taskId;
|
|
3681
3899
|
this.setLabel(assignment.subtaskTitle);
|
|
@@ -3825,6 +4043,7 @@ Now execute your subtask using this context where relevant.`
|
|
|
3825
4043
|
tools = [...tools];
|
|
3826
4044
|
while (iterations < MAX_ITERATIONS) {
|
|
3827
4045
|
iterations++;
|
|
4046
|
+
this.throwIfCancelled();
|
|
3828
4047
|
const options = {
|
|
3829
4048
|
messages: this.context.getMessages(),
|
|
3830
4049
|
systemPrompt: this.systemPromptOverride + systemPrompt + (this.hierarchyContext ? `
|
|
@@ -3845,21 +4064,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
3845
4064
|
if (requiresArtifact) {
|
|
3846
4065
|
stalledArtifactIterations += 1;
|
|
3847
4066
|
if (stalledArtifactIterations >= 2) {
|
|
3848
|
-
if (
|
|
3849
|
-
|
|
3850
|
-
`Help complete: ${this.assignment?.subtaskTitle ?? "unknown task"}`,
|
|
3851
|
-
this.assignment?.description ?? ""
|
|
3852
|
-
);
|
|
3853
|
-
if (toolName) {
|
|
3854
|
-
tools = this.toolRegistry.getToolDefinitions();
|
|
3855
|
-
this.sendStatusUpdate({
|
|
3856
|
-
progressPct: 50,
|
|
3857
|
-
currentAction: `Dynamic tool created: ${toolName}`,
|
|
3858
|
-
status: "IN_PROGRESS"
|
|
3859
|
-
});
|
|
3860
|
-
this.emit("tool:created", { tierId: this.id, toolName });
|
|
3861
|
-
continue;
|
|
3862
|
-
}
|
|
4067
|
+
if (stalledArtifactIterations === 2) {
|
|
4068
|
+
throw new Error(`Worker stalled waiting for artifact creation. Requesting dynamic tool generation from T2 Manager for: ${this.assignment?.subtaskTitle ?? "unknown task"}`);
|
|
3863
4069
|
}
|
|
3864
4070
|
throw new Error("Artifact-producing task stalled without creating or verifying the required files");
|
|
3865
4071
|
}
|
|
@@ -4019,6 +4225,9 @@ ${assignment.expectedOutput}`;
|
|
|
4019
4225
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
4020
4226
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
4021
4227
|
const issues = [];
|
|
4228
|
+
const { exec: exec4 } = await import('child_process');
|
|
4229
|
+
const { promisify: promisify3 } = await import('util');
|
|
4230
|
+
const execAsync3 = promisify3(exec4);
|
|
4022
4231
|
for (const artifactPath of artifactPaths) {
|
|
4023
4232
|
const absolutePath = path17.resolve(process.cwd(), artifactPath);
|
|
4024
4233
|
try {
|
|
@@ -4035,9 +4244,27 @@ ${assignment.expectedOutput}`;
|
|
|
4035
4244
|
const content = await fs7.readFile(absolutePath, "utf-8");
|
|
4036
4245
|
if (!content.trim()) {
|
|
4037
4246
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
4247
|
+
continue;
|
|
4038
4248
|
}
|
|
4039
4249
|
} else if (stat.size < 100) {
|
|
4040
4250
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
4251
|
+
continue;
|
|
4252
|
+
}
|
|
4253
|
+
const ext = path17.extname(absolutePath).toLowerCase();
|
|
4254
|
+
try {
|
|
4255
|
+
if (ext === ".ts" || ext === ".tsx") {
|
|
4256
|
+
await execAsync3(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
4257
|
+
} else if (ext === ".js" || ext === ".jsx") {
|
|
4258
|
+
await execAsync3(`node --check ${absolutePath}`, { timeout: 1e4 });
|
|
4259
|
+
} else if (ext === ".py") {
|
|
4260
|
+
await execAsync3(`python -m py_compile ${absolutePath}`, { timeout: 1e4 });
|
|
4261
|
+
}
|
|
4262
|
+
} catch (err) {
|
|
4263
|
+
const stderr = err?.stderr || String(err);
|
|
4264
|
+
const stdout = err?.stdout || "";
|
|
4265
|
+
issues.push(`Semantic error in ${artifactPath}:
|
|
4266
|
+
${stderr}
|
|
4267
|
+
${stdout}`);
|
|
4041
4268
|
}
|
|
4042
4269
|
} catch {
|
|
4043
4270
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -4434,7 +4661,8 @@ var T2Manager = class extends BaseTier {
|
|
|
4434
4661
|
});
|
|
4435
4662
|
this.emit("peer-sync-received", { fromId, content });
|
|
4436
4663
|
}
|
|
4437
|
-
async execute(assignment, taskId) {
|
|
4664
|
+
async execute(assignment, taskId, signal) {
|
|
4665
|
+
this.signal = signal;
|
|
4438
4666
|
this.assignment = assignment;
|
|
4439
4667
|
this.taskId = taskId;
|
|
4440
4668
|
this.setLabel(assignment.sectionTitle);
|
|
@@ -4446,12 +4674,14 @@ var T2Manager = class extends BaseTier {
|
|
|
4446
4674
|
});
|
|
4447
4675
|
this.log(`T2 managing section: ${assignment.sectionTitle}`);
|
|
4448
4676
|
try {
|
|
4677
|
+
this.throwIfCancelled();
|
|
4449
4678
|
const subtasks = assignment.t3Subtasks.length > 0 ? assignment.t3Subtasks : await this.decomposeSection(assignment);
|
|
4450
4679
|
this.sendStatusUpdate({
|
|
4451
4680
|
progressPct: 20,
|
|
4452
4681
|
currentAction: `Dispatching ${subtasks.length} T3 workers`,
|
|
4453
4682
|
status: "IN_PROGRESS"
|
|
4454
4683
|
});
|
|
4684
|
+
this.throwIfCancelled();
|
|
4455
4685
|
const t3Results = await this.executeSubtasks(subtasks, taskId);
|
|
4456
4686
|
this.sendStatusUpdate({
|
|
4457
4687
|
progressPct: 90,
|
|
@@ -4618,11 +4848,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4618
4848
|
).join(", ")}`,
|
|
4619
4849
|
status: "IN_PROGRESS"
|
|
4620
4850
|
});
|
|
4851
|
+
this.throwIfCancelled();
|
|
4621
4852
|
const waveResults = await Promise.allSettled(
|
|
4622
4853
|
runnableIds.map(async (id) => {
|
|
4623
4854
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4624
4855
|
const worker = workerMap.get(id);
|
|
4625
|
-
const result = await worker.execute(assignment, taskId);
|
|
4856
|
+
const result = await worker.execute(assignment, taskId, this.signal);
|
|
4626
4857
|
resultMap.set(id, result);
|
|
4627
4858
|
return result;
|
|
4628
4859
|
})
|
|
@@ -4636,6 +4867,60 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4636
4867
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4637
4868
|
const retried = await this.retryT3(assignment, taskId);
|
|
4638
4869
|
resultMap.set(id, retried);
|
|
4870
|
+
} else if (r.status === "fulfilled" && r.value.status === "ESCALATED" && r.value.issues.some((i2) => i2.includes("dynamic tool generation"))) {
|
|
4871
|
+
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
4872
|
+
if (this.toolCreator) {
|
|
4873
|
+
this.log(`T3 escalated for tool. T2 spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`);
|
|
4874
|
+
this.sendStatusUpdate({
|
|
4875
|
+
progressPct: 50,
|
|
4876
|
+
currentAction: `Spawning Tool-Builder T3 for: ${assignment.subtaskTitle}`,
|
|
4877
|
+
status: "IN_PROGRESS"
|
|
4878
|
+
});
|
|
4879
|
+
const toolName = await this.toolCreator.createTool(
|
|
4880
|
+
`Help complete: ${assignment.subtaskTitle}`,
|
|
4881
|
+
assignment.description
|
|
4882
|
+
);
|
|
4883
|
+
if (toolName) {
|
|
4884
|
+
this.log(`T2 verifying new tool: ${toolName}`);
|
|
4885
|
+
this.sendStatusUpdate({
|
|
4886
|
+
progressPct: 60,
|
|
4887
|
+
currentAction: `T2 Verifying new tool: ${toolName}`,
|
|
4888
|
+
status: "IN_PROGRESS"
|
|
4889
|
+
});
|
|
4890
|
+
try {
|
|
4891
|
+
const verifyResult = await this.router.generate("T2", {
|
|
4892
|
+
messages: [{ role: "user", content: `A new tool named "${toolName}" was just created dynamically to help with: ${assignment.description}. Based on its name and purpose, does this seem like a valid addition? Reply "VERIFIED" or "REJECTED".` }],
|
|
4893
|
+
systemPrompt: this.systemPromptOverride + "You are T2 Manager verifying a dynamic tool.",
|
|
4894
|
+
maxTokens: 50
|
|
4895
|
+
});
|
|
4896
|
+
if (!verifyResult.content.toUpperCase().includes("REJECTED")) {
|
|
4897
|
+
this.log(`T2 verification passed for ${toolName}. Restarting original T3.`);
|
|
4898
|
+
const retried = await this.retryT3({
|
|
4899
|
+
...assignment,
|
|
4900
|
+
description: `${assignment.description}
|
|
4901
|
+
|
|
4902
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built and verified for you. Use it to complete your task.`
|
|
4903
|
+
}, taskId);
|
|
4904
|
+
resultMap.set(id, retried);
|
|
4905
|
+
} else {
|
|
4906
|
+
this.log(`T2 rejected the dynamic tool: ${toolName}`);
|
|
4907
|
+
resultMap.set(id, r.value);
|
|
4908
|
+
}
|
|
4909
|
+
} catch {
|
|
4910
|
+
const retried = await this.retryT3({
|
|
4911
|
+
...assignment,
|
|
4912
|
+
description: `${assignment.description}
|
|
4913
|
+
|
|
4914
|
+
[SYSTEM NOTIFICATION]: A new dynamic tool "${toolName}" has been built for you. Use it to complete your task.`
|
|
4915
|
+
}, taskId);
|
|
4916
|
+
resultMap.set(id, retried);
|
|
4917
|
+
}
|
|
4918
|
+
} else {
|
|
4919
|
+
resultMap.set(id, r.value);
|
|
4920
|
+
}
|
|
4921
|
+
} else {
|
|
4922
|
+
resultMap.set(id, r.value);
|
|
4923
|
+
}
|
|
4639
4924
|
}
|
|
4640
4925
|
for (const dependent of adj.get(id) ?? []) {
|
|
4641
4926
|
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
@@ -4699,7 +4984,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4699
4984
|
}));
|
|
4700
4985
|
return worker.execute(
|
|
4701
4986
|
{ ...assignment, description: `[RETRY] ${assignment.description}` },
|
|
4702
|
-
taskId
|
|
4987
|
+
taskId,
|
|
4988
|
+
this.signal
|
|
4703
4989
|
);
|
|
4704
4990
|
}
|
|
4705
4991
|
publishSectionOutput(result) {
|
|
@@ -4713,29 +4999,51 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
4713
4999
|
async aggregateResults(assignment, results) {
|
|
4714
5000
|
const completed = results.filter((r) => r.status === "COMPLETED");
|
|
4715
5001
|
if (!completed.length) return `Section ${assignment.sectionTitle} failed \u2014 no T3 workers completed.`;
|
|
4716
|
-
const outputs = completed.map((r, i) => `[T3-${i + 1}]: ${r.output}`).join("\n\n");
|
|
4717
5002
|
const peerOutputs = this.peerSyncBuffer.filter((p) => p.content?.type === "T2_SECTION_OUTPUT").map((p) => `[Peer ${p.fromId} Output]: ${p.content.output}`).join("\n\n");
|
|
4718
|
-
const
|
|
4719
|
-
|
|
4720
|
-
${outputs}
|
|
4721
|
-
${peerOutputs ? `
|
|
5003
|
+
const peerContext = peerOutputs ? `
|
|
4722
5004
|
|
|
4723
5005
|
Context from sibling T2 completed sections (use this to ensure your summary aligns with the overall state):
|
|
4724
|
-
${peerOutputs}` : ""
|
|
4725
|
-
const
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
5006
|
+
${peerOutputs}` : "";
|
|
5007
|
+
const MAX_CHUNK_LENGTH = 15e3;
|
|
5008
|
+
let currentSummary = "";
|
|
5009
|
+
let i = 0;
|
|
5010
|
+
while (i < completed.length) {
|
|
5011
|
+
let chunkText = "";
|
|
5012
|
+
let chunkEnd = i;
|
|
5013
|
+
while (chunkEnd < completed.length) {
|
|
5014
|
+
const nextOutput = `[T3-${chunkEnd + 1}]: ${completed[chunkEnd].output}
|
|
5015
|
+
|
|
5016
|
+
`;
|
|
5017
|
+
if (chunkText.length + nextOutput.length > MAX_CHUNK_LENGTH && chunkEnd > i) {
|
|
5018
|
+
break;
|
|
5019
|
+
}
|
|
5020
|
+
chunkText += nextOutput;
|
|
5021
|
+
chunkEnd++;
|
|
5022
|
+
}
|
|
5023
|
+
i = chunkEnd;
|
|
5024
|
+
const prompt = `Summarize these T3 worker outputs for section "${assignment.sectionTitle}" in 2-3 sentences.
|
|
5025
|
+
${currentSummary ? `
|
|
5026
|
+
PREVIOUS SUMMARY SO FAR:
|
|
5027
|
+
${currentSummary}
|
|
5028
|
+
|
|
5029
|
+
NEW OUTPUTS TO INTEGRATE:
|
|
5030
|
+
` : "\nOUTPUTS:\n"}${chunkText}${peerContext}`;
|
|
5031
|
+
const messages = [{ role: "user", content: prompt }];
|
|
5032
|
+
try {
|
|
5033
|
+
const result = await this.router.generate("T2", {
|
|
5034
|
+
messages,
|
|
5035
|
+
systemPrompt: this.systemPromptOverride + "You are a T2 Manager. Summarize the work of your T3 workers succinctly." + (this.hierarchyContext ? `
|
|
4730
5036
|
|
|
4731
5037
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
5038
|
+
maxTokens: 500
|
|
5039
|
+
});
|
|
5040
|
+
currentSummary = result.content;
|
|
5041
|
+
} catch (err) {
|
|
5042
|
+
this.log(`aggregateResults: LLM summarization failed at chunk \u2014 returning raw T3 outputs. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
5043
|
+
return currentSummary + "\n\n" + chunkText;
|
|
5044
|
+
}
|
|
4738
5045
|
}
|
|
5046
|
+
return currentSummary;
|
|
4739
5047
|
}
|
|
4740
5048
|
determineStatus(results) {
|
|
4741
5049
|
if (results.every((r) => r.status === "COMPLETED")) return "COMPLETED";
|
|
@@ -4863,10 +5171,10 @@ Rules:
|
|
|
4863
5171
|
- If the user asks for Excel/Zip/complex processing, use "run_code" with Python or Node.js
|
|
4864
5172
|
- Ensure every plan includes explicit creation and verification steps for requested artifacts
|
|
4865
5173
|
|
|
4866
|
-
|
|
4867
|
-
-
|
|
4868
|
-
-
|
|
4869
|
-
- Prefer parallel execution: it is significantly faster and reduces total wall-clock time.
|
|
5174
|
+
DEPENDENCY GUIDANCE:
|
|
5175
|
+
- Leave "dependsOn" empty [] for sections that are independent (e.g. writing different files, researching different topics).
|
|
5176
|
+
- Populate "dependsOn" with section IDs ONLY when a later section strictly depends on the output of an earlier one (e.g. write code \u2192 then test it).
|
|
5177
|
+
- Prefer empty dependencies (parallel execution): it is significantly faster and reduces total wall-clock time.
|
|
4870
5178
|
- Within a sequential section, mark T3 subtasks with "dependsOn" only when they truly block each other.
|
|
4871
5179
|
|
|
4872
5180
|
QUALITY RULES:
|
|
@@ -4905,7 +5213,8 @@ var T1Administrator = class extends BaseTier {
|
|
|
4905
5213
|
setToolCreator(creator) {
|
|
4906
5214
|
this.toolCreator = creator;
|
|
4907
5215
|
}
|
|
4908
|
-
async execute(userPrompt, images, systemContext) {
|
|
5216
|
+
async execute(userPrompt, images, systemContext, signal) {
|
|
5217
|
+
this.signal = signal;
|
|
4909
5218
|
this.taskId = randomUUID();
|
|
4910
5219
|
this.setLabel("Administrator");
|
|
4911
5220
|
this.setStatus("ACTIVE");
|
|
@@ -4916,10 +5225,12 @@ var T1Administrator = class extends BaseTier {
|
|
|
4916
5225
|
status: "IN_PROGRESS"
|
|
4917
5226
|
});
|
|
4918
5227
|
this.log(`T1 received task: ${userPrompt.slice(0, 100)}...`);
|
|
5228
|
+
this.throwIfCancelled();
|
|
4919
5229
|
let enrichedPrompt = userPrompt;
|
|
4920
5230
|
if (images?.length) {
|
|
4921
5231
|
enrichedPrompt = await this.analyzeImages(userPrompt, images);
|
|
4922
5232
|
}
|
|
5233
|
+
this.throwIfCancelled();
|
|
4923
5234
|
const plan = await this.decomposeTask(enrichedPrompt, systemContext);
|
|
4924
5235
|
this.sendStatusUpdate({
|
|
4925
5236
|
progressPct: 10,
|
|
@@ -4927,21 +5238,83 @@ var T1Administrator = class extends BaseTier {
|
|
|
4927
5238
|
status: "IN_PROGRESS"
|
|
4928
5239
|
});
|
|
4929
5240
|
this.emit("plan", { taskId: this.taskId, plan });
|
|
4930
|
-
|
|
5241
|
+
this.throwIfCancelled();
|
|
5242
|
+
let allT2Results = await this.dispatchT2Managers(plan.sections);
|
|
5243
|
+
let pass = 1;
|
|
5244
|
+
const MAX_REPLAN_PASSES = 2;
|
|
5245
|
+
while (pass <= MAX_REPLAN_PASSES) {
|
|
5246
|
+
const reviewResult = await this.reviewT2Outputs(enrichedPrompt, plan, allT2Results);
|
|
5247
|
+
if (reviewResult.approved) {
|
|
5248
|
+
this.log("T1 Review passed.");
|
|
5249
|
+
break;
|
|
5250
|
+
}
|
|
5251
|
+
this.log(`T1 Review rejected outputs. Replanning (Pass ${pass}). Reason: ${reviewResult.reason}`);
|
|
5252
|
+
this.sendStatusUpdate({
|
|
5253
|
+
progressPct: 80 + pass * 5,
|
|
5254
|
+
currentAction: `Review failed: ${reviewResult.reason}. Replanning...`,
|
|
5255
|
+
status: "IN_PROGRESS"
|
|
5256
|
+
});
|
|
5257
|
+
const correctionPlan = await this.decomposeTask(`The previous execution plan failed to fully satisfy the original goal or encountered errors.
|
|
5258
|
+
Review reason: ${reviewResult.reason}
|
|
5259
|
+
|
|
5260
|
+
Original goal: ${enrichedPrompt}
|
|
5261
|
+
|
|
5262
|
+
Create a CORRECTION PLAN that contains only the new sections needed to fix the issues. Do not repeat successful sections.`);
|
|
5263
|
+
const correctionResults = await this.dispatchT2Managers(correctionPlan.sections);
|
|
5264
|
+
allT2Results = [...allT2Results, ...correctionResults];
|
|
5265
|
+
pass++;
|
|
5266
|
+
}
|
|
4931
5267
|
this.sendStatusUpdate({
|
|
4932
5268
|
progressPct: 95,
|
|
4933
5269
|
currentAction: "Compiling final output",
|
|
4934
5270
|
status: "IN_PROGRESS"
|
|
4935
5271
|
});
|
|
4936
|
-
const output = await this.compileFinalOutput(userPrompt, plan,
|
|
5272
|
+
const output = await this.compileFinalOutput(userPrompt, plan, allT2Results);
|
|
4937
5273
|
this.setStatus("COMPLETED");
|
|
4938
5274
|
this.sendStatusUpdate({ progressPct: 100, currentAction: "Task complete", status: "IN_PROGRESS" });
|
|
4939
|
-
return { output, t2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
5275
|
+
return { output, t2Results: allT2Results, taskId: this.taskId, complexity: plan.complexity };
|
|
4940
5276
|
}
|
|
4941
5277
|
getEscalations() {
|
|
4942
5278
|
return [...this.escalations];
|
|
4943
5279
|
}
|
|
4944
5280
|
// ── Private ──────────────────────────────────
|
|
5281
|
+
async reviewT2Outputs(originalPrompt, plan, t2Results) {
|
|
5282
|
+
const failedSections = t2Results.filter((r) => r.status === "FAILED");
|
|
5283
|
+
if (failedSections.length > 0) {
|
|
5284
|
+
return {
|
|
5285
|
+
approved: false,
|
|
5286
|
+
reason: `Some T2 managers failed entirely: ${failedSections.map((s) => s.sectionTitle).join(", ")}. Errors: ${failedSections.flatMap((s) => s.issues).join("; ")}`
|
|
5287
|
+
};
|
|
5288
|
+
}
|
|
5289
|
+
const sectionsText = t2Results.map((r) => `**${r.sectionTitle}**
|
|
5290
|
+
${r.sectionSummary}`).join("\n\n");
|
|
5291
|
+
const prompt = `You are a strict QA Reviewer for the Cascade AI system.
|
|
5292
|
+
Review the following execution outputs against the original user prompt.
|
|
5293
|
+
|
|
5294
|
+
Original Request: ${originalPrompt}
|
|
5295
|
+
|
|
5296
|
+
T2 Manager Summaries:
|
|
5297
|
+
${sectionsText}
|
|
5298
|
+
|
|
5299
|
+
Does the current state of the workspace and the outputs fully satisfy the user's request?
|
|
5300
|
+
If yes, reply with exactly: "APPROVED".
|
|
5301
|
+
If no, reply with "REJECTED: [Detailed reason explaining exactly what is missing or incorrect]".`;
|
|
5302
|
+
try {
|
|
5303
|
+
const result = await this.router.generate("T1", {
|
|
5304
|
+
messages: [{ role: "user", content: prompt }],
|
|
5305
|
+
systemPrompt: this.systemPromptOverride + "You are a QA Reviewer.",
|
|
5306
|
+
maxTokens: 500,
|
|
5307
|
+
temperature: 0
|
|
5308
|
+
});
|
|
5309
|
+
const response = result.content.trim();
|
|
5310
|
+
if (response.toUpperCase().startsWith("APPROVED")) {
|
|
5311
|
+
return { approved: true };
|
|
5312
|
+
}
|
|
5313
|
+
return { approved: false, reason: response.replace(/^REJECTED:\s*/i, "") };
|
|
5314
|
+
} catch {
|
|
5315
|
+
return { approved: true };
|
|
5316
|
+
}
|
|
5317
|
+
}
|
|
4945
5318
|
async analyzeImages(prompt, images) {
|
|
4946
5319
|
const visionModel = this.router.getModelForTier("T1");
|
|
4947
5320
|
if (!visionModel?.isVisionCapable) return prompt;
|
|
@@ -4970,29 +5343,35 @@ ${systemContext}` : "";
|
|
|
4970
5343
|
Example: if asked to create files "inside python_exclusive", every subtask that
|
|
4971
5344
|
creates a file must use "python_exclusive/filename.ext" as the path.
|
|
4972
5345
|
|
|
4973
|
-
Return JSON where
|
|
5346
|
+
Return JSON where SECTIONS can declare dependencies on other SECTIONS:
|
|
4974
5347
|
{
|
|
4975
5348
|
"sections": [{
|
|
5349
|
+
"sectionId": "s1",
|
|
5350
|
+
"sectionTitle": "Setup Project",
|
|
5351
|
+
"description": "Initialize the project",
|
|
5352
|
+
"expectedOutput": "Basic structure created",
|
|
5353
|
+
"constraints": [],
|
|
5354
|
+
"dependsOn": [], // \u2190 empty = runs immediately
|
|
4976
5355
|
"t3Subtasks": [{
|
|
4977
5356
|
"subtaskId": "t1",
|
|
4978
|
-
"subtaskTitle": "
|
|
4979
|
-
"
|
|
4980
|
-
"
|
|
4981
|
-
|
|
4982
|
-
"
|
|
4983
|
-
"subtaskTitle": "Save Code to File",
|
|
4984
|
-
"dependsOn": ["t1"], // \u2190 waits for t1 to complete first
|
|
4985
|
-
"executionMode": "parallel"
|
|
4986
|
-
}, {
|
|
4987
|
-
"subtaskId": "t3",
|
|
4988
|
-
"subtaskTitle": "Execute and Verify",
|
|
4989
|
-
"dependsOn": ["t2"], // \u2190 waits for t2
|
|
4990
|
-
"executionMode": "parallel"
|
|
5357
|
+
"subtaskTitle": "Init NPM",
|
|
5358
|
+
"description": "Run npm init",
|
|
5359
|
+
"expectedOutput": "package.json created",
|
|
5360
|
+
"constraints": [],
|
|
5361
|
+
"dependsOn": []
|
|
4991
5362
|
}]
|
|
5363
|
+
}, {
|
|
5364
|
+
"sectionId": "s2",
|
|
5365
|
+
"sectionTitle": "Write Tests",
|
|
5366
|
+
"description": "Write tests for the project",
|
|
5367
|
+
"expectedOutput": "Tests passing",
|
|
5368
|
+
"constraints": [],
|
|
5369
|
+
"dependsOn": ["s1"], // \u2190 waits for section s1 to complete first
|
|
5370
|
+
"t3Subtasks": [...]
|
|
4992
5371
|
}]
|
|
4993
5372
|
}
|
|
4994
|
-
Use dependsOn when a
|
|
4995
|
-
Leave dependsOn empty for
|
|
5373
|
+
Use dependsOn at the SECTION level when a whole T2 Manager needs the output of a previous T2 Manager.
|
|
5374
|
+
Leave dependsOn empty for sections that can run immediately in parallel.`;
|
|
4996
5375
|
const messages = [{ role: "user", content: decompositionPrompt }];
|
|
4997
5376
|
const result = await this.router.generate("T1", {
|
|
4998
5377
|
messages,
|
|
@@ -5120,92 +5499,127 @@ Leave dependsOn empty for subtasks that can run immediately in parallel.`;
|
|
|
5120
5499
|
].filter(Boolean).join(" ");
|
|
5121
5500
|
m.setHierarchyContext(context);
|
|
5122
5501
|
});
|
|
5123
|
-
if (overlapSections.size > 0
|
|
5124
|
-
this.log("Overlap detected \u2014
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5502
|
+
if (overlapSections.size > 0) {
|
|
5503
|
+
this.log("Overlap detected \u2014 adding sequential dependencies for conflicting sections to prevent race conditions");
|
|
5504
|
+
const overlapArray = Array.from(overlapSections);
|
|
5505
|
+
for (let i = 1; i < overlapArray.length; i++) {
|
|
5506
|
+
const section = sections.find((s) => s.sectionId === overlapArray[i]);
|
|
5507
|
+
if (section) {
|
|
5508
|
+
section.dependsOn = [...section.dependsOn || [], overlapArray[i - 1]];
|
|
5128
5509
|
}
|
|
5129
5510
|
}
|
|
5130
5511
|
}
|
|
5131
|
-
const pct = (i) => 10 + Math.floor(i / sections.length * 85);
|
|
5132
|
-
const isSequential = sections.some((s) => s.executionMode === "sequential");
|
|
5133
5512
|
const t2Results = [];
|
|
5134
5513
|
try {
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5514
|
+
t2Results.push(...await this.runT2sWithDependencies(sections, managers, this.taskId));
|
|
5515
|
+
} finally {
|
|
5516
|
+
cleanup();
|
|
5517
|
+
}
|
|
5518
|
+
return t2Results;
|
|
5519
|
+
}
|
|
5520
|
+
/**
|
|
5521
|
+
* Runs T2 managers respecting dependsOn declarations using Kahn's algorithm.
|
|
5522
|
+
*/
|
|
5523
|
+
async runT2sWithDependencies(sections, managers, taskId) {
|
|
5524
|
+
const adj = /* @__PURE__ */ new Map();
|
|
5525
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
5526
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
5527
|
+
const allKeys = new Set(sections.map((s) => s.sectionId));
|
|
5528
|
+
for (const s of sections) {
|
|
5529
|
+
if (!adj.has(s.sectionId)) adj.set(s.sectionId, /* @__PURE__ */ new Set());
|
|
5530
|
+
inDegree.set(s.sectionId, 0);
|
|
5531
|
+
s.dependsOn = (s.dependsOn ?? []).filter((d) => allKeys.has(d));
|
|
5532
|
+
}
|
|
5533
|
+
for (const s of sections) {
|
|
5534
|
+
for (const dep of s.dependsOn ?? []) {
|
|
5535
|
+
adj.get(dep).add(s.sectionId);
|
|
5536
|
+
inDegree.set(s.sectionId, (inDegree.get(s.sectionId) ?? 0) + 1);
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
const queue = [];
|
|
5540
|
+
const degree = new Map(inDegree);
|
|
5541
|
+
for (const [id, deg] of degree.entries()) if (deg === 0) queue.push(id);
|
|
5542
|
+
const visited = /* @__PURE__ */ new Set();
|
|
5543
|
+
while (queue.length > 0) {
|
|
5544
|
+
const u = queue.shift();
|
|
5545
|
+
visited.add(u);
|
|
5546
|
+
for (const v of adj.get(u) ?? /* @__PURE__ */ new Set()) {
|
|
5547
|
+
const newDeg = (degree.get(v) ?? 1) - 1;
|
|
5548
|
+
degree.set(v, newDeg);
|
|
5549
|
+
if (newDeg === 0) queue.push(v);
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
const cycleNodes = [...inDegree.keys()].filter((id) => !visited.has(id));
|
|
5553
|
+
if (cycleNodes.length > 0) {
|
|
5554
|
+
this.log(`\u26A0 Circular dependency detected among sections: [${cycleNodes.join(", ")}]. Breaking cycles.`);
|
|
5555
|
+
for (const s of sections) {
|
|
5556
|
+
if (cycleNodes.includes(s.sectionId)) {
|
|
5557
|
+
const safeDeps = (s.dependsOn ?? []).filter((d) => !cycleNodes.includes(d));
|
|
5558
|
+
for (const removed of (s.dependsOn ?? []).filter((d) => cycleNodes.includes(d))) {
|
|
5559
|
+
inDegree.set(s.sectionId, Math.max(0, (inDegree.get(s.sectionId) ?? 1) - 1));
|
|
5560
|
+
adj.get(removed)?.delete(s.sectionId);
|
|
5166
5561
|
}
|
|
5562
|
+
s.dependsOn = safeDeps;
|
|
5167
5563
|
}
|
|
5168
|
-
}
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5564
|
+
}
|
|
5565
|
+
}
|
|
5566
|
+
const totalSections = sections.length;
|
|
5567
|
+
let completedSections = 0;
|
|
5568
|
+
const executeWave = async () => {
|
|
5569
|
+
const readyIds = [];
|
|
5570
|
+
for (const [id, deg] of inDegree.entries()) {
|
|
5571
|
+
if (deg === 0 && !resultMap.has(id)) {
|
|
5572
|
+
readyIds.push(id);
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5575
|
+
if (readyIds.length === 0) return;
|
|
5576
|
+
await Promise.all(readyIds.map(async (id) => {
|
|
5577
|
+
resultMap.set(id, null);
|
|
5578
|
+
const index = sections.findIndex((s) => s.sectionId === id);
|
|
5579
|
+
const section = sections[index];
|
|
5580
|
+
const manager = managers[index];
|
|
5581
|
+
const progressPct = 10 + Math.floor(completedSections / totalSections * 85);
|
|
5582
|
+
this.sendStatusUpdate({
|
|
5583
|
+
progressPct,
|
|
5584
|
+
currentAction: `T2 working on: ${section.sectionTitle}`,
|
|
5585
|
+
status: "IN_PROGRESS"
|
|
5586
|
+
});
|
|
5587
|
+
this.throwIfCancelled();
|
|
5588
|
+
let result;
|
|
5589
|
+
try {
|
|
5590
|
+
result = await manager.execute(section, taskId, this.signal);
|
|
5591
|
+
manager.shareCompletedOutput(section.sectionId, result.sectionSummary);
|
|
5592
|
+
if (result.status === "ESCALATED") {
|
|
5593
|
+
this.escalations.push({
|
|
5594
|
+
raisedBy: `T2_${section.sectionId}`,
|
|
5595
|
+
sectionId: section.sectionId,
|
|
5596
|
+
attempted: result.issues,
|
|
5597
|
+
blocker: result.issues.join("; "),
|
|
5598
|
+
needs: "Human review required"
|
|
5201
5599
|
});
|
|
5202
5600
|
}
|
|
5601
|
+
} catch (err) {
|
|
5602
|
+
result = {
|
|
5603
|
+
sectionId: section.sectionId,
|
|
5604
|
+
sectionTitle: section.sectionTitle,
|
|
5605
|
+
status: "FAILED",
|
|
5606
|
+
t3Results: [],
|
|
5607
|
+
sectionSummary: "",
|
|
5608
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
5609
|
+
};
|
|
5610
|
+
}
|
|
5611
|
+
resultMap.set(id, result);
|
|
5612
|
+
completedSections++;
|
|
5613
|
+
for (const dependentId of adj.get(id) ?? /* @__PURE__ */ new Set()) {
|
|
5614
|
+
inDegree.set(dependentId, Math.max(0, (inDegree.get(dependentId) ?? 1) - 1));
|
|
5203
5615
|
}
|
|
5616
|
+
}));
|
|
5617
|
+
if (Array.from(inDegree.values()).some((deg) => deg === 0) && resultMap.size < totalSections) {
|
|
5618
|
+
await executeWave();
|
|
5204
5619
|
}
|
|
5205
|
-
}
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
return t2Results;
|
|
5620
|
+
};
|
|
5621
|
+
await executeWave();
|
|
5622
|
+
return sections.map((s) => resultMap.get(s.sectionId)).filter(Boolean);
|
|
5209
5623
|
}
|
|
5210
5624
|
async compileFinalOutput(originalPrompt, plan, t2Results) {
|
|
5211
5625
|
const completedSections = t2Results.filter((r) => r.status !== "FAILED");
|
|
@@ -5655,13 +6069,47 @@ var GitHubTool = class extends BaseTool {
|
|
|
5655
6069
|
}
|
|
5656
6070
|
async execute(input, _options) {
|
|
5657
6071
|
const platform = input["platform"] ?? "github";
|
|
5658
|
-
const token = input["token"] ?? process.env["GITHUB_TOKEN"] ?? process.env["GITLAB_TOKEN"] ?? "";
|
|
5659
6072
|
const operation = input["operation"];
|
|
5660
6073
|
const repo = input["repo"];
|
|
5661
|
-
|
|
5662
|
-
|
|
6074
|
+
let token = input["token"];
|
|
6075
|
+
if (!token) {
|
|
6076
|
+
if (platform === "github") {
|
|
6077
|
+
token = process.env["GITHUB_TOKEN"];
|
|
6078
|
+
} else {
|
|
6079
|
+
token = process.env["GITLAB_TOKEN"];
|
|
6080
|
+
}
|
|
6081
|
+
}
|
|
6082
|
+
if (!token) {
|
|
6083
|
+
const envName = platform === "github" ? "GITHUB_TOKEN" : "GITLAB_TOKEN";
|
|
6084
|
+
return `Error: No ${platform} token provided. Set the ${envName} environment variable or pass a "token" field in the input.`;
|
|
6085
|
+
}
|
|
6086
|
+
try {
|
|
6087
|
+
if (platform === "github") {
|
|
6088
|
+
return await this.executeGitHub(operation, repo, token, input);
|
|
6089
|
+
}
|
|
6090
|
+
return await this.executeGitLab(operation, repo, token, input);
|
|
6091
|
+
} catch (err) {
|
|
6092
|
+
const axiosErr = err;
|
|
6093
|
+
if (axiosErr?.response?.status) {
|
|
6094
|
+
const status = axiosErr.response.status;
|
|
6095
|
+
const msg = axiosErr.response.data?.message ?? "";
|
|
6096
|
+
switch (status) {
|
|
6097
|
+
case 401:
|
|
6098
|
+
return `Authentication failed: Your ${platform} token is invalid or expired. Check your token and try again.`;
|
|
6099
|
+
case 403:
|
|
6100
|
+
return `Permission denied: Your ${platform} token lacks the required scopes for this operation. Needed: repo or workflow.`;
|
|
6101
|
+
case 404:
|
|
6102
|
+
return `Not found: Repository "${repo}" does not exist, or your token cannot access it.`;
|
|
6103
|
+
case 422:
|
|
6104
|
+
return `Validation error from ${platform}: ${msg || "Check your input parameters (branch names, base/head refs, etc.)."}`;
|
|
6105
|
+
case 429:
|
|
6106
|
+
return `Rate limited by ${platform}. Please wait a moment before trying again.`;
|
|
6107
|
+
default:
|
|
6108
|
+
return `${platform} API error (${status}): ${msg || (axiosErr.message ?? "Unknown error")}`;
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
return `${platform} request failed: ${axiosErr.message ?? String(err)}`;
|
|
5663
6112
|
}
|
|
5664
|
-
return this.executeGitLab(operation, repo, token, input);
|
|
5665
6113
|
}
|
|
5666
6114
|
async executeGitHub(operation, repo, token, input) {
|
|
5667
6115
|
const headers = {
|
|
@@ -5748,6 +6196,7 @@ ${response.data.description}`;
|
|
|
5748
6196
|
};
|
|
5749
6197
|
|
|
5750
6198
|
// src/tools/browser.ts
|
|
6199
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
|
|
5751
6200
|
var BrowserTool = class extends BaseTool {
|
|
5752
6201
|
name = "browser";
|
|
5753
6202
|
description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
|
|
@@ -5756,7 +6205,7 @@ var BrowserTool = class extends BaseTool {
|
|
|
5756
6205
|
properties: {
|
|
5757
6206
|
action: {
|
|
5758
6207
|
type: "string",
|
|
5759
|
-
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait"]
|
|
6208
|
+
enum: ["navigate", "click", "fill", "screenshot", "evaluate", "extract_text", "wait", "close"]
|
|
5760
6209
|
},
|
|
5761
6210
|
url: { type: "string", description: "URL to navigate to" },
|
|
5762
6211
|
selector: { type: "string", description: "CSS selector for click/fill" },
|
|
@@ -5776,53 +6225,86 @@ var BrowserTool = class extends BaseTool {
|
|
|
5776
6225
|
try {
|
|
5777
6226
|
playwright = await import('playwright');
|
|
5778
6227
|
} catch {
|
|
5779
|
-
|
|
6228
|
+
return "Error: Playwright is not installed. Run: npm install playwright && npx playwright install chromium";
|
|
5780
6229
|
}
|
|
5781
|
-
if (!this.browser) {
|
|
5782
|
-
const pw = playwright;
|
|
5783
|
-
this.browser = await pw.chromium.launch({ headless: true });
|
|
5784
|
-
const b = this.browser;
|
|
5785
|
-
this.page = await b.newPage();
|
|
5786
|
-
}
|
|
5787
|
-
const page = this.page;
|
|
5788
6230
|
const action = input["action"];
|
|
5789
6231
|
const timeout = input["timeout"] ?? 1e4;
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
case "evaluate": {
|
|
5808
|
-
const result = await page.evaluate(input["script"]);
|
|
5809
|
-
return JSON.stringify(result);
|
|
6232
|
+
if (action === "close") {
|
|
6233
|
+
await this.close();
|
|
6234
|
+
return "Browser closed.";
|
|
6235
|
+
}
|
|
6236
|
+
if (!this.browser || !this.page) {
|
|
6237
|
+
await this.close();
|
|
6238
|
+
const launchPromise = playwright.chromium.launch({ headless: true });
|
|
6239
|
+
const timeoutPromise = new Promise(
|
|
6240
|
+
(_, reject) => setTimeout(() => reject(new Error(`Browser launch timed out after ${BROWSER_LAUNCH_TIMEOUT_MS}ms. Is Chromium installed? Run: npx playwright install chromium`)), BROWSER_LAUNCH_TIMEOUT_MS)
|
|
6241
|
+
);
|
|
6242
|
+
try {
|
|
6243
|
+
this.browser = await Promise.race([launchPromise, timeoutPromise]);
|
|
6244
|
+
this.page = await this.browser.newPage();
|
|
6245
|
+
} catch (err) {
|
|
6246
|
+
this.browser = null;
|
|
6247
|
+
this.page = null;
|
|
6248
|
+
return `Browser launch failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
5810
6249
|
}
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
6250
|
+
}
|
|
6251
|
+
const page = this.page;
|
|
6252
|
+
try {
|
|
6253
|
+
switch (action) {
|
|
6254
|
+
case "navigate": {
|
|
6255
|
+
await page.goto(input["url"], { timeout });
|
|
6256
|
+
const title = await page.title();
|
|
6257
|
+
return `Navigated to ${input["url"]} (title: "${title}")`;
|
|
6258
|
+
}
|
|
6259
|
+
case "click": {
|
|
6260
|
+
await page.click(input["selector"], { timeout });
|
|
6261
|
+
return `Clicked ${input["selector"]}`;
|
|
6262
|
+
}
|
|
6263
|
+
case "fill": {
|
|
6264
|
+
await page.fill(input["selector"], input["value"]);
|
|
6265
|
+
return `Filled ${input["selector"]} with value`;
|
|
6266
|
+
}
|
|
6267
|
+
case "screenshot": {
|
|
6268
|
+
const buf = await page.screenshot({ type: "png" });
|
|
6269
|
+
return `data:image/png;base64,${buf.toString("base64")}`;
|
|
6270
|
+
}
|
|
6271
|
+
case "evaluate": {
|
|
6272
|
+
const result = await page.evaluate(input["script"]);
|
|
6273
|
+
return JSON.stringify(result);
|
|
6274
|
+
}
|
|
6275
|
+
case "extract_text": {
|
|
6276
|
+
const text = await page.locator("body").innerText();
|
|
6277
|
+
return text.slice(0, 1e4);
|
|
6278
|
+
}
|
|
6279
|
+
case "wait": {
|
|
6280
|
+
await page.waitForTimeout(timeout);
|
|
6281
|
+
return `Waited ${timeout}ms`;
|
|
6282
|
+
}
|
|
6283
|
+
default:
|
|
6284
|
+
return `Unknown browser action: ${action}. Supported: navigate, click, fill, screenshot, evaluate, extract_text, wait, close`;
|
|
5814
6285
|
}
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
6286
|
+
} catch (err) {
|
|
6287
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6288
|
+
if (/Target closed|Page crashed|Navigation failed/i.test(errMsg)) {
|
|
6289
|
+
await this.close();
|
|
6290
|
+
return `Browser error (page reset): ${errMsg}`;
|
|
5818
6291
|
}
|
|
5819
|
-
|
|
5820
|
-
throw new Error(`Unknown browser action: ${action}`);
|
|
6292
|
+
return `Browser action "${action}" failed: ${errMsg}`;
|
|
5821
6293
|
}
|
|
5822
6294
|
}
|
|
5823
6295
|
async close() {
|
|
5824
|
-
|
|
5825
|
-
|
|
6296
|
+
try {
|
|
6297
|
+
if (this.page) {
|
|
6298
|
+
await this.page.close().catch(() => {
|
|
6299
|
+
});
|
|
6300
|
+
this.page = null;
|
|
6301
|
+
}
|
|
6302
|
+
if (this.browser) {
|
|
6303
|
+
await this.browser.close().catch(() => {
|
|
6304
|
+
});
|
|
6305
|
+
this.browser = null;
|
|
6306
|
+
}
|
|
6307
|
+
} catch {
|
|
5826
6308
|
this.browser = null;
|
|
5827
6309
|
this.page = null;
|
|
5828
6310
|
}
|
|
@@ -5919,6 +6401,19 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
5919
6401
|
});
|
|
5920
6402
|
}
|
|
5921
6403
|
};
|
|
6404
|
+
function detectCommand(candidates2) {
|
|
6405
|
+
for (const cmd of candidates2) {
|
|
6406
|
+
try {
|
|
6407
|
+
const which = process.platform === "win32" ? "where" : "which";
|
|
6408
|
+
execSync(`${which} ${cmd}`, { stdio: "ignore" });
|
|
6409
|
+
return cmd;
|
|
6410
|
+
} catch {
|
|
6411
|
+
}
|
|
6412
|
+
}
|
|
6413
|
+
return null;
|
|
6414
|
+
}
|
|
6415
|
+
var PYTHON_CMD = detectCommand(["python3", "python"]);
|
|
6416
|
+
var NODE_CMD = detectCommand(["node"]);
|
|
5922
6417
|
var CodeInterpreterTool = class extends BaseTool {
|
|
5923
6418
|
name = "run_code";
|
|
5924
6419
|
description = "Execute a Python or Node.js script to perform complex tasks (data processing, file conversion, etc.). The script is automatically cleaned up after execution.";
|
|
@@ -5934,10 +6429,30 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5934
6429
|
isDangerous() {
|
|
5935
6430
|
return true;
|
|
5936
6431
|
}
|
|
5937
|
-
async execute(input,
|
|
6432
|
+
async execute(input, _options) {
|
|
5938
6433
|
const language = input["language"];
|
|
5939
6434
|
const code = input["code"];
|
|
5940
6435
|
const args = input["args"] ?? [];
|
|
6436
|
+
let cmdPrefix;
|
|
6437
|
+
if (language === "python") {
|
|
6438
|
+
if (!PYTHON_CMD) {
|
|
6439
|
+
return [
|
|
6440
|
+
"Error: Python interpreter not found.",
|
|
6441
|
+
"Please install Python and ensure it is in your PATH.",
|
|
6442
|
+
"Tried: python3, python"
|
|
6443
|
+
].join("\n");
|
|
6444
|
+
}
|
|
6445
|
+
cmdPrefix = PYTHON_CMD;
|
|
6446
|
+
} else {
|
|
6447
|
+
if (!NODE_CMD) {
|
|
6448
|
+
return [
|
|
6449
|
+
"Error: Node.js interpreter not found.",
|
|
6450
|
+
"Please install Node.js and ensure it is in your PATH.",
|
|
6451
|
+
"Tried: node"
|
|
6452
|
+
].join("\n");
|
|
6453
|
+
}
|
|
6454
|
+
cmdPrefix = NODE_CMD;
|
|
6455
|
+
}
|
|
5941
6456
|
const tmpDir = path17.join(process.cwd(), ".cascade", "tmp");
|
|
5942
6457
|
if (!fs14.existsSync(tmpDir)) {
|
|
5943
6458
|
fs14.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -5946,8 +6461,9 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5946
6461
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
5947
6462
|
const filePath = path17.join(tmpDir, fileName);
|
|
5948
6463
|
fs14.writeFileSync(filePath, code, "utf-8");
|
|
5949
|
-
const
|
|
5950
|
-
const
|
|
6464
|
+
const quotedPath = `"${filePath}"`;
|
|
6465
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
6466
|
+
const fullCmd = `${cmdPrefix} ${quotedPath}${quotedArgs ? " " + quotedArgs : ""}`;
|
|
5951
6467
|
return new Promise((resolve) => {
|
|
5952
6468
|
const startMs = Date.now();
|
|
5953
6469
|
exec(fullCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
@@ -5960,10 +6476,17 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
5960
6476
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
5961
6477
|
}
|
|
5962
6478
|
if (error) {
|
|
5963
|
-
|
|
6479
|
+
const timedOut = error.killed && duration >= 3e4;
|
|
6480
|
+
if (timedOut) {
|
|
6481
|
+
resolve(`Execution timed out after 30s. Consider breaking the task into smaller pieces.
|
|
6482
|
+
Partial stdout: ${stdout}
|
|
6483
|
+
Stderr: ${stderr}`);
|
|
6484
|
+
} else {
|
|
6485
|
+
resolve(`Execution failed (${duration}ms):
|
|
5964
6486
|
Error: ${error.message}
|
|
5965
6487
|
Stderr: ${stderr}
|
|
5966
6488
|
Stdout: ${stdout}`);
|
|
6489
|
+
}
|
|
5967
6490
|
} else {
|
|
5968
6491
|
resolve(`Execution successful (${duration}ms):
|
|
5969
6492
|
Stdout: ${stdout}
|
|
@@ -6028,6 +6551,186 @@ ${formatted}`;
|
|
|
6028
6551
|
}
|
|
6029
6552
|
};
|
|
6030
6553
|
|
|
6554
|
+
// src/tools/web-search.ts
|
|
6555
|
+
async function searchSearXNG(query, baseUrl, maxResults) {
|
|
6556
|
+
const url = new URL("/search", baseUrl);
|
|
6557
|
+
url.searchParams.set("q", query);
|
|
6558
|
+
url.searchParams.set("format", "json");
|
|
6559
|
+
url.searchParams.set("categories", "general");
|
|
6560
|
+
url.searchParams.set("engines", "google,bing,duckduckgo");
|
|
6561
|
+
const resp = await fetch(url.toString(), {
|
|
6562
|
+
headers: { "User-Agent": "Cascade-AI/1.0 WebSearchTool" },
|
|
6563
|
+
signal: AbortSignal.timeout(1e4)
|
|
6564
|
+
});
|
|
6565
|
+
if (!resp.ok) {
|
|
6566
|
+
throw new Error(`SearXNG returned HTTP ${resp.status}`);
|
|
6567
|
+
}
|
|
6568
|
+
const data = await resp.json();
|
|
6569
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6570
|
+
title: r.title ?? "",
|
|
6571
|
+
url: r.url ?? "",
|
|
6572
|
+
snippet: r.content ?? "",
|
|
6573
|
+
engine: `searxng(${r.engine ?? "unknown"})`
|
|
6574
|
+
}));
|
|
6575
|
+
}
|
|
6576
|
+
async function searchBrave(query, apiKey, maxResults) {
|
|
6577
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}&safesearch=off`;
|
|
6578
|
+
const resp = await fetch(url, {
|
|
6579
|
+
headers: {
|
|
6580
|
+
"Accept": "application/json",
|
|
6581
|
+
"Accept-Encoding": "gzip",
|
|
6582
|
+
"X-Subscription-Token": apiKey
|
|
6583
|
+
},
|
|
6584
|
+
signal: AbortSignal.timeout(1e4)
|
|
6585
|
+
});
|
|
6586
|
+
if (!resp.ok) {
|
|
6587
|
+
throw new Error(`Brave Search returned HTTP ${resp.status}`);
|
|
6588
|
+
}
|
|
6589
|
+
const data = await resp.json();
|
|
6590
|
+
return (data.web?.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6591
|
+
title: r.title ?? "",
|
|
6592
|
+
url: r.url ?? "",
|
|
6593
|
+
snippet: r.description ?? "",
|
|
6594
|
+
engine: "brave"
|
|
6595
|
+
}));
|
|
6596
|
+
}
|
|
6597
|
+
async function searchTavily(query, apiKey, maxResults) {
|
|
6598
|
+
const resp = await fetch("https://api.tavily.com/search", {
|
|
6599
|
+
method: "POST",
|
|
6600
|
+
headers: {
|
|
6601
|
+
"Content-Type": "application/json",
|
|
6602
|
+
"Authorization": `Bearer ${apiKey}`
|
|
6603
|
+
},
|
|
6604
|
+
body: JSON.stringify({
|
|
6605
|
+
query,
|
|
6606
|
+
max_results: maxResults,
|
|
6607
|
+
search_depth: "basic",
|
|
6608
|
+
include_answer: false,
|
|
6609
|
+
include_raw_content: false
|
|
6610
|
+
}),
|
|
6611
|
+
signal: AbortSignal.timeout(15e3)
|
|
6612
|
+
});
|
|
6613
|
+
if (!resp.ok) {
|
|
6614
|
+
throw new Error(`Tavily returned HTTP ${resp.status}`);
|
|
6615
|
+
}
|
|
6616
|
+
const data = await resp.json();
|
|
6617
|
+
return (data.results ?? []).filter((r) => r.url && r.title).slice(0, maxResults).map((r) => ({
|
|
6618
|
+
title: r.title ?? "",
|
|
6619
|
+
url: r.url ?? "",
|
|
6620
|
+
snippet: r.content ?? "",
|
|
6621
|
+
engine: "tavily"
|
|
6622
|
+
}));
|
|
6623
|
+
}
|
|
6624
|
+
async function searchDuckDuckGoLite(query, maxResults) {
|
|
6625
|
+
const resp = await fetch(`https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`, {
|
|
6626
|
+
headers: { "User-Agent": "Mozilla/5.0 (compatible; Cascade-AI/1.0)" },
|
|
6627
|
+
signal: AbortSignal.timeout(1e4)
|
|
6628
|
+
});
|
|
6629
|
+
if (!resp.ok) throw new Error(`DuckDuckGo Lite returned HTTP ${resp.status}`);
|
|
6630
|
+
const html = await resp.text();
|
|
6631
|
+
const linkPattern = /<a[^>]+class="result-link"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
|
|
6632
|
+
const snippetPattern = /<td[^>]+class="result-snippet"[^>]*>([\s\S]*?)<\/td>/g;
|
|
6633
|
+
const links = [];
|
|
6634
|
+
const snippets = [];
|
|
6635
|
+
let m;
|
|
6636
|
+
while ((m = linkPattern.exec(html)) !== null) {
|
|
6637
|
+
links.push({ url: m[1], title: m[2].trim() });
|
|
6638
|
+
}
|
|
6639
|
+
while ((m = snippetPattern.exec(html)) !== null) {
|
|
6640
|
+
snippets.push(m[1].replace(/<[^>]+>/g, "").trim());
|
|
6641
|
+
}
|
|
6642
|
+
return links.slice(0, maxResults).map((link, i) => ({
|
|
6643
|
+
title: link.title,
|
|
6644
|
+
url: link.url,
|
|
6645
|
+
snippet: snippets[i] ?? "",
|
|
6646
|
+
engine: "duckduckgo-lite"
|
|
6647
|
+
}));
|
|
6648
|
+
}
|
|
6649
|
+
var WebSearchTool = class extends BaseTool {
|
|
6650
|
+
name = "web_search";
|
|
6651
|
+
description = "Search the web for current information, news, documentation, or any topic. Returns a list of relevant results with titles, URLs, and snippets.";
|
|
6652
|
+
inputSchema = {
|
|
6653
|
+
type: "object",
|
|
6654
|
+
properties: {
|
|
6655
|
+
query: { type: "string", description: "The search query" },
|
|
6656
|
+
maxResults: { type: "number", description: "Number of results to return (default: 5, max: 10)" }
|
|
6657
|
+
},
|
|
6658
|
+
required: ["query"]
|
|
6659
|
+
};
|
|
6660
|
+
config;
|
|
6661
|
+
constructor(config = {}) {
|
|
6662
|
+
super();
|
|
6663
|
+
this.config = {
|
|
6664
|
+
searxngUrl: config.searxngUrl ?? process.env["SEARXNG_URL"],
|
|
6665
|
+
braveApiKey: config.braveApiKey ?? process.env["BRAVE_SEARCH_API_KEY"],
|
|
6666
|
+
tavilyApiKey: config.tavilyApiKey ?? process.env["TAVILY_API_KEY"],
|
|
6667
|
+
maxResults: config.maxResults ?? 5
|
|
6668
|
+
};
|
|
6669
|
+
}
|
|
6670
|
+
async execute(input, _options) {
|
|
6671
|
+
const query = input["query"];
|
|
6672
|
+
if (!query?.trim()) return "Error: query is required and must be non-empty.";
|
|
6673
|
+
const maxResults = Math.min(
|
|
6674
|
+
input["maxResults"] ?? this.config.maxResults ?? 5,
|
|
6675
|
+
10
|
|
6676
|
+
);
|
|
6677
|
+
const errors = [];
|
|
6678
|
+
let results = [];
|
|
6679
|
+
if (this.config.searxngUrl) {
|
|
6680
|
+
try {
|
|
6681
|
+
results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
|
|
6682
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6683
|
+
errors.push("SearXNG: returned 0 results");
|
|
6684
|
+
} catch (err) {
|
|
6685
|
+
errors.push(`SearXNG: ${err instanceof Error ? err.message : String(err)}`);
|
|
6686
|
+
}
|
|
6687
|
+
}
|
|
6688
|
+
if (this.config.braveApiKey) {
|
|
6689
|
+
try {
|
|
6690
|
+
results = await searchBrave(query, this.config.braveApiKey, maxResults);
|
|
6691
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6692
|
+
errors.push("Brave: returned 0 results");
|
|
6693
|
+
} catch (err) {
|
|
6694
|
+
errors.push(`Brave: ${err instanceof Error ? err.message : String(err)}`);
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6697
|
+
if (this.config.tavilyApiKey) {
|
|
6698
|
+
try {
|
|
6699
|
+
results = await searchTavily(query, this.config.tavilyApiKey, maxResults);
|
|
6700
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6701
|
+
errors.push("Tavily: returned 0 results");
|
|
6702
|
+
} catch (err) {
|
|
6703
|
+
errors.push(`Tavily: ${err instanceof Error ? err.message : String(err)}`);
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6706
|
+
try {
|
|
6707
|
+
results = await searchDuckDuckGoLite(query, maxResults);
|
|
6708
|
+
if (results.length > 0) return this.formatResults(query, results);
|
|
6709
|
+
errors.push("DuckDuckGo Lite: returned 0 results");
|
|
6710
|
+
} catch (err) {
|
|
6711
|
+
errors.push(`DuckDuckGo Lite: ${err instanceof Error ? err.message : String(err)}`);
|
|
6712
|
+
}
|
|
6713
|
+
const configHint = !this.config.searxngUrl && !this.config.braveApiKey && !this.config.tavilyApiKey ? "\nTip: Configure a search backend for better results:\n \u2022 Self-hosted: set SEARXNG_URL in your environment\n \u2022 Brave Search API: set BRAVE_SEARCH_API_KEY\n \u2022 Tavily API: set TAVILY_API_KEY" : "";
|
|
6714
|
+
return [
|
|
6715
|
+
`Web search for "${query}" failed across all backends:`,
|
|
6716
|
+
...errors.map((e) => ` \u2022 ${e}`),
|
|
6717
|
+
configHint
|
|
6718
|
+
].join("\n");
|
|
6719
|
+
}
|
|
6720
|
+
formatResults(query, results) {
|
|
6721
|
+
const lines = [`Web search results for: "${query}"`, ""];
|
|
6722
|
+
for (let i = 0; i < results.length; i++) {
|
|
6723
|
+
const r = results[i];
|
|
6724
|
+
lines.push(`[${i + 1}] ${r.title}`);
|
|
6725
|
+
lines.push(` URL: ${r.url}`);
|
|
6726
|
+
if (r.snippet) lines.push(` ${r.snippet.slice(0, 300)}`);
|
|
6727
|
+
if (r.engine) lines.push(` Source: ${r.engine}`);
|
|
6728
|
+
lines.push("");
|
|
6729
|
+
}
|
|
6730
|
+
return lines.join("\n");
|
|
6731
|
+
}
|
|
6732
|
+
};
|
|
6733
|
+
|
|
6031
6734
|
// src/tools/mcp.ts
|
|
6032
6735
|
var McpToolWrapper = class extends BaseTool {
|
|
6033
6736
|
name;
|
|
@@ -6149,7 +6852,8 @@ var ToolRegistry = class {
|
|
|
6149
6852
|
new ImageAnalyzeTool(),
|
|
6150
6853
|
new PDFCreateTool(),
|
|
6151
6854
|
new CodeInterpreterTool(),
|
|
6152
|
-
new PeerCommunicationTool()
|
|
6855
|
+
new PeerCommunicationTool(),
|
|
6856
|
+
new WebSearchTool(this.config.webSearch)
|
|
6153
6857
|
];
|
|
6154
6858
|
for (const tool of tools) {
|
|
6155
6859
|
tool.setWorkspaceRoot(this.workspaceRoot);
|
|
@@ -6173,8 +6877,23 @@ var ToolRegistry = class {
|
|
|
6173
6877
|
return this.ignoreMatcher.ignores(posixRel);
|
|
6174
6878
|
}
|
|
6175
6879
|
};
|
|
6176
|
-
var McpClient = class {
|
|
6880
|
+
var McpClient = class _McpClient {
|
|
6881
|
+
static activeProcessPids = /* @__PURE__ */ new Set();
|
|
6882
|
+
/**
|
|
6883
|
+
* Forcefully kills all known MCP child processes.
|
|
6884
|
+
* Call this from global process exit handlers to prevent zombie processes.
|
|
6885
|
+
*/
|
|
6886
|
+
static killAllProcesses() {
|
|
6887
|
+
for (const pid of _McpClient.activeProcessPids) {
|
|
6888
|
+
try {
|
|
6889
|
+
process.kill(pid, "SIGKILL");
|
|
6890
|
+
} catch {
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6893
|
+
_McpClient.activeProcessPids.clear();
|
|
6894
|
+
}
|
|
6177
6895
|
clients = /* @__PURE__ */ new Map();
|
|
6896
|
+
transports = /* @__PURE__ */ new Map();
|
|
6178
6897
|
tools = /* @__PURE__ */ new Map();
|
|
6179
6898
|
trustedServers;
|
|
6180
6899
|
approvalCallback;
|
|
@@ -6203,6 +6922,8 @@ var McpClient = class {
|
|
|
6203
6922
|
);
|
|
6204
6923
|
await client.connect(transport);
|
|
6205
6924
|
this.clients.set(server.name, client);
|
|
6925
|
+
this.transports.set(server.name, transport);
|
|
6926
|
+
if (transport.pid) _McpClient.activeProcessPids.add(transport.pid);
|
|
6206
6927
|
const toolsResult = await client.listTools();
|
|
6207
6928
|
for (const tool of toolsResult.tools) {
|
|
6208
6929
|
for (const existing of this.tools.values()) {
|
|
@@ -6224,8 +6945,11 @@ var McpClient = class {
|
|
|
6224
6945
|
async disconnect(serverName) {
|
|
6225
6946
|
const client = this.clients.get(serverName);
|
|
6226
6947
|
if (client) {
|
|
6948
|
+
const transport = this.transports.get(serverName);
|
|
6949
|
+
if (transport?.pid) _McpClient.activeProcessPids.delete(transport.pid);
|
|
6227
6950
|
await client.close();
|
|
6228
6951
|
this.clients.delete(serverName);
|
|
6952
|
+
this.transports.delete(serverName);
|
|
6229
6953
|
for (const key of this.tools.keys()) {
|
|
6230
6954
|
if (key.startsWith(`${serverName}::`)) this.tools.delete(key);
|
|
6231
6955
|
}
|
|
@@ -6253,6 +6977,13 @@ var McpClient = class {
|
|
|
6253
6977
|
getConnectedServers() {
|
|
6254
6978
|
return Array.from(this.clients.keys());
|
|
6255
6979
|
}
|
|
6980
|
+
getActivePids() {
|
|
6981
|
+
const pids = [];
|
|
6982
|
+
for (const transport of this.transports.values()) {
|
|
6983
|
+
if (transport.pid) pids.push(transport.pid);
|
|
6984
|
+
}
|
|
6985
|
+
return pids;
|
|
6986
|
+
}
|
|
6256
6987
|
isConnected(serverName) {
|
|
6257
6988
|
return this.clients.has(serverName);
|
|
6258
6989
|
}
|
|
@@ -6821,12 +7552,25 @@ var Cascade = class extends EventEmitter {
|
|
|
6821
7552
|
looksLikeSimpleArtifactTask(prompt) {
|
|
6822
7553
|
return /create .*\.(txt|md|json|csv)\b/i.test(prompt) && !/(research|compare|thorough|pdf|report|analy[sz]e|architecture|multi-agent)/i.test(prompt);
|
|
6823
7554
|
}
|
|
6824
|
-
async determineComplexity(prompt, conversationHistory = []) {
|
|
7555
|
+
async determineComplexity(prompt, workspacePath, conversationHistory = []) {
|
|
6825
7556
|
if (this.looksLikeSimpleArtifactTask(prompt)) {
|
|
6826
7557
|
return "Simple";
|
|
6827
7558
|
}
|
|
7559
|
+
let workspaceContext = "";
|
|
7560
|
+
try {
|
|
7561
|
+
const files = await glob("**/*.*", {
|
|
7562
|
+
cwd: workspacePath,
|
|
7563
|
+
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
7564
|
+
nodir: true
|
|
7565
|
+
});
|
|
7566
|
+
workspaceContext = `Workspace Scout: Found ~${files.length} source files in the project.`;
|
|
7567
|
+
} catch {
|
|
7568
|
+
workspaceContext = "Workspace Scout: Could not scan workspace.";
|
|
7569
|
+
}
|
|
6828
7570
|
const sysPrompt = `You are a routing classifier for a hierarchical AI system. Determine task complexity using BOTH the latest user message and the recent conversation context.
|
|
6829
7571
|
|
|
7572
|
+
${workspaceContext}
|
|
7573
|
+
|
|
6830
7574
|
Classification:
|
|
6831
7575
|
- "Simple": basic conversation, direct single-step work, or small troubleshooting
|
|
6832
7576
|
- "Moderate": requires a few steps, some tool use, or a manager coordinating workers
|
|
@@ -6901,7 +7645,7 @@ ${prompt}` : prompt;
|
|
|
6901
7645
|
}
|
|
6902
7646
|
escalator.resolveUserDecision(req.id, approved, always);
|
|
6903
7647
|
});
|
|
6904
|
-
const complexity = await this.determineComplexity(options.prompt, options.conversationHistory);
|
|
7648
|
+
const complexity = await this.determineComplexity(options.prompt, options.workspacePath || process.cwd(), options.conversationHistory);
|
|
6905
7649
|
this.telemetry.capture("cascade:session_start", {
|
|
6906
7650
|
complexity,
|
|
6907
7651
|
providerCount: this.config.providers.length,
|
|
@@ -6981,7 +7725,7 @@ ${prompt}` : prompt;
|
|
|
6981
7725
|
peerT3Ids: [],
|
|
6982
7726
|
parentT2: "root"
|
|
6983
7727
|
};
|
|
6984
|
-
const t3Result = await t3.execute(assignment, taskId);
|
|
7728
|
+
const t3Result = await t3.execute(assignment, taskId, options.signal);
|
|
6985
7729
|
finalOutput = typeof t3Result.output === "string" ? t3Result.output : JSON.stringify(t3Result.output);
|
|
6986
7730
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
6987
7731
|
} else if (complexity === "Moderate") {
|
|
@@ -7004,7 +7748,7 @@ ${prompt}` : prompt;
|
|
|
7004
7748
|
constraints: [],
|
|
7005
7749
|
t3Subtasks: []
|
|
7006
7750
|
};
|
|
7007
|
-
const t2Result = await t2.execute(assignment, taskId);
|
|
7751
|
+
const t2Result = await t2.execute(assignment, taskId, options.signal);
|
|
7008
7752
|
this.emit("tier:status", { tierId: "t2-root", status: "COMPLETED", role: "T2" });
|
|
7009
7753
|
t2Results = [t2Result];
|
|
7010
7754
|
const completed = t2Result.t3Results.filter((r) => r.status === "COMPLETED");
|
|
@@ -7026,13 +7770,22 @@ ${prompt}` : prompt;
|
|
|
7026
7770
|
if (toolCreator) t1.setToolCreator(toolCreator);
|
|
7027
7771
|
bindTierEvents(t1);
|
|
7028
7772
|
t1.on("plan", (e) => this.emit("plan", e));
|
|
7029
|
-
const result = await t1.execute(options.prompt, options.images);
|
|
7773
|
+
const result = await t1.execute(options.prompt, options.images, void 0, options.signal);
|
|
7030
7774
|
finalOutput = result.output;
|
|
7031
7775
|
t2Results = result.t2Results;
|
|
7032
7776
|
}
|
|
7033
7777
|
} catch (err) {
|
|
7034
|
-
|
|
7035
|
-
|
|
7778
|
+
if (err instanceof CascadeCancelledError) {
|
|
7779
|
+
this.emit("run:cancelled", {
|
|
7780
|
+
taskId,
|
|
7781
|
+
reason: err.message,
|
|
7782
|
+
partialOutput: finalOutput || ""
|
|
7783
|
+
});
|
|
7784
|
+
runError = null;
|
|
7785
|
+
} else {
|
|
7786
|
+
runError = err;
|
|
7787
|
+
throw err;
|
|
7788
|
+
}
|
|
7036
7789
|
} finally {
|
|
7037
7790
|
try {
|
|
7038
7791
|
escalator.cancelAllPending();
|
|
@@ -7347,11 +8100,6 @@ var SlashCommandRegistry = class {
|
|
|
7347
8100
|
description: "Show active models per tier",
|
|
7348
8101
|
handler: (_args, ctx) => ({ output: ctx.onModelInfo(), handled: true })
|
|
7349
8102
|
});
|
|
7350
|
-
this.register({
|
|
7351
|
-
command: "/models",
|
|
7352
|
-
description: "Browse available models by provider",
|
|
7353
|
-
handler: (_args, ctx) => ({ output: ctx.onModelsInfo(), handled: true })
|
|
7354
|
-
});
|
|
7355
8103
|
this.register({
|
|
7356
8104
|
command: "/providers",
|
|
7357
8105
|
description: "Show configured providers",
|
|
@@ -7767,6 +8515,9 @@ var ModelsDisplay = ({
|
|
|
7767
8515
|
});
|
|
7768
8516
|
const title = step === "PROVIDER" ? "\u25C8 SELECT PROVIDER" : step === "TIER" ? `\u25C8 APPLY ${picked.provider === "auto" ? "AUTO" : String(picked.provider).toUpperCase()} TO WHICH TIER?` : `\u25C8 ${String(picked.provider).toUpperCase()} \u2192 SELECT MODEL FOR ${picked.tier}`;
|
|
7769
8517
|
const breadcrumb = step === "PROVIDER" ? "Step 1 / 3" : step === "TIER" ? `Step 2 / 3 \xB7 provider: ${picked.provider}` : `Step 3 / 3 \xB7 ${picked.provider} \u2192 ${picked.tier}`;
|
|
8518
|
+
const PAGE_SIZE = 8;
|
|
8519
|
+
const viewStart = Math.max(0, Math.min(cursor - Math.floor(PAGE_SIZE / 2), currentItems.length - PAGE_SIZE));
|
|
8520
|
+
const visibleItems = currentItems.slice(viewStart, viewStart + PAGE_SIZE);
|
|
7770
8521
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
7771
8522
|
/* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
|
|
7772
8523
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: title }),
|
|
@@ -7776,16 +8527,29 @@ var ModelsDisplay = ({
|
|
|
7776
8527
|
breadcrumb,
|
|
7777
8528
|
" \xB7 \u2191/\u2193 navigate \xB7 1\u20139 jump"
|
|
7778
8529
|
] }) }),
|
|
7779
|
-
currentItems.length === 0 ? /* @__PURE__ */ jsx(Text, { italic: true, color: "yellow", children: "No items to show." }) : /* @__PURE__ */
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
8530
|
+
currentItems.length === 0 ? /* @__PURE__ */ jsx(Text, { italic: true, color: "yellow", children: "No items to show." }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
8531
|
+
viewStart > 0 && /* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
|
|
8532
|
+
" \u2191 ",
|
|
8533
|
+
viewStart,
|
|
8534
|
+
" more above"
|
|
8535
|
+
] }),
|
|
8536
|
+
visibleItems.map((item, i) => {
|
|
8537
|
+
const globalIdx = viewStart + i;
|
|
8538
|
+
const focused = globalIdx === cursor;
|
|
8539
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
8540
|
+
/* @__PURE__ */ jsx(Text, { color: focused ? "green" : "gray", children: focused ? "\u276F " : " " }),
|
|
8541
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
8542
|
+
/* @__PURE__ */ jsx(Text, { color: focused ? "white" : "gray", bold: focused, children: item.label }),
|
|
8543
|
+
item.sublabel && /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: ` ${item.sublabel}` })
|
|
8544
|
+
] })
|
|
8545
|
+
] }, `${step}-${item.value}-${globalIdx}`);
|
|
8546
|
+
}),
|
|
8547
|
+
viewStart + PAGE_SIZE < currentItems.length && /* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
|
|
8548
|
+
" \u2193 ",
|
|
8549
|
+
currentItems.length - viewStart - PAGE_SIZE,
|
|
8550
|
+
" more below"
|
|
8551
|
+
] })
|
|
8552
|
+
] })
|
|
7789
8553
|
] });
|
|
7790
8554
|
};
|
|
7791
8555
|
function CostTracker({
|
|
@@ -7979,13 +8743,18 @@ function replReducer(state, action) {
|
|
|
7979
8743
|
async function refreshModelCache(store, providers) {
|
|
7980
8744
|
for (const provider of providers) {
|
|
7981
8745
|
try {
|
|
7982
|
-
const
|
|
8746
|
+
const dummyId = provider.type === "azure" ? provider.deploymentName || "azure-model" : "dummy";
|
|
8747
|
+
const dummyModel = { id: dummyId, name: dummyId, provider: provider.type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
7983
8748
|
let instance;
|
|
7984
8749
|
if (provider.type === "openai") instance = new OpenAIProvider(provider, dummyModel);
|
|
7985
8750
|
else if (provider.type === "gemini") instance = new GeminiProvider(provider, dummyModel);
|
|
7986
8751
|
else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
|
|
7987
8752
|
else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
|
|
7988
8753
|
else if (provider.type === "openai-compatible") instance = new OpenAICompatibleProvider(provider, dummyModel);
|
|
8754
|
+
else if (provider.type === "azure") {
|
|
8755
|
+
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
8756
|
+
instance = new AzureOpenAIProvider2(provider, dummyModel);
|
|
8757
|
+
}
|
|
7989
8758
|
if (instance) {
|
|
7990
8759
|
const fetched = await instance.listModels();
|
|
7991
8760
|
for (const m of fetched) store.upsertCachedModel(m);
|
|
@@ -8123,7 +8892,7 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName })
|
|
|
8123
8892
|
};
|
|
8124
8893
|
const store = new MemoryStore(path17.join(workspacePath, CASCADE_DB_FILE));
|
|
8125
8894
|
storeRef.current = store;
|
|
8126
|
-
globalStoreRef.current = new MemoryStore(path17.join(
|
|
8895
|
+
globalStoreRef.current = new MemoryStore(path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE));
|
|
8127
8896
|
const identityRows = store.listIdentities().map((i) => ({ id: i.id, name: i.name, isDefault: i.isDefault }));
|
|
8128
8897
|
setIdentities(identityRows);
|
|
8129
8898
|
let initialIdentityId = config.defaultIdentityId ?? identityRows.find((i) => i.isDefault)?.id ?? identityRows[0]?.id;
|
|
@@ -8608,14 +9377,29 @@ Use /identity <name|id> to switch.`;
|
|
|
8608
9377
|
const isAutoScrollingRef = useRef(true);
|
|
8609
9378
|
const width = stdout?.columns ?? 100;
|
|
8610
9379
|
const height = stdout?.rows ?? 24;
|
|
8611
|
-
const
|
|
9380
|
+
const hasActiveOrFailed2 = (node) => {
|
|
9381
|
+
if (node.status === "ACTIVE" || node.status === "FAILED") return true;
|
|
9382
|
+
return node.children?.some(hasActiveOrFailed2) ?? false;
|
|
9383
|
+
};
|
|
9384
|
+
let agentTreeHeight = 0;
|
|
9385
|
+
if (state.agentTree && hasActiveOrFailed2(state.agentTree)) {
|
|
9386
|
+
agentTreeHeight = 1;
|
|
9387
|
+
const childrenCount = state.agentTree.children?.length ?? 0;
|
|
9388
|
+
agentTreeHeight += Math.min(childrenCount, 6);
|
|
9389
|
+
if (childrenCount > 6) agentTreeHeight += 1;
|
|
9390
|
+
}
|
|
9391
|
+
let timelineHeight = 0;
|
|
9392
|
+
if (state.showDetails && treeNodesRef.current.size > 0) {
|
|
9393
|
+
timelineHeight = 1;
|
|
9394
|
+
timelineHeight += Math.min(3, treeNodesRef.current.size);
|
|
9395
|
+
}
|
|
9396
|
+
const statusHeight = agentTreeHeight + timelineHeight;
|
|
8612
9397
|
const costHeight = state.showCost ? 6 : 0;
|
|
8613
9398
|
const approvalHeight = state.approvalRequest ? 12 : 0;
|
|
8614
|
-
|
|
8615
|
-
const slashHeight =
|
|
9399
|
+
Math.min(SLASH_PAGE_SIZE, slashEntries.length);
|
|
9400
|
+
const slashHeight = isTypingCommand ? SLASH_PAGE_SIZE + 2 : 0;
|
|
8616
9401
|
const chromeHeight = statusHeight + costHeight + approvalHeight + slashHeight + 7;
|
|
8617
|
-
const
|
|
8618
|
-
const availableHeight = Math.max(4, totalCap - chromeHeight);
|
|
9402
|
+
const availableHeight = Math.max(4, height - chromeHeight);
|
|
8619
9403
|
const allLines = formatToLines(
|
|
8620
9404
|
state.isStreaming ? [...state.messages, { id: "stream", role: "assistant", content: state.streamBuffer, timestamp: (/* @__PURE__ */ new Date()).toISOString() }] : state.messages,
|
|
8621
9405
|
width - 4,
|
|
@@ -8996,10 +9780,14 @@ function wizardReducer(state, action) {
|
|
|
8996
9780
|
return { ...state, currentEntryIdx: next };
|
|
8997
9781
|
}
|
|
8998
9782
|
case "ADD_AZURE": {
|
|
9783
|
+
const prevAzure = state.entries.find((e) => e.type === "azure");
|
|
8999
9784
|
const newEntry = {
|
|
9000
9785
|
id: randomUUID(),
|
|
9001
9786
|
type: "azure",
|
|
9002
|
-
label: `Azure deployment ${state.entries.filter((e) => e.type === "azure").length + 1}
|
|
9787
|
+
label: `Azure deployment ${state.entries.filter((e) => e.type === "azure").length + 1}`,
|
|
9788
|
+
baseUrl: prevAzure?.baseUrl,
|
|
9789
|
+
apiKey: prevAzure?.apiKey,
|
|
9790
|
+
apiVersion: prevAzure?.apiVersion
|
|
9003
9791
|
};
|
|
9004
9792
|
return {
|
|
9005
9793
|
...state,
|
|
@@ -9021,8 +9809,21 @@ function wizardReducer(state, action) {
|
|
|
9021
9809
|
addingAnotherCompat: false
|
|
9022
9810
|
};
|
|
9023
9811
|
}
|
|
9812
|
+
case "ADD_OLLAMA": {
|
|
9813
|
+
const newEntry = {
|
|
9814
|
+
id: randomUUID(),
|
|
9815
|
+
type: "ollama",
|
|
9816
|
+
label: `Ollama endpoint ${state.entries.filter((e) => e.type === "ollama").length + 1}`
|
|
9817
|
+
};
|
|
9818
|
+
return {
|
|
9819
|
+
...state,
|
|
9820
|
+
entries: [...state.entries, newEntry],
|
|
9821
|
+
currentEntryIdx: state.entries.length,
|
|
9822
|
+
addingAnotherOllama: false
|
|
9823
|
+
};
|
|
9824
|
+
}
|
|
9024
9825
|
case "SKIP_MORE":
|
|
9025
|
-
return { ...state, addingAnotherAzure: false, addingAnotherCompat: false, step: "FETCH_MODELS", currentEntryIdx: 0 };
|
|
9826
|
+
return { ...state, addingAnotherAzure: false, addingAnotherCompat: false, addingAnotherOllama: false, step: "FETCH_MODELS", currentEntryIdx: 0 };
|
|
9026
9827
|
case "GO_FETCH":
|
|
9027
9828
|
return { ...state, step: "FETCH_MODELS", fetchLog: [], fetchedModels: [] };
|
|
9028
9829
|
case "SET_FETCH_LOG":
|
|
@@ -9055,6 +9856,7 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9055
9856
|
currentEntryIdx: 0,
|
|
9056
9857
|
addingAnotherAzure: false,
|
|
9057
9858
|
addingAnotherCompat: false,
|
|
9859
|
+
addingAnotherOllama: false,
|
|
9058
9860
|
fetchedModels: [],
|
|
9059
9861
|
fetchLog: [],
|
|
9060
9862
|
tierT1: "auto",
|
|
@@ -9108,8 +9910,9 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9108
9910
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
9109
9911
|
} else if (type === "azure") {
|
|
9110
9912
|
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
9111
|
-
const
|
|
9112
|
-
const
|
|
9913
|
+
const actualModelId = deploymentName || `azure-${entry.id}`;
|
|
9914
|
+
const dummyModel = { id: actualModelId, name: actualModelId, provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
9915
|
+
const p = new AzureOpenAIProvider2({ type, apiKey, baseUrl, deploymentName, apiVersion: entry.apiVersion }, dummyModel);
|
|
9113
9916
|
const fetched = await p.listModels();
|
|
9114
9917
|
fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
|
|
9115
9918
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
@@ -9130,7 +9933,8 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9130
9933
|
type: e.type,
|
|
9131
9934
|
...e.apiKey ? { apiKey: e.apiKey } : {},
|
|
9132
9935
|
...e.baseUrl ? { baseUrl: e.baseUrl } : {},
|
|
9133
|
-
...e.deploymentName ? { deploymentName: e.deploymentName } : {}
|
|
9936
|
+
...e.deploymentName ? { deploymentName: e.deploymentName } : {},
|
|
9937
|
+
...e.apiVersion ? { apiVersion: e.apiVersion } : {}
|
|
9134
9938
|
}));
|
|
9135
9939
|
const models = {};
|
|
9136
9940
|
if (state.tierT1 !== "auto") models["t1"] = state.tierT1;
|
|
@@ -9152,25 +9956,28 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9152
9956
|
}, [state.step, state.entries, state.tierT1, state.tierT2, state.tierT3, workspacePath, onComplete, exit]);
|
|
9153
9957
|
useInput((_input, key) => {
|
|
9154
9958
|
if (state.step === "PROVIDER_SELECT") {
|
|
9155
|
-
if (key.upArrow) setProviderCursor((p) =>
|
|
9156
|
-
if (key.downArrow) setProviderCursor((p) =>
|
|
9959
|
+
if (key.upArrow) setProviderCursor((p) => p <= 0 ? providerOrder.length - 1 : p - 1);
|
|
9960
|
+
if (key.downArrow) setProviderCursor((p) => p >= providerOrder.length - 1 ? 0 : p + 1);
|
|
9157
9961
|
if (_input === " ") dispatch({ type: "TOGGLE_PROVIDER", provider: providerOrder[providerCursor] });
|
|
9158
9962
|
if (_input === "a") dispatch({ type: "TOGGLE_ALL" });
|
|
9159
9963
|
if (_input === "i") dispatch({ type: "INVERT_SELECTION" });
|
|
9160
9964
|
if (key.return) {
|
|
9161
|
-
if (state.selectedTypes.size === 0)
|
|
9965
|
+
if (state.selectedTypes.size === 0) {
|
|
9966
|
+
dispatch({ type: "SET_ERROR", error: "Please select at least one provider using <space> before pressing <enter>." });
|
|
9967
|
+
return;
|
|
9968
|
+
}
|
|
9162
9969
|
dispatch({ type: "CONFIRM_PROVIDERS" });
|
|
9163
|
-
|
|
9970
|
+
const firstType = [...state.selectedTypes][0];
|
|
9971
|
+
setFieldStage(firstType === "azure" ? "deploymentName" : firstType === "openai-compatible" ? "label" : firstType === "ollama" ? "baseUrl" : "apiKey");
|
|
9164
9972
|
setFieldBuffer("");
|
|
9165
9973
|
}
|
|
9166
9974
|
}
|
|
9167
9975
|
if (state.step === "TIER_ASSIGN") {
|
|
9168
|
-
if (key.tab || key.
|
|
9976
|
+
if (key.tab || key.rightArrow) {
|
|
9169
9977
|
const order = ["T1", "T2", "T3"];
|
|
9170
9978
|
const idx = order.indexOf(state.tierSelectFocus);
|
|
9171
9979
|
dispatch({ type: "SET_TIER_FOCUS", tier: order[(idx + 1) % 3] });
|
|
9172
9980
|
}
|
|
9173
|
-
if (key.return) dispatch({ type: "GO_SAVE" });
|
|
9174
9981
|
}
|
|
9175
9982
|
});
|
|
9176
9983
|
const currentEntry = state.entries[state.currentEntryIdx];
|
|
@@ -9180,7 +9987,11 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9180
9987
|
if (fieldStage === "deploymentName") {
|
|
9181
9988
|
dispatch({ type: "SET_ENTRY_FIELD", field: "deploymentName", value: val });
|
|
9182
9989
|
setFieldBuffer("");
|
|
9183
|
-
|
|
9990
|
+
if (currentEntry.baseUrl && currentEntry.apiKey && currentEntry.apiVersion) {
|
|
9991
|
+
setFieldStage("askMore");
|
|
9992
|
+
} else {
|
|
9993
|
+
setFieldStage("baseUrl");
|
|
9994
|
+
}
|
|
9184
9995
|
} else if (fieldStage === "baseUrl") {
|
|
9185
9996
|
dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val });
|
|
9186
9997
|
setFieldBuffer("");
|
|
@@ -9188,6 +9999,10 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9188
9999
|
} else if (fieldStage === "apiKey") {
|
|
9189
10000
|
dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
|
|
9190
10001
|
setFieldBuffer("");
|
|
10002
|
+
setFieldStage("apiVersion");
|
|
10003
|
+
} else if (fieldStage === "apiVersion") {
|
|
10004
|
+
dispatch({ type: "SET_ENTRY_FIELD", field: "apiVersion", value: val || "2024-08-01-preview" });
|
|
10005
|
+
setFieldBuffer("");
|
|
9191
10006
|
setFieldStage("askMore");
|
|
9192
10007
|
}
|
|
9193
10008
|
} else if (currentEntry.type === "openai-compatible") {
|
|
@@ -9207,13 +10022,15 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9207
10022
|
} else if (currentEntry.type === "ollama") {
|
|
9208
10023
|
dispatch({ type: "SET_ENTRY_FIELD", field: "baseUrl", value: val || "http://localhost:11434" });
|
|
9209
10024
|
setFieldBuffer("");
|
|
9210
|
-
|
|
9211
|
-
setFieldStage("apiKey");
|
|
10025
|
+
setFieldStage("askMore");
|
|
9212
10026
|
} else {
|
|
9213
10027
|
dispatch({ type: "SET_ENTRY_FIELD", field: "apiKey", value: val });
|
|
9214
10028
|
setFieldBuffer("");
|
|
10029
|
+
const nextEntry = state.entries[state.currentEntryIdx + 1];
|
|
10030
|
+
if (nextEntry) {
|
|
10031
|
+
setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
|
|
10032
|
+
}
|
|
9215
10033
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9216
|
-
setFieldStage("apiKey");
|
|
9217
10034
|
}
|
|
9218
10035
|
}, [currentEntry, fieldStage]);
|
|
9219
10036
|
if (state.step === "PROVIDER_SELECT") {
|
|
@@ -9246,7 +10063,7 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9246
10063
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
9247
10064
|
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
9248
10065
|
/* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "? " }),
|
|
9249
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: isAzure ? "Add another Azure deployment? (y/n)" : "Add another custom endpoint? (y/n)" })
|
|
10066
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: isAzure ? "Add another Azure deployment? (y/n)" : isOllama ? "Add another Ollama endpoint? (y/n)" : "Add another custom endpoint? (y/n)" })
|
|
9250
10067
|
] }),
|
|
9251
10068
|
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
|
|
9252
10069
|
SelectInput,
|
|
@@ -9260,12 +10077,16 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9260
10077
|
onSelect: (item) => {
|
|
9261
10078
|
if (item.value === "yes") {
|
|
9262
10079
|
if (isAzure) dispatch({ type: "ADD_AZURE" });
|
|
10080
|
+
else if (isOllama) dispatch({ type: "ADD_OLLAMA" });
|
|
9263
10081
|
else dispatch({ type: "ADD_COMPAT" });
|
|
9264
|
-
setFieldStage(isAzure ? "deploymentName" : "label");
|
|
10082
|
+
setFieldStage(isAzure ? "deploymentName" : isOllama ? "baseUrl" : "label");
|
|
9265
10083
|
setFieldBuffer("");
|
|
9266
10084
|
} else {
|
|
10085
|
+
const nextEntry = state.entries[state.currentEntryIdx + 1];
|
|
10086
|
+
if (nextEntry) {
|
|
10087
|
+
setFieldStage(nextEntry.type === "azure" ? "deploymentName" : nextEntry.type === "openai-compatible" ? "label" : nextEntry.type === "ollama" ? "baseUrl" : "apiKey");
|
|
10088
|
+
}
|
|
9267
10089
|
dispatch({ type: "NEXT_ENTRY" });
|
|
9268
|
-
setFieldStage("apiKey");
|
|
9269
10090
|
setFieldBuffer("");
|
|
9270
10091
|
}
|
|
9271
10092
|
}
|
|
@@ -9274,7 +10095,7 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9274
10095
|
] });
|
|
9275
10096
|
}
|
|
9276
10097
|
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`;
|
|
9277
|
-
const isMasked = fieldStage === "apiKey";
|
|
10098
|
+
const isMasked = fieldStage === "apiKey" && !isOllama;
|
|
9278
10099
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
9279
10100
|
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
9280
10101
|
/* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "? " }),
|
|
@@ -9346,7 +10167,16 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
9346
10167
|
SelectInput,
|
|
9347
10168
|
{
|
|
9348
10169
|
items: modelOptions,
|
|
9349
|
-
onSelect: (item) =>
|
|
10170
|
+
onSelect: (item) => {
|
|
10171
|
+
dispatch({ type: "SET_TIER", tier, value: item.value });
|
|
10172
|
+
const order = ["T1", "T2", "T3"];
|
|
10173
|
+
const idx = order.indexOf(tier);
|
|
10174
|
+
if (idx < 2) {
|
|
10175
|
+
dispatch({ type: "SET_TIER_FOCUS", tier: order[idx + 1] });
|
|
10176
|
+
} else {
|
|
10177
|
+
dispatch({ type: "GO_SAVE" });
|
|
10178
|
+
}
|
|
10179
|
+
},
|
|
9350
10180
|
indicatorComponent: ({ isSelected }) => /* @__PURE__ */ jsx(Text, { color: "magenta", children: isSelected ? "\u276F " : " " }),
|
|
9351
10181
|
itemComponent: ({ isSelected, label }) => /* @__PURE__ */ jsx(Text, { color: isSelected ? "magenta" : "white", children: label })
|
|
9352
10182
|
}
|
|
@@ -9824,7 +10654,7 @@ var DashboardServer = class {
|
|
|
9824
10654
|
// ── Setup ─────────────────────────────────────
|
|
9825
10655
|
getGlobalStore() {
|
|
9826
10656
|
if (!this.globalStore) {
|
|
9827
|
-
const globalDbPath = path17.join(
|
|
10657
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9828
10658
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
9829
10659
|
}
|
|
9830
10660
|
return this.globalStore;
|
|
@@ -9886,7 +10716,7 @@ var DashboardServer = class {
|
|
|
9886
10716
|
}
|
|
9887
10717
|
watchRuntimeChanges() {
|
|
9888
10718
|
const workspaceDbPath = path17.join(this.workspacePath, CASCADE_DB_FILE);
|
|
9889
|
-
const globalDbPath = path17.join(
|
|
10719
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9890
10720
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
9891
10721
|
for (const watchPath of watchPaths) {
|
|
9892
10722
|
if (!fs14.existsSync(watchPath)) continue;
|
|
@@ -9994,7 +10824,7 @@ var DashboardServer = class {
|
|
|
9994
10824
|
const sessionId = req.params.id;
|
|
9995
10825
|
this.store.deleteSession(sessionId);
|
|
9996
10826
|
this.store.deleteRuntimeSession(sessionId);
|
|
9997
|
-
const globalDbPath = path17.join(
|
|
10827
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
9998
10828
|
const globalStore = new MemoryStore(globalDbPath);
|
|
9999
10829
|
try {
|
|
10000
10830
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -10008,7 +10838,7 @@ var DashboardServer = class {
|
|
|
10008
10838
|
});
|
|
10009
10839
|
this.app.delete("/api/sessions", auth, (req, res) => {
|
|
10010
10840
|
const body = req.body;
|
|
10011
|
-
const globalDbPath = path17.join(
|
|
10841
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10012
10842
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
10013
10843
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10014
10844
|
try {
|
|
@@ -10031,7 +10861,7 @@ var DashboardServer = class {
|
|
|
10031
10861
|
});
|
|
10032
10862
|
this.app.delete("/api/runtime", auth, (_req, res) => {
|
|
10033
10863
|
this.store.deleteAllRuntimeNodes();
|
|
10034
|
-
const globalDbPath = path17.join(
|
|
10864
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10035
10865
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10036
10866
|
try {
|
|
10037
10867
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -10127,7 +10957,7 @@ var DashboardServer = class {
|
|
|
10127
10957
|
this.app.get("/api/runtime", auth, (req, res) => {
|
|
10128
10958
|
const scope = req.query["scope"] ?? "workspace";
|
|
10129
10959
|
if (scope === "global") {
|
|
10130
|
-
const globalDbPath = path17.join(
|
|
10960
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
10131
10961
|
const globalStore = new MemoryStore(globalDbPath);
|
|
10132
10962
|
try {
|
|
10133
10963
|
res.json({
|
|
@@ -10377,7 +11207,15 @@ async function exportCommand(options = {}) {
|
|
|
10377
11207
|
const spin = ora({ text: "Loading sessions\u2026", color: "magenta" }).start();
|
|
10378
11208
|
let store;
|
|
10379
11209
|
try {
|
|
10380
|
-
const
|
|
11210
|
+
const workspacePath = options.workspacePath ?? process.cwd();
|
|
11211
|
+
const workspaceDbPath = path17.join(workspacePath, CASCADE_DB_FILE);
|
|
11212
|
+
const globalDbPath = path17.join(os3.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE);
|
|
11213
|
+
let dbPath = globalDbPath;
|
|
11214
|
+
try {
|
|
11215
|
+
await fs7.access(workspaceDbPath);
|
|
11216
|
+
dbPath = workspaceDbPath;
|
|
11217
|
+
} catch {
|
|
11218
|
+
}
|
|
10381
11219
|
store = new MemoryStore(dbPath);
|
|
10382
11220
|
} catch (err) {
|
|
10383
11221
|
spin.fail(chalk8.red(`Cannot open memory store: ${err instanceof Error ? err.message : String(err)}`));
|
|
@@ -10516,6 +11354,15 @@ async function telemetryCommand(action) {
|
|
|
10516
11354
|
|
|
10517
11355
|
// src/cli/index.ts
|
|
10518
11356
|
dotenv.config();
|
|
11357
|
+
process.on("exit", () => McpClient.killAllProcesses());
|
|
11358
|
+
process.on("SIGINT", () => {
|
|
11359
|
+
McpClient.killAllProcesses();
|
|
11360
|
+
process.exit(0);
|
|
11361
|
+
});
|
|
11362
|
+
process.on("SIGTERM", () => {
|
|
11363
|
+
McpClient.killAllProcesses();
|
|
11364
|
+
process.exit(0);
|
|
11365
|
+
});
|
|
10519
11366
|
var program = new Command();
|
|
10520
11367
|
program.name("cascade").description("Multi-tier AI orchestration CLI").version(CASCADE_VERSION, "-v, --version").option("-p, --prompt <text>", "Run a single prompt non-interactively").option("-t, --theme <name>", "Color theme", DEFAULT_THEME).option("-w, --workspace <path>", "Workspace path", process.cwd()).option("-i, --identity <name>", "Identity name or ID").option("--no-color", "Disable colors").action(async (options) => {
|
|
10521
11368
|
await startRepl(options);
|