@tractorscorch/clank 1.7.1 → 1.7.2

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,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [1.7.2] — 2026-03-24
10
+
11
+ ### Added
12
+ - **Agent runner template (RUNNER.md)** — structured playbook for sub-agents with task decomposition, tool patterns, and report format. Automatically injected into system prompt for spawned tasks so the model doesn't have to figure out the approach from scratch
13
+ - **`/compact` command** — save model state, clear context, continue where you left off. Available in TUI and Telegram. Uses the LLM to summarize current task state, then clears the conversation and injects the summary as the starting point
14
+ - **CLI/TUI ASCII banner** — Clank logo displayed on startup in both direct chat and TUI mode
15
+
16
+ ### Fixed
17
+ - **Telegram typing indicator leak** — typing indicator now cleared in `finally` block instead of `try` block, so it always stops even if an error occurs during streaming or message editing. Previously, errors would leave the chat permanently showing "typing..."
18
+ - **Install instructions** — split `npm install` and `clank setup` into separate code blocks in README so copying the block doesn't auto-run setup before install finishes
19
+ - **CLI chat version** — was hardcoded as `v0.1.0`, now shows correct version
20
+
21
+ ---
22
+
9
23
  ## [1.7.1] — 2026-03-23
10
24
 
11
25
  ### 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.1-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.2-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>
@@ -56,7 +56,17 @@ Clank is a personal AI gateway — **one daemon, many frontends**. It connects y
56
56
 
57
57
  ```bash
58
58
  npm install -g @tractorscorch/clank
59
+ ```
60
+
61
+ Then run the setup wizard (creates config, picks your model):
62
+
63
+ ```bash
59
64
  clank setup
65
+ ```
66
+
67
+ Start chatting:
68
+
69
+ ```bash
60
70
  clank
61
71
  ```
62
72
 
@@ -64,7 +74,14 @@ clank
64
74
 
65
75
  ```bash
66
76
  curl -fsSL https://raw.githubusercontent.com/ItsTrag1c/Clank/main/install.sh | bash
77
+ ```
78
+
79
+ Then run setup and start:
80
+
81
+ ```bash
67
82
  clank setup
83
+ ```
84
+ ```bash
68
85
  clank
69
86
  ```
70
87
 
@@ -75,7 +92,7 @@ That's it. Setup auto-detects your local models, configures the gateway, and get
75
92
  | Platform | Download |
76
93
  |----------|----------|
77
94
  | **npm** (all platforms) | `npm install -g @tractorscorch/clank` |
