@tractorscorch/clank 1.7.0 → 1.7.1

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,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [1.7.1] — 2026-03-23
10
+
11
+ ### Added
12
+ - **Telegram bot menu** — all commands registered with Telegram via `setMyCommands()`, appear when you type `/`
13
+ - **Tool indicators with emojis** — when the agent uses tools, Telegram shows emoji indicators above the response (📄 read\_file, 💻 bash, 🔍 search, 🌐 web, ✏️ edit, 🚀 spawn, etc.)
14
+ - **`/kill <id>` command** — kill a specific background task by short ID (first 8 chars), cascades to children
15
+ - **`/killall` command** — kill all running background tasks
16
+ - **`/version` command** — show Clank version
17
+ - **`/think` per-chat toggle** — actually toggles thinking display per Telegram chat (was a no-op before)
18
+
19
+ ### Changed
20
+ - **`/tasks` shows short IDs** — each task now shows its 8-char ID for use with `/kill`
21
+ - **`/status` shows more info** — model, agents, running tasks, thinking state, uptime
22
+ - **`/agents` shows default agent** — includes the default agent and its model, not just custom agents
23
+ - **`/model` shows fallbacks** — displays the full fallback chain
24
+ - **`/agent <name>` works** — actually switches agent by resetting session (was a stub)
25
+ - **Comprehensive docs** — full rewrite of Install Guide and User Guide for v1.7.x features
26
+ - **Website docs page** — new /docs tab on clanksuite.dev with provider table, command reference, and quick start
27
+
28
+ ---
29
+
9
30
  ## [1.7.0] — 2026-03-23
10
31
 
11
32
  ### 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.0-blue.svg" alt="Version" /></a>
