@tractorscorch/clank 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/dist/index.js +626 -253
- package/dist/index.js.map +1 -1
- package/dist/web/index.html +17 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -297,6 +297,67 @@ ${summary.trim()}`,
|
|
|
297
297
|
}
|
|
298
298
|
});
|
|
299
299
|
|
|
300
|
+
// src/memory/auto-persist.ts
|
|
301
|
+
import { readFile, writeFile } from "fs/promises";
|
|
302
|
+
import { existsSync } from "fs";
|
|
303
|
+
import { join } from "path";
|
|
304
|
+
function shouldPersist(userMessage) {
|
|
305
|
+
return PERSIST_TRIGGERS.some((pattern) => pattern.test(userMessage));
|
|
306
|
+
}
|
|
307
|
+
function extractMemory(userMessage) {
|
|
308
|
+
const rememberMatch = userMessage.match(/remember\s+(?:that\s+)?(.+)/i);
|
|
309
|
+
if (rememberMatch) return rememberMatch[1].trim();
|
|
310
|
+
for (const pattern of PERSIST_TRIGGERS) {
|
|
311
|
+
if (pattern.test(userMessage)) {
|
|
312
|
+
const firstSentence = userMessage.split(/[.!?\n]/)[0]?.trim();
|
|
313
|
+
return firstSentence && firstSentence.length < 200 ? firstSentence : userMessage.slice(0, 200);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
async function appendToMemory(workspaceDir, entry) {
|
|
319
|
+
const memoryPath = join(workspaceDir, "MEMORY.md");
|
|
320
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
321
|
+
const newEntry = `
|
|
322
|
+
- [${timestamp}] ${entry}`;
|
|
323
|
+
if (existsSync(memoryPath)) {
|
|
324
|
+
const existing = await readFile(memoryPath, "utf-8");
|
|
325
|
+
if (existing.includes(entry)) return;
|
|
326
|
+
const lines = existing.split("\n");
|
|
327
|
+
if (lines.length > 200) return;
|
|
328
|
+
await writeFile(memoryPath, existing.trimEnd() + newEntry + "\n", "utf-8");
|
|
329
|
+
} else {
|
|
330
|
+
await writeFile(
|
|
331
|
+
memoryPath,
|
|
332
|
+
`# MEMORY.md \u2014 Persistent Memory
|
|
333
|
+
|
|
334
|
+
Things learned across sessions:
|
|
335
|
+
${newEntry}
|
|
336
|
+
`,
|
|
337
|
+
"utf-8"
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
var PERSIST_TRIGGERS;
|
|
342
|
+
var init_auto_persist = __esm({
|
|
343
|
+
"src/memory/auto-persist.ts"() {
|
|
344
|
+
"use strict";
|
|
345
|
+
init_esm_shims();
|
|
346
|
+
PERSIST_TRIGGERS = [
|
|
347
|
+
/remember\s+(that|this|:)/i,
|
|
348
|
+
/don'?t\s+forget/i,
|
|
349
|
+
/always\s+(use|do|make|keep)/i,
|
|
350
|
+
/never\s+(use|do|make)/i,
|
|
351
|
+
/my\s+(name|email|timezone|preference)/i,
|
|
352
|
+
/i\s+(prefer|like|want|need|use)\s/i,
|
|
353
|
+
/from now on/i,
|
|
354
|
+
/going forward/i,
|
|
355
|
+
/important:\s/i,
|
|
356
|
+
/note:\s/i
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
300
361
|
// src/providers/types.ts
|
|
301
362
|
var BaseProvider;
|
|
302
363
|
var init_types = __esm({
|
|
@@ -685,6 +746,7 @@ var init_agent = __esm({
|
|
|
685
746
|
"use strict";
|
|
686
747
|
init_esm_shims();
|
|
687
748
|
init_context_engine();
|
|
749
|
+
init_auto_persist();
|
|
688
750
|
init_ollama();
|
|
689
751
|
init_prompt_fallback();
|
|
690
752
|
MAX_ITERATIONS = 50;
|
|
@@ -731,6 +793,9 @@ var init_agent = __esm({
|
|
|
731
793
|
const ctxSize = await this.resolvedProvider.provider.detectContextWindow();
|
|
732
794
|
this.contextEngine.setContextWindow(ctxSize);
|
|
733
795
|
}
|
|
796
|
+
if (this.contextEngine.needsCompaction()) {
|
|
797
|
+
await this.contextEngine.compactSmart();
|
|
798
|
+
}
|
|
734
799
|
}
|
|
735
800
|
/** Cancel the current request */
|
|
736
801
|
cancel() {
|
|
@@ -748,6 +813,13 @@ var init_agent = __esm({
|
|
|
748
813
|
this.abortController = new AbortController();
|
|
749
814
|
const signal = this.abortController.signal;
|
|
750
815
|
this.contextEngine.ingest({ role: "user", content: text });
|
|
816
|
+
if (shouldPersist(text)) {
|
|
817
|
+
const memory = extractMemory(text);
|
|
818
|
+
if (memory) {
|
|
819
|
+
appendToMemory(this.identity.workspace, memory).catch(() => {
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
751
823
|
if (this.currentSession && !this.currentSession.label) {
|
|
752
824
|
const label = text.length > 60 ? text.slice(0, 57) + "..." : text;
|
|
753
825
|
await this.sessionStore.setLabel(this.currentSession.normalizedKey, label);
|
|
@@ -788,36 +860,58 @@ var init_agent = __esm({
|
|
|
788
860
|
const toolCalls = [];
|
|
789
861
|
let promptTokens = 0;
|
|
790
862
|
let outputTokens = 0;
|
|
863
|
+
let streamSuccess = false;
|
|
791
864
|
this.emit("response-start");
|
|
792
|
-
for
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
865
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
866
|
+
try {
|
|
867
|
+
const streamIterator = activeProvider.stream(
|
|
868
|
+
this.contextEngine.getMessages(),
|
|
869
|
+
this.systemPrompt,
|
|
870
|
+
toolDefs,
|
|
871
|
+
signal
|
|
872
|
+
);
|
|
873
|
+
for await (const event of streamIterator) {
|
|
874
|
+
switch (event.type) {
|
|
875
|
+
case "text":
|
|
876
|
+
iterationText += event.content;
|
|
877
|
+
this.emit("token", { content: event.content });
|
|
878
|
+
break;
|
|
879
|
+
case "thinking":
|
|
880
|
+
this.emit("thinking-start");
|
|
881
|
+
break;
|
|
882
|
+
case "tool_call":
|
|
883
|
+
toolCalls.push({
|
|
884
|
+
id: event.id,
|
|
885
|
+
name: event.name,
|
|
886
|
+
arguments: event.arguments
|
|
887
|
+
});
|
|
888
|
+
break;
|
|
889
|
+
case "usage":
|
|
890
|
+
promptTokens = event.promptTokens;
|
|
891
|
+
outputTokens = event.outputTokens;
|
|
892
|
+
break;
|
|
893
|
+
case "done":
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
streamSuccess = true;
|
|
898
|
+
break;
|
|
899
|
+
} catch (streamErr) {
|
|
900
|
+
if (attempt === 0 && !signal.aborted) {
|
|
901
|
+
this.emit("error", {
|
|
902
|
+
message: `Model connection failed, retrying... (${streamErr instanceof Error ? streamErr.message : "unknown"})`,
|
|
903
|
+
recoverable: true
|
|
811
904
|
});
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
break;
|
|
817
|
-
case "done":
|
|
818
|
-
break;
|
|
905
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
throw streamErr;
|
|
819
909
|
}
|
|
820
910
|
}
|
|
911
|
+
if (!streamSuccess) {
|
|
912
|
+
this.emit("error", { message: "Model failed to respond after retry", recoverable: false });
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
821
915
|
this.emit("usage", {
|
|
822
916
|
promptTokens,
|
|
823
917
|
outputTokens,
|
|
@@ -956,30 +1050,47 @@ __export(system_prompt_exports, {
|
|
|
956
1050
|
buildSystemPrompt: () => buildSystemPrompt,
|
|
957
1051
|
ensureWorkspaceFiles: () => ensureWorkspaceFiles
|
|
958
1052
|
});
|
|
959
|
-
import { readFile } from "fs/promises";
|
|
960
|
-
import { existsSync } from "fs";
|
|
961
|
-
import { join } from "path";
|
|
1053
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1054
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1055
|
+
import { join as join2 } from "path";
|
|
962
1056
|
import { platform, hostname } from "os";
|
|
963
1057
|
async function buildSystemPrompt(opts) {
|
|
964
1058
|
const parts = [];
|
|
965
|
-
const
|
|
966
|
-
if (
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1059
|
+
const compact = opts.compact ?? false;
|
|
1060
|
+
if (!compact) {
|
|
1061
|
+
const workspaceContent = await loadWorkspaceFiles(opts.workspaceDir);
|
|
1062
|
+
if (workspaceContent) {
|
|
1063
|
+
parts.push(workspaceContent);
|
|
1064
|
+
parts.push("---");
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (compact) {
|
|
1068
|
+
parts.push(`Agent: ${opts.identity.name} | Model: ${opts.identity.model.primary} | Dir: ${opts.identity.workspace}`);
|
|
1069
|
+
} else {
|
|
1070
|
+
parts.push("## Runtime");
|
|
1071
|
+
parts.push(`Agent: ${opts.identity.name} (${opts.identity.id})`);
|
|
1072
|
+
parts.push(`Model: ${opts.identity.model.primary}`);
|
|
1073
|
+
parts.push(`Workspace: ${opts.identity.workspace}`);
|
|
1074
|
+
parts.push(`Platform: ${platform()} (${hostname()})`);
|
|
1075
|
+
parts.push(`Channel: ${opts.channel || "cli"}`);
|
|
1076
|
+
parts.push(`Tool tier: ${opts.identity.toolTier}`);
|
|
1077
|
+
}
|
|
977
1078
|
parts.push("");
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1079
|
+
if (compact) {
|
|
1080
|
+
parts.push("You are a helpful AI assistant with tools. Be concise. Use tools proactively. Read files before editing.");
|
|
1081
|
+
} else {
|
|
1082
|
+
parts.push("## Instructions");
|
|
1083
|
+
parts.push("You are a helpful AI assistant with access to tools for reading/writing files, running commands, and more.");
|
|
1084
|
+
parts.push("Be concise and direct. Use tools proactively to accomplish tasks.");
|
|
1085
|
+
parts.push("When you need to make changes, read the relevant files first to understand the context.");
|
|
1086
|
+
parts.push("You can configure yourself \u2014 use the config, channel, agent, and model management tools to modify your own setup.");
|
|
1087
|
+
}
|
|
1088
|
+
if (opts.thinking === "off") {
|
|
1089
|
+
parts.push("");
|
|
1090
|
+
parts.push("Do NOT use extended thinking or reasoning blocks. Respond directly and concisely.");
|
|
1091
|
+
}
|
|
1092
|
+
parts.push("");
|
|
1093
|
+
parts.push("When you learn something important about the user or project, save it using the config or memory tools so you remember it next time.");
|
|
983
1094
|
parts.push("");
|
|
984
1095
|
const projectMemory = await loadProjectMemory(opts.identity.workspace);
|
|
985
1096
|
if (projectMemory) {
|
|
@@ -992,10 +1103,10 @@ async function buildSystemPrompt(opts) {
|
|
|
992
1103
|
async function loadWorkspaceFiles(workspaceDir) {
|
|
993
1104
|
const sections = [];
|
|
994
1105
|
for (const filename of WORKSPACE_FILES) {
|
|
995
|
-
const filePath =
|
|
996
|
-
if (
|
|
1106
|
+
const filePath = join2(workspaceDir, filename);
|
|
1107
|
+
if (existsSync2(filePath)) {
|
|
997
1108
|
try {
|
|
998
|
-
const content = await
|
|
1109
|
+
const content = await readFile2(filePath, "utf-8");
|
|
999
1110
|
if (content.trim()) {
|
|
1000
1111
|
sections.push(content.trim());
|
|
1001
1112
|
}
|
|
@@ -1008,10 +1119,10 @@ async function loadWorkspaceFiles(workspaceDir) {
|
|
|
1008
1119
|
async function loadProjectMemory(projectRoot) {
|
|
1009
1120
|
const candidates = [".clank.md", ".clankbuild.md", ".llamabuild.md"];
|
|
1010
1121
|
for (const filename of candidates) {
|
|
1011
|
-
const filePath =
|
|
1012
|
-
if (
|
|
1122
|
+
const filePath = join2(projectRoot, filename);
|
|
1123
|
+
if (existsSync2(filePath)) {
|
|
1013
1124
|
try {
|
|
1014
|
-
const content = await
|
|
1125
|
+
const content = await readFile2(filePath, "utf-8");
|
|
1015
1126
|
return content.trim() || null;
|
|
1016
1127
|
} catch {
|
|
1017
1128
|
continue;
|
|
@@ -1024,9 +1135,9 @@ async function ensureWorkspaceFiles(workspaceDir, templateDir) {
|
|
|
1024
1135
|
const { mkdir: mkdir7, copyFile } = await import("fs/promises");
|
|
1025
1136
|
await mkdir7(workspaceDir, { recursive: true });
|
|
1026
1137
|
for (const filename of [...WORKSPACE_FILES, "BOOTSTRAP.md", "HEARTBEAT.md"]) {
|
|
1027
|
-
const target =
|
|
1028
|
-
const source =
|
|
1029
|
-
if (!
|
|
1138
|
+
const target = join2(workspaceDir, filename);
|
|
1139
|
+
const source = join2(templateDir, filename);
|
|
1140
|
+
if (!existsSync2(target) && existsSync2(source)) {
|
|
1030
1141
|
await copyFile(source, target);
|
|
1031
1142
|
}
|
|
1032
1143
|
}
|
|
@@ -1186,7 +1297,7 @@ var init_path_guard = __esm({
|
|
|
1186
1297
|
});
|
|
1187
1298
|
|
|
1188
1299
|
// src/tools/read-file.ts
|
|
1189
|
-
import { readFile as
|
|
1300
|
+
import { readFile as readFile3, stat } from "fs/promises";
|
|
1190
1301
|
var readFileTool;
|
|
1191
1302
|
var init_read_file = __esm({
|
|
1192
1303
|
"src/tools/read-file.ts"() {
|
|
@@ -1235,7 +1346,7 @@ var init_read_file = __esm({
|
|
|
1235
1346
|
if (probe.subarray(0, probeLen).includes(0)) {
|
|
1236
1347
|
return `Binary file detected: ${filePath} (${fileStats.size} bytes)`;
|
|
1237
1348
|
}
|
|
1238
|
-
const content = await
|
|
1349
|
+
const content = await readFile3(filePath, "utf-8");
|
|
1239
1350
|
const lines = content.split("\n");
|
|
1240
1351
|
const offset = Math.max(1, Number(args.offset) || 1);
|
|
1241
1352
|
const limit = Number(args.limit) || lines.length;
|
|
@@ -1252,7 +1363,7 @@ var init_read_file = __esm({
|
|
|
1252
1363
|
});
|
|
1253
1364
|
|
|
1254
1365
|
// src/tools/write-file.ts
|
|
1255
|
-
import { writeFile, mkdir } from "fs/promises";
|
|
1366
|
+
import { writeFile as writeFile2, mkdir } from "fs/promises";
|
|
1256
1367
|
import { dirname, isAbsolute as isAbsolute2 } from "path";
|
|
1257
1368
|
var writeFileTool;
|
|
1258
1369
|
var init_write_file = __esm({
|
|
@@ -1294,7 +1405,7 @@ var init_write_file = __esm({
|
|
|
1294
1405
|
const filePath = guard.path;
|
|
1295
1406
|
try {
|
|
1296
1407
|
await mkdir(dirname(filePath), { recursive: true });
|
|
1297
|
-
await
|
|
1408
|
+
await writeFile2(filePath, args.content, "utf-8");
|
|
1298
1409
|
const lines = args.content.split("\n").length;
|
|
1299
1410
|
return `Wrote ${lines} lines to ${filePath}`;
|
|
1300
1411
|
} catch (err) {
|
|
@@ -1310,7 +1421,7 @@ var init_write_file = __esm({
|
|
|
1310
1421
|
});
|
|
1311
1422
|
|
|
1312
1423
|
// src/tools/edit-file.ts
|
|
1313
|
-
import { readFile as
|
|
1424
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
1314
1425
|
import { isAbsolute as isAbsolute3 } from "path";
|
|
1315
1426
|
var editFileTool;
|
|
1316
1427
|
var init_edit_file = __esm({
|
|
@@ -1351,7 +1462,7 @@ var init_edit_file = __esm({
|
|
|
1351
1462
|
if (!guard.ok) return guard.error;
|
|
1352
1463
|
const filePath = guard.path;
|
|
1353
1464
|
try {
|
|
1354
|
-
const content = await
|
|
1465
|
+
const content = await readFile4(filePath, "utf-8");
|
|
1355
1466
|
const oldStr = args.old_string;
|
|
1356
1467
|
const newStr = args.new_string;
|
|
1357
1468
|
const replaceAll = Boolean(args.replace_all);
|
|
@@ -1365,7 +1476,7 @@ var init_edit_file = __esm({
|
|
|
1365
1476
|
}
|
|
1366
1477
|
}
|
|
1367
1478
|
const updated = replaceAll ? content.split(oldStr).join(newStr) : content.replace(oldStr, newStr);
|
|
1368
|
-
await
|
|
1479
|
+
await writeFile3(filePath, updated, "utf-8");
|
|
1369
1480
|
const replacements = replaceAll ? content.split(oldStr).length - 1 : 1;
|
|
1370
1481
|
return `Edited ${filePath} (${replacements} replacement${replacements > 1 ? "s" : ""})`;
|
|
1371
1482
|
} catch (err) {
|
|
@@ -1382,7 +1493,7 @@ var init_edit_file = __esm({
|
|
|
1382
1493
|
|
|
1383
1494
|
// src/tools/list-directory.ts
|
|
1384
1495
|
import { readdir, stat as stat2 } from "fs/promises";
|
|
1385
|
-
import { join as
|
|
1496
|
+
import { join as join3 } from "path";
|
|
1386
1497
|
function formatSize(bytes) {
|
|
1387
1498
|
if (bytes < 1024) return `${bytes}B`;
|
|
1388
1499
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
@@ -1423,7 +1534,7 @@ var init_list_directory = __esm({
|
|
|
1423
1534
|
const lines = [];
|
|
1424
1535
|
for (const entry of entries.slice(0, 100)) {
|
|
1425
1536
|
try {
|
|
1426
|
-
const full =
|
|
1537
|
+
const full = join3(dirPath, entry);
|
|
1427
1538
|
const s = await stat2(full);
|
|
1428
1539
|
const type = s.isDirectory() ? "dir" : "file";
|
|
1429
1540
|
const size = s.isDirectory() ? "" : ` (${formatSize(s.size)})`;
|
|
@@ -1446,8 +1557,8 @@ var init_list_directory = __esm({
|
|
|
1446
1557
|
});
|
|
1447
1558
|
|
|
1448
1559
|
// src/tools/search-files.ts
|
|
1449
|
-
import { readdir as readdir2, readFile as
|
|
1450
|
-
import { join as
|
|
1560
|
+
import { readdir as readdir2, readFile as readFile5, stat as stat3 } from "fs/promises";
|
|
1561
|
+
import { join as join4, relative as relative2 } from "path";
|
|
1451
1562
|
var IGNORE_DIRS, searchFilesTool;
|
|
1452
1563
|
var init_search_files = __esm({
|
|
1453
1564
|
"src/tools/search-files.ts"() {
|
|
@@ -1522,7 +1633,7 @@ var init_search_files = __esm({
|
|
|
1522
1633
|
for (const entry of entries) {
|
|
1523
1634
|
if (results.length >= maxResults) return;
|
|
1524
1635
|
if (IGNORE_DIRS.has(entry)) continue;
|
|
1525
|
-
const full =
|
|
1636
|
+
const full = join4(dir, entry);
|
|
1526
1637
|
let s;
|
|
1527
1638
|
try {
|
|
1528
1639
|
s = await stat3(full);
|
|
@@ -1537,7 +1648,7 @@ var init_search_files = __esm({
|
|
|
1537
1648
|
if (!entry.endsWith(ext)) continue;
|
|
1538
1649
|
}
|
|
1539
1650
|
try {
|
|
1540
|
-
const content = await
|
|
1651
|
+
const content = await readFile5(full, "utf-8");
|
|
1541
1652
|
const lines = content.split("\n");
|
|
1542
1653
|
for (let i = 0; i < lines.length; i++) {
|
|
1543
1654
|
regex.lastIndex = 0;
|
|
@@ -1562,7 +1673,7 @@ var init_search_files = __esm({
|
|
|
1562
1673
|
|
|
1563
1674
|
// src/tools/glob-files.ts
|
|
1564
1675
|
import { readdir as readdir3, stat as stat4 } from "fs/promises";
|
|
1565
|
-
import { join as
|
|
1676
|
+
import { join as join5 } from "path";
|
|
1566
1677
|
function globToRegex(pattern) {
|
|
1567
1678
|
let regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
1568
1679
|
return new RegExp(`^${regex}$`, "i");
|
|
@@ -1628,7 +1739,7 @@ var init_glob_files = __esm({
|
|
|
1628
1739
|
for (const entry of entries) {
|
|
1629
1740
|
if (matches.length >= 200) return;
|
|
1630
1741
|
if (IGNORE_DIRS2.has(entry)) continue;
|
|
1631
|
-
const full =
|
|
1742
|
+
const full = join5(dir, entry);
|
|
1632
1743
|
const rel = relDir ? `${relDir}/${entry}` : entry;
|
|
1633
1744
|
let s;
|
|
1634
1745
|
try {
|
|
@@ -1854,19 +1965,19 @@ var init_git = __esm({
|
|
|
1854
1965
|
});
|
|
1855
1966
|
|
|
1856
1967
|
// src/config/config.ts
|
|
1857
|
-
import { readFile as
|
|
1858
|
-
import { existsSync as
|
|
1859
|
-
import { join as
|
|
1968
|
+
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir2 } from "fs/promises";
|
|
1969
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1970
|
+
import { join as join6 } from "path";
|
|
1860
1971
|
import { homedir, platform as platform3 } from "os";
|
|
1861
1972
|
import JSON5 from "json5";
|
|
1862
1973
|
function getConfigDir() {
|
|
1863
1974
|
if (platform3() === "win32") {
|
|
1864
|
-
return
|
|
1975
|
+
return join6(process.env.APPDATA || join6(homedir(), "AppData", "Roaming"), "Clank");
|
|
1865
1976
|
}
|
|
1866
|
-
return
|
|
1977
|
+
return join6(homedir(), ".clank");
|
|
1867
1978
|
}
|
|
1868
1979
|
function getConfigPath() {
|
|
1869
|
-
return
|
|
1980
|
+
return join6(getConfigDir(), "config.json5");
|
|
1870
1981
|
}
|
|
1871
1982
|
function defaultConfig() {
|
|
1872
1983
|
return {
|
|
@@ -1878,7 +1989,7 @@ function defaultConfig() {
|
|
|
1878
1989
|
agents: {
|
|
1879
1990
|
defaults: {
|
|
1880
1991
|
model: { primary: "ollama/qwen3.5" },
|
|
1881
|
-
workspace:
|
|
1992
|
+
workspace: join6(getConfigDir(), "workspace"),
|
|
1882
1993
|
toolTier: "auto",
|
|
1883
1994
|
temperature: 0.7
|
|
1884
1995
|
},
|
|
@@ -1937,11 +2048,11 @@ function deepMerge(target, source) {
|
|
|
1937
2048
|
async function loadConfig() {
|
|
1938
2049
|
const configPath = getConfigPath();
|
|
1939
2050
|
const defaults = defaultConfig();
|
|
1940
|
-
if (!
|
|
2051
|
+
if (!existsSync3(configPath)) {
|
|
1941
2052
|
return defaults;
|
|
1942
2053
|
}
|
|
1943
2054
|
try {
|
|
1944
|
-
const raw = await
|
|
2055
|
+
const raw = await readFile6(configPath, "utf-8");
|
|
1945
2056
|
const parsed = JSON5.parse(raw);
|
|
1946
2057
|
const substituted = substituteEnvVars(parsed);
|
|
1947
2058
|
return deepMerge(defaults, substituted);
|
|
@@ -1954,15 +2065,15 @@ async function saveConfig(config) {
|
|
|
1954
2065
|
const configPath = getConfigPath();
|
|
1955
2066
|
await mkdir2(getConfigDir(), { recursive: true });
|
|
1956
2067
|
const content = JSON5.stringify(config, null, 2);
|
|
1957
|
-
await
|
|
2068
|
+
await writeFile4(configPath, content, "utf-8");
|
|
1958
2069
|
}
|
|
1959
2070
|
async function ensureConfigDir() {
|
|
1960
2071
|
const configDir = getConfigDir();
|
|
1961
2072
|
await mkdir2(configDir, { recursive: true });
|
|
1962
|
-
await mkdir2(
|
|
1963
|
-
await mkdir2(
|
|
1964
|
-
await mkdir2(
|
|
1965
|
-
await mkdir2(
|
|
2073
|
+
await mkdir2(join6(configDir, "workspace"), { recursive: true });
|
|
2074
|
+
await mkdir2(join6(configDir, "conversations"), { recursive: true });
|
|
2075
|
+
await mkdir2(join6(configDir, "memory"), { recursive: true });
|
|
2076
|
+
await mkdir2(join6(configDir, "logs"), { recursive: true });
|
|
1966
2077
|
}
|
|
1967
2078
|
var init_config = __esm({
|
|
1968
2079
|
"src/config/config.ts"() {
|
|
@@ -3358,9 +3469,9 @@ var init_model_tool = __esm({
|
|
|
3358
3469
|
});
|
|
3359
3470
|
|
|
3360
3471
|
// src/sessions/store.ts
|
|
3361
|
-
import { readFile as
|
|
3362
|
-
import { existsSync as
|
|
3363
|
-
import { join as
|
|
3472
|
+
import { readFile as readFile7, writeFile as writeFile5, unlink, mkdir as mkdir3 } from "fs/promises";
|
|
3473
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3474
|
+
import { join as join7 } from "path";
|
|
3364
3475
|
import { randomUUID } from "crypto";
|
|
3365
3476
|
var SessionStore;
|
|
3366
3477
|
var init_store = __esm({
|
|
@@ -3373,14 +3484,14 @@ var init_store = __esm({
|
|
|
3373
3484
|
index = /* @__PURE__ */ new Map();
|
|
3374
3485
|
constructor(storeDir) {
|
|
3375
3486
|
this.storeDir = storeDir;
|
|
3376
|
-
this.indexPath =
|
|
3487
|
+
this.indexPath = join7(storeDir, "sessions.json");
|
|
3377
3488
|
}
|
|
3378
3489
|
/** Initialize the store — load index from disk */
|
|
3379
3490
|
async init() {
|
|
3380
3491
|
await mkdir3(this.storeDir, { recursive: true });
|
|
3381
|
-
if (
|
|
3492
|
+
if (existsSync4(this.indexPath)) {
|
|
3382
3493
|
try {
|
|
3383
|
-
const raw = await
|
|
3494
|
+
const raw = await readFile7(this.indexPath, "utf-8");
|
|
3384
3495
|
const entries = JSON.parse(raw);
|
|
3385
3496
|
for (const entry of entries) {
|
|
3386
3497
|
this.index.set(entry.normalizedKey, entry);
|
|
@@ -3393,7 +3504,7 @@ var init_store = __esm({
|
|
|
3393
3504
|
/** Save the index to disk */
|
|
3394
3505
|
async saveIndex() {
|
|
3395
3506
|
const entries = Array.from(this.index.values());
|
|
3396
|
-
await
|
|
3507
|
+
await writeFile5(this.indexPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
3397
3508
|
}
|
|
3398
3509
|
/** Get or create a session for a normalized key */
|
|
3399
3510
|
async resolve(normalizedKey, opts) {
|
|
@@ -3419,10 +3530,10 @@ var init_store = __esm({
|
|
|
3419
3530
|
}
|
|
3420
3531
|
/** Load conversation messages for a session */
|
|
3421
3532
|
async loadMessages(sessionId) {
|
|
3422
|
-
const path2 =
|
|
3423
|
-
if (!
|
|
3533
|
+
const path2 = join7(this.storeDir, `${sessionId}.json`);
|
|
3534
|
+
if (!existsSync4(path2)) return [];
|
|
3424
3535
|
try {
|
|
3425
|
-
const raw = await
|
|
3536
|
+
const raw = await readFile7(path2, "utf-8");
|
|
3426
3537
|
return JSON.parse(raw);
|
|
3427
3538
|
} catch {
|
|
3428
3539
|
return [];
|
|
@@ -3430,8 +3541,8 @@ var init_store = __esm({
|
|
|
3430
3541
|
}
|
|
3431
3542
|
/** Save conversation messages for a session */
|
|
3432
3543
|
async saveMessages(sessionId, messages) {
|
|
3433
|
-
const path2 =
|
|
3434
|
-
await
|
|
3544
|
+
const path2 = join7(this.storeDir, `${sessionId}.json`);
|
|
3545
|
+
await writeFile5(path2, JSON.stringify(messages, null, 2), "utf-8");
|
|
3435
3546
|
}
|
|
3436
3547
|
/** List all sessions, sorted by last used */
|
|
3437
3548
|
list() {
|
|
@@ -3443,7 +3554,7 @@ var init_store = __esm({
|
|
|
3443
3554
|
if (!entry) return false;
|
|
3444
3555
|
this.index.delete(normalizedKey);
|
|
3445
3556
|
await this.saveIndex();
|
|
3446
|
-
const path2 =
|
|
3557
|
+
const path2 = join7(this.storeDir, `${entry.id}.json`);
|
|
3447
3558
|
try {
|
|
3448
3559
|
await unlink(path2);
|
|
3449
3560
|
} catch {
|
|
@@ -3454,7 +3565,7 @@ var init_store = __esm({
|
|
|
3454
3565
|
async reset(normalizedKey) {
|
|
3455
3566
|
const entry = this.index.get(normalizedKey);
|
|
3456
3567
|
if (!entry) return null;
|
|
3457
|
-
const path2 =
|
|
3568
|
+
const path2 = join7(this.storeDir, `${entry.id}.json`);
|
|
3458
3569
|
try {
|
|
3459
3570
|
await unlink(path2);
|
|
3460
3571
|
} catch {
|
|
@@ -3500,7 +3611,7 @@ var init_sessions = __esm({
|
|
|
3500
3611
|
});
|
|
3501
3612
|
|
|
3502
3613
|
// src/tools/self-config/session-tool.ts
|
|
3503
|
-
import { join as
|
|
3614
|
+
import { join as join8 } from "path";
|
|
3504
3615
|
var sessionTool;
|
|
3505
3616
|
var init_session_tool = __esm({
|
|
3506
3617
|
"src/tools/self-config/session-tool.ts"() {
|
|
@@ -3534,7 +3645,7 @@ var init_session_tool = __esm({
|
|
|
3534
3645
|
return { ok: true };
|
|
3535
3646
|
},
|
|
3536
3647
|
async execute(args) {
|
|
3537
|
-
const store = new SessionStore(
|
|
3648
|
+
const store = new SessionStore(join8(getConfigDir(), "conversations"));
|
|
3538
3649
|
await store.init();
|
|
3539
3650
|
const action = args.action;
|
|
3540
3651
|
if (action === "list") {
|
|
@@ -3563,9 +3674,9 @@ var init_session_tool = __esm({
|
|
|
3563
3674
|
});
|
|
3564
3675
|
|
|
3565
3676
|
// src/cron/scheduler.ts
|
|
3566
|
-
import { readFile as
|
|
3567
|
-
import { existsSync as
|
|
3568
|
-
import { join as
|
|
3677
|
+
import { readFile as readFile8, appendFile, mkdir as mkdir4, writeFile as writeFile6 } from "fs/promises";
|
|
3678
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3679
|
+
import { join as join9 } from "path";
|
|
3569
3680
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3570
3681
|
var CronScheduler;
|
|
3571
3682
|
var init_scheduler = __esm({
|
|
@@ -3580,8 +3691,8 @@ var init_scheduler = __esm({
|
|
|
3580
3691
|
running = false;
|
|
3581
3692
|
onJobDue;
|
|
3582
3693
|
constructor(storeDir) {
|
|
3583
|
-
this.jobsPath =
|
|
3584
|
-
this.runsDir =
|
|
3694
|
+
this.jobsPath = join9(storeDir, "jobs.jsonl");
|
|
3695
|
+
this.runsDir = join9(storeDir, "runs");
|
|
3585
3696
|
}
|
|
3586
3697
|
/** Initialize — load jobs from disk */
|
|
3587
3698
|
async init() {
|
|
@@ -3703,9 +3814,9 @@ var init_scheduler = __esm({
|
|
|
3703
3814
|
}
|
|
3704
3815
|
/** Load jobs from JSONL file */
|
|
3705
3816
|
async loadJobs() {
|
|
3706
|
-
if (!
|
|
3817
|
+
if (!existsSync5(this.jobsPath)) return;
|
|
3707
3818
|
try {
|
|
3708
|
-
const raw = await
|
|
3819
|
+
const raw = await readFile8(this.jobsPath, "utf-8");
|
|
3709
3820
|
this.jobs = raw.split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
3710
3821
|
} catch {
|
|
3711
3822
|
this.jobs = [];
|
|
@@ -3714,11 +3825,11 @@ var init_scheduler = __esm({
|
|
|
3714
3825
|
/** Save jobs to JSONL file */
|
|
3715
3826
|
async saveJobs() {
|
|
3716
3827
|
const content = this.jobs.map((j) => JSON.stringify(j)).join("\n") + "\n";
|
|
3717
|
-
await
|
|
3828
|
+
await writeFile6(this.jobsPath, content, "utf-8");
|
|
3718
3829
|
}
|
|
3719
3830
|
/** Log a run result */
|
|
3720
3831
|
async logRun(log) {
|
|
3721
|
-
const logPath =
|
|
3832
|
+
const logPath = join9(this.runsDir, `${log.jobId}.jsonl`);
|
|
3722
3833
|
await appendFile(logPath, JSON.stringify(log) + "\n", "utf-8");
|
|
3723
3834
|
}
|
|
3724
3835
|
};
|
|
@@ -3739,7 +3850,7 @@ var init_cron = __esm({
|
|
|
3739
3850
|
});
|
|
3740
3851
|
|
|
3741
3852
|
// src/tools/self-config/cron-tool.ts
|
|
3742
|
-
import { join as
|
|
3853
|
+
import { join as join10 } from "path";
|
|
3743
3854
|
var cronTool;
|
|
3744
3855
|
var init_cron_tool = __esm({
|
|
3745
3856
|
"src/tools/self-config/cron-tool.ts"() {
|
|
@@ -3777,7 +3888,7 @@ var init_cron_tool = __esm({
|
|
|
3777
3888
|
return { ok: true };
|
|
3778
3889
|
},
|
|
3779
3890
|
async execute(args, ctx) {
|
|
3780
|
-
const scheduler = new CronScheduler(
|
|
3891
|
+
const scheduler = new CronScheduler(join10(getConfigDir(), "cron"));
|
|
3781
3892
|
await scheduler.init();
|
|
3782
3893
|
const action = args.action;
|
|
3783
3894
|
if (action === "list") {
|
|
@@ -3931,11 +4042,9 @@ var init_tts = __esm({
|
|
|
3931
4042
|
constructor(config) {
|
|
3932
4043
|
this.config = config;
|
|
3933
4044
|
}
|
|
3934
|
-
/** Check if TTS is available */
|
|
3935
4045
|
isAvailable() {
|
|
3936
4046
|
return !!(this.config.integrations.elevenlabs?.enabled && this.config.integrations.elevenlabs?.apiKey);
|
|
3937
4047
|
}
|
|
3938
|
-
/** Convert text to speech */
|
|
3939
4048
|
async synthesize(text, opts) {
|
|
3940
4049
|
const elevenlabs = this.config.integrations.elevenlabs;
|
|
3941
4050
|
if (!elevenlabs?.enabled || !elevenlabs.apiKey) return null;
|
|
@@ -3953,29 +4062,21 @@ var init_tts = __esm({
|
|
|
3953
4062
|
body: JSON.stringify({
|
|
3954
4063
|
text,
|
|
3955
4064
|
model_id: model,
|
|
3956
|
-
voice_settings: {
|
|
3957
|
-
stability: 0.5,
|
|
3958
|
-
similarity_boost: 0.75
|
|
3959
|
-
}
|
|
4065
|
+
voice_settings: { stability: 0.5, similarity_boost: 0.75 }
|
|
3960
4066
|
})
|
|
3961
4067
|
}
|
|
3962
4068
|
);
|
|
3963
4069
|
if (!res.ok) {
|
|
3964
|
-
|
|
3965
|
-
console.error(`ElevenLabs TTS error ${res.status}: ${err}`);
|
|
4070
|
+
console.error(`ElevenLabs TTS error ${res.status}`);
|
|
3966
4071
|
return null;
|
|
3967
4072
|
}
|
|
3968
4073
|
const arrayBuffer = await res.arrayBuffer();
|
|
3969
|
-
return {
|
|
3970
|
-
audioBuffer: Buffer.from(arrayBuffer),
|
|
3971
|
-
format: "mp3"
|
|
3972
|
-
};
|
|
4074
|
+
return { audioBuffer: Buffer.from(arrayBuffer), format: "mp3" };
|
|
3973
4075
|
} catch (err) {
|
|
3974
4076
|
console.error(`TTS error: ${err instanceof Error ? err.message : err}`);
|
|
3975
4077
|
return null;
|
|
3976
4078
|
}
|
|
3977
4079
|
}
|
|
3978
|
-
/** List available voices from ElevenLabs */
|
|
3979
4080
|
async listVoices() {
|
|
3980
4081
|
const elevenlabs = this.config.integrations.elevenlabs;
|
|
3981
4082
|
if (!elevenlabs?.enabled || !elevenlabs.apiKey) return [];
|
|
@@ -3996,53 +4097,67 @@ var init_tts = __esm({
|
|
|
3996
4097
|
constructor(config) {
|
|
3997
4098
|
this.config = config;
|
|
3998
4099
|
}
|
|
3999
|
-
/** Check if STT is available */
|
|
4000
4100
|
isAvailable() {
|
|
4001
4101
|
const whisper = this.config.integrations.whisper;
|
|
4002
4102
|
if (whisper?.enabled) {
|
|
4103
|
+
if (whisper.provider === "groq" && whisper.apiKey) return true;
|
|
4003
4104
|
if (whisper.provider === "openai" && whisper.apiKey) return true;
|
|
4004
4105
|
if (whisper.provider === "local") return true;
|
|
4005
4106
|
}
|
|
4006
4107
|
if (this.config.models.providers.openai?.apiKey) return true;
|
|
4108
|
+
if (this.config.integrations.groq?.apiKey) return true;
|
|
4007
4109
|
return false;
|
|
4008
4110
|
}
|
|
4009
|
-
/** Transcribe audio to text */
|
|
4010
4111
|
async transcribe(audioBuffer, format = "ogg") {
|
|
4011
4112
|
const whisper = this.config.integrations.whisper;
|
|
4012
|
-
const
|
|
4013
|
-
if (
|
|
4014
|
-
|
|
4113
|
+
const groqKey = whisper?.provider === "groq" && whisper?.apiKey ? whisper.apiKey : this.config.integrations.groq?.apiKey;
|
|
4114
|
+
if (groqKey) {
|
|
4115
|
+
const result = await this.transcribeAPI(audioBuffer, format, groqKey, "https://api.groq.com/openai/v1/audio/transcriptions", "whisper-large-v3-turbo");
|
|
4116
|
+
if (result) return result;
|
|
4117
|
+
}
|
|
4118
|
+
const openaiKey = whisper?.provider === "openai" && whisper?.apiKey ? whisper.apiKey : this.config.models.providers.openai?.apiKey;
|
|
4119
|
+
if (openaiKey) {
|
|
4120
|
+
const result = await this.transcribeAPI(audioBuffer, format, openaiKey, "https://api.openai.com/v1/audio/transcriptions", "whisper-1");
|
|
4121
|
+
if (result) return result;
|
|
4015
4122
|
}
|
|
4016
|
-
|
|
4123
|
+
if (whisper?.provider === "local") {
|
|
4124
|
+
return this.transcribeLocal(audioBuffer, format);
|
|
4125
|
+
}
|
|
4126
|
+
return null;
|
|
4017
4127
|
}
|
|
4018
|
-
/** Transcribe via OpenAI
|
|
4019
|
-
async
|
|
4128
|
+
/** Transcribe via OpenAI-compatible API (works for both OpenAI and Groq) */
|
|
4129
|
+
async transcribeAPI(audioBuffer, format, apiKey, endpoint, model) {
|
|
4020
4130
|
try {
|
|
4021
4131
|
const blob = new Blob([new Uint8Array(audioBuffer)], { type: `audio/${format}` });
|
|
4022
4132
|
const formData = new FormData();
|
|
4023
4133
|
formData.append("file", blob, `audio.${format}`);
|
|
4024
|
-
formData.append("model",
|
|
4025
|
-
const res = await fetch(
|
|
4134
|
+
formData.append("model", model);
|
|
4135
|
+
const res = await fetch(endpoint, {
|
|
4026
4136
|
method: "POST",
|
|
4027
4137
|
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
4028
4138
|
body: formData
|
|
4029
4139
|
});
|
|
4030
|
-
if (!res.ok)
|
|
4140
|
+
if (!res.ok) {
|
|
4141
|
+
const errText = await res.text().catch(() => "");
|
|
4142
|
+
console.error(`STT API error ${res.status}: ${errText.slice(0, 200)}`);
|
|
4143
|
+
return null;
|
|
4144
|
+
}
|
|
4031
4145
|
const data = await res.json();
|
|
4032
4146
|
return data.text ? { text: data.text, language: data.language } : null;
|
|
4033
|
-
} catch {
|
|
4147
|
+
} catch (err) {
|
|
4148
|
+
console.error(`STT error: ${err instanceof Error ? err.message : err}`);
|
|
4034
4149
|
return null;
|
|
4035
4150
|
}
|
|
4036
4151
|
}
|
|
4037
4152
|
/** Transcribe via local whisper.cpp */
|
|
4038
4153
|
async transcribeLocal(audioBuffer, format) {
|
|
4039
4154
|
try {
|
|
4040
|
-
const { writeFile:
|
|
4155
|
+
const { writeFile: writeFile10, unlink: unlink5 } = await import("fs/promises");
|
|
4041
4156
|
const { execSync: execSync3 } = await import("child_process");
|
|
4042
|
-
const { join:
|
|
4157
|
+
const { join: join20 } = await import("path");
|
|
4043
4158
|
const { tmpdir } = await import("os");
|
|
4044
|
-
const tmpFile =
|
|
4045
|
-
await
|
|
4159
|
+
const tmpFile = join20(tmpdir(), `clank-stt-${Date.now()}.${format}`);
|
|
4160
|
+
await writeFile10(tmpFile, audioBuffer);
|
|
4046
4161
|
const output = execSync3(`whisper "${tmpFile}" --model base.en --output-txt`, {
|
|
4047
4162
|
encoding: "utf-8",
|
|
4048
4163
|
timeout: 6e4
|
|
@@ -4110,11 +4225,11 @@ var init_voice_tool = __esm({
|
|
|
4110
4225
|
voiceId: args.voice_id
|
|
4111
4226
|
});
|
|
4112
4227
|
if (!result) return "Error: TTS synthesis failed";
|
|
4113
|
-
const { writeFile:
|
|
4114
|
-
const { join:
|
|
4228
|
+
const { writeFile: writeFile10 } = await import("fs/promises");
|
|
4229
|
+
const { join: join20 } = await import("path");
|
|
4115
4230
|
const { tmpdir } = await import("os");
|
|
4116
|
-
const outPath =
|
|
4117
|
-
await
|
|
4231
|
+
const outPath = join20(tmpdir(), `clank-tts-${Date.now()}.${result.format}`);
|
|
4232
|
+
await writeFile10(outPath, result.audioBuffer);
|
|
4118
4233
|
return `Audio generated: ${outPath} (${result.format}, ${Math.round(result.audioBuffer.length / 1024)}KB)`;
|
|
4119
4234
|
}
|
|
4120
4235
|
};
|
|
@@ -4137,16 +4252,16 @@ var init_voice_tool = __esm({
|
|
|
4137
4252
|
return { ok: true };
|
|
4138
4253
|
},
|
|
4139
4254
|
async execute(args) {
|
|
4140
|
-
const { readFile:
|
|
4141
|
-
const { existsSync:
|
|
4255
|
+
const { readFile: readFile13 } = await import("fs/promises");
|
|
4256
|
+
const { existsSync: existsSync12 } = await import("fs");
|
|
4142
4257
|
const filePath = args.file_path;
|
|
4143
|
-
if (!
|
|
4258
|
+
if (!existsSync12(filePath)) return `Error: File not found: ${filePath}`;
|
|
4144
4259
|
const config = await loadConfig();
|
|
4145
4260
|
const engine = new STTEngine(config);
|
|
4146
4261
|
if (!engine.isAvailable()) {
|
|
4147
4262
|
return "Error: Speech-to-text not configured. Need OpenAI API key or local whisper.cpp installed.";
|
|
4148
4263
|
}
|
|
4149
|
-
const audioBuffer = await
|
|
4264
|
+
const audioBuffer = await readFile13(filePath);
|
|
4150
4265
|
const ext = filePath.split(".").pop() || "wav";
|
|
4151
4266
|
const result = await engine.transcribe(audioBuffer, ext);
|
|
4152
4267
|
if (!result) return "Error: Transcription failed";
|
|
@@ -4178,6 +4293,53 @@ var init_voice_tool = __esm({
|
|
|
4178
4293
|
}
|
|
4179
4294
|
});
|
|
4180
4295
|
|
|
4296
|
+
// src/tools/self-config/file-share-tool.ts
|
|
4297
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4298
|
+
import { stat as stat5 } from "fs/promises";
|
|
4299
|
+
var MAX_FILE_SIZE, fileShareTool;
|
|
4300
|
+
var init_file_share_tool = __esm({
|
|
4301
|
+
"src/tools/self-config/file-share-tool.ts"() {
|
|
4302
|
+
"use strict";
|
|
4303
|
+
init_esm_shims();
|
|
4304
|
+
init_path_guard();
|
|
4305
|
+
MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
4306
|
+
fileShareTool = {
|
|
4307
|
+
definition: {
|
|
4308
|
+
name: "share_file",
|
|
4309
|
+
description: "Share a file from the workspace with the user via the current channel (Telegram, Discord, etc.). The file must be within the workspace. Max 10MB.",
|
|
4310
|
+
parameters: {
|
|
4311
|
+
type: "object",
|
|
4312
|
+
properties: {
|
|
4313
|
+
path: { type: "string", description: "Path to the file to share" },
|
|
4314
|
+
caption: { type: "string", description: "Optional message to send with the file" }
|
|
4315
|
+
},
|
|
4316
|
+
required: ["path"]
|
|
4317
|
+
}
|
|
4318
|
+
},
|
|
4319
|
+
safetyLevel: "medium",
|
|
4320
|
+
readOnly: true,
|
|
4321
|
+
validate(args, ctx) {
|
|
4322
|
+
if (!args.path || typeof args.path !== "string") return { ok: false, error: "path is required" };
|
|
4323
|
+
const guard = guardPath(args.path, ctx.projectRoot);
|
|
4324
|
+
if (!guard.ok) return { ok: false, error: guard.error };
|
|
4325
|
+
return { ok: true };
|
|
4326
|
+
},
|
|
4327
|
+
async execute(args, ctx) {
|
|
4328
|
+
const guard = guardPath(args.path, ctx.projectRoot, { allowExternal: ctx.allowExternal });
|
|
4329
|
+
if (!guard.ok) return guard.error;
|
|
4330
|
+
if (!existsSync6(guard.path)) return `Error: File not found: ${guard.path}`;
|
|
4331
|
+
const fileStats = await stat5(guard.path);
|
|
4332
|
+
if (fileStats.size > MAX_FILE_SIZE) return `Error: File too large (${Math.round(fileStats.size / 1024 / 1024)}MB, max 10MB)`;
|
|
4333
|
+
const caption = args.caption ? ` with caption: "${args.caption}"` : "";
|
|
4334
|
+
return `File ready to share: ${guard.path} (${Math.round(fileStats.size / 1024)}KB)${caption}. The file will be sent through the current channel.`;
|
|
4335
|
+
},
|
|
4336
|
+
formatConfirmation(args) {
|
|
4337
|
+
return `Share file: ${args.path}`;
|
|
4338
|
+
}
|
|
4339
|
+
};
|
|
4340
|
+
}
|
|
4341
|
+
});
|
|
4342
|
+
|
|
4181
4343
|
// src/tools/self-config/index.ts
|
|
4182
4344
|
function registerSelfConfigTools(registry) {
|
|
4183
4345
|
registry.register(configTool);
|
|
@@ -4191,6 +4353,7 @@ function registerSelfConfigTools(registry) {
|
|
|
4191
4353
|
registry.register(ttsTool);
|
|
4192
4354
|
registry.register(sttTool);
|
|
4193
4355
|
registry.register(voiceListTool);
|
|
4356
|
+
registry.register(fileShareTool);
|
|
4194
4357
|
}
|
|
4195
4358
|
var init_self_config = __esm({
|
|
4196
4359
|
"src/tools/self-config/index.ts"() {
|
|
@@ -4205,6 +4368,7 @@ var init_self_config = __esm({
|
|
|
4205
4368
|
init_gateway_tool();
|
|
4206
4369
|
init_message_tool();
|
|
4207
4370
|
init_voice_tool();
|
|
4371
|
+
init_file_share_tool();
|
|
4208
4372
|
init_config_tool();
|
|
4209
4373
|
init_channel_tool();
|
|
4210
4374
|
init_agent_tool();
|
|
@@ -4214,6 +4378,7 @@ var init_self_config = __esm({
|
|
|
4214
4378
|
init_gateway_tool();
|
|
4215
4379
|
init_message_tool();
|
|
4216
4380
|
init_voice_tool();
|
|
4381
|
+
init_file_share_tool();
|
|
4217
4382
|
}
|
|
4218
4383
|
});
|
|
4219
4384
|
|
|
@@ -4275,7 +4440,7 @@ __export(chat_exports, {
|
|
|
4275
4440
|
runChat: () => runChat
|
|
4276
4441
|
});
|
|
4277
4442
|
import { createInterface } from "readline";
|
|
4278
|
-
import { join as
|
|
4443
|
+
import { join as join11 } from "path";
|
|
4279
4444
|
async function runChat(opts) {
|
|
4280
4445
|
await ensureConfigDir();
|
|
4281
4446
|
const config = await loadConfig();
|
|
@@ -4292,9 +4457,9 @@ async function runChat(opts) {
|
|
|
4292
4457
|
console.log(dim("Starting gateway..."));
|
|
4293
4458
|
const { fork: fork2 } = await import("child_process");
|
|
4294
4459
|
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
4295
|
-
const { dirname: dirname6, join:
|
|
4460
|
+
const { dirname: dirname6, join: join20 } = await import("path");
|
|
4296
4461
|
const __filename4 = fileURLToPath6(import.meta.url);
|
|
4297
|
-
const entryPoint =
|
|
4462
|
+
const entryPoint = join20(dirname6(__filename4), "index.js");
|
|
4298
4463
|
const child = fork2(entryPoint, ["gateway", "start", "--foreground"], {
|
|
4299
4464
|
detached: true,
|
|
4300
4465
|
stdio: "ignore"
|
|
@@ -4342,7 +4507,7 @@ async function runChat(opts) {
|
|
|
4342
4507
|
console.error(dim("Make sure Ollama is running or configure a cloud provider in ~/.clank/config.json5"));
|
|
4343
4508
|
process.exit(1);
|
|
4344
4509
|
}
|
|
4345
|
-
const sessionStore = new SessionStore(
|
|
4510
|
+
const sessionStore = new SessionStore(join11(getConfigDir(), "conversations"));
|
|
4346
4511
|
await sessionStore.init();
|
|
4347
4512
|
const toolRegistry = createFullRegistry();
|
|
4348
4513
|
const identity = {
|
|
@@ -4500,9 +4665,9 @@ var init_chat = __esm({
|
|
|
4500
4665
|
});
|
|
4501
4666
|
|
|
4502
4667
|
// src/memory/memory.ts
|
|
4503
|
-
import { readFile as
|
|
4504
|
-
import { existsSync as
|
|
4505
|
-
import { join as
|
|
4668
|
+
import { readFile as readFile9, writeFile as writeFile7, readdir as readdir5, mkdir as mkdir5 } from "fs/promises";
|
|
4669
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4670
|
+
import { join as join12 } from "path";
|
|
4506
4671
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4507
4672
|
function tokenize(text) {
|
|
4508
4673
|
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3 && !STOPWORDS.has(t));
|
|
@@ -4650,12 +4815,12 @@ var init_memory = __esm({
|
|
|
4650
4815
|
meta = /* @__PURE__ */ new Map();
|
|
4651
4816
|
constructor(memoryDir) {
|
|
4652
4817
|
this.memoryDir = memoryDir;
|
|
4653
|
-
this.metaPath =
|
|
4818
|
+
this.metaPath = join12(memoryDir, "_meta.json");
|
|
4654
4819
|
}
|
|
4655
4820
|
/** Initialize — create dirs and load metadata */
|
|
4656
4821
|
async init() {
|
|
4657
4822
|
for (const cat of ["identity", "knowledge", "lessons", "context"]) {
|
|
4658
|
-
await mkdir5(
|
|
4823
|
+
await mkdir5(join12(this.memoryDir, cat), { recursive: true });
|
|
4659
4824
|
}
|
|
4660
4825
|
await this.loadMeta();
|
|
4661
4826
|
}
|
|
@@ -4700,10 +4865,10 @@ var init_memory = __esm({
|
|
|
4700
4865
|
let used = 0;
|
|
4701
4866
|
if (projectRoot) {
|
|
4702
4867
|
for (const name of [".clank.md", ".clankbuild.md", ".llamabuild.md"]) {
|
|
4703
|
-
const path2 =
|
|
4704
|
-
if (
|
|
4868
|
+
const path2 = join12(projectRoot, name);
|
|
4869
|
+
if (existsSync7(path2)) {
|
|
4705
4870
|
try {
|
|
4706
|
-
const content = await
|
|
4871
|
+
const content = await readFile9(path2, "utf-8");
|
|
4707
4872
|
if (content.trim() && used + content.length < budgetChars) {
|
|
4708
4873
|
parts.push("## Project Memory\n" + content.trim());
|
|
4709
4874
|
used += content.length;
|
|
@@ -4714,10 +4879,10 @@ var init_memory = __esm({
|
|
|
4714
4879
|
}
|
|
4715
4880
|
}
|
|
4716
4881
|
}
|
|
4717
|
-
const globalPath =
|
|
4718
|
-
if (
|
|
4882
|
+
const globalPath = join12(this.memoryDir, "..", "workspace", "MEMORY.md");
|
|
4883
|
+
if (existsSync7(globalPath)) {
|
|
4719
4884
|
try {
|
|
4720
|
-
const content = await
|
|
4885
|
+
const content = await readFile9(globalPath, "utf-8");
|
|
4721
4886
|
if (content.trim() && used + content.length < budgetChars) {
|
|
4722
4887
|
parts.push("## Global Memory\n" + content.trim());
|
|
4723
4888
|
used += content.length;
|
|
@@ -4738,8 +4903,8 @@ ${entry.content}`);
|
|
|
4738
4903
|
async add(category, title, content) {
|
|
4739
4904
|
const id = randomUUID3();
|
|
4740
4905
|
const filename = `${title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50)}.md`;
|
|
4741
|
-
const filePath =
|
|
4742
|
-
await
|
|
4906
|
+
const filePath = join12(this.memoryDir, category, filename);
|
|
4907
|
+
await writeFile7(filePath, `# ${title}
|
|
4743
4908
|
|
|
4744
4909
|
${content}`, "utf-8");
|
|
4745
4910
|
this.meta.set(id, {
|
|
@@ -4784,15 +4949,15 @@ ${content}`, "utf-8");
|
|
|
4784
4949
|
async loadAll() {
|
|
4785
4950
|
const entries = [];
|
|
4786
4951
|
for (const category of ["identity", "knowledge", "lessons", "context"]) {
|
|
4787
|
-
const dir =
|
|
4788
|
-
if (!
|
|
4952
|
+
const dir = join12(this.memoryDir, category);
|
|
4953
|
+
if (!existsSync7(dir)) continue;
|
|
4789
4954
|
try {
|
|
4790
4955
|
const files = await readdir5(dir);
|
|
4791
4956
|
for (const file of files) {
|
|
4792
4957
|
if (!file.endsWith(".md")) continue;
|
|
4793
|
-
const filePath =
|
|
4958
|
+
const filePath = join12(dir, file);
|
|
4794
4959
|
try {
|
|
4795
|
-
const content = await
|
|
4960
|
+
const content = await readFile9(filePath, "utf-8");
|
|
4796
4961
|
const title = content.split("\n")[0]?.replace(/^#\s*/, "") || file;
|
|
4797
4962
|
entries.push({
|
|
4798
4963
|
id: file,
|
|
@@ -4817,9 +4982,9 @@ ${content}`, "utf-8");
|
|
|
4817
4982
|
return recency * frequencyBoost;
|
|
4818
4983
|
}
|
|
4819
4984
|
async loadMeta() {
|
|
4820
|
-
if (!
|
|
4985
|
+
if (!existsSync7(this.metaPath)) return;
|
|
4821
4986
|
try {
|
|
4822
|
-
const raw = await
|
|
4987
|
+
const raw = await readFile9(this.metaPath, "utf-8");
|
|
4823
4988
|
const entries = JSON.parse(raw);
|
|
4824
4989
|
for (const e of entries) this.meta.set(e.id, e);
|
|
4825
4990
|
} catch {
|
|
@@ -4827,7 +4992,7 @@ ${content}`, "utf-8");
|
|
|
4827
4992
|
}
|
|
4828
4993
|
async saveMeta() {
|
|
4829
4994
|
const entries = Array.from(this.meta.values());
|
|
4830
|
-
await
|
|
4995
|
+
await writeFile7(this.metaPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
4831
4996
|
}
|
|
4832
4997
|
};
|
|
4833
4998
|
}
|
|
@@ -4839,6 +5004,7 @@ var init_memory2 = __esm({
|
|
|
4839
5004
|
"use strict";
|
|
4840
5005
|
init_esm_shims();
|
|
4841
5006
|
init_memory();
|
|
5007
|
+
init_auto_persist();
|
|
4842
5008
|
}
|
|
4843
5009
|
});
|
|
4844
5010
|
|
|
@@ -5034,15 +5200,53 @@ var init_telegram = __esm({
|
|
|
5034
5200
|
if (!this.gateway) return;
|
|
5035
5201
|
try {
|
|
5036
5202
|
await ctx.api.sendChatAction(chatId, "typing");
|
|
5037
|
-
|
|
5203
|
+
let streamMsgId = null;
|
|
5204
|
+
let accumulated = "";
|
|
5205
|
+
let lastEditTime = 0;
|
|
5206
|
+
const EDIT_INTERVAL = 800;
|
|
5207
|
+
const response = await this.gateway.handleInboundMessageStreaming(
|
|
5038
5208
|
{
|
|
5039
5209
|
channel: "telegram",
|
|
5040
5210
|
peerId: chatId,
|
|
5041
5211
|
peerKind: isGroup ? "group" : "dm"
|
|
5042
5212
|
},
|
|
5043
|
-
msg.text
|
|
5213
|
+
msg.text,
|
|
5214
|
+
{
|
|
5215
|
+
onToken: (content) => {
|
|
5216
|
+
accumulated += content;
|
|
5217
|
+
const now = Date.now();
|
|
5218
|
+
if (!streamMsgId && accumulated.length > 20) {
|
|
5219
|
+
bot.api.sendMessage(chatId, accumulated + " \u258D").then((sent) => {
|
|
5220
|
+
streamMsgId = sent.message_id;
|
|
5221
|
+
lastEditTime = now;
|
|
5222
|
+
}).catch(() => {
|
|
5223
|
+
});
|
|
5224
|
+
return;
|
|
5225
|
+
}
|
|
5226
|
+
if (streamMsgId && now - lastEditTime > EDIT_INTERVAL) {
|
|
5227
|
+
lastEditTime = now;
|
|
5228
|
+
const display = accumulated.length > 4e3 ? accumulated.slice(-3900) + " \u258D" : accumulated + " \u258D";
|
|
5229
|
+
bot.api.editMessageText(chatId, streamMsgId, display).catch(() => {
|
|
5230
|
+
});
|
|
5231
|
+
}
|
|
5232
|
+
},
|
|
5233
|
+
onToolStart: (name) => {
|
|
5234
|
+
if (!streamMsgId) {
|
|
5235
|
+
bot.api.sendChatAction(chatId, "typing").catch(() => {
|
|
5236
|
+
});
|
|
5237
|
+
}
|
|
5238
|
+
},
|
|
5239
|
+
onError: (message) => {
|
|
5240
|
+
bot.api.sendMessage(chatId, `Error: ${message.slice(0, 200)}`).catch(() => {
|
|
5241
|
+
});
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5044
5244
|
);
|
|
5045
|
-
if (response) {
|
|
5245
|
+
if (streamMsgId && response) {
|
|
5246
|
+
const finalText = response.length > 4e3 ? response.slice(0, 3950) + "\n... (truncated)" : response;
|
|
5247
|
+
await bot.api.editMessageText(chatId, streamMsgId, finalText).catch(() => {
|
|
5248
|
+
});
|
|
5249
|
+
} else if (response && !streamMsgId) {
|
|
5046
5250
|
const chunks = splitMessage(response, 4e3);
|
|
5047
5251
|
for (const chunk of chunks) {
|
|
5048
5252
|
await ctx.api.sendMessage(chatId, chunk);
|
|
@@ -5106,7 +5310,8 @@ var init_telegram = __esm({
|
|
|
5106
5310
|
const { TTSEngine: TTSEngine2 } = await Promise.resolve().then(() => (init_voice(), voice_exports));
|
|
5107
5311
|
const tts = new TTSEngine2(config);
|
|
5108
5312
|
if (tts.isAvailable() && response.length < 2e3) {
|
|
5109
|
-
const
|
|
5313
|
+
const agentVoice = config.agents.list.find((a) => a.voiceId)?.voiceId;
|
|
5314
|
+
const audio = await tts.synthesize(response, { voiceId: agentVoice });
|
|
5110
5315
|
if (audio) {
|
|
5111
5316
|
const { InputFile } = await import("grammy");
|
|
5112
5317
|
await ctx.api.sendVoice(chatId, new InputFile(audio.audioBuffer, "reply.mp3"));
|
|
@@ -5128,6 +5333,94 @@ var init_telegram = __esm({
|
|
|
5128
5333
|
});
|
|
5129
5334
|
chatLocks.set(chatId, next);
|
|
5130
5335
|
});
|
|
5336
|
+
bot.on("message:photo", async (ctx) => {
|
|
5337
|
+
const msg = ctx.message;
|
|
5338
|
+
const chatId = msg.chat.id;
|
|
5339
|
+
if (msg.date < startupTime - 30) return;
|
|
5340
|
+
if (telegramConfig.allowFrom && telegramConfig.allowFrom.length > 0) {
|
|
5341
|
+
const username = msg.from?.username ? `@${msg.from.username}` : "";
|
|
5342
|
+
const userIdStr = String(msg.from?.id || "");
|
|
5343
|
+
const allowed = telegramConfig.allowFrom.map(String);
|
|
5344
|
+
if (!allowed.some((a) => a === userIdStr || a.toLowerCase() === username.toLowerCase() || a.toLowerCase() === (msg.from?.username || "").toLowerCase())) return;
|
|
5345
|
+
}
|
|
5346
|
+
const processPhoto = async () => {
|
|
5347
|
+
if (!this.gateway) return;
|
|
5348
|
+
try {
|
|
5349
|
+
const photo = msg.photo[msg.photo.length - 1];
|
|
5350
|
+
const file = await bot.api.getFile(photo.file_id);
|
|
5351
|
+
const fileUrl = `https://api.telegram.org/file/bot${telegramConfig.botToken}/${file.file_path}`;
|
|
5352
|
+
const caption = msg.caption || "";
|
|
5353
|
+
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
|
|
5354
|
+
const response = await this.gateway.handleInboundMessage(
|
|
5355
|
+
{ channel: "telegram", peerId: chatId, peerKind: isGroup ? "group" : "dm" },
|
|
5356
|
+
`[Image received: ${fileUrl}]${caption ? ` Caption: ${caption}` : ""}
|
|
5357
|
+
|
|
5358
|
+
Describe or analyze the image if you can, or acknowledge it.`
|
|
5359
|
+
);
|
|
5360
|
+
if (response) {
|
|
5361
|
+
const chunks = splitMessage(response, 4e3);
|
|
5362
|
+
for (const chunk of chunks) await ctx.api.sendMessage(chatId, chunk);
|
|
5363
|
+
}
|
|
5364
|
+
} catch (err) {
|
|
5365
|
+
await ctx.api.sendMessage(chatId, `Error: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`);
|
|
5366
|
+
}
|
|
5367
|
+
};
|
|
5368
|
+
const prev = chatLocks.get(chatId) || Promise.resolve();
|
|
5369
|
+
chatLocks.set(chatId, prev.then(processPhoto).catch(() => {
|
|
5370
|
+
}));
|
|
5371
|
+
});
|
|
5372
|
+
bot.on("message:document", async (ctx) => {
|
|
5373
|
+
const msg = ctx.message;
|
|
5374
|
+
const chatId = msg.chat.id;
|
|
5375
|
+
if (msg.date < startupTime - 30) return;
|
|
5376
|
+
if (telegramConfig.allowFrom && telegramConfig.allowFrom.length > 0) {
|
|
5377
|
+
const username = msg.from?.username ? `@${msg.from.username}` : "";
|
|
5378
|
+
const userIdStr = String(msg.from?.id || "");
|
|
5379
|
+
const allowed = telegramConfig.allowFrom.map(String);
|
|
5380
|
+
if (!allowed.some((a) => a === userIdStr || a.toLowerCase() === username.toLowerCase() || a.toLowerCase() === (msg.from?.username || "").toLowerCase())) return;
|
|
5381
|
+
}
|
|
5382
|
+
const processDoc = async () => {
|
|
5383
|
+
if (!this.gateway) return;
|
|
5384
|
+
try {
|
|
5385
|
+
const doc = msg.document;
|
|
5386
|
+
if (!doc) return;
|
|
5387
|
+
if (doc.file_size && doc.file_size > 10 * 1024 * 1024) {
|
|
5388
|
+
await ctx.api.sendMessage(chatId, "File too large (max 10MB).");
|
|
5389
|
+
return;
|
|
5390
|
+
}
|
|
5391
|
+
const file = await bot.api.getFile(doc.file_id);
|
|
5392
|
+
const fileUrl = `https://api.telegram.org/file/bot${telegramConfig.botToken}/${file.file_path}`;
|
|
5393
|
+
const res = await fetch(fileUrl);
|
|
5394
|
+
if (!res.ok) {
|
|
5395
|
+
await ctx.api.sendMessage(chatId, "Could not download file.");
|
|
5396
|
+
return;
|
|
5397
|
+
}
|
|
5398
|
+
const { writeFile: wf } = await import("fs/promises");
|
|
5399
|
+
const { join: join20 } = await import("path");
|
|
5400
|
+
const { tmpdir } = await import("os");
|
|
5401
|
+
const safeName = (doc.file_name || "file").replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
5402
|
+
const savePath = join20(tmpdir(), `clank-upload-${Date.now()}-${safeName}`);
|
|
5403
|
+
await wf(savePath, Buffer.from(await res.arrayBuffer()));
|
|
5404
|
+
const caption = msg.caption || "";
|
|
5405
|
+
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
|
|
5406
|
+
const response = await this.gateway.handleInboundMessage(
|
|
5407
|
+
{ channel: "telegram", peerId: chatId, peerKind: isGroup ? "group" : "dm" },
|
|
5408
|
+
`[File received: "${doc.file_name}" saved to ${savePath}]${caption ? ` Note: ${caption}` : ""}
|
|
5409
|
+
|
|
5410
|
+
You can read this file with the read_file tool.`
|
|
5411
|
+
);
|
|
5412
|
+
if (response) {
|
|
5413
|
+
const chunks = splitMessage(response, 4e3);
|
|
5414
|
+
for (const chunk of chunks) await ctx.api.sendMessage(chatId, chunk);
|
|
5415
|
+
}
|
|
5416
|
+
} catch (err) {
|
|
5417
|
+
await ctx.api.sendMessage(chatId, `Error: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`);
|
|
5418
|
+
}
|
|
5419
|
+
};
|
|
5420
|
+
const prev = chatLocks.get(chatId) || Promise.resolve();
|
|
5421
|
+
chatLocks.set(chatId, prev.then(processDoc).catch(() => {
|
|
5422
|
+
}));
|
|
5423
|
+
});
|
|
5131
5424
|
bot.start({
|
|
5132
5425
|
onStart: () => {
|
|
5133
5426
|
this.running = true;
|
|
@@ -5370,9 +5663,9 @@ var init_web = __esm({
|
|
|
5370
5663
|
});
|
|
5371
5664
|
|
|
5372
5665
|
// src/plugins/loader.ts
|
|
5373
|
-
import { readdir as readdir6, readFile as
|
|
5374
|
-
import { existsSync as
|
|
5375
|
-
import { join as
|
|
5666
|
+
import { readdir as readdir6, readFile as readFile10 } from "fs/promises";
|
|
5667
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5668
|
+
import { join as join13 } from "path";
|
|
5376
5669
|
import { homedir as homedir2 } from "os";
|
|
5377
5670
|
var PluginLoader;
|
|
5378
5671
|
var init_loader = __esm({
|
|
@@ -5405,25 +5698,25 @@ var init_loader = __esm({
|
|
|
5405
5698
|
/** Discover plugin directories */
|
|
5406
5699
|
async discoverPlugins() {
|
|
5407
5700
|
const dirs = [];
|
|
5408
|
-
const userPluginDir =
|
|
5409
|
-
if (
|
|
5701
|
+
const userPluginDir = join13(homedir2(), ".clank", "plugins");
|
|
5702
|
+
if (existsSync8(userPluginDir)) {
|
|
5410
5703
|
try {
|
|
5411
5704
|
const entries = await readdir6(userPluginDir, { withFileTypes: true });
|
|
5412
5705
|
for (const entry of entries) {
|
|
5413
5706
|
if (entry.isDirectory()) {
|
|
5414
|
-
dirs.push(
|
|
5707
|
+
dirs.push(join13(userPluginDir, entry.name));
|
|
5415
5708
|
}
|
|
5416
5709
|
}
|
|
5417
5710
|
} catch {
|
|
5418
5711
|
}
|
|
5419
5712
|
}
|
|
5420
|
-
const nodeModulesDir =
|
|
5421
|
-
if (
|
|
5713
|
+
const nodeModulesDir = join13(process.cwd(), "node_modules");
|
|
5714
|
+
if (existsSync8(nodeModulesDir)) {
|
|
5422
5715
|
try {
|
|
5423
5716
|
const entries = await readdir6(nodeModulesDir);
|
|
5424
5717
|
for (const entry of entries) {
|
|
5425
5718
|
if (entry.startsWith("clank-plugin-")) {
|
|
5426
|
-
dirs.push(
|
|
5719
|
+
dirs.push(join13(nodeModulesDir, entry));
|
|
5427
5720
|
}
|
|
5428
5721
|
}
|
|
5429
5722
|
} catch {
|
|
@@ -5433,9 +5726,9 @@ var init_loader = __esm({
|
|
|
5433
5726
|
}
|
|
5434
5727
|
/** Load a single plugin from a directory */
|
|
5435
5728
|
async loadPlugin(dir) {
|
|
5436
|
-
const manifestPath =
|
|
5437
|
-
if (!
|
|
5438
|
-
const raw = await
|
|
5729
|
+
const manifestPath = join13(dir, "clank-plugin.json");
|
|
5730
|
+
if (!existsSync8(manifestPath)) return null;
|
|
5731
|
+
const raw = await readFile10(manifestPath, "utf-8");
|
|
5439
5732
|
const manifest = JSON.parse(raw);
|
|
5440
5733
|
if (!manifest.name) return null;
|
|
5441
5734
|
const plugin = {
|
|
@@ -5447,7 +5740,7 @@ var init_loader = __esm({
|
|
|
5447
5740
|
if (manifest.tools) {
|
|
5448
5741
|
for (const toolEntry of manifest.tools) {
|
|
5449
5742
|
try {
|
|
5450
|
-
const entrypoint =
|
|
5743
|
+
const entrypoint = join13(dir, toolEntry.entrypoint);
|
|
5451
5744
|
const mod = await import(entrypoint);
|
|
5452
5745
|
const tool = mod.default || mod.tool;
|
|
5453
5746
|
if (tool) {
|
|
@@ -5461,7 +5754,7 @@ var init_loader = __esm({
|
|
|
5461
5754
|
if (manifest.hooks) {
|
|
5462
5755
|
for (const hookEntry of manifest.hooks) {
|
|
5463
5756
|
try {
|
|
5464
|
-
const handlerPath =
|
|
5757
|
+
const handlerPath = join13(dir, hookEntry.handler);
|
|
5465
5758
|
const mod = await import(handlerPath);
|
|
5466
5759
|
const handler = mod.default || mod.handler;
|
|
5467
5760
|
if (handler) {
|
|
@@ -5519,8 +5812,8 @@ var init_plugins = __esm({
|
|
|
5519
5812
|
// src/gateway/server.ts
|
|
5520
5813
|
import { createServer } from "http";
|
|
5521
5814
|
import { WebSocketServer, WebSocket } from "ws";
|
|
5522
|
-
import { readFile as
|
|
5523
|
-
import { join as
|
|
5815
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
5816
|
+
import { join as join14, dirname as dirname2 } from "path";
|
|
5524
5817
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5525
5818
|
var GatewayServer;
|
|
5526
5819
|
var init_server = __esm({
|
|
@@ -5554,12 +5847,18 @@ var init_server = __esm({
|
|
|
5554
5847
|
pluginLoader;
|
|
5555
5848
|
adapters = [];
|
|
5556
5849
|
running = false;
|
|
5850
|
+
/** Rate limiting: track message timestamps per session */
|
|
5851
|
+
rateLimiter = /* @__PURE__ */ new Map();
|
|
5852
|
+
RATE_LIMIT_WINDOW = 6e4;
|
|
5853
|
+
// 1 minute
|
|
5854
|
+
RATE_LIMIT_MAX = 20;
|
|
5855
|
+
// max 20 messages per minute per session
|
|
5557
5856
|
constructor(config) {
|
|
5558
5857
|
this.config = config;
|
|
5559
|
-
this.sessionStore = new SessionStore(
|
|
5858
|
+
this.sessionStore = new SessionStore(join14(getConfigDir(), "conversations"));
|
|
5560
5859
|
this.toolRegistry = createFullRegistry();
|
|
5561
|
-
this.memoryManager = new MemoryManager(
|
|
5562
|
-
this.cronScheduler = new CronScheduler(
|
|
5860
|
+
this.memoryManager = new MemoryManager(join14(getConfigDir(), "memory"));
|
|
5861
|
+
this.cronScheduler = new CronScheduler(join14(getConfigDir(), "cron"));
|
|
5563
5862
|
this.configWatcher = new ConfigWatcher();
|
|
5564
5863
|
this.pluginLoader = new PluginLoader();
|
|
5565
5864
|
}
|
|
@@ -5629,6 +5928,10 @@ var init_server = __esm({
|
|
|
5629
5928
|
* This is the main entry point for all non-WebSocket messages.
|
|
5630
5929
|
*/
|
|
5631
5930
|
async handleInboundMessage(context, text) {
|
|
5931
|
+
const rlKey = deriveSessionKey(context);
|
|
5932
|
+
if (this.isRateLimited(rlKey)) {
|
|
5933
|
+
throw new Error("Rate limited \u2014 too many messages. Wait a moment.");
|
|
5934
|
+
}
|
|
5632
5935
|
const agentId = resolveRoute(
|
|
5633
5936
|
context,
|
|
5634
5937
|
[],
|
|
@@ -5640,6 +5943,51 @@ var init_server = __esm({
|
|
|
5640
5943
|
const engine = await this.getOrCreateEngine(sessionKey, agentId, context.channel);
|
|
5641
5944
|
return engine.sendMessage(text);
|
|
5642
5945
|
}
|
|
5946
|
+
/**
|
|
5947
|
+
* Handle an inbound message with streaming callbacks.
|
|
5948
|
+
* Used by channel adapters for real-time streaming (e.g., Telegram message editing).
|
|
5949
|
+
*/
|
|
5950
|
+
async handleInboundMessageStreaming(context, text, callbacks) {
|
|
5951
|
+
const agentId = resolveRoute(
|
|
5952
|
+
context,
|
|
5953
|
+
[],
|
|
5954
|
+
this.config.agents.list.map((a) => ({ id: a.id, name: a.name })),
|
|
5955
|
+
this.config.agents.list[0]?.id || "default"
|
|
5956
|
+
);
|
|
5957
|
+
const sessionKey = deriveSessionKey(context);
|
|
5958
|
+
const engine = await this.getOrCreateEngine(sessionKey, agentId, context.channel);
|
|
5959
|
+
const listeners = [];
|
|
5960
|
+
if (callbacks.onToken) {
|
|
5961
|
+
const fn = (data) => callbacks.onToken(data.content);
|
|
5962
|
+
engine.on("token", fn);
|
|
5963
|
+
listeners.push(["token", fn]);
|
|
5964
|
+
}
|
|
5965
|
+
if (callbacks.onToolStart) {
|
|
5966
|
+
const fn = (data) => callbacks.onToolStart(data.name);
|
|
5967
|
+
engine.on("tool-start", fn);
|
|
5968
|
+
listeners.push(["tool-start", fn]);
|
|
5969
|
+
}
|
|
5970
|
+
if (callbacks.onToolResult) {
|
|
5971
|
+
const fn = (data) => {
|
|
5972
|
+
const d = data;
|
|
5973
|
+
callbacks.onToolResult(d.name, d.success);
|
|
5974
|
+
};
|
|
5975
|
+
engine.on("tool-result", fn);
|
|
5976
|
+
listeners.push(["tool-result", fn]);
|
|
5977
|
+
}
|
|
5978
|
+
if (callbacks.onError) {
|
|
5979
|
+
const fn = (data) => callbacks.onError(data.message);
|
|
5980
|
+
engine.on("error", fn);
|
|
5981
|
+
listeners.push(["error", fn]);
|
|
5982
|
+
}
|
|
5983
|
+
try {
|
|
5984
|
+
return await engine.sendMessage(text);
|
|
5985
|
+
} finally {
|
|
5986
|
+
for (const [event, fn] of listeners) {
|
|
5987
|
+
engine.removeListener(event, fn);
|
|
5988
|
+
}
|
|
5989
|
+
}
|
|
5990
|
+
}
|
|
5643
5991
|
/** Stop the gateway server */
|
|
5644
5992
|
async stop() {
|
|
5645
5993
|
this.running = false;
|
|
@@ -5673,7 +6021,7 @@ var init_server = __esm({
|
|
|
5673
6021
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5674
6022
|
res.end(JSON.stringify({
|
|
5675
6023
|
status: "ok",
|
|
5676
|
-
version: "1.
|
|
6024
|
+
version: "1.4.0",
|
|
5677
6025
|
uptime: process.uptime(),
|
|
5678
6026
|
clients: this.clients.size,
|
|
5679
6027
|
agents: this.engines.size
|
|
@@ -5703,14 +6051,14 @@ var init_server = __esm({
|
|
|
5703
6051
|
if (url === "/chat" || url === "/") {
|
|
5704
6052
|
try {
|
|
5705
6053
|
const __dirname4 = dirname2(fileURLToPath2(import.meta.url));
|
|
5706
|
-
const htmlPath =
|
|
5707
|
-
const html = await
|
|
6054
|
+
const htmlPath = join14(__dirname4, "..", "web", "index.html");
|
|
6055
|
+
const html = await readFile11(htmlPath, "utf-8");
|
|
5708
6056
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
5709
6057
|
res.end(html);
|
|
5710
6058
|
return;
|
|
5711
6059
|
} catch {
|
|
5712
6060
|
try {
|
|
5713
|
-
const html = await
|
|
6061
|
+
const html = await readFile11(join14(process.cwd(), "src", "web", "index.html"), "utf-8");
|
|
5714
6062
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
5715
6063
|
res.end(html);
|
|
5716
6064
|
return;
|
|
@@ -5781,7 +6129,7 @@ var init_server = __esm({
|
|
|
5781
6129
|
const hello = {
|
|
5782
6130
|
type: "hello",
|
|
5783
6131
|
protocol: PROTOCOL_VERSION,
|
|
5784
|
-
version: "1.
|
|
6132
|
+
version: "1.4.0",
|
|
5785
6133
|
agents: this.config.agents.list.map((a) => ({
|
|
5786
6134
|
id: a.id,
|
|
5787
6135
|
name: a.name || a.id,
|
|
@@ -5955,6 +6303,10 @@ var init_server = __esm({
|
|
|
5955
6303
|
this.sendResponse(client, frame.id, false, void 0, "message is required");
|
|
5956
6304
|
return;
|
|
5957
6305
|
}
|
|
6306
|
+
if (this.isRateLimited(client.sessionKey)) {
|
|
6307
|
+
this.sendResponse(client, frame.id, false, void 0, "Rate limited \u2014 too many messages. Wait a moment.");
|
|
6308
|
+
return;
|
|
6309
|
+
}
|
|
5958
6310
|
try {
|
|
5959
6311
|
const engine = await this.getOrCreateEngine(client.sessionKey, client.agentId, client.clientName);
|
|
5960
6312
|
const cleanup = this.wireEngineEvents(engine, client);
|
|
@@ -5969,6 +6321,15 @@ var init_server = __esm({
|
|
|
5969
6321
|
this.sendResponse(client, frame.id, false, void 0, msg);
|
|
5970
6322
|
}
|
|
5971
6323
|
}
|
|
6324
|
+
/** Check if a session is rate limited */
|
|
6325
|
+
isRateLimited(sessionKey) {
|
|
6326
|
+
const now = Date.now();
|
|
6327
|
+
const timestamps = this.rateLimiter.get(sessionKey) || [];
|
|
6328
|
+
const recent = timestamps.filter((t) => now - t < this.RATE_LIMIT_WINDOW);
|
|
6329
|
+
recent.push(now);
|
|
6330
|
+
this.rateLimiter.set(sessionKey, recent);
|
|
6331
|
+
return recent.length > this.RATE_LIMIT_MAX;
|
|
6332
|
+
}
|
|
5972
6333
|
/** Cancel current request for a client */
|
|
5973
6334
|
handleCancel(client) {
|
|
5974
6335
|
const engine = this.engines.get(client.sessionKey);
|
|
@@ -5994,10 +6355,14 @@ var init_server = __esm({
|
|
|
5994
6355
|
toolTier: agentConfig?.toolTier || this.config.agents.defaults.toolTier || "auto",
|
|
5995
6356
|
tools: agentConfig?.tools
|
|
5996
6357
|
};
|
|
6358
|
+
const compact = agentConfig?.compactPrompt ?? this.config.agents.defaults.compactPrompt ?? false;
|
|
6359
|
+
const thinking = agentConfig?.thinking ?? this.config.agents.defaults.thinking ?? "auto";
|
|
5997
6360
|
const systemPrompt = await buildSystemPrompt({
|
|
5998
6361
|
identity,
|
|
5999
6362
|
workspaceDir: identity.workspace,
|
|
6000
|
-
channel
|
|
6363
|
+
channel,
|
|
6364
|
+
compact,
|
|
6365
|
+
thinking
|
|
6001
6366
|
});
|
|
6002
6367
|
const memoryBlock = await this.memoryManager.buildMemoryBlock("", identity.workspace);
|
|
6003
6368
|
const fullPrompt = memoryBlock ? systemPrompt + "\n\n---\n\n" + memoryBlock : systemPrompt;
|
|
@@ -6112,9 +6477,9 @@ __export(gateway_cmd_exports, {
|
|
|
6112
6477
|
isGatewayRunning: () => isGatewayRunning
|
|
6113
6478
|
});
|
|
6114
6479
|
import { fork } from "child_process";
|
|
6115
|
-
import { writeFile as
|
|
6116
|
-
import { existsSync as
|
|
6117
|
-
import { join as
|
|
6480
|
+
import { writeFile as writeFile8, readFile as readFile12, unlink as unlink3 } from "fs/promises";
|
|
6481
|
+
import { existsSync as existsSync9 } from "fs";
|
|
6482
|
+
import { join as join15, dirname as dirname3 } from "path";
|
|
6118
6483
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6119
6484
|
async function isGatewayRunning(port) {
|
|
6120
6485
|
const config = await loadConfig();
|
|
@@ -6127,7 +6492,7 @@ async function isGatewayRunning(port) {
|
|
|
6127
6492
|
}
|
|
6128
6493
|
}
|
|
6129
6494
|
function pidFilePath() {
|
|
6130
|
-
return
|
|
6495
|
+
return join15(getConfigDir(), "gateway.pid");
|
|
6131
6496
|
}
|
|
6132
6497
|
async function gatewayStartForeground(opts) {
|
|
6133
6498
|
await ensureConfigDir();
|
|
@@ -6139,7 +6504,7 @@ async function gatewayStartForeground(opts) {
|
|
|
6139
6504
|
console.log(green2(` Gateway already running on port ${config.gateway.port}`));
|
|
6140
6505
|
return;
|
|
6141
6506
|
}
|
|
6142
|
-
await
|
|
6507
|
+
await writeFile8(pidFilePath(), String(process.pid), "utf-8");
|
|
6143
6508
|
const server = new GatewayServer(config);
|
|
6144
6509
|
const shutdown = async () => {
|
|
6145
6510
|
console.log(dim2("\nShutting down..."));
|
|
@@ -6172,10 +6537,10 @@ async function gatewayStartBackground() {
|
|
|
6172
6537
|
return true;
|
|
6173
6538
|
}
|
|
6174
6539
|
console.log(dim2(" Starting gateway in background..."));
|
|
6175
|
-
const entryPoint =
|
|
6176
|
-
const logFile =
|
|
6540
|
+
const entryPoint = join15(dirname3(__filename2), "index.js");
|
|
6541
|
+
const logFile = join15(getConfigDir(), "logs", "gateway.log");
|
|
6177
6542
|
const { mkdir: mkdir7 } = await import("fs/promises");
|
|
6178
|
-
await mkdir7(
|
|
6543
|
+
await mkdir7(join15(getConfigDir(), "logs"), { recursive: true });
|
|
6179
6544
|
const child = fork(entryPoint, ["gateway", "start", "--foreground"], {
|
|
6180
6545
|
detached: true,
|
|
6181
6546
|
stdio: ["ignore", "ignore", "ignore", "ipc"]
|
|
@@ -6204,9 +6569,9 @@ async function gatewayStart(opts) {
|
|
|
6204
6569
|
}
|
|
6205
6570
|
async function gatewayStop() {
|
|
6206
6571
|
const pidPath = pidFilePath();
|
|
6207
|
-
if (
|
|
6572
|
+
if (existsSync9(pidPath)) {
|
|
6208
6573
|
try {
|
|
6209
|
-
const pid = parseInt(await
|
|
6574
|
+
const pid = parseInt(await readFile12(pidPath, "utf-8"), 10);
|
|
6210
6575
|
process.kill(pid, "SIGTERM");
|
|
6211
6576
|
await unlink3(pidPath);
|
|
6212
6577
|
console.log(green2("Gateway stopped"));
|
|
@@ -6237,8 +6602,8 @@ async function gatewayStatus() {
|
|
|
6237
6602
|
console.log(dim2(` Clients: ${data.clients?.length || 0}`));
|
|
6238
6603
|
console.log(dim2(` Sessions: ${data.sessions?.length || 0}`));
|
|
6239
6604
|
const pidPath = pidFilePath();
|
|
6240
|
-
if (
|
|
6241
|
-
const pid = await
|
|
6605
|
+
if (existsSync9(pidPath)) {
|
|
6606
|
+
const pid = await readFile12(pidPath, "utf-8");
|
|
6242
6607
|
console.log(dim2(` PID: ${pid.trim()}`));
|
|
6243
6608
|
}
|
|
6244
6609
|
} else {
|
|
@@ -6266,8 +6631,8 @@ var init_gateway_cmd = __esm({
|
|
|
6266
6631
|
});
|
|
6267
6632
|
|
|
6268
6633
|
// src/daemon/install.ts
|
|
6269
|
-
import { writeFile as
|
|
6270
|
-
import { join as
|
|
6634
|
+
import { writeFile as writeFile9, mkdir as mkdir6, unlink as unlink4 } from "fs/promises";
|
|
6635
|
+
import { join as join16 } from "path";
|
|
6271
6636
|
import { homedir as homedir3, platform as platform4 } from "os";
|
|
6272
6637
|
import { execSync } from "child_process";
|
|
6273
6638
|
async function installDaemon() {
|
|
@@ -6335,8 +6700,8 @@ async function daemonStatus() {
|
|
|
6335
6700
|
}
|
|
6336
6701
|
}
|
|
6337
6702
|
async function installLaunchd() {
|
|
6338
|
-
const plistDir =
|
|
6339
|
-
const plistPath =
|
|
6703
|
+
const plistDir = join16(homedir3(), "Library", "LaunchAgents");
|
|
6704
|
+
const plistPath = join16(plistDir, "com.clank.gateway.plist");
|
|
6340
6705
|
const clankPath = execSync("which clank || echo clank", { encoding: "utf-8" }).trim();
|
|
6341
6706
|
await mkdir6(plistDir, { recursive: true });
|
|
6342
6707
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -6357,18 +6722,18 @@ async function installLaunchd() {
|
|
|
6357
6722
|
<key>KeepAlive</key>
|
|
6358
6723
|
<true/>
|
|
6359
6724
|
<key>StandardOutPath</key>
|
|
6360
|
-
<string>${
|
|
6725
|
+
<string>${join16(homedir3(), ".clank", "logs", "gateway.log")}</string>
|
|
6361
6726
|
<key>StandardErrorPath</key>
|
|
6362
|
-
<string>${
|
|
6727
|
+
<string>${join16(homedir3(), ".clank", "logs", "gateway-error.log")}</string>
|
|
6363
6728
|
</dict>
|
|
6364
6729
|
</plist>`;
|
|
6365
|
-
await
|
|
6730
|
+
await writeFile9(plistPath, plist, "utf-8");
|
|
6366
6731
|
execSync(`launchctl load "${plistPath}"`);
|
|
6367
6732
|
console.log(green3("Daemon installed (launchd)"));
|
|
6368
6733
|
console.log(dim3(` Plist: ${plistPath}`));
|
|
6369
6734
|
}
|
|
6370
6735
|
async function uninstallLaunchd() {
|
|
6371
|
-
const plistPath =
|
|
6736
|
+
const plistPath = join16(homedir3(), "Library", "LaunchAgents", "com.clank.gateway.plist");
|
|
6372
6737
|
try {
|
|
6373
6738
|
execSync(`launchctl unload "${plistPath}"`);
|
|
6374
6739
|
await unlink4(plistPath);
|
|
@@ -6406,8 +6771,8 @@ async function uninstallTaskScheduler() {
|
|
|
6406
6771
|
}
|
|
6407
6772
|
}
|
|
6408
6773
|
async function installSystemd() {
|
|
6409
|
-
const unitDir =
|
|
6410
|
-
const unitPath =
|
|
6774
|
+
const unitDir = join16(homedir3(), ".config", "systemd", "user");
|
|
6775
|
+
const unitPath = join16(unitDir, "clank-gateway.service");
|
|
6411
6776
|
const clankPath = execSync("which clank || echo clank", { encoding: "utf-8" }).trim();
|
|
6412
6777
|
await mkdir6(unitDir, { recursive: true });
|
|
6413
6778
|
const unit = `[Unit]
|
|
@@ -6422,7 +6787,7 @@ RestartSec=5
|
|
|
6422
6787
|
[Install]
|
|
6423
6788
|
WantedBy=default.target
|
|
6424
6789
|
`;
|
|
6425
|
-
await
|
|
6790
|
+
await writeFile9(unitPath, unit, "utf-8");
|
|
6426
6791
|
execSync("systemctl --user daemon-reload");
|
|
6427
6792
|
execSync("systemctl --user enable clank-gateway");
|
|
6428
6793
|
execSync("systemctl --user start clank-gateway");
|
|
@@ -6433,7 +6798,7 @@ async function uninstallSystemd() {
|
|
|
6433
6798
|
try {
|
|
6434
6799
|
execSync("systemctl --user stop clank-gateway");
|
|
6435
6800
|
execSync("systemctl --user disable clank-gateway");
|
|
6436
|
-
const unitPath =
|
|
6801
|
+
const unitPath = join16(homedir3(), ".config", "systemd", "user", "clank-gateway.service");
|
|
6437
6802
|
await unlink4(unitPath);
|
|
6438
6803
|
execSync("systemctl --user daemon-reload");
|
|
6439
6804
|
console.log(green3("Daemon uninstalled"));
|
|
@@ -6474,7 +6839,7 @@ __export(setup_exports, {
|
|
|
6474
6839
|
});
|
|
6475
6840
|
import { createInterface as createInterface2 } from "readline";
|
|
6476
6841
|
import { randomBytes } from "crypto";
|
|
6477
|
-
import { dirname as dirname4, join as
|
|
6842
|
+
import { dirname as dirname4, join as join17 } from "path";
|
|
6478
6843
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6479
6844
|
function ask(rl, question) {
|
|
6480
6845
|
return new Promise((resolve4) => rl.question(question, resolve4));
|
|
@@ -6577,8 +6942,8 @@ async function runSetup(opts) {
|
|
|
6577
6942
|
console.log("");
|
|
6578
6943
|
console.log(dim4(" Creating workspace..."));
|
|
6579
6944
|
const { ensureWorkspaceFiles: ensureWorkspaceFiles2 } = await Promise.resolve().then(() => (init_system_prompt(), system_prompt_exports));
|
|
6580
|
-
const templateDir =
|
|
6581
|
-
const wsDir =
|
|
6945
|
+
const templateDir = join17(__dirname2, "..", "workspace", "templates");
|
|
6946
|
+
const wsDir = join17(getConfigDir(), "workspace");
|
|
6582
6947
|
try {
|
|
6583
6948
|
await ensureWorkspaceFiles2(wsDir, templateDir);
|
|
6584
6949
|
} catch {
|
|
@@ -6642,27 +7007,35 @@ async function runSetup(opts) {
|
|
|
6642
7007
|
console.log(green4(" ElevenLabs configured (TTS available)"));
|
|
6643
7008
|
}
|
|
6644
7009
|
}
|
|
6645
|
-
const addWhisper = await ask(rl, cyan2(" Set up speech-to-text (
|
|
7010
|
+
const addWhisper = await ask(rl, cyan2(" Set up speech-to-text (voice messages)? [y/N] "));
|
|
6646
7011
|
if (addWhisper.toLowerCase() === "y") {
|
|
6647
|
-
console.log(dim4(" 1.
|
|
6648
|
-
console.log(dim4(" 2.
|
|
7012
|
+
console.log(dim4(" 1. Groq (recommended \u2014 free, fast)"));
|
|
7013
|
+
console.log(dim4(" 2. OpenAI Whisper API (paid, uses OpenAI key)"));
|
|
7014
|
+
console.log(dim4(" 3. Local whisper.cpp (requires manual install)"));
|
|
6649
7015
|
const whisperChoice = await ask(rl, cyan2(" Choice [1]: "));
|
|
6650
|
-
if (whisperChoice === "
|
|
7016
|
+
if (whisperChoice === "3") {
|
|
6651
7017
|
config.integrations.whisper = { enabled: true, provider: "local" };
|
|
6652
7018
|
console.log(green4(" Local whisper.cpp configured"));
|
|
6653
7019
|
console.log(dim4(" Make sure whisper is installed and in PATH"));
|
|
6654
|
-
} else {
|
|
7020
|
+
} else if (whisperChoice === "2") {
|
|
6655
7021
|
const existingKey = config.models.providers.openai?.apiKey;
|
|
6656
7022
|
if (existingKey) {
|
|
6657
7023
|
config.integrations.whisper = { enabled: true, provider: "openai", apiKey: existingKey };
|
|
6658
7024
|
console.log(green4(" Whisper configured (using existing OpenAI key)"));
|
|
6659
7025
|
} else {
|
|
6660
|
-
const key = await ask(rl, cyan2(" OpenAI API key
|
|
7026
|
+
const key = await ask(rl, cyan2(" OpenAI API key: "));
|
|
6661
7027
|
if (key.trim()) {
|
|
6662
7028
|
config.integrations.whisper = { enabled: true, provider: "openai", apiKey: key.trim() };
|
|
6663
7029
|
console.log(green4(" Whisper configured"));
|
|
6664
7030
|
}
|
|
6665
7031
|
}
|
|
7032
|
+
} else {
|
|
7033
|
+
console.log(dim4(" Get a free API key at: https://console.groq.com/keys"));
|
|
7034
|
+
const key = await ask(rl, cyan2(" Groq API key: "));
|
|
7035
|
+
if (key.trim()) {
|
|
7036
|
+
config.integrations.whisper = { enabled: true, provider: "groq", apiKey: key.trim() };
|
|
7037
|
+
console.log(green4(" Groq Whisper configured (free, fast)"));
|
|
7038
|
+
}
|
|
6666
7039
|
}
|
|
6667
7040
|
}
|
|
6668
7041
|
if (isAdvanced) {
|
|
@@ -6731,9 +7104,9 @@ var fix_exports = {};
|
|
|
6731
7104
|
__export(fix_exports, {
|
|
6732
7105
|
runFix: () => runFix
|
|
6733
7106
|
});
|
|
6734
|
-
import { existsSync as
|
|
7107
|
+
import { existsSync as existsSync10 } from "fs";
|
|
6735
7108
|
import { readdir as readdir7 } from "fs/promises";
|
|
6736
|
-
import { join as
|
|
7109
|
+
import { join as join18 } from "path";
|
|
6737
7110
|
async function runFix(opts) {
|
|
6738
7111
|
console.log("");
|
|
6739
7112
|
console.log(" Clank Diagnostics");
|
|
@@ -6763,7 +7136,7 @@ async function runFix(opts) {
|
|
|
6763
7136
|
}
|
|
6764
7137
|
async function checkConfig() {
|
|
6765
7138
|
const configPath = getConfigPath();
|
|
6766
|
-
if (!
|
|
7139
|
+
if (!existsSync10(configPath)) {
|
|
6767
7140
|
return {
|
|
6768
7141
|
name: "Config",
|
|
6769
7142
|
status: "warn",
|
|
@@ -6831,8 +7204,8 @@ async function checkModels() {
|
|
|
6831
7204
|
return { name: "Model (primary)", status: "ok", message: modelId };
|
|
6832
7205
|
}
|
|
6833
7206
|
async function checkSessions() {
|
|
6834
|
-
const sessDir =
|
|
6835
|
-
if (!
|
|
7207
|
+
const sessDir = join18(getConfigDir(), "conversations");
|
|
7208
|
+
if (!existsSync10(sessDir)) {
|
|
6836
7209
|
return { name: "Sessions", status: "ok", message: "no sessions yet" };
|
|
6837
7210
|
}
|
|
6838
7211
|
try {
|
|
@@ -6844,8 +7217,8 @@ async function checkSessions() {
|
|
|
6844
7217
|
}
|
|
6845
7218
|
}
|
|
6846
7219
|
async function checkWorkspace() {
|
|
6847
|
-
const wsDir =
|
|
6848
|
-
if (!
|
|
7220
|
+
const wsDir = join18(getConfigDir(), "workspace");
|
|
7221
|
+
if (!existsSync10(wsDir)) {
|
|
6849
7222
|
return {
|
|
6850
7223
|
name: "Workspace",
|
|
6851
7224
|
status: "warn",
|
|
@@ -7138,7 +7511,7 @@ async function runTui(opts) {
|
|
|
7138
7511
|
ws.on("open", () => {
|
|
7139
7512
|
ws.send(JSON.stringify({
|
|
7140
7513
|
type: "connect",
|
|
7141
|
-
params: { auth: { token }, mode: "tui", version: "1.
|
|
7514
|
+
params: { auth: { token }, mode: "tui", version: "1.4.0" }
|
|
7142
7515
|
}));
|
|
7143
7516
|
});
|
|
7144
7517
|
ws.on("message", (data) => {
|
|
@@ -7487,7 +7860,7 @@ __export(uninstall_exports, {
|
|
|
7487
7860
|
});
|
|
7488
7861
|
import { createInterface as createInterface4 } from "readline";
|
|
7489
7862
|
import { rm } from "fs/promises";
|
|
7490
|
-
import { existsSync as
|
|
7863
|
+
import { existsSync as existsSync11 } from "fs";
|
|
7491
7864
|
async function runUninstall(opts) {
|
|
7492
7865
|
const configDir = getConfigDir();
|
|
7493
7866
|
console.log("");
|
|
@@ -7526,7 +7899,7 @@ async function runUninstall(opts) {
|
|
|
7526
7899
|
} catch {
|
|
7527
7900
|
}
|
|
7528
7901
|
console.log(dim10(" Deleting data..."));
|
|
7529
|
-
if (
|
|
7902
|
+
if (existsSync11(configDir)) {
|
|
7530
7903
|
await rm(configDir, { recursive: true, force: true });
|
|
7531
7904
|
console.log(green10(` Removed ${configDir}`));
|
|
7532
7905
|
} else {
|
|
@@ -7564,12 +7937,12 @@ init_esm_shims();
|
|
|
7564
7937
|
import { Command } from "commander";
|
|
7565
7938
|
import { readFileSync } from "fs";
|
|
7566
7939
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7567
|
-
import { dirname as dirname5, join as
|
|
7940
|
+
import { dirname as dirname5, join as join19 } from "path";
|
|
7568
7941
|
var __filename3 = fileURLToPath5(import.meta.url);
|
|
7569
7942
|
var __dirname3 = dirname5(__filename3);
|
|
7570
|
-
var version = "1.
|
|
7943
|
+
var version = "1.4.0";
|
|
7571
7944
|
try {
|
|
7572
|
-
const pkg = JSON.parse(readFileSync(
|
|
7945
|
+
const pkg = JSON.parse(readFileSync(join19(__dirname3, "..", "package.json"), "utf-8"));
|
|
7573
7946
|
version = pkg.version;
|
|
7574
7947
|
} catch {
|
|
7575
7948
|
}
|
|
@@ -7678,10 +8051,10 @@ pipeline.command("status <id>").description("Check pipeline execution status").a
|
|
|
7678
8051
|
});
|
|
7679
8052
|
var cron = program.command("cron").description("Manage scheduled jobs");
|
|
7680
8053
|
cron.command("list").description("List cron jobs").action(async () => {
|
|
7681
|
-
const { join:
|
|
8054
|
+
const { join: join20 } = await import("path");
|
|
7682
8055
|
const { getConfigDir: getConfigDir3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
7683
8056
|
const { CronScheduler: CronScheduler2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
7684
|
-
const scheduler = new CronScheduler2(
|
|
8057
|
+
const scheduler = new CronScheduler2(join20(getConfigDir3(), "cron"));
|
|
7685
8058
|
await scheduler.init();
|
|
7686
8059
|
const jobs = scheduler.listJobs();
|
|
7687
8060
|
if (jobs.length === 0) {
|
|
@@ -7693,10 +8066,10 @@ cron.command("list").description("List cron jobs").action(async () => {
|
|
|
7693
8066
|
}
|
|
7694
8067
|
});
|
|
7695
8068
|
cron.command("add").description("Add a cron job").requiredOption("--schedule <expr>", "Schedule (e.g., '1h', '30m', 'daily')").requiredOption("--prompt <text>", "What the agent should do").option("--name <name>", "Job name").option("--agent <id>", "Agent ID", "default").action(async (opts) => {
|
|
7696
|
-
const { join:
|
|
8069
|
+
const { join: join20 } = await import("path");
|
|
7697
8070
|
const { getConfigDir: getConfigDir3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
7698
8071
|
const { CronScheduler: CronScheduler2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
7699
|
-
const scheduler = new CronScheduler2(
|
|
8072
|
+
const scheduler = new CronScheduler2(join20(getConfigDir3(), "cron"));
|
|
7700
8073
|
await scheduler.init();
|
|
7701
8074
|
const job = await scheduler.addJob({
|
|
7702
8075
|
name: opts.name || "CLI Job",
|
|
@@ -7707,10 +8080,10 @@ cron.command("add").description("Add a cron job").requiredOption("--schedule <ex
|
|
|
7707
8080
|
console.log(` Job created: ${job.id.slice(0, 8)} \u2014 "${job.name}" every ${job.schedule}`);
|
|
7708
8081
|
});
|
|
7709
8082
|
cron.command("remove <id>").description("Remove a cron job").action(async (id) => {
|
|
7710
|
-
const { join:
|
|
8083
|
+
const { join: join20 } = await import("path");
|
|
7711
8084
|
const { getConfigDir: getConfigDir3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
7712
8085
|
const { CronScheduler: CronScheduler2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
7713
|
-
const scheduler = new CronScheduler2(
|
|
8086
|
+
const scheduler = new CronScheduler2(join20(getConfigDir3(), "cron"));
|
|
7714
8087
|
await scheduler.init();
|
|
7715
8088
|
const removed = await scheduler.removeJob(id);
|
|
7716
8089
|
console.log(removed ? ` Job ${id.slice(0, 8)} removed` : ` Job not found`);
|