@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 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.4.8-blue.svg" alt="Version" /></a>
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.4.8_macos](https://github.com/ItsTrag1c/Clank/releases/latest/download/Clank_1.4.8_macos) |
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 { done, value } = await reader.read();
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(messages, systemPrompt),
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 timeoutSignal = AbortSignal.timeout(9e4);
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 { done, value } = await reader.read();
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
- await ctx.api.sendMessage(chatId, `Error: ${errMsg.slice(0, 200)}`);
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
- return await engine.sendMessage(text);
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.4.8",
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.4.8",
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.4.8" }
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.4.8";
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;