12
+ <a href="https://github.com/ItsTrag1c/Clank/releases/latest"><img src="https://img.shields.io/badge/version-1.7.1-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.7.0_macos](https://github.com/ItsTrag1c/Clank/releases/latest/download/Clank_1.7.0_macos) |
78
+ | **macOS** (Apple Silicon) | [Clank_1.7.1_macos](https://github.com/ItsTrag1c/Clank/releases/latest/download/Clank_1.7.1_macos) |
79
79
 
80
80
  ## Security Notice
81
81
 
package/dist/index.js CHANGED
@@ -6085,6 +6085,67 @@ var init_base = __esm({
6085
6085
 
6086
6086
  // src/adapters/telegram.ts
6087
6087
  import { Bot } from "grammy";
6088
+ function toolEmoji(name) {
6089
+ const map = {
6090
+ read_file: "\u{1F4C4}",
6091
+ write_file: "\u270F\uFE0F",
6092
+ edit_file: "\u270F\uFE0F",
6093
+ list_directory: "\u{1F4C1}",
6094
+ search_files: "\u{1F50D}",
6095
+ glob_files: "\u{1F50D}",
6096
+ bash: "\u{1F4BB}",
6097
+ git: "\u{1F4E6}",
6098
+ web_search: "\u{1F310}",
6099
+ web_fetch: "\u{1F310}",
6100
+ spawn_task: "\u{1F680}",
6101
+ manage_agent: "\u{1F916}",
6102
+ manage_model: "\u{1F9E0}",
6103
+ manage_config: "\u2699\uFE0F",
6104
+ manage_session: "\u{1F4CB}",
6105
+ manage_cron: "\u23F0",
6106
+ tts: "\u{1F50A}",
6107
+ stt: "\u{1F3A4}"
6108
+ };
6109
+ return map[name] || "\u{1F527}";
6110
+ }
6111
+ function formatTool(name, done) {
6112
+ const emoji = toolEmoji(name);
6113
+ if (done === void 0) return `${emoji} ${name}`;
6114
+ return done ? `${emoji} ${name} \u2713` : `${emoji} ${name} \u2717`;
6115
+ }
6116
+ function buildStreamDisplay(response, thinking, tools, showThinking) {
6117
+ const parts = [];
6118
+ if (showThinking && thinking) {
6119
+ const truncated = thinking.length > 500 ? thinking.slice(-450) + "..." : thinking;
6120
+ parts.push(`\u{1F4AD} ${truncated}`);
6121
+ parts.push("");
6122
+ }
6123
+ if (tools.length > 0) {
6124
+ const toolLine = tools.map((t) => {
6125
+ if (t.done === void 0) return `${toolEmoji(t.name)} ${t.name}...`;
6126
+ return formatTool(t.name, t.done);
6127
+ }).join(" ");
6128
+ parts.push(toolLine);
6129
+ parts.push("");
6130
+ }
6131
+ parts.push(response);
6132
+ return parts.join("\n");
6133
+ }
6134
+ function buildFinalDisplay(response, thinking, tools, showThinking) {
6135
+ const parts = [];
6136
+ if (showThinking && thinking) {
6137
+ const truncated = thinking.length > 1e3 ? thinking.slice(0, 950) + "..." : thinking;
6138
+ parts.push(`\u{1F4AD} _${truncated}_`);
6139
+ parts.push("");
6140
+ }
6141
+ if (tools.length > 0) {
6142
+ const toolLine = tools.map((t) => formatTool(t.name, t.done ?? true)).join(" ");
6143
+ parts.push(toolLine);
6144
+ parts.push("");
6145
+ }
6146
+ parts.push(response);
6147
+ return parts.join("\n");
6148
+ }
6088
6149
  function splitMessage(text, maxLen) {
6089
6150
  if (text.length <= maxLen) return [text];
6090
6151
  const chunks = [];
@@ -6101,12 +6162,13 @@ function splitMessage(text, maxLen) {
6101
6162
  }
6102
6163
  return chunks;
6103
6164
  }
6104
- var TelegramAdapter;
6165
+ var thinkingEnabled, TelegramAdapter;
6105
6166
  var init_telegram = __esm({
6106
6167
  "src/adapters/telegram.ts"() {
6107
6168
  "use strict";
6108
6169
  init_esm_shims();
6109
6170
  init_base();
6171
+ thinkingEnabled = /* @__PURE__ */ new Map();
6110
6172
  TelegramAdapter = class extends ChannelAdapter {
6111
6173
  id = "telegram";
6112
6174
  name = "Telegram";
@@ -6114,6 +6176,7 @@ var init_telegram = __esm({
6114
6176
  config = null;
6115
6177
  bot = null;
6116
6178
  running = false;
6179
+ startedAt = 0;
6117
6180
  init(gateway2, config) {
6118
6181
  this.gateway = gateway2;
6119
6182
  this.config = config;
@@ -6124,9 +6187,25 @@ var init_telegram = __esm({
6124
6187
  console.log(" Telegram: disabled or no bot token configured");
6125
6188
  return;
6126
6189
  }
6190
+ this.startedAt = Date.now();
6127
6191
  try {
6128
6192
  this.bot = new Bot(telegramConfig.botToken);
6129
6193
  const bot = this.bot;
6194
+ await bot.api.setMyCommands([
6195
+ { command: "help", description: "Show available commands" },
6196
+ { command: "new", description: "Start a new session" },
6197
+ { command: "reset", description: "Clear current session" },
6198
+ { command: "status", description: "Agent status and info" },
6199
+ { command: "agents", description: "List available agents" },
6200
+ { command: "tasks", description: "Show background tasks" },
6201
+ { command: "kill", description: "Kill a background task" },
6202
+ { command: "killall", description: "Kill all running tasks" },
6203
+ { command: "model", description: "Show current model" },
6204
+ { command: "sessions", description: "List recent sessions" },
6205
+ { command: "think", description: "Toggle thinking display" },
6206
+ { command: "version", description: "Show Clank version" }
6207
+ ]).catch(() => {
6208
+ });
6130
6209
  const startupTime = Math.floor(Date.now() / 1e3);
6131
6210
  const chatLocks = /* @__PURE__ */ new Map();
6132
6211
  bot.on("message:text", async (ctx) => {
@@ -6166,15 +6245,18 @@ var init_telegram = __esm({
6166
6245
  try {
6167
6246
  console.log(` Telegram: processing message from ${userId} in ${chatId}`);
6168
6247
  await ctx.api.sendChatAction(chatId, "typing");
6169
- const typingInterval2 = setInterval(() => {
6248
+ const typingInterval = setInterval(() => {
6170
6249
  bot.api.sendChatAction(chatId, "typing").catch(() => {
6171
6250
  });
6172
6251
  }, 4e3);
6173
6252
  let streamMsgId = null;
6174
6253
  let sendingInitial = false;
6175
6254
  let accumulated = "";
6255
+ let thinkingText = "";
6176
6256
  let lastEditTime = 0;
6177
6257
  const EDIT_INTERVAL = 800;
6258
+ const showThinking = thinkingEnabled.get(chatId) ?? false;
6259
+ let toolIndicators = [];
6178
6260
  const response = await this.gateway.handleInboundMessageStreaming(
6179
6261
  {
6180
6262
  channel: "telegram",
@@ -6188,7 +6270,8 @@ var init_telegram = __esm({
6188
6270
  const now = Date.now();
6189
6271
  if (!streamMsgId && !sendingInitial && accumulated.length > 20) {
6190
6272
  sendingInitial = true;
6191
- bot.api.sendMessage(chatId, accumulated + " \u258D").then((sent) => {
6273
+ const display = buildStreamDisplay(accumulated, thinkingText, toolIndicators, showThinking);
6274
+ bot.api.sendMessage(chatId, display + " \u258D").then((sent) => {
6192
6275
  streamMsgId = sent.message_id;
6193
6276
  lastEditTime = now;
6194
6277
  }).catch(() => {
@@ -6197,19 +6280,32 @@ var init_telegram = __esm({
6197
6280
  }
6198
6281
  if (streamMsgId && now - lastEditTime > EDIT_INTERVAL) {
6199
6282
  lastEditTime = now;
6200
- const display = accumulated.length > 4e3 ? accumulated.slice(-3900) + " \u258D" : accumulated + " \u258D";
6201
- bot.api.editMessageText(chatId, streamMsgId, display).catch(() => {
6283
+ const display = buildStreamDisplay(accumulated, thinkingText, toolIndicators, showThinking);
6284
+ const truncated = display.length > 4e3 ? display.slice(-3900) + " \u258D" : display + " \u258D";
6285
+ bot.api.editMessageText(chatId, streamMsgId, truncated).catch(() => {
6202
6286
  });
6203
6287
  }
6204
6288
  },
6289
+ onThinking: (content) => {
6290
+ thinkingText += content;
6291
+ },
6205
6292
  onToolStart: (name) => {
6206
- if (!streamMsgId) {
6293
+ toolIndicators.push({ name });
6294
+ if (streamMsgId) {
6295
+ const display = buildStreamDisplay(accumulated, thinkingText, toolIndicators, showThinking);
6296
+ bot.api.editMessageText(chatId, streamMsgId, display + " \u258D").catch(() => {
6297
+ });
6298
+ } else {
6207
6299
  bot.api.sendChatAction(chatId, "typing").catch(() => {
6208
6300
  });
6209
6301
  }
6210
6302
  },
6303
+ onToolResult: (name, success) => {
6304
+ const tool = toolIndicators.find((t) => t.name === name && t.done === void 0);
6305
+ if (tool) tool.done = success;
6306
+ },
6211
6307
  onError: (message) => {
6212
- bot.api.sendMessage(chatId, `Error: ${message.slice(0, 200)}`).catch(() => {
6308
+ bot.api.sendMessage(chatId, `\u26A0\uFE0F ${message.slice(0, 200)}`).catch(() => {
6213
6309
  });
6214
6310
  }
6215
6311
  }
@@ -6229,22 +6325,23 @@ var init_telegram = __esm({
6229
6325
  });
6230
6326
  }
6231
6327
  if (streamMsgId && response) {
6232
- const finalText = response.length > 4e3 ? response.slice(0, 3950) + "\n... (truncated)" : response;
6328
+ const display = buildFinalDisplay(response, thinkingText, toolIndicators, showThinking);
6329
+ const finalText = display.length > 4e3 ? display.slice(0, 3950) + "\n... (truncated)" : display;
6233
6330
  await bot.api.editMessageText(chatId, streamMsgId, finalText).catch(() => {
6234
6331
  });
6235
6332
  } else if (response && !streamMsgId) {
6236
- const chunks = splitMessage(response, 4e3);
6333
+ const display = buildFinalDisplay(response, thinkingText, toolIndicators, showThinking);
6334
+ const chunks = splitMessage(display, 4e3);
6237
6335
  for (const chunk of chunks) {
6238
6336
  await ctx.api.sendMessage(chatId, chunk);
6239
6337
  }
6240
6338
  }
6241
- clearInterval(typingInterval2);
6339
+ clearInterval(typingInterval);
6242
6340
  console.log(` Telegram: response complete (${response?.length || 0} chars)`);
6243
6341
  } catch (err) {
6244
- clearInterval(typingInterval);
6245
6342
  const errMsg = err instanceof Error ? err.message : String(err);
6246
6343
  console.error(` Telegram: message handler error \u2014 ${errMsg}`);
6247
- await ctx.api.sendMessage(chatId, `Error: ${errMsg.slice(0, 200)}`).catch(() => {
6344
+ await ctx.api.sendMessage(chatId, `\u26A0\uFE0F Error: ${errMsg.slice(0, 200)}`).catch(() => {
6248
6345
  });
6249
6346
  }
6250
6347
  };
@@ -6276,7 +6373,7 @@ var init_telegram = __esm({
6276
6373
  const fileUrl = `https://api.telegram.org/file/bot${telegramConfig.botToken}/${file.file_path}`;
6277
6374
  const res = await fetch(fileUrl);
6278
6375
  if (!res.ok) {
6279
- await ctx.api.sendMessage(chatId, "Error: could not download voice message");
6376
+ await ctx.api.sendMessage(chatId, "\u26A0\uFE0F Could not download voice message");
6280
6377
  return;
6281
6378
  }
6282
6379
  const audioBuffer = Buffer.from(await res.arrayBuffer());
@@ -6285,7 +6382,7 @@ var init_telegram = __esm({
6285
6382
  const config = await loadConfig2();
6286
6383
  const stt = new STTEngine2(config);
6287
6384
  if (!stt.isAvailable()) {
6288
- await ctx.api.sendMessage(chatId, "Voice messages require speech-to-text. Set up Whisper: /help");
6385
+ await ctx.api.sendMessage(chatId, "Voice messages require speech-to-text. Configure Whisper in settings.");
6289
6386
  return;
6290
6387
  }
6291
6388
  const transcription = await stt.transcribe(audioBuffer, "ogg");
@@ -6317,7 +6414,7 @@ var init_telegram = __esm({
6317
6414
  }
6318
6415
  } catch (err) {
6319
6416
  const errMsg = err instanceof Error ? err.message : String(err);
6320
- await ctx.api.sendMessage(chatId, `Error: ${errMsg.slice(0, 200)}`);
6417
+ await ctx.api.sendMessage(chatId, `\u26A0\uFE0F Error: ${errMsg.slice(0, 200)}`);
6321
6418
  }
6322
6419
  };
6323
6420
  const prev = chatLocks.get(chatId) || Promise.resolve();
@@ -6354,7 +6451,7 @@ Describe or analyze the image if you can, or acknowledge it.`
6354
6451
  for (const chunk of chunks) await ctx.api.sendMessage(chatId, chunk);
6355
6452
  }
6356
6453
  } catch (err) {
6357
- await ctx.api.sendMessage(chatId, `Error: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`);
6454
+ await ctx.api.sendMessage(chatId, `\u26A0\uFE0F Error: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`);
6358
6455
  }
6359
6456
  };
6360
6457
  const prev = chatLocks.get(chatId) || Promise.resolve();
@@ -6406,7 +6503,7 @@ You can read this file with the read_file tool.`
6406
6503
  for (const chunk of chunks) await ctx.api.sendMessage(chatId, chunk);
6407
6504
  }
6408
6505
  } catch (err) {
6409
- await ctx.api.sendMessage(chatId, `Error: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`);
6506
+ await ctx.api.sendMessage(chatId, `\u26A0\uFE0F Error: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`);
6410
6507
  }
6411
6508
  };
6412
6509
  const prev = chatLocks.get(chatId) || Promise.resolve();
@@ -6441,45 +6538,94 @@ You can read this file with the read_file tool.`
6441
6538
  case "help":
6442
6539
  case "start":
6443
6540
  return [
6444
- "*Clank Commands*",
6541
+ "\u{1F527} *Clank Commands*",
6445
6542
  "",
6446
- "/help \u2014 Show this help",
6447
- "/status \u2014 Agent and model info",
6448
- "/agents \u2014 List available agents",
6449
- "/agent <name> \u2014 Switch to a different agent",
6450
- "/sessions \u2014 List recent sessions",
6543
+ "\u{1F4AC} *Chat*",
6451
6544
  "/new \u2014 Start a new session",
6452
- "/reset \u2014 Clear current session",
6545
+ "/reset \u2014 Clear current session history",
6546
+ "",
6547
+ "\u{1F4CA} *Info*",
6548
+ "/status \u2014 Agent, model, and session info",
6549
+ "/agents \u2014 List available agents",
6453
6550
  "/model \u2014 Show current model",
6454
6551
  "/tasks \u2014 Show background tasks",
6455
- "/think \u2014 Toggle thinking display"
6552
+ "/kill <id> \u2014 Kill a background task",
6553
+ "/killall \u2014 Kill all running tasks",
6554
+ "/version \u2014 Show Clank version",
6555
+ "",
6556
+ "\u2699\uFE0F *Settings*",
6557
+ "/agent <name> \u2014 Switch to a different agent",
6558
+ "/think \u2014 Toggle thinking display",
6559
+ "",
6560
+ "_Send any message to chat with the agent._"
6456
6561
  ].join("\n");
6457
6562
  case "status": {
6458
6563
  const cfg = this.config;
6459
6564
  const model = cfg?.agents?.defaults?.model?.primary || "unknown";
6460
- const agents2 = cfg?.agents?.list?.length || 0;
6565
+ const agentCount = cfg?.agents?.list?.length || 0;
6566
+ const tasks = this.gateway?.getTaskRegistry()?.list() || [];
6567
+ const runningTasks = tasks.filter((t) => t.status === "running").length;
6568
+ const uptime = Math.round((Date.now() - this.startedAt) / 6e4);
6569
+ const thinking = thinkingEnabled.get(chatId) ? "on" : "off";
6461
6570
  return [
6462
- "*Status*",
6463
- `Model: \`${model}\``,
6464
- `Agents: ${agents2} configured`,
6465
- `Chat: ${isGroup ? "group" : "DM"} (${chatId})`
6571
+ "\u{1F4CA} *Status*",
6572
+ "",
6573
+ `*Model:* \`${model}\``,
6574
+ `*Agents:* ${agentCount || 1} configured`,
6575
+ `*Tasks:* ${runningTasks} running / ${tasks.length} total`,
6576
+ `*Thinking:* ${thinking}`,
6577
+ `*Chat:* ${isGroup ? "group" : "DM"} (\`${chatId}\`)`,
6578
+ `*Uptime:* ${uptime} min`
6466
6579
  ].join("\n");
6467
6580
  }
6468
6581
  case "agents": {
6469
6582
  const list = this.config?.agents?.list || [];
6470
- if (list.length === 0) return "No custom agents configured. Using default agent.";
6471
- return "*Agents:*\n" + list.map(
6472
- (a) => `\u2022 *${a.name || a.id}* \u2014 \`${a.model?.primary || "default"}\``
6473
- ).join("\n");
6474
- }
6475
- case "agent":
6476
- if (args[0]) {
6477
- return `Agent switching via Telegram coming soon. Use the config tool in chat: "switch to agent ${args[0]}"`;
6583
+ const defaultModel = this.config?.agents?.defaults?.model?.primary || "unknown";
6584
+ if (list.length === 0) {
6585
+ return `\u{1F4CB} *Agents*
6586
+
6587
+ \u2022 *default* \u2014 \`${defaultModel}\`
6588
+
6589
+ _No custom agents. Configure in config.json5._`;
6590
+ }
6591
+ const lines = list.map(
6592
+ (a) => `\u2022 *${a.name || a.id}* \u2014 \`${a.model?.primary || defaultModel}\``
6593
+ );
6594
+ return `\u{1F4CB} *Agents*
6595
+
6596
+ \u2022 *default* \u2014 \`${defaultModel}\`
6597
+ ${lines.join("\n")}
6598
+
6599
+ _Switch with /agent <name>_`;
6600
+ }
6601
+ case "agent": {
6602
+ if (!args[0]) return "Usage: /agent <name>\n\nSee /agents for available agents.";
6603
+ const targetId = args[0].toLowerCase();
6604
+ const list = this.config?.agents?.list || [];
6605
+ const found = list.find((a) => a.id.toLowerCase() === targetId || (a.name || "").toLowerCase() === targetId);
6606
+ if (!found && targetId !== "default") {
6607
+ return `Agent "${args[0]}" not found. See /agents for available agents.`;
6608
+ }
6609
+ if (this.gateway) {
6610
+ await this.gateway.resetSession({
6611
+ channel: "telegram",
6612
+ peerId: chatId,
6613
+ peerKind: isGroup ? "group" : "dm"
6614
+ });
6478
6615
  }
6479
- return "Usage: /agent <name>";
6616
+ const name = found ? found.name || found.id : "default";
6617
+ return `Switched to agent *${name}*. Session reset \u2014 send a message to begin.`;
6618
+ }
6480
6619
  case "sessions": {
6481
- if (!this.gateway) return "Gateway not connected";
6482
- return "Use /new to start a fresh session, or /reset to clear the current one.";
6620
+ if (!this.gateway) return "Gateway not connected.";
6621
+ return [
6622
+ "\u{1F4C1} *Sessions*",
6623
+ "",
6624
+ "/new \u2014 Start a fresh session",
6625
+ "/reset \u2014 Clear current session history",
6626
+ "",
6627
+ `Current: \`${isGroup ? "group" : "dm"}:telegram:${chatId}\``
6628
+ ].join("\n");
6483
6629
  }
6484
6630
  case "new":
6485
6631
  case "reset":
@@ -6490,23 +6636,78 @@ You can read this file with the read_file tool.`
6490
6636
  peerKind: isGroup ? "group" : "dm"
6491
6637
  });
6492
6638
  }
6493
- return command === "new" ? "New session started. Send a message to begin." : "Session reset. History cleared.";
6639
+ return command === "new" ? "\u2728 New session started. Send a message to begin." : "\u{1F5D1} Session cleared. History erased.";
6494
6640
  case "model": {
6495
6641
  const model = this.config?.agents?.defaults?.model?.primary || "unknown";
6496
- return `Current model: \`${model}\``;
6642
+ const fallbacks = this.config?.agents?.defaults?.model?.fallbacks || [];
6643
+ const lines = [`\u{1F916} *Current Model*
6644
+
6645
+ Primary: \`${model}\``];
6646
+ if (fallbacks.length > 0) {
6647
+ lines.push(`Fallbacks: ${fallbacks.map((f) => `\`${f}\``).join(", ")}`);
6648
+ }
6649
+ return lines.join("\n");
6497
6650
  }
6498
6651
  case "tasks": {
6499
6652
  const tasks = this.gateway?.getTaskRegistry()?.list() || [];
6500
- if (tasks.length === 0) return "No background tasks.";
6501
- return "*Background Tasks:*\n" + tasks.map((t) => {
6653
+ if (tasks.length === 0) return "\u{1F4CB} No background tasks.";
6654
+ const lines = tasks.map((t) => {
6502
6655
  const elapsed = Math.round(((t.completedAt || Date.now()) - t.startedAt) / 1e3);
6656
+ const status = t.status === "running" ? "\u23F3" : t.status === "completed" ? "\u2705" : t.status === "failed" ? "\u274C" : "\u23F1";
6503
6657
  const depth = t.spawnDepth > 0 ? ` [depth ${t.spawnDepth}]` : "";
6504
6658
  const kids = t.children.length > 0 ? ` (${t.children.length} children)` : "";
6505
- return `\u2022 *${t.label.slice(0, 40)}* (${t.agentId})${depth}${kids} \u2014 ${t.status} (${elapsed}s)`;
6506
- }).join("\n");
6659
+ const shortId = t.id.slice(0, 8);
6660
+ return `${status} \`${shortId}\` *${t.label.slice(0, 35)}* (${t.agentId})${depth}${kids} \u2014 ${elapsed}s`;
6661
+ });
6662
+ return `\u{1F4CB} *Background Tasks*
6663
+
6664
+ ${lines.join("\n")}
6665
+
6666
+ _Kill with /kill <id> or /killall_`;
6667
+ }
6668
+ case "kill": {
6669
+ if (!this.gateway) return "Gateway not connected.";
6670
+ if (!args[0]) return "Usage: /kill <task-id>\n\nSee /tasks for task IDs.";
6671
+ const registry = this.gateway.getTaskRegistry();
6672
+ const shortId = args[0];
6673
+ const allTasks = registry.list();
6674
+ const match = allTasks.find((t) => t.id.startsWith(shortId) && t.status === "running");
6675
+ if (!match) return `No running task matching \`${shortId}\`. See /tasks.`;
6676
+ const subEngine = this.gateway.engines?.get(`task:${match.id}`);
6677
+ if (subEngine) {
6678
+ subEngine.cancel();
6679
+ subEngine.destroy();
6680
+ this.gateway.engines?.delete(`task:${match.id}`);
6681
+ }
6682
+ registry.cancel(match.id);
6683
+ const cascaded = registry.cascadeCancel(`task:${match.id}`);
6684
+ const cascade = cascaded > 0 ? ` + ${cascaded} child task(s)` : "";
6685
+ return `\u{1F5D1} Killed task \`${match.id.slice(0, 8)}\` \u2014 *${match.label.slice(0, 40)}*${cascade}`;
6686
+ }
6687
+ case "killall": {
6688
+ if (!this.gateway) return "Gateway not connected.";
6689
+ const registry = this.gateway.getTaskRegistry();
6690
+ const running = registry.list({ status: "running" });
6691
+ if (running.length === 0) return "No running tasks to kill.";
6692
+ for (const t of running) {
6693
+ const subEngine = this.gateway.engines?.get(`task:${t.id}`);
6694
+ if (subEngine) {
6695
+ subEngine.cancel();
6696
+ subEngine.destroy();
6697
+ this.gateway.engines?.delete(`task:${t.id}`);
6698
+ }
6699
+ registry.cancel(t.id);
6700
+ }
6701
+ return `\u{1F5D1} Killed *${running.length}* running task(s).`;
6702
+ }
6703
+ case "think": {
6704
+ const current = thinkingEnabled.get(chatId) ?? false;
6705
+ thinkingEnabled.set(chatId, !current);
6706
+ 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.";
6707
+ }
6708
+ case "version": {
6709
+ return `\u{1F527} *Clank* v1.7.1`;
6507
6710
  }
6508
- case "think":
6509
- return "Thinking display toggled. (Note: thinking visibility is per-client in the TUI/Web UI)";
6510
6711
  default:
6511
6712
  return null;
6512
6713
  }
@@ -7006,6 +7207,11 @@ var init_server = __esm({
7006
7207
  engine.on("token", fn);
7007
7208
  listeners.push(["token", fn]);
7008
7209
  }
7210
+ if (callbacks.onThinking) {
7211
+ const fn = (data) => callbacks.onThinking(data.content);
7212
+ engine.on("thinking", fn);
7213
+ listeners.push(["thinking", fn]);
7214
+ }
7009
7215
  if (callbacks.onToolStart) {
7010
7216
  const fn = (data) => callbacks.onToolStart(data.name);
7011
7217
  engine.on("tool-start", fn);
@@ -7079,7 +7285,7 @@ var init_server = __esm({
7079
7285
  res.writeHead(200, { "Content-Type": "application/json" });
7080
7286
  res.end(JSON.stringify({
7081
7287
  status: "ok",
7082
- version: "1.7.0",
7288
+ version: "1.7.1",
7083
7289
  uptime: process.uptime(),
7084
7290
  clients: this.clients.size,
7085
7291
  agents: this.engines.size
@@ -7191,7 +7397,7 @@ var init_server = __esm({
7191
7397
  const hello = {
7192
7398
  type: "hello",
7193
7399
  protocol: PROTOCOL_VERSION,
7194
- version: "1.7.0",
7400
+ version: "1.7.1",
7195
7401
  agents: this.config.agents.list.map((a) => ({
7196
7402
  id: a.id,
7197
7403
  name: a.name || a.id,
@@ -8805,7 +9011,7 @@ async function runTui(opts) {
8805
9011
  ws.on("open", () => {
8806
9012
  ws.send(JSON.stringify({
8807
9013
  type: "connect",
8808
- params: { auth: { token }, mode: "tui", version: "1.7.0" }
9014
+ params: { auth: { token }, mode: "tui", version: "1.7.1" }
8809
9015
  }));
8810
9016
  });
8811
9017
  ws.on("message", (data) => {
@@ -9234,7 +9440,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
9234
9440
  import { dirname as dirname5, join as join20 } from "path";
9235
9441
  var __filename3 = fileURLToPath5(import.meta.url);
9236
9442
  var __dirname3 = dirname5(__filename3);
9237
- var version = "1.7.0";
9443
+ var version = "1.7.1";
9238
9444
  try {
9239
9445
  const pkg = JSON.parse(readFileSync(join20(__dirname3, "..", "package.json"), "utf-8"));
9240
9446
  version = pkg.version;