78
- | **macOS** (Apple Silicon) | [Clank_1.7.1_macos](https://github.com/ItsTrag1c/Clank/releases/latest/download/Clank_1.7.1_macos) |
95
+ | **macOS** (Apple Silicon) | [Clank_1.7.2_macos](https://github.com/ItsTrag1c/Clank/releases/latest/download/Clank_1.7.2_macos) |
79
96
 
80
97
  ## Security Notice
81
98
 
package/dist/index.js CHANGED
@@ -1175,6 +1175,74 @@ ${results}`
1175
1175
  getContextEngine() {
1176
1176
  return this.contextEngine;
1177
1177
  }
1178
+ /**
1179
+ * Compact: summarize current state, clear context, inject summary.
1180
+ * Returns the summary so callers can display it.
1181
+ */
1182
+ async compactSession() {
1183
+ const messages = this.contextEngine.getMessages();
1184
+ if (messages.length < 3) {
1185
+ return "Nothing to compact \u2014 session is too short.";
1186
+ }
1187
+ const conversationText = messages.slice(-30).map((m) => {
1188
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
1189
+ const truncated = content.length > 400 ? content.slice(0, 400) + "..." : content;
1190
+ return `${m.role}: ${truncated}`;
1191
+ }).join("\n\n");
1192
+ const summaryPrompt = [
1193
+ "You are summarizing a conversation for context continuity.",
1194
+ "The user is compacting their session \u2014 they want to clear context but continue seamlessly.",
1195
+ "",
1196
+ "Produce a concise state summary covering:",
1197
+ "- What task(s) the user is working on",
1198
+ "- Key decisions made so far",
1199
+ "- Files created, modified, or discussed",
1200
+ "- Current progress and what comes next",
1201
+ "- Any important context (preferences, constraints, blockers)",
1202
+ "",
1203
+ "Format as bullet points. Be brief but complete \u2014 this is the ONLY context the model will have when resuming.",
1204
+ "",
1205
+ "Conversation:",
1206
+ conversationText
1207
+ ].join("\n");
1208
+ let summary = "";
1209
+ if (this.resolvedProvider) {
1210
+ try {
1211
+ for await (const event of this.resolvedProvider.provider.stream(
1212
+ [{ role: "user", content: summaryPrompt }],
1213
+ "You are a conversation summarizer. Output only the summary.",
1214
+ []
1215
+ )) {
1216
+ if (event.type === "text") {
1217
+ summary += event.content;
1218
+ }
1219
+ }
1220
+ } catch {
1221
+ summary = messages.slice(-6).map((m) => {
1222
+ const content = typeof m.content === "string" ? m.content : "";
1223
+ return `- [${m.role}] ${content.slice(0, 150)}`;
1224
+ }).join("\n");
1225
+ }
1226
+ }
1227
+ if (!summary.trim()) {
1228
+ summary = "Session was active but summary generation failed. Ask the user for context.";
1229
+ }
1230
+ this.contextEngine.clear();
1231
+ this.contextEngine.ingest({
1232
+ role: "user",
1233
+ content: `[Session compacted \u2014 previous context summarized below]
1234
+
1235
+ ${summary.trim()}
1236
+
1237
+ ---
1238
+ The session was compacted by the user. Continue from where you left off. You have full context above.`,
1239
+ _compacted: true
1240
+ });
1241
+ if (this.currentSession) {
1242
+ await this.sessionStore.saveMessages(this.currentSession.id, this.contextEngine.getMessages());
1243
+ }
1244
+ return summary.trim();
1245
+ }
1178
1246
  /** Destroy the engine and clean up */
1179
1247
  destroy() {
1180
1248
  this.cancel();
@@ -1197,12 +1265,20 @@ import { platform, hostname } from "os";
1197
1265
  async function buildSystemPrompt(opts) {
1198
1266
  const parts = [];
1199
1267
  const compact = opts.compact ?? false;
1268
+ const isSubAgent = (opts.spawnDepth ?? 0) > 0;
1200
1269
  if (!compact) {
1201
1270
  const workspaceContent = await loadWorkspaceFiles(opts.workspaceDir);
1202
1271
  if (workspaceContent) {
1203
1272
  parts.push(workspaceContent);
1204
1273
  parts.push("---");
1205
1274
  }
1275
+ if (isSubAgent) {
1276
+ const runnerContent = await loadSingleFile(opts.workspaceDir, SUB_AGENT_FILE);
1277
+ if (runnerContent) {
1278
+ parts.push(runnerContent);
1279
+ parts.push("---");
1280
+ }
1281
+ }
1206
1282
  }
1207
1283
  if (compact) {
1208
1284
  parts.push(`Agent: ${opts.identity.name} | Model: ${opts.identity.model.primary} | Dir: ${opts.identity.workspace}`);
@@ -1269,6 +1345,18 @@ async function loadWorkspaceFiles(workspaceDir) {
1269
1345
  }
1270
1346
  return sections.length > 0 ? sections.join("\n\n---\n\n") : null;
1271
1347
  }
1348
+ async function loadSingleFile(workspaceDir, filename) {
1349
+ const filePath = join2(workspaceDir, filename);
1350
+ if (existsSync2(filePath)) {
1351
+ try {
1352
+ const content = await readFile2(filePath, "utf-8");
1353
+ return content.trim() || null;
1354
+ } catch {
1355
+ return null;
1356
+ }
1357
+ }
1358
+ return null;
1359
+ }
1272
1360
  async function loadProjectMemory(projectRoot) {
1273
1361
  const candidates = [".clank.md", ".clankbuild.md", ".llamabuild.md"];
1274
1362
  for (const filename of candidates) {
@@ -1287,7 +1375,7 @@ async function loadProjectMemory(projectRoot) {
1287
1375
  async function ensureWorkspaceFiles(workspaceDir, templateDir) {
1288
1376
  const { mkdir: mkdir7, copyFile } = await import("fs/promises");
1289
1377
  await mkdir7(workspaceDir, { recursive: true });
1290
- for (const filename of [...WORKSPACE_FILES, "BOOTSTRAP.md", "HEARTBEAT.md"]) {
1378
+ for (const filename of [...WORKSPACE_FILES, "BOOTSTRAP.md", "HEARTBEAT.md", "RUNNER.md"]) {
1291
1379
  const target = join2(workspaceDir, filename);
1292
1380
  const source = join2(templateDir, filename);
1293
1381
  if (!existsSync2(target) && existsSync2(source)) {
@@ -1295,7 +1383,7 @@ async function ensureWorkspaceFiles(workspaceDir, templateDir) {
1295
1383
  }
1296
1384
  }
1297
1385
  }
1298
- var WORKSPACE_FILES;
1386
+ var WORKSPACE_FILES, SUB_AGENT_FILE;
1299
1387
  var init_system_prompt = __esm({
1300
1388
  "src/engine/system-prompt.ts"() {
1301
1389
  "use strict";
@@ -1308,6 +1396,7 @@ var init_system_prompt = __esm({
1308
1396
  "TOOLS.md",
1309
1397
  "MEMORY.md"
1310
1398
  ];
1399
+ SUB_AGENT_FILE = "RUNNER.md";
1311
1400
  }
1312
1401
  });
1313
1402
 
@@ -5057,8 +5146,12 @@ async function runChat(opts) {
5057
5146
  console.error(red(`Error: ${message}${recoverable ? " (recoverable)" : ""}`));
5058
5147
  });
5059
5148
  console.log("");
5060
- console.log(bold("Clank") + dim(` v0.1.0 | ${resolved.modelId} | ${identity.toolTier} tier`));
5061
- console.log(dim("Type your message. Press Ctrl+C to exit.\n"));
5149
+ console.log(cyan(" ___ _ _ "));
5150
+ console.log(cyan(" / __|| | __ _ _ _ | |__"));
5151
+ console.log(cyan(" | (__ | |/ _` || ' \\| / /"));
5152
+ console.log(cyan(" \\___||_|\\__,_||_||_|_\\_\\"));
5153
+ console.log(dim(` v1.7.2 | ${resolved.modelId} | ${identity.toolTier} tier`));
5154
+ console.log(dim(" Type your message. Press Ctrl+C to exit.\n"));
5062
5155
  const rl = createInterface({
5063
5156
  input: process.stdin,
5064
5157
  output: process.stdout,
@@ -5123,7 +5216,7 @@ async function handleSlashCommand(input, engine, _rl) {
5123
5216
  console.log(dim(`Unknown command: /${cmd}. Type /help for available commands.`));
5124
5217
  }
5125
5218
  }
5126
- var dim, bold, green, yellow, red, cyan;
5219
+ var dim, green, yellow, red, cyan;
5127
5220
  var init_chat = __esm({
5128
5221
  "src/cli/chat.ts"() {
5129
5222
  "use strict";
@@ -5134,7 +5227,6 @@ var init_chat = __esm({
5134
5227
  init_config2();
5135
5228
  init_sessions();
5136
5229
  dim = (s) => `\x1B[2m${s}\x1B[0m`;
5137
- bold = (s) => `\x1B[1m${s}\x1B[0m`;
5138
5230
  green = (s) => `\x1B[32m${s}\x1B[0m`;
5139
5231
  yellow = (s) => `\x1B[33m${s}\x1B[0m`;
5140
5232
  red = (s) => `\x1B[31m${s}\x1B[0m`;
@@ -6195,6 +6287,7 @@ var init_telegram = __esm({
6195
6287
  { command: "help", description: "Show available commands" },
6196
6288
  { command: "new", description: "Start a new session" },
6197
6289
  { command: "reset", description: "Clear current session" },
6290
+ { command: "compact", description: "Save state and clear context" },
6198
6291
  { command: "status", description: "Agent status and info" },
6199
6292
  { command: "agents", description: "List available agents" },
6200
6293
  { command: "tasks", description: "Show background tasks" },
@@ -6245,7 +6338,7 @@ var init_telegram = __esm({
6245
6338
  try {
6246
6339
  console.log(` Telegram: processing message from ${userId} in ${chatId}`);
6247
6340
  await ctx.api.sendChatAction(chatId, "typing");
6248
- const typingInterval = setInterval(() => {
6341
+ const typingInterval2 = setInterval(() => {
6249
6342
  bot.api.sendChatAction(chatId, "typing").catch(() => {
6250
6343
  });
6251
6344
  }, 4e3);
@@ -6336,13 +6429,14 @@ var init_telegram = __esm({
6336
6429
  await ctx.api.sendMessage(chatId, chunk);
6337
6430
  }
6338
6431
  }
6339
- clearInterval(typingInterval);
6340
6432
  console.log(` Telegram: response complete (${response?.length || 0} chars)`);
6341
6433
  } catch (err) {
6342
6434
  const errMsg = err instanceof Error ? err.message : String(err);
6343
6435
  console.error(` Telegram: message handler error \u2014 ${errMsg}`);
6344
6436
  await ctx.api.sendMessage(chatId, `\u26A0\uFE0F Error: ${errMsg.slice(0, 200)}`).catch(() => {
6345
6437
  });
6438
+ } finally {
6439
+ clearInterval(typingInterval);
6346
6440
  }
6347
6441
  };
6348
6442
  const prev = chatLocks.get(chatId) || Promise.resolve();
@@ -6543,6 +6637,7 @@ You can read this file with the read_file tool.`
6543
6637
  "\u{1F4AC} *Chat*",
6544
6638
  "/new \u2014 Start a new session",
6545
6639
  "/reset \u2014 Clear current session history",
6640
+ "/compact \u2014 Save state, clear context, continue",
6546
6641
  "",
6547
6642
  "\u{1F4CA} *Info*",
6548
6643
  "/status \u2014 Agent, model, and session info",
@@ -6637,6 +6732,22 @@ _Switch with /agent <name>_`;
6637
6732
  });
6638
6733
  }
6639
6734
  return command === "new" ? "\u2728 New session started. Send a message to begin." : "\u{1F5D1} Session cleared. History erased.";
6735
+ case "compact": {
6736
+ if (!this.gateway) return "Gateway not connected.";
6737
+ const summary = await this.gateway.compactSession({
6738
+ channel: "telegram",
6739
+ peerId: chatId,
6740
+ peerKind: isGroup ? "group" : "dm"
6741
+ });
6742
+ if (!summary) return "Nothing to compact \u2014 no active session.";
6743
+ const preview = summary.length > 300 ? summary.slice(0, 300) + "..." : summary;
6744
+ return `\u{1F4E6} *Session compacted*
6745
+
6746
+ Context cleared and state saved. The agent will continue where it left off.
6747
+
6748
+ _Summary:_
6749
+ ${preview}`;
6750
+ }
6640
6751
  case "model": {
6641
6752
  const model = this.config?.agents?.defaults?.model?.primary || "unknown";
6642
6753
  const fallbacks = this.config?.agents?.defaults?.model?.fallbacks || [];
@@ -6706,7 +6817,7 @@ _Kill with /kill <id> or /killall_`;
6706
6817
  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
6818
  }
6708
6819
  case "version": {
6709
- return `\u{1F527} *Clank* v1.7.1`;
6820
+ return `\u{1F527} *Clank* v1.7.2`;
6710
6821
  }
6711
6822
  default:
6712
6823
  return null;
@@ -7161,6 +7272,17 @@ var init_server = __esm({
7161
7272
  this.engines.delete(sessionKey);
7162
7273
  }
7163
7274
  }
7275
+ /**
7276
+ * Compact a session — summarize state, clear context, inject summary.
7277
+ * Used by channel adapters (Telegram /compact command).
7278
+ * Returns the summary text, or null if no active session.
7279
+ */
7280
+ async compactSession(context) {
7281
+ const sessionKey = deriveSessionKey(context);
7282
+ const engine = this.engines.get(sessionKey);
7283
+ if (!engine) return null;
7284
+ return engine.compactSession();
7285
+ }
7164
7286
  /**
7165
7287
  * Handle an inbound message from any channel adapter.
7166
7288
  * This is the main entry point for all non-WebSocket messages.
@@ -7285,7 +7407,7 @@ var init_server = __esm({
7285
7407
  res.writeHead(200, { "Content-Type": "application/json" });
7286
7408
  res.end(JSON.stringify({
7287
7409
  status: "ok",
7288
- version: "1.7.1",
7410
+ version: "1.7.2",
7289
7411
  uptime: process.uptime(),
7290
7412
  clients: this.clients.size,
7291
7413
  agents: this.engines.size
@@ -7397,7 +7519,7 @@ var init_server = __esm({
7397
7519
  const hello = {
7398
7520
  type: "hello",
7399
7521
  protocol: PROTOCOL_VERSION,
7400
- version: "1.7.1",
7522
+ version: "1.7.2",
7401
7523
  agents: this.config.agents.list.map((a) => ({
7402
7524
  id: a.id,
7403
7525
  name: a.name || a.id,
@@ -7454,6 +7576,17 @@ var init_server = __esm({
7454
7576
  this.sendResponse(client, frame.id, true);
7455
7577
  break;
7456
7578
  }
7579
+ case "session.compact": {
7580
+ const compactKey = frame.params?.sessionKey || client.sessionKey;
7581
+ const compactEngine = this.engines.get(compactKey);
7582
+ if (compactEngine) {
7583
+ const summary = await compactEngine.compactSession();
7584
+ this.sendResponse(client, frame.id, true, { summary });
7585
+ } else {
7586
+ this.sendResponse(client, frame.id, false, "No active session to compact");
7587
+ }
7588
+ break;
7589
+ }
7457
7590
  // === Agents ===
7458
7591
  case "agent.list":
7459
7592
  this.sendResponse(client, frame.id, true, this.config.agents.list.map((a) => ({
@@ -7687,6 +7820,7 @@ var init_server = __esm({
7687
7820
  toolTier: agentConfig?.toolTier || this.config.agents.defaults.toolTier || "auto",
7688
7821
  tools: agentConfig?.tools
7689
7822
  };
7823
+ const currentDepth = sessionKey.startsWith("task:") ? (this.taskRegistry.getBySessionKey(sessionKey)?.spawnDepth ?? 0) + 1 : 0;
7690
7824
  const compact = agentConfig?.compactPrompt ?? this.config.agents.defaults.compactPrompt ?? false;
7691
7825
  const thinking = agentConfig?.thinking ?? this.config.agents.defaults.thinking ?? "auto";
7692
7826
  const systemPrompt = await buildSystemPrompt({
@@ -7694,12 +7828,12 @@ var init_server = __esm({
7694
7828
  workspaceDir: identity.workspace,
7695
7829
  channel,
7696
7830
  compact,
7697
- thinking
7831
+ thinking,
7832
+ spawnDepth: currentDepth
7698
7833
  });
7699
7834
  const memoryBudget = resolved.isLocal ? 1500 : 4e3;
7700
7835
  const memoryBlock = await this.memoryManager.buildMemoryBlock("", identity.workspace, memoryBudget);
7701
7836
  const fullPrompt = memoryBlock ? systemPrompt + "\n\n---\n\n" + memoryBlock : systemPrompt;
7702
- const currentDepth = sessionKey.startsWith("task:") ? (this.taskRegistry.getBySessionKey(sessionKey)?.spawnDepth ?? 0) + 1 : 0;
7703
7837
  const maxSpawnDepth = this.config.agents.defaults.subagents?.maxSpawnDepth ?? 1;
7704
7838
  const maxConcurrent = this.config.agents.defaults.subagents?.maxConcurrent ?? 8;
7705
7839
  const canSpawn = currentDepth < maxSpawnDepth;
@@ -8286,7 +8420,7 @@ async function runSetup(opts) {
8286
8420
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
8287
8421
  try {
8288
8422
  console.log("");
8289
- console.log(bold2(" Welcome to Clank"));
8423
+ console.log(bold(" Welcome to Clank"));
8290
8424
  console.log("");
8291
8425
  console.log(" Clank is an AI agent that can read, write, and");
8292
8426
  console.log(" delete files, execute commands, and access the web.");
@@ -8304,7 +8438,7 @@ async function runSetup(opts) {
8304
8438
  console.log("");
8305
8439
  console.log(" How would you like to set up Clank?");
8306
8440
  console.log("");
8307
- console.log(" 1. " + bold2("Quick Start") + " (recommended)");
8441
+ console.log(" 1. " + bold("Quick Start") + " (recommended)");
8308
8442
  console.log(dim4(" Auto-detect local models, sensible defaults"));
8309
8443
  console.log(" 2. Advanced");
8310
8444
  console.log(dim4(" Full control over gateway, models, channels"));
@@ -8571,7 +8705,7 @@ async function runSetup(opts) {
8571
8705
  await saveConfig(config);
8572
8706
  console.log(green4("\n Config saved to " + getConfigDir() + "/config.json5"));
8573
8707
  console.log("");
8574
- console.log(bold2(" Clank is ready!"));
8708
+ console.log(bold(" Clank is ready!"));
8575
8709
  console.log("");
8576
8710
  console.log(" Start chatting:");
8577
8711
  console.log(dim4(" clank chat \u2014 CLI chat"));
@@ -8582,7 +8716,7 @@ async function runSetup(opts) {
8582
8716
  rl.close();
8583
8717
  }
8584
8718
  }
8585
- var __dirname2, dim4, bold2, green4, yellow2, cyan2;
8719
+ var __dirname2, dim4, bold, green4, yellow2, cyan2;
8586
8720
  var init_setup = __esm({
8587
8721
  "src/cli/setup.ts"() {
8588
8722
  "use strict";
@@ -8592,7 +8726,7 @@ var init_setup = __esm({
8592
8726
  init_daemon();
8593
8727
  __dirname2 = dirname4(fileURLToPath4(import.meta.url));
8594
8728
  dim4 = (s) => `\x1B[2m${s}\x1B[0m`;
8595
- bold2 = (s) => `\x1B[1m${s}\x1B[0m`;
8729
+ bold = (s) => `\x1B[1m${s}\x1B[0m`;
8596
8730
  green4 = (s) => `\x1B[32m${s}\x1B[0m`;
8597
8731
  yellow2 = (s) => `\x1B[33m${s}\x1B[0m`;
8598
8732
  cyan2 = (s) => `\x1B[36m${s}\x1B[0m`;
@@ -9005,13 +9139,17 @@ async function runTui(opts) {
9005
9139
  return;
9006
9140
  }
9007
9141
  console.log("");
9008
- console.log(bold3(" Clank TUI") + dim8(` | connecting to ${wsUrl}...`));
9142
+ console.log(cyan5(" ___ _ _ "));
9143
+ console.log(cyan5(" / __|| | __ _ _ _ | |__"));
9144
+ console.log(cyan5(" | (__ | |/ _` || ' \\| / /"));
9145
+ console.log(cyan5(" \\___||_|\\__,_||_||_|_\\_\\"));
9146
+ console.log(dim8(` TUI | connecting to ${wsUrl}...`));
9009
9147
  const ws = new WebSocket2(wsUrl);
9010
9148
  state.ws = ws;
9011
9149
  ws.on("open", () => {
9012
9150
  ws.send(JSON.stringify({
9013
9151
  type: "connect",
9014
- params: { auth: { token }, mode: "tui", version: "1.7.1" }
9152
+ params: { auth: { token }, mode: "tui", version: "1.7.2" }
9015
9153
  }));
9016
9154
  });
9017
9155
  ws.on("message", (data) => {
@@ -9184,6 +9322,11 @@ function handleFrame(state, frame) {
9184
9322
  if (!res.ok && res.error) {
9185
9323
  console.log(red5(`
9186
9324
  Error: ${res.error}`));
9325
+ } else if (res.ok && res.data?.summary) {
9326
+ const summary = res.data.summary.slice(0, 500);
9327
+ console.log(green8(" Session compacted.") + dim8(" Context cleared, state saved."));
9328
+ console.log(dim8(` Summary:
9329
+ ${summary}`));
9187
9330
  }
9188
9331
  state.streaming = false;
9189
9332
  }
@@ -9200,6 +9343,7 @@ async function handleSlashCommand2(state, input, rl) {
9200
9343
  console.log(dim8(" /model [id] \u2014 Show current model"));
9201
9344
  console.log(dim8(" /think \u2014 Toggle thinking display"));
9202
9345
  console.log(dim8(" /tools \u2014 Toggle tool output"));
9346
+ console.log(dim8(" /compact \u2014 Save state, clear context, continue"));
9203
9347
  console.log(dim8(" /new \u2014 Start new session"));
9204
9348
  console.log(dim8(" /reset \u2014 Reset current session"));
9205
9349
  console.log(dim8(" /exit \u2014 Exit"));
@@ -9256,6 +9400,15 @@ async function handleSlashCommand2(state, input, rl) {
9256
9400
  state.showToolOutput = !state.showToolOutput;
9257
9401
  console.log(dim8(` Tool output: ${state.showToolOutput ? "on" : "off"}`));
9258
9402
  break;
9403
+ case "compact":
9404
+ console.log(dim8(" Compacting session..."));
9405
+ state.ws?.send(JSON.stringify({
9406
+ type: "req",
9407
+ id: ++state.reqId,
9408
+ method: "session.compact",
9409
+ params: { sessionKey: state.sessionKey }
9410
+ }));
9411
+ break;
9259
9412
  case "new":
9260
9413
  state.sessionKey = `tui:${state.agentId}:${Date.now()}`;
9261
9414
  console.log(green8(` New session: ${state.sessionKey}`));
@@ -9280,7 +9433,7 @@ async function handleSlashCommand2(state, input, rl) {
9280
9433
  function printStatusBar(state) {
9281
9434
  console.log(dim8(` ${state.agentName} | ${state.modelId} | ${state.sessionKey}`));
9282
9435
  }
9283
- var dim8, bold3, green8, red5, cyan5, italic;
9436
+ var dim8, green8, red5, cyan5, italic;
9284
9437
  var init_tui = __esm({
9285
9438
  "src/cli/tui.ts"() {
9286
9439
  "use strict";
@@ -9288,7 +9441,6 @@ var init_tui = __esm({
9288
9441
  init_config2();
9289
9442
  init_protocol();
9290
9443
  dim8 = (s) => `\x1B[2m${s}\x1B[0m`;
9291
- bold3 = (s) => `\x1B[1m${s}\x1B[0m`;
9292
9444
  green8 = (s) => `\x1B[32m${s}\x1B[0m`;
9293
9445
  red5 = (s) => `\x1B[31m${s}\x1B[0m`;
9294
9446
  cyan5 = (s) => `\x1B[36m${s}\x1B[0m`;
@@ -9364,7 +9516,7 @@ import { existsSync as existsSync12 } from "fs";
9364
9516
  async function runUninstall(opts) {
9365
9517
  const configDir = getConfigDir();
9366
9518
  console.log("");
9367
- console.log(bold4(" Uninstall Clank"));
9519
+ console.log(bold2(" Uninstall Clank"));
9368
9520
  console.log("");
9369
9521
  console.log(" This will permanently remove:");
9370
9522
  console.log(red7(` ${configDir}`));
@@ -9417,7 +9569,7 @@ async function runUninstall(opts) {
9417
9569
  console.log(green10(" Clank has been completely removed."));
9418
9570
  console.log("");
9419
9571
  }
9420
- var dim10, bold4, green10, red7, yellow4;
9572
+ var dim10, bold2, green10, red7, yellow4;
9421
9573
  var init_uninstall = __esm({
9422
9574
  "src/cli/uninstall.ts"() {
9423
9575
  "use strict";
@@ -9425,7 +9577,7 @@ var init_uninstall = __esm({
9425
9577
  init_config2();
9426
9578
  init_gateway_cmd();
9427
9579
  dim10 = (s) => `\x1B[2m${s}\x1B[0m`;
9428
- bold4 = (s) => `\x1B[1m${s}\x1B[0m`;
9580
+ bold2 = (s) => `\x1B[1m${s}\x1B[0m`;
9429
9581
  green10 = (s) => `\x1B[32m${s}\x1B[0m`;
9430
9582
  red7 = (s) => `\x1B[31m${s}\x1B[0m`;
9431
9583
  yellow4 = (s) => `\x1B[33m${s}\x1B[0m`;
@@ -9440,7 +9592,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
9440
9592
  import { dirname as dirname5, join as join20 } from "path";
9441
9593
  var __filename3 = fileURLToPath5(import.meta.url);
9442
9594
  var __dirname3 = dirname5(__filename3);
9443
- var version = "1.7.1";
9595
+ var version = "1.7.2";
9444
9596
  try {
9445
9597
  const pkg = JSON.parse(readFileSync(join20(__dirname3, "..", "package.json"), "utf-8"));
9446
9598
  version = pkg.version;