@tractorscorch/clank 1.7.2 → 1.7.3
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 +13 -0
- package/README.md +2 -2
- package/dist/index.js +82 -15
- package/dist/index.js.map +1 -1
- package/dist/workspace/templates/SOUL.md +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [1.7.3] — 2026-03-25
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Smart memory for local models** — local models no longer load the full MEMORY.md into the system prompt (wastes context tokens). Instead, memories are injected via TF-IDF relevance matching with a tighter 800-char budget, keeping only what's relevant to the current conversation
|
|
13
|
+
- **System file protection rule** — agents are now instructed not to modify, delete, or overwrite files outside the workspace or current working directory unless the user explicitly names the file. Prevents local models from touching system files, OS directories, or config dotfiles unprompted
|
|
14
|
+
- **Proactive auto-compaction** — context is now checked after every tool result (not just at the start of each loop iteration). If a large tool result pushes context past the threshold, tier-1 compaction fires immediately with zero latency. A pre-send safety check at 90% utilization catches anything that slips through
|
|
15
|
+
- **Context overflow recovery** — if a provider returns a context-length error, the engine now auto-compacts aggressively and retries once instead of crashing. If context is still critically full (>95%), warns the user to use `/compact` or `/new`
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **TF-IDF memory relevance** — `buildMemoryBlock()` was called with an empty query string, making relevance scoring useless. Now passes session context for proper matching
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
9
22
|
## [1.7.2] — 2026-03-24
|
|
10
23
|
|
|
11
24
|
### Added
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="https://github.com/ItsTrag1c/Clank/releases/latest"><img src="https://img.shields.io/badge/version-1.7.
|
|
12
|
+
<a href="https://github.com/ItsTrag1c/Clank/releases/latest"><img src="https://img.shields.io/badge/version-1.7.3-blue.svg" alt="Version" /></a>
|
|
13
13
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License" /></a>
|
|
14
14
|
<a href="https://www.npmjs.com/package/@tractorscorch/clank"><img src="https://img.shields.io/npm/v/@tractorscorch/clank.svg" alt="npm" /></a>
|
|
15
15
|
<a href="https://github.com/ItsTrag1c/Clank/stargazers"><img src="https://img.shields.io/github/stars/ItsTrag1c/Clank.svg" alt="Stars" /></a>
|
|
@@ -92,7 +92,7 @@ That's it. Setup auto-detects your local models, configures the gateway, and get
|
|
|
92
92
|
| Platform | Download |
|
|
93
93
|
|----------|----------|
|
|
94
94
|
| **npm** (all platforms) | `npm install -g @tractorscorch/clank` |
|
|
95
|
-
| **macOS** (Apple Silicon) | [Clank_1.7.
|
|
95
|
+
| **macOS** (Apple Silicon) | [Clank_1.7.3_macos](https://github.com/ItsTrag1c/Clank/releases/latest/download/Clank_1.7.3_macos) |
|
|
96
96
|
|
|
97
97
|
## Security Notice
|
|
98
98
|
|
package/dist/index.js
CHANGED
|
@@ -320,6 +320,30 @@ ${summary.trim()}`,
|
|
|
320
320
|
this.compactTier1Aggressive();
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
|
+
/** Check if context is critically full (>95% utilization) */
|
|
324
|
+
isOverflowing() {
|
|
325
|
+
return this.utilizationPercent() >= 95;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Tier-1 only compaction — synchronous, no LLM call.
|
|
329
|
+
* Safe to call mid-turn without adding latency.
|
|
330
|
+
*/
|
|
331
|
+
compactTier1Only() {
|
|
332
|
+
const before = this.messages.length;
|
|
333
|
+
const tokensBefore = this.estimateTokens();
|
|
334
|
+
this.compactTier1();
|
|
335
|
+
if (this.utilizationPercent() >= 70) {
|
|
336
|
+
this.compactTier1Aggressive();
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
ok: true,
|
|
340
|
+
tier: 1,
|
|
341
|
+
messagesBefore: before,
|
|
342
|
+
messagesAfter: this.messages.length,
|
|
343
|
+
estimatedTokensBefore: tokensBefore,
|
|
344
|
+
estimatedTokensAfter: this.estimateTokens()
|
|
345
|
+
};
|
|
346
|
+
}
|
|
323
347
|
/** Clear all messages */
|
|
324
348
|
clear() {
|
|
325
349
|
this.messages = [];
|
|
@@ -988,6 +1012,9 @@ ${results}`
|
|
|
988
1012
|
outputTokens = 0;
|
|
989
1013
|
}
|
|
990
1014
|
try {
|
|
1015
|
+
if (this.contextEngine.utilizationPercent() > 90) {
|
|
1016
|
+
this.contextEngine.compactTier1Only();
|
|
1017
|
+
}
|
|
991
1018
|
const streamIterator = activeProvider.stream(
|
|
992
1019
|
this.contextEngine.getMessages(),
|
|
993
1020
|
this.systemPrompt,
|
|
@@ -1024,6 +1051,21 @@ ${results}`
|
|
|
1024
1051
|
} catch (streamErr) {
|
|
1025
1052
|
const errMsg = streamErr instanceof Error ? streamErr.message : "unknown";
|
|
1026
1053
|
const isTimeout = streamErr instanceof Error && (streamErr.name === "TimeoutError" || streamErr.name === "AbortError" || errMsg.includes("timed out"));
|
|
1054
|
+
const isContextError = /context.*(length|limit|exceeded)|too many tokens|maximum.*tokens|token limit/i.test(errMsg);
|
|
1055
|
+
if (attempt === 0 && isContextError && !signal.aborted) {
|
|
1056
|
+
this.emit("error", {
|
|
1057
|
+
message: "Context limit hit \u2014 compacting and retrying...",
|
|
1058
|
+
recoverable: true
|
|
1059
|
+
});
|
|
1060
|
+
this.contextEngine.compactTier1Only();
|
|
1061
|
+
if (this.contextEngine.isOverflowing()) {
|
|
1062
|
+
this.emit("error", {
|
|
1063
|
+
message: "Context is nearly full \u2014 use /compact or /new to free space",
|
|
1064
|
+
recoverable: true
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1027
1069
|
const isRetryable = !isTimeout && !signal.aborted && (errMsg.includes("connection dropped") || errMsg.includes("stopped responding") || errMsg.includes("empty response") || errMsg.includes("fetch failed") || errMsg.includes("ECONNREFUSED") || errMsg.includes("ECONNRESET"));
|
|
1028
1070
|
if (attempt === 0 && (isRetryable || !signal.aborted && !isTimeout)) {
|
|
1029
1071
|
this.emit("error", {
|
|
@@ -1129,6 +1171,15 @@ ${results}`
|
|
|
1129
1171
|
this.contextEngine.ingest(result);
|
|
1130
1172
|
this.emit("tool-result", { id: tc.id, name: tc.name, success: false, summary: errMsg });
|
|
1131
1173
|
}
|
|
1174
|
+
if (this.contextEngine.needsCompaction()) {
|
|
1175
|
+
this.contextEngine.compactTier1Only();
|
|
1176
|
+
if (this.contextEngine.isOverflowing()) {
|
|
1177
|
+
this.emit("error", {
|
|
1178
|
+
message: "Context is nearly full \u2014 use /compact or /new to free space",
|
|
1179
|
+
recoverable: true
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1132
1183
|
}
|
|
1133
1184
|
}
|
|
1134
1185
|
if (iterationCount >= MAX_ITERATIONS) {
|
|
@@ -1265,9 +1316,11 @@ import { platform, hostname } from "os";
|
|
|
1265
1316
|
async function buildSystemPrompt(opts) {
|
|
1266
1317
|
const parts = [];
|
|
1267
1318
|
const compact = opts.compact ?? false;
|
|
1319
|
+
const isLocal = opts.isLocal ?? false;
|
|
1268
1320
|
const isSubAgent = (opts.spawnDepth ?? 0) > 0;
|
|
1269
1321
|
if (!compact) {
|
|
1270
|
-
const
|
|
1322
|
+
const files = isLocal ? WORKSPACE_FILES_LOCAL : WORKSPACE_FILES;
|
|
1323
|
+
const workspaceContent = await loadWorkspaceFiles(opts.workspaceDir, files);
|
|
1271
1324
|
if (workspaceContent) {
|
|
1272
1325
|
parts.push(workspaceContent);
|
|
1273
1326
|
parts.push("---");
|
|
@@ -1297,7 +1350,8 @@ async function buildSystemPrompt(opts) {
|
|
|
1297
1350
|
"You are an AI agent running LOCALLY on the user's machine.",
|
|
1298
1351
|
"You have tools: read_file, write_file, edit_file, list_directory, bash, search_files, glob_files, git, web_search, web_fetch, and self-config tools.",
|
|
1299
1352
|
"ALWAYS use your tools. NEVER say you cannot access files, run commands, or perform actions. You CAN \u2014 use your tools.",
|
|
1300
|
-
"NEVER apologize and refuse. If asked to do something, DO IT with your tools or explain what tool you need."
|
|
1353
|
+
"NEVER apologize and refuse. If asked to do something, DO IT with your tools or explain what tool you need.",
|
|
1354
|
+
"Do NOT modify files outside your workspace or the user's current directory unless the user explicitly names the file."
|
|
1301
1355
|
].join(" "));
|
|
1302
1356
|
} else {
|
|
1303
1357
|
parts.push("## CRITICAL: You Are a Local Agent With Tools");
|
|
@@ -1313,13 +1367,18 @@ async function buildSystemPrompt(opts) {
|
|
|
1313
1367
|
parts.push("3. NEVER apologize and refuse to act. If a task requires a tool, use it. If you lack a specific tool, say which tool you need \u2014 do not give a generic refusal.");
|
|
1314
1368
|
parts.push("4. Read files before editing them. Use tools proactively without being asked twice.");
|
|
1315
1369
|
parts.push("5. You can configure yourself \u2014 use the config, channel, agent, and model management tools to modify your own setup.");
|
|
1370
|
+
parts.push("6. Do NOT modify, delete, or overwrite files outside your workspace directory or the user's current working directory unless the user explicitly names the file. System files, OS directories, and config dotfiles are off-limits by default.");
|
|
1316
1371
|
}
|
|
1317
1372
|
if (opts.thinking === "off") {
|
|
1318
1373
|
parts.push("");
|
|
1319
1374
|
parts.push("Do NOT use extended thinking or reasoning blocks. Respond directly and concisely.");
|
|
1320
1375
|
}
|
|
1321
1376
|
parts.push("");
|
|
1322
|
-
|
|
1377
|
+
if (isLocal) {
|
|
1378
|
+
parts.push("Your memories are managed automatically. Use memory tools to save or recall important information. Do not rely on conversation history for long-term facts.");
|
|
1379
|
+
} else {
|
|
1380
|
+
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.");
|
|
1381
|
+
}
|
|
1323
1382
|
parts.push("");
|
|
1324
1383
|
const projectMemory = await loadProjectMemory(opts.identity.workspace);
|
|
1325
1384
|
if (projectMemory) {
|
|
@@ -1329,9 +1388,9 @@ async function buildSystemPrompt(opts) {
|
|
|
1329
1388
|
}
|
|
1330
1389
|
return parts.join("\n");
|
|
1331
1390
|
}
|
|
1332
|
-
async function loadWorkspaceFiles(workspaceDir) {
|
|
1391
|
+
async function loadWorkspaceFiles(workspaceDir, files = WORKSPACE_FILES) {
|
|
1333
1392
|
const sections = [];
|
|
1334
|
-
for (const filename of
|
|
1393
|
+
for (const filename of files) {
|
|
1335
1394
|
const filePath = join2(workspaceDir, filename);
|
|
1336
1395
|
if (existsSync2(filePath)) {
|
|
1337
1396
|
try {
|
|
@@ -1383,7 +1442,7 @@ async function ensureWorkspaceFiles(workspaceDir, templateDir) {
|
|
|
1383
1442
|
}
|
|
1384
1443
|
}
|
|
1385
1444
|
}
|
|
1386
|
-
var WORKSPACE_FILES, SUB_AGENT_FILE;
|
|
1445
|
+
var WORKSPACE_FILES, WORKSPACE_FILES_LOCAL, SUB_AGENT_FILE;
|
|
1387
1446
|
var init_system_prompt = __esm({
|
|
1388
1447
|
"src/engine/system-prompt.ts"() {
|
|
1389
1448
|
"use strict";
|
|
@@ -1396,6 +1455,13 @@ var init_system_prompt = __esm({
|
|
|
1396
1455
|
"TOOLS.md",
|
|
1397
1456
|
"MEMORY.md"
|
|
1398
1457
|
];
|
|
1458
|
+
WORKSPACE_FILES_LOCAL = [
|
|
1459
|
+
"SOUL.md",
|
|
1460
|
+
"USER.md",
|
|
1461
|
+
"IDENTITY.md",
|
|
1462
|
+
"AGENTS.md",
|
|
1463
|
+
"TOOLS.md"
|
|
1464
|
+
];
|
|
1399
1465
|
SUB_AGENT_FILE = "RUNNER.md";
|
|
1400
1466
|
}
|
|
1401
1467
|
});
|
|
@@ -5150,7 +5216,7 @@ async function runChat(opts) {
|
|
|
5150
5216
|
console.log(cyan(" / __|| | __ _ _ _ | |__"));
|
|
5151
5217
|
console.log(cyan(" | (__ | |/ _` || ' \\| / /"));
|
|
5152
5218
|
console.log(cyan(" \\___||_|\\__,_||_||_|_\\_\\"));
|
|
5153
|
-
console.log(dim(` v1.7.
|
|
5219
|
+
console.log(dim(` v1.7.3 | ${resolved.modelId} | ${identity.toolTier} tier`));
|
|
5154
5220
|
console.log(dim(" Type your message. Press Ctrl+C to exit.\n"));
|
|
5155
5221
|
const rl = createInterface({
|
|
5156
5222
|
input: process.stdin,
|
|
@@ -6817,7 +6883,7 @@ _Kill with /kill <id> or /killall_`;
|
|
|
6817
6883
|
return !current ? "\u{1F4AD} Thinking display *on* \u2014 you'll see the model's reasoning above responses." : "\u{1F4AD} Thinking display *off* \u2014 only the final response will be shown.";
|
|
6818
6884
|
}
|
|
6819
6885
|
case "version": {
|
|
6820
|
-
return `\u{1F527} *Clank* v1.7.
|
|
6886
|
+
return `\u{1F527} *Clank* v1.7.3`;
|
|
6821
6887
|
}
|
|
6822
6888
|
default:
|
|
6823
6889
|
return null;
|
|
@@ -7407,7 +7473,7 @@ var init_server = __esm({
|
|
|
7407
7473
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7408
7474
|
res.end(JSON.stringify({
|
|
7409
7475
|
status: "ok",
|
|
7410
|
-
version: "1.7.
|
|
7476
|
+
version: "1.7.3",
|
|
7411
7477
|
uptime: process.uptime(),
|
|
7412
7478
|
clients: this.clients.size,
|
|
7413
7479
|
agents: this.engines.size
|
|
@@ -7519,7 +7585,7 @@ var init_server = __esm({
|
|
|
7519
7585
|
const hello = {
|
|
7520
7586
|
type: "hello",
|
|
7521
7587
|
protocol: PROTOCOL_VERSION,
|
|
7522
|
-
version: "1.7.
|
|
7588
|
+
version: "1.7.3",
|
|
7523
7589
|
agents: this.config.agents.list.map((a) => ({
|
|
7524
7590
|
id: a.id,
|
|
7525
7591
|
name: a.name || a.id,
|
|
@@ -7829,10 +7895,11 @@ var init_server = __esm({
|
|
|
7829
7895
|
channel,
|
|
7830
7896
|
compact,
|
|
7831
7897
|
thinking,
|
|
7832
|
-
spawnDepth: currentDepth
|
|
7898
|
+
spawnDepth: currentDepth,
|
|
7899
|
+
isLocal: resolved.isLocal
|
|
7833
7900
|
});
|
|
7834
|
-
const memoryBudget = resolved.isLocal ?
|
|
7835
|
-
const memoryBlock = await this.memoryManager.buildMemoryBlock("", identity.workspace, memoryBudget);
|
|
7901
|
+
const memoryBudget = resolved.isLocal ? 800 : 4e3;
|
|
7902
|
+
const memoryBlock = await this.memoryManager.buildMemoryBlock("session", identity.workspace, memoryBudget);
|
|
7836
7903
|
const fullPrompt = memoryBlock ? systemPrompt + "\n\n---\n\n" + memoryBlock : systemPrompt;
|
|
7837
7904
|
const maxSpawnDepth = this.config.agents.defaults.subagents?.maxSpawnDepth ?? 1;
|
|
7838
7905
|
const maxConcurrent = this.config.agents.defaults.subagents?.maxConcurrent ?? 8;
|
|
@@ -9149,7 +9216,7 @@ async function runTui(opts) {
|
|
|
9149
9216
|
ws.on("open", () => {
|
|
9150
9217
|
ws.send(JSON.stringify({
|
|
9151
9218
|
type: "connect",
|
|
9152
|
-
params: { auth: { token }, mode: "tui", version: "1.7.
|
|
9219
|
+
params: { auth: { token }, mode: "tui", version: "1.7.3" }
|
|
9153
9220
|
}));
|
|
9154
9221
|
});
|
|
9155
9222
|
ws.on("message", (data) => {
|
|
@@ -9592,7 +9659,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
|
|
|
9592
9659
|
import { dirname as dirname5, join as join20 } from "path";
|
|
9593
9660
|
var __filename3 = fileURLToPath5(import.meta.url);
|
|
9594
9661
|
var __dirname3 = dirname5(__filename3);
|
|
9595
|
-
var version = "1.7.
|
|
9662
|
+
var version = "1.7.3";
|
|
9596
9663
|
try {
|
|
9597
9664
|
const pkg = JSON.parse(readFileSync(join20(__dirname3, "..", "package.json"), "utf-8"));
|
|
9598
9665
|
version = pkg.version;
|