@tractorscorch/clank 1.4.8 → 1.5.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 +16 -0
- package/README.md +2 -2
- package/dist/index.js +45 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [1.5.0] — 2026-03-23
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **Model hangs forever on large prompts/tool calls** — the connection-level timeout (120s) only covers the initial HTTP request; once streaming starts, `reader.read()` waits indefinitely for the next chunk. Added per-chunk 60s timeout via `Promise.race` — if the model stops sending data mid-stream (OOM, stuck processing), Clank detects it and reports an error instead of hanging forever
|
|
13
|
+
- **Debug logging for Telegram** — added request/response lifecycle logging to diagnose message handling issues
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [1.4.9] — 2026-03-22
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **llama.cpp/local models crashing on tool calls** — OpenAI provider (used for llama.cpp, LM Studio, vLLM) was missing the orphaned tool result filter that Ollama had; orphaned tool results after compaction caused 400 API errors and permanent session corruption
|
|
21
|
+
- **Local model timeout too short** — OpenAI provider used 90s cloud timeout for local models; now uses 120s for local (matching Ollama) since large quantized models need time to process
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
9
25
|
## [1.4.8] — 2026-03-22
|
|
10
26
|
|
|
11
27
|
### Fixed
|
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.
|
|
12
|
+
<a href="https://github.com/ItsTrag1c/Clank/releases/latest"><img src="https://img.shields.io/badge/version-1.5.0-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>
|
|
@@ -75,7 +75,7 @@ That's it. Setup auto-detects your local models, configures the gateway, and get
|
|
|
75
75
|
| Platform | Download |
|
|
76
76
|
|----------|----------|
|
|
77
77
|
| **npm** (all platforms) | `npm install -g @tractorscorch/clank` |
|
|
78
|
-
| **macOS** (Apple Silicon) | [Clank_1.
|
|
78
|
+
| **macOS** (Apple Silicon) | [Clank_1.5.0_macos](https://github.com/ItsTrag1c/Clank/releases/latest/download/Clank_1.5.0_macos) |
|
|
79
79
|
|
|
80
80
|
## Features
|
|
81
81
|
|
package/dist/index.js
CHANGED
|
@@ -580,9 +580,14 @@ var init_ollama = __esm({
|
|
|
580
580
|
const decoder = new TextDecoder();
|
|
581
581
|
let buffer = "";
|
|
582
582
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
583
|
+
const CHUNK_TIMEOUT = 6e4;
|
|
583
584
|
try {
|
|
584
585
|
while (true) {
|
|
585
|
-
const
|
|
586
|
+
const readPromise = reader.read();
|
|
587
|
+
const timeoutPromise = new Promise(
|
|
588
|
+
(_, reject) => setTimeout(() => reject(new Error("Model stopped responding (no data for 60s)")), CHUNK_TIMEOUT)
|
|
589
|
+
);
|
|
590
|
+
const { done, value } = await Promise.race([readPromise, timeoutPromise]);
|
|
586
591
|
if (done) break;
|
|
587
592
|
buffer += decoder.decode(value, { stream: true });
|
|
588
593
|
const lines = buffer.split("\n");
|
|
@@ -2969,9 +2974,21 @@ var init_openai = __esm({
|
|
|
2969
2974
|
return result;
|
|
2970
2975
|
}
|
|
2971
2976
|
async *stream(messages, systemPrompt, tools, signal) {
|
|
2977
|
+
const toolCallIds = /* @__PURE__ */ new Set();
|
|
2978
|
+
for (const msg of messages) {
|
|
2979
|
+
if (msg.role === "assistant" && msg.tool_calls) {
|
|
2980
|
+
for (const tc of msg.tool_calls) toolCallIds.add(tc.id);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
const sanitized = messages.filter((msg) => {
|
|
2984
|
+
if (msg.role === "tool" && msg.tool_call_id && !toolCallIds.has(msg.tool_call_id)) {
|
|
2985
|
+
return false;
|
|
2986
|
+
}
|
|
2987
|
+
return true;
|
|
2988
|
+
});
|
|
2972
2989
|
const body = {
|
|
2973
2990
|
model: this.model,
|
|
2974
|
-
messages: this.prepareMessages(
|
|
2991
|
+
messages: this.prepareMessages(sanitized, systemPrompt),
|
|
2975
2992
|
stream: true,
|
|
2976
2993
|
stream_options: { include_usage: true }
|
|
2977
2994
|
};
|
|
@@ -2987,7 +3004,8 @@ var init_openai = __esm({
|
|
|
2987
3004
|
if (this.apiKey) {
|
|
2988
3005
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
2989
3006
|
}
|
|
2990
|
-
const
|
|
3007
|
+
const timeoutMs = this.isLocal ? 12e4 : 9e4;
|
|
3008
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
2991
3009
|
const effectiveSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
2992
3010
|
const res = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
2993
3011
|
method: "POST",
|
|
@@ -3004,9 +3022,14 @@ var init_openai = __esm({
|
|
|
3004
3022
|
const decoder = new TextDecoder();
|
|
3005
3023
|
let buffer = "";
|
|
3006
3024
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
3025
|
+
const CHUNK_TIMEOUT = 6e4;
|
|
3007
3026
|
try {
|
|
3008
3027
|
while (true) {
|
|
3009
|
-
const
|
|
3028
|
+
const readPromise = reader.read();
|
|
3029
|
+
const timeoutPromise = new Promise(
|
|
3030
|
+
(_, reject) => setTimeout(() => reject(new Error("Model stopped responding (no data for 60s)")), CHUNK_TIMEOUT)
|
|
3031
|
+
);
|
|
3032
|
+
const { done, value } = await Promise.race([readPromise, timeoutPromise]);
|
|
3010
3033
|
if (done) break;
|
|
3011
3034
|
buffer += decoder.decode(value, { stream: true });
|
|
3012
3035
|
const lines = buffer.split("\n");
|
|
@@ -5283,6 +5306,7 @@ var init_telegram = __esm({
|
|
|
5283
5306
|
const processMessage = async () => {
|
|
5284
5307
|
if (!this.gateway) return;
|
|
5285
5308
|
try {
|
|
5309
|
+
console.log(` Telegram: processing message from ${userId} in ${chatId}`);
|
|
5286
5310
|
await ctx.api.sendChatAction(chatId, "typing");
|
|
5287
5311
|
let streamMsgId = null;
|
|
5288
5312
|
let sendingInitial = false;
|
|
@@ -5352,13 +5376,17 @@ var init_telegram = __esm({
|
|
|
5352
5376
|
await ctx.api.sendMessage(chatId, chunk);
|
|
5353
5377
|
}
|
|
5354
5378
|
}
|
|
5379
|
+
console.log(` Telegram: response complete (${response?.length || 0} chars)`);
|
|
5355
5380
|
} catch (err) {
|
|
5356
5381
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5357
|
-
|
|
5382
|
+
console.error(` Telegram: message handler error \u2014 ${errMsg}`);
|
|
5383
|
+
await ctx.api.sendMessage(chatId, `Error: ${errMsg.slice(0, 200)}`).catch(() => {
|
|
5384
|
+
});
|
|
5358
5385
|
}
|
|
5359
5386
|
};
|
|
5360
5387
|
const prev = chatLocks.get(chatId) || Promise.resolve();
|
|
5361
|
-
const next = prev.then(processMessage).catch(() => {
|
|
5388
|
+
const next = prev.then(processMessage).catch((err) => {
|
|
5389
|
+
console.error(` Telegram: queue error \u2014 ${err instanceof Error ? err.message : err}`);
|
|
5362
5390
|
});
|
|
5363
5391
|
chatLocks.set(chatId, next);
|
|
5364
5392
|
});
|
|
@@ -6091,7 +6119,13 @@ var init_server = __esm({
|
|
|
6091
6119
|
listeners.push(["error", fn]);
|
|
6092
6120
|
}
|
|
6093
6121
|
try {
|
|
6094
|
-
|
|
6122
|
+
console.log(` Streaming: sending message to engine (session: ${sessionKey})`);
|
|
6123
|
+
const result = await engine.sendMessage(text);
|
|
6124
|
+
console.log(` Streaming: engine returned (${result?.length || 0} chars)`);
|
|
6125
|
+
return result;
|
|
6126
|
+
} catch (err) {
|
|
6127
|
+
console.error(` Streaming: engine error \u2014 ${err instanceof Error ? err.message : err}`);
|
|
6128
|
+
throw err;
|
|
6095
6129
|
} finally {
|
|
6096
6130
|
for (const [event, fn] of listeners) {
|
|
6097
6131
|
engine.removeListener(event, fn);
|
|
@@ -6131,7 +6165,7 @@ var init_server = __esm({
|
|
|
6131
6165
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6132
6166
|
res.end(JSON.stringify({
|
|
6133
6167
|
status: "ok",
|
|
6134
|
-
version: "1.
|
|
6168
|
+
version: "1.5.0",
|
|
6135
6169
|
uptime: process.uptime(),
|
|
6136
6170
|
clients: this.clients.size,
|
|
6137
6171
|
agents: this.engines.size
|
|
@@ -6243,7 +6277,7 @@ var init_server = __esm({
|
|
|
6243
6277
|
const hello = {
|
|
6244
6278
|
type: "hello",
|
|
6245
6279
|
protocol: PROTOCOL_VERSION,
|
|
6246
|
-
version: "1.
|
|
6280
|
+
version: "1.5.0",
|
|
6247
6281
|
agents: this.config.agents.list.map((a) => ({
|
|
6248
6282
|
id: a.id,
|
|
6249
6283
|
name: a.name || a.id,
|
|
@@ -7637,7 +7671,7 @@ async function runTui(opts) {
|
|
|
7637
7671
|
ws.on("open", () => {
|
|
7638
7672
|
ws.send(JSON.stringify({
|
|
7639
7673
|
type: "connect",
|
|
7640
|
-
params: { auth: { token }, mode: "tui", version: "1.
|
|
7674
|
+
params: { auth: { token }, mode: "tui", version: "1.5.0" }
|
|
7641
7675
|
}));
|
|
7642
7676
|
});
|
|
7643
7677
|
ws.on("message", (data) => {
|
|
@@ -8066,7 +8100,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
|
|
|
8066
8100
|
import { dirname as dirname5, join as join19 } from "path";
|
|
8067
8101
|
var __filename3 = fileURLToPath5(import.meta.url);
|
|
8068
8102
|
var __dirname3 = dirname5(__filename3);
|
|
8069
|
-
var version = "1.
|
|
8103
|
+
var version = "1.5.0";
|
|
8070
8104
|
try {
|
|
8071
8105
|
const pkg = JSON.parse(readFileSync(join19(__dirname3, "..", "package.json"), "utf-8"));
|
|
8072
8106
|
version = pkg.version;
|