@tractorscorch/clank 1.7.1 → 1.7.3

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/dist/index.js CHANGED
@@ -320,6 +320,30 @@ ${summary.trim()}`,
320
320
  this.compactTier1Aggressive();
321
321
  }
322
322
  }
323
+ /** Check if context is critically full (>95% utilization) */
324
+ isOverflowing() {
325
+ return this.utilizationPercent() >= 95;
326
+ }
327
+ /**
328
+ * Tier-1 only compaction — synchronous, no LLM call.
329
+ * Safe to call mid-turn without adding latency.
330
+ */
331
+ compactTier1Only() {
332
+ const before = this.messages.length;
333
+ const tokensBefore = this.estimateTokens();
334
+ this.compactTier1();
335
+ if (this.utilizationPercent() >= 70) {
336
+ this.compactTier1Aggressive();
337
+ }
338
+ return {
339
+ ok: true,
340
+ tier: 1,
341
+ messagesBefore: before,
342
+ messagesAfter: this.messages.length,
343
+ estimatedTokensBefore: tokensBefore,
344
+ estimatedTokensAfter: this.estimateTokens()
345
+ };
346
+ }
323
347
  /** Clear all messages */
324
348
  clear() {
325
349
  this.messages = [];
@@ -988,6 +1012,9 @@ ${results}`
988
1012
  outputTokens = 0;
989
1013
  }
990
1014
  try {
1015
+ if (this.contextEngine.utilizationPercent() > 90) {
1016
+ this.contextEngine.compactTier1Only();
1017
+ }
991
1018
  const streamIterator = activeProvider.stream(
992
1019
  this.contextEngine.getMessages(),
993
1020
  this.systemPrompt,
@@ -1024,6 +1051,21 @@ ${results}`
1024
1051
  } catch (streamErr) {
1025
1052
  const errMsg = streamErr instanceof Error ? streamErr.message : "unknown";
1026
1053
  const isTimeout = streamErr instanceof Error && (streamErr.name === "TimeoutError" || streamErr.name === "AbortError" || errMsg.includes("timed out"));
1054
+ const isContextError = /context.*(length|limit|exceeded)|too many tokens|maximum.*tokens|token limit/i.test(errMsg);
1055
+ if (attempt === 0 && isContextError && !signal.aborted) {
1056
+ this.emit("error", {
1057
+ message: "Context limit hit \u2014 compacting and retrying...",
1058
+ recoverable: true
1059
+ });
1060
+ this.contextEngine.compactTier1Only();
1061
+ if (this.contextEngine.isOverflowing()) {
1062
+ this.emit("error", {
1063
+ message: "Context is nearly full \u2014 use /compact or /new to free space",
1064
+ recoverable: true
1065
+ });
1066
+ }
1067
+ continue;
1068
+ }
1027
1069
  const isRetryable = !isTimeout && !signal.aborted && (errMsg.includes("connection dropped") || errMsg.includes("stopped responding") || errMsg.includes("empty response") || errMsg.includes("fetch failed") || errMsg.includes("ECONNREFUSED") || errMsg.includes("ECONNRESET"));
1028
1070
  if (attempt === 0 && (isRetryable || !signal.aborted && !isTimeout)) {
1029
1071
  this.emit("error", {
@@ -1129,6 +1171,15 @@ ${results}`
1129
1171
  this.contextEngine.ingest(result);
1130
1172
  this.emit("tool-result", { id: tc.id, name: tc.name, success: false, summary: errMsg });
1131
1173
  }
1174
+ if (this.contextEngine.needsCompaction()) {
1175
+ this.contextEngine.compactTier1Only();
1176
+ if (this.contextEngine.isOverflowing()) {
1177
+ this.emit("error", {
1178
+ message: "Context is nearly full \u2014 use /compact or /new to free space",
1179
+ recoverable: true
1180
+ });
1181
+ }
1182
+ }
1132
1183
  }
1133
1184
  }
