@tractorscorch/clank 1.4.3 → 1.4.5
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 +18 -0
- package/README.md +2 -2
- package/dist/index.js +32 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [1.4.5] — 2026-03-22
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **Gateway unresponsive after messages** — WebSocket frame handler was not awaited, causing unhandled promise rejections that silently killed the gateway process
|
|
13
|
+
- **Added `unhandledRejection` handler** — gateway now logs rejected promises instead of dying silently
|
|
14
|
+
- **Provider timeout fallback** — all providers (Ollama, Anthropic, OpenAI, Google) now have a fallback timeout (120s local, 90s cloud) if no abort signal is provided, preventing indefinite hangs when a model is unresponsive
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## [1.4.4] — 2026-03-22
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Gateway crash after 4-5 messages** — confirmation handler WebSocket listeners were never removed on timeout, accumulating orphaned handlers per message until the process crashed
|
|
22
|
+
- **Engine listener limit** — set `maxListeners` to 30 on AgentEngine (Node.js default of 10 was too low since each message cycle wires 10 event listeners)
|
|
23
|
+
- **Rate limiter memory leak** — stale session entries in the rate limiter Map were never purged; added periodic cleanup when map exceeds 100 entries
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
9
27
|
## [1.4.3] — 2026-03-22
|
|
10
28
|
|
|
11
29
|
### 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.
|
|
12
|
+
<a href="https://github.com/ItsTrag1c/Clank/releases/latest"><img src="https://img.shields.io/badge/version-1.4.5-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.
|
|
78
|
+
| **macOS** (Apple Silicon) | [Clank_1.4.5_macos](https://github.com/ItsTrag1c/Clank/releases/latest/download/Clank_1.4.5_macos) |
|
|
79
79
|
|
|
80
80
|
## Features
|
|
81
81
|
|
package/dist/index.js
CHANGED
|
@@ -513,11 +513,12 @@ var init_ollama = __esm({
|
|
|
513
513
|
if (this.maxResponseTokens) {
|
|
514
514
|
body.max_tokens = this.maxResponseTokens;
|
|
515
515
|
}
|
|
516
|
+
const effectiveSignal = signal || AbortSignal.timeout(12e4);
|
|
516
517
|
const res = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
517
518
|
method: "POST",
|
|
518
519
|
headers: { "Content-Type": "application/json" },
|
|
519
520
|
body: JSON.stringify(body),
|
|
520
|
-
signal
|
|
521
|
+
signal: effectiveSignal
|
|
521
522
|
});
|
|
522
523
|
if (!res.ok) {
|
|
523
524
|
const text = await res.text().catch(() => "Unknown error");
|
|
@@ -764,6 +765,7 @@ var init_agent = __esm({
|
|
|
764
765
|
alwaysApproved = /* @__PURE__ */ new Set();
|
|
765
766
|
constructor(opts) {
|
|
766
767
|
super();
|
|
768
|
+
this.setMaxListeners(30);
|
|
767
769
|
this.identity = opts.identity;
|
|
768
770
|
this.toolRegistry = opts.toolRegistry;
|
|
769
771
|
this.sessionStore = opts.sessionStore;
|
|
@@ -2707,6 +2709,7 @@ var init_anthropic = __esm({
|
|
|
2707
2709
|
if (tools.length > 0) {
|
|
2708
2710
|
body.tools = this.formatTools(tools);
|
|
2709
2711
|
}
|
|
2712
|
+
const effectiveSignal = signal || AbortSignal.timeout(9e4);
|
|
2710
2713
|
const res = await fetch(`${this.baseUrl}/v1/messages`, {
|
|
2711
2714
|
method: "POST",
|
|
2712
2715
|
headers: {
|
|
@@ -2715,7 +2718,7 @@ var init_anthropic = __esm({
|
|
|
2715
2718
|
"anthropic-version": "2023-06-01"
|
|
2716
2719
|
},
|
|
2717
2720
|
body: JSON.stringify(body),
|
|
2718
|
-
signal
|
|
2721
|
+
signal: effectiveSignal
|
|
2719
2722
|
});
|
|
2720
2723
|
if (!res.ok) {
|
|
2721
2724
|
const text = await res.text().catch(() => "Unknown error");
|
|
@@ -2933,11 +2936,12 @@ var init_openai = __esm({
|
|
|
2933
2936
|
if (this.apiKey) {
|
|
2934
2937
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
2935
2938
|
}
|
|
2939
|
+
const effectiveSignal = signal || AbortSignal.timeout(9e4);
|
|
2936
2940
|
const res = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
2937
2941
|
method: "POST",
|
|
2938
2942
|
headers,
|
|
2939
2943
|
body: JSON.stringify(body),
|
|
2940
|
-
signal
|
|
2944
|
+
signal: effectiveSignal
|
|
2941
2945
|
});
|
|
2942
2946
|
if (!res.ok) {
|
|
2943
2947
|
const text = await res.text().catch(() => "Unknown error");
|
|
@@ -3159,11 +3163,12 @@ var init_google = __esm({
|
|
|
3159
3163
|
body.tools = this.formatTools(tools);
|
|
3160
3164
|
}
|
|
3161
3165
|
const url = `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:streamGenerateContent?key=${this.apiKey}&alt=sse`;
|
|
3166
|
+
const effectiveSignal = signal || AbortSignal.timeout(9e4);
|
|
3162
3167
|
const res = await fetch(url, {
|
|
3163
3168
|
method: "POST",
|
|
3164
3169
|
headers: { "Content-Type": "application/json" },
|
|
3165
3170
|
body: JSON.stringify(body),
|
|
3166
|
-
signal
|
|
3171
|
+
signal: effectiveSignal
|
|
3167
3172
|
});
|
|
3168
3173
|
if (!res.ok) {
|
|
3169
3174
|
const text = await res.text().catch(() => "Unknown error");
|
|
@@ -5892,6 +5897,9 @@ var init_server = __esm({
|
|
|
5892
5897
|
}
|
|
5893
5898
|
/** Start the gateway server */
|
|
5894
5899
|
async start() {
|
|
5900
|
+
process.on("unhandledRejection", (err) => {
|
|
5901
|
+
console.error(` Unhandled rejection: ${err instanceof Error ? err.message : err}`);
|
|
5902
|
+
});
|
|
5895
5903
|
if (this.config.gateway.auth.mode === "token" && !this.config.gateway.auth.token) {
|
|
5896
5904
|
const { randomBytes: randomBytes2 } = await import("crypto");
|
|
5897
5905
|
this.config.gateway.auth.token = randomBytes2(16).toString("hex");
|
|
@@ -6056,7 +6064,7 @@ var init_server = __esm({
|
|
|
6056
6064
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6057
6065
|
res.end(JSON.stringify({
|
|
6058
6066
|
status: "ok",
|
|
6059
|
-
version: "1.4.
|
|
6067
|
+
version: "1.4.5",
|
|
6060
6068
|
uptime: process.uptime(),
|
|
6061
6069
|
clients: this.clients.size,
|
|
6062
6070
|
agents: this.engines.size
|
|
@@ -6117,7 +6125,11 @@ var init_server = __esm({
|
|
|
6117
6125
|
this.clients.set(ws, client);
|
|
6118
6126
|
ws.on("message", (data) => {
|
|
6119
6127
|
const frame = parseFrame(data.toString());
|
|
6120
|
-
if (frame)
|
|
6128
|
+
if (frame) {
|
|
6129
|
+
this.handleFrame(client, frame).catch((err) => {
|
|
6130
|
+
console.error(` Frame handler error: ${err instanceof Error ? err.message : err}`);
|
|
6131
|
+
});
|
|
6132
|
+
}
|
|
6121
6133
|
});
|
|
6122
6134
|
ws.on("close", () => {
|
|
6123
6135
|
this.clients.delete(ws);
|
|
@@ -6164,7 +6176,7 @@ var init_server = __esm({
|
|
|
6164
6176
|
const hello = {
|
|
6165
6177
|
type: "hello",
|
|
6166
6178
|
protocol: PROTOCOL_VERSION,
|
|
6167
|
-
version: "1.4.
|
|
6179
|
+
version: "1.4.5",
|
|
6168
6180
|
agents: this.config.agents.list.map((a) => ({
|
|
6169
6181
|
id: a.id,
|
|
6170
6182
|
name: a.name || a.id,
|
|
@@ -6363,6 +6375,13 @@ var init_server = __esm({
|
|
|
6363
6375
|
const recent = timestamps.filter((t) => now - t < this.RATE_LIMIT_WINDOW);
|
|
6364
6376
|
recent.push(now);
|
|
6365
6377
|
this.rateLimiter.set(sessionKey, recent);
|
|
6378
|
+
if (this.rateLimiter.size > 100) {
|
|
6379
|
+
for (const [key, ts] of this.rateLimiter) {
|
|
6380
|
+
if (ts.length === 0 || now - ts[ts.length - 1] > this.RATE_LIMIT_WINDOW * 2) {
|
|
6381
|
+
this.rateLimiter.delete(key);
|
|
6382
|
+
}
|
|
6383
|
+
}
|
|
6384
|
+
}
|
|
6366
6385
|
return recent.length > this.RATE_LIMIT_MAX;
|
|
6367
6386
|
}
|
|
6368
6387
|
/** Cancel current request for a client */
|
|
@@ -6448,7 +6467,6 @@ var init_server = __esm({
|
|
|
6448
6467
|
}
|
|
6449
6468
|
const confirmId = `confirm_${Date.now()}`;
|
|
6450
6469
|
this.sendEvent(client, "confirm-needed", { id: confirmId, actions });
|
|
6451
|
-
const timeout = setTimeout(() => resolve4(false), 3e4);
|
|
6452
6470
|
const resolveHandler = (raw) => {
|
|
6453
6471
|
const frame = parseFrame(raw.toString());
|
|
6454
6472
|
if (frame?.type === "req" && frame.method === "confirm.resolve") {
|
|
@@ -6461,6 +6479,10 @@ var init_server = __esm({
|
|
|
6461
6479
|
}
|
|
6462
6480
|
};
|
|
6463
6481
|
client.ws.on("message", resolveHandler);
|
|
6482
|
+
const timeout = setTimeout(() => {
|
|
6483
|
+
client.ws.removeListener("message", resolveHandler);
|
|
6484
|
+
resolve4(false);
|
|
6485
|
+
}, 3e4);
|
|
6464
6486
|
};
|
|
6465
6487
|
engine.on("confirm-needed", confirmListener);
|
|
6466
6488
|
listeners.push(["confirm-needed", confirmListener]);
|
|
@@ -7548,7 +7570,7 @@ async function runTui(opts) {
|
|
|
7548
7570
|
ws.on("open", () => {
|
|
7549
7571
|
ws.send(JSON.stringify({
|
|
7550
7572
|
type: "connect",
|
|
7551
|
-
params: { auth: { token }, mode: "tui", version: "1.4.
|
|
7573
|
+
params: { auth: { token }, mode: "tui", version: "1.4.5" }
|
|
7552
7574
|
}));
|
|
7553
7575
|
});
|
|
7554
7576
|
ws.on("message", (data) => {
|
|
@@ -7977,7 +7999,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
|
|
|
7977
7999
|
import { dirname as dirname5, join as join19 } from "path";
|
|
7978
8000
|
var __filename3 = fileURLToPath5(import.meta.url);
|
|
7979
8001
|
var __dirname3 = dirname5(__filename3);
|
|
7980
|
-
var version = "1.4.
|
|
8002
|
+
var version = "1.4.5";
|
|
7981
8003
|
try {
|
|
7982
8004
|
const pkg = JSON.parse(readFileSync(join19(__dirname3, "..", "package.json"), "utf-8"));
|
|
7983
8005
|
version = pkg.version;
|