@tractorscorch/clank 1.3.1 → 1.4.1
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 +43 -0
- package/dist/index.js +619 -226
- 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
|
}
|
|
@@ -1165,6 +1276,10 @@ var init_registry = __esm({
|
|
|
1165
1276
|
});
|
|
1166
1277
|
|
|
1167
1278
|
// src/tools/path-guard.ts
|
|
1279
|
+
var path_guard_exports = {};
|
|
1280
|
+
__export(path_guard_exports, {
|
|
1281
|
+
guardPath: () => guardPath
|
|
1282
|
+
});
|
|
1168
1283
|
import { resolve, isAbsolute, normalize, relative } from "path";
|
|
1169
1284
|
function guardPath(inputPath, projectRoot, opts) {
|
|
1170
1285
|
const resolved = isAbsolute(inputPath) ? normalize(inputPath) : normalize(resolve(projectRoot, inputPath));
|
|
@@ -1186,7 +1301,7 @@ var init_path_guard = __esm({
|
|
|
1186
1301
|
});
|
|
1187
1302
|
|
|
1188
1303
|
// src/tools/read-file.ts
|
|
1189
|
-
import { readFile as
|
|
1304
|
+
import { readFile as readFile3, stat } from "fs/promises";
|
|
1190
1305
|
var readFileTool;
|
|
1191
1306
|
var init_read_file = __esm({
|
|
1192
1307
|
"src/tools/read-file.ts"() {
|
|
@@ -1235,7 +1350,7 @@ var init_read_file = __esm({
|
|
|
1235
1350
|
if (probe.subarray(0, probeLen).includes(0)) {
|
|
1236
1351
|
return `Binary file detected: ${filePath} (${fileStats.size} bytes)`;
|
|
1237
1352
|
}
|
|
1238
|
-
const content = await
|
|
1353
|
+
const content = await readFile3(filePath, "utf-8");
|
|
1239
1354
|
const lines = content.split("\n");
|
|
1240
1355
|
const offset = Math.max(1, Number(args.offset) || 1);
|
|
1241
1356
|
const limit = Number(args.limit) || lines.length;
|
|
@@ -1252,7 +1367,7 @@ var init_read_file = __esm({
|
|
|
1252
1367
|
});
|
|
1253
1368
|
|
|
1254
1369
|
// src/tools/write-file.ts
|
|
1255
|
-
import { writeFile, mkdir } from "fs/promises";
|
|
1370
|
+
import { writeFile as writeFile2, mkdir } from "fs/promises";
|
|
1256
1371
|
import { dirname, isAbsolute as isAbsolute2 } from "path";
|
|
1257
1372
|
var writeFileTool;
|
|
1258
1373
|
var init_write_file = __esm({
|
|
@@ -1294,7 +1409,7 @@ var init_write_file = __esm({
|
|
|
1294
1409
|
const filePath = guard.path;
|
|
1295
1410
|
try {
|
|
1296
1411
|
await mkdir(dirname(filePath), { recursive: true });
|
|
1297
|
-
await
|
|
1412
|
+
await writeFile2(filePath, args.content, "utf-8");
|
|
1298
1413
|
const lines = args.content.split("\n").length;
|
|
1299
1414
|
return `Wrote ${lines} lines to ${filePath}`;
|
|
1300
1415
|
} catch (err) {
|
|
@@ -1310,7 +1425,7 @@ var init_write_file = __esm({
|
|
|
1310
1425
|
});
|
|
1311
1426
|
|
|
1312
1427
|
// src/tools/edit-file.ts
|
|
1313
|
-
import { readFile as
|
|
1428
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
1314
1429
|
import { isAbsolute as isAbsolute3 } from "path";
|
|
1315
1430
|
var editFileTool;
|
|
1316
1431
|
var init_edit_file = __esm({
|
|
@@ -1351,7 +1466,7 @@ var init_edit_file = __esm({
|
|
|
1351
1466
|
if (!guard.ok) return guard.error;
|
|
1352
1467
|
const filePath = guard.path;
|
|
1353
1468
|
try {
|
|
1354
|
-
const content = await
|
|
1469
|
+
const content = await readFile4(filePath, "utf-8");
|
|
1355
1470
|
const oldStr = args.old_string;
|
|
1356
1471
|
const newStr = args.new_string;
|
|
1357
1472
|
const replaceAll = Boolean(args.replace_all);
|
|
@@ -1365,7 +1480,7 @@ var init_edit_file = __esm({
|
|
|
1365
1480
|
}
|
|
1366
1481
|
}
|
|
1367
1482
|
const updated = replaceAll ? content.split(oldStr).join(newStr) : content.replace(oldStr, newStr);
|
|
1368
|
-
await
|
|
1483
|
+
await writeFile3(filePath, updated, "utf-8");
|
|
1369
1484
|
const replacements = replaceAll ? content.split(oldStr).length - 1 : 1;
|
|
1370
1485
|
return `Edited ${filePath} (${replacements} replacement${replacements > 1 ? "s" : ""})`;
|
|
1371
1486
|
} catch (err) {
|
|
@@ -1382,7 +1497,7 @@ var init_edit_file = __esm({
|
|
|
1382
1497
|
|
|
1383
1498
|
// src/tools/list-directory.ts
|
|
1384
1499
|
import { readdir, stat as stat2 } from "fs/promises";
|
|
1385
|
-
import { join as
|
|
1500
|
+
import { join as join3 } from "path";
|
|
1386
1501
|
function formatSize(bytes) {
|
|
1387
1502
|
if (bytes < 1024) return `${bytes}B`;
|
|
1388
1503
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
@@ -1423,7 +1538,7 @@ var init_list_directory = __esm({
|
|
|
1423
1538
|
const lines = [];
|
|
1424
1539
|
for (const entry of entries.slice(0, 100)) {
|
|
1425
1540
|
try {
|
|
1426
|
-
const full =
|
|
1541
|
+
const full = join3(dirPath, entry);
|
|
1427
1542
|
const s = await stat2(full);
|
|
1428
1543
|
const type = s.isDirectory() ? "dir" : "file";
|
|
1429
1544
|
const size = s.isDirectory() ? "" : ` (${formatSize(s.size)})`;
|
|
@@ -1446,8 +1561,8 @@ var init_list_directory = __esm({
|
|
|
1446
1561
|
});
|
|
1447
1562
|
|
|
1448
1563
|
// src/tools/search-files.ts
|
|
1449
|
-
import { readdir as readdir2, readFile as
|
|
1450
|
-
import { join as
|
|
1564
|
+
import { readdir as readdir2, readFile as readFile5, stat as stat3 } from "fs/promises";
|
|
1565
|
+
import { join as join4, relative as relative2 } from "path";
|
|
1451
1566
|
var IGNORE_DIRS, searchFilesTool;
|
|
1452
1567
|
var init_search_files = __esm({
|
|
1453
1568
|
"src/tools/search-files.ts"() {
|
|
@@ -1522,7 +1637,7 @@ var init_search_files = __esm({
|
|
|
1522
1637
|
for (const entry of entries) {
|
|
1523
1638
|
if (results.length >= maxResults) return;
|
|
1524
1639
|
if (IGNORE_DIRS.has(entry)) continue;
|
|
1525
|
-
const full =
|
|
1640
|
+
const full = join4(dir, entry);
|
|
1526
1641
|
let s;
|
|
1527
1642
|
try {
|
|
1528
1643
|
s = await stat3(full);
|
|
@@ -1537,7 +1652,7 @@ var init_search_files = __esm({
|
|
|
1537
1652
|
if (!entry.endsWith(ext)) continue;
|
|
1538
1653
|
}
|
|
1539
1654
|
try {
|
|
1540
|
-
const content = await
|
|
1655
|
+
const content = await readFile5(full, "utf-8");
|
|
1541
1656
|
const lines = content.split("\n");
|
|
1542
1657
|
for (let i = 0; i < lines.length; i++) {
|
|
1543
1658
|
regex.lastIndex = 0;
|
|
@@ -1562,7 +1677,7 @@ var init_search_files = __esm({
|
|
|
1562
1677
|
|
|
1563
1678
|
// src/tools/glob-files.ts
|
|
1564
1679
|
import { readdir as readdir3, stat as stat4 } from "fs/promises";
|
|
1565
|
-
import { join as
|
|
1680
|
+
import { join as join5 } from "path";
|
|
1566
1681
|
function globToRegex(pattern) {
|
|
1567
1682
|
let regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
1568
1683
|
return new RegExp(`^${regex}$`, "i");
|
|
@@ -1628,7 +1743,7 @@ var init_glob_files = __esm({
|
|
|
1628
1743
|
for (const entry of entries) {
|
|
1629
1744
|
if (matches.length >= 200) return;
|
|
1630
1745
|
if (IGNORE_DIRS2.has(entry)) continue;
|
|
1631
|
-
const full =
|
|
1746
|
+
const full = join5(dir, entry);
|
|
1632
1747
|
const rel = relDir ? `${relDir}/${entry}` : entry;
|
|
1633
1748
|
let s;
|
|
1634
1749
|
try {
|
|
@@ -1854,19 +1969,19 @@ var init_git = __esm({
|
|
|
1854
1969
|
});
|
|
1855
1970
|
|
|
1856
1971
|
// src/config/config.ts
|
|
1857
|
-
import { readFile as
|
|
1858
|
-
import { existsSync as
|
|
1859
|
-
import { join as
|
|
1972
|
+
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir2 } from "fs/promises";
|
|
1973
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1974
|
+
import { join as join6 } from "path";
|
|
1860
1975
|
import { homedir, platform as platform3 } from "os";
|
|
1861
1976
|
import JSON5 from "json5";
|
|
1862
1977
|
function getConfigDir() {
|
|
1863
1978
|
if (platform3() === "win32") {
|
|
1864
|
-
return
|
|
1979
|
+
return join6(process.env.APPDATA || join6(homedir(), "AppData", "Roaming"), "Clank");
|
|
1865
1980
|
}
|
|
1866
|
-
return
|
|
1981
|
+
return join6(homedir(), ".clank");
|
|
1867
1982
|
}
|
|
1868
1983
|
function getConfigPath() {
|
|
1869
|
-
return
|
|
1984
|
+
return join6(getConfigDir(), "config.json5");
|
|
1870
1985
|
}
|
|
1871
1986
|
function defaultConfig() {
|
|
1872
1987
|
return {
|
|
@@ -1878,7 +1993,7 @@ function defaultConfig() {
|
|
|
1878
1993
|
agents: {
|
|
1879
1994
|
defaults: {
|
|
1880
1995
|
model: { primary: "ollama/qwen3.5" },
|
|
1881
|
-
workspace:
|
|
1996
|
+
workspace: join6(getConfigDir(), "workspace"),
|
|
1882
1997
|
toolTier: "auto",
|
|
1883
1998
|
temperature: 0.7
|
|
1884
1999
|
},
|
|
@@ -1937,11 +2052,11 @@ function deepMerge(target, source) {
|
|
|
1937
2052
|
async function loadConfig() {
|
|
1938
2053
|
const configPath = getConfigPath();
|
|
1939
2054
|
const defaults = defaultConfig();
|
|
1940
|
-
if (!
|
|
2055
|
+
if (!existsSync3(configPath)) {
|
|
1941
2056
|
return defaults;
|
|
1942
2057
|
}
|
|
1943
2058
|
try {
|
|
1944
|
-
const raw = await
|
|
2059
|
+
const raw = await readFile6(configPath, "utf-8");
|
|
1945
2060
|
const parsed = JSON5.parse(raw);
|
|
1946
2061
|
const substituted = substituteEnvVars(parsed);
|
|
1947
2062
|
return deepMerge(defaults, substituted);
|
|
@@ -1954,15 +2069,15 @@ async function saveConfig(config) {
|
|
|
1954
2069
|
const configPath = getConfigPath();
|
|
1955
2070
|
await mkdir2(getConfigDir(), { recursive: true });
|
|
1956
2071
|
const content = JSON5.stringify(config, null, 2);
|
|
1957
|
-
await
|
|
2072
|
+
await writeFile4(configPath, content, "utf-8");
|
|
1958
2073
|
}
|
|
1959
2074
|
async function ensureConfigDir() {
|
|
1960
2075
|
const configDir = getConfigDir();
|
|
1961
2076
|
await mkdir2(configDir, { recursive: true });
|
|
1962
|
-
await mkdir2(
|
|
1963
|
-
await mkdir2(
|
|
1964
|
-
await mkdir2(
|
|
1965
|
-
await mkdir2(
|
|
2077
|
+
await mkdir2(join6(configDir, "workspace"), { recursive: true });
|
|
2078
|
+
await mkdir2(join6(configDir, "conversations"), { recursive: true });
|
|
2079
|
+
await mkdir2(join6(configDir, "memory"), { recursive: true });
|
|
2080
|
+
await mkdir2(join6(configDir, "logs"), { recursive: true });
|
|
1966
2081
|
}
|
|
1967
2082
|
var init_config = __esm({
|
|
1968
2083
|
"src/config/config.ts"() {
|
|
@@ -2144,6 +2259,12 @@ var init_web_fetch = __esm({
|
|
|
2144
2259
|
if (host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "0.0.0.0") {
|
|
2145
2260
|
return { ok: false, error: "localhost URLs are blocked (SSRF protection)" };
|
|
2146
2261
|
}
|
|
2262
|
+
if (/^10\./.test(host) || /^192\.168\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host)) {
|
|
2263
|
+
return { ok: false, error: "Private network IPs are blocked (SSRF protection)" };
|
|
2264
|
+
}
|
|
2265
|
+
if (host.startsWith("[::ffff:")) {
|
|
2266
|
+
return { ok: false, error: "IPv4-mapped IPv6 addresses are blocked" };
|
|
2267
|
+
}
|
|
2147
2268
|
if (host === "169.254.169.254" || host === "metadata.google.internal") {
|
|
2148
2269
|
return { ok: false, error: "Cloud metadata endpoints are blocked" };
|
|
2149
2270
|
}
|
|
@@ -2280,9 +2401,21 @@ var init_config_tool = __esm({
|
|
|
2280
2401
|
return `Key not found: ${key}`;
|
|
2281
2402
|
}
|
|
2282
2403
|
}
|
|
2283
|
-
|
|
2404
|
+
if (typeof current === "object") {
|
|
2405
|
+
return JSON.stringify(redactConfig(current), null, 2);
|
|
2406
|
+
}
|
|
2407
|
+
const SENSITIVE = /* @__PURE__ */ new Set(["apikey", "api_key", "apiKey", "token", "bottoken", "botToken", "secret", "password", "pin"]);
|
|
2408
|
+
const lastKey = keys[keys.length - 1];
|
|
2409
|
+
if (SENSITIVE.has(lastKey) && typeof current === "string") {
|
|
2410
|
+
return "[REDACTED]";
|
|
2411
|
+
}
|
|
2412
|
+
return String(current);
|
|
2284
2413
|
}
|
|
2285
2414
|
if (action === "set") {
|
|
2415
|
+
const BLOCKED_KEYS = ["__proto__", "constructor", "prototype"];
|
|
2416
|
+
if (keys.some((k) => BLOCKED_KEYS.includes(k))) {
|
|
2417
|
+
return "Error: blocked \u2014 unsafe key";
|
|
2418
|
+
}
|
|
2286
2419
|
let parsed = args.value;
|
|
2287
2420
|
try {
|
|
2288
2421
|
parsed = JSON.parse(args.value);
|
|
@@ -3358,9 +3491,9 @@ var init_model_tool = __esm({
|
|
|
3358
3491
|
});
|
|
3359
3492
|
|
|
3360
3493
|
// src/sessions/store.ts
|
|
3361
|
-
import { readFile as
|
|
3362
|
-
import { existsSync as
|
|
3363
|
-
import { join as
|
|
3494
|
+
import { readFile as readFile7, writeFile as writeFile5, unlink, mkdir as mkdir3 } from "fs/promises";
|
|
3495
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3496
|
+
import { join as join7 } from "path";
|
|
3364
3497
|
import { randomUUID } from "crypto";
|
|
3365
3498
|
var SessionStore;
|
|
3366
3499
|
var init_store = __esm({
|
|
@@ -3373,14 +3506,14 @@ var init_store = __esm({
|
|
|
3373
3506
|
index = /* @__PURE__ */ new Map();
|
|
3374
3507
|
constructor(storeDir) {
|
|
3375
3508
|
this.storeDir = storeDir;
|
|
3376
|
-
this.indexPath =
|
|
3509
|
+
this.indexPath = join7(storeDir, "sessions.json");
|
|
3377
3510
|
}
|
|
3378
3511
|
/** Initialize the store — load index from disk */
|
|
3379
3512
|
async init() {
|
|
3380
3513
|
await mkdir3(this.storeDir, { recursive: true });
|
|
3381
|
-
if (
|
|
3514
|
+
if (existsSync4(this.indexPath)) {
|
|
3382
3515
|
try {
|
|
3383
|
-
const raw = await
|
|
3516
|
+
const raw = await readFile7(this.indexPath, "utf-8");
|
|
3384
3517
|
const entries = JSON.parse(raw);
|
|
3385
3518
|
for (const entry of entries) {
|
|
3386
3519
|
this.index.set(entry.normalizedKey, entry);
|
|
@@ -3393,7 +3526,7 @@ var init_store = __esm({
|
|
|
3393
3526
|
/** Save the index to disk */
|
|
3394
3527
|
async saveIndex() {
|
|
3395
3528
|
const entries = Array.from(this.index.values());
|
|
3396
|
-
await
|
|
3529
|
+
await writeFile5(this.indexPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
3397
3530
|
}
|
|
3398
3531
|
/** Get or create a session for a normalized key */
|
|
3399
3532
|
async resolve(normalizedKey, opts) {
|
|
@@ -3419,10 +3552,10 @@ var init_store = __esm({
|
|
|
3419
3552
|
}
|
|
3420
3553
|
/** Load conversation messages for a session */
|
|
3421
3554
|
async loadMessages(sessionId) {
|
|
3422
|
-
const path2 =
|
|
3423
|
-
if (!
|
|
3555
|
+
const path2 = join7(this.storeDir, `${sessionId}.json`);
|
|
3556
|
+
if (!existsSync4(path2)) return [];
|
|
3424
3557
|
try {
|
|
3425
|
-
const raw = await
|
|
3558
|
+
const raw = await readFile7(path2, "utf-8");
|
|
3426
3559
|
return JSON.parse(raw);
|
|
3427
3560
|
} catch {
|
|
3428
3561
|
return [];
|
|
@@ -3430,8 +3563,8 @@ var init_store = __esm({
|
|
|
3430
3563
|
}
|
|
3431
3564
|
/** Save conversation messages for a session */
|
|
3432
3565
|
async saveMessages(sessionId, messages) {
|
|
3433
|
-
const path2 =
|
|
3434
|
-
await
|
|
3566
|
+
const path2 = join7(this.storeDir, `${sessionId}.json`);
|
|
3567
|
+
await writeFile5(path2, JSON.stringify(messages, null, 2), "utf-8");
|
|
3435
3568
|
}
|
|
3436
3569
|
/** List all sessions, sorted by last used */
|
|
3437
3570
|
list() {
|
|
@@ -3443,7 +3576,7 @@ var init_store = __esm({
|
|
|
3443
3576
|
if (!entry) return false;
|
|
3444
3577
|
this.index.delete(normalizedKey);
|
|
3445
3578
|
await this.saveIndex();
|
|
3446
|
-
const path2 =
|
|
3579
|
+
const path2 = join7(this.storeDir, `${entry.id}.json`);
|
|
3447
3580
|
try {
|
|
3448
3581
|
await unlink(path2);
|
|
3449
3582
|
} catch {
|
|
@@ -3454,7 +3587,7 @@ var init_store = __esm({
|
|
|
3454
3587
|
async reset(normalizedKey) {
|
|
3455
3588
|
const entry = this.index.get(normalizedKey);
|
|
3456
3589
|
if (!entry) return null;
|
|
3457
|
-
const path2 =
|
|
3590
|
+
const path2 = join7(this.storeDir, `${entry.id}.json`);
|
|
3458
3591
|
try {
|
|
3459
3592
|
await unlink(path2);
|
|
3460
3593
|
} catch {
|
|
@@ -3500,7 +3633,7 @@ var init_sessions = __esm({
|
|
|
3500
3633
|
});
|
|
3501
3634
|
|
|
3502
3635
|
// src/tools/self-config/session-tool.ts
|
|
3503
|
-
import { join as
|
|
3636
|
+
import { join as join8 } from "path";
|
|
3504
3637
|
var sessionTool;
|
|
3505
3638
|
var init_session_tool = __esm({
|
|
3506
3639
|
"src/tools/self-config/session-tool.ts"() {
|
|
@@ -3534,7 +3667,7 @@ var init_session_tool = __esm({
|
|
|
3534
3667
|
return { ok: true };
|
|
3535
3668
|
},
|
|
3536
3669
|
async execute(args) {
|
|
3537
|
-
const store = new SessionStore(
|
|
3670
|
+
const store = new SessionStore(join8(getConfigDir(), "conversations"));
|
|
3538
3671
|
await store.init();
|
|
3539
3672
|
const action = args.action;
|
|
3540
3673
|
if (action === "list") {
|
|
@@ -3563,9 +3696,9 @@ var init_session_tool = __esm({
|
|
|
3563
3696
|
});
|
|
3564
3697
|
|
|
3565
3698
|
// src/cron/scheduler.ts
|
|
3566
|
-
import { readFile as
|
|
3567
|
-
import { existsSync as
|
|
3568
|
-
import { join as
|
|
3699
|
+
import { readFile as readFile8, appendFile, mkdir as mkdir4, writeFile as writeFile6 } from "fs/promises";
|
|
3700
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3701
|
+
import { join as join9 } from "path";
|
|
3569
3702
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3570
3703
|
var CronScheduler;
|
|
3571
3704
|
var init_scheduler = __esm({
|
|
@@ -3580,8 +3713,8 @@ var init_scheduler = __esm({
|
|
|
3580
3713
|
running = false;
|
|
3581
3714
|
onJobDue;
|
|
3582
3715
|
constructor(storeDir) {
|
|
3583
|
-
this.jobsPath =
|
|
3584
|
-
this.runsDir =
|
|
3716
|
+
this.jobsPath = join9(storeDir, "jobs.jsonl");
|
|
3717
|
+
this.runsDir = join9(storeDir, "runs");
|
|
3585
3718
|
}
|
|
3586
3719
|
/** Initialize — load jobs from disk */
|
|
3587
3720
|
async init() {
|
|
@@ -3703,9 +3836,9 @@ var init_scheduler = __esm({
|
|
|
3703
3836
|
}
|
|
3704
3837
|
/** Load jobs from JSONL file */
|
|
3705
3838
|
async loadJobs() {
|
|
3706
|
-
if (!
|
|
3839
|
+
if (!existsSync5(this.jobsPath)) return;
|
|
3707
3840
|
try {
|
|
3708
|
-
const raw = await
|
|
3841
|
+
const raw = await readFile8(this.jobsPath, "utf-8");
|
|
3709
3842
|
this.jobs = raw.split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
3710
3843
|
} catch {
|
|
3711
3844
|
this.jobs = [];
|
|
@@ -3714,11 +3847,11 @@ var init_scheduler = __esm({
|
|
|
3714
3847
|
/** Save jobs to JSONL file */
|
|
3715
3848
|
async saveJobs() {
|
|
3716
3849
|
const content = this.jobs.map((j) => JSON.stringify(j)).join("\n") + "\n";
|
|
3717
|
-
await
|
|
3850
|
+
await writeFile6(this.jobsPath, content, "utf-8");
|
|
3718
3851
|
}
|
|
3719
3852
|
/** Log a run result */
|
|
3720
3853
|
async logRun(log) {
|
|
3721
|
-
const logPath =
|
|
3854
|
+
const logPath = join9(this.runsDir, `${log.jobId}.jsonl`);
|
|
3722
3855
|
await appendFile(logPath, JSON.stringify(log) + "\n", "utf-8");
|
|
3723
3856
|
}
|
|
3724
3857
|
};
|
|
@@ -3739,7 +3872,7 @@ var init_cron = __esm({
|
|
|
3739
3872
|
});
|
|
3740
3873
|
|
|
3741
3874
|
// src/tools/self-config/cron-tool.ts
|
|
3742
|
-
import { join as
|
|
3875
|
+
import { join as join10 } from "path";
|
|
3743
3876
|
var cronTool;
|
|
3744
3877
|
var init_cron_tool = __esm({
|
|
3745
3878
|
"src/tools/self-config/cron-tool.ts"() {
|
|
@@ -3777,7 +3910,7 @@ var init_cron_tool = __esm({
|
|
|
3777
3910
|
return { ok: true };
|
|
3778
3911
|
},
|
|
3779
3912
|
async execute(args, ctx) {
|
|
3780
|
-
const scheduler = new CronScheduler(
|
|
3913
|
+
const scheduler = new CronScheduler(join10(getConfigDir(), "cron"));
|
|
3781
3914
|
await scheduler.init();
|
|
3782
3915
|
const action = args.action;
|
|
3783
3916
|
if (action === "list") {
|
|
@@ -4041,12 +4174,12 @@ var init_tts = __esm({
|
|
|
4041
4174
|
/** Transcribe via local whisper.cpp */
|
|
4042
4175
|
async transcribeLocal(audioBuffer, format) {
|
|
4043
4176
|
try {
|
|
4044
|
-
const { writeFile:
|
|
4177
|
+
const { writeFile: writeFile10, unlink: unlink5 } = await import("fs/promises");
|
|
4045
4178
|
const { execSync: execSync3 } = await import("child_process");
|
|
4046
|
-
const { join:
|
|
4179
|
+
const { join: join20 } = await import("path");
|
|
4047
4180
|
const { tmpdir } = await import("os");
|
|
4048
|
-
const tmpFile =
|
|
4049
|
-
await
|
|
4181
|
+
const tmpFile = join20(tmpdir(), `clank-stt-${Date.now()}.${format}`);
|
|
4182
|
+
await writeFile10(tmpFile, audioBuffer);
|
|
4050
4183
|
const output = execSync3(`whisper "${tmpFile}" --model base.en --output-txt`, {
|
|
4051
4184
|
encoding: "utf-8",
|
|
4052
4185
|
timeout: 6e4
|
|
@@ -4114,11 +4247,11 @@ var init_voice_tool = __esm({
|
|
|
4114
4247
|
voiceId: args.voice_id
|
|
4115
4248
|
});
|
|
4116
4249
|
if (!result) return "Error: TTS synthesis failed";
|
|
4117
|
-
const { writeFile:
|
|
4118
|
-
const { join:
|
|
4250
|
+
const { writeFile: writeFile10 } = await import("fs/promises");
|
|
4251
|
+
const { join: join20 } = await import("path");
|
|
4119
4252
|
const { tmpdir } = await import("os");
|
|
4120
|
-
const outPath =
|
|
4121
|
-
await
|
|
4253
|
+
const outPath = join20(tmpdir(), `clank-tts-${Date.now()}.${result.format}`);
|
|
4254
|
+
await writeFile10(outPath, result.audioBuffer);
|
|
4122
4255
|
return `Audio generated: ${outPath} (${result.format}, ${Math.round(result.audioBuffer.length / 1024)}KB)`;
|
|
4123
4256
|
}
|
|
4124
4257
|
};
|
|
@@ -4136,21 +4269,24 @@ var init_voice_tool = __esm({
|
|
|
4136
4269
|
},
|
|
4137
4270
|
safetyLevel: "low",
|
|
4138
4271
|
readOnly: true,
|
|
4139
|
-
validate(args) {
|
|
4272
|
+
validate(args, ctx) {
|
|
4140
4273
|
if (!args.file_path || typeof args.file_path !== "string") return { ok: false, error: "file_path is required" };
|
|
4141
4274
|
return { ok: true };
|
|
4142
4275
|
},
|
|
4143
|
-
async execute(args) {
|
|
4144
|
-
const { readFile:
|
|
4145
|
-
const { existsSync:
|
|
4146
|
-
const
|
|
4147
|
-
|
|
4276
|
+
async execute(args, ctx) {
|
|
4277
|
+
const { readFile: readFile13 } = await import("fs/promises");
|
|
4278
|
+
const { existsSync: existsSync12 } = await import("fs");
|
|
4279
|
+
const { guardPath: guardPath2 } = await Promise.resolve().then(() => (init_path_guard(), path_guard_exports));
|
|
4280
|
+
const guard = guardPath2(args.file_path, ctx.projectRoot, { allowExternal: ctx.allowExternal });
|
|
4281
|
+
if (!guard.ok) return guard.error;
|
|
4282
|
+
const filePath = guard.path;
|
|
4283
|
+
if (!existsSync12(filePath)) return `Error: File not found: ${filePath}`;
|
|
4148
4284
|
const config = await loadConfig();
|
|
4149
4285
|
const engine = new STTEngine(config);
|
|
4150
4286
|
if (!engine.isAvailable()) {
|
|
4151
4287
|
return "Error: Speech-to-text not configured. Need OpenAI API key or local whisper.cpp installed.";
|
|
4152
4288
|
}
|
|
4153
|
-
const audioBuffer = await
|
|
4289
|
+
const audioBuffer = await readFile13(filePath);
|
|
4154
4290
|
const ext = filePath.split(".").pop() || "wav";
|
|
4155
4291
|
const result = await engine.transcribe(audioBuffer, ext);
|
|
4156
4292
|
if (!result) return "Error: Transcription failed";
|
|
@@ -4182,6 +4318,53 @@ var init_voice_tool = __esm({
|
|
|
4182
4318
|
}
|
|
4183
4319
|
});
|
|
4184
4320
|
|
|
4321
|
+
// src/tools/self-config/file-share-tool.ts
|
|
4322
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4323
|
+
import { stat as stat5 } from "fs/promises";
|
|
4324
|
+
var MAX_FILE_SIZE, fileShareTool;
|
|
4325
|
+
var init_file_share_tool = __esm({
|
|
4326
|
+
"src/tools/self-config/file-share-tool.ts"() {
|
|
4327
|
+
"use strict";
|
|
4328
|
+
init_esm_shims();
|
|
4329
|
+
init_path_guard();
|
|
4330
|
+
MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
4331
|
+
fileShareTool = {
|
|
4332
|
+
definition: {
|
|
4333
|
+
name: "share_file",
|
|
4334
|
+
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.",
|
|
4335
|
+
parameters: {
|
|
4336
|
+
type: "object",
|
|
4337
|
+
properties: {
|
|
4338
|
+
path: { type: "string", description: "Path to the file to share" },
|
|
4339
|
+
caption: { type: "string", description: "Optional message to send with the file" }
|
|
4340
|
+
},
|
|
4341
|
+
required: ["path"]
|
|
4342
|
+
}
|
|
4343
|
+
},
|
|
4344
|
+
safetyLevel: "medium",
|
|
4345
|
+
readOnly: true,
|
|
4346
|
+
validate(args, ctx) {
|
|
4347
|
+
if (!args.path || typeof args.path !== "string") return { ok: false, error: "path is required" };
|
|
4348
|
+
const guard = guardPath(args.path, ctx.projectRoot);
|
|
4349
|
+
if (!guard.ok) return { ok: false, error: guard.error };
|
|
4350
|
+
return { ok: true };
|
|
4351
|
+
},
|
|
4352
|
+
async execute(args, ctx) {
|
|
4353
|
+
const guard = guardPath(args.path, ctx.projectRoot, { allowExternal: ctx.allowExternal });
|
|
4354
|
+
if (!guard.ok) return guard.error;
|
|
4355
|
+
if (!existsSync6(guard.path)) return `Error: File not found: ${guard.path}`;
|
|
4356
|
+
const fileStats = await stat5(guard.path);
|
|
4357
|
+
if (fileStats.size > MAX_FILE_SIZE) return `Error: File too large (${Math.round(fileStats.size / 1024 / 1024)}MB, max 10MB)`;
|
|
4358
|
+
const caption = args.caption ? ` with caption: "${args.caption}"` : "";
|
|
4359
|
+
return `File ready to share: ${guard.path} (${Math.round(fileStats.size / 1024)}KB)${caption}. The file will be sent through the current channel.`;
|
|
4360
|
+
},
|
|
4361
|
+
formatConfirmation(args) {
|
|
4362
|
+
return `Share file: ${args.path}`;
|
|
4363
|
+
}
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
});
|
|
4367
|
+
|
|
4185
4368
|
// src/tools/self-config/index.ts
|
|
4186
4369
|
function registerSelfConfigTools(registry) {
|
|
4187
4370
|
registry.register(configTool);
|
|
@@ -4195,6 +4378,7 @@ function registerSelfConfigTools(registry) {
|
|
|
4195
4378
|
registry.register(ttsTool);
|
|
4196
4379
|
registry.register(sttTool);
|
|
4197
4380
|
registry.register(voiceListTool);
|
|
4381
|
+
registry.register(fileShareTool);
|
|
4198
4382
|
}
|
|
4199
4383
|
var init_self_config = __esm({
|
|
4200
4384
|
"src/tools/self-config/index.ts"() {
|
|
@@ -4209,6 +4393,7 @@ var init_self_config = __esm({
|
|
|
4209
4393
|
init_gateway_tool();
|
|
4210
4394
|
init_message_tool();
|
|
4211
4395
|
init_voice_tool();
|
|
4396
|
+
init_file_share_tool();
|
|
4212
4397
|
init_config_tool();
|
|
4213
4398
|
init_channel_tool();
|
|
4214
4399
|
init_agent_tool();
|
|
@@ -4218,6 +4403,7 @@ var init_self_config = __esm({
|
|
|
4218
4403
|
init_gateway_tool();
|
|
4219
4404
|
init_message_tool();
|
|
4220
4405
|
init_voice_tool();
|
|
4406
|
+
init_file_share_tool();
|
|
4221
4407
|
}
|
|
4222
4408
|
});
|
|
4223
4409
|
|
|
@@ -4279,7 +4465,7 @@ __export(chat_exports, {
|
|
|
4279
4465
|
runChat: () => runChat
|
|
4280
4466
|
});
|
|
4281
4467
|
import { createInterface } from "readline";
|
|
4282
|
-
import { join as
|
|
4468
|
+
import { join as join11 } from "path";
|
|
4283
4469
|
async function runChat(opts) {
|
|
4284
4470
|
await ensureConfigDir();
|
|
4285
4471
|
const config = await loadConfig();
|
|
@@ -4296,9 +4482,9 @@ async function runChat(opts) {
|
|
|
4296
4482
|
console.log(dim("Starting gateway..."));
|
|
4297
4483
|
const { fork: fork2 } = await import("child_process");
|
|
4298
4484
|
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
4299
|
-
const { dirname: dirname6, join:
|
|
4485
|
+
const { dirname: dirname6, join: join20 } = await import("path");
|
|
4300
4486
|
const __filename4 = fileURLToPath6(import.meta.url);
|
|
4301
|
-
const entryPoint =
|
|
4487
|
+
const entryPoint = join20(dirname6(__filename4), "index.js");
|
|
4302
4488
|
const child = fork2(entryPoint, ["gateway", "start", "--foreground"], {
|
|
4303
4489
|
detached: true,
|
|
4304
4490
|
stdio: "ignore"
|
|
@@ -4346,7 +4532,7 @@ async function runChat(opts) {
|
|
|
4346
4532
|
console.error(dim("Make sure Ollama is running or configure a cloud provider in ~/.clank/config.json5"));
|
|
4347
4533
|
process.exit(1);
|
|
4348
4534
|
}
|
|
4349
|
-
const sessionStore = new SessionStore(
|
|
4535
|
+
const sessionStore = new SessionStore(join11(getConfigDir(), "conversations"));
|
|
4350
4536
|
await sessionStore.init();
|
|
4351
4537
|
const toolRegistry = createFullRegistry();
|
|
4352
4538
|
const identity = {
|
|
@@ -4504,9 +4690,9 @@ var init_chat = __esm({
|
|
|
4504
4690
|
});
|
|
4505
4691
|
|
|
4506
4692
|
// src/memory/memory.ts
|
|
4507
|
-
import { readFile as
|
|
4508
|
-
import { existsSync as
|
|
4509
|
-
import { join as
|
|
4693
|
+
import { readFile as readFile9, writeFile as writeFile7, readdir as readdir5, mkdir as mkdir5 } from "fs/promises";
|
|
4694
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4695
|
+
import { join as join12 } from "path";
|
|
4510
4696
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4511
4697
|
function tokenize(text) {
|
|
4512
4698
|
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3 && !STOPWORDS.has(t));
|
|
@@ -4654,12 +4840,12 @@ var init_memory = __esm({
|
|
|
4654
4840
|
meta = /* @__PURE__ */ new Map();
|
|
4655
4841
|
constructor(memoryDir) {
|
|
4656
4842
|
this.memoryDir = memoryDir;
|
|
4657
|
-
this.metaPath =
|
|
4843
|
+
this.metaPath = join12(memoryDir, "_meta.json");
|
|
4658
4844
|
}
|
|
4659
4845
|
/** Initialize — create dirs and load metadata */
|
|
4660
4846
|
async init() {
|
|
4661
4847
|
for (const cat of ["identity", "knowledge", "lessons", "context"]) {
|
|
4662
|
-
await mkdir5(
|
|
4848
|
+
await mkdir5(join12(this.memoryDir, cat), { recursive: true });
|
|
4663
4849
|
}
|
|
4664
4850
|
await this.loadMeta();
|
|
4665
4851
|
}
|
|
@@ -4704,10 +4890,10 @@ var init_memory = __esm({
|
|
|
4704
4890
|
let used = 0;
|
|
4705
4891
|
if (projectRoot) {
|
|
4706
4892
|
for (const name of [".clank.md", ".clankbuild.md", ".llamabuild.md"]) {
|
|
4707
|
-
const path2 =
|
|
4708
|
-
if (
|
|
4893
|
+
const path2 = join12(projectRoot, name);
|
|
4894
|
+
if (existsSync7(path2)) {
|
|
4709
4895
|
try {
|
|
4710
|
-
const content = await
|
|
4896
|
+
const content = await readFile9(path2, "utf-8");
|
|
4711
4897
|
if (content.trim() && used + content.length < budgetChars) {
|
|
4712
4898
|
parts.push("## Project Memory\n" + content.trim());
|
|
4713
4899
|
used += content.length;
|
|
@@ -4718,10 +4904,10 @@ var init_memory = __esm({
|
|
|
4718
4904
|
}
|
|
4719
4905
|
}
|
|
4720
4906
|
}
|
|
4721
|
-
const globalPath =
|
|
4722
|
-
if (
|
|
4907
|
+
const globalPath = join12(this.memoryDir, "..", "workspace", "MEMORY.md");
|
|
4908
|
+
if (existsSync7(globalPath)) {
|
|
4723
4909
|
try {
|
|
4724
|
-
const content = await
|
|
4910
|
+
const content = await readFile9(globalPath, "utf-8");
|
|
4725
4911
|
if (content.trim() && used + content.length < budgetChars) {
|
|
4726
4912
|
parts.push("## Global Memory\n" + content.trim());
|
|
4727
4913
|
used += content.length;
|
|
@@ -4742,8 +4928,8 @@ ${entry.content}`);
|
|
|
4742
4928
|
async add(category, title, content) {
|
|
4743
4929
|
const id = randomUUID3();
|
|
4744
4930
|
const filename = `${title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50)}.md`;
|
|
4745
|
-
const filePath =
|
|
4746
|
-
await
|
|
4931
|
+
const filePath = join12(this.memoryDir, category, filename);
|
|
4932
|
+
await writeFile7(filePath, `# ${title}
|
|
4747
4933
|
|
|
4748
4934
|
${content}`, "utf-8");
|
|
4749
4935
|
this.meta.set(id, {
|
|
@@ -4788,15 +4974,15 @@ ${content}`, "utf-8");
|
|
|
4788
4974
|
async loadAll() {
|
|
4789
4975
|
const entries = [];
|
|
4790
4976
|
for (const category of ["identity", "knowledge", "lessons", "context"]) {
|
|
4791
|
-
const dir =
|
|
4792
|
-
if (!
|
|
4977
|
+
const dir = join12(this.memoryDir, category);
|
|
4978
|
+
if (!existsSync7(dir)) continue;
|
|
4793
4979
|
try {
|
|
4794
4980
|
const files = await readdir5(dir);
|
|
4795
4981
|
for (const file of files) {
|
|
4796
4982
|
if (!file.endsWith(".md")) continue;
|
|
4797
|
-
const filePath =
|
|
4983
|
+
const filePath = join12(dir, file);
|
|
4798
4984
|
try {
|
|
4799
|
-
const content = await
|
|
4985
|
+
const content = await readFile9(filePath, "utf-8");
|
|
4800
4986
|
const title = content.split("\n")[0]?.replace(/^#\s*/, "") || file;
|
|
4801
4987
|
entries.push({
|
|
4802
4988
|
id: file,
|
|
@@ -4821,9 +5007,9 @@ ${content}`, "utf-8");
|
|
|
4821
5007
|
return recency * frequencyBoost;
|
|
4822
5008
|
}
|
|
4823
5009
|
async loadMeta() {
|
|
4824
|
-
if (!
|
|
5010
|
+
if (!existsSync7(this.metaPath)) return;
|
|
4825
5011
|
try {
|
|
4826
|
-
const raw = await
|
|
5012
|
+
const raw = await readFile9(this.metaPath, "utf-8");
|
|
4827
5013
|
const entries = JSON.parse(raw);
|
|
4828
5014
|
for (const e of entries) this.meta.set(e.id, e);
|
|
4829
5015
|
} catch {
|
|
@@ -4831,7 +5017,7 @@ ${content}`, "utf-8");
|
|
|
4831
5017
|
}
|
|
4832
5018
|
async saveMeta() {
|
|
4833
5019
|
const entries = Array.from(this.meta.values());
|
|
4834
|
-
await
|
|
5020
|
+
await writeFile7(this.metaPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
4835
5021
|
}
|
|
4836
5022
|
};
|
|
4837
5023
|
}
|
|
@@ -4843,6 +5029,7 @@ var init_memory2 = __esm({
|
|
|
4843
5029
|
"use strict";
|
|
4844
5030
|
init_esm_shims();
|
|
4845
5031
|
init_memory();
|
|
5032
|
+
init_auto_persist();
|
|
4846
5033
|
}
|
|
4847
5034
|
});
|
|
4848
5035
|
|
|
@@ -5038,15 +5225,53 @@ var init_telegram = __esm({
|
|
|
5038
5225
|
if (!this.gateway) return;
|
|
5039
5226
|
try {
|
|
5040
5227
|
await ctx.api.sendChatAction(chatId, "typing");
|
|
5041
|
-
|
|
5228
|
+
let streamMsgId = null;
|
|
5229
|
+
let accumulated = "";
|
|
5230
|
+
let lastEditTime = 0;
|
|
5231
|
+
const EDIT_INTERVAL = 800;
|
|
5232
|
+
const response = await this.gateway.handleInboundMessageStreaming(
|
|
5042
5233
|
{
|
|
5043
5234
|
channel: "telegram",
|
|
5044
5235
|
peerId: chatId,
|
|
5045
5236
|
peerKind: isGroup ? "group" : "dm"
|
|
5046
5237
|
},
|
|
5047
|
-
msg.text
|
|
5238
|
+
msg.text,
|
|
5239
|
+
{
|
|
5240
|
+
onToken: (content) => {
|
|
5241
|
+
accumulated += content;
|
|
5242
|
+
const now = Date.now();
|
|
5243
|
+
if (!streamMsgId && accumulated.length > 20) {
|
|
5244
|
+
bot.api.sendMessage(chatId, accumulated + " \u258D").then((sent) => {
|
|
5245
|
+
streamMsgId = sent.message_id;
|
|
5246
|
+
lastEditTime = now;
|
|
5247
|
+
}).catch(() => {
|
|
5248
|
+
});
|
|
5249
|
+
return;
|
|
5250
|
+
}
|
|
5251
|
+
if (streamMsgId && now - lastEditTime > EDIT_INTERVAL) {
|
|
5252
|
+
lastEditTime = now;
|
|
5253
|
+
const display = accumulated.length > 4e3 ? accumulated.slice(-3900) + " \u258D" : accumulated + " \u258D";
|
|
5254
|
+
bot.api.editMessageText(chatId, streamMsgId, display).catch(() => {
|
|
5255
|
+
});
|
|
5256
|
+
}
|
|
5257
|
+
},
|
|
5258
|
+
onToolStart: (name) => {
|
|
5259
|
+
if (!streamMsgId) {
|
|
5260
|
+
bot.api.sendChatAction(chatId, "typing").catch(() => {
|
|
5261
|
+
});
|
|
5262
|
+
}
|
|
5263
|
+
},
|
|
5264
|
+
onError: (message) => {
|
|
5265
|
+
bot.api.sendMessage(chatId, `Error: ${message.slice(0, 200)}`).catch(() => {
|
|
5266
|
+
});
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5048
5269
|
);
|
|
5049
|
-
if (response) {
|
|
5270
|
+
if (streamMsgId && response) {
|
|
5271
|
+
const finalText = response.length > 4e3 ? response.slice(0, 3950) + "\n... (truncated)" : response;
|
|
5272
|
+
await bot.api.editMessageText(chatId, streamMsgId, finalText).catch(() => {
|
|
5273
|
+
});
|
|
5274
|
+
} else if (response && !streamMsgId) {
|
|
5050
5275
|
const chunks = splitMessage(response, 4e3);
|
|
5051
5276
|
for (const chunk of chunks) {
|
|
5052
5277
|
await ctx.api.sendMessage(chatId, chunk);
|
|
@@ -5110,7 +5335,8 @@ var init_telegram = __esm({
|
|
|
5110
5335
|
const { TTSEngine: TTSEngine2 } = await Promise.resolve().then(() => (init_voice(), voice_exports));
|
|
5111
5336
|
const tts = new TTSEngine2(config);
|
|
5112
5337
|
if (tts.isAvailable() && response.length < 2e3) {
|
|
5113
|
-
const
|
|
5338
|
+
const agentVoice = config.agents.list.find((a) => a.voiceId)?.voiceId;
|
|
5339
|
+
const audio = await tts.synthesize(response, { voiceId: agentVoice });
|
|
5114
5340
|
if (audio) {
|
|
5115
5341
|
const { InputFile } = await import("grammy");
|
|
5116
5342
|
await ctx.api.sendVoice(chatId, new InputFile(audio.audioBuffer, "reply.mp3"));
|
|
@@ -5132,6 +5358,94 @@ var init_telegram = __esm({
|
|
|
5132
5358
|
});
|
|
5133
5359
|
chatLocks.set(chatId, next);
|
|
5134
5360
|
});
|
|
5361
|
+
bot.on("message:photo", async (ctx) => {
|
|
5362
|
+
const msg = ctx.message;
|
|
5363
|
+
const chatId = msg.chat.id;
|
|
5364
|
+
if (msg.date < startupTime - 30) return;
|
|
5365
|
+
if (telegramConfig.allowFrom && telegramConfig.allowFrom.length > 0) {
|
|
5366
|
+
const username = msg.from?.username ? `@${msg.from.username}` : "";
|
|
5367
|
+
const userIdStr = String(msg.from?.id || "");
|
|
5368
|
+
const allowed = telegramConfig.allowFrom.map(String);
|
|
5369
|
+
if (!allowed.some((a) => a === userIdStr || a.toLowerCase() === username.toLowerCase() || a.toLowerCase() === (msg.from?.username || "").toLowerCase())) return;
|
|
5370
|
+
}
|
|
5371
|
+
const processPhoto = async () => {
|
|
5372
|
+
if (!this.gateway) return;
|
|
5373
|
+
try {
|
|
5374
|
+
const photo = msg.photo[msg.photo.length - 1];
|
|
5375
|
+
const file = await bot.api.getFile(photo.file_id);
|
|
5376
|
+
const fileUrl = `https://api.telegram.org/file/bot${telegramConfig.botToken}/${file.file_path}`;
|
|
5377
|
+
const caption = msg.caption || "";
|
|
5378
|
+
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
|
|
5379
|
+
const response = await this.gateway.handleInboundMessage(
|
|
5380
|
+
{ channel: "telegram", peerId: chatId, peerKind: isGroup ? "group" : "dm" },
|
|
5381
|
+
`[Image received: ${fileUrl}]${caption ? ` Caption: ${caption}` : ""}
|
|
5382
|
+
|
|
5383
|
+
Describe or analyze the image if you can, or acknowledge it.`
|
|
5384
|
+
);
|
|
5385
|
+
if (response) {
|
|
5386
|
+
const chunks = splitMessage(response, 4e3);
|
|
5387
|
+
for (const chunk of chunks) await ctx.api.sendMessage(chatId, chunk);
|
|
5388
|
+
}
|
|
5389
|
+
} catch (err) {
|
|
5390
|
+
await ctx.api.sendMessage(chatId, `Error: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`);
|
|
5391
|
+
}
|
|
5392
|
+
};
|
|
5393
|
+
const prev = chatLocks.get(chatId) || Promise.resolve();
|
|
5394
|
+
chatLocks.set(chatId, prev.then(processPhoto).catch(() => {
|
|
5395
|
+
}));
|
|
5396
|
+
});
|
|
5397
|
+
bot.on("message:document", async (ctx) => {
|
|
5398
|
+
const msg = ctx.message;
|
|
5399
|
+
const chatId = msg.chat.id;
|
|
5400
|
+
if (msg.date < startupTime - 30) return;
|
|
5401
|
+
if (telegramConfig.allowFrom && telegramConfig.allowFrom.length > 0) {
|
|
5402
|
+
const username = msg.from?.username ? `@${msg.from.username}` : "";
|
|
5403
|
+
const userIdStr = String(msg.from?.id || "");
|
|
5404
|
+
const allowed = telegramConfig.allowFrom.map(String);
|
|
5405
|
+
if (!allowed.some((a) => a === userIdStr || a.toLowerCase() === username.toLowerCase() || a.toLowerCase() === (msg.from?.username || "").toLowerCase())) return;
|
|
5406
|
+
}
|
|
5407
|
+
const processDoc = async () => {
|
|
5408
|
+
if (!this.gateway) return;
|
|
5409
|
+
try {
|
|
5410
|
+
const doc = msg.document;
|
|
5411
|
+
if (!doc) return;
|
|
5412
|
+
if (doc.file_size && doc.file_size > 10 * 1024 * 1024) {
|
|
5413
|
+
await ctx.api.sendMessage(chatId, "File too large (max 10MB).");
|
|
5414
|
+
return;
|
|
5415
|
+
}
|
|
5416
|
+
const file = await bot.api.getFile(doc.file_id);
|
|
5417
|
+
const fileUrl = `https://api.telegram.org/file/bot${telegramConfig.botToken}/${file.file_path}`;
|
|
5418
|
+
const res = await fetch(fileUrl);
|
|
5419
|
+
if (!res.ok) {
|
|
5420
|
+
await ctx.api.sendMessage(chatId, "Could not download file.");
|
|
5421
|
+
return;
|
|
5422
|
+
}
|
|
5423
|
+
const { writeFile: wf } = await import("fs/promises");
|
|
5424
|
+
const { join: join20 } = await import("path");
|
|
5425
|
+
const { tmpdir } = await import("os");
|
|
5426
|
+
const safeName = (doc.file_name || "file").replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
5427
|
+
const savePath = join20(tmpdir(), `clank-upload-${Date.now()}-${safeName}`);
|
|
5428
|
+
await wf(savePath, Buffer.from(await res.arrayBuffer()));
|
|
5429
|
+
const caption = msg.caption || "";
|
|
5430
|
+
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
|
|
5431
|
+
const response = await this.gateway.handleInboundMessage(
|
|
5432
|
+
{ channel: "telegram", peerId: chatId, peerKind: isGroup ? "group" : "dm" },
|
|
5433
|
+
`[File received: "${doc.file_name}" saved to ${savePath}]${caption ? ` Note: ${caption}` : ""}
|
|
5434
|
+
|
|
5435
|
+
You can read this file with the read_file tool.`
|
|
5436
|
+
);
|
|
5437
|
+
if (response) {
|
|
5438
|
+
const chunks = splitMessage(response, 4e3);
|
|
5439
|
+
for (const chunk of chunks) await ctx.api.sendMessage(chatId, chunk);
|
|
5440
|
+
}
|
|
5441
|
+
} catch (err) {
|
|
5442
|
+
await ctx.api.sendMessage(chatId, `Error: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`);
|
|
5443
|
+
}
|
|
5444
|
+
};
|
|
5445
|
+
const prev = chatLocks.get(chatId) || Promise.resolve();
|
|
5446
|
+
chatLocks.set(chatId, prev.then(processDoc).catch(() => {
|
|
5447
|
+
}));
|
|
5448
|
+
});
|
|
5135
5449
|
bot.start({
|
|
5136
5450
|
onStart: () => {
|
|
5137
5451
|
this.running = true;
|
|
@@ -5374,9 +5688,9 @@ var init_web = __esm({
|
|
|
5374
5688
|
});
|
|
5375
5689
|
|
|
5376
5690
|
// src/plugins/loader.ts
|
|
5377
|
-
import { readdir as readdir6, readFile as
|
|
5378
|
-
import { existsSync as
|
|
5379
|
-
import { join as
|
|
5691
|
+
import { readdir as readdir6, readFile as readFile10 } from "fs/promises";
|
|
5692
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5693
|
+
import { join as join13 } from "path";
|
|
5380
5694
|
import { homedir as homedir2 } from "os";
|
|
5381
5695
|
var PluginLoader;
|
|
5382
5696
|
var init_loader = __esm({
|
|
@@ -5409,25 +5723,25 @@ var init_loader = __esm({
|
|
|
5409
5723
|
/** Discover plugin directories */
|
|
5410
5724
|
async discoverPlugins() {
|
|
5411
5725
|
const dirs = [];
|
|
5412
|
-
const userPluginDir =
|
|
5413
|
-
if (
|
|
5726
|
+
const userPluginDir = join13(homedir2(), ".clank", "plugins");
|
|
5727
|
+
if (existsSync8(userPluginDir)) {
|
|
5414
5728
|
try {
|
|
5415
5729
|
const entries = await readdir6(userPluginDir, { withFileTypes: true });
|
|
5416
5730
|
for (const entry of entries) {
|
|
5417
5731
|
if (entry.isDirectory()) {
|
|
5418
|
-
dirs.push(
|
|
5732
|
+
dirs.push(join13(userPluginDir, entry.name));
|
|
5419
5733
|
}
|
|
5420
5734
|
}
|
|
5421
5735
|
} catch {
|
|
5422
5736
|
}
|
|
5423
5737
|
}
|
|
5424
|
-
const nodeModulesDir =
|
|
5425
|
-
if (
|
|
5738
|
+
const nodeModulesDir = join13(process.cwd(), "node_modules");
|
|
5739
|
+
if (existsSync8(nodeModulesDir)) {
|
|
5426
5740
|
try {
|
|
5427
5741
|
const entries = await readdir6(nodeModulesDir);
|
|
5428
5742
|
for (const entry of entries) {
|
|
5429
5743
|
if (entry.startsWith("clank-plugin-")) {
|
|
5430
|
-
dirs.push(
|
|
5744
|
+
dirs.push(join13(nodeModulesDir, entry));
|
|
5431
5745
|
}
|
|
5432
5746
|
}
|
|
5433
5747
|
} catch {
|
|
@@ -5437,9 +5751,9 @@ var init_loader = __esm({
|
|
|
5437
5751
|
}
|
|
5438
5752
|
/** Load a single plugin from a directory */
|
|
5439
5753
|
async loadPlugin(dir) {
|
|
5440
|
-
const manifestPath =
|
|
5441
|
-
if (!
|
|
5442
|
-
const raw = await
|
|
5754
|
+
const manifestPath = join13(dir, "clank-plugin.json");
|
|
5755
|
+
if (!existsSync8(manifestPath)) return null;
|
|
5756
|
+
const raw = await readFile10(manifestPath, "utf-8");
|
|
5443
5757
|
const manifest = JSON.parse(raw);
|
|
5444
5758
|
if (!manifest.name) return null;
|
|
5445
5759
|
const plugin = {
|
|
@@ -5451,7 +5765,7 @@ var init_loader = __esm({
|
|
|
5451
5765
|
if (manifest.tools) {
|
|
5452
5766
|
for (const toolEntry of manifest.tools) {
|
|
5453
5767
|
try {
|
|
5454
|
-
const entrypoint =
|
|
5768
|
+
const entrypoint = join13(dir, toolEntry.entrypoint);
|
|
5455
5769
|
const mod = await import(entrypoint);
|
|
5456
5770
|
const tool = mod.default || mod.tool;
|
|
5457
5771
|
if (tool) {
|
|
@@ -5465,7 +5779,7 @@ var init_loader = __esm({
|
|
|
5465
5779
|
if (manifest.hooks) {
|
|
5466
5780
|
for (const hookEntry of manifest.hooks) {
|
|
5467
5781
|
try {
|
|
5468
|
-
const handlerPath =
|
|
5782
|
+
const handlerPath = join13(dir, hookEntry.handler);
|
|
5469
5783
|
const mod = await import(handlerPath);
|
|
5470
5784
|
const handler = mod.default || mod.handler;
|
|
5471
5785
|
if (handler) {
|
|
@@ -5523,8 +5837,8 @@ var init_plugins = __esm({
|
|
|
5523
5837
|
// src/gateway/server.ts
|
|
5524
5838
|
import { createServer } from "http";
|
|
5525
5839
|
import { WebSocketServer, WebSocket } from "ws";
|
|
5526
|
-
import { readFile as
|
|
5527
|
-
import { join as
|
|
5840
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
5841
|
+
import { join as join14, dirname as dirname2 } from "path";
|
|
5528
5842
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5529
5843
|
var GatewayServer;
|
|
5530
5844
|
var init_server = __esm({
|
|
@@ -5558,12 +5872,18 @@ var init_server = __esm({
|
|
|
5558
5872
|
pluginLoader;
|
|
5559
5873
|
adapters = [];
|
|
5560
5874
|
running = false;
|
|
5875
|
+
/** Rate limiting: track message timestamps per session */
|
|
5876
|
+
rateLimiter = /* @__PURE__ */ new Map();
|
|
5877
|
+
RATE_LIMIT_WINDOW = 6e4;
|
|
5878
|
+
// 1 minute
|
|
5879
|
+
RATE_LIMIT_MAX = 20;
|
|
5880
|
+
// max 20 messages per minute per session
|
|
5561
5881
|
constructor(config) {
|
|
5562
5882
|
this.config = config;
|
|
5563
|
-
this.sessionStore = new SessionStore(
|
|
5883
|
+
this.sessionStore = new SessionStore(join14(getConfigDir(), "conversations"));
|
|
5564
5884
|
this.toolRegistry = createFullRegistry();
|
|
5565
|
-
this.memoryManager = new MemoryManager(
|
|
5566
|
-
this.cronScheduler = new CronScheduler(
|
|
5885
|
+
this.memoryManager = new MemoryManager(join14(getConfigDir(), "memory"));
|
|
5886
|
+
this.cronScheduler = new CronScheduler(join14(getConfigDir(), "cron"));
|
|
5567
5887
|
this.configWatcher = new ConfigWatcher();
|
|
5568
5888
|
this.pluginLoader = new PluginLoader();
|
|
5569
5889
|
}
|
|
@@ -5633,6 +5953,10 @@ var init_server = __esm({
|
|
|
5633
5953
|
* This is the main entry point for all non-WebSocket messages.
|
|
5634
5954
|
*/
|
|
5635
5955
|
async handleInboundMessage(context, text) {
|
|
5956
|
+
const rlKey = deriveSessionKey(context);
|
|
5957
|
+
if (this.isRateLimited(rlKey)) {
|
|
5958
|
+
throw new Error("Rate limited \u2014 too many messages. Wait a moment.");
|
|
5959
|
+
}
|
|
5636
5960
|
const agentId = resolveRoute(
|
|
5637
5961
|
context,
|
|
5638
5962
|
[],
|
|
@@ -5644,6 +5968,58 @@ var init_server = __esm({
|
|
|
5644
5968
|
const engine = await this.getOrCreateEngine(sessionKey, agentId, context.channel);
|
|
5645
5969
|
return engine.sendMessage(text);
|
|
5646
5970
|
}
|
|
5971
|
+
/**
|
|
5972
|
+
* Handle an inbound message with streaming callbacks.
|
|
5973
|
+
* Used by channel adapters for real-time streaming (e.g., Telegram message editing).
|
|
5974
|
+
*/
|
|
5975
|
+
async handleInboundMessageStreaming(context, text, callbacks) {
|
|
5976
|
+
const rlKey = deriveSessionKey(context);
|
|
5977
|
+
if (this.isRateLimited(rlKey)) {
|
|
5978
|
+
throw new Error("Rate limited \u2014 too many messages. Wait a moment.");
|
|
5979
|
+
}
|
|
5980
|
+
return this._handleInboundMessageStreamingInner(context, text, callbacks);
|
|
5981
|
+
}
|
|
5982
|
+
async _handleInboundMessageStreamingInner(context, text, callbacks) {
|
|
5983
|
+
const agentId = resolveRoute(
|
|
5984
|
+
context,
|
|
5985
|
+
[],
|
|
5986
|
+
this.config.agents.list.map((a) => ({ id: a.id, name: a.name })),
|
|
5987
|
+
this.config.agents.list[0]?.id || "default"
|
|
5988
|
+
);
|
|
5989
|
+
const sessionKey = deriveSessionKey(context);
|
|
5990
|
+
const engine = await this.getOrCreateEngine(sessionKey, agentId, context.channel);
|
|
5991
|
+
const listeners = [];
|
|
5992
|
+
if (callbacks.onToken) {
|
|
5993
|
+
const fn = (data) => callbacks.onToken(data.content);
|
|
5994
|
+
engine.on("token", fn);
|
|
5995
|
+
listeners.push(["token", fn]);
|
|
5996
|
+
}
|
|
5997
|
+
if (callbacks.onToolStart) {
|
|
5998
|
+
const fn = (data) => callbacks.onToolStart(data.name);
|
|
5999
|
+
engine.on("tool-start", fn);
|
|
6000
|
+
listeners.push(["tool-start", fn]);
|
|
6001
|
+
}
|
|
6002
|
+
if (callbacks.onToolResult) {
|
|
6003
|
+
const fn = (data) => {
|
|
6004
|
+
const d = data;
|
|
6005
|
+
callbacks.onToolResult(d.name, d.success);
|
|
6006
|
+
};
|
|
6007
|
+
engine.on("tool-result", fn);
|
|
6008
|
+
listeners.push(["tool-result", fn]);
|
|
6009
|
+
}
|
|
6010
|
+
if (callbacks.onError) {
|
|
6011
|
+
const fn = (data) => callbacks.onError(data.message);
|
|
6012
|
+
engine.on("error", fn);
|
|
6013
|
+
listeners.push(["error", fn]);
|
|
6014
|
+
}
|
|
6015
|
+
try {
|
|
6016
|
+
return await engine.sendMessage(text);
|
|
6017
|
+
} finally {
|
|
6018
|
+
for (const [event, fn] of listeners) {
|
|
6019
|
+
engine.removeListener(event, fn);
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
5647
6023
|
/** Stop the gateway server */
|
|
5648
6024
|
async stop() {
|
|
5649
6025
|
this.running = false;
|
|
@@ -5677,7 +6053,7 @@ var init_server = __esm({
|
|
|
5677
6053
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5678
6054
|
res.end(JSON.stringify({
|
|
5679
6055
|
status: "ok",
|
|
5680
|
-
version: "1.
|
|
6056
|
+
version: "1.4.1",
|
|
5681
6057
|
uptime: process.uptime(),
|
|
5682
6058
|
clients: this.clients.size,
|
|
5683
6059
|
agents: this.engines.size
|
|
@@ -5707,14 +6083,14 @@ var init_server = __esm({
|
|
|
5707
6083
|
if (url === "/chat" || url === "/") {
|
|
5708
6084
|
try {
|
|
5709
6085
|
const __dirname4 = dirname2(fileURLToPath2(import.meta.url));
|
|
5710
|
-
const htmlPath =
|
|
5711
|
-
const html = await
|
|
6086
|
+
const htmlPath = join14(__dirname4, "..", "web", "index.html");
|
|
6087
|
+
const html = await readFile11(htmlPath, "utf-8");
|
|
5712
6088
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
5713
6089
|
res.end(html);
|
|
5714
6090
|
return;
|
|
5715
6091
|
} catch {
|
|
5716
6092
|
try {
|
|
5717
|
-
const html = await
|
|
6093
|
+
const html = await readFile11(join14(process.cwd(), "src", "web", "index.html"), "utf-8");
|
|
5718
6094
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
5719
6095
|
res.end(html);
|
|
5720
6096
|
return;
|
|
@@ -5785,7 +6161,7 @@ var init_server = __esm({
|
|
|
5785
6161
|
const hello = {
|
|
5786
6162
|
type: "hello",
|
|
5787
6163
|
protocol: PROTOCOL_VERSION,
|
|
5788
|
-
version: "1.
|
|
6164
|
+
version: "1.4.1",
|
|
5789
6165
|
agents: this.config.agents.list.map((a) => ({
|
|
5790
6166
|
id: a.id,
|
|
5791
6167
|
name: a.name || a.id,
|
|
@@ -5959,6 +6335,10 @@ var init_server = __esm({
|
|
|
5959
6335
|
this.sendResponse(client, frame.id, false, void 0, "message is required");
|
|
5960
6336
|
return;
|
|
5961
6337
|
}
|
|
6338
|
+
if (this.isRateLimited(client.sessionKey)) {
|
|
6339
|
+
this.sendResponse(client, frame.id, false, void 0, "Rate limited \u2014 too many messages. Wait a moment.");
|
|
6340
|
+
return;
|
|
6341
|
+
}
|
|
5962
6342
|
try {
|
|
5963
6343
|
const engine = await this.getOrCreateEngine(client.sessionKey, client.agentId, client.clientName);
|
|
5964
6344
|
const cleanup = this.wireEngineEvents(engine, client);
|
|
@@ -5973,6 +6353,15 @@ var init_server = __esm({
|
|
|
5973
6353
|
this.sendResponse(client, frame.id, false, void 0, msg);
|
|
5974
6354
|
}
|
|
5975
6355
|
}
|
|
6356
|
+
/** Check if a session is rate limited */
|
|
6357
|
+
isRateLimited(sessionKey) {
|
|
6358
|
+
const now = Date.now();
|
|
6359
|
+
const timestamps = this.rateLimiter.get(sessionKey) || [];
|
|
6360
|
+
const recent = timestamps.filter((t) => now - t < this.RATE_LIMIT_WINDOW);
|
|
6361
|
+
recent.push(now);
|
|
6362
|
+
this.rateLimiter.set(sessionKey, recent);
|
|
6363
|
+
return recent.length > this.RATE_LIMIT_MAX;
|
|
6364
|
+
}
|
|
5976
6365
|
/** Cancel current request for a client */
|
|
5977
6366
|
handleCancel(client) {
|
|
5978
6367
|
const engine = this.engines.get(client.sessionKey);
|
|
@@ -5998,10 +6387,14 @@ var init_server = __esm({
|
|
|
5998
6387
|
toolTier: agentConfig?.toolTier || this.config.agents.defaults.toolTier || "auto",
|
|
5999
6388
|
tools: agentConfig?.tools
|
|
6000
6389
|
};
|
|
6390
|
+
const compact = agentConfig?.compactPrompt ?? this.config.agents.defaults.compactPrompt ?? false;
|
|
6391
|
+
const thinking = agentConfig?.thinking ?? this.config.agents.defaults.thinking ?? "auto";
|
|
6001
6392
|
const systemPrompt = await buildSystemPrompt({
|
|
6002
6393
|
identity,
|
|
6003
6394
|
workspaceDir: identity.workspace,
|
|
6004
|
-
channel
|
|
6395
|
+
channel,
|
|
6396
|
+
compact,
|
|
6397
|
+
thinking
|
|
6005
6398
|
});
|
|
6006
6399
|
const memoryBlock = await this.memoryManager.buildMemoryBlock("", identity.workspace);
|
|
6007
6400
|
const fullPrompt = memoryBlock ? systemPrompt + "\n\n---\n\n" + memoryBlock : systemPrompt;
|
|
@@ -6116,9 +6509,9 @@ __export(gateway_cmd_exports, {
|
|
|
6116
6509
|
isGatewayRunning: () => isGatewayRunning
|
|
6117
6510
|
});
|
|
6118
6511
|
import { fork } from "child_process";
|
|
6119
|
-
import { writeFile as
|
|
6120
|
-
import { existsSync as
|
|
6121
|
-
import { join as
|
|
6512
|
+
import { writeFile as writeFile8, readFile as readFile12, unlink as unlink3 } from "fs/promises";
|
|
6513
|
+
import { existsSync as existsSync9 } from "fs";
|
|
6514
|
+
import { join as join15, dirname as dirname3 } from "path";
|
|
6122
6515
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6123
6516
|
async function isGatewayRunning(port) {
|
|
6124
6517
|
const config = await loadConfig();
|
|
@@ -6131,7 +6524,7 @@ async function isGatewayRunning(port) {
|
|
|
6131
6524
|
}
|
|
6132
6525
|
}
|
|
6133
6526
|
function pidFilePath() {
|
|
6134
|
-
return
|
|
6527
|
+
return join15(getConfigDir(), "gateway.pid");
|
|
6135
6528
|
}
|
|
6136
6529
|
async function gatewayStartForeground(opts) {
|
|
6137
6530
|
await ensureConfigDir();
|
|
@@ -6143,7 +6536,7 @@ async function gatewayStartForeground(opts) {
|
|
|
6143
6536
|
console.log(green2(` Gateway already running on port ${config.gateway.port}`));
|
|
6144
6537
|
return;
|
|
6145
6538
|
}
|
|
6146
|
-
await
|
|
6539
|
+
await writeFile8(pidFilePath(), String(process.pid), "utf-8");
|
|
6147
6540
|
const server = new GatewayServer(config);
|
|
6148
6541
|
const shutdown = async () => {
|
|
6149
6542
|
console.log(dim2("\nShutting down..."));
|
|
@@ -6176,10 +6569,10 @@ async function gatewayStartBackground() {
|
|
|
6176
6569
|
return true;
|
|
6177
6570
|
}
|
|
6178
6571
|
console.log(dim2(" Starting gateway in background..."));
|
|
6179
|
-
const entryPoint =
|
|
6180
|
-
const logFile =
|
|
6572
|
+
const entryPoint = join15(dirname3(__filename2), "index.js");
|
|
6573
|
+
const logFile = join15(getConfigDir(), "logs", "gateway.log");
|
|
6181
6574
|
const { mkdir: mkdir7 } = await import("fs/promises");
|
|
6182
|
-
await mkdir7(
|
|
6575
|
+
await mkdir7(join15(getConfigDir(), "logs"), { recursive: true });
|
|
6183
6576
|
const child = fork(entryPoint, ["gateway", "start", "--foreground"], {
|
|
6184
6577
|
detached: true,
|
|
6185
6578
|
stdio: ["ignore", "ignore", "ignore", "ipc"]
|
|
@@ -6208,9 +6601,9 @@ async function gatewayStart(opts) {
|
|
|
6208
6601
|
}
|
|
6209
6602
|
async function gatewayStop() {
|
|
6210
6603
|
const pidPath = pidFilePath();
|
|
6211
|
-
if (
|
|
6604
|
+
if (existsSync9(pidPath)) {
|
|
6212
6605
|
try {
|
|
6213
|
-
const pid = parseInt(await
|
|
6606
|
+
const pid = parseInt(await readFile12(pidPath, "utf-8"), 10);
|
|
6214
6607
|
process.kill(pid, "SIGTERM");
|
|
6215
6608
|
await unlink3(pidPath);
|
|
6216
6609
|
console.log(green2("Gateway stopped"));
|
|
@@ -6241,8 +6634,8 @@ async function gatewayStatus() {
|
|
|
6241
6634
|
console.log(dim2(` Clients: ${data.clients?.length || 0}`));
|
|
6242
6635
|
console.log(dim2(` Sessions: ${data.sessions?.length || 0}`));
|
|
6243
6636
|
const pidPath = pidFilePath();
|
|
6244
|
-
if (
|
|
6245
|
-
const pid = await
|
|
6637
|
+
if (existsSync9(pidPath)) {
|
|
6638
|
+
const pid = await readFile12(pidPath, "utf-8");
|
|
6246
6639
|
console.log(dim2(` PID: ${pid.trim()}`));
|
|
6247
6640
|
}
|
|
6248
6641
|
} else {
|
|
@@ -6270,8 +6663,8 @@ var init_gateway_cmd = __esm({
|
|
|
6270
6663
|
});
|
|
6271
6664
|
|
|
6272
6665
|
// src/daemon/install.ts
|
|
6273
|
-
import { writeFile as
|
|
6274
|
-
import { join as
|
|
6666
|
+
import { writeFile as writeFile9, mkdir as mkdir6, unlink as unlink4 } from "fs/promises";
|
|
6667
|
+
import { join as join16 } from "path";
|
|
6275
6668
|
import { homedir as homedir3, platform as platform4 } from "os";
|
|
6276
6669
|
import { execSync } from "child_process";
|
|
6277
6670
|
async function installDaemon() {
|
|
@@ -6339,8 +6732,8 @@ async function daemonStatus() {
|
|
|
6339
6732
|
}
|
|
6340
6733
|
}
|
|
6341
6734
|
async function installLaunchd() {
|
|
6342
|
-
const plistDir =
|
|
6343
|
-
const plistPath =
|
|
6735
|
+
const plistDir = join16(homedir3(), "Library", "LaunchAgents");
|
|
6736
|
+
const plistPath = join16(plistDir, "com.clank.gateway.plist");
|
|
6344
6737
|
const clankPath = execSync("which clank || echo clank", { encoding: "utf-8" }).trim();
|
|
6345
6738
|
await mkdir6(plistDir, { recursive: true });
|
|
6346
6739
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -6361,18 +6754,18 @@ async function installLaunchd() {
|
|
|
6361
6754
|
<key>KeepAlive</key>
|
|
6362
6755
|
<true/>
|
|
6363
6756
|
<key>StandardOutPath</key>
|
|
6364
|
-
<string>${
|
|
6757
|
+
<string>${join16(homedir3(), ".clank", "logs", "gateway.log")}</string>
|
|
6365
6758
|
<key>StandardErrorPath</key>
|
|
6366
|
-
<string>${
|
|
6759
|
+
<string>${join16(homedir3(), ".clank", "logs", "gateway-error.log")}</string>
|
|
6367
6760
|
</dict>
|
|
6368
6761
|
</plist>`;
|
|
6369
|
-
await
|
|
6762
|
+
await writeFile9(plistPath, plist, "utf-8");
|
|
6370
6763
|
execSync(`launchctl load "${plistPath}"`);
|
|
6371
6764
|
console.log(green3("Daemon installed (launchd)"));
|
|
6372
6765
|
console.log(dim3(` Plist: ${plistPath}`));
|
|
6373
6766
|
}
|
|
6374
6767
|
async function uninstallLaunchd() {
|
|
6375
|
-
const plistPath =
|
|
6768
|
+
const plistPath = join16(homedir3(), "Library", "LaunchAgents", "com.clank.gateway.plist");
|
|
6376
6769
|
try {
|
|
6377
6770
|
execSync(`launchctl unload "${plistPath}"`);
|
|
6378
6771
|
await unlink4(plistPath);
|
|
@@ -6410,8 +6803,8 @@ async function uninstallTaskScheduler() {
|
|
|
6410
6803
|
}
|
|
6411
6804
|
}
|
|
6412
6805
|
async function installSystemd() {
|
|
6413
|
-
const unitDir =
|
|
6414
|
-
const unitPath =
|
|
6806
|
+
const unitDir = join16(homedir3(), ".config", "systemd", "user");
|
|
6807
|
+
const unitPath = join16(unitDir, "clank-gateway.service");
|
|
6415
6808
|
const clankPath = execSync("which clank || echo clank", { encoding: "utf-8" }).trim();
|
|
6416
6809
|
await mkdir6(unitDir, { recursive: true });
|
|
6417
6810
|
const unit = `[Unit]
|
|
@@ -6426,7 +6819,7 @@ RestartSec=5
|
|
|
6426
6819
|
[Install]
|
|
6427
6820
|
WantedBy=default.target
|
|
6428
6821
|
`;
|
|
6429
|
-
await
|
|
6822
|
+
await writeFile9(unitPath, unit, "utf-8");
|
|
6430
6823
|
execSync("systemctl --user daemon-reload");
|
|
6431
6824
|
execSync("systemctl --user enable clank-gateway");
|
|
6432
6825
|
execSync("systemctl --user start clank-gateway");
|
|
@@ -6437,7 +6830,7 @@ async function uninstallSystemd() {
|
|
|
6437
6830
|
try {
|
|
6438
6831
|
execSync("systemctl --user stop clank-gateway");
|
|
6439
6832
|
execSync("systemctl --user disable clank-gateway");
|
|
6440
|
-
const unitPath =
|
|
6833
|
+
const unitPath = join16(homedir3(), ".config", "systemd", "user", "clank-gateway.service");
|
|
6441
6834
|
await unlink4(unitPath);
|
|
6442
6835
|
execSync("systemctl --user daemon-reload");
|
|
6443
6836
|
console.log(green3("Daemon uninstalled"));
|
|
@@ -6478,7 +6871,7 @@ __export(setup_exports, {
|
|
|
6478
6871
|
});
|
|
6479
6872
|
import { createInterface as createInterface2 } from "readline";
|
|
6480
6873
|
import { randomBytes } from "crypto";
|
|
6481
|
-
import { dirname as dirname4, join as
|
|
6874
|
+
import { dirname as dirname4, join as join17 } from "path";
|
|
6482
6875
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6483
6876
|
function ask(rl, question) {
|
|
6484
6877
|
return new Promise((resolve4) => rl.question(question, resolve4));
|
|
@@ -6581,8 +6974,8 @@ async function runSetup(opts) {
|
|
|
6581
6974
|
console.log("");
|
|
6582
6975
|
console.log(dim4(" Creating workspace..."));
|
|
6583
6976
|
const { ensureWorkspaceFiles: ensureWorkspaceFiles2 } = await Promise.resolve().then(() => (init_system_prompt(), system_prompt_exports));
|
|
6584
|
-
const templateDir =
|
|
6585
|
-
const wsDir =
|
|
6977
|
+
const templateDir = join17(__dirname2, "..", "workspace", "templates");
|
|
6978
|
+
const wsDir = join17(getConfigDir(), "workspace");
|
|
6586
6979
|
try {
|
|
6587
6980
|
await ensureWorkspaceFiles2(wsDir, templateDir);
|
|
6588
6981
|
} catch {
|
|
@@ -6743,9 +7136,9 @@ var fix_exports = {};
|
|
|
6743
7136
|
__export(fix_exports, {
|
|
6744
7137
|
runFix: () => runFix
|
|
6745
7138
|
});
|
|
6746
|
-
import { existsSync as
|
|
7139
|
+
import { existsSync as existsSync10 } from "fs";
|
|
6747
7140
|
import { readdir as readdir7 } from "fs/promises";
|
|
6748
|
-
import { join as
|
|
7141
|
+
import { join as join18 } from "path";
|
|
6749
7142
|
async function runFix(opts) {
|
|
6750
7143
|
console.log("");
|
|
6751
7144
|
console.log(" Clank Diagnostics");
|
|
@@ -6775,7 +7168,7 @@ async function runFix(opts) {
|
|
|
6775
7168
|
}
|
|
6776
7169
|
async function checkConfig() {
|
|
6777
7170
|
const configPath = getConfigPath();
|
|
6778
|
-
if (!
|
|
7171
|
+
if (!existsSync10(configPath)) {
|
|
6779
7172
|
return {
|
|
6780
7173
|
name: "Config",
|
|
6781
7174
|
status: "warn",
|
|
@@ -6843,8 +7236,8 @@ async function checkModels() {
|
|
|
6843
7236
|
return { name: "Model (primary)", status: "ok", message: modelId };
|
|
6844
7237
|
}
|
|
6845
7238
|
async function checkSessions() {
|
|
6846
|
-
const sessDir =
|
|
6847
|
-
if (!
|
|
7239
|
+
const sessDir = join18(getConfigDir(), "conversations");
|
|
7240
|
+
if (!existsSync10(sessDir)) {
|
|
6848
7241
|
return { name: "Sessions", status: "ok", message: "no sessions yet" };
|
|
6849
7242
|
}
|
|
6850
7243
|
try {
|
|
@@ -6856,8 +7249,8 @@ async function checkSessions() {
|
|
|
6856
7249
|
}
|
|
6857
7250
|
}
|
|
6858
7251
|
async function checkWorkspace() {
|
|
6859
|
-
const wsDir =
|
|
6860
|
-
if (!
|
|
7252
|
+
const wsDir = join18(getConfigDir(), "workspace");
|
|
7253
|
+
if (!existsSync10(wsDir)) {
|
|
6861
7254
|
return {
|
|
6862
7255
|
name: "Workspace",
|
|
6863
7256
|
status: "warn",
|
|
@@ -7150,7 +7543,7 @@ async function runTui(opts) {
|
|
|
7150
7543
|
ws.on("open", () => {
|
|
7151
7544
|
ws.send(JSON.stringify({
|
|
7152
7545
|
type: "connect",
|
|
7153
|
-
params: { auth: { token }, mode: "tui", version: "1.
|
|
7546
|
+
params: { auth: { token }, mode: "tui", version: "1.4.1" }
|
|
7154
7547
|
}));
|
|
7155
7548
|
});
|
|
7156
7549
|
ws.on("message", (data) => {
|
|
@@ -7499,7 +7892,7 @@ __export(uninstall_exports, {
|
|
|
7499
7892
|
});
|
|
7500
7893
|
import { createInterface as createInterface4 } from "readline";
|
|
7501
7894
|
import { rm } from "fs/promises";
|
|
7502
|
-
import { existsSync as
|
|
7895
|
+
import { existsSync as existsSync11 } from "fs";
|
|
7503
7896
|
async function runUninstall(opts) {
|
|
7504
7897
|
const configDir = getConfigDir();
|
|
7505
7898
|
console.log("");
|
|
@@ -7538,7 +7931,7 @@ async function runUninstall(opts) {
|
|
|
7538
7931
|
} catch {
|
|
7539
7932
|
}
|
|
7540
7933
|
console.log(dim10(" Deleting data..."));
|
|
7541
|
-
if (
|
|
7934
|
+
if (existsSync11(configDir)) {
|
|
7542
7935
|
await rm(configDir, { recursive: true, force: true });
|
|
7543
7936
|
console.log(green10(` Removed ${configDir}`));
|
|
7544
7937
|
} else {
|
|
@@ -7576,12 +7969,12 @@ init_esm_shims();
|
|
|
7576
7969
|
import { Command } from "commander";
|
|
7577
7970
|
import { readFileSync } from "fs";
|
|
7578
7971
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7579
|
-
import { dirname as dirname5, join as
|
|
7972
|
+
import { dirname as dirname5, join as join19 } from "path";
|
|
7580
7973
|
var __filename3 = fileURLToPath5(import.meta.url);
|
|
7581
7974
|
var __dirname3 = dirname5(__filename3);
|
|
7582
|
-
var version = "1.
|
|
7975
|
+
var version = "1.4.1";
|
|
7583
7976
|
try {
|
|
7584
|
-
const pkg = JSON.parse(readFileSync(
|
|
7977
|
+
const pkg = JSON.parse(readFileSync(join19(__dirname3, "..", "package.json"), "utf-8"));
|
|
7585
7978
|
version = pkg.version;
|
|
7586
7979
|
} catch {
|
|
7587
7980
|
}
|
|
@@ -7690,10 +8083,10 @@ pipeline.command("status <id>").description("Check pipeline execution status").a
|
|
|
7690
8083
|
});
|
|
7691
8084
|
var cron = program.command("cron").description("Manage scheduled jobs");
|
|
7692
8085
|
cron.command("list").description("List cron jobs").action(async () => {
|
|
7693
|
-
const { join:
|
|
8086
|
+
const { join: join20 } = await import("path");
|
|
7694
8087
|
const { getConfigDir: getConfigDir3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
7695
8088
|
const { CronScheduler: CronScheduler2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
7696
|
-
const scheduler = new CronScheduler2(
|
|
8089
|
+
const scheduler = new CronScheduler2(join20(getConfigDir3(), "cron"));
|
|
7697
8090
|
await scheduler.init();
|
|
7698
8091
|
const jobs = scheduler.listJobs();
|
|
7699
8092
|
if (jobs.length === 0) {
|
|
@@ -7705,10 +8098,10 @@ cron.command("list").description("List cron jobs").action(async () => {
|
|
|
7705
8098
|
}
|
|
7706
8099
|
});
|
|
7707
8100
|
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) => {
|
|
7708
|
-
const { join:
|
|
8101
|
+
const { join: join20 } = await import("path");
|
|
7709
8102
|
const { getConfigDir: getConfigDir3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
7710
8103
|
const { CronScheduler: CronScheduler2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
7711
|
-
const scheduler = new CronScheduler2(
|
|
8104
|
+
const scheduler = new CronScheduler2(join20(getConfigDir3(), "cron"));
|
|
7712
8105
|
await scheduler.init();
|
|
7713
8106
|
const job = await scheduler.addJob({
|
|
7714
8107
|
name: opts.name || "CLI Job",
|
|
@@ -7719,10 +8112,10 @@ cron.command("add").description("Add a cron job").requiredOption("--schedule <ex
|
|
|
7719
8112
|
console.log(` Job created: ${job.id.slice(0, 8)} \u2014 "${job.name}" every ${job.schedule}`);
|
|
7720
8113
|
});
|
|
7721
8114
|
cron.command("remove <id>").description("Remove a cron job").action(async (id) => {
|
|
7722
|
-
const { join:
|
|
8115
|
+
const { join: join20 } = await import("path");
|
|
7723
8116
|
const { getConfigDir: getConfigDir3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
7724
8117
|
const { CronScheduler: CronScheduler2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
7725
|
-
const scheduler = new CronScheduler2(
|
|
8118
|
+
const scheduler = new CronScheduler2(join20(getConfigDir3(), "cron"));
|
|
7726
8119
|
await scheduler.init();
|
|
7727
8120
|
const removed = await scheduler.removeJob(id);
|
|
7728
8121
|
console.log(removed ? ` Job ${id.slice(0, 8)} removed` : ` Job not found`);
|