1134
1185
  if (iterationCount >= MAX_ITERATIONS) {
@@ -1175,6 +1226,74 @@ ${results}`
1175
1226
  getContextEngine() {
1176
1227
  return this.contextEngine;
1177
1228
  }
1229
+ /**
1230
+ * Compact: summarize current state, clear context, inject summary.
1231
+ * Returns the summary so callers can display it.
1232
+ */
1233
+ async compactSession() {
1234
+ const messages = this.contextEngine.getMessages();
1235
+ if (messages.length < 3) {
1236
+ return "Nothing to compact \u2014 session is too short.";
1237
+ }
1238
+ const conversationText = messages.slice(-30).map((m) => {
1239
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
1240
+ const truncated = content.length > 400 ? content.slice(0, 400) + "..." : content;
1241
+ return `${m.role}: ${truncated}`;
1242
+ }).join("\n\n");
1243
+ const summaryPrompt = [
1244
+ "You are summarizing a conversation for context continuity.",
1245
+ "The user is compacting their session \u2014 they want to clear context but continue seamlessly.",
1246
+ "",
1247
+ "Produce a concise state summary covering:",
1248
+ "- What task(s) the user is working on",
1249
+ "- Key decisions made so far",
1250
+ "- Files created, modified, or discussed",
1251
+ "- Current progress and what comes next",
1252
+ "- Any important context (preferences, constraints, blockers)",
1253
+ "",
1254
+ "Format as bullet points. Be brief but complete \u2014 this is the ONLY context the model will have when resuming.",
1255
+ "",
1256
+ "Conversation:",
1257
+ conversationText
1258
+ ].join("\n");
1259
+ let summary = "";
1260
+ if (this.resolvedProvider) {
1261
+ try {
1262
+ for await (const event of this.resolvedProvider.provider.stream(
1263
+ [{ role: "user", content: summaryPrompt }],
1264
+ "You are a conversation summarizer. Output only the summary.",
1265
+ []
1266
+ )) {
1267
+ if (event.type === "text") {
1268
+ summary += event.content;
1269
+ }
1270
+ }
1271
+ } catch {
1272
+ summary = messages.slice(-6).map((m) => {
1273
+ const content = typeof m.content === "string" ? m.content : "";
1274
+ return `- [${m.role}] ${content.slice(0, 150)}`;
1275
+ }).join("\n");
1276
+ }
1277
+ }
1278
+ if (!summary.trim()) {
1279
+ summary = "Session was active but summary generation failed. Ask the user for context.";
1280
+ }
1281
+ this.contextEngine.clear();
1282
+ this.contextEngine.ingest({
1283
+ role: "user",
1284
+ content: `[Session compacted \u2014 previous context summarized below]
1285
+
1286
+ ${summary.trim()}
1287
+
1288
+ ---
1289
+ The session was compacted by the user. Continue from where you left off. You have full context above.`,
1290
+ _compacted: true
1291
+ });
1292
+ if (this.currentSession) {
1293
+ await this.sessionStore.saveMessages(this.currentSession.id, this.contextEngine.getMessages());
1294
+ }
1295
+ return summary.trim();
1296
+ }
1178
1297
  /** Destroy the engine and clean up */
1179
1298
  destroy() {
1180
1299
  this.cancel();
@@ -1197,12 +1316,22 @@ import { platform, hostname } from "os";
1197
1316
  async function buildSystemPrompt(opts) {
1198
1317
  const parts = [];
1199
1318
  const compact = opts.compact ?? false;
1319
+ const isLocal = opts.isLocal ?? false;
1320
+ const isSubAgent = (opts.spawnDepth ?? 0) > 0;
1200
1321
  if (!compact) {
1201
- const workspaceContent = await loadWorkspaceFiles(opts.workspaceDir);
1322
+ const files = isLocal ? WORKSPACE_FILES_LOCAL : WORKSPACE_FILES;
1323
+ const workspaceContent = await loadWorkspaceFiles(opts.workspaceDir, files);
1202
1324
  if (workspaceContent) {
1203
1325
  parts.push(workspaceContent);
1204
1326
  parts.push("---");
1205
1327
  }
1328
+ if (isSubAgent) {
1329
+ const runnerContent = await loadSingleFile(opts.workspaceDir, SUB_AGENT_FILE);
1330
+ if (runnerContent) {
1331
+ parts.push(runnerContent);
1332
+ parts.push("---");
1333
+ }
1334
+ }
1206
1335
  }
1207
1336
  if (compact) {
1208
1337
  parts.push(`Agent: ${opts.identity.name} | Model: ${opts.identity.model.primary} | Dir: ${opts.identity.workspace}`);
@@ -1221,7 +1350,8 @@ async function buildSystemPrompt(opts) {
1221
1350
  "You are an AI agent running LOCALLY on the user's machine.",
1222
1351
  "You have tools: read_file, write_file, edit_file, list_directory, bash, search_files, glob_files, git, web_search, web_fetch, and self-config tools.",
1223
1352
  "ALWAYS use your tools. NEVER say you cannot access files, run commands, or perform actions. You CAN \u2014 use your tools.",
1224
- "NEVER apologize and refuse. If asked to do something, DO IT with your tools or explain what tool you need."
1353
+ "NEVER apologize and refuse. If asked to do something, DO IT with your tools or explain what tool you need.",
1354
+ "Do NOT modify files outside your workspace or the user's current directory unless the user explicitly names the file."
1225
1355
  ].join(" "));
1226
1356
  } else {
1227
1357
  parts.push("## CRITICAL: You Are a Local Agent With Tools");
@@ -1237,13 +1367,18 @@ async function buildSystemPrompt(opts) {
1237
1367
  parts.push("3. NEVER apologize and refuse to act. If a task requires a tool, use it. If you lack a specific tool, say which tool you need \u2014 do not give a generic refusal.");
1238
1368
  parts.push("4. Read files before editing them. Use tools proactively without being asked twice.");
1239
1369
  parts.push("5. You can configure yourself \u2014 use the config, channel, agent, and model management tools to modify your own setup.");
1370
+ parts.push("6. Do NOT modify, delete, or overwrite files outside your workspace directory or the user's current working directory unless the user explicitly names the file. System files, OS directories, and config dotfiles are off-limits by default.");
1240
1371
  }
1241
1372
  if (opts.thinking === "off") {
1242
1373
  parts.push("");
1243
1374
  parts.push("Do NOT use extended thinking or reasoning blocks. Respond directly and concisely.");
1244
1375
  }
1245
1376
  parts.push("");
1246
- parts.push("When you learn something important about the user or project, save it using the config or memory tools so you remember it next time.");
1377
+ if (isLocal) {
1378
+ parts.push("Your memories are managed automatically. Use memory tools to save or recall important information. Do not rely on conversation history for long-term facts.");
1379
+ } else {
1380
+ parts.push("When you learn something important about the user or project, save it using the config or memory tools so you remember it next time.");
1381
+ }
1247
1382
  parts.push("");
1248
1383
  const projectMemory = await loadProjectMemory(opts.identity.workspace);
1249
1384
  if (projectMemory) {
@@ -1253,9 +1388,9 @@ async function buildSystemPrompt(opts) {
1253
1388
  }
1254
1389
  return parts.join("\n");
1255
1390
  }
1256
- async function loadWorkspaceFiles(workspaceDir) {
1391
+ async function loadWorkspaceFiles(workspaceDir, files = WORKSPACE_FILES) {
1257
1392
  const sections = [];
1258
- for (const filename of WORKSPACE_FILES) {
1393
+ for (const filename of files) {
1259
1394
  const filePath = join2(workspaceDir, filename);
1260
1395
  if (existsSync2(filePath)) {
1261
1396
  try {
@@ -1269,6 +1404,18 @@ async function loadWorkspaceFiles(workspaceDir) {
1269
1404
  }
1270
1405
  return sections.length > 0 ? sections.join("\n\n---\n\n") : null;
1271
1406
  }
1407
+ async function loadSingleFile(workspaceDir, filename) {
1408
+ const filePath = join2(workspaceDir, filename);
1409
+ if (existsSync2(filePath)) {
1410
+ try {
1411
+ const content = await readFile2(filePath, "utf-8");
1412
+ return content.trim() || null;
1413
+ } catch {
1414
+ return null;
1415
+ }
1416
+ }
1417
+ return null;
1418
+ }
1272
1419
  async function loadProjectMemory(projectRoot) {
1273
1420
  const candidates = [".clank.md", ".clankbuild.md", ".llamabuild.md"];
1274
1421
  for (const filename of candidates) {
@@ -1287,7 +1434,7 @@ async function loadProjectMemory(projectRoot) {
1287
1434
  async function ensureWorkspaceFiles(workspaceDir, templateDir) {
1288
1435
  const { mkdir: mkdir7, copyFile } = await import("fs/promises");
1289
1436
  await mkdir7(workspaceDir, { recursive: true });
1290
- for (const filename of [...WORKSPACE_FILES, "BOOTSTRAP.md", "HEARTBEAT.md"]) {
1437
+ for (const filename of [...WORKSPACE_FILES, "BOOTSTRAP.md", "HEARTBEAT.md", "RUNNER.md"]) {
1291
1438
  const target = join2(workspaceDir, filename);
1292
1439
  const source = join2(templateDir, filename);
1293
1440
  if (!existsSync2(target) && existsSync2(source)) {
@@ -1295,7 +1442,7 @@ async function ensureWorkspaceFiles(workspaceDir, templateDir) {
1295
1442
  }
1296
1443
  }
1297
1444
  }
1298
- var WORKSPACE_FILES;
1445
+ var WORKSPACE_FILES, WORKSPACE_FILES_LOCAL, SUB_AGENT_FILE;
1299
1446
  var init_system_prompt = __esm({
1300
1447
  "src/engine/system-prompt.ts"() {
1301
1448
  "use strict";
@@ -1308,6 +1455,14 @@ var init_system_prompt = __esm({
1308
1455
  "TOOLS.md",
1309
1456
  "MEMORY.md"
1310
1457
  ];
1458
+ WORKSPACE_FILES_LOCAL = [
1459
+ "SOUL.md",
1460
+ "USER.md",
1461
+ "IDENTITY.md",
1462
+ "AGENTS.md",
1463
+ "TOOLS.md"
1464
+ ];
1465
+ SUB_AGENT_FILE = "RUNNER.md";
1311
1466
  }
1312
1467
  });
1313
1468
 
@@ -5057,8 +5212,12 @@ async function runChat(opts) {
5057
5212
  console.error(red(`Error: ${message}${recoverable ? " (recoverable)" : ""}`));
5058
5213
  });
5059
5214
  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"));
5215
+ console.log(cyan(" ___ _ _ "));
5216
+ console.log(cyan(" / __|| | __ _ _ _ | |__"));
5217
+ console.log(cyan(" | (__ | |/ _` || ' \\| / /"));
5218
+ console.log(cyan(" \\___||_|\\__,_||_||_|_\\_\\"));
5219
+ console.log(dim(` v1.7.3 | ${resolved.modelId} | ${identity.toolTier} tier`));
5220
+ console.log(dim(" Type your message. Press Ctrl+C to exit.\n"));
5062
5221
  const rl = createInterface({
5063
5222
  input: process.stdin,
5064
5223
  output: process.stdout,
@@ -5123,7 +5282,7 @@ async function handleSlashCommand(input, engine, _rl) {
5123
5282
  console.log(dim(`Unknown command: /${cmd}. Type /help for available commands.`));
5124
5283
  }
5125
5284
  }
5126
- var dim, bold, green, yellow, red, cyan;
5285
+ var dim, green, yellow, red, cyan;
5127
5286
  var init_chat = __esm({
5128
5287
  "src/cli/chat.ts"() {
5129
5288
  "use strict";
@@ -5134,7 +5293,6 @@ var init_chat = __esm({
5134
5293
  init_config2();
5135
5294
  init_sessions();
5136
5295
  dim = (s) => `\x1B[2m${s}\x1B[0m`;
5137
- bold = (s) => `\x1B[1m${s}\x1B[0m`;
5138
5296
  green = (s) => `\x1B[32m${s}\x1B[0m`;
5139
5297
  yellow = (s) => `\x1B[33m${s}\x1B[0m`;
5140
5298
  red = (s) => `\x1B[31m${s}\x1B[0m`;
@@ -6195,6 +6353,7 @@ var init_telegram = __esm({
6195
6353
  { command: "help", description: "Show available commands" },
6196
6354
  { command: "new", description: "Start a new session" },
6197
6355
  { command: "reset", description: "Clear current session" },
6356
+ { command: "compact", description: "Save state and clear context" },
6198
6357
  { command: "status", description: "Agent status and info" },
6199
6358
  { command: "agents", description: "List available agents" },
6200
6359
  { command: "tasks", description: "Show background tasks" },
@@ -6245,7 +6404,7 @@ var init_telegram = __esm({
6245
6404
  try {
6246
6405
  console.log(` Telegram: processing message from ${userId} in ${chatId}`);
6247
6406
  await ctx.api.sendChatAction(chatId, "typing");
6248
- const typingInterval = setInterval(() => {
6407
+ const typingInterval2 = setInterval(() => {
6249
6408
  bot.api.sendChatAction(chatId, "typing").catch(() => {
6250
6409
  });
6251
6410
  }, 4e3);
@@ -6336,13 +6495,14 @@ var init_telegram = __esm({
6336
6495
  await ctx.api.sendMessage(chatId, chunk);
6337
6496
  }
6338
6497
  }
6339
- clearInterval(typingInterval);
6340
6498
  console.log(` Telegram: response complete (${response?.length || 0} chars)`);
6341
6499
  } catch (err) {
6342
6500
  const errMsg = err instanceof Error ? err.message : String(err);
6343
6501
  console.error(` Telegram: message handler error \u2014 ${errMsg}`);
6344
6502
  await ctx.api.sendMessage(chatId, `\u26A0\uFE0F Error: ${errMsg.slice(0, 200)}`).catch(() => {
6345
6503
  });
6504
+ } finally {
6505
+ clearInterval(typingInterval);
6346
6506
  }
6347
6507
  };
6348
6508
  const prev = chatLocks.get(chatId) || Promise.resolve();
@@ -6543,6 +6703,7 @@ You can read this file with the read_file tool.`
6543
6703
  "\u{1F4AC} *Chat*",
6544
6704
  "/new \u2014 Start a new session",
6545
6705
  "/reset \u2014 Clear current session history",
6706
+ "/compact \u2014 Save state, clear context, continue",
6546
6707
  "",
6547
6708
  "\u{1F4CA} *Info*",
6548
6709
  "/status \u2014 Agent, model, and session info",
@@ -6637,6 +6798,22 @@ _Switch with /agent <name>_`;
6637
6798
  });
6638
6799
  }
6639
6800
  return command === "new" ? "\u2728 New session started. Send a message to begin." : "\u{1F5D1} Session cleared. History erased.";
6801
+ case "compact": {
6802
+ if (!this.gateway) return "Gateway not connected.";
6803
+ const summary = await this.gateway.compactSession({
6804
+ channel: "telegram",
6805
+ peerId: chatId,
6806
+ peerKind: isGroup ? "group" : "dm"
6807
+ });
6808
+ if (!summary) return "Nothing to compact \u2014 no active session.";
6809
+ const preview = summary.length > 300 ? summary.slice(0, 300) + "..." : summary;
6810
+ return `\u{1F4E6} *Session compacted*
6811
+
6812
+ Context cleared and state saved. The agent will continue where it left off.
6813
+
6814
+ _Summary:_
6815
+ ${preview}`;
6816
+ }
6640
6817
  case "model": {
6641
6818
  const model = this.config?.agents?.defaults?.model?.primary || "unknown";
6642
6819
  const fallbacks = this.config?.agents?.defaults?.model?.fallbacks || [];
@@ -6706,7 +6883,7 @@ _Kill with /kill <id> or /killall_`;
6706
6883
  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
6884
  }
6708
6885
  case "version": {
6709
- return `\u{1F527} *Clank* v1.7.1`;
6886
+ return `\u{1F527} *Clank* v1.7.3`;
6710
6887
  }
6711
6888
  default:
6712
6889
  return null;
@@ -7161,6 +7338,17 @@ var init_server = __esm({
7161
7338
  this.engines.delete(sessionKey);
7162
7339
  }
7163
7340
  }
7341
+ /**
7342
+ * Compact a session — summarize state, clear context, inject summary.
7343
+ * Used by channel adapters (Telegram /compact command).
7344
+ * Returns the summary text, or null if no active session.
7345
+ */
7346
+ async compactSession(context) {
7347
+ const sessionKey = deriveSessionKey(context);
7348
+ const engine = this.engines.get(sessionKey);
7349
+ if (!engine) return null;
7350
+ return engine.compactSession();
7351
+ }
7164
7352
  /**
7165
7353
  * Handle an inbound message from any channel adapter.
7166
7354
  * This is the main entry point for all non-WebSocket messages.
@@ -7285,7 +7473,7 @@ var init_server = __esm({
7285
7473
  res.writeHead(200, { "Content-Type": "application/json" });
7286
7474
  res.end(JSON.stringify({
7287
7475
  status: "ok",
7288
- version: "1.7.1",
7476
+ version: "1.7.3",
7289
7477
  uptime: process.uptime(),
7290
7478
  clients: this.clients.size,
7291
7479
  agents: this.engines.size
@@ -7397,7 +7585,7 @@ var init_server = __esm({
7397
7585
  const hello = {
7398
7586
  type: "hello",
7399
7587
  protocol: PROTOCOL_VERSION,
7400
- version: "1.7.1",
7588
+ version: "1.7.3",
7401
7589
  agents: this.config.agents.list.map((a) => ({
7402
7590
  id: a.id,
7403
7591
  name: a.name || a.id,
@@ -7454,6 +7642,17 @@ var init_server = __esm({
7454
7642
  this.sendResponse(client, frame.id, true);
7455
7643
  break;
7456
7644
  }
7645
+ case "session.compact": {
7646
+ const compactKey = frame.params?.sessionKey || client.sessionKey;
7647
+ const compactEngine = this.engines.get(compactKey);
7648
+ if (compactEngine) {
7649
+ const summary = await compactEngine.compactSession();
7650
+ this.sendResponse(client, frame.id, true, { summary });
7651
+ } else {
7652
+ this.sendResponse(client, frame.id, false, "No active session to compact");
7653
+ }
7654
+ break;
7655
+ }
7457
7656
  // === Agents ===
7458
7657
  case "agent.list":
7459
7658
  this.sendResponse(client, frame.id, true, this.config.agents.list.map((a) => ({
@@ -7687,6 +7886,7 @@ var init_server = __esm({
7687
7886
  toolTier: agentConfig?.toolTier || this.config.agents.defaults.toolTier || "auto",
7688
7887
  tools: agentConfig?.tools
7689
7888
  };
7889
+ const currentDepth = sessionKey.startsWith("task:") ? (this.taskRegistry.getBySessionKey(sessionKey)?.spawnDepth ?? 0) + 1 : 0;
7690
7890
  const compact = agentConfig?.compactPrompt ?? this.config.agents.defaults.compactPrompt ?? false;
7691
7891
  const thinking = agentConfig?.thinking ?? this.config.agents.defaults.thinking ?? "auto";
7692
7892
  const systemPrompt = await buildSystemPrompt({
@@ -7694,12 +7894,13 @@ var init_server = __esm({
7694
7894
  workspaceDir: identity.workspace,
7695
7895
  channel,
7696
7896
  compact,
7697
- thinking
7897
+ thinking,
7898
+ spawnDepth: currentDepth,
7899
+ isLocal: resolved.isLocal
7698
7900
  });
7699
- const memoryBudget = resolved.isLocal ? 1500 : 4e3;
7700
- const memoryBlock = await this.memoryManager.buildMemoryBlock("", identity.workspace, memoryBudget);
7901
+ const memoryBudget = resolved.isLocal ? 800 : 4e3;
7902
+ const memoryBlock = await this.memoryManager.buildMemoryBlock("session", identity.workspace, memoryBudget);
7701
7903
  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
7904
  const maxSpawnDepth = this.config.agents.defaults.subagents?.maxSpawnDepth ?? 1;
7704
7905
  const maxConcurrent = this.config.agents.defaults.subagents?.maxConcurrent ?? 8;
7705
7906
  const canSpawn = currentDepth < maxSpawnDepth;
@@ -8286,7 +8487,7 @@ async function runSetup(opts) {
8286
8487
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
8287
8488
  try {
8288
8489
  console.log("");
8289
- console.log(bold2(" Welcome to Clank"));
8490
+ console.log(bold(" Welcome to Clank"));
8290
8491
  console.log("");
8291
8492
  console.log(" Clank is an AI agent that can read, write, and");
8292
8493
  console.log(" delete files, execute commands, and access the web.");
@@ -8304,7 +8505,7 @@ async function runSetup(opts) {
8304
8505
  console.log("");
8305
8506
  console.log(" How would you like to set up Clank?");
8306
8507
  console.log("");
8307
- console.log(" 1. " + bold2("Quick Start") + " (recommended)");
8508
+ console.log(" 1. " + bold("Quick Start") + " (recommended)");
8308
8509
  console.log(dim4(" Auto-detect local models, sensible defaults"));
8309
8510
  console.log(" 2. Advanced");
8310
8511
  console.log(dim4(" Full control over gateway, models, channels"));
@@ -8571,7 +8772,7 @@ async function runSetup(opts) {
8571
8772
  await saveConfig(config);
8572
8773
  console.log(green4("\n Config saved to " + getConfigDir() + "/config.json5"));
8573
8774
  console.log("");
8574
- console.log(bold2(" Clank is ready!"));
8775
+ console.log(bold(" Clank is ready!"));
8575
8776
  console.log("");
8576
8777
  console.log(" Start chatting:");
8577
8778
  console.log(dim4(" clank chat \u2014 CLI chat"));
@@ -8582,7 +8783,7 @@ async function runSetup(opts) {
8582
8783
  rl.close();
8583
8784
  }
8584
8785
  }
8585
- var __dirname2, dim4, bold2, green4, yellow2, cyan2;
8786
+ var __dirname2, dim4, bold, green4, yellow2, cyan2;
8586
8787
  var init_setup = __esm({
8587
8788
  "src/cli/setup.ts"() {
8588
8789
  "use strict";
@@ -8592,7 +8793,7 @@ var init_setup = __esm({
8592
8793
  init_daemon();
8593
8794
  __dirname2 = dirname4(fileURLToPath4(import.meta.url));
8594
8795
  dim4 = (s) => `\x1B[2m${s}\x1B[0m`;
8595
- bold2 = (s) => `\x1B[1m${s}\x1B[0m`;
8796
+ bold = (s) => `\x1B[1m${s}\x1B[0m`;
8596
8797
  green4 = (s) => `\x1B[32m${s}\x1B[0m`;
8597
8798
  yellow2 = (s) => `\x1B[33m${s}\x1B[0m`;
8598
8799
  cyan2 = (s) => `\x1B[36m${s}\x1B[0m`;
@@ -9005,13 +9206,17 @@ async function runTui(opts) {
9005
9206
  return;
9006
9207
  }
9007
9208
  console.log("");
9008
- console.log(bold3(" Clank TUI") + dim8(` | connecting to ${wsUrl}...`));
9209
+ console.log(cyan5(" ___ _ _ "));
9210
+ console.log(cyan5(" / __|| | __ _ _ _ | |__"));
9211
+ console.log(cyan5(" | (__ | |/ _` || ' \\| / /"));
9212
+ console.log(cyan5(" \\___||_|\\__,_||_||_|_\\_\\"));
9213
+ console.log(dim8(` TUI | connecting to ${wsUrl}...`));
9009
9214
  const ws = new WebSocket2(wsUrl);
9010
9215
  state.ws = ws;
9011
9216
  ws.on("open", () => {
9012
9217
  ws.send(JSON.stringify({
9013
9218
  type: "connect",
9014
- params: { auth: { token }, mode: "tui", version: "1.7.1" }
9219
+ params: { auth: { token }, mode: "tui", version: "1.7.3" }
9015
9220
  }));
9016
9221
  });
9017
9222
  ws.on("message", (data) => {
@@ -9184,6 +9389,11 @@ function handleFrame(state, frame) {
9184
9389
  if (!res.ok && res.error) {
9185
9390
  console.log(red5(`
9186
9391
  Error: ${res.error}`));
9392
+ } else if (res.ok && res.data?.summary) {
9393
+ const summary = res.data.summary.slice(0, 500);
9394
+ console.log(green8(" Session compacted.") + dim8(" Context cleared, state saved."));
9395
+ console.log(dim8(` Summary:
9396
+ ${summary}`));
9187
9397
  }
9188
9398
  state.streaming = false;
9189
9399
  }
@@ -9200,6 +9410,7 @@ async function handleSlashCommand2(state, input, rl) {
9200
9410
  console.log(dim8(" /model [id] \u2014 Show current model"));
9201
9411
  console.log(dim8(" /think \u2014 Toggle thinking display"));
9202
9412
  console.log(dim8(" /tools \u2014 Toggle tool output"));
9413
+ console.log(dim8(" /compact \u2014 Save state, clear context, continue"));
9203
9414
  console.log(dim8(" /new \u2014 Start new session"));
9204
9415
  console.log(dim8(" /reset \u2014 Reset current session"));
9205
9416
  console.log(dim8(" /exit \u2014 Exit"));
@@ -9256,6 +9467,15 @@ async function handleSlashCommand2(state, input, rl) {
9256
9467
  state.showToolOutput = !state.showToolOutput;
9257
9468
  console.log(dim8(` Tool output: ${state.showToolOutput ? "on" : "off"}`));
9258
9469
  break;
9470
+ case "compact":
9471
+ console.log(dim8(" Compacting session..."));
9472
+ state.ws?.send(JSON.stringify({
9473
+ type: "req",
9474
+ id: ++state.reqId,
9475
+ method: "session.compact",
9476
+ params: { sessionKey: state.sessionKey }
9477
+ }));
9478
+ break;
9259
9479
  case "new":
9260
9480
  state.sessionKey = `tui:${state.agentId}:${Date.now()}`;
9261
9481
  console.log(green8(` New session: ${state.sessionKey}`));
@@ -9280,7 +9500,7 @@ async function handleSlashCommand2(state, input, rl) {
9280
9500
  function printStatusBar(state) {
9281
9501
  console.log(dim8(` ${state.agentName} | ${state.modelId} | ${state.sessionKey}`));
9282
9502
  }
9283
- var dim8, bold3, green8, red5, cyan5, italic;
9503
+ var dim8, green8, red5, cyan5, italic;
9284
9504
  var init_tui = __esm({
9285
9505
  "src/cli/tui.ts"() {
9286
9506
  "use strict";
@@ -9288,7 +9508,6 @@ var init_tui = __esm({
9288
9508
  init_config2();
9289
9509
  init_protocol();
9290
9510
  dim8 = (s) => `\x1B[2m${s}\x1B[0m`;
9291
- bold3 = (s) => `\x1B[1m${s}\x1B[0m`;
9292
9511
  green8 = (s) => `\x1B[32m${s}\x1B[0m`;
9293
9512
  red5 = (s) => `\x1B[31m${s}\x1B[0m`;
9294
9513
  cyan5 = (s) => `\x1B[36m${s}\x1B[0m`;
@@ -9364,7 +9583,7 @@ import { existsSync as existsSync12 } from "fs";
9364
9583
  async function runUninstall(opts) {
9365
9584
  const configDir = getConfigDir();
9366
9585
  console.log("");
9367
- console.log(bold4(" Uninstall Clank"));
9586
+ console.log(bold2(" Uninstall Clank"));
9368
9587
  console.log("");
9369
9588
  console.log(" This will permanently remove:");
9370
9589
  console.log(red7(` ${configDir}`));
@@ -9417,7 +9636,7 @@ async function runUninstall(opts) {
9417
9636
  console.log(green10(" Clank has been completely removed."));
9418
9637
  console.log("");
9419
9638
  }
9420
- var dim10, bold4, green10, red7, yellow4;
9639
+ var dim10, bold2, green10, red7, yellow4;
9421
9640
  var init_uninstall = __esm({
9422
9641
  "src/cli/uninstall.ts"() {
9423
9642
  "use strict";
@@ -9425,7 +9644,7 @@ var init_uninstall = __esm({
9425
9644
  init_config2();
9426
9645
  init_gateway_cmd();
9427
9646
  dim10 = (s) => `\x1B[2m${s}\x1B[0m`;
9428
- bold4 = (s) => `\x1B[1m${s}\x1B[0m`;
9647
+ bold2 = (s) => `\x1B[1m${s}\x1B[0m`;
9429
9648
  green10 = (s) => `\x1B[32m${s}\x1B[0m`;
9430
9649
  red7 = (s) => `\x1B[31m${s}\x1B[0m`;
9431
9650
  yellow4 = (s) => `\x1B[33m${s}\x1B[0m`;
@@ -9440,7 +9659,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
9440
9659
  import { dirname as dirname5, join as join20 } from "path";
9441
9660
  var __filename3 = fileURLToPath5(import.meta.url);
9442
9661
  var __dirname3 = dirname5(__filename3);
9443
- var version = "1.7.1";
9662
+ var version = "1.7.3";
9444
9663
  try {
9445
9664
  const pkg = JSON.parse(readFileSync(join20(__dirname3, "..", "package.json"), "utf-8"));
9446
9665
  version = pkg.version;