cc-claw 0.18.5 → 0.19.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.
Files changed (2) hide show
  1. package/dist/cli.js +2259 -202
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ var VERSION;
33
33
  var init_version = __esm({
34
34
  "src/version.ts"() {
35
35
  "use strict";
36
- VERSION = true ? "0.18.5" : (() => {
36
+ VERSION = true ? "0.19.0" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -1690,6 +1690,34 @@ function initSchema(db3) {
1690
1690
  } catch {
1691
1691
  }
1692
1692
  applySalienceDecay(db3);
1693
+ db3.exec(`
1694
+ CREATE TABLE IF NOT EXISTS ollama_servers (
1695
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1696
+ name TEXT NOT NULL UNIQUE,
1697
+ host TEXT NOT NULL,
1698
+ port INTEGER NOT NULL DEFAULT 11434,
1699
+ api_key TEXT,
1700
+ status TEXT NOT NULL DEFAULT 'offline',
1701
+ last_health_check TEXT,
1702
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1703
+ );
1704
+ `);
1705
+ db3.exec(`
1706
+ CREATE TABLE IF NOT EXISTS ollama_models (
1707
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1708
+ server_id INTEGER NOT NULL REFERENCES ollama_servers(id) ON DELETE CASCADE,
1709
+ name TEXT NOT NULL,
1710
+ family TEXT,
1711
+ parameter_size TEXT,
1712
+ quantization TEXT,
1713
+ context_window INTEGER DEFAULT 4096,
1714
+ size_bytes INTEGER,
1715
+ digest TEXT,
1716
+ last_seen TEXT NOT NULL DEFAULT (datetime('now')),
1717
+ quality_score REAL,
1718
+ UNIQUE(server_id, name)
1719
+ );
1720
+ `);
1693
1721
  initAgentTables(db3);
1694
1722
  initMcpTables(db3);
1695
1723
  initActivityTable(db3);
@@ -2028,7 +2056,7 @@ var init_embeddings = __esm({
2028
2056
  return results[0];
2029
2057
  }
2030
2058
  async embedBatch(texts) {
2031
- const url = `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:batchEmbedContents?key=${this.apiKey}`;
2059
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:batchEmbedContents`;
2032
2060
  const requests = texts.map((text) => ({
2033
2061
  model: `models/${this.model}`,
2034
2062
  content: { parts: [{ text }] },
@@ -2036,7 +2064,10 @@ var init_embeddings = __esm({
2036
2064
  }));
2037
2065
  const res = await fetch(url, {
2038
2066
  method: "POST",
2039
- headers: { "Content-Type": "application/json" },
2067
+ headers: {
2068
+ "Content-Type": "application/json",
2069
+ "x-goog-api-key": this.apiKey
2070
+ },
2040
2071
  body: JSON.stringify({ requests })
2041
2072
  });
2042
2073
  if (!res.ok) {
@@ -2485,6 +2516,7 @@ var chat_settings_exports = {};
2485
2516
  __export(chat_settings_exports, {
2486
2517
  ALL_TOOLS: () => ALL_TOOLS,
2487
2518
  ChatSettingsSnapshot: () => ChatSettingsSnapshot,
2519
+ GLOBAL_SUMMARIZER_SENTINEL: () => GLOBAL_SUMMARIZER_SENTINEL,
2488
2520
  clearAgentMode: () => clearAgentMode,
2489
2521
  clearAllPaidSlots: () => clearAllPaidSlots,
2490
2522
  clearChatPaidSlots: () => clearChatPaidSlots,
@@ -2504,6 +2536,7 @@ __export(chat_settings_exports, {
2504
2536
  getCwd: () => getCwd,
2505
2537
  getEnabledTools: () => getEnabledTools,
2506
2538
  getExecMode: () => getExecMode,
2539
+ getGlobalSummarizer: () => getGlobalSummarizer,
2507
2540
  getMode: () => getMode,
2508
2541
  getModel: () => getModel,
2509
2542
  getPendingEscalation: () => getPendingEscalation,
@@ -2521,6 +2554,7 @@ __export(chat_settings_exports, {
2521
2554
  setBackend: () => setBackend,
2522
2555
  setCwd: () => setCwd,
2523
2556
  setExecMode: () => setExecMode,
2557
+ setGlobalSummarizer: () => setGlobalSummarizer,
2524
2558
  setMode: () => setMode,
2525
2559
  setModel: () => setModel,
2526
2560
  setSessionLogEnabled: () => setSessionLogEnabled,
@@ -2755,11 +2789,29 @@ function clearThinkingLevel(chatId) {
2755
2789
  getDb().prepare("DELETE FROM chat_thinking WHERE chat_id = ?").run(chatId);
2756
2790
  }
2757
2791
  function getSummarizer(chatId) {
2758
- const row = getDb().prepare(
2792
+ const perChat = getDb().prepare(
2759
2793
  "SELECT backend, model FROM chat_summarizer WHERE chat_id = ?"
2760
2794
  ).get(chatId);
2795
+ if (perChat && (perChat.backend || perChat.model)) return perChat;
2796
+ const global = getDb().prepare(
2797
+ "SELECT backend, model FROM chat_summarizer WHERE chat_id = ?"
2798
+ ).get(GLOBAL_SUMMARIZER_SENTINEL);
2799
+ if (global && (global.backend || global.model)) return global;
2800
+ return { backend: null, model: null };
2801
+ }
2802
+ function getGlobalSummarizer() {
2803
+ const row = getDb().prepare(
2804
+ "SELECT backend, model FROM chat_summarizer WHERE chat_id = ?"
2805
+ ).get(GLOBAL_SUMMARIZER_SENTINEL);
2761
2806
  return row ?? { backend: null, model: null };
2762
2807
  }
2808
+ function setGlobalSummarizer(backend2, model2) {
2809
+ getDb().prepare(`
2810
+ INSERT INTO chat_summarizer (chat_id, backend, model)
2811
+ VALUES (?, ?, ?)
2812
+ ON CONFLICT(chat_id) DO UPDATE SET backend = ?, model = ?
2813
+ `).run(GLOBAL_SUMMARIZER_SENTINEL, backend2, model2, backend2, model2);
2814
+ }
2763
2815
  function setSummarizer(chatId, backend2, model2) {
2764
2816
  getDb().prepare(`
2765
2817
  INSERT INTO chat_summarizer (chat_id, backend, model)
@@ -2836,7 +2888,7 @@ function clearChatPaidSlots(chatId) {
2836
2888
  function clearAllPaidSlots() {
2837
2889
  getDb().prepare("DELETE FROM chat_allow_paid_slots").run();
2838
2890
  }
2839
- var pendingEscalations, ESCALATION_TTL_MS, ALL_TOOLS, ChatSettingsSnapshot;
2891
+ var pendingEscalations, ESCALATION_TTL_MS, ALL_TOOLS, GLOBAL_SUMMARIZER_SENTINEL, ChatSettingsSnapshot;
2840
2892
  var init_chat_settings = __esm({
2841
2893
  "src/memory/chat-settings.ts"() {
2842
2894
  "use strict";
@@ -2845,6 +2897,7 @@ var init_chat_settings = __esm({
2845
2897
  pendingEscalations = /* @__PURE__ */ new Map();
2846
2898
  ESCALATION_TTL_MS = 5 * 60 * 1e3;
2847
2899
  ALL_TOOLS = ["Read", "Glob", "Grep", "Bash", "Write", "Edit", "WebFetch", "WebSearch", "Agent", "AskUserQuestion"];
2900
+ GLOBAL_SUMMARIZER_SENTINEL = "__global__";
2848
2901
  ChatSettingsSnapshot = class {
2849
2902
  constructor(chatId) {
2850
2903
  this.chatId = chatId;
@@ -3754,6 +3807,7 @@ var store_exports5 = {};
3754
3807
  __export(store_exports5, {
3755
3808
  ALL_TOOLS: () => ALL_TOOLS,
3756
3809
  ChatSettingsSnapshot: () => ChatSettingsSnapshot,
3810
+ GLOBAL_SUMMARIZER_SENTINEL: () => GLOBAL_SUMMARIZER_SENTINEL,
3757
3811
  addBackendSlot: () => addBackendSlot,
3758
3812
  addGeminiSlot: () => addGeminiSlot,
3759
3813
  addHeartbeatWatch: () => addHeartbeatWatch,
@@ -3815,6 +3869,7 @@ __export(store_exports5, {
3815
3869
  getExecMode: () => getExecMode,
3816
3870
  getGeminiRotationMode: () => getGeminiRotationMode,
3817
3871
  getGeminiSlots: () => getGeminiSlots,
3872
+ getGlobalSummarizer: () => getGlobalSummarizer,
3818
3873
  getHeartbeatConfig: () => getHeartbeatConfig,
3819
3874
  getJobById: () => getJobById,
3820
3875
  getJobRuns: () => getJobRuns,
@@ -3892,6 +3947,7 @@ __export(store_exports5, {
3892
3947
  setExecMode: () => setExecMode,
3893
3948
  setGeminiRotationMode: () => setGeminiRotationMode,
3894
3949
  setGeminiSlotEnabled: () => setGeminiSlotEnabled,
3950
+ setGlobalSummarizer: () => setGlobalSummarizer,
3895
3951
  setHeartbeatConfig: () => setHeartbeatConfig,
3896
3952
  setMode: () => setMode,
3897
3953
  setModel: () => setModel,
@@ -5231,19 +5287,1043 @@ var init_cursor = __esm({
5231
5287
  } : void 0
5232
5288
  });
5233
5289
  }
5234
- return events;
5290
+ return events;
5291
+ }
5292
+ getSubagentInstructions() {
5293
+ return "You have native sub-agent support via the Task tool. Built-in agent types: generalPurpose (multi-step tasks), explore (fast codebase search), shell (command execution), code-reviewer. Use them for parallel work.";
5294
+ }
5295
+ shouldKillOnResult() {
5296
+ return false;
5297
+ }
5298
+ resolveModelWithThinking(model2, level) {
5299
+ if (!model2 || !level || level === "auto") return model2;
5300
+ const variants = THINKING_VARIANTS[model2];
5301
+ if (!variants) return model2;
5302
+ return variants[level] ?? model2;
5303
+ }
5304
+ };
5305
+ }
5306
+ });
5307
+
5308
+ // src/services/ollama/client.ts
5309
+ var client_exports = {};
5310
+ __export(client_exports, {
5311
+ chat: () => chat,
5312
+ generate: () => generate,
5313
+ listModels: () => listModels,
5314
+ ping: () => ping,
5315
+ runningModels: () => runningModels,
5316
+ showModel: () => showModel
5317
+ });
5318
+ function buildHeaders(apiKey) {
5319
+ const headers = {
5320
+ "Content-Type": "application/json"
5321
+ };
5322
+ if (apiKey) {
5323
+ headers["Authorization"] = `Bearer ${apiKey}`;
5324
+ }
5325
+ return headers;
5326
+ }
5327
+ async function parseNdjsonStream(reader, onChunk, signal) {
5328
+ const decoder = new TextDecoder();
5329
+ let buffer = "";
5330
+ try {
5331
+ while (true) {
5332
+ if (signal?.aborted) break;
5333
+ const { done, value } = await reader.read();
5334
+ if (done) break;
5335
+ buffer += decoder.decode(value, { stream: true });
5336
+ const lines = buffer.split("\n");
5337
+ buffer = lines.pop() ?? "";
5338
+ for (const line of lines) {
5339
+ const trimmed = line.trim();
5340
+ if (!trimmed) continue;
5341
+ try {
5342
+ const parsed = JSON.parse(trimmed);
5343
+ onChunk(parsed);
5344
+ } catch {
5345
+ }
5346
+ }
5347
+ }
5348
+ if (buffer.trim()) {
5349
+ try {
5350
+ const parsed = JSON.parse(buffer.trim());
5351
+ onChunk(parsed);
5352
+ } catch {
5353
+ }
5354
+ }
5355
+ } finally {
5356
+ reader.releaseLock();
5357
+ }
5358
+ }
5359
+ async function ping(baseUrl, opts) {
5360
+ try {
5361
+ const controller = new AbortController();
5362
+ const timeout = setTimeout(
5363
+ () => controller.abort(),
5364
+ opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS
5365
+ );
5366
+ try {
5367
+ const res = await fetch(baseUrl, {
5368
+ signal: controller.signal,
5369
+ headers: buildHeaders(opts?.apiKey)
5370
+ });
5371
+ return res.ok;
5372
+ } finally {
5373
+ clearTimeout(timeout);
5374
+ }
5375
+ } catch {
5376
+ return false;
5377
+ }
5378
+ }
5379
+ async function listModels(baseUrl, opts) {
5380
+ const res = await fetch(`${baseUrl}/api/tags`, {
5381
+ signal: opts?.signal,
5382
+ headers: buildHeaders(opts?.apiKey)
5383
+ });
5384
+ if (!res.ok) {
5385
+ throw new Error(`Ollama listModels failed: ${res.status} ${res.statusText}`);
5386
+ }
5387
+ return res.json();
5388
+ }
5389
+ async function showModel(baseUrl, model2, opts) {
5390
+ const res = await fetch(`${baseUrl}/api/show`, {
5391
+ method: "POST",
5392
+ headers: buildHeaders(opts?.apiKey),
5393
+ body: JSON.stringify({ name: model2 }),
5394
+ signal: opts?.signal
5395
+ });
5396
+ if (!res.ok) {
5397
+ throw new Error(`Ollama showModel(${model2}) failed: ${res.status} ${res.statusText}`);
5398
+ }
5399
+ return res.json();
5400
+ }
5401
+ async function runningModels(baseUrl, opts) {
5402
+ const res = await fetch(`${baseUrl}/api/ps`, {
5403
+ signal: opts?.signal,
5404
+ headers: buildHeaders(opts?.apiKey)
5405
+ });
5406
+ if (!res.ok) {
5407
+ throw new Error(`Ollama runningModels failed: ${res.status} ${res.statusText}`);
5408
+ }
5409
+ return res.json();
5410
+ }
5411
+ async function generate(baseUrl, model2, prompt, opts) {
5412
+ const overallTimeoutMs = opts?.timeoutMs ?? DEFAULT_GENERATE_TIMEOUT_MS;
5413
+ const controller = new AbortController();
5414
+ if (opts?.signal) {
5415
+ if (opts.signal.aborted) {
5416
+ controller.abort(opts.signal.reason);
5417
+ } else {
5418
+ opts.signal.addEventListener("abort", () => controller.abort(opts.signal.reason), { once: true });
5419
+ }
5420
+ }
5421
+ const overallTimer = setTimeout(() => controller.abort("Overall timeout"), overallTimeoutMs);
5422
+ let firstByteReceived = false;
5423
+ const firstByteTimer = setTimeout(() => {
5424
+ if (!firstByteReceived) {
5425
+ controller.abort("First byte timeout \u2014 model may be loading into VRAM");
5426
+ }
5427
+ }, FIRST_BYTE_TIMEOUT_MS);
5428
+ try {
5429
+ const body = {
5430
+ model: model2,
5431
+ prompt,
5432
+ stream: true
5433
+ };
5434
+ if (opts?.systemPrompt) body.system = opts.systemPrompt;
5435
+ if (opts?.temperature != null || opts?.maxTokens != null) {
5436
+ const options = {};
5437
+ if (opts.temperature != null) options.temperature = opts.temperature;
5438
+ if (opts.maxTokens != null) options.num_predict = opts.maxTokens;
5439
+ body.options = options;
5440
+ }
5441
+ const res = await fetch(`${baseUrl}/api/generate`, {
5442
+ method: "POST",
5443
+ headers: buildHeaders(opts?.apiKey),
5444
+ body: JSON.stringify(body),
5445
+ signal: controller.signal
5446
+ });
5447
+ if (!res.ok) {
5448
+ const errorText = await res.text().catch(() => "");
5449
+ throw new Error(`Ollama generate failed: ${res.status} ${res.statusText} \u2014 ${errorText}`);
5450
+ }
5451
+ if (!res.body) {
5452
+ throw new Error("Ollama generate: response body is null (streaming not supported?)");
5453
+ }
5454
+ const reader = res.body.getReader();
5455
+ let fullText = "";
5456
+ let promptTokens = 0;
5457
+ let completionTokens = 0;
5458
+ await parseNdjsonStream(
5459
+ reader,
5460
+ (chunk) => {
5461
+ if (!firstByteReceived) {
5462
+ firstByteReceived = true;
5463
+ clearTimeout(firstByteTimer);
5464
+ }
5465
+ if (chunk.response) {
5466
+ fullText += chunk.response;
5467
+ opts?.onStream?.(chunk.response);
5468
+ }
5469
+ if (chunk.done) {
5470
+ promptTokens = chunk.prompt_eval_count ?? 0;
5471
+ completionTokens = chunk.eval_count ?? 0;
5472
+ }
5473
+ },
5474
+ controller.signal
5475
+ );
5476
+ return {
5477
+ text: fullText,
5478
+ usage: { promptTokens, completionTokens }
5479
+ };
5480
+ } finally {
5481
+ clearTimeout(overallTimer);
5482
+ clearTimeout(firstByteTimer);
5483
+ }
5484
+ }
5485
+ async function chat(baseUrl, model2, messages, opts) {
5486
+ const overallTimeoutMs = opts?.timeoutMs ?? DEFAULT_GENERATE_TIMEOUT_MS;
5487
+ const controller = new AbortController();
5488
+ if (opts?.signal) {
5489
+ if (opts.signal.aborted) {
5490
+ controller.abort(opts.signal.reason);
5491
+ } else {
5492
+ opts.signal.addEventListener("abort", () => controller.abort(opts.signal.reason), { once: true });
5493
+ }
5494
+ }
5495
+ const overallTimer = setTimeout(() => controller.abort("Overall timeout"), overallTimeoutMs);
5496
+ let firstByteReceived = false;
5497
+ const firstByteTimer = setTimeout(() => {
5498
+ if (!firstByteReceived) {
5499
+ controller.abort("First byte timeout \u2014 model may be loading into VRAM");
5500
+ }
5501
+ }, FIRST_BYTE_TIMEOUT_MS);
5502
+ try {
5503
+ const body = {
5504
+ model: model2,
5505
+ messages,
5506
+ stream: true
5507
+ };
5508
+ if (opts?.temperature != null || opts?.maxTokens != null) {
5509
+ const options = {};
5510
+ if (opts.temperature != null) options.temperature = opts.temperature;
5511
+ if (opts.maxTokens != null) options.num_predict = opts.maxTokens;
5512
+ body.options = options;
5513
+ }
5514
+ const res = await fetch(`${baseUrl}/api/chat`, {
5515
+ method: "POST",
5516
+ headers: buildHeaders(opts?.apiKey),
5517
+ body: JSON.stringify(body),
5518
+ signal: controller.signal
5519
+ });
5520
+ if (!res.ok) {
5521
+ const errorText = await res.text().catch(() => "");
5522
+ throw new Error(`Ollama chat failed: ${res.status} ${res.statusText} \u2014 ${errorText}`);
5523
+ }
5524
+ if (!res.body) {
5525
+ throw new Error("Ollama chat: response body is null (streaming not supported?)");
5526
+ }
5527
+ const reader = res.body.getReader();
5528
+ let fullText = "";
5529
+ let promptTokens = 0;
5530
+ let completionTokens = 0;
5531
+ await parseNdjsonStream(
5532
+ reader,
5533
+ (chunk) => {
5534
+ if (!firstByteReceived) {
5535
+ firstByteReceived = true;
5536
+ clearTimeout(firstByteTimer);
5537
+ }
5538
+ if (chunk.message?.content) {
5539
+ fullText += chunk.message.content;
5540
+ opts?.onStream?.(chunk.message.content);
5541
+ }
5542
+ if (chunk.done) {
5543
+ promptTokens = chunk.prompt_eval_count ?? 0;
5544
+ completionTokens = chunk.eval_count ?? 0;
5545
+ }
5546
+ },
5547
+ controller.signal
5548
+ );
5549
+ return {
5550
+ text: fullText,
5551
+ usage: { promptTokens, completionTokens }
5552
+ };
5553
+ } finally {
5554
+ clearTimeout(overallTimer);
5555
+ clearTimeout(firstByteTimer);
5556
+ }
5557
+ }
5558
+ var DEFAULT_TIMEOUT_MS, DEFAULT_GENERATE_TIMEOUT_MS, FIRST_BYTE_TIMEOUT_MS;
5559
+ var init_client = __esm({
5560
+ "src/services/ollama/client.ts"() {
5561
+ "use strict";
5562
+ DEFAULT_TIMEOUT_MS = 1e4;
5563
+ DEFAULT_GENERATE_TIMEOUT_MS = 12e4;
5564
+ FIRST_BYTE_TIMEOUT_MS = 6e4;
5565
+ }
5566
+ });
5567
+
5568
+ // src/services/ollama/store.ts
5569
+ var store_exports6 = {};
5570
+ __export(store_exports6, {
5571
+ addServer: () => addServer,
5572
+ getAvailableModels: () => getAvailableModels,
5573
+ getBaseUrl: () => getBaseUrl,
5574
+ getModelByName: () => getModelByName,
5575
+ getServer: () => getServer,
5576
+ getServerById: () => getServerById,
5577
+ listModels: () => listModels2,
5578
+ listServers: () => listServers,
5579
+ removeServer: () => removeServer,
5580
+ removeStaleModels: () => removeStaleModels,
5581
+ updateModelContextWindow: () => updateModelContextWindow,
5582
+ updateModelQualityScore: () => updateModelQualityScore,
5583
+ updateServerStatus: () => updateServerStatus,
5584
+ upsertModel: () => upsertModel
5585
+ });
5586
+ function addServer(name, host, port, apiKey) {
5587
+ const db3 = getDb();
5588
+ const result = db3.prepare(`
5589
+ INSERT INTO ollama_servers (name, host, port, api_key, status)
5590
+ VALUES (?, ?, ?, ?, 'offline')
5591
+ `).run(name, host, port, apiKey ?? null);
5592
+ return {
5593
+ id: Number(result.lastInsertRowid),
5594
+ name,
5595
+ host,
5596
+ port,
5597
+ apiKey: apiKey ?? null,
5598
+ status: "offline",
5599
+ lastHealthCheck: null,
5600
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5601
+ };
5602
+ }
5603
+ function removeServer(name) {
5604
+ const result = getDb().prepare(
5605
+ "DELETE FROM ollama_servers WHERE name = ?"
5606
+ ).run(name);
5607
+ return result.changes > 0;
5608
+ }
5609
+ function getServer(name) {
5610
+ const row = getDb().prepare(
5611
+ "SELECT id, name, host, port, api_key, status, last_health_check, created_at FROM ollama_servers WHERE name = ?"
5612
+ ).get(name);
5613
+ return row ? mapServerRow(row) : void 0;
5614
+ }
5615
+ function getServerById(id) {
5616
+ const row = getDb().prepare(
5617
+ "SELECT id, name, host, port, api_key, status, last_health_check, created_at FROM ollama_servers WHERE id = ?"
5618
+ ).get(id);
5619
+ return row ? mapServerRow(row) : void 0;
5620
+ }
5621
+ function listServers() {
5622
+ const rows = getDb().prepare(
5623
+ "SELECT id, name, host, port, api_key, status, last_health_check, created_at FROM ollama_servers ORDER BY name"
5624
+ ).all();
5625
+ return rows.map(mapServerRow);
5626
+ }
5627
+ function updateServerStatus(serverId, status) {
5628
+ getDb().prepare(
5629
+ "UPDATE ollama_servers SET status = ?, last_health_check = datetime('now') WHERE id = ?"
5630
+ ).run(status, serverId);
5631
+ }
5632
+ function getBaseUrl(server) {
5633
+ return `http://${server.host}:${server.port}`;
5634
+ }
5635
+ function upsertModel(serverId, model2) {
5636
+ getDb().prepare(`
5637
+ INSERT INTO ollama_models (server_id, name, family, parameter_size, quantization, context_window, size_bytes, digest, last_seen)
5638
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
5639
+ ON CONFLICT(server_id, name) DO UPDATE SET
5640
+ family = excluded.family,
5641
+ parameter_size = excluded.parameter_size,
5642
+ quantization = excluded.quantization,
5643
+ context_window = COALESCE(excluded.context_window, context_window),
5644
+ size_bytes = excluded.size_bytes,
5645
+ digest = excluded.digest,
5646
+ last_seen = datetime('now')
5647
+ `).run(
5648
+ serverId,
5649
+ model2.name,
5650
+ model2.family ?? null,
5651
+ model2.parameterSize ?? null,
5652
+ model2.quantization ?? null,
5653
+ model2.contextWindow ?? 4096,
5654
+ model2.sizeBytes ?? 0,
5655
+ model2.digest ?? ""
5656
+ );
5657
+ }
5658
+ function listModels2(serverId) {
5659
+ const db3 = getDb();
5660
+ let rows;
5661
+ if (serverId != null) {
5662
+ rows = db3.prepare(
5663
+ "SELECT id, server_id, name, family, parameter_size, quantization, context_window, size_bytes, digest, last_seen, quality_score FROM ollama_models WHERE server_id = ? ORDER BY name"
5664
+ ).all(serverId);
5665
+ } else {
5666
+ rows = db3.prepare(
5667
+ "SELECT id, server_id, name, family, parameter_size, quantization, context_window, size_bytes, digest, last_seen, quality_score FROM ollama_models ORDER BY server_id, name"
5668
+ ).all();
5669
+ }
5670
+ return rows.map(mapModelRow);
5671
+ }
5672
+ function getModelByName(name, serverId) {
5673
+ const db3 = getDb();
5674
+ let row;
5675
+ if (serverId != null) {
5676
+ row = db3.prepare(
5677
+ "SELECT id, server_id, name, family, parameter_size, quantization, context_window, size_bytes, digest, last_seen, quality_score FROM ollama_models WHERE name = ? AND server_id = ?"
5678
+ ).get(name, serverId);
5679
+ } else {
5680
+ row = db3.prepare(`
5681
+ SELECT m.id, m.server_id, m.name, m.family, m.parameter_size, m.quantization, m.context_window, m.size_bytes, m.digest, m.last_seen, m.quality_score
5682
+ FROM ollama_models m
5683
+ JOIN ollama_servers s ON s.id = m.server_id
5684
+ WHERE m.name = ? AND s.status = 'online'
5685
+ ORDER BY m.quality_score DESC NULLS LAST
5686
+ LIMIT 1
5687
+ `).get(name);
5688
+ }
5689
+ return row ? mapModelRow(row) : void 0;
5690
+ }
5691
+ function updateModelQualityScore(modelId, score) {
5692
+ getDb().prepare(
5693
+ "UPDATE ollama_models SET quality_score = ? WHERE id = ?"
5694
+ ).run(score, modelId);
5695
+ }
5696
+ function updateModelContextWindow(modelId, contextWindow) {
5697
+ getDb().prepare(
5698
+ "UPDATE ollama_models SET context_window = ? WHERE id = ?"
5699
+ ).run(contextWindow, modelId);
5700
+ }
5701
+ function removeStaleModels(serverId, currentNames) {
5702
+ if (currentNames.length === 0) {
5703
+ const r2 = getDb().prepare(
5704
+ "DELETE FROM ollama_models WHERE server_id = ?"
5705
+ ).run(serverId);
5706
+ return r2.changes;
5707
+ }
5708
+ const placeholders = currentNames.map(() => "?").join(", ");
5709
+ const r = getDb().prepare(
5710
+ `DELETE FROM ollama_models WHERE server_id = ? AND name NOT IN (${placeholders})`
5711
+ ).run(serverId, ...currentNames);
5712
+ return r.changes;
5713
+ }
5714
+ function getAvailableModels() {
5715
+ const rows = getDb().prepare(`
5716
+ SELECT m.id, m.server_id, m.name, m.family, m.parameter_size, m.quantization, m.context_window, m.size_bytes, m.digest, m.last_seen, m.quality_score
5717
+ FROM ollama_models m
5718
+ JOIN ollama_servers s ON s.id = m.server_id
5719
+ WHERE s.status = 'online'
5720
+ ORDER BY m.quality_score DESC NULLS LAST, m.size_bytes DESC
5721
+ `).all();
5722
+ return rows.map(mapModelRow);
5723
+ }
5724
+ function mapServerRow(row) {
5725
+ return {
5726
+ id: row.id,
5727
+ name: row.name,
5728
+ host: row.host,
5729
+ port: row.port,
5730
+ apiKey: row.api_key,
5731
+ status: row.status,
5732
+ lastHealthCheck: row.last_health_check,
5733
+ createdAt: row.created_at
5734
+ };
5735
+ }
5736
+ function mapModelRow(row) {
5737
+ return {
5738
+ id: row.id,
5739
+ serverId: row.server_id,
5740
+ name: row.name,
5741
+ family: row.family,
5742
+ parameterSize: row.parameter_size,
5743
+ quantization: row.quantization,
5744
+ contextWindow: row.context_window,
5745
+ sizeBytes: row.size_bytes,
5746
+ digest: row.digest,
5747
+ lastSeen: row.last_seen,
5748
+ qualityScore: row.quality_score
5749
+ };
5750
+ }
5751
+ var init_store6 = __esm({
5752
+ "src/services/ollama/store.ts"() {
5753
+ "use strict";
5754
+ init_store5();
5755
+ }
5756
+ });
5757
+
5758
+ // src/services/ollama/router.ts
5759
+ async function routeModelForTask(task, preferredModel) {
5760
+ if (preferredModel) {
5761
+ const model2 = getModelByName(preferredModel);
5762
+ if (model2) {
5763
+ const server2 = getServerById(model2.serverId);
5764
+ if (server2 && server2.status === "online") {
5765
+ return { model: model2, server: server2, reason: `User-pinned model "${preferredModel}"` };
5766
+ }
5767
+ }
5768
+ }
5769
+ const available = getAvailableModels();
5770
+ if (available.length === 0) return void 0;
5771
+ const loadedNames = await getLoadedModelNames();
5772
+ const candidates = filterByTask(available, task);
5773
+ if (candidates.length === 0) {
5774
+ if (available.length === 0) return void 0;
5775
+ const fallback = available[0];
5776
+ const server2 = getServerById(fallback.serverId);
5777
+ return server2 ? { model: fallback, server: server2, reason: "Fallback \u2014 no task-appropriate models" } : void 0;
5778
+ }
5779
+ const ranked = rankCandidates(candidates, task, loadedNames);
5780
+ const best = ranked[0];
5781
+ const server = getServerById(best.model.serverId);
5782
+ if (!server) return void 0;
5783
+ return { model: best.model, server, reason: best.reason };
5784
+ }
5785
+ function routeModelForTaskSync(task, preferredModel) {
5786
+ if (preferredModel) {
5787
+ const model2 = getModelByName(preferredModel);
5788
+ if (model2) {
5789
+ const server2 = getServerById(model2.serverId);
5790
+ if (server2 && server2.status === "online") {
5791
+ return { model: model2, server: server2, reason: `User-pinned model "${preferredModel}"` };
5792
+ }
5793
+ }
5794
+ }
5795
+ const available = getAvailableModels();
5796
+ if (available.length === 0) return void 0;
5797
+ const candidates = filterByTask(available, task);
5798
+ const pool = candidates.length > 0 ? candidates : available;
5799
+ const ranked = rankCandidates(pool, task, /* @__PURE__ */ new Set());
5800
+ const best = ranked[0];
5801
+ const server = getServerById(best.model.serverId);
5802
+ if (!server) return void 0;
5803
+ return { model: best.model, server, reason: best.reason };
5804
+ }
5805
+ async function getLoadedModelNames() {
5806
+ const loaded = /* @__PURE__ */ new Set();
5807
+ const servers = listServers().filter((s) => s.status === "online");
5808
+ for (const server of servers) {
5809
+ try {
5810
+ const baseUrl = getBaseUrl(server);
5811
+ const ps = await runningModels(baseUrl, {
5812
+ apiKey: server.apiKey
5813
+ });
5814
+ for (const m of ps.models) {
5815
+ loaded.add(m.name);
5816
+ }
5817
+ } catch {
5818
+ }
5819
+ }
5820
+ if (loaded.size > 0) {
5821
+ log(`[ollama/router] Loaded models: ${[...loaded].join(", ")}`);
5822
+ }
5823
+ return loaded;
5824
+ }
5825
+ function filterByTask(models, task) {
5826
+ switch (task) {
5827
+ case "summarize":
5828
+ return models.filter((m) => {
5829
+ const params = parseParamSize(m.parameterSize);
5830
+ const ctxOk = (m.contextWindow ?? 4096) >= MIN_SUMMARIZE_CTX;
5831
+ const sizeOk = params === null || params <= MAX_SUMMARIZER_PARAMS_B;
5832
+ return ctxOk && sizeOk;
5833
+ });
5834
+ case "classify":
5835
+ return models.filter((m) => {
5836
+ const params = parseParamSize(m.parameterSize);
5837
+ return params === null || params <= MAX_CLASSIFY_PARAMS_B;
5838
+ });
5839
+ case "cron":
5840
+ return models.filter((m) => {
5841
+ const params = parseParamSize(m.parameterSize);
5842
+ const sizeOk = params === null || params <= MAX_SUMMARIZER_PARAMS_B;
5843
+ const qualityOk = m.qualityScore === null || m.qualityScore >= MIN_CRON_QUALITY;
5844
+ return sizeOk && qualityOk;
5845
+ });
5846
+ case "chat":
5847
+ return [...models];
5848
+ default:
5849
+ return [...models];
5850
+ }
5851
+ }
5852
+ function rankCandidates(models, task, loadedNames) {
5853
+ const ranked = models.map((model2) => {
5854
+ let score = 0;
5855
+ const reasons = [];
5856
+ if (model2.qualityScore !== null) {
5857
+ score += model2.qualityScore * 100;
5858
+ reasons.push(`quality=${model2.qualityScore.toFixed(2)}`);
5859
+ }
5860
+ if (loadedNames.has(model2.name)) {
5861
+ score += LOADED_BONUS;
5862
+ reasons.push("loaded");
5863
+ }
5864
+ switch (task) {
5865
+ case "summarize":
5866
+ score += (model2.contextWindow ?? 4096) / 1e3;
5867
+ break;
5868
+ case "chat":
5869
+ score += (parseParamSize(model2.parameterSize) ?? 0) * 2;
5870
+ break;
5871
+ case "cron":
5872
+ score += Math.max(0, 20 - (parseParamSize(model2.parameterSize) ?? 0));
5873
+ break;
5874
+ case "classify":
5875
+ score += Math.max(0, 30 - (parseParamSize(model2.parameterSize) ?? 0) * 3);
5876
+ break;
5877
+ }
5878
+ return {
5879
+ model: model2,
5880
+ score,
5881
+ reason: reasons.length > 0 ? reasons.join(", ") : "default"
5882
+ };
5883
+ });
5884
+ ranked.sort((a, b) => b.score - a.score);
5885
+ return ranked;
5886
+ }
5887
+ function parseParamSize(size) {
5888
+ if (!size) return null;
5889
+ const match = size.match(/([\d.]+)\s*[Bb]/);
5890
+ if (!match) return null;
5891
+ return parseFloat(match[1]);
5892
+ }
5893
+ var MAX_SUMMARIZER_PARAMS_B, MAX_CLASSIFY_PARAMS_B, MIN_SUMMARIZE_CTX, MIN_CRON_QUALITY, LOADED_BONUS;
5894
+ var init_router = __esm({
5895
+ "src/services/ollama/router.ts"() {
5896
+ "use strict";
5897
+ init_store6();
5898
+ init_client();
5899
+ init_log();
5900
+ MAX_SUMMARIZER_PARAMS_B = 14;
5901
+ MAX_CLASSIFY_PARAMS_B = 9;
5902
+ MIN_SUMMARIZE_CTX = 4096;
5903
+ MIN_CRON_QUALITY = 0.7;
5904
+ LOADED_BONUS = 100;
5905
+ }
5906
+ });
5907
+
5908
+ // src/services/ollama/quality-gate.ts
5909
+ var quality_gate_exports = {};
5910
+ __export(quality_gate_exports, {
5911
+ ensureQualityScore: () => ensureQualityScore,
5912
+ runQualityGate: () => runQualityGate
5913
+ });
5914
+ async function runQualityGate(model2, server) {
5915
+ const baseUrl = getBaseUrl(server);
5916
+ const startTime = Date.now();
5917
+ try {
5918
+ const result = await generate(baseUrl, model2.name, TEST_PROMPT, {
5919
+ timeoutMs: TEST_TIMEOUT_MS,
5920
+ temperature: 0.3,
5921
+ // Low temp for deterministic test
5922
+ maxTokens: 300,
5923
+ // Short response expected
5924
+ apiKey: server.apiKey
5925
+ });
5926
+ const responseTimeMs = Date.now() - startTime;
5927
+ const response = result.text.trim();
5928
+ if (response.length < MIN_RESPONSE_LENGTH) {
5929
+ const score = 0.1;
5930
+ updateModelQualityScore(model2.id, score);
5931
+ return {
5932
+ passed: false,
5933
+ score,
5934
+ responseLength: response.length,
5935
+ keywordsFound: [],
5936
+ keywordsMissed: EXPECTED_KEYWORDS,
5937
+ responseTimeMs,
5938
+ hasRepetition: false,
5939
+ error: `Response too short (${response.length} chars, min ${MIN_RESPONSE_LENGTH})`
5940
+ };
5941
+ }
5942
+ const lowerResponse = response.toLowerCase();
5943
+ const found = EXPECTED_KEYWORDS.filter((k) => lowerResponse.includes(k));
5944
+ const missed = EXPECTED_KEYWORDS.filter((k) => !lowerResponse.includes(k));
5945
+ const keywordScore = found.length / EXPECTED_KEYWORDS.length * 0.5;
5946
+ const timeScore = responseTimeMs < 1e4 ? 0.3 : Math.max(0, 0.3 * (1 - (responseTimeMs - 1e4) / 2e4));
5947
+ const hasRepetition = detectRepetition(response);
5948
+ const repetitionScore = hasRepetition ? 0 : 0.2;
5949
+ const totalScore = parseFloat((keywordScore + timeScore + repetitionScore).toFixed(3));
5950
+ const passed = found.length >= 2 && response.length >= MIN_RESPONSE_LENGTH && !hasRepetition;
5951
+ updateModelQualityScore(model2.id, totalScore);
5952
+ log(`[ollama/quality] ${model2.name}: score=${totalScore} (keywords=${found.length}/${EXPECTED_KEYWORDS.length}, time=${responseTimeMs}ms, repetition=${hasRepetition})`);
5953
+ return {
5954
+ passed,
5955
+ score: totalScore,
5956
+ responseLength: response.length,
5957
+ keywordsFound: found,
5958
+ keywordsMissed: missed,
5959
+ responseTimeMs,
5960
+ hasRepetition
5961
+ };
5962
+ } catch (err) {
5963
+ const errorMsg = err instanceof Error ? err.message : String(err);
5964
+ warn(`[ollama/quality] ${model2.name} test failed: ${errorMsg}`);
5965
+ updateModelQualityScore(model2.id, 0);
5966
+ return {
5967
+ passed: false,
5968
+ score: 0,
5969
+ responseLength: 0,
5970
+ keywordsFound: [],
5971
+ keywordsMissed: EXPECTED_KEYWORDS,
5972
+ responseTimeMs: Date.now() - startTime,
5973
+ hasRepetition: false,
5974
+ error: errorMsg
5975
+ };
5976
+ }
5977
+ }
5978
+ async function ensureQualityScore(model2, server) {
5979
+ if (model2.qualityScore !== null) return model2.qualityScore;
5980
+ const result = await runQualityGate(model2, server);
5981
+ return result.score;
5982
+ }
5983
+ function detectRepetition(text) {
5984
+ const tail = text.slice(-200);
5985
+ for (let len = 6; len <= 50; len++) {
5986
+ const pattern = tail.slice(-len);
5987
+ if (!pattern.trim()) continue;
5988
+ let count = 0;
5989
+ let pos = 0;
5990
+ while (pos < tail.length) {
5991
+ const idx = tail.indexOf(pattern, pos);
5992
+ if (idx === -1) break;
5993
+ count++;
5994
+ pos = idx + 1;
5995
+ }
5996
+ if (count >= 3) return true;
5997
+ }
5998
+ return false;
5999
+ }
6000
+ var TEST_PROMPT, EXPECTED_KEYWORDS, TEST_TIMEOUT_MS, MIN_RESPONSE_LENGTH;
6001
+ var init_quality_gate = __esm({
6002
+ "src/services/ollama/quality-gate.ts"() {
6003
+ "use strict";
6004
+ init_client();
6005
+ init_store6();
6006
+ init_log();
6007
+ TEST_PROMPT = `Summarize the following text in 2-3 sentences:
6008
+
6009
+ The James Webb Space Telescope (JWST) launched in December 2021 and has revolutionized astronomy by capturing infrared images of the universe with unprecedented clarity. Its primary mirror, spanning 6.5 meters, is significantly larger than Hubble's 2.4-meter mirror, allowing it to observe galaxies formed shortly after the Big Bang. JWST operates at the second Lagrange point (L2), approximately 1.5 million kilometers from Earth, where it maintains a stable thermal environment shielded from solar radiation. Early discoveries include detailed atmospheric analysis of exoplanets, revealing the presence of water vapor and carbon dioxide in their atmospheres. The telescope has also identified some of the most distant galaxies ever observed, challenging existing models of galaxy formation in the early universe.`;
6010
+ EXPECTED_KEYWORDS = ["webb", "telescope", "infrared", "galaxies"];
6011
+ TEST_TIMEOUT_MS = 3e4;
6012
+ MIN_RESPONSE_LENGTH = 80;
6013
+ }
6014
+ });
6015
+
6016
+ // src/services/ollama/service.ts
6017
+ var service_exports = {};
6018
+ __export(service_exports, {
6019
+ addServer: () => addServer2,
6020
+ discoverModels: () => discoverModels,
6021
+ getLoadedModels: () => getLoadedModels,
6022
+ getModelForTask: () => getModelForTask,
6023
+ getModelForTaskAsync: () => getModelForTaskAsync,
6024
+ getServer: () => getServer2,
6025
+ getServerForModel: () => getServerForModel,
6026
+ healthCheck: () => healthCheck,
6027
+ isAnyServerOnline: () => isAnyServerOnline,
6028
+ listServers: () => listServers2,
6029
+ removeServer: () => removeServer2,
6030
+ runQualityGateTest: () => runQualityGateTest
6031
+ });
6032
+ function addServer2(name, host, port = 11434, apiKey) {
6033
+ const existing = getServer(name);
6034
+ if (existing) throw new Error(`Server "${name}" already exists`);
6035
+ const server = addServer(name, host, port, apiKey);
6036
+ log(`[ollama] Added server: ${name} (${host}:${port})`);
6037
+ return server;
6038
+ }
6039
+ function removeServer2(name) {
6040
+ const removed = removeServer(name);
6041
+ if (removed) log(`[ollama] Removed server: ${name}`);
6042
+ return removed;
6043
+ }
6044
+ function listServers2() {
6045
+ return listServers();
6046
+ }
6047
+ function getServer2(name) {
6048
+ return getServer(name);
6049
+ }
6050
+ async function healthCheck(serverName) {
6051
+ const servers = serverName ? [getServer(serverName)].filter(Boolean) : listServers();
6052
+ if (servers.length === 0) {
6053
+ warn("[ollama] No servers registered");
6054
+ return [];
6055
+ }
6056
+ const results = [];
6057
+ for (const server of servers) {
6058
+ const baseUrl = getBaseUrl(server);
6059
+ const ok = await ping(baseUrl, {
6060
+ timeoutMs: HEALTH_PING_TIMEOUT_MS,
6061
+ apiKey: server.apiKey
6062
+ });
6063
+ const newStatus = ok ? "online" : "offline";
6064
+ updateServerStatus(server.id, newStatus);
6065
+ results.push({ ...server, status: newStatus });
6066
+ if (newStatus !== server.status) {
6067
+ log(`[ollama] Server ${server.name}: ${server.status} \u2192 ${newStatus}`);
6068
+ }
6069
+ }
6070
+ return results;
6071
+ }
6072
+ async function isAnyServerOnline() {
6073
+ const servers = listServers();
6074
+ if (servers.length === 0) return false;
6075
+ for (const server of servers) {
6076
+ const baseUrl = getBaseUrl(server);
6077
+ const ok = await ping(baseUrl, {
6078
+ timeoutMs: 2e3,
6079
+ apiKey: server.apiKey
6080
+ });
6081
+ if (ok) {
6082
+ if (server.status !== "online") {
6083
+ updateServerStatus(server.id, "online");
6084
+ }
6085
+ return true;
6086
+ }
6087
+ }
6088
+ return false;
6089
+ }
6090
+ async function discoverModels(serverName) {
6091
+ const servers = serverName ? [getServer(serverName)].filter(Boolean) : listServers();
6092
+ const allDiscovered = [];
6093
+ for (const server of servers) {
6094
+ const baseUrl = getBaseUrl(server);
6095
+ try {
6096
+ const tags = await listModels(baseUrl, { apiKey: server.apiKey });
6097
+ const modelNames = [];
6098
+ for (const m of tags.models) {
6099
+ modelNames.push(m.name);
6100
+ let contextWindow = 4096;
6101
+ try {
6102
+ const showData = await showModel(baseUrl, m.name, { apiKey: server.apiKey });
6103
+ contextWindow = extractContextWindow(showData);
6104
+ } catch {
6105
+ }
6106
+ upsertModel(server.id, {
6107
+ name: m.name,
6108
+ family: m.details.family ?? null,
6109
+ parameterSize: m.details.parameter_size ?? null,
6110
+ quantization: m.details.quantization_level ?? null,
6111
+ contextWindow,
6112
+ sizeBytes: m.size,
6113
+ digest: m.digest
6114
+ });
6115
+ }
6116
+ const staleCount = removeStaleModels(server.id, modelNames);
6117
+ if (staleCount > 0) {
6118
+ log(`[ollama] Removed ${staleCount} stale model(s) from ${server.name}`);
6119
+ }
6120
+ updateServerStatus(server.id, "online");
6121
+ log(`[ollama] Discovered ${modelNames.length} model(s) on ${server.name}: ${modelNames.join(", ")}`);
6122
+ } catch (err) {
6123
+ error(`[ollama] Failed to discover models on ${server.name}:`, err);
6124
+ updateServerStatus(server.id, "offline");
6125
+ }
6126
+ }
6127
+ return getAvailableModels();
6128
+ }
6129
+ async function getLoadedModels(serverName) {
6130
+ const servers = serverName ? [getServer(serverName)].filter(Boolean) : listServers().filter((s) => s.status === "online");
6131
+ const loaded = [];
6132
+ for (const server of servers) {
6133
+ try {
6134
+ const baseUrl = getBaseUrl(server);
6135
+ const ps = await runningModels(baseUrl, { apiKey: server.apiKey });
6136
+ for (const m of ps.models) {
6137
+ loaded.push({ server: server.name, modelName: m.name, sizeVram: m.size_vram });
6138
+ }
6139
+ } catch {
6140
+ }
6141
+ }
6142
+ return loaded;
6143
+ }
6144
+ function getModelForTask(task, preferredModel) {
6145
+ return routeModelForTaskSync(task, preferredModel);
6146
+ }
6147
+ async function getModelForTaskAsync(task, preferredModel) {
6148
+ return routeModelForTask(task, preferredModel);
6149
+ }
6150
+ async function runQualityGateTest(modelName, serverName) {
6151
+ const model2 = serverName ? getModelByName(modelName, getServer(serverName)?.id) : getModelByName(modelName);
6152
+ if (!model2) return void 0;
6153
+ const server = getServerById(model2.serverId);
6154
+ if (!server || server.status !== "online") return void 0;
6155
+ const { runQualityGate: runQualityGate2 } = await Promise.resolve().then(() => (init_quality_gate(), quality_gate_exports));
6156
+ return runQualityGate2(model2, server);
6157
+ }
6158
+ function getServerForModel(modelName) {
6159
+ const model2 = getModelByName(modelName);
6160
+ if (!model2) return void 0;
6161
+ const server = getServerById(model2.serverId);
6162
+ if (!server || server.status !== "online") return void 0;
6163
+ return {
6164
+ baseUrl: getBaseUrl(server),
6165
+ apiKey: server.apiKey
6166
+ };
6167
+ }
6168
+ function extractContextWindow(show) {
6169
+ const info = show.model_info ?? {};
6170
+ for (const key of Object.keys(info)) {
6171
+ if (key.includes("context_length") || key.includes("context_window")) {
6172
+ const val = info[key];
6173
+ if (typeof val === "number" && val > 0) return val;
6174
+ }
6175
+ }
6176
+ const paramLines = (show.parameters ?? "").split("\n");
6177
+ for (const line of paramLines) {
6178
+ const match = line.match(/num_ctx\s+(\d+)/);
6179
+ if (match) return parseInt(match[1], 10);
6180
+ }
6181
+ return 4096;
6182
+ }
6183
+ var HEALTH_PING_TIMEOUT_MS;
6184
+ var init_service = __esm({
6185
+ "src/services/ollama/service.ts"() {
6186
+ "use strict";
6187
+ init_client();
6188
+ init_store6();
6189
+ init_router();
6190
+ init_log();
6191
+ HEALTH_PING_TIMEOUT_MS = 3e3;
6192
+ }
6193
+ });
6194
+
6195
+ // src/services/ollama/prompt.ts
6196
+ var prompt_exports = {};
6197
+ __export(prompt_exports, {
6198
+ stripForLocalModel: () => stripForLocalModel
6199
+ });
6200
+ function stripForLocalModel(prompt) {
6201
+ return prompt.replace(/\[SEND_FILE:[^\]]*\]/g, "").replace(/\[GENERATE_IMAGE:[^\]]*\]/g, "").replace(/\[REACT:[^\]]*\]/g, "").replace(/\[HISTORY_SEARCH:[^\]]*\]/g, "").replace(/You have access to the following tools:[\s\S]*?(?=\n\n)/g, "").replace(/Available tools:[\s\S]*?(?=\n\n)/g, "").replace(/MCP servers?:[\s\S]*?(?=\n\n)/g, "").replace(/You are CC-Claw[\s\S]*?(?=\n\n)/g, "").replace(/\n{3,}/g, "\n\n").trim();
6202
+ }
6203
+ var init_prompt = __esm({
6204
+ "src/services/ollama/prompt.ts"() {
6205
+ "use strict";
6206
+ }
6207
+ });
6208
+
6209
+ // src/services/ollama/index.ts
6210
+ var ollama_exports = {};
6211
+ __export(ollama_exports, {
6212
+ OllamaClient: () => client_exports,
6213
+ OllamaPrompt: () => prompt_exports,
6214
+ OllamaService: () => service_exports,
6215
+ OllamaStore: () => store_exports6
6216
+ });
6217
+ var init_ollama = __esm({
6218
+ "src/services/ollama/index.ts"() {
6219
+ "use strict";
6220
+ init_client();
6221
+ init_service();
6222
+ init_store6();
6223
+ init_prompt();
6224
+ }
6225
+ });
6226
+
6227
+ // src/backends/ollama.ts
6228
+ var OLLAMA_HTTP_SENTINEL, OllamaAdapter;
6229
+ var init_ollama2 = __esm({
6230
+ "src/backends/ollama.ts"() {
6231
+ "use strict";
6232
+ init_ollama();
6233
+ init_prompt();
6234
+ OLLAMA_HTTP_SENTINEL = "__ollama_http__";
6235
+ OllamaAdapter = class {
6236
+ id = "ollama";
6237
+ displayName = "Ollama (Local)";
6238
+ // Dynamically populated — starts empty and is refreshed by discoverModels()
6239
+ availableModels = {};
6240
+ pricing = {};
6241
+ contextWindow = {};
6242
+ get defaultModel() {
6243
+ try {
6244
+ const route = service_exports.getModelForTask("chat");
6245
+ return route?.model.name ?? "";
6246
+ } catch {
6247
+ return "";
6248
+ }
5235
6249
  }
5236
- getSubagentInstructions() {
5237
- return "You have native sub-agent support via the Task tool. Built-in agent types: generalPurpose (multi-step tasks), explore (fast codebase search), shell (command execution), code-reviewer. Use them for parallel work.";
6250
+ get summarizerModel() {
6251
+ try {
6252
+ const route = service_exports.getModelForTask("summarize");
6253
+ return route?.model.name ?? "";
6254
+ } catch {
6255
+ return "";
6256
+ }
6257
+ }
6258
+ // ── CLI stubs (Ollama doesn't use CLI spawning) ────────────────────
6259
+ getExecutablePath() {
6260
+ return OLLAMA_HTTP_SENTINEL;
6261
+ }
6262
+ getEnv() {
6263
+ return {};
6264
+ }
6265
+ buildSpawnConfig(_opts) {
6266
+ throw new Error(
6267
+ "Ollama uses streamDirect(), not CLI spawning. If you're seeing this, askAgent's streamDirect check was bypassed."
6268
+ );
6269
+ }
6270
+ applyThinkingConfig(_level, _model) {
6271
+ return {};
6272
+ }
6273
+ parseLine(_raw) {
6274
+ return [];
5238
6275
  }
5239
6276
  shouldKillOnResult() {
5240
6277
  return false;
5241
6278
  }
5242
- resolveModelWithThinking(model2, level) {
5243
- if (!model2 || !level || level === "auto") return model2;
5244
- const variants = THINKING_VARIANTS[model2];
5245
- if (!variants) return model2;
5246
- return variants[level] ?? model2;
6279
+ // ── Direct execution (the real path) ───────────────────────────────
6280
+ async streamDirect(prompt, model2, opts) {
6281
+ const serverInfo = service_exports.getServerForModel(model2);
6282
+ if (!serverInfo) {
6283
+ throw new Error(`No online Ollama server found hosting model "${model2}"`);
6284
+ }
6285
+ const cleanPrompt = stripForLocalModel(prompt);
6286
+ const result = await client_exports.generate(
6287
+ serverInfo.baseUrl,
6288
+ model2,
6289
+ cleanPrompt,
6290
+ {
6291
+ systemPrompt: opts?.systemPrompt,
6292
+ temperature: opts?.temperature,
6293
+ maxTokens: opts?.maxTokens,
6294
+ timeoutMs: opts?.timeoutMs,
6295
+ signal: opts?.signal,
6296
+ onStream: opts?.onStream,
6297
+ apiKey: serverInfo.apiKey
6298
+ }
6299
+ );
6300
+ return {
6301
+ text: result.text,
6302
+ usage: {
6303
+ input: result.usage.promptTokens,
6304
+ output: result.usage.completionTokens
6305
+ }
6306
+ };
6307
+ }
6308
+ /**
6309
+ * Refresh the availableModels / pricing / contextWindow maps
6310
+ * from the discovered models in the database.
6311
+ * Called after discoverModels() or on startup.
6312
+ */
6313
+ refreshModelCatalog() {
6314
+ for (const key of Object.keys(this.availableModels)) delete this.availableModels[key];
6315
+ for (const key of Object.keys(this.pricing)) delete this.pricing[key];
6316
+ for (const key of Object.keys(this.contextWindow)) delete this.contextWindow[key];
6317
+ const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
6318
+ const models = OllamaStore.getAvailableModels();
6319
+ for (const m of models) {
6320
+ this.availableModels[m.name] = {
6321
+ label: `${m.name}${m.parameterSize ? ` (${m.parameterSize})` : ""}`,
6322
+ thinking: "none"
6323
+ };
6324
+ this.pricing[m.name] = { in: 0, out: 0, cache: 0 };
6325
+ this.contextWindow[m.name] = m.contextWindow ?? 4096;
6326
+ }
5247
6327
  }
5248
6328
  };
5249
6329
  }
@@ -5261,7 +6341,8 @@ var init_types = __esm({
5261
6341
  CLAUDE: "claude",
5262
6342
  GEMINI: "gemini",
5263
6343
  CODEX: "codex",
5264
- CURSOR: "cursor"
6344
+ CURSOR: "cursor",
6345
+ OLLAMA: "ollama"
5265
6346
  };
5266
6347
  }
5267
6348
  });
@@ -5276,14 +6357,17 @@ __export(backends_exports, {
5276
6357
  getAllBackendIds: () => getAllBackendIds,
5277
6358
  getAvailableAdapters: () => getAvailableAdapters,
5278
6359
  getAvailableBackendIds: () => getAvailableBackendIds,
6360
+ getAvailableChatBackendIds: () => getAvailableChatBackendIds,
6361
+ getChatBackendIds: () => getChatBackendIds,
5279
6362
  isBackendId: () => isBackendId,
5280
6363
  probeBackendAvailability: () => probeBackendAvailability
5281
6364
  });
5282
6365
  import { existsSync as existsSync6 } from "fs";
5283
6366
  import { execSync as execSync2 } from "child_process";
5284
- function probeBackendAvailability() {
6367
+ async function probeBackendAvailability() {
5285
6368
  availableSet.clear();
5286
6369
  for (const [id, adapter] of Object.entries(adapters)) {
6370
+ if (id === "ollama") continue;
5287
6371
  try {
5288
6372
  const exe = adapter.getExecutablePath();
5289
6373
  if (existsSync6(exe) || resolveOnPath(exe)) {
@@ -5292,6 +6376,14 @@ function probeBackendAvailability() {
5292
6376
  } catch {
5293
6377
  }
5294
6378
  }
6379
+ try {
6380
+ const { OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
6381
+ const online = await OllamaService.isAnyServerOnline();
6382
+ if (online) {
6383
+ availableSet.add("ollama");
6384
+ }
6385
+ } catch {
6386
+ }
5295
6387
  log(`[backends] Available: ${[...availableSet].join(", ") || "none"}`);
5296
6388
  }
5297
6389
  function resolveOnPath(name) {
@@ -5320,6 +6412,9 @@ function getAllAdapters() {
5320
6412
  function getAllBackendIds() {
5321
6413
  return Object.keys(adapters);
5322
6414
  }
6415
+ function getChatBackendIds() {
6416
+ return CHAT_BACKEND_IDS;
6417
+ }
5323
6418
  function getAvailableAdapters() {
5324
6419
  if (availableSet.size === 0) return Object.values(adapters);
5325
6420
  return Object.values(adapters).filter((a) => availableSet.has(a.id));
@@ -5328,7 +6423,10 @@ function getAvailableBackendIds() {
5328
6423
  if (availableSet.size === 0) return Object.keys(adapters);
5329
6424
  return [...availableSet];
5330
6425
  }
5331
- var adapters, availableSet;
6426
+ function getAvailableChatBackendIds() {
6427
+ return CHAT_BACKEND_IDS.filter((id) => availableSet.size === 0 || availableSet.has(id));
6428
+ }
6429
+ var adapters, CHAT_BACKEND_IDS, availableSet;
5332
6430
  var init_backends = __esm({
5333
6431
  "src/backends/index.ts"() {
5334
6432
  "use strict";
@@ -5336,6 +6434,7 @@ var init_backends = __esm({
5336
6434
  init_gemini();
5337
6435
  init_codex();
5338
6436
  init_cursor();
6437
+ init_ollama2();
5339
6438
  init_store5();
5340
6439
  init_log();
5341
6440
  init_types();
@@ -5343,8 +6442,10 @@ var init_backends = __esm({
5343
6442
  claude: new ClaudeAdapter(),
5344
6443
  gemini: new GeminiAdapter(),
5345
6444
  codex: new CodexAdapter(),
5346
- cursor: new CursorAdapter()
6445
+ cursor: new CursorAdapter(),
6446
+ ollama: new OllamaAdapter()
5347
6447
  };
6448
+ CHAT_BACKEND_IDS = ["claude", "gemini", "codex", "cursor"];
5348
6449
  availableSet = /* @__PURE__ */ new Set();
5349
6450
  }
5350
6451
  });
@@ -6324,7 +7425,20 @@ function getTranscriptCap(model2) {
6324
7425
  if (model2 && /flash/i.test(model2)) return TRANSCRIPT_CAP_FLASH;
6325
7426
  return TRANSCRIPT_CAP_DEFAULT;
6326
7427
  }
6327
- function buildTranscript(entries) {
7428
+ function getOllamaTranscriptCap(model2) {
7429
+ try {
7430
+ const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
7431
+ const models = OllamaStore.listModels();
7432
+ const match = models.find((m) => m.name === model2);
7433
+ if (match?.contextWindow && match.contextWindow > 0) {
7434
+ return Math.min(Math.floor(match.contextWindow * 3 * 0.8), TRANSCRIPT_CAP_DEFAULT);
7435
+ }
7436
+ } catch {
7437
+ }
7438
+ return TRANSCRIPT_CAP_OLLAMA_DEFAULT;
7439
+ }
7440
+ function buildTranscript(entries, cap) {
7441
+ const transcriptCap = cap ?? getTranscriptCap();
6328
7442
  const lines = [];
6329
7443
  let totalLen = 0;
6330
7444
  for (let i = entries.length - 1; i >= 0; i--) {
@@ -6333,7 +7447,7 @@ function buildTranscript(entries) {
6333
7447
  const label2 = e.role === "user" ? "User" : "Agent";
6334
7448
  const text = e.text.length > limit ? e.text.slice(0, limit) + "\n[...truncated]" : e.text;
6335
7449
  const line = `[${label2}] ${text}`;
6336
- if (totalLen + line.length > getTranscriptCap()) {
7450
+ if (totalLen + line.length > transcriptCap) {
6337
7451
  lines.push("[...earlier messages truncated for length]");
6338
7452
  break;
6339
7453
  }
@@ -6343,6 +7457,17 @@ function buildTranscript(entries) {
6343
7457
  lines.reverse();
6344
7458
  return lines.join("\n\n");
6345
7459
  }
7460
+ function parseSummaryResult(rawText) {
7461
+ const summaryMatch = rawText.match(/SUMMARY:\s*(.+?)(?=\nKEY_DETAILS:|\nTOPICS:|$)/s);
7462
+ const keyDetailsMatch = rawText.match(/KEY_DETAILS:\s*(.+?)(?=\nTOPICS:|$)/s);
7463
+ const topicsMatch = rawText.match(/TOPICS:\s*(.+)/);
7464
+ let summary = summaryMatch?.[1]?.trim() ?? rawText.trim();
7465
+ const keyDetails = keyDetailsMatch?.[1]?.trim();
7466
+ if (keyDetails) summary += `
7467
+ Key details: ${keyDetails}`;
7468
+ const topics = topicsMatch?.[1]?.trim() ?? "";
7469
+ return { summary, topics };
7470
+ }
6346
7471
  async function attemptSummarize(chatId, adapter, model2, entries) {
6347
7472
  const transcript = buildTranscript(entries);
6348
7473
  const prompt = `${SUMMARIZE_PROMPT}
@@ -6430,14 +7555,7 @@ ${transcript}`;
6430
7555
  warn(`[summarize] ${adapter.id}:${model2} returned empty result for chat ${chatId}`);
6431
7556
  return { success: false, rawText: "" };
6432
7557
  }
6433
- const summaryMatch = resultText.match(/SUMMARY:\s*(.+?)(?=\nKEY_DETAILS:|\nTOPICS:|$)/s);
6434
- const keyDetailsMatch = resultText.match(/KEY_DETAILS:\s*(.+?)(?=\nTOPICS:|$)/s);
6435
- const topicsMatch = resultText.match(/TOPICS:\s*(.+)/);
6436
- let summary = summaryMatch?.[1]?.trim() ?? resultText.trim();
6437
- const keyDetails = keyDetailsMatch?.[1]?.trim();
6438
- if (keyDetails) summary += `
6439
- Key details: ${keyDetails}`;
6440
- const topics = topicsMatch?.[1]?.trim() ?? "";
7558
+ const { summary, topics } = parseSummaryResult(resultText);
6441
7559
  saveSessionSummaryWithEmbedding(chatId, summary, topics, Math.floor(entries.length / 2));
6442
7560
  log(`[summarize] Saved summary via ${adapter.id}:${model2} for chat ${chatId}`);
6443
7561
  return { success: true, rawText: resultText };
@@ -6446,6 +7564,35 @@ Key details: ${keyDetails}`;
6446
7564
  return { success: false, rawText: "" };
6447
7565
  }
6448
7566
  }
7567
+ async function attemptSummarizeDirect(chatId, directFn, backendId, model2, entries, transcriptCap) {
7568
+ const transcript = buildTranscript(entries, transcriptCap);
7569
+ const prompt = `${SUMMARIZE_PROMPT}
7570
+
7571
+ Conversation:
7572
+ ${transcript}`;
7573
+ try {
7574
+ const result = await Promise.race([
7575
+ directFn(prompt),
7576
+ new Promise(
7577
+ (_, reject) => setTimeout(() => reject(new Error("Direct summarize timeout")), SUMMARIZE_TIMEOUT_MS)
7578
+ )
7579
+ ]);
7580
+ if (result.usage) {
7581
+ addUsage(chatId, result.usage.input, result.usage.output, 0, model2);
7582
+ }
7583
+ if (!result.text) {
7584
+ warn(`[summarize] ${backendId}:${model2} (direct) returned empty for ${chatId}`);
7585
+ return { success: false, rawText: "" };
7586
+ }
7587
+ const { summary, topics } = parseSummaryResult(result.text);
7588
+ saveSessionSummaryWithEmbedding(chatId, summary, topics, Math.floor(entries.length / 2));
7589
+ log(`[summarize] Saved summary via ${backendId}:${model2} (direct) for chat ${chatId}`);
7590
+ return { success: true, rawText: result.text };
7591
+ } catch (err) {
7592
+ warn(`[summarize] ${backendId}:${model2} (direct) failed for ${chatId}: ${errorMessage(err)}`);
7593
+ return { success: false, rawText: "" };
7594
+ }
7595
+ }
6449
7596
  async function extractAndLogSignals(rawText, chatId, adapterId, model2) {
6450
7597
  try {
6451
7598
  const { getReflectionStatus: getReflectionStatus2, logSignal: logSignal2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
@@ -6493,6 +7640,26 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
6493
7640
  const excluded = getAdapter(excludeBackend);
6494
7641
  tried.add(`${excluded.id}:${excluded.summarizerModel}`);
6495
7642
  }
7643
+ const globalConfig = getGlobalSummarizer();
7644
+ if (globalConfig.backend === "ollama") {
7645
+ try {
7646
+ const ollamaAdapter = getAdapter("ollama");
7647
+ if (ollamaAdapter.streamDirect) {
7648
+ const ollamaModel = globalConfig.model ?? ollamaAdapter.summarizerModel;
7649
+ const cap = getOllamaTranscriptCap(ollamaModel);
7650
+ const key = `${ollamaAdapter.id}:${ollamaModel}`;
7651
+ tried.add(key);
7652
+ const directFn = (prompt) => ollamaAdapter.streamDirect(prompt, ollamaModel);
7653
+ const result = await attemptSummarizeDirect(chatId, directFn, "ollama", ollamaModel, entries, cap);
7654
+ if (result.success) {
7655
+ await extractAndLogSignals(result.rawText, chatId, "ollama", ollamaModel);
7656
+ if (clearLogAfter) clearLog(chatId);
7657
+ return true;
7658
+ }
7659
+ }
7660
+ } catch {
7661
+ }
7662
+ }
6496
7663
  try {
6497
7664
  const config2 = getSummarizer(chatId);
6498
7665
  if (config2.backend !== "off") {
@@ -6535,7 +7702,14 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
6535
7702
  if (!tried.has(key)) {
6536
7703
  tried.add(key);
6537
7704
  fallbackCount++;
6538
- const result = await attemptSummarize(chatId, adapter, model2, entries);
7705
+ let result;
7706
+ if (adapter.streamDirect) {
7707
+ const cap = adapter.id === "ollama" ? getOllamaTranscriptCap(model2) : getTranscriptCap(model2);
7708
+ const directFn = (prompt) => adapter.streamDirect(prompt, model2);
7709
+ result = await attemptSummarizeDirect(chatId, directFn, adapter.id, model2, entries, cap);
7710
+ } else {
7711
+ result = await attemptSummarize(chatId, adapter, model2, entries);
7712
+ }
6539
7713
  if (result.success) {
6540
7714
  await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
6541
7715
  if (clearLogAfter) clearLog(chatId);
@@ -6573,7 +7747,7 @@ async function summarizeAllPending() {
6573
7747
  await summarizeSession(chatId);
6574
7748
  }
6575
7749
  }
6576
- var MIN_PAIRS, summarizationLocks, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP_DEFAULT, TRANSCRIPT_CAP_FLASH, SUMMARIZE_TIMEOUT_MS, SUMMARIZE_PROMPT, VALID_SIGNAL_TYPES;
7750
+ var MIN_PAIRS, summarizationLocks, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP_DEFAULT, TRANSCRIPT_CAP_FLASH, TRANSCRIPT_CAP_OLLAMA_DEFAULT, SUMMARIZE_TIMEOUT_MS, SUMMARIZE_PROMPT, VALID_SIGNAL_TYPES;
6577
7751
  var init_summarize = __esm({
6578
7752
  "src/memory/summarize.ts"() {
6579
7753
  "use strict";
@@ -6590,6 +7764,7 @@ var init_summarize = __esm({
6590
7764
  AGENT_MSG_LIMIT = 6e3;
6591
7765
  TRANSCRIPT_CAP_DEFAULT = 1e5;
6592
7766
  TRANSCRIPT_CAP_FLASH = 5e4;
7767
+ TRANSCRIPT_CAP_OLLAMA_DEFAULT = 15e3;
6593
7768
  SUMMARIZE_TIMEOUT_MS = 6e4;
6594
7769
  SUMMARIZE_PROMPT = `You are summarizing a conversation session for long-term episodic memory. This summary will be injected into future conversations to provide context, so it must contain enough specific detail to be useful weeks or months later.
6595
7770
 
@@ -7647,7 +8822,8 @@ function spawnAgentProcess(runner, opts, callbacks) {
7647
8822
  } catch {
7648
8823
  try {
7649
8824
  child.kill("SIGTERM");
7650
- } catch {
8825
+ } catch (killErr) {
8826
+ warn(`[spawn] Kill fallback failed for pid ${child.pid}: ${killErr}`);
7651
8827
  }
7652
8828
  }
7653
8829
  }
@@ -7673,6 +8849,7 @@ var init_spawn = __esm({
7673
8849
  "use strict";
7674
8850
  init_env();
7675
8851
  init_roles();
8852
+ init_log();
7676
8853
  SENSITIVE_ENV_KEYS = [
7677
8854
  "TELEGRAM_BOT_TOKEN",
7678
8855
  "GROQ_API_KEY",
@@ -7683,7 +8860,10 @@ var init_spawn = __esm({
7683
8860
  // Audit C15: API keys that should not leak to sub-agents
7684
8861
  "ANTHROPIC_API_KEY",
7685
8862
  "GEMINI_API_KEY",
7686
- "OPENAI_API_KEY"
8863
+ "OPENAI_API_KEY",
8864
+ // Audit C26: Cursor and Codex keys were missing — sub-agents inherited them
8865
+ "CURSOR_API_KEY",
8866
+ "CODEX_API_KEY"
7687
8867
  ];
7688
8868
  }
7689
8869
  });
@@ -11255,6 +12435,9 @@ var init_apply = __esm({
11255
12435
  });
11256
12436
 
11257
12437
  // src/dashboard/routes/evolve.ts
12438
+ function resolveChatId(body) {
12439
+ return body.chatId || (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim() || null;
12440
+ }
11258
12441
  var handleAnalyze, handleApply, handleReject, handleUndo, handleEvolveOn, handleEvolveOff, handleEvolveModel, handleEvolveSettings;
11259
12442
  var init_evolve = __esm({
11260
12443
  "src/dashboard/routes/evolve.ts"() {
@@ -11265,8 +12448,9 @@ var init_evolve = __esm({
11265
12448
  handleAnalyze = async (req, res) => {
11266
12449
  try {
11267
12450
  const body = JSON.parse(await readBody(req));
12451
+ const chatId = resolveChatId(body);
12452
+ if (!chatId) return jsonResponse(res, { error: "No chatId provided and ALLOWED_CHAT_ID is not set" }, 400);
11268
12453
  const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
11269
- const chatId = body.chatId || (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim() || "default";
11270
12454
  const insights = await runAnalysis2(chatId);
11271
12455
  jsonResponse(res, { success: true, insights: insights.length });
11272
12456
  } catch (err) {
@@ -11310,7 +12494,8 @@ var init_evolve = __esm({
11310
12494
  const { existsSync: fileExists, readFileSync: fileRead } = await import("fs");
11311
12495
  const { join: join35 } = await import("path");
11312
12496
  const { CC_CLAW_HOME: home } = await Promise.resolve().then(() => (init_paths(), paths_exports));
11313
- const chatId = body.chatId || (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim() || "default";
12497
+ const chatId = resolveChatId(body);
12498
+ if (!chatId) return jsonResponse(res, { error: "No chatId provided and ALLOWED_CHAT_ID is not set" }, 400);
11314
12499
  const soulPath = join35(home, "identity/SOUL.md");
11315
12500
  const userPath = join35(home, "identity/USER.md");
11316
12501
  const soul = fileExists(soulPath) ? fileRead(soulPath, "utf-8") : "";
@@ -11325,7 +12510,8 @@ var init_evolve = __esm({
11325
12510
  try {
11326
12511
  const body = JSON.parse(await readBody(req));
11327
12512
  const { setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
11328
- const chatId = body.chatId || (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim() || "default";
12513
+ const chatId = resolveChatId(body);
12514
+ if (!chatId) return jsonResponse(res, { error: "No chatId provided and ALLOWED_CHAT_ID is not set" }, 400);
11329
12515
  setReflectionStatus2(getDb(), chatId, "frozen");
11330
12516
  jsonResponse(res, { success: true, status: "frozen" });
11331
12517
  } catch (err) {
@@ -11336,7 +12522,8 @@ var init_evolve = __esm({
11336
12522
  try {
11337
12523
  const body = JSON.parse(await readBody(req));
11338
12524
  const { setReflectionModelConfig: setReflectionModelConfig2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
11339
- const chatId = body.chatId || (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim() || "default";
12525
+ const chatId = resolveChatId(body);
12526
+ if (!chatId) return jsonResponse(res, { error: "No chatId provided and ALLOWED_CHAT_ID is not set" }, 400);
11340
12527
  setReflectionModelConfig2(getDb(), chatId, body.mode, body.backend, body.model);
11341
12528
  jsonResponse(res, { success: true });
11342
12529
  } catch (err) {
@@ -11347,7 +12534,8 @@ var init_evolve = __esm({
11347
12534
  try {
11348
12535
  const body = JSON.parse(await readBody(req));
11349
12536
  const { setReflectionSettings: setReflectionSettings2, getReflectionSettings: getReflectionSettings2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
11350
- const chatId = body.chatId || (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim() || "default";
12537
+ const chatId = resolveChatId(body);
12538
+ if (!chatId) return jsonResponse(res, { error: "No chatId provided and ALLOWED_CHAT_ID is not set" }, 400);
11351
12539
  const updates = {};
11352
12540
  if (body.perFileCap !== void 0) updates.perFileCap = body.perFileCap;
11353
12541
  if (body.backupRetentionDays !== void 0) updates.backupRetentionDays = body.backupRetentionDays;
@@ -11808,8 +12996,10 @@ __export(agent_exports, {
11808
12996
  getInFlightMessage: () => getInFlightMessage,
11809
12997
  isChatBusy: () => isChatBusy,
11810
12998
  isSyntheticChatId: () => isSyntheticChatId,
12999
+ startStaleChatSweep: () => startStaleChatSweep,
11811
13000
  stopAgent: () => stopAgent,
11812
- stopAllActiveAgents: () => stopAllActiveAgents
13001
+ stopAllActiveAgents: () => stopAllActiveAgents,
13002
+ stopStaleChatSweep: () => stopStaleChatSweep
11813
13003
  });
11814
13004
  import { spawn as spawn6 } from "child_process";
11815
13005
  import { createInterface as createInterface5 } from "readline";
@@ -11830,6 +13020,26 @@ function killProcessGroup(proc, signal = "SIGTERM") {
11830
13020
  }
11831
13021
  }
11832
13022
  }
13023
+ function sweepStaleChatEntries() {
13024
+ for (const [chatId, state] of activeChats) {
13025
+ if (state.process && state.process.exitCode !== null) {
13026
+ warn(`[agent] Sweeping stale activeChats entry for ${chatId} (process exited with code ${state.process.exitCode})`);
13027
+ if (state.killTimer) clearTimeout(state.killTimer);
13028
+ activeChats.delete(chatId);
13029
+ }
13030
+ }
13031
+ }
13032
+ function startStaleChatSweep() {
13033
+ if (staleSweepTimer) return;
13034
+ staleSweepTimer = setInterval(sweepStaleChatEntries, 15 * 60 * 1e3);
13035
+ staleSweepTimer.unref();
13036
+ }
13037
+ function stopStaleChatSweep() {
13038
+ if (staleSweepTimer) {
13039
+ clearInterval(staleSweepTimer);
13040
+ staleSweepTimer = void 0;
13041
+ }
13042
+ }
11833
13043
  function withChatLock(chatId, fn) {
11834
13044
  const prev = chatLocks.get(chatId) ?? Promise.resolve();
11835
13045
  const isBlocked = activeChats.has(chatId);
@@ -11858,6 +13068,9 @@ function stopAgent(chatId) {
11858
13068
  const state = activeChats.get(chatId);
11859
13069
  if (!state) return false;
11860
13070
  state.cancelled = true;
13071
+ if (state.abortController) {
13072
+ state.abortController.abort();
13073
+ }
11861
13074
  if (state.process) {
11862
13075
  killProcessGroup(state.process, "SIGTERM");
11863
13076
  state.killTimer = setTimeout(() => {
@@ -12324,6 +13537,49 @@ async function askAgentImpl(chatId, userMessage, opts) {
12324
13537
  const effectiveAgentMode = optsAgentMode ?? getAgentMode(settingsChat);
12325
13538
  const sideQuestCtx = settingsSourceChatId ? { parentChatId: settingsSourceChatId, actualChatId: chatId } : void 0;
12326
13539
  const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, settingsChat, mode, responseStyle, effectiveAgentMode, sideQuestCtx, planningDirective);
13540
+ if (adapter.streamDirect) {
13541
+ const resolvedModel2 = model2 ?? adapter.defaultModel;
13542
+ const abortController = new AbortController();
13543
+ const cancelState2 = { cancelled: false, userMessage, abortController };
13544
+ activeChats.set(chatId, cancelState2);
13545
+ try {
13546
+ const sdResult = await adapter.streamDirect(fullPrompt, resolvedModel2, {
13547
+ timeoutMs: timeoutMs ?? 12e4,
13548
+ onStream,
13549
+ signal: abortController.signal
13550
+ });
13551
+ if (!isSyntheticChatId(chatId)) {
13552
+ appendToLog(chatId, userMessage, sdResult.text, adapter.id, resolvedModel2, null);
13553
+ const AUTO_SUMMARIZE_THRESHOLD = 30;
13554
+ const pairCount = tier !== "chat" ? getMessagePairCount(chatId) : 0;
13555
+ if (pairCount >= AUTO_SUMMARIZE_THRESHOLD) {
13556
+ log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
13557
+ summarizeWithFallbackChain(chatId).then((saved) => {
13558
+ if (saved) {
13559
+ clearSession(chatId);
13560
+ opts?.onCompaction?.(chatId);
13561
+ }
13562
+ }).catch((err) => {
13563
+ warn(`[agent] Auto-summarize failed for chat ${chatId}: ${err}`);
13564
+ });
13565
+ }
13566
+ }
13567
+ const sdUsage = sdResult.usage ?? { input: 0, output: 0 };
13568
+ if (sdUsage.input + sdUsage.output > 0) {
13569
+ addUsage(chatId, sdUsage.input, sdUsage.output, 0, resolvedModel2);
13570
+ }
13571
+ if (cancelState2.cancelled) {
13572
+ return { text: "Stopped.", usage: { input: sdUsage.input, output: sdUsage.output, cacheRead: 0 } };
13573
+ }
13574
+ return {
13575
+ text: sdResult.text || `(No response from ${adapter.displayName})`,
13576
+ usage: { input: sdUsage.input, output: sdUsage.output, cacheRead: 0 },
13577
+ resolvedModel: resolvedModel2
13578
+ };
13579
+ } finally {
13580
+ activeChats.delete(chatId);
13581
+ }
13582
+ }
12327
13583
  const existingSessionId = settingsSourceChatId ? null : getSessionId(settingsChat);
12328
13584
  const allowedTools = getEnabledTools(settingsChat);
12329
13585
  const mcpConfigPath = tier !== "slim" && effectiveAgentMode !== "native" && MCP_CONFIG_FLAG[adapter.id] ? getMcpConfigPath(chatId) : null;
@@ -12471,24 +13727,46 @@ async function askAgentImpl(chatId, userMessage, opts) {
12471
13727
  result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, retryOpts);
12472
13728
  }
12473
13729
  } else if (errMsg.startsWith(CONTENT_SILENCE_TIMEOUT_ERROR) && existingSessionId) {
12474
- warn(`[agent] Content silence on ${adapter.id} \u2014 clearing session ${existingSessionId} and retrying fresh`);
12475
- clearSession(chatId);
12476
- if (useGeminiRotation) {
12477
- const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
12478
- const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
12479
- result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, geminiRotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
12480
- } else if (useBackendRotation) {
12481
- const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
12482
- result = await spawnWithSlotRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, backendRotationMode, allowPaid, spawnOpts, rotationCb);
12483
- } else {
12484
- const retryOpts = (() => {
12485
- if (adapter.id !== "gemini") return spawnOpts;
12486
- const geminiAdapter = adapter;
12487
- const { env, slot } = geminiAdapter.resolveSlotEnv(chatId);
12488
- if (slot) return { ...spawnOpts, envOverride: env };
12489
- return spawnOpts;
12490
- })();
12491
- result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, retryOpts);
13730
+ warn(`[agent] Content silence on ${adapter.id} \u2014 retrying with session ${existingSessionId}`);
13731
+ try {
13732
+ if (useGeminiRotation) {
13733
+ const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
13734
+ const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
13735
+ result = await spawnGeminiWithRotation(chatId, adapter, configWithSession, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, geminiRotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
13736
+ } else if (useBackendRotation) {
13737
+ const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
13738
+ result = await spawnWithSlotRotation(chatId, adapter, configWithSession, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, backendRotationMode, allowPaid, spawnOpts, rotationCb);
13739
+ } else {
13740
+ const retryOpts = (() => {
13741
+ if (adapter.id !== "gemini") return spawnOpts;
13742
+ const geminiAdapter = adapter;
13743
+ const { env, slot } = geminiAdapter.resolveSlotEnv(chatId);
13744
+ if (slot) return { ...spawnOpts, envOverride: env };
13745
+ return spawnOpts;
13746
+ })();
13747
+ result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, retryOpts);
13748
+ }
13749
+ } catch (retryErr) {
13750
+ const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
13751
+ warn(`[agent] Content silence session retry also failed on ${adapter.id} \u2014 clearing session and retrying fresh`);
13752
+ clearSession(chatId);
13753
+ if (useGeminiRotation) {
13754
+ const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
13755
+ const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
13756
+ result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, geminiRotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
13757
+ } else if (useBackendRotation) {
13758
+ const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
13759
+ result = await spawnWithSlotRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, backendRotationMode, allowPaid, spawnOpts, rotationCb);
13760
+ } else {
13761
+ const retryOpts = (() => {
13762
+ if (adapter.id !== "gemini") return spawnOpts;
13763
+ const geminiAdapter = adapter;
13764
+ const { env, slot } = geminiAdapter.resolveSlotEnv(chatId);
13765
+ if (slot) return { ...spawnOpts, envOverride: env };
13766
+ return spawnOpts;
13767
+ })();
13768
+ result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, retryOpts);
13769
+ }
12492
13770
  }
12493
13771
  } else {
12494
13772
  if (!isSyntheticChatId(chatId)) {
@@ -12594,7 +13872,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
12594
13872
  if (!flag) return args;
12595
13873
  return [...args, ...flag, mcpConfigPath];
12596
13874
  }
12597
- var activeChats, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, CONTENT_SILENCE_TIMEOUT_MS, CONTENT_SILENCE_TIMEOUT_ERROR, FIRST_RESPONSE_TIMEOUT_ERROR, FREE_SLOTS_EXHAUSTED, GEMINI_FALLBACK_CHAIN, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
13875
+ var activeChats, staleSweepTimer, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, CONTENT_SILENCE_TIMEOUT_MS, CONTENT_SILENCE_TIMEOUT_ERROR, FIRST_RESPONSE_TIMEOUT_ERROR, FREE_SLOTS_EXHAUSTED, GEMINI_FALLBACK_CHAIN, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
12598
13876
  var init_agent = __esm({
12599
13877
  "src/agent.ts"() {
12600
13878
  "use strict";
@@ -14284,6 +15562,12 @@ var init_session_log2 = __esm({
14284
15562
  });
14285
15563
 
14286
15564
  // src/router/live-status.ts
15565
+ function canFlushGlobally() {
15566
+ return Date.now() - globalLastFlushAt >= GLOBAL_MIN_GAP_MS;
15567
+ }
15568
+ function markGlobalFlush() {
15569
+ globalLastFlushAt = Date.now();
15570
+ }
14287
15571
  function dedupThinking(entries) {
14288
15572
  const out = [];
14289
15573
  for (const e of entries) {
@@ -14327,7 +15611,7 @@ function makeLiveStatus(chatId, channel, modelLabel, verboseLevel, showThinking)
14327
15611
  };
14328
15612
  return { liveStatus, toolCb };
14329
15613
  }
14330
- var FLUSH_INTERVAL_DM_MS, FLUSH_INTERVAL_GROUP_MS, MAX_THINKING_CHARS, TRIM_THRESHOLD, MAX_ENTRIES, LiveStatusMessage;
15614
+ var FLUSH_INTERVAL_DM_MS, FLUSH_INTERVAL_GROUP_MS, MAX_THINKING_CHARS, GLOBAL_MIN_GAP_MS, globalLastFlushAt, TRIM_THRESHOLD, MAX_ENTRIES, LiveStatusMessage;
14331
15615
  var init_live_status = __esm({
14332
15616
  "src/router/live-status.ts"() {
14333
15617
  "use strict";
@@ -14336,6 +15620,8 @@ var init_live_status = __esm({
14336
15620
  FLUSH_INTERVAL_DM_MS = 1e3;
14337
15621
  FLUSH_INTERVAL_GROUP_MS = 3e3;
14338
15622
  MAX_THINKING_CHARS = 800;
15623
+ GLOBAL_MIN_GAP_MS = 500;
15624
+ globalLastFlushAt = 0;
14339
15625
  TRIM_THRESHOLD = 3500;
14340
15626
  MAX_ENTRIES = 200;
14341
15627
  LiveStatusMessage = class {
@@ -14435,10 +15721,12 @@ var init_live_status = __esm({
14435
15721
  async flush() {
14436
15722
  if (this.finalized || !this.messageId || !this.channel.editText) return;
14437
15723
  if (Date.now() < this.nextFlushAllowedAt) return;
15724
+ if (!canFlushGlobally()) return;
14438
15725
  const deduped = dedupThinking(this.entries);
14439
15726
  const body = renderEntries(deduped, this.modelLabel, Date.now() - this.startTime, this.hasTrimmed);
14440
15727
  if (body === this.lastRendered) return;
14441
15728
  this.lastRendered = body;
15729
+ markGlobalFlush();
14442
15730
  try {
14443
15731
  await this.channel.editText(this.chatId, this.messageId, body, "plain");
14444
15732
  } catch (err) {
@@ -16101,10 +17389,21 @@ function assembleHeartbeatPrompt(chatId) {
16101
17389
  if (failedSinceLastBeat.length > 0) {
16102
17390
  healthLines.push(`${failedSinceLastBeat.length} job run(s) failed recently.`);
16103
17391
  }
16104
- for (const backend2 of ["claude", "gemini", "codex", "cursor"]) {
17392
+ for (const backend2 of getAllBackendIds()) {
16105
17393
  const limitMsg = checkBackendLimits(backend2);
16106
17394
  if (limitMsg) healthLines.push(limitMsg);
16107
17395
  }
17396
+ try {
17397
+ const { OllamaService } = (init_ollama(), __toCommonJS(ollama_exports));
17398
+ const servers = OllamaService.listServers();
17399
+ if (servers.length > 0) {
17400
+ const offline = servers.filter((s) => s.status === "offline");
17401
+ if (offline.length > 0) {
17402
+ healthLines.push(`Ollama: ${offline.length}/${servers.length} server(s) offline: ${offline.map((s) => s.name).join(", ")}`);
17403
+ }
17404
+ }
17405
+ } catch {
17406
+ }
16108
17407
  if (healthLines.length > 0) {
16109
17408
  sections.push(`[System health]
16110
17409
  ${healthLines.join("\n")}`);
@@ -17487,13 +18786,13 @@ async function handleEvolveCallback(chatId, data, channel) {
17487
18786
  const { getReflectionStatus: getReflectionStatus2, setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
17488
18787
  const current = getReflectionStatus2(getDb(), chatId);
17489
18788
  if (current === "frozen") {
17490
- const { readFileSync: readFileSync27, existsSync: existsSync55 } = await import("fs");
18789
+ const { readFileSync: readFileSync27, existsSync: existsSync56 } = await import("fs");
17491
18790
  const { join: join35 } = await import("path");
17492
18791
  const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
17493
18792
  const soulPath = join35(CC_CLAW_HOME3, "identity/SOUL.md");
17494
18793
  const userPath = join35(CC_CLAW_HOME3, "identity/USER.md");
17495
- const soul = existsSync55(soulPath) ? readFileSync27(soulPath, "utf-8") : "";
17496
- const user = existsSync55(userPath) ? readFileSync27(userPath, "utf-8") : "";
18794
+ const soul = existsSync56(soulPath) ? readFileSync27(soulPath, "utf-8") : "";
18795
+ const user = existsSync56(userPath) ? readFileSync27(userPath, "utf-8") : "";
17497
18796
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
17498
18797
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
17499
18798
  logActivity2(getDb(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
@@ -20393,6 +21692,278 @@ var init_command_handlers = __esm({
20393
21692
  }
20394
21693
  });
20395
21694
 
21695
+ // src/router/ollama.ts
21696
+ var ollama_exports2 = {};
21697
+ __export(ollama_exports2, {
21698
+ handleOllamaCallback: () => handleOllamaCallback,
21699
+ handleOllamaCommand: () => handleOllamaCommand
21700
+ });
21701
+ async function handleOllamaCommand(chatId, commandArgs, channel) {
21702
+ const [sub, ...rest] = (commandArgs || "").trim().split(/\s+/);
21703
+ switch (sub) {
21704
+ case "models":
21705
+ return sendModelList(chatId, channel, rest[0]);
21706
+ case "health":
21707
+ return sendHealthCheck(chatId, channel);
21708
+ case "discover":
21709
+ return sendDiscover(chatId, channel, rest[0]);
21710
+ case "add":
21711
+ if (rest.length >= 2) return handleAdd(chatId, channel, rest[0], rest[1], rest[2] ? parseInt(rest[2], 10) : void 0);
21712
+ if (rest.length === 1) return handleAdd(chatId, channel, rest[0], rest[0]);
21713
+ await channel.sendText(chatId, "Usage: /ollama add <name> <host> [port]", { parseMode: "plain" });
21714
+ return;
21715
+ case "remove":
21716
+ if (rest[0]) return sendRemoveConfirm(chatId, channel, rest[0]);
21717
+ await channel.sendText(chatId, "Usage: /ollama remove <server-name>", { parseMode: "plain" });
21718
+ return;
21719
+ default:
21720
+ return sendOllamaDashboard(chatId, channel);
21721
+ }
21722
+ }
21723
+ async function sendOllamaDashboard(chatId, channel) {
21724
+ const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
21725
+ const servers = OllamaStore.listServers();
21726
+ if (servers.length === 0) {
21727
+ const text = [
21728
+ "<b>\u{1F999} Ollama</b>",
21729
+ "",
21730
+ "No servers configured.",
21731
+ "",
21732
+ "Add your first server:",
21733
+ " <code>/ollama add &lt;name&gt; &lt;host&gt; [port]</code>",
21734
+ "",
21735
+ "Example:",
21736
+ " <code>/ollama add local 192.168.1.100</code>"
21737
+ ].join("\n");
21738
+ if (typeof channel.sendKeyboard === "function") {
21739
+ await channel.sendKeyboard(chatId, text, [
21740
+ [{ label: "\u{1F4D6} Help", data: "ollama:help" }]
21741
+ ]);
21742
+ } else {
21743
+ await channel.sendText(chatId, text, { parseMode: "html" });
21744
+ }
21745
+ return;
21746
+ }
21747
+ const lines = ["<b>\u{1F999} Ollama Servers</b>", ""];
21748
+ for (const s of servers) {
21749
+ const dot = s.status === "online" ? "\u{1F7E2}" : "\u{1F534}";
21750
+ const modelCount = OllamaStore.listModels(s.id).length;
21751
+ lines.push(`${dot} <b>${s.name}</b> <code>${s.host}:${s.port}</code>`);
21752
+ lines.push(` ${modelCount} model${modelCount !== 1 ? "s" : ""} \xB7 ${s.status}`);
21753
+ }
21754
+ const onlineCount = servers.filter((s) => s.status === "online").length;
21755
+ lines.push("", `${onlineCount}/${servers.length} online`);
21756
+ if (typeof channel.sendKeyboard === "function") {
21757
+ const buttons = [
21758
+ [
21759
+ { label: "\u{1F50D} Discover", data: "ollama:discover" },
21760
+ { label: "\u{1F49A} Health", data: "ollama:health", style: "success" }
21761
+ ],
21762
+ [
21763
+ { label: "\u{1F4CB} Models", data: "ollama:models" },
21764
+ { label: "\u2795 Add", data: "ollama:add_prompt" }
21765
+ ]
21766
+ ];
21767
+ if (servers.length <= 4) {
21768
+ const removeRow = servers.map((s) => ({
21769
+ label: `\u{1F5D1} ${s.name}`,
21770
+ data: `ollama:remove_confirm:${s.name}`,
21771
+ style: "danger"
21772
+ }));
21773
+ buttons.push(removeRow);
21774
+ }
21775
+ await channel.sendKeyboard(chatId, lines.join("\n"), buttons);
21776
+ } else {
21777
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "html" });
21778
+ }
21779
+ }
21780
+ async function handleAdd(chatId, channel, name, host, port) {
21781
+ const { OllamaStore, OllamaClient, OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
21782
+ const existing = OllamaStore.getServer(name);
21783
+ if (existing) {
21784
+ await channel.sendText(chatId, `Server "${name}" already exists. Remove it first.`, { parseMode: "plain" });
21785
+ return;
21786
+ }
21787
+ const actualPort = port ?? 11434;
21788
+ await channel.sendText(chatId, `Adding ${name} (${host}:${actualPort})...`, { parseMode: "plain" });
21789
+ const online = await OllamaClient.ping(`http://${host}:${actualPort}`, { timeoutMs: 5e3 });
21790
+ const server = OllamaStore.addServer(name, host, actualPort, null);
21791
+ OllamaStore.updateServerStatus(server.id, online ? "online" : "offline");
21792
+ if (!online) {
21793
+ await channel.sendText(chatId, `\u26A0\uFE0F Server "${name}" added but not responding. Check connectivity.`, { parseMode: "plain" });
21794
+ return;
21795
+ }
21796
+ const models = await OllamaService.discoverModels(name);
21797
+ const lines = [
21798
+ `\u2705 Added "${name}" (${host}:${actualPort})`,
21799
+ "",
21800
+ `Found ${models.length} model(s):`
21801
+ ];
21802
+ for (const m of models) {
21803
+ lines.push(` \u2022 ${m.name}${m.parameterSize ? ` (${m.parameterSize})` : ""}`);
21804
+ }
21805
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
21806
+ }
21807
+ async function sendModelList(chatId, channel, serverName) {
21808
+ const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
21809
+ let models;
21810
+ if (serverName) {
21811
+ const server = OllamaStore.getServer(serverName);
21812
+ if (!server) {
21813
+ await channel.sendText(chatId, `Server "${serverName}" not found.`, { parseMode: "plain" });
21814
+ return;
21815
+ }
21816
+ models = OllamaStore.listModels(server.id);
21817
+ } else {
21818
+ models = OllamaStore.getAvailableModels();
21819
+ }
21820
+ if (models.length === 0) {
21821
+ await channel.sendText(chatId, "No models discovered. Run /ollama discover first.", { parseMode: "plain" });
21822
+ return;
21823
+ }
21824
+ const lines = [
21825
+ `<b>\u{1F9E0} Ollama Models</b>${serverName ? ` (${serverName})` : ""}`,
21826
+ ""
21827
+ ];
21828
+ for (const m of models) {
21829
+ const sizeGB = m.sizeBytes > 0 ? `${(m.sizeBytes / 1e9).toFixed(1)}GB` : "";
21830
+ const ctxK = m.contextWindow ? `${Math.round(m.contextWindow / 1e3)}K ctx` : "";
21831
+ const meta = [m.parameterSize, m.quantization, sizeGB, ctxK].filter(Boolean).join(" \xB7 ");
21832
+ lines.push(` \u2022 <b>${m.name}</b>`);
21833
+ if (meta) lines.push(` <i>${meta}</i>`);
21834
+ }
21835
+ lines.push("", `${models.length} model${models.length !== 1 ? "s" : ""} total`);
21836
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "html" });
21837
+ }
21838
+ async function sendDiscover(chatId, channel, serverName) {
21839
+ await channel.sendText(chatId, "\u{1F50D} Discovering models...", { parseMode: "plain" });
21840
+ try {
21841
+ const { OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
21842
+ const models = await OllamaService.discoverModels(serverName);
21843
+ if (models.length === 0) {
21844
+ await channel.sendText(chatId, "No models found. Check server connectivity.", { parseMode: "plain" });
21845
+ return;
21846
+ }
21847
+ const lines = [`\u2705 Found ${models.length} model(s):`, ""];
21848
+ for (const m of models) {
21849
+ const meta = [m.parameterSize, m.contextWindow ? `${Math.round(m.contextWindow / 1e3)}K ctx` : ""].filter(Boolean).join(" \xB7 ");
21850
+ lines.push(` \u2022 ${m.name}${meta ? ` (${meta})` : ""}`);
21851
+ }
21852
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
21853
+ } catch (err) {
21854
+ await channel.sendText(chatId, `Discovery failed: ${err instanceof Error ? err.message : String(err)}`, { parseMode: "plain" });
21855
+ }
21856
+ }
21857
+ async function sendHealthCheck(chatId, channel) {
21858
+ await channel.sendText(chatId, "\u{1F49A} Pinging servers...", { parseMode: "plain" });
21859
+ try {
21860
+ const { OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
21861
+ const results = await OllamaService.healthCheck();
21862
+ if (results.length === 0) {
21863
+ await channel.sendText(chatId, "No servers configured.", { parseMode: "plain" });
21864
+ return;
21865
+ }
21866
+ const lines = ["Health Check Results:", ""];
21867
+ for (const s of results) {
21868
+ const dot = s.status === "online" ? "\u{1F7E2}" : "\u{1F534}";
21869
+ lines.push(`${dot} ${s.name} (${s.host}:${s.port}) \u2014 ${s.status}`);
21870
+ }
21871
+ const online = results.filter((s) => s.status === "online").length;
21872
+ lines.push("", `${online}/${results.length} online`);
21873
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
21874
+ } catch (err) {
21875
+ await channel.sendText(chatId, `Health check failed: ${err instanceof Error ? err.message : String(err)}`, { parseMode: "plain" });
21876
+ }
21877
+ }
21878
+ async function sendRemoveConfirm(chatId, channel, name) {
21879
+ const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
21880
+ const server = OllamaStore.getServer(name);
21881
+ if (!server) {
21882
+ await channel.sendText(chatId, `Server "${name}" not found.`, { parseMode: "plain" });
21883
+ return;
21884
+ }
21885
+ const modelCount = OllamaStore.listModels(server.id).length;
21886
+ const text = [
21887
+ `Remove server <b>${name}</b>?`,
21888
+ "",
21889
+ `Host: ${server.host}:${server.port}`,
21890
+ `Status: ${server.status}`,
21891
+ `Models: ${modelCount}`,
21892
+ "",
21893
+ "This will also remove all cached model data."
21894
+ ].join("\n");
21895
+ if (typeof channel.sendKeyboard === "function") {
21896
+ await channel.sendKeyboard(chatId, text, [
21897
+ [
21898
+ { label: "\u2705 Confirm", data: `ollama:remove:${name}`, style: "danger" },
21899
+ { label: "\u274C Cancel", data: "ollama:dashboard" }
21900
+ ]
21901
+ ]);
21902
+ } else {
21903
+ await channel.sendText(chatId, text + "\n\nUse /ollama remove <name> to confirm.", { parseMode: "html" });
21904
+ }
21905
+ }
21906
+ async function handleOllamaCallback(chatId, data, channel) {
21907
+ const parts = data.split(":");
21908
+ const action = parts[1];
21909
+ switch (action) {
21910
+ case "dashboard":
21911
+ return sendOllamaDashboard(chatId, channel);
21912
+ case "models":
21913
+ return sendModelList(chatId, channel);
21914
+ case "discover":
21915
+ return sendDiscover(chatId, channel);
21916
+ case "health":
21917
+ return sendHealthCheck(chatId, channel);
21918
+ case "remove_confirm": {
21919
+ const name = parts.slice(2).join(":");
21920
+ return sendRemoveConfirm(chatId, channel, name);
21921
+ }
21922
+ case "remove": {
21923
+ const name = parts.slice(2).join(":");
21924
+ const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
21925
+ const removed = OllamaStore.removeServer(name);
21926
+ if (removed) {
21927
+ await channel.sendText(chatId, `\u2705 Removed server "${name}"`, { parseMode: "plain" });
21928
+ } else {
21929
+ await channel.sendText(chatId, `Server "${name}" not found.`, { parseMode: "plain" });
21930
+ }
21931
+ return sendOllamaDashboard(chatId, channel);
21932
+ }
21933
+ case "add_prompt":
21934
+ await channel.sendText(chatId, [
21935
+ "To add a server, send:",
21936
+ "",
21937
+ " /ollama add <name> <host> [port]",
21938
+ "",
21939
+ "Examples:",
21940
+ " /ollama add local 192.168.1.100",
21941
+ " /ollama add mac-studio 10.0.0.5 11434",
21942
+ " /ollama add cloud api.ollama.example.com 443"
21943
+ ].join("\n"), { parseMode: "plain" });
21944
+ return;
21945
+ case "help":
21946
+ await channel.sendText(chatId, [
21947
+ "\u{1F999} Ollama Commands:",
21948
+ "",
21949
+ "/ollama \u2014 Server dashboard",
21950
+ "/ollama add <name> <host> [port] \u2014 Add server",
21951
+ "/ollama remove <name> \u2014 Remove server",
21952
+ "/ollama models [server] \u2014 List models",
21953
+ "/ollama discover [server] \u2014 Discover models",
21954
+ "/ollama health \u2014 Ping all servers"
21955
+ ].join("\n"), { parseMode: "plain" });
21956
+ return;
21957
+ default:
21958
+ await channel.sendText(chatId, "Unknown action.", { parseMode: "plain" });
21959
+ }
21960
+ }
21961
+ var init_ollama3 = __esm({
21962
+ "src/router/ollama.ts"() {
21963
+ "use strict";
21964
+ }
21965
+ });
21966
+
20396
21967
  // src/router/commands.ts
20397
21968
  async function handleCommand(msg, channel) {
20398
21969
  const { chatId, command, commandArgs } = msg;
@@ -20567,9 +22138,33 @@ async function handleCommand(msg, channel) {
20567
22138
  case "evolve":
20568
22139
  await handleEvolveCommandWrapper(chatId, commandArgs, msg, channel);
20569
22140
  break;
22141
+ case "reflect": {
22142
+ const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
22143
+ const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
22144
+ const { getPrimaryChatId: getPrimaryChatId2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
22145
+ const reflectionChatId = getPrimaryChatId2() || chatId;
22146
+ await channel.sendText(chatId, "\u{1F50D} Running reflection analysis\u2026", { parseMode: "plain" });
22147
+ try {
22148
+ const insights = await runAnalysis2(reflectionChatId, { force: true });
22149
+ if (insights.length > 0) {
22150
+ const nightlyItems = insights.map((ins, i) => ({ id: i + 1, ...ins }));
22151
+ await channel.sendText(chatId, formatNightlySummary2(nightlyItems), { parseMode: "plain" });
22152
+ } else {
22153
+ await channel.sendText(chatId, "No new insights from reflection analysis.", { parseMode: "plain" });
22154
+ }
22155
+ } catch (e) {
22156
+ await channel.sendText(chatId, `Analysis failed: ${e}`, { parseMode: "plain" });
22157
+ }
22158
+ break;
22159
+ }
20570
22160
  case "optimize":
20571
22161
  await handleOptimizeCommandWrapper(chatId, commandArgs, msg, channel);
20572
22162
  break;
22163
+ case "ollama": {
22164
+ const { handleOllamaCommand: handleOllamaCommand2 } = await Promise.resolve().then(() => (init_ollama3(), ollama_exports2));
22165
+ await handleOllamaCommand2(chatId, commandArgs ?? "", channel);
22166
+ break;
22167
+ }
20573
22168
  default:
20574
22169
  await channel.sendText(chatId, `Unknown command: /${command}. Type /help for available commands.`, { parseMode: "plain" });
20575
22170
  }
@@ -20741,7 +22336,7 @@ ${PERM_MODES[chosen]}`,
20741
22336
  const pending = getPendingEscalation(chatId);
20742
22337
  if (pending) {
20743
22338
  removePendingEscalation(chatId);
20744
- const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router(), router_exports));
22339
+ const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router2(), router_exports));
20745
22340
  await handleMessage2({ text: pending, chatId, source: "telegram", type: "text" }, channel);
20746
22341
  }
20747
22342
  } else if (data === "perm:deny") {
@@ -20800,7 +22395,7 @@ ${PERM_MODES[chosen]}`,
20800
22395
  removePendingPlan(chatId);
20801
22396
  await replaceWithText("\u2705 Approved. Executing...");
20802
22397
  bypassBusyCheck.add(chatId);
20803
- const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router(), router_exports));
22398
+ const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router2(), router_exports));
20804
22399
  const overrideMsg = `[SYSTEM: Planning mode disabled. Execution APPROVED. Please execute the plan.]
20805
22400
  ${plan.originalMessage}`;
20806
22401
  await handleMessage2(
@@ -20828,7 +22423,7 @@ ${plan.originalMessage}`;
20828
22423
  setExecMode(chatId, "yolo");
20829
22424
  await channel.sendText(chatId, "\u26A1 Switched to YOLO mode. Executing without approval gate...", { parseMode: "plain" });
20830
22425
  bypassBusyCheck.add(chatId);
20831
- const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router(), router_exports));
22426
+ const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router2(), router_exports));
20832
22427
  const overrideMsg = `[SYSTEM: Planning mode disabled. YOLO mode enabled. Proceed immediately without asking for approval.]
20833
22428
  ${plan.originalMessage}`;
20834
22429
  await handleMessage2(
@@ -21142,7 +22737,7 @@ ${rotationNote}`, { parseMode: "html" });
21142
22737
  const pending = pendingFallbackMessages2.get(chatId);
21143
22738
  if (pending) {
21144
22739
  pendingFallbackMessages2.delete(chatId);
21145
- const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router(), router_exports));
22740
+ const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router2(), router_exports));
21146
22741
  await handleMessage2(pending.msg, pending.channel);
21147
22742
  }
21148
22743
  } else if (action === "deny") {
@@ -21216,14 +22811,14 @@ ${rotationNote}`, { parseMode: "html" });
21216
22811
  stopAgent(targetChatId);
21217
22812
  await channel.sendText(chatId, "\u26A1 Stopping current task and processing your message\u2026", { parseMode: "plain" });
21218
22813
  bypassBusyCheck.add(targetChatId);
21219
- const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router(), router_exports));
22814
+ const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router2(), router_exports));
21220
22815
  handleMessage2(pending.msg, pending.channel).catch(() => {
21221
22816
  });
21222
22817
  } else if (action === "queue" && pending) {
21223
22818
  pendingInterrupts.delete(targetChatId);
21224
22819
  bypassBusyCheck.add(targetChatId);
21225
22820
  await channel.sendText(chatId, "\u{1F4E5} Message queued \u2014 will process after current task.", { parseMode: "plain" });
21226
- const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router(), router_exports));
22821
+ const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router2(), router_exports));
21227
22822
  handleMessage2(pending.msg, pending.channel).catch(() => {
21228
22823
  });
21229
22824
  } else if (action === "sidequest") {
@@ -21267,7 +22862,7 @@ ${rotationNote}`, { parseMode: "html" });
21267
22862
  setBackend(targetChatId, targetBackend);
21268
22863
  const adapter = getAdapter(targetBackend);
21269
22864
  await channel.sendText(chatId, `Switched to ${adapter.displayName}. Resending your message\u2026`, { parseMode: "plain" });
21270
- const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router(), router_exports));
22865
+ const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router2(), router_exports));
21271
22866
  await handleMessage2(pendingMsg.msg, pendingMsg.channel);
21272
22867
  } else {
21273
22868
  await channel.sendText(chatId, "Fallback expired. Use /backend to switch manually.", { parseMode: "plain" });
@@ -21281,6 +22876,10 @@ ${rotationNote}`, { parseMode: "html" });
21281
22876
  } else if (data.startsWith("opt:")) {
21282
22877
  await handleOptimizeCallback(chatId, data, channel);
21283
22878
  return;
22879
+ } else if (data.startsWith("ollama:")) {
22880
+ const { handleOllamaCallback: handleOllamaCallback2 } = await Promise.resolve().then(() => (init_ollama3(), ollama_exports2));
22881
+ await handleOllamaCallback2(chatId, data, channel);
22882
+ return;
21284
22883
  } else if (data.startsWith("summ:")) {
21285
22884
  const action = data.slice(5);
21286
22885
  if (action === "all") {
@@ -22175,7 +23774,7 @@ Approve paid usage for this session?`,
22175
23774
  }
22176
23775
  }
22177
23776
  }
22178
- var init_router = __esm({
23777
+ var init_router2 = __esm({
22179
23778
  "src/router.ts"() {
22180
23779
  "use strict";
22181
23780
  init_profile();
@@ -22499,7 +24098,7 @@ async function runWithRetry(job, model2, runId, t0) {
22499
24098
  const vLevel = getVerboseLevel3(chatId);
22500
24099
  let onToolAction;
22501
24100
  if (vLevel !== "off") {
22502
- const { makeToolActionCallback: makeToolActionCallback2 } = await Promise.resolve().then(() => (init_router(), router_exports));
24101
+ const { makeToolActionCallback: makeToolActionCallback2 } = await Promise.resolve().then(() => (init_router2(), router_exports));
22503
24102
  const channelName = job.channel ?? "telegram";
22504
24103
  const { getChannelRegistry: getChannelRegistry2 } = await Promise.resolve().then(() => (init_delivery(), delivery_exports));
22505
24104
  const channel = getChannelRegistry2()?.get(channelName);
@@ -23178,7 +24777,7 @@ var init_telegram = __esm({
23178
24777
  return `<s>${text}</s>`;
23179
24778
  },
23180
24779
  codespan(code) {
23181
- return `<code>${escapeHtml2(code)}</code>`;
24780
+ return `<code>${code}</code>`;
23182
24781
  },
23183
24782
  code(code, lang) {
23184
24783
  const langAttr = lang ? ` class="language-${escapeHtml2(lang)}"` : "";
@@ -23229,13 +24828,37 @@ ${body.replace(/<[^>]*>/g, "").trim()}</code>
23229
24828
 
23230
24829
  // src/channels/telegram.ts
23231
24830
  import { API_CONSTANTS, Bot, GrammyError, InlineKeyboard, InputFile } from "grammy";
24831
+ function tripCircuitBreaker(retrySec) {
24832
+ const until = Date.now() + retrySec * 1e3;
24833
+ if (until > circuitBreakerUntil) {
24834
+ circuitBreakerUntil = until;
24835
+ warn(`[telegram] Circuit breaker tripped \u2014 pausing ALL Telegram API calls for ${retrySec}s`);
24836
+ }
24837
+ }
24838
+ async function waitForCircuitBreaker() {
24839
+ const remaining = circuitBreakerUntil - Date.now();
24840
+ if (remaining <= 0) return false;
24841
+ if (remaining > GIVE_UP_THRESHOLD_SEC * 1e3) return true;
24842
+ await new Promise((r) => setTimeout(r, remaining));
24843
+ return false;
24844
+ }
23232
24845
  async function withRetry(label2, fn) {
23233
24846
  for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
24847
+ const tooLong = await waitForCircuitBreaker();
24848
+ if (tooLong) {
24849
+ warn(`[telegram] 429 on ${label2} \u2014 circuit breaker cooldown too long, giving up`);
24850
+ throw new GrammyError(`Circuit breaker active \u2014 skipping ${label2}`, { ok: false, error_code: 429, description: "Rate limited (circuit breaker)" }, label2, {});
24851
+ }
23234
24852
  try {
23235
24853
  return await fn();
23236
24854
  } catch (err) {
23237
24855
  if (err instanceof GrammyError && err.error_code === 429) {
23238
- const retrySec = Math.min(err.parameters?.retry_after ?? FALLBACK_RETRY_SEC, MAX_RETRY_WAIT_SEC);
24856
+ const retrySec = err.parameters?.retry_after ?? FALLBACK_RETRY_SEC;
24857
+ tripCircuitBreaker(retrySec);
24858
+ if (retrySec > GIVE_UP_THRESHOLD_SEC) {
24859
+ warn(`[telegram] 429 on ${label2} \u2014 retry_after ${retrySec}s exceeds threshold, giving up`);
24860
+ throw err;
24861
+ }
23239
24862
  if (attempt < MAX_RETRIES2) {
23240
24863
  warn(`[telegram] 429 on ${label2} (attempt ${attempt + 1}/${MAX_RETRIES2}) \u2014 retrying in ${retrySec}s`);
23241
24864
  await new Promise((r) => setTimeout(r, retrySec * 1e3));
@@ -23267,7 +24890,7 @@ function numericChatId(chatId) {
23267
24890
  const raw = chatId.includes(":") ? chatId.split(":").pop() : chatId;
23268
24891
  return parseInt(raw);
23269
24892
  }
23270
- var MAX_RETRIES2, FALLBACK_RETRY_SEC, MAX_RETRY_WAIT_SEC, FAST_PATH_COMMANDS, TelegramChannel;
24893
+ var MAX_RETRIES2, FALLBACK_RETRY_SEC, GIVE_UP_THRESHOLD_SEC, circuitBreakerUntil, FAST_PATH_COMMANDS, TelegramChannel;
23271
24894
  var init_telegram2 = __esm({
23272
24895
  "src/channels/telegram.ts"() {
23273
24896
  "use strict";
@@ -23276,7 +24899,8 @@ var init_telegram2 = __esm({
23276
24899
  init_store5();
23277
24900
  MAX_RETRIES2 = 3;
23278
24901
  FALLBACK_RETRY_SEC = 3;
23279
- MAX_RETRY_WAIT_SEC = 30;
24902
+ GIVE_UP_THRESHOLD_SEC = 60;
24903
+ circuitBreakerUntil = 0;
23280
24904
  FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
23281
24905
  TelegramChannel = class {
23282
24906
  name = "telegram";
@@ -23347,7 +24971,6 @@ var init_telegram2 = __esm({
23347
24971
  { command: "summarizer", description: "Configure session summarization model" },
23348
24972
  // Permissions & tools
23349
24973
  { command: "permissions", description: "Permission mode (yolo/safe/readonly/plan)" },
23350
- { command: "mode", description: "Execution gate (approved/yolo)" },
23351
24974
  { command: "tools", description: "Configure which tools the agent can use" },
23352
24975
  { command: "verbose", description: "Tool visibility (off/normal/verbose)" },
23353
24976
  { command: "debug", description: "Toggle session debug logging (full tool I/O)" },
@@ -23392,7 +25015,9 @@ var init_telegram2 = __esm({
23392
25015
  { command: "intent", description: "Test intent classifier on a message" },
23393
25016
  { command: "evolve", description: "Self-learning & evolution controls" },
23394
25017
  { command: "reflect", description: "Trigger reflection analysis" },
23395
- { command: "optimize", description: "Audit identity files and skills" }
25018
+ { command: "optimize", description: "Audit identity files and skills" },
25019
+ // Ollama
25020
+ { command: "ollama", description: "Manage Ollama local LLM servers" }
23396
25021
  ]);
23397
25022
  this.bot.on("message", async (ctx) => {
23398
25023
  const chatId = ctx.chat.id.toString();
@@ -23613,10 +25238,11 @@ var init_telegram2 = __esm({
23613
25238
  }
23614
25239
  const MAX_KEYBOARD_TEXT = 4e3;
23615
25240
  const safeText = text.length > MAX_KEYBOARD_TEXT ? text.slice(0, MAX_KEYBOARD_TEXT) + "\n\n\u2026(truncated)" : text;
25241
+ const formatted = formatForTelegram(safeText);
23616
25242
  try {
23617
25243
  const msg = await withRetry(
23618
25244
  "sendKeyboard",
23619
- () => this.bot.api.sendMessage(numericChatId(chatId), safeText, {
25245
+ () => this.bot.api.sendMessage(numericChatId(chatId), formatted, {
23620
25246
  parse_mode: "HTML",
23621
25247
  reply_markup: keyboard
23622
25248
  })
@@ -23624,6 +25250,28 @@ var init_telegram2 = __esm({
23624
25250
  return msg.message_id.toString();
23625
25251
  } catch (err) {
23626
25252
  error(`[telegram] sendKeyboard failed (chat=${chatId}, textLen=${text.length}):`, err);
25253
+ try {
25254
+ const escaped = safeText.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
25255
+ const retryMsg = await withRetry(
25256
+ "sendKeyboard:plain",
25257
+ () => this.bot.api.sendMessage(numericChatId(chatId), escaped, {
25258
+ reply_markup: keyboard
25259
+ })
25260
+ );
25261
+ return retryMsg.message_id.toString();
25262
+ } catch (plainErr) {
25263
+ error(`[telegram] sendKeyboard plain retry also failed:`, plainErr);
25264
+ }
25265
+ try {
25266
+ const plainText = safeText.replace(/<[^>]+>/g, "");
25267
+ if (plainText.trim()) {
25268
+ await withRetry(
25269
+ "sendKeyboard:text-rescue",
25270
+ () => this.bot.api.sendMessage(numericChatId(chatId), plainText, {})
25271
+ );
25272
+ }
25273
+ } catch {
25274
+ }
23627
25275
  try {
23628
25276
  const fallbackMsg = await withRetry(
23629
25277
  "sendKeyboard:fallback",
@@ -24667,13 +26315,41 @@ async function main() {
24667
26315
  }
24668
26316
  try {
24669
26317
  const { getAdapter: getAdapter4, probeBackendAvailability: probeBackendAvailability2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
24670
- probeBackendAvailability2();
26318
+ await probeBackendAvailability2();
24671
26319
  const claude = getAdapter4("claude");
24672
26320
  if ("getAuthMode" in claude) claude.getAuthMode();
24673
26321
  const cursor = getAdapter4("cursor");
24674
26322
  if ("probeTier" in cursor) cursor.probeTier();
24675
26323
  } catch {
24676
26324
  }
26325
+ (async () => {
26326
+ try {
26327
+ const { OllamaService, OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
26328
+ const servers = OllamaStore.listServers();
26329
+ if (servers.length === 0) return;
26330
+ const checkedServers = await OllamaService.healthCheck();
26331
+ const onlineCount = checkedServers.filter((s) => s.status === "online").length;
26332
+ let modelCount = 0;
26333
+ for (const server of checkedServers) {
26334
+ if (server.status === "online") {
26335
+ const models = await OllamaService.discoverModels(server.name);
26336
+ modelCount += models.length;
26337
+ }
26338
+ }
26339
+ if (modelCount > 0) {
26340
+ try {
26341
+ const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
26342
+ const adapter = getAdapter4("ollama");
26343
+ if ("refreshModelCatalog" in adapter) {
26344
+ adapter.refreshModelCatalog();
26345
+ }
26346
+ } catch {
26347
+ }
26348
+ }
26349
+ log(`[cc-claw] Ollama: ${onlineCount}/${servers.length} server(s) online, ${modelCount} model(s) discovered`);
26350
+ } catch {
26351
+ }
26352
+ })();
24677
26353
  let pendingSummarizeNotify = null;
24678
26354
  const { getLoggedChatIds: getLoggedChatIds2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
24679
26355
  const pendingCount = getLoggedChatIds2().length;
@@ -24730,6 +26406,8 @@ async function main() {
24730
26406
  startPersistedJobs();
24731
26407
  initRunnerRegistry();
24732
26408
  initOrchestrator();
26409
+ const { startStaleChatSweep: startStaleChatSweep2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
26410
+ startStaleChatSweep2();
24733
26411
  let notifyQueue = Promise.resolve();
24734
26412
  setNotifyCallback((chatId, message) => {
24735
26413
  notifyQueue = notifyQueue.then(async () => {
@@ -24808,8 +26486,9 @@ async function main() {
24808
26486
  const shutdown = async (signal) => {
24809
26487
  log(`[cc-claw] Received ${signal}, shutting down...`);
24810
26488
  try {
24811
- const { stopAllActiveAgents: stopAllActiveAgents2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
26489
+ const { stopAllActiveAgents: stopAllActiveAgents2, stopStaleChatSweep: stopStaleChatSweep2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
24812
26490
  stopAllActiveAgents2();
26491
+ stopStaleChatSweep2();
24813
26492
  stopAllHeartbeats();
24814
26493
  stopHealthMonitor3();
24815
26494
  stopMonitor();
@@ -24839,6 +26518,13 @@ async function main() {
24839
26518
  };
24840
26519
  process.on("SIGTERM", () => shutdown("SIGTERM"));
24841
26520
  process.on("SIGINT", () => shutdown("SIGINT"));
26521
+ process.on("unhandledRejection", (reason, promise) => {
26522
+ error("[cc-claw] Unhandled promise rejection:", reason);
26523
+ });
26524
+ process.on("uncaughtException", (err, origin) => {
26525
+ error(`[cc-claw] Uncaught exception (${origin}):`, err);
26526
+ setTimeout(() => process.exit(1), 500);
26527
+ });
24842
26528
  }
24843
26529
  var LOG_MAX_BYTES;
24844
26530
  var init_index = __esm({
@@ -24854,7 +26540,7 @@ var init_index = __esm({
24854
26540
  init_server();
24855
26541
  init_registry3();
24856
26542
  init_telegram2();
24857
- init_router();
26543
+ init_router2();
24858
26544
  init_summarize();
24859
26545
  init_detect();
24860
26546
  init_bootstrap();
@@ -24987,8 +26673,8 @@ var init_api_client = __esm({
24987
26673
  });
24988
26674
 
24989
26675
  // src/service.ts
24990
- var service_exports = {};
24991
- __export(service_exports, {
26676
+ var service_exports2 = {};
26677
+ __export(service_exports2, {
24992
26678
  installService: () => installService,
24993
26679
  serviceStatus: () => serviceStatus,
24994
26680
  uninstallService: () => uninstallService
@@ -25228,7 +26914,7 @@ function serviceStatus() {
25228
26914
  else console.error(` Unsupported platform: ${os2}.`);
25229
26915
  }
25230
26916
  var PLIST_LABEL, PLIST_PATH, SYSTEMD_DIR, UNIT_PATH;
25231
- var init_service = __esm({
26917
+ var init_service2 = __esm({
25232
26918
  "src/service.ts"() {
25233
26919
  "use strict";
25234
26920
  init_paths();
@@ -25403,7 +27089,7 @@ var init_daemon = __esm({
25403
27089
 
25404
27090
  // src/cli/resolve-chat.ts
25405
27091
  import { readFileSync as readFileSync19 } from "fs";
25406
- function resolveChatId(globalOpts) {
27092
+ function resolveChatId2(globalOpts) {
25407
27093
  const explicit = globalOpts.chat;
25408
27094
  if (explicit) return explicit;
25409
27095
  if (_cachedDefault) return _cachedDefault;
@@ -25437,7 +27123,7 @@ async function statusCommand(globalOpts, localOpts) {
25437
27123
  try {
25438
27124
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
25439
27125
  const readDb = openDatabaseReadOnly2();
25440
- const chatId = resolveChatId(globalOpts);
27126
+ const chatId = resolveChatId2(globalOpts);
25441
27127
  const { getAdapterForChat: getAdapterForChat2, getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
25442
27128
  let backend2 = null;
25443
27129
  let modelName = "not set";
@@ -25505,6 +27191,17 @@ async function statusCommand(globalOpts, localOpts) {
25505
27191
  },
25506
27192
  db: { path: DB_PATH, sizeBytes: dbStat?.size ?? 0, exists: !!dbStat }
25507
27193
  };
27194
+ try {
27195
+ const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
27196
+ const servers = OllamaStore.listServers();
27197
+ if (servers.length > 0) {
27198
+ const online = servers.filter((s) => s.status === "online").length;
27199
+ const models = OllamaStore.getAvailableModels();
27200
+ const topModel = models.length > 0 ? models.sort((a, b) => (b.qualityScore ?? 0) - (a.qualityScore ?? 0))[0].name : void 0;
27201
+ data.ollama = { servers: servers.length, online, models: models.length, topModel };
27202
+ }
27203
+ } catch {
27204
+ }
25508
27205
  readDb.close();
25509
27206
  output(data, (d) => {
25510
27207
  const s = d;
@@ -25530,9 +27227,20 @@ async function statusCommand(globalOpts, localOpts) {
25530
27227
  kvLine("Requests", String(s.usage.requests)),
25531
27228
  kvLine("In tokens", s.usage.input_tokens.toLocaleString()),
25532
27229
  kvLine("Out tokens", s.usage.output_tokens.toLocaleString()),
25533
- kvLine("Cache read", s.usage.cache_read_tokens.toLocaleString()),
25534
- ""
27230
+ kvLine("Cache read", s.usage.cache_read_tokens.toLocaleString())
25535
27231
  );
27232
+ if (s.ollama) {
27233
+ lines.push(
27234
+ "",
27235
+ divider("Ollama (Local)"),
27236
+ kvLine("Servers", `${s.ollama.online}/${s.ollama.servers} online`),
27237
+ kvLine("Models", String(s.ollama.models))
27238
+ );
27239
+ if (s.ollama.topModel) {
27240
+ lines.push(kvLine("Top model", s.ollama.topModel));
27241
+ }
27242
+ }
27243
+ lines.push("");
25536
27244
  return lines.join("\n");
25537
27245
  });
25538
27246
  } catch (err) {
@@ -25679,15 +27387,41 @@ async function doctorCommand(globalOpts, localOpts) {
25679
27387
  try {
25680
27388
  const { readFileSync: readFileSync27 } = await import("fs");
25681
27389
  const logContent = readFileSync27(ERROR_LOG_PATH, "utf-8");
25682
- const recentLines = logContent.split("\n").filter(Boolean).slice(-100);
27390
+ const recentLines = logContent.split("\n").filter(Boolean).slice(-500);
25683
27391
  const last24h = Date.now() - 864e5;
25684
27392
  const recentErrors = recentLines.filter((line) => {
25685
- const match = line.match(/^\[(\d{4}-\d{2}-\d{2}T[\d:]+)/);
25686
- if (match) return new Date(match[1]).getTime() > last24h;
27393
+ const match = line.match(/^\[(\d{4}-\d{2}-\d{2})\s+([\d:]+)/);
27394
+ if (match) return (/* @__PURE__ */ new Date(`${match[1]}T${match[2]}`)).getTime() > last24h;
25687
27395
  return false;
25688
27396
  });
25689
27397
  if (recentErrors.length > 0) {
25690
- checks.push({ name: "Recent errors", status: "warning", message: `${recentErrors.length} errors in last 24h`, fix: "cc-claw logs --error" });
27398
+ let rate429 = 0;
27399
+ let contentSilence = 0;
27400
+ let spawnTimeout = 0;
27401
+ let other = 0;
27402
+ for (const line of recentErrors) {
27403
+ if (/429|rate.?limit/i.test(line)) rate429++;
27404
+ else if (/content silence/i.test(line)) contentSilence++;
27405
+ else if (/spawn timeout|timeout after \d+s/i.test(line)) spawnTimeout++;
27406
+ else other++;
27407
+ }
27408
+ if (rate429 > 10) {
27409
+ checks.push({ name: "Telegram rate limits", status: "error", message: `${rate429} rate-limit (429) errors in last 24h \u2014 message delivery blocked`, fix: "cc-claw logs --error" });
27410
+ } else if (rate429 > 0) {
27411
+ checks.push({ name: "Telegram rate limits", status: "warning", message: `${rate429} rate-limit (429) errors in last 24h`, fix: "cc-claw logs --error" });
27412
+ }
27413
+ if (contentSilence > 0) {
27414
+ checks.push({ name: "Content silence", status: "warning", message: `${contentSilence} agent silence timeout(s) in last 24h \u2014 API went unresponsive`, fix: "cc-claw logs --error" });
27415
+ }
27416
+ if (spawnTimeout > 0) {
27417
+ checks.push({ name: "Spawn timeouts", status: "warning", message: `${spawnTimeout} backend timeout(s) in last 24h`, fix: "cc-claw logs --error" });
27418
+ }
27419
+ if (other > 0) {
27420
+ checks.push({ name: "Other errors", status: "warning", message: `${other} other error(s) in last 24h`, fix: "cc-claw logs --error" });
27421
+ }
27422
+ if (rate429 === 0 && contentSilence === 0 && spawnTimeout === 0 && other === 0) {
27423
+ checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
27424
+ }
25691
27425
  } else {
25692
27426
  checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
25693
27427
  }
@@ -25697,6 +27431,35 @@ async function doctorCommand(globalOpts, localOpts) {
25697
27431
  } else {
25698
27432
  checks.push({ name: "Error log", status: "ok", message: "no error log file" });
25699
27433
  }
27434
+ try {
27435
+ const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
27436
+ const servers = OllamaStore.listServers();
27437
+ if (servers.length > 0) {
27438
+ const online = servers.filter((s) => s.status === "online");
27439
+ const models = OllamaStore.getAvailableModels();
27440
+ if (online.length === servers.length) {
27441
+ checks.push({
27442
+ name: "Ollama",
27443
+ status: "ok",
27444
+ message: `${online.length} server(s) online, ${models.length} model(s)`
27445
+ });
27446
+ } else if (online.length > 0) {
27447
+ checks.push({
27448
+ name: "Ollama",
27449
+ status: "warning",
27450
+ message: `${online.length}/${servers.length} server(s) online, ${models.length} model(s)`
27451
+ });
27452
+ } else {
27453
+ checks.push({
27454
+ name: "Ollama",
27455
+ status: "warning",
27456
+ message: `${servers.length} server(s) configured, all offline`,
27457
+ fix: "Check Ollama is running: ollama serve"
27458
+ });
27459
+ }
27460
+ }
27461
+ } catch {
27462
+ }
25700
27463
  if (localOpts.fix) {
25701
27464
  const fixable = checks.filter((c) => c.status !== "ok" && c.fix);
25702
27465
  for (const check of fixable) {
@@ -26555,6 +28318,271 @@ var init_backend_cmd_factory = __esm({
26555
28318
  }
26556
28319
  });
26557
28320
 
28321
+ // src/cli/commands/ollama.ts
28322
+ var ollama_exports3 = {};
28323
+ __export(ollama_exports3, {
28324
+ ollamaAdd: () => ollamaAdd,
28325
+ ollamaDiscover: () => ollamaDiscover,
28326
+ ollamaHealth: () => ollamaHealth,
28327
+ ollamaList: () => ollamaList,
28328
+ ollamaModels: () => ollamaModels,
28329
+ ollamaRemove: () => ollamaRemove,
28330
+ ollamaTest: () => ollamaTest
28331
+ });
28332
+ import { existsSync as existsSync35 } from "fs";
28333
+ function requireDb3() {
28334
+ if (!existsSync35(DB_PATH)) {
28335
+ outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
28336
+ process.exit(1);
28337
+ }
28338
+ }
28339
+ async function requireWriteDb3() {
28340
+ requireDb3();
28341
+ if (!dbInitialized3) {
28342
+ const { initDatabase: initDatabase2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28343
+ initDatabase2();
28344
+ dbInitialized3 = true;
28345
+ }
28346
+ }
28347
+ async function ollamaList(globalOpts) {
28348
+ requireDb3();
28349
+ const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28350
+ const readDb = openDatabaseReadOnly2();
28351
+ const servers = readDb.prepare(`
28352
+ SELECT id, name, host, port, status, last_health_check, created_at
28353
+ FROM ollama_servers ORDER BY name
28354
+ `).all();
28355
+ const models = readDb.prepare(`
28356
+ SELECT server_id, COUNT(*) as count FROM ollama_models GROUP BY server_id
28357
+ `).all();
28358
+ readDb.close();
28359
+ const modelCounts = new Map(models.map((r) => [r.server_id, r.count]));
28360
+ if (servers.length === 0) {
28361
+ output(
28362
+ { servers: [] },
28363
+ () => "\n No Ollama servers configured.\n Add one with: cc-claw ollama add <name> <host>\n"
28364
+ );
28365
+ return;
28366
+ }
28367
+ const data = servers.map((s) => ({
28368
+ ...s,
28369
+ modelCount: modelCounts.get(s.id) ?? 0
28370
+ }));
28371
+ output(data, (d) => {
28372
+ const list = d;
28373
+ const lines = ["", divider("Ollama Servers"), ""];
28374
+ for (const s of list) {
28375
+ const dot = statusDot(s.status === "online" ? "active" : "offline");
28376
+ lines.push(` ${dot} ${s.name} ${muted(`(${s.host}:${s.port})`)}`);
28377
+ lines.push(` Models: ${s.modelCount} \xB7 Status: ${s.status === "online" ? success("online") : error2("offline")}`);
28378
+ if (s.last_health_check) {
28379
+ lines.push(` Last check: ${muted(s.last_health_check)}`);
28380
+ }
28381
+ }
28382
+ lines.push("");
28383
+ return lines.join("\n");
28384
+ });
28385
+ }
28386
+ async function ollamaAdd(globalOpts, name, host, opts) {
28387
+ await requireWriteDb3();
28388
+ const port = opts.port ? parseInt(opts.port, 10) : 11434;
28389
+ if (isNaN(port) || port < 1 || port > 65535) {
28390
+ outputError("INVALID_PORT", "Port must be a number between 1 and 65535.");
28391
+ process.exit(1);
28392
+ }
28393
+ const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
28394
+ const existing = OllamaStore.getServer(name);
28395
+ if (existing) {
28396
+ outputError("ALREADY_EXISTS", `Server "${name}" already exists. Remove it first with: cc-claw ollama remove ${name}`);
28397
+ process.exit(1);
28398
+ }
28399
+ console.log(` Checking ${host}:${port}...`);
28400
+ const { OllamaClient } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
28401
+ const online = await OllamaClient.ping(`http://${host}:${port}`, {
28402
+ timeoutMs: 5e3,
28403
+ apiKey: opts.key ?? void 0
28404
+ });
28405
+ if (!online) {
28406
+ console.log(warning(" \u26A0 Server did not respond \u2014 saving as offline."));
28407
+ } else {
28408
+ console.log(success(" \u2713 Server is reachable."));
28409
+ }
28410
+ const server = OllamaStore.addServer(name, host, port, opts.key ?? null);
28411
+ OllamaStore.updateServerStatus(server.id, online ? "online" : "offline");
28412
+ output(
28413
+ { id: server.id, name, host, port, status: online ? "online" : "offline" },
28414
+ () => success(`
28415
+ Added Ollama server "${name}" (${host}:${port})
28416
+ `)
28417
+ );
28418
+ if (online) {
28419
+ console.log(" Discovering models...");
28420
+ const { OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
28421
+ const discovered = await OllamaService.discoverModels(name);
28422
+ console.log(success(` \u2713 Found ${discovered.length} model(s)`));
28423
+ if (discovered.length > 0) {
28424
+ for (const m of discovered) {
28425
+ console.log(` \xB7 ${m.name}${m.parameterSize ? ` (${m.parameterSize})` : ""}`);
28426
+ }
28427
+ }
28428
+ console.log("");
28429
+ }
28430
+ }
28431
+ async function ollamaRemove(globalOpts, name) {
28432
+ await requireWriteDb3();
28433
+ const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
28434
+ const removed = OllamaStore.removeServer(name);
28435
+ if (removed) {
28436
+ output({ removed: true, name }, () => success(`Removed server "${name}"`));
28437
+ } else {
28438
+ outputError("NOT_FOUND", `Server "${name}" not found.`);
28439
+ }
28440
+ }
28441
+ async function ollamaModels(globalOpts, opts) {
28442
+ requireDb3();
28443
+ const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28444
+ const readDb = openDatabaseReadOnly2();
28445
+ let query;
28446
+ let params;
28447
+ if (opts.server) {
28448
+ const server = readDb.prepare("SELECT id FROM ollama_servers WHERE name = ?").get(opts.server);
28449
+ if (!server) {
28450
+ readDb.close();
28451
+ outputError("NOT_FOUND", `Server "${opts.server}" not found.`);
28452
+ return;
28453
+ }
28454
+ query = `
28455
+ SELECT m.name, m.family, m.parameter_size, m.quantization, m.context_window, m.size_bytes, m.quality_score,
28456
+ s.name as server_name
28457
+ FROM ollama_models m
28458
+ JOIN ollama_servers s ON s.id = m.server_id
28459
+ WHERE m.server_id = ?
28460
+ ORDER BY m.name
28461
+ `;
28462
+ params = [server.id];
28463
+ } else {
28464
+ query = `
28465
+ SELECT m.name, m.family, m.parameter_size, m.quantization, m.context_window, m.size_bytes, m.quality_score,
28466
+ s.name as server_name
28467
+ FROM ollama_models m
28468
+ JOIN ollama_servers s ON s.id = m.server_id
28469
+ ORDER BY s.name, m.name
28470
+ `;
28471
+ params = [];
28472
+ }
28473
+ const rows = readDb.prepare(query).all(...params);
28474
+ readDb.close();
28475
+ if (rows.length === 0) {
28476
+ output(
28477
+ { models: [] },
28478
+ () => opts.server ? `
28479
+ No models discovered on "${opts.server}".
28480
+ Run: cc-claw ollama discover --server ${opts.server}
28481
+ ` : "\n No models discovered.\n Run: cc-claw ollama discover\n"
28482
+ );
28483
+ return;
28484
+ }
28485
+ output(rows, (d) => {
28486
+ const models = d;
28487
+ const lines = ["", divider("Ollama Models"), ""];
28488
+ let lastServer = "";
28489
+ for (const m of models) {
28490
+ if (m.server_name !== lastServer) {
28491
+ if (lastServer) lines.push("");
28492
+ lines.push(` ${muted(`\u2500\u2500 ${m.server_name} \u2500\u2500`)}`);
28493
+ lastServer = m.server_name;
28494
+ }
28495
+ const sizeGB = m.size_bytes > 0 ? `${(m.size_bytes / 1e9).toFixed(1)}GB` : "";
28496
+ const ctxK = m.context_window ? `${(m.context_window / 1e3).toFixed(0)}K ctx` : "";
28497
+ const quality = m.quality_score !== null ? `Q:${m.quality_score}` : "";
28498
+ const meta = [m.parameter_size, m.quantization, sizeGB, ctxK, quality].filter(Boolean).join(" \xB7 ");
28499
+ lines.push(` ${m.name} ${muted(meta ? `(${meta})` : "")}`);
28500
+ }
28501
+ lines.push("");
28502
+ return lines.join("\n");
28503
+ });
28504
+ }
28505
+ async function ollamaDiscover(globalOpts, opts) {
28506
+ await requireWriteDb3();
28507
+ const { OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
28508
+ if (opts.server) {
28509
+ const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
28510
+ const server = OllamaStore.getServer(opts.server);
28511
+ if (!server) {
28512
+ outputError("NOT_FOUND", `Server "${opts.server}" not found.`);
28513
+ return;
28514
+ }
28515
+ }
28516
+ console.log(opts.server ? ` Discovering models on ${opts.server}...` : " Discovering models on all servers...");
28517
+ const models = await OllamaService.discoverModels(opts.server);
28518
+ output(models.map((m) => ({
28519
+ name: m.name,
28520
+ parameterSize: m.parameterSize,
28521
+ contextWindow: m.contextWindow,
28522
+ sizeBytes: m.sizeBytes
28523
+ })), (d) => {
28524
+ const list = d;
28525
+ if (list.length === 0) {
28526
+ return "\n No models found. Check server connectivity.\n";
28527
+ }
28528
+ const lines = [`
28529
+ ${success(`\u2713 Discovered ${list.length} model(s):`)}`, ""];
28530
+ for (const m of list) {
28531
+ const sizeGB = m.sizeBytes > 0 ? `${(m.sizeBytes / 1e9).toFixed(1)}GB` : "";
28532
+ const ctxK = m.contextWindow ? `${(m.contextWindow / 1e3).toFixed(0)}K ctx` : "";
28533
+ const meta = [m.parameterSize, sizeGB, ctxK].filter(Boolean).join(" \xB7 ");
28534
+ lines.push(` \xB7 ${m.name} ${muted(meta ? `(${meta})` : "")}`);
28535
+ }
28536
+ lines.push("");
28537
+ return lines.join("\n");
28538
+ });
28539
+ }
28540
+ async function ollamaHealth(globalOpts) {
28541
+ await requireWriteDb3();
28542
+ const { OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
28543
+ console.log(" Pinging all Ollama servers...");
28544
+ const results = await OllamaService.healthCheck();
28545
+ if (results.length === 0) {
28546
+ output({ servers: [] }, () => "\n No Ollama servers configured.\n");
28547
+ return;
28548
+ }
28549
+ output(
28550
+ results.map((s) => ({ name: s.name, host: s.host, port: s.port, status: s.status })),
28551
+ (d) => {
28552
+ const servers = d;
28553
+ const lines = ["", divider("Ollama Health"), ""];
28554
+ for (const s of servers) {
28555
+ const dot = statusDot(s.status === "online" ? "active" : "offline");
28556
+ const statusLabel = s.status === "online" ? success("online") : error2("offline");
28557
+ lines.push(` ${dot} ${s.name} ${muted(`(${s.host}:${s.port})`)} \u2014 ${statusLabel}`);
28558
+ }
28559
+ const online = servers.filter((s) => s.status === "online").length;
28560
+ lines.push("");
28561
+ lines.push(` ${online}/${servers.length} servers online`);
28562
+ lines.push("");
28563
+ return lines.join("\n");
28564
+ }
28565
+ );
28566
+ }
28567
+ async function ollamaTest(globalOpts, model2) {
28568
+ output(
28569
+ { model: model2, status: "not_implemented" },
28570
+ () => `
28571
+ ${warning("Quality gate testing is coming in Phase 4.")}
28572
+ Model: ${model2}
28573
+ `
28574
+ );
28575
+ }
28576
+ var dbInitialized3;
28577
+ var init_ollama4 = __esm({
28578
+ "src/cli/commands/ollama.ts"() {
28579
+ "use strict";
28580
+ init_format2();
28581
+ init_paths();
28582
+ dbInitialized3 = false;
28583
+ }
28584
+ });
28585
+
26558
28586
  // src/cli/commands/backend.ts
26559
28587
  var backend_exports = {};
26560
28588
  __export(backend_exports, {
@@ -26562,12 +28590,12 @@ __export(backend_exports, {
26562
28590
  backendList: () => backendList,
26563
28591
  backendSet: () => backendSet
26564
28592
  });
26565
- import { existsSync as existsSync35 } from "fs";
28593
+ import { existsSync as existsSync36 } from "fs";
26566
28594
  async function backendList(globalOpts) {
26567
28595
  const { getAvailableAdapters: getAvailableAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
26568
- const chatId = resolveChatId(globalOpts);
28596
+ const chatId = resolveChatId2(globalOpts);
26569
28597
  let activeBackend = null;
26570
- if (existsSync35(DB_PATH)) {
28598
+ if (existsSync36(DB_PATH)) {
26571
28599
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
26572
28600
  const readDb = openDatabaseReadOnly2();
26573
28601
  try {
@@ -26597,8 +28625,8 @@ async function backendList(globalOpts) {
26597
28625
  });
26598
28626
  }
26599
28627
  async function backendGet(globalOpts) {
26600
- const chatId = resolveChatId(globalOpts);
26601
- if (!existsSync35(DB_PATH)) {
28628
+ const chatId = resolveChatId2(globalOpts);
28629
+ if (!existsSync36(DB_PATH)) {
26602
28630
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
26603
28631
  process.exit(1);
26604
28632
  }
@@ -26615,7 +28643,7 @@ async function backendSet(globalOpts, name) {
26615
28643
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
26616
28644
  process.exit(1);
26617
28645
  }
26618
- const chatId = resolveChatId(globalOpts);
28646
+ const chatId = resolveChatId2(globalOpts);
26619
28647
  const res = await apiPost2("/api/backend/set", { chatId, backend: name });
26620
28648
  if (res.ok) {
26621
28649
  output({ backend: name, success: true }, () => `
@@ -26642,13 +28670,13 @@ __export(model_exports, {
26642
28670
  modelList: () => modelList,
26643
28671
  modelSet: () => modelSet
26644
28672
  });
26645
- import { existsSync as existsSync36 } from "fs";
28673
+ import { existsSync as existsSync37 } from "fs";
26646
28674
  async function modelList(globalOpts) {
26647
- const chatId = resolveChatId(globalOpts);
28675
+ const chatId = resolveChatId2(globalOpts);
26648
28676
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
26649
28677
  const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
26650
28678
  let backendId = "claude";
26651
- if (existsSync36(DB_PATH)) {
28679
+ if (existsSync37(DB_PATH)) {
26652
28680
  const readDb = openDatabaseReadOnly2();
26653
28681
  try {
26654
28682
  const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
@@ -26680,8 +28708,8 @@ async function modelList(globalOpts) {
26680
28708
  }
26681
28709
  }
26682
28710
  async function modelGet(globalOpts) {
26683
- const chatId = resolveChatId(globalOpts);
26684
- if (!existsSync36(DB_PATH)) {
28711
+ const chatId = resolveChatId2(globalOpts);
28712
+ if (!existsSync37(DB_PATH)) {
26685
28713
  outputError("DB_NOT_FOUND", "Database not found.");
26686
28714
  process.exit(1);
26687
28715
  }
@@ -26698,7 +28726,7 @@ async function modelSet(globalOpts, name) {
26698
28726
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
26699
28727
  process.exit(1);
26700
28728
  }
26701
- const chatId = resolveChatId(globalOpts);
28729
+ const chatId = resolveChatId2(globalOpts);
26702
28730
  const res = await apiPost2("/api/model/set", { chatId, model: name });
26703
28731
  if (res.ok) {
26704
28732
  output({ model: name, success: true }, () => `
@@ -26725,9 +28753,9 @@ __export(memory_exports2, {
26725
28753
  memoryList: () => memoryList,
26726
28754
  memorySearch: () => memorySearch
26727
28755
  });
26728
- import { existsSync as existsSync37 } from "fs";
28756
+ import { existsSync as existsSync38 } from "fs";
26729
28757
  async function memoryList(globalOpts) {
26730
- if (!existsSync37(DB_PATH)) {
28758
+ if (!existsSync38(DB_PATH)) {
26731
28759
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
26732
28760
  process.exit(1);
26733
28761
  }
@@ -26751,7 +28779,7 @@ async function memoryList(globalOpts) {
26751
28779
  });
26752
28780
  }
26753
28781
  async function memorySearch(globalOpts, query) {
26754
- if (!existsSync37(DB_PATH)) {
28782
+ if (!existsSync38(DB_PATH)) {
26755
28783
  outputError("DB_NOT_FOUND", "Database not found.");
26756
28784
  process.exit(1);
26757
28785
  }
@@ -26773,14 +28801,14 @@ async function memorySearch(globalOpts, query) {
26773
28801
  });
26774
28802
  }
26775
28803
  async function memoryHistory(globalOpts, opts) {
26776
- if (!existsSync37(DB_PATH)) {
28804
+ if (!existsSync38(DB_PATH)) {
26777
28805
  outputError("DB_NOT_FOUND", "Database not found.");
26778
28806
  process.exit(1);
26779
28807
  }
26780
28808
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
26781
28809
  const readDb = openDatabaseReadOnly2();
26782
28810
  const limit = parseInt(opts.limit ?? "10", 10);
26783
- const chatId = resolveChatId(globalOpts);
28811
+ const chatId = resolveChatId2(globalOpts);
26784
28812
  const summaries = readDb.prepare(
26785
28813
  "SELECT * FROM session_summaries WHERE chat_id = ? ORDER BY created_at DESC LIMIT ?"
26786
28814
  ).all(chatId, limit);
@@ -26821,7 +28849,7 @@ __export(cron_exports2, {
26821
28849
  cronList: () => cronList,
26822
28850
  cronRuns: () => cronRuns
26823
28851
  });
26824
- import { existsSync as existsSync38 } from "fs";
28852
+ import { existsSync as existsSync39 } from "fs";
26825
28853
  function parseFallbacks(raw) {
26826
28854
  return raw.slice(0, 3).map((f) => {
26827
28855
  const [backend2, ...rest] = f.split(":");
@@ -26842,7 +28870,7 @@ function parseAndValidateTimeout(raw) {
26842
28870
  return val;
26843
28871
  }
26844
28872
  async function cronList(globalOpts) {
26845
- if (!existsSync38(DB_PATH)) {
28873
+ if (!existsSync39(DB_PATH)) {
26846
28874
  outputError("DB_NOT_FOUND", "Database not found.");
26847
28875
  process.exit(1);
26848
28876
  }
@@ -26880,7 +28908,7 @@ async function cronList(globalOpts) {
26880
28908
  });
26881
28909
  }
26882
28910
  async function cronHealth(globalOpts) {
26883
- if (!existsSync38(DB_PATH)) {
28911
+ if (!existsSync39(DB_PATH)) {
26884
28912
  outputError("DB_NOT_FOUND", "Database not found.");
26885
28913
  process.exit(1);
26886
28914
  }
@@ -26918,7 +28946,7 @@ async function cronCreate(globalOpts, opts) {
26918
28946
  outputError("MISSING_SCHEDULE", "Must specify one of: --cron, --at, or --every");
26919
28947
  process.exit(1);
26920
28948
  }
26921
- const chatId = resolveChatId(globalOpts);
28949
+ const chatId = resolveChatId2(globalOpts);
26922
28950
  const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
26923
28951
  const timeout = parseAndValidateTimeout(opts.timeout);
26924
28952
  try {
@@ -27039,7 +29067,7 @@ async function cronEdit(globalOpts, id, opts) {
27039
29067
  }
27040
29068
  }
27041
29069
  async function cronRuns(globalOpts, jobId, opts) {
27042
- if (!existsSync38(DB_PATH)) {
29070
+ if (!existsSync39(DB_PATH)) {
27043
29071
  outputError("DB_NOT_FOUND", "Database not found.");
27044
29072
  process.exit(1);
27045
29073
  }
@@ -27086,9 +29114,9 @@ __export(agents_exports, {
27086
29114
  runnersList: () => runnersList,
27087
29115
  tasksList: () => tasksList
27088
29116
  });
27089
- import { existsSync as existsSync39 } from "fs";
29117
+ import { existsSync as existsSync40 } from "fs";
27090
29118
  async function agentsList(globalOpts) {
27091
- if (!existsSync39(DB_PATH)) {
29119
+ if (!existsSync40(DB_PATH)) {
27092
29120
  outputError("DB_NOT_FOUND", "Database not found.");
27093
29121
  process.exit(1);
27094
29122
  }
@@ -27119,7 +29147,7 @@ async function agentsList(globalOpts) {
27119
29147
  });
27120
29148
  }
27121
29149
  async function tasksList(globalOpts) {
27122
- if (!existsSync39(DB_PATH)) {
29150
+ if (!existsSync40(DB_PATH)) {
27123
29151
  outputError("DB_NOT_FOUND", "Database not found.");
27124
29152
  process.exit(1);
27125
29153
  }
@@ -27149,7 +29177,7 @@ async function agentsSpawn(globalOpts, opts) {
27149
29177
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
27150
29178
  process.exit(1);
27151
29179
  }
27152
- const chatId = resolveChatId(globalOpts);
29180
+ const chatId = resolveChatId2(globalOpts);
27153
29181
  const res = await apiPost2("/api/orchestrator/spawn", {
27154
29182
  chatId,
27155
29183
  runner: opts.runner,
@@ -27192,7 +29220,7 @@ async function agentsCancelAll(globalOpts) {
27192
29220
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.");
27193
29221
  process.exit(1);
27194
29222
  }
27195
- const chatId = resolveChatId(globalOpts);
29223
+ const chatId = resolveChatId2(globalOpts);
27196
29224
  const res = await apiPost2("/api/orchestrator/cancel-all", { chatId });
27197
29225
  if (res.ok) {
27198
29226
  const { success: s } = await Promise.resolve().then(() => (init_format2(), format_exports));
@@ -27247,10 +29275,10 @@ __export(db_exports, {
27247
29275
  dbPath: () => dbPath,
27248
29276
  dbStats: () => dbStats
27249
29277
  });
27250
- import { existsSync as existsSync40, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
29278
+ import { existsSync as existsSync41, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
27251
29279
  import { dirname as dirname7 } from "path";
27252
29280
  async function dbStats(globalOpts) {
27253
- if (!existsSync40(DB_PATH)) {
29281
+ if (!existsSync41(DB_PATH)) {
27254
29282
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
27255
29283
  process.exit(1);
27256
29284
  }
@@ -27258,7 +29286,7 @@ async function dbStats(globalOpts) {
27258
29286
  const readDb = openDatabaseReadOnly2();
27259
29287
  const mainSize = statSync11(DB_PATH).size;
27260
29288
  const walPath = DB_PATH + "-wal";
27261
- const walSize = existsSync40(walPath) ? statSync11(walPath).size : 0;
29289
+ const walSize = existsSync41(walPath) ? statSync11(walPath).size : 0;
27262
29290
  const tableNames = readDb.prepare(
27263
29291
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
27264
29292
  ).all();
@@ -27292,7 +29320,7 @@ async function dbPath(globalOpts) {
27292
29320
  output({ path: DB_PATH }, (d) => d.path);
27293
29321
  }
27294
29322
  async function dbBackup(globalOpts, destPath) {
27295
- if (!existsSync40(DB_PATH)) {
29323
+ if (!existsSync41(DB_PATH)) {
27296
29324
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
27297
29325
  process.exit(1);
27298
29326
  }
@@ -27301,7 +29329,7 @@ async function dbBackup(globalOpts, destPath) {
27301
29329
  mkdirSync16(dirname7(dest), { recursive: true });
27302
29330
  copyFileSync3(DB_PATH, dest);
27303
29331
  const walPath = DB_PATH + "-wal";
27304
- if (existsSync40(walPath)) copyFileSync3(walPath, dest + "-wal");
29332
+ if (existsSync41(walPath)) copyFileSync3(walPath, dest + "-wal");
27305
29333
  output({ path: dest, sizeBytes: statSync11(dest).size }, (d) => {
27306
29334
  const b = d;
27307
29335
  return `
@@ -27330,9 +29358,9 @@ __export(usage_exports, {
27330
29358
  usageCost: () => usageCost,
27331
29359
  usageTokens: () => usageTokens
27332
29360
  });
27333
- import { existsSync as existsSync41 } from "fs";
29361
+ import { existsSync as existsSync42 } from "fs";
27334
29362
  function ensureDb() {
27335
- if (!existsSync41(DB_PATH)) {
29363
+ if (!existsSync42(DB_PATH)) {
27336
29364
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
27337
29365
  process.exit(1);
27338
29366
  }
@@ -27384,7 +29412,7 @@ async function usageCost(globalOpts, opts) {
27384
29412
  return lines.join("\n");
27385
29413
  });
27386
29414
  } else {
27387
- const chatId = resolveChatId(globalOpts);
29415
+ const chatId = resolveChatId2(globalOpts);
27388
29416
  const usage2 = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
27389
29417
  readDb.close();
27390
29418
  if (!usage2 || usage2.request_count === 0) {
@@ -27522,15 +29550,15 @@ __export(config_exports2, {
27522
29550
  configList: () => configList,
27523
29551
  configSet: () => configSet
27524
29552
  });
27525
- import { existsSync as existsSync42, readFileSync as readFileSync24 } from "fs";
29553
+ import { existsSync as existsSync43, readFileSync as readFileSync24 } from "fs";
27526
29554
  async function configList(globalOpts) {
27527
- if (!existsSync42(DB_PATH)) {
29555
+ if (!existsSync43(DB_PATH)) {
27528
29556
  outputError("DB_NOT_FOUND", "Database not found.");
27529
29557
  process.exit(1);
27530
29558
  }
27531
29559
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
27532
29560
  const readDb = openDatabaseReadOnly2();
27533
- const chatId = resolveChatId(globalOpts);
29561
+ const chatId = resolveChatId2(globalOpts);
27534
29562
  const config2 = {};
27535
29563
  for (const key of RUNTIME_KEYS) {
27536
29564
  const { table, col } = KEY_TABLE_MAP[key];
@@ -27558,13 +29586,13 @@ async function configGet(globalOpts, key) {
27558
29586
  outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
27559
29587
  process.exit(1);
27560
29588
  }
27561
- if (!existsSync42(DB_PATH)) {
29589
+ if (!existsSync43(DB_PATH)) {
27562
29590
  outputError("DB_NOT_FOUND", "Database not found.");
27563
29591
  process.exit(1);
27564
29592
  }
27565
29593
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
27566
29594
  const readDb = openDatabaseReadOnly2();
27567
- const chatId = resolveChatId(globalOpts);
29595
+ const chatId = resolveChatId2(globalOpts);
27568
29596
  const { table, col } = KEY_TABLE_MAP[key];
27569
29597
  if (!ALLOWED_TABLES.has(table) || !ALLOWED_COLS.has(col)) {
27570
29598
  outputError("INVALID_KEY", `Invalid config mapping for "${key}".`);
@@ -27592,7 +29620,7 @@ async function configSet(globalOpts, key, value) {
27592
29620
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
27593
29621
  process.exit(1);
27594
29622
  }
27595
- const chatId = resolveChatId(globalOpts);
29623
+ const chatId = resolveChatId2(globalOpts);
27596
29624
  const res = await apiPost2("/api/config/set", { chatId, key, value });
27597
29625
  if (res.ok) {
27598
29626
  output({ key, value, success: true }, () => `
@@ -27604,7 +29632,7 @@ async function configSet(globalOpts, key, value) {
27604
29632
  }
27605
29633
  }
27606
29634
  async function configEnv(_globalOpts) {
27607
- if (!existsSync42(ENV_PATH)) {
29635
+ if (!existsSync43(ENV_PATH)) {
27608
29636
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
27609
29637
  process.exit(1);
27610
29638
  }
@@ -27658,15 +29686,15 @@ __export(session_exports, {
27658
29686
  sessionGet: () => sessionGet,
27659
29687
  sessionNew: () => sessionNew
27660
29688
  });
27661
- import { existsSync as existsSync43 } from "fs";
29689
+ import { existsSync as existsSync44 } from "fs";
27662
29690
  async function sessionGet(globalOpts) {
27663
- if (!existsSync43(DB_PATH)) {
29691
+ if (!existsSync44(DB_PATH)) {
27664
29692
  outputError("DB_NOT_FOUND", "Database not found.");
27665
29693
  process.exit(1);
27666
29694
  }
27667
29695
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
27668
29696
  const readDb = openDatabaseReadOnly2();
27669
- const chatId = resolveChatId(globalOpts);
29697
+ const chatId = resolveChatId2(globalOpts);
27670
29698
  const session2 = readDb.prepare("SELECT * FROM sessions WHERE chat_id = ?").get(chatId);
27671
29699
  readDb.close();
27672
29700
  const data = session2 ? { id: session2.session_id, active: true, updatedAt: session2.updated_at } : { id: null, active: false, updatedAt: null };
@@ -27689,7 +29717,7 @@ async function sessionNew(globalOpts) {
27689
29717
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
27690
29718
  process.exit(1);
27691
29719
  }
27692
- const chatId = resolveChatId(globalOpts);
29720
+ const chatId = resolveChatId2(globalOpts);
27693
29721
  const res = await apiPost2("/api/session/new", { chatId });
27694
29722
  if (res.ok) {
27695
29723
  output({ success: true }, () => `
@@ -27721,9 +29749,9 @@ __export(permissions_exports, {
27721
29749
  verboseGet: () => verboseGet,
27722
29750
  verboseSet: () => verboseSet
27723
29751
  });
27724
- import { existsSync as existsSync44 } from "fs";
29752
+ import { existsSync as existsSync45 } from "fs";
27725
29753
  function ensureDb2() {
27726
- if (!existsSync44(DB_PATH)) {
29754
+ if (!existsSync45(DB_PATH)) {
27727
29755
  outputError("DB_NOT_FOUND", "Database not found.");
27728
29756
  process.exit(1);
27729
29757
  }
@@ -27732,7 +29760,7 @@ async function permissionsGet(globalOpts) {
27732
29760
  ensureDb2();
27733
29761
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
27734
29762
  const readDb = openDatabaseReadOnly2();
27735
- const chatId = resolveChatId(globalOpts);
29763
+ const chatId = resolveChatId2(globalOpts);
27736
29764
  const row = readDb.prepare("SELECT mode FROM chat_mode WHERE chat_id = ?").get(chatId);
27737
29765
  readDb.close();
27738
29766
  const mode = row?.mode ?? "yolo";
@@ -27750,7 +29778,7 @@ async function permissionsSet(globalOpts, mode) {
27750
29778
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
27751
29779
  process.exit(1);
27752
29780
  }
27753
- const chatId = resolveChatId(globalOpts);
29781
+ const chatId = resolveChatId2(globalOpts);
27754
29782
  const res = await apiPost2("/api/permissions/set", { chatId, mode });
27755
29783
  if (res.ok) {
27756
29784
  output({ mode, success: true }, () => `
@@ -27765,7 +29793,7 @@ async function toolsList(globalOpts) {
27765
29793
  ensureDb2();
27766
29794
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
27767
29795
  const readDb = openDatabaseReadOnly2();
27768
- const chatId = resolveChatId(globalOpts);
29796
+ const chatId = resolveChatId2(globalOpts);
27769
29797
  const rows = readDb.prepare("SELECT tool, enabled FROM chat_tools WHERE chat_id = ?").all(chatId);
27770
29798
  readDb.close();
27771
29799
  const ALL_TOOLS3 = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebFetch", "WebSearch", "Agent", "AskUserQuestion"];
@@ -27793,7 +29821,7 @@ async function toolsReset(globalOpts) {
27793
29821
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
27794
29822
  process.exit(1);
27795
29823
  }
27796
- const chatId = resolveChatId(globalOpts);
29824
+ const chatId = resolveChatId2(globalOpts);
27797
29825
  const res = await apiPost2("/api/tools/reset", { chatId });
27798
29826
  if (res.ok) {
27799
29827
  output({ success: true }, () => `
@@ -27810,7 +29838,7 @@ async function toggleTool4(globalOpts, name, enabled) {
27810
29838
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
27811
29839
  process.exit(1);
27812
29840
  }
27813
- const chatId = resolveChatId(globalOpts);
29841
+ const chatId = resolveChatId2(globalOpts);
27814
29842
  const res = await apiPost2("/api/tools/toggle", { chatId, tool: name, enabled });
27815
29843
  if (res.ok) {
27816
29844
  const label2 = enabled ? "enabled" : "disabled";
@@ -27826,7 +29854,7 @@ async function verboseGet(globalOpts) {
27826
29854
  ensureDb2();
27827
29855
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
27828
29856
  const readDb = openDatabaseReadOnly2();
27829
- const chatId = resolveChatId(globalOpts);
29857
+ const chatId = resolveChatId2(globalOpts);
27830
29858
  const row = readDb.prepare("SELECT level FROM chat_verbose WHERE chat_id = ?").get(chatId);
27831
29859
  readDb.close();
27832
29860
  const level = row?.level ?? "off";
@@ -27843,7 +29871,7 @@ async function verboseSet(globalOpts, level) {
27843
29871
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
27844
29872
  process.exit(1);
27845
29873
  }
27846
- const chatId = resolveChatId(globalOpts);
29874
+ const chatId = resolveChatId2(globalOpts);
27847
29875
  const res = await apiPost2("/api/verbose/set", { chatId, level });
27848
29876
  if (res.ok) {
27849
29877
  output({ level, success: true }, () => `
@@ -27870,15 +29898,15 @@ __export(cwd_exports, {
27870
29898
  cwdGet: () => cwdGet,
27871
29899
  cwdSet: () => cwdSet
27872
29900
  });
27873
- import { existsSync as existsSync45 } from "fs";
29901
+ import { existsSync as existsSync46 } from "fs";
27874
29902
  async function cwdGet(globalOpts) {
27875
- if (!existsSync45(DB_PATH)) {
29903
+ if (!existsSync46(DB_PATH)) {
27876
29904
  outputError("DB_NOT_FOUND", "Database not found.");
27877
29905
  process.exit(1);
27878
29906
  }
27879
29907
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
27880
29908
  const readDb = openDatabaseReadOnly2();
27881
- const chatId = resolveChatId(globalOpts);
29909
+ const chatId = resolveChatId2(globalOpts);
27882
29910
  const row = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
27883
29911
  readDb.close();
27884
29912
  const cwd = row?.cwd ?? null;
@@ -27890,7 +29918,7 @@ async function cwdSet(globalOpts, path) {
27890
29918
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
27891
29919
  process.exit(1);
27892
29920
  }
27893
- const chatId = resolveChatId(globalOpts);
29921
+ const chatId = resolveChatId2(globalOpts);
27894
29922
  const resolved = path.startsWith("~") ? path.replace("~", process.env.HOME ?? "") : path;
27895
29923
  const res = await apiPost2("/api/cwd/set", { chatId, cwd: resolved });
27896
29924
  if (res.ok) {
@@ -27908,7 +29936,7 @@ async function cwdClear(globalOpts) {
27908
29936
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
27909
29937
  process.exit(1);
27910
29938
  }
27911
- const chatId = resolveChatId(globalOpts);
29939
+ const chatId = resolveChatId2(globalOpts);
27912
29940
  const res = await apiPost2("/api/cwd/clear", { chatId });
27913
29941
  if (res.ok) {
27914
29942
  output({ success: true }, () => `
@@ -27934,15 +29962,15 @@ __export(voice_exports, {
27934
29962
  voiceGet: () => voiceGet,
27935
29963
  voiceSet: () => voiceSet
27936
29964
  });
27937
- import { existsSync as existsSync46 } from "fs";
29965
+ import { existsSync as existsSync47 } from "fs";
27938
29966
  async function voiceGet(globalOpts) {
27939
- if (!existsSync46(DB_PATH)) {
29967
+ if (!existsSync47(DB_PATH)) {
27940
29968
  outputError("DB_NOT_FOUND", "Database not found.");
27941
29969
  process.exit(1);
27942
29970
  }
27943
29971
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
27944
29972
  const readDb = openDatabaseReadOnly2();
27945
- const chatId = resolveChatId(globalOpts);
29973
+ const chatId = resolveChatId2(globalOpts);
27946
29974
  const row = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
27947
29975
  readDb.close();
27948
29976
  const enabled = !!row?.enabled;
@@ -27959,7 +29987,7 @@ async function voiceSet(globalOpts, value) {
27959
29987
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
27960
29988
  process.exit(1);
27961
29989
  }
27962
- const chatId = resolveChatId(globalOpts);
29990
+ const chatId = resolveChatId2(globalOpts);
27963
29991
  const res = await apiPost2("/api/voice/set", { chatId, enabled: value === "on" });
27964
29992
  if (res.ok) {
27965
29993
  output({ enabled: value === "on", success: true }, () => `
@@ -27985,15 +30013,15 @@ __export(heartbeat_exports, {
27985
30013
  heartbeatGet: () => heartbeatGet,
27986
30014
  heartbeatSet: () => heartbeatSet
27987
30015
  });
27988
- import { existsSync as existsSync47 } from "fs";
30016
+ import { existsSync as existsSync48 } from "fs";
27989
30017
  async function heartbeatGet(globalOpts) {
27990
- if (!existsSync47(DB_PATH)) {
30018
+ if (!existsSync48(DB_PATH)) {
27991
30019
  outputError("DB_NOT_FOUND", "Database not found.");
27992
30020
  process.exit(1);
27993
30021
  }
27994
30022
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
27995
30023
  const readDb = openDatabaseReadOnly2();
27996
- const chatId = resolveChatId(globalOpts);
30024
+ const chatId = resolveChatId2(globalOpts);
27997
30025
  const row = readDb.prepare("SELECT * FROM chat_heartbeat WHERE chat_id = ?").get(chatId);
27998
30026
  readDb.close();
27999
30027
  const data = row ? {
@@ -28025,7 +30053,7 @@ async function heartbeatSet(globalOpts, subcommand, value) {
28025
30053
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
28026
30054
  process.exit(1);
28027
30055
  }
28028
- const chatId = resolveChatId(globalOpts);
30056
+ const chatId = resolveChatId2(globalOpts);
28029
30057
  let body = { chatId };
28030
30058
  let successMsg = "";
28031
30059
  switch (subcommand) {
@@ -28096,15 +30124,15 @@ __export(summarizer_exports, {
28096
30124
  summarizerGet: () => summarizerGet,
28097
30125
  summarizerSet: () => summarizerSet
28098
30126
  });
28099
- import { existsSync as existsSync48 } from "fs";
30127
+ import { existsSync as existsSync49 } from "fs";
28100
30128
  async function summarizerGet(globalOpts) {
28101
- if (!existsSync48(DB_PATH)) {
30129
+ if (!existsSync49(DB_PATH)) {
28102
30130
  outputError("DB_NOT_FOUND", "Database not found.");
28103
30131
  process.exit(1);
28104
30132
  }
28105
30133
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28106
30134
  const readDb = openDatabaseReadOnly2();
28107
- const chatId = resolveChatId(globalOpts);
30135
+ const chatId = resolveChatId2(globalOpts);
28108
30136
  const row = readDb.prepare("SELECT backend, model FROM chat_summarizer WHERE chat_id = ?").get(chatId);
28109
30137
  readDb.close();
28110
30138
  const value = row?.backend === "off" ? "off" : row?.backend ? `${row.backend}:${row.model ?? "default"}` : "auto";
@@ -28116,7 +30144,7 @@ async function summarizerSet(globalOpts, value) {
28116
30144
  outputError("DAEMON_OFFLINE", "Daemon not running.");
28117
30145
  process.exit(1);
28118
30146
  }
28119
- const chatId = resolveChatId(globalOpts);
30147
+ const chatId = resolveChatId2(globalOpts);
28120
30148
  const res = await apiPost2("/api/summarizer/set", { chatId, value });
28121
30149
  if (res.ok) {
28122
30150
  output({ success: true }, () => `
@@ -28142,15 +30170,15 @@ __export(thinking_exports, {
28142
30170
  thinkingGet: () => thinkingGet,
28143
30171
  thinkingSet: () => thinkingSet
28144
30172
  });
28145
- import { existsSync as existsSync49 } from "fs";
30173
+ import { existsSync as existsSync50 } from "fs";
28146
30174
  async function thinkingGet(globalOpts) {
28147
- if (!existsSync49(DB_PATH)) {
30175
+ if (!existsSync50(DB_PATH)) {
28148
30176
  outputError("DB_NOT_FOUND", "Database not found.");
28149
30177
  process.exit(1);
28150
30178
  }
28151
30179
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28152
30180
  const readDb = openDatabaseReadOnly2();
28153
- const chatId = resolveChatId(globalOpts);
30181
+ const chatId = resolveChatId2(globalOpts);
28154
30182
  const row = readDb.prepare("SELECT level FROM chat_thinking WHERE chat_id = ?").get(chatId);
28155
30183
  readDb.close();
28156
30184
  output({ level: row?.level ?? "auto" }, (d) => d.level);
@@ -28161,7 +30189,7 @@ async function thinkingSet(globalOpts, level) {
28161
30189
  outputError("DAEMON_OFFLINE", "Daemon not running.");
28162
30190
  process.exit(1);
28163
30191
  }
28164
- const chatId = resolveChatId(globalOpts);
30192
+ const chatId = resolveChatId2(globalOpts);
28165
30193
  const res = await apiPost2("/api/thinking/set", { chatId, level });
28166
30194
  if (res.ok) {
28167
30195
  output({ success: true }, () => `
@@ -28188,9 +30216,9 @@ __export(chats_exports, {
28188
30216
  chatsList: () => chatsList,
28189
30217
  chatsRemoveAlias: () => chatsRemoveAlias
28190
30218
  });
28191
- import { existsSync as existsSync50 } from "fs";
30219
+ import { existsSync as existsSync51 } from "fs";
28192
30220
  async function chatsList(_globalOpts) {
28193
- if (!existsSync50(DB_PATH)) {
30221
+ if (!existsSync51(DB_PATH)) {
28194
30222
  outputError("DB_NOT_FOUND", "Database not found.");
28195
30223
  process.exit(1);
28196
30224
  }
@@ -28318,9 +30346,9 @@ var mcps_exports2 = {};
28318
30346
  __export(mcps_exports2, {
28319
30347
  mcpsList: () => mcpsList
28320
30348
  });
28321
- import { existsSync as existsSync51 } from "fs";
30349
+ import { existsSync as existsSync52 } from "fs";
28322
30350
  async function mcpsList(_globalOpts) {
28323
- if (!existsSync51(DB_PATH)) {
30351
+ if (!existsSync52(DB_PATH)) {
28324
30352
  outputError("DB_NOT_FOUND", "Database not found.");
28325
30353
  process.exit(1);
28326
30354
  }
@@ -28357,11 +30385,11 @@ __export(chat_exports2, {
28357
30385
  chatSend: () => chatSend
28358
30386
  });
28359
30387
  import { request as httpRequest2 } from "http";
28360
- import { readFileSync as readFileSync25, existsSync as existsSync52 } from "fs";
30388
+ import { readFileSync as readFileSync25, existsSync as existsSync53 } from "fs";
28361
30389
  function getToken2() {
28362
30390
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
28363
30391
  try {
28364
- if (existsSync52(TOKEN_PATH2)) return readFileSync25(TOKEN_PATH2, "utf-8").trim();
30392
+ if (existsSync53(TOKEN_PATH2)) return readFileSync25(TOKEN_PATH2, "utf-8").trim();
28365
30393
  } catch {
28366
30394
  }
28367
30395
  return null;
@@ -28372,7 +30400,7 @@ async function chatSend(globalOpts, message, cmdOpts) {
28372
30400
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
28373
30401
  process.exit(1);
28374
30402
  }
28375
- const chatId = resolveChatId(globalOpts);
30403
+ const chatId = resolveChatId2(globalOpts);
28376
30404
  const token = getToken2();
28377
30405
  const payload = JSON.stringify({
28378
30406
  chatId,
@@ -28506,7 +30534,7 @@ async function tuiCommand(globalOpts, cmdOpts) {
28506
30534
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
28507
30535
  process.exit(1);
28508
30536
  }
28509
- const chatId = resolveChatId(globalOpts);
30537
+ const chatId = resolveChatId2(globalOpts);
28510
30538
  const { chatSend: chatSend2 } = await Promise.resolve().then(() => (init_chat2(), chat_exports2));
28511
30539
  const rl2 = createInterface10({
28512
30540
  input: process.stdin,
@@ -28831,9 +30859,9 @@ __export(evolve_exports2, {
28831
30859
  evolveStatus: () => evolveStatus,
28832
30860
  evolveUndo: () => evolveUndo
28833
30861
  });
28834
- import { existsSync as existsSync53 } from "fs";
30862
+ import { existsSync as existsSync54 } from "fs";
28835
30863
  function ensureDb3() {
28836
- if (!existsSync53(DB_PATH)) {
30864
+ if (!existsSync54(DB_PATH)) {
28837
30865
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
28838
30866
  process.exit(1);
28839
30867
  }
@@ -28849,7 +30877,7 @@ async function evolveStatus(globalOpts) {
28849
30877
  ensureDb3();
28850
30878
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28851
30879
  const readDb = openDatabaseReadOnly2();
28852
- const chatId = resolveChatId(globalOpts);
30880
+ const chatId = resolveChatId2(globalOpts);
28853
30881
  const { getReflectionStatus: getReflectionStatus2, getPendingInsightCount: getPendingInsightCount2, getReflectionModelConfig: getReflectionModelConfig2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
28854
30882
  const status = getReflectionStatus2(readDb, chatId);
28855
30883
  const pendingCount = getPendingInsightCount2(readDb, chatId);
@@ -28889,7 +30917,7 @@ async function evolveStatus(globalOpts) {
28889
30917
  }
28890
30918
  async function evolveAnalyze(globalOpts) {
28891
30919
  await ensureDaemon();
28892
- const chatId = resolveChatId(globalOpts);
30920
+ const chatId = resolveChatId2(globalOpts);
28893
30921
  const { apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
28894
30922
  const res = await apiPost2("/api/evolve/analyze", { chatId });
28895
30923
  if (res.ok) {
@@ -28906,7 +30934,7 @@ async function evolveList(globalOpts) {
28906
30934
  ensureDb3();
28907
30935
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28908
30936
  const readDb = openDatabaseReadOnly2();
28909
- const chatId = resolveChatId(globalOpts);
30937
+ const chatId = resolveChatId2(globalOpts);
28910
30938
  const { getPendingInsights: getPendingInsights2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
28911
30939
  const insights = getPendingInsights2(readDb, chatId);
28912
30940
  readDb.close();
@@ -29051,7 +31079,7 @@ async function evolveUndo(globalOpts, idStr) {
29051
31079
  }
29052
31080
  async function evolveOn(globalOpts) {
29053
31081
  await ensureDaemon();
29054
- const chatId = resolveChatId(globalOpts);
31082
+ const chatId = resolveChatId2(globalOpts);
29055
31083
  const { apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
29056
31084
  const res = await apiPost2("/api/evolve/on", { chatId });
29057
31085
  if (res.ok) {
@@ -29065,7 +31093,7 @@ async function evolveOn(globalOpts) {
29065
31093
  }
29066
31094
  async function evolveOff(globalOpts) {
29067
31095
  await ensureDaemon();
29068
- const chatId = resolveChatId(globalOpts);
31096
+ const chatId = resolveChatId2(globalOpts);
29069
31097
  const { apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
29070
31098
  const res = await apiPost2("/api/evolve/off", { chatId });
29071
31099
  if (res.ok) {
@@ -29082,7 +31110,7 @@ async function evolveModel(globalOpts, mode, opts) {
29082
31110
  ensureDb3();
29083
31111
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
29084
31112
  const readDb = openDatabaseReadOnly2();
29085
- const chatId2 = resolveChatId(globalOpts);
31113
+ const chatId2 = resolveChatId2(globalOpts);
29086
31114
  const { getReflectionModelConfig: getReflectionModelConfig2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
29087
31115
  const config2 = getReflectionModelConfig2(readDb, chatId2);
29088
31116
  readDb.close();
@@ -29095,7 +31123,7 @@ async function evolveModel(globalOpts, mode, opts) {
29095
31123
  return;
29096
31124
  }
29097
31125
  await ensureDaemon();
29098
- const chatId = resolveChatId(globalOpts);
31126
+ const chatId = resolveChatId2(globalOpts);
29099
31127
  if (!["auto", "pinned", "cheap"].includes(mode)) {
29100
31128
  outputError("INVALID_MODE", "Mode must be auto, pinned, or cheap.");
29101
31129
  process.exit(1);
@@ -29120,7 +31148,7 @@ async function evolveStats(globalOpts, opts) {
29120
31148
  ensureDb3();
29121
31149
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
29122
31150
  const readDb = openDatabaseReadOnly2();
29123
- const chatId = resolveChatId(globalOpts);
31151
+ const chatId = resolveChatId2(globalOpts);
29124
31152
  const days = parseInt(opts.days ?? "30", 10);
29125
31153
  const { buildGrowthReportData: buildGrowthReportData2 } = await Promise.resolve().then(() => (init_metrics(), metrics_exports));
29126
31154
  const report = buildGrowthReportData2(readDb, chatId, days);
@@ -29149,7 +31177,7 @@ async function evolveHistory(globalOpts, opts) {
29149
31177
  ensureDb3();
29150
31178
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
29151
31179
  const readDb = openDatabaseReadOnly2();
29152
- const chatId = resolveChatId(globalOpts);
31180
+ const chatId = resolveChatId2(globalOpts);
29153
31181
  const limit = parseInt(opts.limit ?? "20", 10);
29154
31182
  let insights;
29155
31183
  if (opts.status) {
@@ -29186,7 +31214,7 @@ async function evolveHistory(globalOpts, opts) {
29186
31214
  async function evolveSettings(globalOpts, opts) {
29187
31215
  ensureDb3();
29188
31216
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
29189
- const chatId = resolveChatId(globalOpts);
31217
+ const chatId = resolveChatId2(globalOpts);
29190
31218
  const hasUpdates = opts.perFileCap !== void 0 || opts.backupRetentionDays !== void 0;
29191
31219
  if (hasUpdates) {
29192
31220
  await ensureDaemon();
@@ -29307,7 +31335,7 @@ var init_optimize2 = __esm({
29307
31335
 
29308
31336
  // src/setup.ts
29309
31337
  var setup_exports = {};
29310
- import { existsSync as existsSync54, writeFileSync as writeFileSync12, readFileSync as readFileSync26, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
31338
+ import { existsSync as existsSync55, writeFileSync as writeFileSync12, readFileSync as readFileSync26, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
29311
31339
  import { execFileSync as execFileSync5 } from "child_process";
29312
31340
  import { createInterface as createInterface11 } from "readline";
29313
31341
  import { join as join34 } from "path";
@@ -29385,10 +31413,10 @@ async function setup() {
29385
31413
  }
29386
31414
  console.log("");
29387
31415
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
29388
- if (!existsSync54(dir)) mkdirSync18(dir, { recursive: true });
31416
+ if (!existsSync55(dir)) mkdirSync18(dir, { recursive: true });
29389
31417
  }
29390
31418
  const env = {};
29391
- const envSource = existsSync54(ENV_PATH) ? ENV_PATH : existsSync54(".env") ? ".env" : null;
31419
+ const envSource = existsSync55(ENV_PATH) ? ENV_PATH : existsSync55(".env") ? ".env" : null;
29392
31420
  if (envSource) {
29393
31421
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
29394
31422
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
@@ -29399,7 +31427,7 @@ async function setup() {
29399
31427
  }
29400
31428
  }
29401
31429
  const cwdDb = join34(process.cwd(), "cc-claw.db");
29402
- if (existsSync54(cwdDb) && !existsSync54(DB_PATH)) {
31430
+ if (existsSync55(cwdDb) && !existsSync55(DB_PATH)) {
29403
31431
  const { size } = statSync12(cwdDb);
29404
31432
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
29405
31433
  const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
@@ -29624,7 +31652,7 @@ async function setup() {
29624
31652
  const installDaemon = await confirm("Install CC-Claw as a background service?", true);
29625
31653
  if (installDaemon) {
29626
31654
  try {
29627
- const { installService: installService2 } = await Promise.resolve().then(() => (init_service(), service_exports));
31655
+ const { installService: installService2 } = await Promise.resolve().then(() => (init_service2(), service_exports2));
29628
31656
  installService2();
29629
31657
  console.log("");
29630
31658
  console.log(dim(" Useful commands:"));
@@ -29700,7 +31728,7 @@ function registerServiceCommands(cmd) {
29700
31728
  if (opts.foreground) {
29701
31729
  await Promise.resolve().then(() => (init_index(), index_exports));
29702
31730
  } else {
29703
- const { installService: installService2 } = await Promise.resolve().then(() => (init_service(), service_exports));
31731
+ const { installService: installService2 } = await Promise.resolve().then(() => (init_service2(), service_exports2));
29704
31732
  installService2();
29705
31733
  }
29706
31734
  });
@@ -29713,15 +31741,15 @@ function registerServiceCommands(cmd) {
29713
31741
  await restartService2();
29714
31742
  });
29715
31743
  cmd.command("status").description("Service health (launchd/systemd)").action(async () => {
29716
- const { serviceStatus: serviceStatus2 } = await Promise.resolve().then(() => (init_service(), service_exports));
31744
+ const { serviceStatus: serviceStatus2 } = await Promise.resolve().then(() => (init_service2(), service_exports2));
29717
31745
  serviceStatus2();
29718
31746
  });
29719
31747
  cmd.command("install").description("Install as background service").action(async () => {
29720
- const { installService: installService2 } = await Promise.resolve().then(() => (init_service(), service_exports));
31748
+ const { installService: installService2 } = await Promise.resolve().then(() => (init_service2(), service_exports2));
29721
31749
  installService2();
29722
31750
  });
29723
31751
  cmd.command("uninstall").description("Remove background service").action(async () => {
29724
- const { uninstallService: uninstallService2 } = await Promise.resolve().then(() => (init_service(), service_exports));
31752
+ const { uninstallService: uninstallService2 } = await Promise.resolve().then(() => (init_service2(), service_exports2));
29725
31753
  uninstallService2();
29726
31754
  });
29727
31755
  }
@@ -29824,6 +31852,35 @@ function registerUnifiedSlotCommands(parentCmd, backendId, displayName) {
29824
31852
  }
29825
31853
  registerUnifiedSlotCommands(program, "claude", "Claude");
29826
31854
  registerUnifiedSlotCommands(program, "codex", "Codex");
31855
+ var ollama = program.command("ollama").description("Manage Ollama local LLM servers and models");
31856
+ ollama.command("list").description("List all configured Ollama servers").action(async () => {
31857
+ const { ollamaList: ollamaList2 } = await Promise.resolve().then(() => (init_ollama4(), ollama_exports3));
31858
+ await ollamaList2(program.opts());
31859
+ });
31860
+ ollama.command("add <name> <host>").description("Add an Ollama server").option("--port <n>", "Port (default: 11434)").option("--key <apiKey>", "API key (for remote/proxy servers)").action(async (name, host, opts) => {
31861
+ const { ollamaAdd: ollamaAdd2 } = await Promise.resolve().then(() => (init_ollama4(), ollama_exports3));
31862
+ await ollamaAdd2(program.opts(), name, host, opts);
31863
+ });
31864
+ ollama.command("remove <name>").description("Remove an Ollama server and its cached models").action(async (name) => {
31865
+ const { ollamaRemove: ollamaRemove2 } = await Promise.resolve().then(() => (init_ollama4(), ollama_exports3));
31866
+ await ollamaRemove2(program.opts(), name);
31867
+ });
31868
+ ollama.command("models").description("List discovered models across all servers").option("--server <name>", "Filter to a specific server").action(async (opts) => {
31869
+ const { ollamaModels: ollamaModels2 } = await Promise.resolve().then(() => (init_ollama4(), ollama_exports3));
31870
+ await ollamaModels2(program.opts(), opts);
31871
+ });
31872
+ ollama.command("discover").description("Discover models on servers (syncs to local DB)").option("--server <name>", "Discover on a specific server only").action(async (opts) => {
31873
+ const { ollamaDiscover: ollamaDiscover2 } = await Promise.resolve().then(() => (init_ollama4(), ollama_exports3));
31874
+ await ollamaDiscover2(program.opts(), opts);
31875
+ });
31876
+ ollama.command("health").description("Ping all servers and update status").action(async () => {
31877
+ const { ollamaHealth: ollamaHealth2 } = await Promise.resolve().then(() => (init_ollama4(), ollama_exports3));
31878
+ await ollamaHealth2(program.opts());
31879
+ });
31880
+ ollama.command("test <model>").description("Run quality gate test on a model (Phase 4)").action(async (model2) => {
31881
+ const { ollamaTest: ollamaTest2 } = await Promise.resolve().then(() => (init_ollama4(), ollama_exports3));
31882
+ await ollamaTest2(program.opts(), model2);
31883
+ });
29827
31884
  var backend = program.command("backend").description("Manage AI backend");
29828
31885
  backend.command("list").description("Available backends with status").action(async () => {
29829
31886
  const { backendList: backendList2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
@@ -30118,12 +32175,12 @@ mcps.command("list").description("All registered MCP servers").action(async () =
30118
32175
  const { mcpsList: mcpsList2 } = await Promise.resolve().then(() => (init_mcps2(), mcps_exports2));
30119
32176
  await mcpsList2(program.opts());
30120
32177
  });
30121
- var chat = program.command("chat").description("Chat with the AI");
30122
- chat.command("send <message>").description("Send a message and get a response").option("--backend <name>", "Override backend").option("--model <name>", "Override model").option("--thinking <level>", "Override thinking level").option("--cwd <path>", "Override working directory").option("--stream", "Stream response tokens as they arrive").action(async (message, opts) => {
32178
+ var chat2 = program.command("chat").description("Chat with the AI");
32179
+ chat2.command("send <message>").description("Send a message and get a response").option("--backend <name>", "Override backend").option("--model <name>", "Override model").option("--thinking <level>", "Override thinking level").option("--cwd <path>", "Override working directory").option("--stream", "Stream response tokens as they arrive").action(async (message, opts) => {
30123
32180
  const { chatSend: chatSend2 } = await Promise.resolve().then(() => (init_chat2(), chat_exports2));
30124
32181
  await chatSend2(program.opts(), message, opts);
30125
32182
  });
30126
- chat.command("stop").description("Cancel the current running task").action(async () => {
32183
+ chat2.command("stop").description("Cancel the current running task").action(async () => {
30127
32184
  const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
30128
32185
  if (!await isDaemonRunning2()) {
30129
32186
  const { outputError: outputError3 } = await Promise.resolve().then(() => (init_format2(), format_exports));
@@ -30143,7 +32200,7 @@ chat.command("stop").description("Cancel the current running task").action(async
30143
32200
  process.exit(1);
30144
32201
  }
30145
32202
  });
30146
- chat.argument("[message]", "Message to send (shorthand for chat send)").action(async (message, opts) => {
32203
+ chat2.argument("[message]", "Message to send (shorthand for chat send)").action(async (message, opts) => {
30147
32204
  if (message && !["send", "stop"].includes(message)) {
30148
32205
  const { chatSend: chatSend2 } = await Promise.resolve().then(() => (init_chat2(), chat_exports2));
30149
32206
  await chatSend2(program.opts(), message, opts);
@@ -30223,11 +32280,11 @@ program.command("start", { hidden: true }).description("Run the bot in the foreg
30223
32280
  await Promise.resolve().then(() => (init_index(), index_exports));
30224
32281
  });
30225
32282
  program.command("install", { hidden: true }).description("Install as background service \u2014 alias for service install").action(async () => {
30226
- const { installService: installService2 } = await Promise.resolve().then(() => (init_service(), service_exports));
32283
+ const { installService: installService2 } = await Promise.resolve().then(() => (init_service2(), service_exports2));
30227
32284
  installService2();
30228
32285
  });
30229
32286
  program.command("uninstall", { hidden: true }).description("Remove background service \u2014 alias for service uninstall").action(async () => {
30230
- const { uninstallService: uninstallService2 } = await Promise.resolve().then(() => (init_service(), service_exports));
32287
+ const { uninstallService: uninstallService2 } = await Promise.resolve().then(() => (init_service2(), service_exports2));
30231
32288
  uninstallService2();
30232
32289
  });
30233
32290
  program.command("setup").description("Interactive configuration wizard").action(async () => {