@tractorscorch/clank 1.3.1 → 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 +27 -0
- package/dist/index.js +583 -222
- 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") {
|
|
@@ -4041,12 +4152,12 @@ var init_tts = __esm({
|
|
|
4041
4152
|
/** Transcribe via local whisper.cpp */
|
|
4042
4153
|
async transcribeLocal(audioBuffer, format) {
|
|
4043
4154
|
try {
|
|
4044
|
-
const { writeFile:
|
|
4155
|
+
const { writeFile: writeFile10, unlink: unlink5 } = await import("fs/promises");
|
|
4045
4156
|
const { execSync: execSync3 } = await import("child_process");
|
|
4046
|
-
const { join:
|
|
4157
|
+
const { join: join20 } = await import("path");
|
|
4047
4158
|
const { tmpdir } = await import("os");
|
|
4048
|
-
const tmpFile =
|
|
4049
|
-
await
|
|
4159
|
+
const tmpFile = join20(tmpdir(), `clank-stt-${Date.now()}.${format}`);
|
|
4160
|
+
await writeFile10(tmpFile, audioBuffer);
|
|
4050
4161
|
const output = execSync3(`whisper "${tmpFile}" --model base.en --output-txt`, {
|
|
4051
4162
|
encoding: "utf-8",
|
|
4052
4163
|
timeout: 6e4
|
|
@@ -4114,11 +4225,11 @@ var init_voice_tool = __esm({
|
|
|
4114
4225
|
voiceId: args.voice_id
|
|
4115
4226
|
});
|
|
4116
4227
|
if (!result) return "Error: TTS synthesis failed";
|
|
4117
|
-
const { writeFile:
|
|
4118
|
-
const { join:
|
|
4228
|
+
const { writeFile: writeFile10 } = await import("fs/promises");
|
|
4229
|
+
const { join: join20 } = await import("path");
|
|
4119
4230
|
const { tmpdir } = await import("os");
|
|
4120
|
-
const outPath =
|
|
4121
|
-
await
|
|
4231
|
+
const outPath = join20(tmpdir(), `clank-tts-${Date.now()}.${result.format}`);
|
|
4232
|
+
await writeFile10(outPath, result.audioBuffer);
|
|
4122
4233
|
return `Audio generated: ${outPath} (${result.format}, ${Math.round(result.audioBuffer.length / 1024)}KB)`;
|
|
4123
4234
|
}
|
|
4124
4235
|
};
|
|
@@ -4141,16 +4252,16 @@ var init_voice_tool = __esm({
|
|
|
4141
4252
|
return { ok: true };
|
|
4142
4253
|
},
|
|
4143
4254
|
async execute(args) {
|
|
4144
|
-
const { readFile:
|
|
4145
|
-
const { existsSync:
|
|
4255
|
+
const { readFile: readFile13 } = await import("fs/promises");
|
|
4256
|
+
const { existsSync: existsSync12 } = await import("fs");
|
|
4146
4257
|
const filePath = args.file_path;
|
|
4147
|
-
if (!
|
|
4258
|
+
if (!existsSync12(filePath)) return `Error: File not found: ${filePath}`;
|
|
4148
4259
|
const config = await loadConfig();
|
|
4149
4260
|
const engine = new STTEngine(config);
|
|
4150
4261
|
if (!engine.isAvailable()) {
|
|
4151
4262
|
return "Error: Speech-to-text not configured. Need OpenAI API key or local whisper.cpp installed.";
|
|
4152
4263
|
}
|
|
4153
|
-
const audioBuffer = await
|
|
4264
|
+
const audioBuffer = await readFile13(filePath);
|
|
4154
4265
|
const ext = filePath.split(".").pop() || "wav";
|
|
4155
4266
|
const result = await engine.transcribe(audioBuffer, ext);
|
|
4156
4267
|
if (!result) return "Error: Transcription failed";
|
|
@@ -4182,6 +4293,53 @@ var init_voice_tool = __esm({
|
|
|
4182
4293
|
}
|
|
4183
4294
|
});
|
|
4184
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
|
+
|
|
4185
4343
|
// src/tools/self-config/index.ts
|
|
4186
4344
|
function registerSelfConfigTools(registry) {
|
|
4187
4345
|
registry.register(configTool);
|
|
@@ -4195,6 +4353,7 @@ function registerSelfConfigTools(registry) {
|
|
|
4195
4353
|
registry.register(ttsTool);
|
|
4196
4354
|
registry.register(sttTool);
|
|
4197
4355
|
registry.register(voiceListTool);
|
|
4356
|
+
registry.register(fileShareTool);
|
|
4198
4357
|
}
|
|
4199
4358
|
var init_self_config = __esm({
|
|
4200
4359
|
"src/tools/self-config/index.ts"() {
|
|
@@ -4209,6 +4368,7 @@ var init_self_config = __esm({
|
|
|
4209
4368
|
init_gateway_tool();
|
|
4210
4369
|
init_message_tool();
|
|
4211
4370
|
init_voice_tool();
|
|
4371
|
+
init_file_share_tool();
|
|
4212
4372
|
init_config_tool();
|
|
4213
4373
|
init_channel_tool();
|
|
4214
4374
|
init_agent_tool();
|
|
@@ -4218,6 +4378,7 @@ var init_self_config = __esm({
|
|
|
4218
4378
|
init_gateway_tool();
|
|
4219
4379
|
init_message_tool();
|
|
4220
4380
|
init_voice_tool();
|
|
4381
|
+
init_file_share_tool();
|
|
4221
4382
|
}
|
|
4222
4383
|
});
|
|
4223
4384
|
|
|
@@ -4279,7 +4440,7 @@ __export(chat_exports, {
|
|
|
4279
4440
|
runChat: () => runChat
|
|
4280
4441
|
});
|
|
4281
4442
|
import { createInterface } from "readline";
|
|
4282
|
-
import { join as
|
|
4443
|
+
import { join as join11 } from "path";
|
|
4283
4444
|
async function runChat(opts) {
|
|
4284
4445
|
await ensureConfigDir();
|
|
4285
4446
|
const config = await loadConfig();
|
|
@@ -4296,9 +4457,9 @@ async function runChat(opts) {
|
|
|
4296
4457
|
console.log(dim("Starting gateway..."));
|
|
4297
4458
|
const { fork: fork2 } = await import("child_process");
|
|
4298
4459
|
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
4299
|
-
const { dirname: dirname6, join:
|
|
4460
|
+
const { dirname: dirname6, join: join20 } = await import("path");
|
|
4300
4461
|
const __filename4 = fileURLToPath6(import.meta.url);
|
|
4301
|
-
const entryPoint =
|
|
4462
|
+
const entryPoint = join20(dirname6(__filename4), "index.js");
|
|
4302
4463
|
const child = fork2(entryPoint, ["gateway", "start", "--foreground"], {
|
|
4303
4464
|
detached: true,
|
|
4304
4465
|
stdio: "ignore"
|
|
@@ -4346,7 +4507,7 @@ async function runChat(opts) {
|
|
|
4346
4507
|
console.error(dim("Make sure Ollama is running or configure a cloud provider in ~/.clank/config.json5"));
|
|
4347
4508
|
process.exit(1);
|
|
4348
4509
|
}
|
|
4349
|
-
const sessionStore = new SessionStore(
|
|
4510
|
+
const sessionStore = new SessionStore(join11(getConfigDir(), "conversations"));
|
|
4350
4511
|
await sessionStore.init();
|
|
4351
4512
|
const toolRegistry = createFullRegistry();
|
|
4352
4513
|
const identity = {
|
|
@@ -4504,9 +4665,9 @@ var init_chat = __esm({
|
|
|
4504
4665
|
});
|
|
4505
4666
|
|
|
4506
4667
|
// src/memory/memory.ts
|
|
4507
|
-
import { readFile as
|
|
4508
|
-
import { existsSync as
|
|
4509
|
-
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";
|
|
4510
4671
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4511
4672
|
function tokenize(text) {
|
|
4512
4673
|
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3 && !STOPWORDS.has(t));
|
|
@@ -4654,12 +4815,12 @@ var init_memory = __esm({
|
|
|
4654
4815
|
meta = /* @__PURE__ */ new Map();
|
|
4655
4816
|
constructor(memoryDir) {
|
|
4656
4817
|
this.memoryDir = memoryDir;
|
|
4657
|
-
this.metaPath =
|
|
4818
|
+
this.metaPath = join12(memoryDir, "_meta.json");
|
|
4658
4819
|
}
|
|
4659
4820
|
/** Initialize — create dirs and load metadata */
|
|
4660
4821
|
async init() {
|
|
4661
4822
|
for (const cat of ["identity", "knowledge", "lessons", "context"]) {
|
|
4662
|
-
await mkdir5(
|
|
4823
|
+
await mkdir5(join12(this.memoryDir, cat), { recursive: true });
|
|
4663
4824
|
}
|
|
4664
4825
|
await this.loadMeta();
|
|
4665
4826
|
}
|
|
@@ -4704,10 +4865,10 @@ var init_memory = __esm({
|
|
|
4704
4865
|
let used = 0;
|
|
4705
4866
|
if (projectRoot) {
|
|
4706
4867
|
for (const name of [".clank.md", ".clankbuild.md", ".llamabuild.md"]) {
|
|
4707
|
-
const path2 =
|
|
4708
|
-
if (
|
|
4868
|
+
const path2 = join12(projectRoot, name);
|
|
4869
|
+
if (existsSync7(path2)) {
|
|
4709
4870
|
try {
|
|
4710
|
-
const content = await
|
|
4871
|
+
const content = await readFile9(path2, "utf-8");
|
|
4711
4872
|
if (content.trim() && used + content.length < budgetChars) {
|
|
4712
4873
|
parts.push("## Project Memory\n" + content.trim());
|
|
4713
4874
|
used += content.length;
|
|
@@ -4718,10 +4879,10 @@ var init_memory = __esm({
|
|
|
4718
4879
|
}
|
|
4719
4880
|
}
|
|
4720
4881
|
}
|
|
4721
|
-
const globalPath =
|
|
4722
|
-
if (
|
|
4882
|
+
const globalPath = join12(this.memoryDir, "..", "workspace", "MEMORY.md");
|
|
4883
|
+
if (existsSync7(globalPath)) {
|
|
4723
4884
|
try {
|
|
4724
|
-
const content = await
|
|
4885
|
+
const content = await readFile9(globalPath, "utf-8");
|
|
4725
4886
|
if (content.trim() && used + content.length < budgetChars) {
|
|
4726
4887
|
parts.push("## Global Memory\n" + content.trim());
|
|
4727
4888
|
used += content.length;
|
|
@@ -4742,8 +4903,8 @@ ${entry.content}`);
|
|
|
4742
4903
|
async add(category, title, content) {
|
|
4743
4904
|
const id = randomUUID3();
|
|
4744
4905
|
const filename = `${title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50)}.md`;
|
|
4745
|
-
const filePath =
|
|
4746
|
-
await
|
|
4906
|
+
const filePath = join12(this.memoryDir, category, filename);
|
|
4907
|
+
await writeFile7(filePath, `# ${title}
|
|
4747
4908
|
|
|
4748
4909
|
${content}`, "utf-8");
|
|
4749
4910
|
this.meta.set(id, {
|
|
@@ -4788,15 +4949,15 @@ ${content}`, "utf-8");
|
|
|
4788
4949
|
async loadAll() {
|
|
4789
4950
|
const entries = [];
|
|
4790
4951
|
for (const category of ["identity", "knowledge", "lessons", "context"]) {
|
|
4791
|
-
const dir =
|
|
4792
|
-
if (!
|
|
4952
|
+
const dir = join12(this.memoryDir, category);
|
|
4953
|
+
if (!existsSync7(dir)) continue;
|
|
4793
4954
|
try {
|
|
4794
4955
|
const files = await readdir5(dir);
|
|
4795
4956
|
for (const file of files) {
|
|
4796
4957
|
if (!file.endsWith(".md")) continue;
|
|
4797
|
-
const filePath =
|
|
4958
|
+
const filePath = join12(dir, file);
|
|
4798
4959
|
try {
|
|
4799
|
-
const content = await
|
|
4960
|
+
const content = await readFile9(filePath, "utf-8");
|
|
4800
4961
|
const title = content.split("\n")[0]?.replace(/^#\s*/, "") || file;
|
|
4801
4962
|
entries.push({
|
|
4802
4963
|
id: file,
|
|
@@ -4821,9 +4982,9 @@ ${content}`, "utf-8");
|
|
|
4821
4982
|
return recency * frequencyBoost;
|
|
4822
4983
|
}
|
|
4823
4984
|
async loadMeta() {
|
|
4824
|
-
if (!
|
|
4985
|
+
if (!existsSync7(this.metaPath)) return;
|
|
4825
4986
|
try {
|
|
4826
|
-
const raw = await
|
|
4987
|
+
const raw = await readFile9(this.metaPath, "utf-8");
|
|
4827
4988
|
const entries = JSON.parse(raw);
|
|
4828
4989
|
for (const e of entries) this.meta.set(e.id, e);
|
|
4829
4990
|
} catch {
|
|
@@ -4831,7 +4992,7 @@ ${content}`, "utf-8");
|
|
|
4831
4992
|
}
|
|
4832
4993
|
async saveMeta() {
|
|
4833
4994
|
const entries = Array.from(this.meta.values());
|
|
4834
|
-
await
|
|
4995
|
+
await writeFile7(this.metaPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
4835
4996
|
}
|
|
4836
4997
|
};
|
|
4837
4998
|
}
|
|
@@ -4843,6 +5004,7 @@ var init_memory2 = __esm({
|
|
|
4843
5004
|
"use strict";
|
|
4844
5005
|
init_esm_shims();
|
|
4845
5006
|
init_memory();
|
|
5007
|
+
init_auto_persist();
|
|
4846
5008
|
}
|
|
4847
5009
|
});
|
|
4848
5010
|
|
|
@@ -5038,15 +5200,53 @@ var init_telegram = __esm({
|
|
|
5038
5200
|
if (!this.gateway) return;
|
|
5039
5201
|
try {
|
|
5040
5202
|
await ctx.api.sendChatAction(chatId, "typing");
|
|
5041
|
-
|
|
5203
|
+
let streamMsgId = null;
|
|
5204
|
+
let accumulated = "";
|
|
5205
|
+
let lastEditTime = 0;
|
|
5206
|
+
const EDIT_INTERVAL = 800;
|
|
5207
|
+
const response = await this.gateway.handleInboundMessageStreaming(
|
|
5042
5208
|
{
|
|
5043
5209
|
channel: "telegram",
|
|
5044
5210
|
peerId: chatId,
|
|
5045
5211
|
peerKind: isGroup ? "group" : "dm"
|
|
5046
5212
|
},
|
|
5047
|
-
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
|
+
}
|
|
5048
5244
|
);
|
|
5049
|
-
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) {
|
|
5050
5250
|
const chunks = splitMessage(response, 4e3);
|
|
5051
5251
|
for (const chunk of chunks) {
|
|
5052
5252
|
await ctx.api.sendMessage(chatId, chunk);
|
|
@@ -5110,7 +5310,8 @@ var init_telegram = __esm({
|
|
|
5110
5310
|
const { TTSEngine: TTSEngine2 } = await Promise.resolve().then(() => (init_voice(), voice_exports));
|
|
5111
5311
|
const tts = new TTSEngine2(config);
|
|
5112
5312
|
if (tts.isAvailable() && response.length < 2e3) {
|
|
5113
|
-
const
|
|
5313
|
+
const agentVoice = config.agents.list.find((a) => a.voiceId)?.voiceId;
|
|
5314
|
+
const audio = await tts.synthesize(response, { voiceId: agentVoice });
|
|
5114
5315
|
if (audio) {
|
|
5115
5316
|
const { InputFile } = await import("grammy");
|
|
5116
5317
|
await ctx.api.sendVoice(chatId, new InputFile(audio.audioBuffer, "reply.mp3"));
|
|
@@ -5132,6 +5333,94 @@ var init_telegram = __esm({
|
|
|
5132
5333
|
});
|
|
5133
5334
|
chatLocks.set(chatId, next);
|
|
5134
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
|
+
});
|
|
5135
5424
|
bot.start({
|
|
5136
5425
|
onStart: () => {
|
|
5137
5426
|
this.running = true;
|
|
@@ -5374,9 +5663,9 @@ var init_web = __esm({
|
|
|
5374
5663
|
});
|
|
5375
5664
|
|
|
5376
5665
|
// src/plugins/loader.ts
|
|
5377
|
-
import { readdir as readdir6, readFile as
|
|
5378
|
-
import { existsSync as
|
|
5379
|
-
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";
|
|
5380
5669
|
import { homedir as homedir2 } from "os";
|
|
5381
5670
|
var PluginLoader;
|
|
5382
5671
|
var init_loader = __esm({
|
|
@@ -5409,25 +5698,25 @@ var init_loader = __esm({
|
|
|
5409
5698
|
/** Discover plugin directories */
|
|
5410
5699
|
async discoverPlugins() {
|
|
5411
5700
|
const dirs = [];
|
|
5412
|
-
const userPluginDir =
|
|
5413
|
-
if (
|
|
5701
|
+
const userPluginDir = join13(homedir2(), ".clank", "plugins");
|
|
5702
|
+
if (existsSync8(userPluginDir)) {
|
|
5414
5703
|
try {
|
|
5415
5704
|
const entries = await readdir6(userPluginDir, { withFileTypes: true });
|
|
5416
5705
|
for (const entry of entries) {
|
|
5417
5706
|
if (entry.isDirectory()) {
|
|
5418
|
-
dirs.push(
|
|
5707
|
+
dirs.push(join13(userPluginDir, entry.name));
|
|
5419
5708
|
}
|
|
5420
5709
|
}
|
|
5421
5710
|
} catch {
|
|
5422
5711
|
}
|
|
5423
5712
|
}
|
|
5424
|
-
const nodeModulesDir =
|
|
5425
|
-
if (
|
|
5713
|
+
const nodeModulesDir = join13(process.cwd(), "node_modules");
|
|
5714
|
+
if (existsSync8(nodeModulesDir)) {
|
|
5426
5715
|
try {
|
|
5427
5716
|
const entries = await readdir6(nodeModulesDir);
|
|
5428
5717
|
for (const entry of entries) {
|
|
5429
5718
|
if (entry.startsWith("clank-plugin-")) {
|
|
5430
|
-
dirs.push(
|
|
5719
|
+
dirs.push(join13(nodeModulesDir, entry));
|
|
5431
5720
|
}
|
|
5432
5721
|
}
|
|
5433
5722
|
} catch {
|
|
@@ -5437,9 +5726,9 @@ var init_loader = __esm({
|
|
|
5437
5726
|
}
|
|
5438
5727
|
/** Load a single plugin from a directory */
|
|
5439
5728
|
async loadPlugin(dir) {
|
|
5440
|
-
const manifestPath =
|
|
5441
|
-
if (!
|
|
5442
|
-
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");
|
|
5443
5732
|
const manifest = JSON.parse(raw);
|
|
5444
5733
|
if (!manifest.name) return null;
|
|
5445
5734
|
const plugin = {
|
|
@@ -5451,7 +5740,7 @@ var init_loader = __esm({
|
|
|
5451
5740
|
if (manifest.tools) {
|
|
5452
5741
|
for (const toolEntry of manifest.tools) {
|
|
5453
5742
|
try {
|
|
5454
|
-
const entrypoint =
|
|
5743
|
+
const entrypoint = join13(dir, toolEntry.entrypoint);
|
|
5455
5744
|
const mod = await import(entrypoint);
|
|
5456
5745
|
const tool = mod.default || mod.tool;
|
|
5457
5746
|
if (tool) {
|
|
@@ -5465,7 +5754,7 @@ var init_loader = __esm({
|
|
|
5465
5754
|
if (manifest.hooks) {
|
|
5466
5755
|
for (const hookEntry of manifest.hooks) {
|
|
5467
5756
|
try {
|
|
5468
|
-
const handlerPath =
|
|
5757
|
+
const handlerPath = join13(dir, hookEntry.handler);
|
|
5469
5758
|
const mod = await import(handlerPath);
|
|
5470
5759
|
const handler = mod.default || mod.handler;
|
|
5471
5760
|
if (handler) {
|
|
@@ -5523,8 +5812,8 @@ var init_plugins = __esm({
|
|
|
5523
5812
|
// src/gateway/server.ts
|
|
5524
5813
|
import { createServer } from "http";
|
|
5525
5814
|
import { WebSocketServer, WebSocket } from "ws";
|
|
5526
|
-
import { readFile as
|
|
5527
|
-
import { join as
|
|
5815
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
5816
|
+
import { join as join14, dirname as dirname2 } from "path";
|
|
5528
5817
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5529
5818
|
var GatewayServer;
|
|
5530
5819
|
var init_server = __esm({
|
|
@@ -5558,12 +5847,18 @@ var init_server = __esm({
|
|
|
5558
5847
|
pluginLoader;
|
|
5559
5848
|
adapters = [];
|
|
5560
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
|
|
5561
5856
|
constructor(config) {
|
|
5562
5857
|
this.config = config;
|
|
5563
|
-
this.sessionStore = new SessionStore(
|
|
5858
|
+
this.sessionStore = new SessionStore(join14(getConfigDir(), "conversations"));
|
|
5564
5859
|
this.toolRegistry = createFullRegistry();
|
|
5565
|
-
this.memoryManager = new MemoryManager(
|
|
5566
|
-
this.cronScheduler = new CronScheduler(
|
|
5860
|
+
this.memoryManager = new MemoryManager(join14(getConfigDir(), "memory"));
|
|
5861
|
+
this.cronScheduler = new CronScheduler(join14(getConfigDir(), "cron"));
|
|
5567
5862
|
this.configWatcher = new ConfigWatcher();
|
|
5568
5863
|
this.pluginLoader = new PluginLoader();
|
|
5569
5864
|
}
|
|
@@ -5633,6 +5928,10 @@ var init_server = __esm({
|
|
|
5633
5928
|
* This is the main entry point for all non-WebSocket messages.
|
|
5634
5929
|
*/
|
|
5635
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
|
+
}
|
|
5636
5935
|
const agentId = resolveRoute(
|
|
5637
5936
|
context,
|
|
5638
5937
|
[],
|
|
@@ -5644,6 +5943,51 @@ var init_server = __esm({
|
|
|
5644
5943
|
const engine = await this.getOrCreateEngine(sessionKey, agentId, context.channel);
|
|
5645
5944
|
return engine.sendMessage(text);
|
|
5646
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
|
+
}
|
|
5647
5991
|
/** Stop the gateway server */
|
|
5648
5992
|
async stop() {
|
|
5649
5993
|
this.running = false;
|
|
@@ -5677,7 +6021,7 @@ var init_server = __esm({
|
|
|
5677
6021
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5678
6022
|
res.end(JSON.stringify({
|
|
5679
6023
|
status: "ok",
|
|
5680
|
-
version: "1.
|
|
6024
|
+
version: "1.4.0",
|
|
5681
6025
|
uptime: process.uptime(),
|
|
5682
6026
|
clients: this.clients.size,
|
|
5683
6027
|
agents: this.engines.size
|
|
@@ -5707,14 +6051,14 @@ var init_server = __esm({
|
|
|
5707
6051
|
if (url === "/chat" || url === "/") {
|
|
5708
6052
|
try {
|
|
5709
6053
|
const __dirname4 = dirname2(fileURLToPath2(import.meta.url));
|
|
5710
|
-
const htmlPath =
|
|
5711
|
-
const html = await
|
|
6054
|
+
const htmlPath = join14(__dirname4, "..", "web", "index.html");
|
|
6055
|
+
const html = await readFile11(htmlPath, "utf-8");
|
|
5712
6056
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
5713
6057
|
res.end(html);
|
|
5714
6058
|
return;
|
|
5715
6059
|
} catch {
|
|
5716
6060
|
try {
|
|
5717
|
-
const html = await
|
|
6061
|
+
const html = await readFile11(join14(process.cwd(), "src", "web", "index.html"), "utf-8");
|
|
5718
6062
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
5719
6063
|
res.end(html);
|
|
5720
6064
|
return;
|
|
@@ -5785,7 +6129,7 @@ var init_server = __esm({
|
|
|
5785
6129
|
const hello = {
|
|
5786
6130
|
type: "hello",
|
|
5787
6131
|
protocol: PROTOCOL_VERSION,
|
|
5788
|
-
version: "1.
|
|
6132
|
+
version: "1.4.0",
|
|
5789
6133
|
agents: this.config.agents.list.map((a) => ({
|
|
5790
6134
|
id: a.id,
|
|
5791
6135
|
name: a.name || a.id,
|
|
@@ -5959,6 +6303,10 @@ var init_server = __esm({
|
|
|
5959
6303
|
this.sendResponse(client, frame.id, false, void 0, "message is required");
|
|
5960
6304
|
return;
|
|
5961
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
|
+
}
|
|
5962
6310
|
try {
|
|
5963
6311
|
const engine = await this.getOrCreateEngine(client.sessionKey, client.agentId, client.clientName);
|
|
5964
6312
|
const cleanup = this.wireEngineEvents(engine, client);
|
|
@@ -5973,6 +6321,15 @@ var init_server = __esm({
|
|
|
5973
6321
|
this.sendResponse(client, frame.id, false, void 0, msg);
|
|
5974
6322
|
}
|
|
5975
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
|
+
}
|
|
5976
6333
|
/** Cancel current request for a client */
|
|
5977
6334
|
handleCancel(client) {
|
|
5978
6335
|
const engine = this.engines.get(client.sessionKey);
|
|
@@ -5998,10 +6355,14 @@ var init_server = __esm({
|
|
|
5998
6355
|
toolTier: agentConfig?.toolTier || this.config.agents.defaults.toolTier || "auto",
|
|
5999
6356
|
tools: agentConfig?.tools
|
|
6000
6357
|
};
|
|
6358
|
+
const compact = agentConfig?.compactPrompt ?? this.config.agents.defaults.compactPrompt ?? false;
|
|
6359
|
+
const thinking = agentConfig?.thinking ?? this.config.agents.defaults.thinking ?? "auto";
|
|
6001
6360
|
const systemPrompt = await buildSystemPrompt({
|
|
6002
6361
|
identity,
|
|
6003
6362
|
workspaceDir: identity.workspace,
|
|
6004
|
-
channel
|
|
6363
|
+
channel,
|
|
6364
|
+
compact,
|
|
6365
|
+
thinking
|
|
6005
6366
|
});
|
|
6006
6367
|
const memoryBlock = await this.memoryManager.buildMemoryBlock("", identity.workspace);
|
|
6007
6368
|
const fullPrompt = memoryBlock ? systemPrompt + "\n\n---\n\n" + memoryBlock : systemPrompt;
|
|
@@ -6116,9 +6477,9 @@ __export(gateway_cmd_exports, {
|
|
|
6116
6477
|
isGatewayRunning: () => isGatewayRunning
|
|
6117
6478
|
});
|
|
6118
6479
|
import { fork } from "child_process";
|
|
6119
|
-
import { writeFile as
|
|
6120
|
-
import { existsSync as
|
|
6121
|
-
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";
|
|
6122
6483
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6123
6484
|
async function isGatewayRunning(port) {
|
|
6124
6485
|
const config = await loadConfig();
|
|
@@ -6131,7 +6492,7 @@ async function isGatewayRunning(port) {
|
|
|
6131
6492
|
}
|
|
6132
6493
|
}
|
|
6133
6494
|
function pidFilePath() {
|
|
6134
|
-
return
|
|
6495
|
+
return join15(getConfigDir(), "gateway.pid");
|
|
6135
6496
|
}
|
|
6136
6497
|
async function gatewayStartForeground(opts) {
|
|
6137
6498
|
await ensureConfigDir();
|
|
@@ -6143,7 +6504,7 @@ async function gatewayStartForeground(opts) {
|
|
|
6143
6504
|
console.log(green2(` Gateway already running on port ${config.gateway.port}`));
|
|
6144
6505
|
return;
|
|
6145
6506
|
}
|
|
6146
|
-
await
|
|
6507
|
+
await writeFile8(pidFilePath(), String(process.pid), "utf-8");
|
|
6147
6508
|
const server = new GatewayServer(config);
|
|
6148
6509
|
const shutdown = async () => {
|
|
6149
6510
|
console.log(dim2("\nShutting down..."));
|
|
@@ -6176,10 +6537,10 @@ async function gatewayStartBackground() {
|
|
|
6176
6537
|
return true;
|
|
6177
6538
|
}
|
|
6178
6539
|
console.log(dim2(" Starting gateway in background..."));
|
|
6179
|
-
const entryPoint =
|
|
6180
|
-
const logFile =
|
|
6540
|
+
const entryPoint = join15(dirname3(__filename2), "index.js");
|
|
6541
|
+
const logFile = join15(getConfigDir(), "logs", "gateway.log");
|
|
6181
6542
|
const { mkdir: mkdir7 } = await import("fs/promises");
|
|
6182
|
-
await mkdir7(
|
|
6543
|
+
await mkdir7(join15(getConfigDir(), "logs"), { recursive: true });
|
|
6183
6544
|
const child = fork(entryPoint, ["gateway", "start", "--foreground"], {
|
|
6184
6545
|
detached: true,
|
|
6185
6546
|
stdio: ["ignore", "ignore", "ignore", "ipc"]
|
|
@@ -6208,9 +6569,9 @@ async function gatewayStart(opts) {
|
|
|
6208
6569
|
}
|
|
6209
6570
|
async function gatewayStop() {
|
|
6210
6571
|
const pidPath = pidFilePath();
|
|
6211
|
-
if (
|
|
6572
|
+
if (existsSync9(pidPath)) {
|
|
6212
6573
|
try {
|
|
6213
|
-
const pid = parseInt(await
|
|
6574
|
+
const pid = parseInt(await readFile12(pidPath, "utf-8"), 10);
|
|
6214
6575
|
process.kill(pid, "SIGTERM");
|
|
6215
6576
|
await unlink3(pidPath);
|
|
6216
6577
|
console.log(green2("Gateway stopped"));
|
|
@@ -6241,8 +6602,8 @@ async function gatewayStatus() {
|
|
|
6241
6602
|
console.log(dim2(` Clients: ${data.clients?.length || 0}`));
|
|
6242
6603
|
console.log(dim2(` Sessions: ${data.sessions?.length || 0}`));
|
|
6243
6604
|
const pidPath = pidFilePath();
|
|
6244
|
-
if (
|
|
6245
|
-
const pid = await
|
|
6605
|
+
if (existsSync9(pidPath)) {
|
|
6606
|
+
const pid = await readFile12(pidPath, "utf-8");
|
|
6246
6607
|
console.log(dim2(` PID: ${pid.trim()}`));
|
|
6247
6608
|
}
|
|
6248
6609
|
} else {
|
|
@@ -6270,8 +6631,8 @@ var init_gateway_cmd = __esm({
|
|
|
6270
6631
|
});
|
|
6271
6632
|
|
|
6272
6633
|
// src/daemon/install.ts
|
|
6273
|
-
import { writeFile as
|
|
6274
|
-
import { join as
|
|
6634
|
+
import { writeFile as writeFile9, mkdir as mkdir6, unlink as unlink4 } from "fs/promises";
|
|
6635
|
+
import { join as join16 } from "path";
|
|
6275
6636
|
import { homedir as homedir3, platform as platform4 } from "os";
|
|
6276
6637
|
import { execSync } from "child_process";
|
|
6277
6638
|
async function installDaemon() {
|
|
@@ -6339,8 +6700,8 @@ async function daemonStatus() {
|
|
|
6339
6700
|
}
|
|
6340
6701
|
}
|
|
6341
6702
|
async function installLaunchd() {
|
|
6342
|
-
const plistDir =
|
|
6343
|
-
const plistPath =
|
|
6703
|
+
const plistDir = join16(homedir3(), "Library", "LaunchAgents");
|
|
6704
|
+
const plistPath = join16(plistDir, "com.clank.gateway.plist");
|
|
6344
6705
|
const clankPath = execSync("which clank || echo clank", { encoding: "utf-8" }).trim();
|
|
6345
6706
|
await mkdir6(plistDir, { recursive: true });
|
|
6346
6707
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -6361,18 +6722,18 @@ async function installLaunchd() {
|
|
|
6361
6722
|
<key>KeepAlive</key>
|
|
6362
6723
|
<true/>
|
|
6363
6724
|
<key>StandardOutPath</key>
|
|
6364
|
-
<string>${
|
|
6725
|
+
<string>${join16(homedir3(), ".clank", "logs", "gateway.log")}</string>
|
|
6365
6726
|
<key>StandardErrorPath</key>
|
|
6366
|
-
<string>${
|
|
6727
|
+
<string>${join16(homedir3(), ".clank", "logs", "gateway-error.log")}</string>
|
|
6367
6728
|
</dict>
|
|
6368
6729
|
</plist>`;
|
|
6369
|
-
await
|
|
6730
|
+
await writeFile9(plistPath, plist, "utf-8");
|
|
6370
6731
|
execSync(`launchctl load "${plistPath}"`);
|
|
6371
6732
|
console.log(green3("Daemon installed (launchd)"));
|
|
6372
6733
|
console.log(dim3(` Plist: ${plistPath}`));
|
|
6373
6734
|
}
|
|
6374
6735
|
async function uninstallLaunchd() {
|
|
6375
|
-
const plistPath =
|
|
6736
|
+
const plistPath = join16(homedir3(), "Library", "LaunchAgents", "com.clank.gateway.plist");
|
|
6376
6737
|
try {
|
|
6377
6738
|
execSync(`launchctl unload "${plistPath}"`);
|
|
6378
6739
|
await unlink4(plistPath);
|
|
@@ -6410,8 +6771,8 @@ async function uninstallTaskScheduler() {
|
|
|
6410
6771
|
}
|
|
6411
6772
|
}
|
|
6412
6773
|
async function installSystemd() {
|
|
6413
|
-
const unitDir =
|
|
6414
|
-
const unitPath =
|
|
6774
|
+
const unitDir = join16(homedir3(), ".config", "systemd", "user");
|
|
6775
|
+
const unitPath = join16(unitDir, "clank-gateway.service");
|
|
6415
6776
|
const clankPath = execSync("which clank || echo clank", { encoding: "utf-8" }).trim();
|
|
6416
6777
|
await mkdir6(unitDir, { recursive: true });
|
|
6417
6778
|
const unit = `[Unit]
|
|
@@ -6426,7 +6787,7 @@ RestartSec=5
|
|
|
6426
6787
|
[Install]
|
|
6427
6788
|
WantedBy=default.target
|
|
6428
6789
|
`;
|
|
6429
|
-
await
|
|
6790
|
+
await writeFile9(unitPath, unit, "utf-8");
|
|
6430
6791
|
execSync("systemctl --user daemon-reload");
|
|
6431
6792
|
execSync("systemctl --user enable clank-gateway");
|
|
6432
6793
|
execSync("systemctl --user start clank-gateway");
|
|
@@ -6437,7 +6798,7 @@ async function uninstallSystemd() {
|
|
|
6437
6798
|
try {
|
|
6438
6799
|
execSync("systemctl --user stop clank-gateway");
|
|
6439
6800
|
execSync("systemctl --user disable clank-gateway");
|
|
6440
|
-
const unitPath =
|
|
6801
|
+
const unitPath = join16(homedir3(), ".config", "systemd", "user", "clank-gateway.service");
|
|
6441
6802
|
await unlink4(unitPath);
|
|
6442
6803
|
execSync("systemctl --user daemon-reload");
|
|
6443
6804
|
console.log(green3("Daemon uninstalled"));
|
|
@@ -6478,7 +6839,7 @@ __export(setup_exports, {
|
|
|
6478
6839
|
});
|
|
6479
6840
|
import { createInterface as createInterface2 } from "readline";
|
|
6480
6841
|
import { randomBytes } from "crypto";
|
|
6481
|
-
import { dirname as dirname4, join as
|
|
6842
|
+
import { dirname as dirname4, join as join17 } from "path";
|
|
6482
6843
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6483
6844
|
function ask(rl, question) {
|
|
6484
6845
|
return new Promise((resolve4) => rl.question(question, resolve4));
|
|
@@ -6581,8 +6942,8 @@ async function runSetup(opts) {
|
|
|
6581
6942
|
console.log("");
|
|
6582
6943
|
console.log(dim4(" Creating workspace..."));
|
|
6583
6944
|
const { ensureWorkspaceFiles: ensureWorkspaceFiles2 } = await Promise.resolve().then(() => (init_system_prompt(), system_prompt_exports));
|
|
6584
|
-
const templateDir =
|
|
6585
|
-
const wsDir =
|
|
6945
|
+
const templateDir = join17(__dirname2, "..", "workspace", "templates");
|
|
6946
|
+
const wsDir = join17(getConfigDir(), "workspace");
|
|
6586
6947
|
try {
|
|
6587
6948
|
await ensureWorkspaceFiles2(wsDir, templateDir);
|
|
6588
6949
|
} catch {
|
|
@@ -6743,9 +7104,9 @@ var fix_exports = {};
|
|
|
6743
7104
|
__export(fix_exports, {
|
|
6744
7105
|
runFix: () => runFix
|
|
6745
7106
|
});
|
|
6746
|
-
import { existsSync as
|
|
7107
|
+
import { existsSync as existsSync10 } from "fs";
|
|
6747
7108
|
import { readdir as readdir7 } from "fs/promises";
|
|
6748
|
-
import { join as
|
|
7109
|
+
import { join as join18 } from "path";
|
|
6749
7110
|
async function runFix(opts) {
|
|
6750
7111
|
console.log("");
|
|
6751
7112
|
console.log(" Clank Diagnostics");
|
|
@@ -6775,7 +7136,7 @@ async function runFix(opts) {
|
|
|
6775
7136
|
}
|
|
6776
7137
|
async function checkConfig() {
|
|
6777
7138
|
const configPath = getConfigPath();
|
|
6778
|
-
if (!
|
|
7139
|
+
if (!existsSync10(configPath)) {
|
|
6779
7140
|
return {
|
|
6780
7141
|
name: "Config",
|
|
6781
7142
|
status: "warn",
|
|
@@ -6843,8 +7204,8 @@ async function checkModels() {
|
|
|
6843
7204
|
return { name: "Model (primary)", status: "ok", message: modelId };
|
|
6844
7205
|
}
|
|
6845
7206
|
async function checkSessions() {
|
|
6846
|
-
const sessDir =
|
|
6847
|
-
if (!
|
|
7207
|
+
const sessDir = join18(getConfigDir(), "conversations");
|
|
7208
|
+
if (!existsSync10(sessDir)) {
|
|
6848
7209
|
return { name: "Sessions", status: "ok", message: "no sessions yet" };
|
|
6849
7210
|
}
|
|
6850
7211
|
try {
|
|
@@ -6856,8 +7217,8 @@ async function checkSessions() {
|
|
|
6856
7217
|
}
|
|
6857
7218
|
}
|
|
6858
7219
|
async function checkWorkspace() {
|
|
6859
|
-
const wsDir =
|
|
6860
|
-
if (!
|
|
7220
|
+
const wsDir = join18(getConfigDir(), "workspace");
|
|
7221
|
+
if (!existsSync10(wsDir)) {
|
|
6861
7222
|
return {
|
|
6862
7223
|
name: "Workspace",
|
|
6863
7224
|
status: "warn",
|
|
@@ -7150,7 +7511,7 @@ async function runTui(opts) {
|
|
|
7150
7511
|
ws.on("open", () => {
|
|
7151
7512
|
ws.send(JSON.stringify({
|
|
7152
7513
|
type: "connect",
|
|
7153
|
-
params: { auth: { token }, mode: "tui", version: "1.
|
|
7514
|
+
params: { auth: { token }, mode: "tui", version: "1.4.0" }
|
|
7154
7515
|
}));
|
|
7155
7516
|
});
|
|
7156
7517
|
ws.on("message", (data) => {
|
|
@@ -7499,7 +7860,7 @@ __export(uninstall_exports, {
|
|
|
7499
7860
|
});
|
|
7500
7861
|
import { createInterface as createInterface4 } from "readline";
|
|
7501
7862
|
import { rm } from "fs/promises";
|
|
7502
|
-
import { existsSync as
|
|
7863
|
+
import { existsSync as existsSync11 } from "fs";
|
|
7503
7864
|
async function runUninstall(opts) {
|
|
7504
7865
|
const configDir = getConfigDir();
|
|
7505
7866
|
console.log("");
|
|
@@ -7538,7 +7899,7 @@ async function runUninstall(opts) {
|
|
|
7538
7899
|
} catch {
|
|
7539
7900
|
}
|
|
7540
7901
|
console.log(dim10(" Deleting data..."));
|
|
7541
|
-
if (
|
|
7902
|
+
if (existsSync11(configDir)) {
|
|
7542
7903
|
await rm(configDir, { recursive: true, force: true });
|
|
7543
7904
|
console.log(green10(` Removed ${configDir}`));
|
|
7544
7905
|
} else {
|
|
@@ -7576,12 +7937,12 @@ init_esm_shims();
|
|
|
7576
7937
|
import { Command } from "commander";
|
|
7577
7938
|
import { readFileSync } from "fs";
|
|
7578
7939
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7579
|
-
import { dirname as dirname5, join as
|
|
7940
|
+
import { dirname as dirname5, join as join19 } from "path";
|
|
7580
7941
|
var __filename3 = fileURLToPath5(import.meta.url);
|
|
7581
7942
|
var __dirname3 = dirname5(__filename3);
|
|
7582
|
-
var version = "1.
|
|
7943
|
+
var version = "1.4.0";
|
|
7583
7944
|
try {
|
|
7584
|
-
const pkg = JSON.parse(readFileSync(
|
|
7945
|
+
const pkg = JSON.parse(readFileSync(join19(__dirname3, "..", "package.json"), "utf-8"));
|
|
7585
7946
|
version = pkg.version;
|
|
7586
7947
|
} catch {
|
|
7587
7948
|
}
|
|
@@ -7690,10 +8051,10 @@ pipeline.command("status <id>").description("Check pipeline execution status").a
|
|
|
7690
8051
|
});
|
|
7691
8052
|
var cron = program.command("cron").description("Manage scheduled jobs");
|
|
7692
8053
|
cron.command("list").description("List cron jobs").action(async () => {
|
|
7693
|
-
const { join:
|
|
8054
|
+
const { join: join20 } = await import("path");
|
|
7694
8055
|
const { getConfigDir: getConfigDir3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
7695
8056
|
const { CronScheduler: CronScheduler2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
7696
|
-
const scheduler = new CronScheduler2(
|
|
8057
|
+
const scheduler = new CronScheduler2(join20(getConfigDir3(), "cron"));
|
|
7697
8058
|
await scheduler.init();
|
|
7698
8059
|
const jobs = scheduler.listJobs();
|
|
7699
8060
|
if (jobs.length === 0) {
|
|
@@ -7705,10 +8066,10 @@ cron.command("list").description("List cron jobs").action(async () => {
|
|
|
7705
8066
|
}
|
|
7706
8067
|
});
|
|
7707
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) => {
|
|
7708
|
-
const { join:
|
|
8069
|
+
const { join: join20 } = await import("path");
|
|
7709
8070
|
const { getConfigDir: getConfigDir3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
7710
8071
|
const { CronScheduler: CronScheduler2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
7711
|
-
const scheduler = new CronScheduler2(
|
|
8072
|
+
const scheduler = new CronScheduler2(join20(getConfigDir3(), "cron"));
|
|
7712
8073
|
await scheduler.init();
|
|
7713
8074
|
const job = await scheduler.addJob({
|
|
7714
8075
|
name: opts.name || "CLI Job",
|
|
@@ -7719,10 +8080,10 @@ cron.command("add").description("Add a cron job").requiredOption("--schedule <ex
|
|
|
7719
8080
|
console.log(` Job created: ${job.id.slice(0, 8)} \u2014 "${job.name}" every ${job.schedule}`);
|
|
7720
8081
|
});
|
|
7721
8082
|
cron.command("remove <id>").description("Remove a cron job").action(async (id) => {
|
|
7722
|
-
const { join:
|
|
8083
|
+
const { join: join20 } = await import("path");
|
|
7723
8084
|
const { getConfigDir: getConfigDir3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
7724
8085
|
const { CronScheduler: CronScheduler2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
7725
|
-
const scheduler = new CronScheduler2(
|
|
8086
|
+
const scheduler = new CronScheduler2(join20(getConfigDir3(), "cron"));
|
|
7726
8087
|
await scheduler.init();
|
|
7727
8088
|
const removed = await scheduler.removeJob(id);
|
|
7728
8089
|
console.log(removed ? ` Job ${id.slice(0, 8)} removed` : ` Job not found`);
|