cc-claw 0.12.8 → 0.13.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 +1354 -726
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -72,7 +72,7 @@ var VERSION;
72
72
  var init_version = __esm({
73
73
  "src/version.ts"() {
74
74
  "use strict";
75
- VERSION = true ? "0.12.8" : (() => {
75
+ VERSION = true ? "0.13.0" : (() => {
76
76
  try {
77
77
  return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
78
78
  } catch {
@@ -83,6 +83,13 @@ var init_version = __esm({
83
83
  });
84
84
 
85
85
  // src/log.ts
86
+ var log_exports = {};
87
+ __export(log_exports, {
88
+ error: () => error,
89
+ errorMessage: () => errorMessage,
90
+ log: () => log,
91
+ warn: () => warn
92
+ });
86
93
  function ts() {
87
94
  const d = /* @__PURE__ */ new Date();
88
95
  const pad = (n) => String(n).padStart(2, "0");
@@ -102,7 +109,17 @@ function error(...args) {
102
109
  console.error(`[${ts()}]`, ...args);
103
110
  }
104
111
  function errorMessage(err) {
105
- return err instanceof Error ? err.message : String(err);
112
+ if (!(err instanceof Error)) return String(err);
113
+ let msg = err.message;
114
+ let cause = err.cause;
115
+ let depth = 0;
116
+ while (cause && depth < 5) {
117
+ const causeMsg = cause instanceof Error ? cause.message : String(cause);
118
+ msg += ` [caused by: ${causeMsg}]`;
119
+ cause = cause instanceof Error ? cause.cause : void 0;
120
+ depth++;
121
+ }
122
+ return msg;
106
123
  }
107
124
  var init_log = __esm({
108
125
  "src/log.ts"() {
@@ -122,6 +139,7 @@ __export(store_exports, {
122
139
  getActiveOrchestration: () => getActiveOrchestration,
123
140
  getAgent: () => getAgent,
124
141
  getNextQueuedAgent: () => getNextQueuedAgent,
142
+ getNextQueuedAgentGlobal: () => getNextQueuedAgentGlobal,
125
143
  getOrchestration: () => getOrchestration,
126
144
  getState: () => getState,
127
145
  getTask: () => getTask,
@@ -267,26 +285,28 @@ function setOrchestrationBudget(db3, id, budgetLimit) {
267
285
  }
268
286
  function createAgent(db3, opts) {
269
287
  const id = randomUUID();
270
- db3.prepare(`
271
- INSERT INTO agents (id, orchestrationId, parentAgentId, runnerId, name, description, model, task, skills, permMode, role, persona, maxRuntimeMs, summarizeResult, createdAt)
272
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
273
- `).run(
274
- id,
275
- opts.orchestrationId,
276
- opts.parentAgentId ?? null,
277
- opts.runnerId,
278
- opts.name ?? null,
279
- opts.description ?? null,
280
- opts.model ?? null,
281
- opts.task ?? null,
282
- JSON.stringify(opts.skills ?? []),
283
- opts.permMode ?? "inherit",
284
- opts.role ?? "worker",
285
- opts.persona ?? null,
286
- opts.maxRuntimeMs ?? 6e5,
287
- opts.summarizeResult ?? 0
288
- );
289
- db3.prepare("UPDATE orchestrations SET agentCount = agentCount + 1 WHERE id = ?").run(opts.orchestrationId);
288
+ db3.transaction(() => {
289
+ db3.prepare(`
290
+ INSERT INTO agents (id, orchestrationId, parentAgentId, runnerId, name, description, model, task, skills, permMode, role, persona, maxRuntimeMs, summarizeResult, createdAt)
291
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
292
+ `).run(
293
+ id,
294
+ opts.orchestrationId,
295
+ opts.parentAgentId ?? null,
296
+ opts.runnerId,
297
+ opts.name ?? null,
298
+ opts.description ?? null,
299
+ opts.model ?? null,
300
+ opts.task ?? null,
301
+ JSON.stringify(opts.skills ?? []),
302
+ opts.permMode ?? "inherit",
303
+ opts.role ?? "worker",
304
+ opts.persona ?? null,
305
+ opts.maxRuntimeMs ?? 6e5,
306
+ opts.summarizeResult ?? 0
307
+ );
308
+ db3.prepare("UPDATE orchestrations SET agentCount = agentCount + 1 WHERE id = ?").run(opts.orchestrationId);
309
+ })();
290
310
  return id;
291
311
  }
292
312
  function getAgent(db3, id) {
@@ -302,18 +322,41 @@ function listQueuedAgents(db3) {
302
322
  return db3.prepare("SELECT * FROM agents WHERE status = 'queued' ORDER BY createdAt").all();
303
323
  }
304
324
  function cleanupStaleAgents(db3) {
305
- const result = db3.prepare(`
325
+ const stale = db3.prepare(`
326
+ SELECT id, pid FROM agents WHERE status IN ('starting', 'running', 'idle')
327
+ `).all();
328
+ let cleaned = 0;
329
+ const markFailed = db3.prepare(`
306
330
  UPDATE agents SET status = 'failed', completedAt = datetime('now'),
307
331
  resultSummary = 'Marked stale after service restart'
308
- WHERE status IN ('queued', 'starting', 'running', 'idle')
309
- `).run();
310
- return result.changes;
332
+ WHERE id = ?
333
+ `);
334
+ for (const agent of stale) {
335
+ let isAlive = false;
336
+ if (agent.pid) {
337
+ try {
338
+ process.kill(agent.pid, 0);
339
+ isAlive = true;
340
+ } catch {
341
+ }
342
+ }
343
+ if (!isAlive) {
344
+ markFailed.run(agent.id);
345
+ cleaned++;
346
+ }
347
+ }
348
+ return cleaned;
311
349
  }
312
350
  function getNextQueuedAgent(db3, orchestrationId) {
313
351
  return db3.prepare(
314
352
  "SELECT * FROM agents WHERE orchestrationId = ? AND status = 'queued' ORDER BY createdAt LIMIT 1"
315
353
  ).get(orchestrationId) ?? null;
316
354
  }
355
+ function getNextQueuedAgentGlobal(db3) {
356
+ return db3.prepare(
357
+ "SELECT * FROM agents WHERE status = 'queued' ORDER BY createdAt LIMIT 1"
358
+ ).get() ?? null;
359
+ }
317
360
  function updateAgentStatus(db3, id, status) {
318
361
  const updates = ["status = ?"];
319
362
  const params = [status];
@@ -386,20 +429,28 @@ function updateTaskStatus(db3, id, status, result) {
386
429
  db3.prepare(`UPDATE agent_tasks SET ${updates.join(", ")} WHERE id = ?`).run(...params);
387
430
  }
388
431
  function claimTask(db3, taskId, agentId) {
389
- const task = getTask(db3, taskId);
390
- if (!task || task.status !== "pending") return false;
391
- const blockedBy = JSON.parse(task.blockedBy);
392
- if (blockedBy.length > 0) {
393
- const placeholders = blockedBy.map(() => "?").join(",");
394
- const incomplete = db3.prepare(
395
- `SELECT COUNT(*) as count FROM agent_tasks WHERE id IN (${placeholders}) AND status NOT IN ('completed', 'failed', 'abandoned')`
396
- ).get(...blockedBy);
397
- if (incomplete.count > 0) return false;
398
- }
399
- const result = db3.prepare(
400
- "UPDATE agent_tasks SET assignee = ?, status = 'in_progress' WHERE id = ? AND status = 'pending'"
401
- ).run(agentId, taskId);
402
- return result.changes > 0;
432
+ const claim = db3.transaction(() => {
433
+ const task = getTask(db3, taskId);
434
+ if (!task || task.status !== "pending") return false;
435
+ let blockedBy;
436
+ try {
437
+ blockedBy = JSON.parse(task.blockedBy);
438
+ } catch {
439
+ blockedBy = [];
440
+ }
441
+ if (blockedBy.length > 0) {
442
+ const placeholders = blockedBy.map(() => "?").join(",");
443
+ const incomplete = db3.prepare(
444
+ `SELECT COUNT(*) as count FROM agent_tasks WHERE id IN (${placeholders}) AND status NOT IN ('completed', 'failed', 'abandoned')`
445
+ ).get(...blockedBy);
446
+ if (incomplete.count > 0) return false;
447
+ }
448
+ const result = db3.prepare(
449
+ "UPDATE agent_tasks SET assignee = ?, status = 'in_progress' WHERE id = ? AND status = 'pending'"
450
+ ).run(agentId, taskId);
451
+ return result.changes > 0;
452
+ });
453
+ return claim();
403
454
  }
404
455
  function sendInboxMessage(db3, opts) {
405
456
  const result = db3.prepare(`
@@ -422,6 +473,12 @@ function markAllRead(db3, orchestrationId, agentId) {
422
473
  ).run(orchestrationId, agentId);
423
474
  }
424
475
  function setState(db3, orchestrationId, key, value, setBy) {
476
+ const existing = db3.prepare(
477
+ "SELECT setBy, value FROM orchestration_state WHERE orchestrationId = ? AND key = ?"
478
+ ).get(orchestrationId, key);
479
+ if (existing && existing.setBy !== setBy) {
480
+ console.warn(`[state] Key "${key}" overwritten by ${setBy} (was set by ${existing.setBy})`);
481
+ }
425
482
  db3.prepare(`
426
483
  INSERT INTO orchestration_state (orchestrationId, key, value, setBy, updatedAt)
427
484
  VALUES (?, ?, ?, ?, datetime('now'))
@@ -602,7 +659,7 @@ function initActivityTable(db3) {
602
659
  db3.exec(
603
660
  "CREATE INDEX IF NOT EXISTS idx_activity_chat_time ON activity_log (chatId, createdAt DESC)"
604
661
  );
605
- cleanupActivity(db3);
662
+ setTimeout(() => cleanupActivity(db3), 5e3);
606
663
  }
607
664
  function logActivity(db3, event) {
608
665
  db3.prepare(`
@@ -641,18 +698,23 @@ function cleanupActivity(db3) {
641
698
  if (aged.changes > 0) {
642
699
  log(`[activity] Pruned ${aged.changes} events older than ${RETENTION_DAYS} days`);
643
700
  }
644
- const excess = db3.prepare(`
701
+ const chats2 = db3.prepare(
702
+ "SELECT DISTINCT chatId FROM activity_log"
703
+ ).all();
704
+ let totalExcess = 0;
705
+ const deleteStmt = db3.prepare(`
645
706
  DELETE FROM activity_log
646
- WHERE id NOT IN (
647
- SELECT id FROM (
648
- SELECT id, ROW_NUMBER() OVER (PARTITION BY chatId ORDER BY createdAt DESC) AS rn
649
- FROM activity_log
650
- ) ranked
651
- WHERE rn <= ?
707
+ WHERE chatId = ? AND id NOT IN (
708
+ SELECT id FROM activity_log WHERE chatId = ?
709
+ ORDER BY createdAt DESC LIMIT ?
652
710
  )
653
- `).run(MAX_ROWS_PER_CHAT);
654
- if (excess.changes > 0) {
655
- log(`[activity] Capped activity log: pruned ${excess.changes} excess rows`);
711
+ `);
712
+ for (const { chatId } of chats2) {
713
+ const result = deleteStmt.run(chatId, chatId, MAX_ROWS_PER_CHAT);
714
+ totalExcess += result.changes;
715
+ }
716
+ if (totalExcess > 0) {
717
+ log(`[activity] Capped activity log: pruned ${totalExcess} excess rows across ${chats2.length} chats`);
656
718
  }
657
719
  }
658
720
  var RETENTION_DAYS, MAX_ROWS_PER_CHAT;
@@ -695,12 +757,14 @@ __export(store_exports4, {
695
757
  advanceReviewSession: () => advanceReviewSession,
696
758
  cleanupBackupFiles: () => cleanupBackupFiles,
697
759
  cleanupReflectionData: () => cleanupReflectionData,
760
+ clearDiscussionMode: () => clearDiscussionMode,
698
761
  createReviewSession: () => createReviewSession,
699
762
  deleteReviewSession: () => deleteReviewSession,
700
763
  getActiveReflectionChatIds: () => getActiveReflectionChatIds,
701
764
  getAppliedCountTodayForFile: () => getAppliedCountTodayForFile,
702
765
  getAppliedInsightCountToday: () => getAppliedInsightCountToday,
703
766
  getAppliedInsights: () => getAppliedInsights,
767
+ getDiscussionMode: () => getDiscussionMode,
704
768
  getGrowthMetrics: () => getGrowthMetrics,
705
769
  getInsightById: () => getInsightById,
706
770
  getLastAnalysisTime: () => getLastAnalysisTime,
@@ -718,6 +782,7 @@ __export(store_exports4, {
718
782
  logInsight: () => logInsight,
719
783
  logSignal: () => logSignal,
720
784
  markSignalsProcessed: () => markSignalsProcessed,
785
+ setDiscussionMode: () => setDiscussionMode,
721
786
  setReflectionModelConfig: () => setReflectionModelConfig,
722
787
  setReflectionSettings: () => setReflectionSettings,
723
788
  setReflectionStatus: () => setReflectionStatus,
@@ -1104,10 +1169,21 @@ function cleanupReflectionData(db3) {
1104
1169
  DELETE FROM review_sessions WHERE created_at < datetime('now', '-1 day')
1105
1170
  `).run();
1106
1171
  }
1172
+ function setDiscussionMode(chatId, insightId) {
1173
+ discussionMode.set(chatId, insightId);
1174
+ }
1175
+ function getDiscussionMode(chatId) {
1176
+ return discussionMode.get(chatId) ?? null;
1177
+ }
1178
+ function clearDiscussionMode(chatId) {
1179
+ discussionMode.delete(chatId);
1180
+ }
1181
+ var discussionMode;
1107
1182
  var init_store4 = __esm({
1108
1183
  "src/reflection/store.ts"() {
1109
1184
  "use strict";
1110
1185
  init_paths();
1186
+ discussionMode = /* @__PURE__ */ new Map();
1111
1187
  }
1112
1188
  });
1113
1189
 
@@ -1225,7 +1301,7 @@ function hybridScore(item, vectorWeight) {
1225
1301
  const w = vectorWeight ?? getVectorWeight();
1226
1302
  const relevance = w * item.vectorScore + (1 - w) * item.ftsScore;
1227
1303
  const decay = Math.pow(1 - item.decayRate, item.daysSinceAccess);
1228
- return relevance * item.salience * decay;
1304
+ return Math.max(0, relevance * item.salience * decay);
1229
1305
  }
1230
1306
  function serializeEmbedding(embedding) {
1231
1307
  return JSON.stringify(embedding);
@@ -1471,6 +1547,7 @@ __export(store_exports5, {
1471
1547
  pinChatGeminiSlot: () => pinChatGeminiSlot,
1472
1548
  pruneJobRuns: () => pruneJobRuns,
1473
1549
  pruneMessageLog: () => pruneMessageLog,
1550
+ reenableSlot: () => reenableSlot,
1474
1551
  removeChatAlias: () => removeChatAlias,
1475
1552
  removeGeminiSlot: () => removeGeminiSlot,
1476
1553
  removeHeartbeatWatch: () => removeHeartbeatWatch,
@@ -1521,14 +1598,22 @@ import Database from "better-sqlite3";
1521
1598
  function openDatabaseReadOnly() {
1522
1599
  const readDb = new Database(DB_PATH, { readonly: true });
1523
1600
  readDb.exec("PRAGMA journal_mode = WAL");
1524
- readDb.exec("PRAGMA busy_timeout = 3000");
1601
+ readDb.exec("PRAGMA busy_timeout = 5000");
1525
1602
  return readDb;
1526
1603
  }
1527
1604
  function initDatabase() {
1528
1605
  db = new Database(DB_PATH);
1529
1606
  db.exec("PRAGMA journal_mode = WAL");
1530
- db.exec("PRAGMA busy_timeout = 3000");
1607
+ db.exec("PRAGMA busy_timeout = 5000");
1608
+ db.exec("PRAGMA synchronous = NORMAL");
1531
1609
  db.exec("PRAGMA foreign_keys = ON");
1610
+ walCheckpointTimer = setInterval(() => {
1611
+ try {
1612
+ db.pragma("wal_checkpoint(PASSIVE)");
1613
+ } catch {
1614
+ }
1615
+ }, 30 * 60 * 1e3);
1616
+ walCheckpointTimer.unref();
1532
1617
  db.exec(`
1533
1618
  CREATE TABLE IF NOT EXISTS memories (
1534
1619
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1712,9 +1797,42 @@ function initDatabase() {
1712
1797
  consecutive_failures INTEGER NOT NULL DEFAULT 0
1713
1798
  );
1714
1799
  `);
1800
+ const v1Info = db.prepare("PRAGMA table_info(jobs_v1)").all();
1801
+ const v1Cols = new Set(v1Info.map((c) => c.name));
1802
+ const allColumns = [
1803
+ "id",
1804
+ "schedule_type",
1805
+ "cron",
1806
+ "at_time",
1807
+ "every_ms",
1808
+ "title",
1809
+ "description",
1810
+ "chat_id",
1811
+ "backend",
1812
+ "model",
1813
+ "thinking",
1814
+ "timeout",
1815
+ "fallbacks",
1816
+ "session_type",
1817
+ "channel",
1818
+ "target",
1819
+ "delivery_mode",
1820
+ "timezone",
1821
+ "job_type",
1822
+ "enabled",
1823
+ "active",
1824
+ "created_at",
1825
+ "last_run_at",
1826
+ "next_run_at",
1827
+ "consecutive_failures"
1828
+ ];
1829
+ const selectParts = allColumns.map((col) => {
1830
+ if (v1Cols.has(col)) return col;
1831
+ return null;
1832
+ }).filter(Boolean);
1715
1833
  db.exec(`
1716
- INSERT INTO jobs (id, cron, description, chat_id, active, created_at)
1717
- SELECT id, cron, description, chat_id, active, created_at FROM jobs_v1
1834
+ INSERT INTO jobs (${selectParts.join(", ")})
1835
+ SELECT ${selectParts.join(", ")} FROM jobs_v1
1718
1836
  `);
1719
1837
  db.exec("DROP TABLE jobs_v1");
1720
1838
  db.exec("COMMIT");
@@ -2071,7 +2189,8 @@ function saveMemoryWithEmbedding(trigger, content, type = "semantic") {
2071
2189
  warn(`[embeddings] Failed to update memory ${id} embedding: ${errorMessage(err)}`);
2072
2190
  }
2073
2191
  }
2074
- }).catch(() => {
2192
+ }).catch((err) => {
2193
+ warn(`[embeddings] Failed to generate embedding for memory ${id}: ${errorMessage(err)}`);
2075
2194
  });
2076
2195
  return id;
2077
2196
  }
@@ -2971,21 +3090,29 @@ function getNextGeminiSlot(chatId, mode) {
2971
3090
  const eligible = getEligibleGeminiSlots(mode);
2972
3091
  return eligible.length > 0 ? eligible[0] : null;
2973
3092
  }
2974
- function markSlotExhausted(slotId, quotaClass) {
2975
- const cooldownHours = quotaClass === "billing" ? 24 : 6;
3093
+ function markSlotExhausted(slotId, quotaClass, reason) {
3094
+ const cooldownHours = quotaClass === "billing" ? 24 : quotaClass === "capacity" ? 1 : 6;
2976
3095
  const disable = quotaClass === "billing" ? 1 : 0;
2977
3096
  db.prepare(`
2978
3097
  UPDATE gemini_credentials
2979
- SET cooldown_until = datetime('now', '+${cooldownHours} hours'),
3098
+ SET cooldown_until = datetime('now', '+' || ? || ' hours'),
2980
3099
  consecutive_errors = consecutive_errors + 1,
2981
3100
  enabled = CASE WHEN consecutive_errors + 1 >= 10 THEN 0 ELSE CASE WHEN ? = 1 THEN 0 ELSE enabled END END
2982
3101
  WHERE id = ?
2983
- `).run(disable, slotId);
3102
+ `).run(cooldownHours, disable, slotId);
2984
3103
  }
2985
3104
  function markSlotSuccess(slotId) {
2986
3105
  db.prepare(`
2987
3106
  UPDATE gemini_credentials
2988
- SET last_used = datetime('now'), consecutive_errors = 0, cooldown_until = NULL
3107
+ SET last_used = datetime('now'), consecutive_errors = 0,
3108
+ cooldown_until = CASE WHEN cooldown_until < datetime('now') THEN NULL ELSE cooldown_until END
3109
+ WHERE id = ?
3110
+ `).run(slotId);
3111
+ }
3112
+ function reenableSlot(slotId) {
3113
+ db.prepare(`
3114
+ UPDATE gemini_credentials
3115
+ SET enabled = 1, consecutive_errors = 0, cooldown_until = NULL
2989
3116
  WHERE id = ?
2990
3117
  `).run(slotId);
2991
3118
  }
@@ -3055,7 +3182,7 @@ function pruneMessageLog(retentionDays = 30, rowCapPerChat = 2e3) {
3055
3182
  `).run(chat_id, chat_id, rowCapPerChat);
3056
3183
  }
3057
3184
  }
3058
- var db, STOP_WORDS, pendingEscalations, ESCALATION_TTL_MS, ALL_TOOLS, JOB_SELECT;
3185
+ var db, walCheckpointTimer, STOP_WORDS, pendingEscalations, ESCALATION_TTL_MS, ALL_TOOLS, JOB_SELECT;
3059
3186
  var init_store5 = __esm({
3060
3187
  "src/memory/store.ts"() {
3061
3188
  "use strict";
@@ -3236,6 +3363,26 @@ var init_env = __esm({
3236
3363
  }
3237
3364
  });
3238
3365
 
3366
+ // src/backends/strip-thinking.ts
3367
+ function stripThinkingContent(text) {
3368
+ let result = text;
3369
+ for (const pattern of THINKING_PATTERNS) {
3370
+ result = result.replace(pattern, "");
3371
+ }
3372
+ return result.trim();
3373
+ }
3374
+ var THINKING_PATTERNS;
3375
+ var init_strip_thinking = __esm({
3376
+ "src/backends/strip-thinking.ts"() {
3377
+ "use strict";
3378
+ THINKING_PATTERNS = [
3379
+ /<think>[\s\S]*?<\/think>/gi,
3380
+ /<thinking>[\s\S]*?<\/thinking>/gi,
3381
+ /\[Thought:\s*true\][\s\S]*?(?:\[\/Thought\]|\[Thought:\s*false\])/gi
3382
+ ];
3383
+ }
3384
+ });
3385
+
3239
3386
  // src/backends/claude.ts
3240
3387
  import { existsSync as existsSync2 } from "fs";
3241
3388
  import { execSync } from "child_process";
@@ -3245,6 +3392,7 @@ var init_claude = __esm({
3245
3392
  "src/backends/claude.ts"() {
3246
3393
  "use strict";
3247
3394
  init_env();
3395
+ init_strip_thinking();
3248
3396
  init_paths();
3249
3397
  init_log();
3250
3398
  ADAPTIVE_MODELS = /* @__PURE__ */ new Set(["claude-opus-4-6", "claude-sonnet-4-6", "claude-opus-4-6[1m]", "claude-sonnet-4-6[1m]"]);
@@ -3448,7 +3596,8 @@ var init_claude = __esm({
3448
3596
  }
3449
3597
  }
3450
3598
  if (textParts.length > 0) {
3451
- events.push({ type: "text", text: textParts.join("") });
3599
+ const cleaned = stripThinkingContent(textParts.join(""));
3600
+ if (cleaned) events.push({ type: "text", text: cleaned });
3452
3601
  }
3453
3602
  } else if (line.type === "user") {
3454
3603
  const message = line.message;
@@ -3492,25 +3641,14 @@ var init_claude = __esm({
3492
3641
  import { existsSync as existsSync3, mkdirSync } from "fs";
3493
3642
  import { execSync as execSync2 } from "child_process";
3494
3643
  import { join as join5 } from "path";
3495
- function stripThinkingContent(text) {
3496
- let result = text;
3497
- for (const pattern of THINKING_PATTERNS) {
3498
- result = result.replace(pattern, "");
3499
- }
3500
- return result.trim();
3501
- }
3502
- var THINKING_PATTERNS, GeminiAdapter;
3644
+ var GeminiAdapter;
3503
3645
  var init_gemini = __esm({
3504
3646
  "src/backends/gemini.ts"() {
3505
3647
  "use strict";
3506
3648
  init_store5();
3507
3649
  init_env();
3508
3650
  init_paths();
3509
- THINKING_PATTERNS = [
3510
- /<think>[\s\S]*?<\/think>/gi,
3511
- /<thinking>[\s\S]*?<\/thinking>/gi,
3512
- /\[Thought:\s*true\][\s\S]*?(?:\[\/Thought\]|\[Thought:\s*false\])/gi
3513
- ];
3651
+ init_strip_thinking();
3514
3652
  GeminiAdapter = class {
3515
3653
  id = "gemini";
3516
3654
  displayName = "Gemini";
@@ -3694,6 +3832,7 @@ var init_codex = __esm({
3694
3832
  "src/backends/codex.ts"() {
3695
3833
  "use strict";
3696
3834
  init_env();
3835
+ init_strip_thinking();
3697
3836
  CodexAdapter = class {
3698
3837
  id = "codex";
3699
3838
  displayName = "Codex";
@@ -3813,7 +3952,8 @@ var init_codex = __esm({
3813
3952
  } else if (line.type === "item.completed") {
3814
3953
  const item = line.item;
3815
3954
  if (item?.type === "agent_message" && item.text) {
3816
- events.push({ type: "text", text: item.text });
3955
+ const cleaned = stripThinkingContent(item.text);
3956
+ if (cleaned) events.push({ type: "text", text: cleaned });
3817
3957
  } else if (item?.type === "command_execution") {
3818
3958
  events.push({
3819
3959
  type: "tool_end",
@@ -4262,7 +4402,7 @@ function extractKeyField(input) {
4262
4402
  }
4263
4403
  const sorted = Object.keys(input).sort();
4264
4404
  const parts = sorted.map((k) => `${k}:${JSON.stringify(input[k])}`);
4265
- return parts.join("|");
4405
+ return parts.join("|").slice(0, 500);
4266
4406
  }
4267
4407
  function fingerprint(toolName, input) {
4268
4408
  const key = extractKeyField(input);
@@ -4272,8 +4412,8 @@ var DEFAULT_WINDOW_SIZE, DEFAULT_THRESHOLD, HARD_TURN_CAP, ToolLoopDetector;
4272
4412
  var init_tool_loop_detector = __esm({
4273
4413
  "src/tool-loop-detector.ts"() {
4274
4414
  "use strict";
4275
- DEFAULT_WINDOW_SIZE = 15;
4276
- DEFAULT_THRESHOLD = 3;
4415
+ DEFAULT_WINDOW_SIZE = parseInt(process.env.CC_CLAW_LOOP_WINDOW_SIZE ?? "15", 10);
4416
+ DEFAULT_THRESHOLD = parseInt(process.env.CC_CLAW_LOOP_THRESHOLD ?? "3", 10);
4277
4417
  HARD_TURN_CAP = 200;
4278
4418
  ToolLoopDetector = class {
4279
4419
  windowSize;
@@ -4320,15 +4460,24 @@ var init_tool_loop_detector = __esm({
4320
4460
  });
4321
4461
 
4322
4462
  // src/memory/inject.ts
4463
+ function getTopK(query) {
4464
+ const wordCount = query.split(/\s+/).length;
4465
+ const scale = wordCount <= 3 ? 0.5 : wordCount <= 8 ? 0.75 : 1;
4466
+ return {
4467
+ vectorK: Math.max(5, Math.round(BASE_VECTOR_TOP_K * scale)),
4468
+ ftsK: Math.max(5, Math.round(BASE_FTS_TOP_K * scale))
4469
+ };
4470
+ }
4323
4471
  function daysSince(dateStr) {
4324
- const date = /* @__PURE__ */ new Date(dateStr.replace(" ", "T") + (dateStr.includes("Z") ? "" : "Z"));
4472
+ const trimmed = dateStr.trim();
4473
+ const date = /* @__PURE__ */ new Date(trimmed.replace(" ", "T") + (trimmed.includes("Z") ? "" : "Z"));
4325
4474
  return Math.max(0, (Date.now() - date.getTime()) / (1e3 * 60 * 60 * 24));
4326
4475
  }
4327
4476
  function normalizeFtsScores(items) {
4328
4477
  const scores = /* @__PURE__ */ new Map();
4329
4478
  if (items.length === 0) return scores;
4330
4479
  const ranks = items.map((it) => Math.abs(it._ftsRank ?? 0));
4331
- const maxRank = Math.max(...ranks, 1e-3);
4480
+ const maxRank = ranks.reduce((max, r) => r > max ? r : max, 1e-3);
4332
4481
  for (let i = 0; i < items.length; i++) {
4333
4482
  scores.set(items[i].id, ranks[i] / maxRank);
4334
4483
  }
@@ -4398,14 +4547,15 @@ async function injectMemoryContext(userMessage) {
4398
4547
  queryEmbedding = await embedQuery(userMessage);
4399
4548
  }
4400
4549
  const hasVectors = queryEmbedding !== null;
4550
+ const { vectorK, ftsK } = getTopK(userMessage);
4401
4551
  let ftsMemories = [];
4402
4552
  let ftsSessions = [];
4403
4553
  try {
4404
- ftsMemories = searchMemories(userMessage, FTS_TOP_K);
4554
+ ftsMemories = searchMemories(userMessage, ftsK);
4405
4555
  } catch {
4406
4556
  }
4407
4557
  try {
4408
- ftsSessions = searchSessionSummaries(userMessage, FTS_TOP_K);
4558
+ ftsSessions = searchSessionSummaries(userMessage, ftsK);
4409
4559
  } catch {
4410
4560
  }
4411
4561
  let combinedMemories;
@@ -4413,8 +4563,8 @@ async function injectMemoryContext(userMessage) {
4413
4563
  if (hasVectors) {
4414
4564
  const allEmbeddedMemories = getAllMemoriesWithEmbeddings();
4415
4565
  const allEmbeddedSessions = getAllSessionSummariesWithEmbeddings();
4416
- const memVectorMatches = vectorSearch(queryEmbedding, allEmbeddedMemories, VECTOR_TOP_K);
4417
- const sesVectorMatches = vectorSearch(queryEmbedding, allEmbeddedSessions, VECTOR_TOP_K);
4566
+ const memVectorMatches = vectorSearch(queryEmbedding, allEmbeddedMemories, vectorK);
4567
+ const sesVectorMatches = vectorSearch(queryEmbedding, allEmbeddedSessions, vectorK);
4418
4568
  const memMap = /* @__PURE__ */ new Map();
4419
4569
  for (const m of ftsMemories) memMap.set(m.id, m);
4420
4570
  for (const vm of memVectorMatches) {
@@ -4483,16 +4633,16 @@ async function injectMemoryContext(userMessage) {
4483
4633
  ${lines.join("\n")}
4484
4634
  [End memory context]`;
4485
4635
  }
4486
- var MEMORY_DECAY_RATE, SESSION_DECAY_RATE, VECTOR_TOP_K, FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS, MAX_MEMORY_CHARS, MAX_SESSION_CHARS, MAX_BRIDGE_CHARS, pendingContextBridges;
4636
+ var MEMORY_DECAY_RATE, SESSION_DECAY_RATE, BASE_VECTOR_TOP_K, BASE_FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS, MAX_MEMORY_CHARS, MAX_SESSION_CHARS, MAX_BRIDGE_CHARS, pendingContextBridges;
4487
4637
  var init_inject = __esm({
4488
4638
  "src/memory/inject.ts"() {
4489
4639
  "use strict";
4490
4640
  init_store5();
4491
4641
  init_embeddings();
4492
- MEMORY_DECAY_RATE = 0.02;
4493
- SESSION_DECAY_RATE = 5e-3;
4494
- VECTOR_TOP_K = 20;
4495
- FTS_TOP_K = 20;
4642
+ MEMORY_DECAY_RATE = parseFloat(process.env.CC_CLAW_MEMORY_DECAY_RATE ?? "0.02");
4643
+ SESSION_DECAY_RATE = parseFloat(process.env.CC_CLAW_SESSION_DECAY_RATE ?? "0.005");
4644
+ BASE_VECTOR_TOP_K = 20;
4645
+ BASE_FTS_TOP_K = 20;
4496
4646
  FINAL_TOP_K_MEMORIES = 5;
4497
4647
  FINAL_TOP_K_SESSIONS = 3;
4498
4648
  MAX_MEMORY_CHARS = 500;
@@ -4587,6 +4737,16 @@ You are an expert user and operator of the CC-Claw architecture. Because you are
4587
4737
  - **Files**: You can send physical files to the user across Telegram by simply writing \`[SEND_FILE:/absolute/path/to/file]\` in your response.
4588
4738
  - **MCP Ecosystem**: You are deeply natively integrated with Model Context Protocol (MCP) servers (like Perplexity, NotebookLM, Context7) granting you immense external reach.
4589
4739
 
4740
+ ## 7. CLI Reference
4741
+ - Run \\\`cc-claw --ai\\\` for a comprehensive command reference covering all CLI and Telegram commands
4742
+ - Use \\\`cc-claw --ai --install\\\` to install the skill to all backend skill directories
4743
+
4744
+ ## 8. CRITICAL RULES \u2014 Memory & State
4745
+ - NEVER use native CLI memory tools (Gemini's save_memory, Claude's memory, etc.) \u2014 they are backend-local
4746
+ - NEVER modify the SQLite database directly (no INSERT/UPDATE/DELETE SQL)
4747
+ - ALWAYS use CC-Claw slash commands (/remember, /evolve, etc.) or the cc-claw CLI
4748
+ - Memory MUST be cross-backend persistent \u2014 only CC-Claw's own API guarantees this
4749
+
4590
4750
  If the user asks *how* to do something with CC-Claw, use this expertise to suggest the most native, idiomatic approach available in the architecture.
4591
4751
  `;
4592
4752
  }
@@ -4740,6 +4900,14 @@ function syncNativeCliFiles() {
4740
4900
  "- For heartbeat checks: respond with exactly HEARTBEAT_OK if nothing needs attention",
4741
4901
  "- Your working directory is ~/.cc-claw/workspace/",
4742
4902
  "- Skills are in ~/.cc-claw/workspace/skills/",
4903
+ "",
4904
+ "## CRITICAL: Tool Usage Rules",
4905
+ "",
4906
+ "- Do NOT use native CLI memory/storage tools (save_memory, etc.) \u2014 they are backend-local and invisible to other backends",
4907
+ "- Do NOT modify the database directly with SQL (no INSERT/UPDATE/DELETE) \u2014 use CC-Claw commands only",
4908
+ "- To save a memory: use /remember in Telegram, or tell the user to use it",
4909
+ "- To learn your full CLI: run `cc-claw --ai`",
4910
+ "- All state changes must go through the CC-Claw API layer for cross-backend persistence",
4743
4911
  ""
4744
4912
  ].join("\n");
4745
4913
  const ccClawPath = join6(IDENTITY_PATH, "CC-CLAW.md");
@@ -4997,6 +5165,19 @@ var init_loader = __esm({
4997
5165
  }
4998
5166
  });
4999
5167
 
5168
+ // src/text-utils.ts
5169
+ function appendTextChunk(accumulated, chunk) {
5170
+ if (!accumulated) return chunk;
5171
+ if (!chunk) return accumulated;
5172
+ const needsSpace = !/\s$/.test(accumulated) && !/^\s/.test(chunk);
5173
+ return needsSpace ? accumulated + " " + chunk : accumulated + chunk;
5174
+ }
5175
+ var init_text_utils = __esm({
5176
+ "src/text-utils.ts"() {
5177
+ "use strict";
5178
+ }
5179
+ });
5180
+
5000
5181
  // src/memory/session-log.ts
5001
5182
  var session_log_exports = {};
5002
5183
  __export(session_log_exports, {
@@ -5006,8 +5187,13 @@ __export(session_log_exports, {
5006
5187
  getLastMessageTimestamp: () => getLastMessageTimestamp,
5007
5188
  getLog: () => getLog,
5008
5189
  getLoggedChatIds: () => getLoggedChatIds,
5009
- getMessagePairCount: () => getMessagePairCount
5190
+ getMessagePairCount: () => getMessagePairCount,
5191
+ parseDbTimestamp: () => parseDbTimestamp
5010
5192
  });
5193
+ function parseDbTimestamp(ts2) {
5194
+ const trimmed = ts2.trim();
5195
+ return (/* @__PURE__ */ new Date(trimmed + (trimmed.includes("Z") ? "" : "Z"))).getTime();
5196
+ }
5011
5197
  function appendToLog(chatId, userMessage, assistantResponse, backend2, model2, sessionId) {
5012
5198
  const now = Date.now();
5013
5199
  appendMessageLog(chatId, "user", userMessage, backend2 ?? null, model2 ?? null, sessionId ?? null);
@@ -5027,7 +5213,8 @@ function getLog(chatId) {
5027
5213
  return rows.map((r) => ({
5028
5214
  role: r.role,
5029
5215
  text: r.content,
5030
- timestamp: (/* @__PURE__ */ new Date(r.created_at + (r.created_at.includes("Z") ? "" : "Z"))).getTime()
5216
+ // Audit C26: Use shared timestamp parser for consistency with inject.ts
5217
+ timestamp: parseDbTimestamp(r.created_at)
5031
5218
  }));
5032
5219
  }
5033
5220
  function getMessagePairCount(chatId) {
@@ -5035,7 +5222,8 @@ function getMessagePairCount(chatId) {
5035
5222
  if (cached && cached.length > 0) {
5036
5223
  return Math.floor(cached.length / 2);
5037
5224
  }
5038
- return Math.floor(getUnsummarizedLog(chatId).length / 2);
5225
+ const dbRows = getUnsummarizedLog(chatId);
5226
+ return Math.floor(dbRows.length / 2);
5039
5227
  }
5040
5228
  function clearLog(chatId) {
5041
5229
  markMessageLogSummarized(chatId);
@@ -5049,7 +5237,7 @@ function getLastMessageTimestamp(chatId) {
5049
5237
  const rows = getUnsummarizedLog(chatId);
5050
5238
  if (rows.length === 0) return null;
5051
5239
  const last = rows[rows.length - 1];
5052
- return (/* @__PURE__ */ new Date(last.created_at + (last.created_at.includes("Z") ? "" : "Z"))).getTime();
5240
+ return parseDbTimestamp(last.created_at);
5053
5241
  }
5054
5242
  function getCachedLog(chatId) {
5055
5243
  return cache.get(chatId) ?? [];
@@ -5062,7 +5250,7 @@ var init_session_log = __esm({
5062
5250
  "src/memory/session-log.ts"() {
5063
5251
  "use strict";
5064
5252
  init_store5();
5065
- CACHE_SIZE = 20;
5253
+ CACHE_SIZE = 50;
5066
5254
  cache = /* @__PURE__ */ new Map();
5067
5255
  }
5068
5256
  });
@@ -5077,21 +5265,27 @@ __export(summarize_exports, {
5077
5265
  });
5078
5266
  import { spawn } from "child_process";
5079
5267
  import { createInterface } from "readline";
5268
+ function getTranscriptCap(model2) {
5269
+ if (model2 && /flash/i.test(model2)) return TRANSCRIPT_CAP_FLASH;
5270
+ return TRANSCRIPT_CAP_DEFAULT;
5271
+ }
5080
5272
  function buildTranscript(entries) {
5081
5273
  const lines = [];
5082
5274
  let totalLen = 0;
5083
- for (const e of entries) {
5275
+ for (let i = entries.length - 1; i >= 0; i--) {
5276
+ const e = entries[i];
5084
5277
  const limit = e.role === "user" ? USER_MSG_LIMIT : AGENT_MSG_LIMIT;
5085
5278
  const label2 = e.role === "user" ? "User" : "Agent";
5086
5279
  const text = e.text.length > limit ? e.text.slice(0, limit) + "\n[...truncated]" : e.text;
5087
5280
  const line = `[${label2}] ${text}`;
5088
- if (totalLen + line.length > TRANSCRIPT_CAP) {
5281
+ if (totalLen + line.length > getTranscriptCap()) {
5089
5282
  lines.push("[...earlier messages truncated for length]");
5090
5283
  break;
5091
5284
  }
5092
5285
  lines.push(line);
5093
5286
  totalLen += line.length;
5094
5287
  }
5288
+ lines.reverse();
5095
5289
  return lines.join("\n\n");
5096
5290
  }
5097
5291
  async function attemptSummarize(chatId, adapter, model2, entries) {
@@ -5121,11 +5315,12 @@ ${transcript}`;
5121
5315
  });
5122
5316
  proc.stderr?.resume();
5123
5317
  const rl2 = createInterface({ input: proc.stdout });
5318
+ let killTimer;
5124
5319
  const timeout = setTimeout(() => {
5125
5320
  warn(`[summarize] Timeout (${adapter.id}:${model2}) for chat ${chatId}`);
5126
5321
  rl2.close();
5127
5322
  proc.kill("SIGTERM");
5128
- setTimeout(() => proc.kill("SIGKILL"), 2e3);
5323
+ killTimer = setTimeout(() => proc.kill("SIGKILL"), 2e3);
5129
5324
  }, SUMMARIZE_TIMEOUT_MS);
5130
5325
  rl2.on("line", (line) => {
5131
5326
  if (!line.trim()) return;
@@ -5137,7 +5332,7 @@ ${transcript}`;
5137
5332
  }
5138
5333
  const events = adapter.parseLine(msg);
5139
5334
  for (const ev of events) {
5140
- if (ev.type === "text" && ev.text) accumulatedText += ev.text;
5335
+ if (ev.type === "text" && ev.text) accumulatedText = appendTextChunk(accumulatedText, ev.text);
5141
5336
  if (ev.type === "usage" && ev.usage) {
5142
5337
  inputTokens += ev.usage.input;
5143
5338
  outputTokens += ev.usage.output;
@@ -5157,13 +5352,23 @@ ${transcript}`;
5157
5352
  }
5158
5353
  }
5159
5354
  });
5160
- proc.on("error", () => {
5161
- clearTimeout(timeout);
5162
- resolve();
5355
+ let settled = false;
5356
+ proc.on("error", (err) => {
5357
+ warn(`[summarize] Spawn error (${adapter.id}:${model2}): ${err}`);
5358
+ if (!settled) {
5359
+ settled = true;
5360
+ clearTimeout(timeout);
5361
+ clearTimeout(killTimer);
5362
+ resolve();
5363
+ }
5163
5364
  });
5164
5365
  proc.on("close", () => {
5165
- clearTimeout(timeout);
5166
- resolve();
5366
+ if (!settled) {
5367
+ settled = true;
5368
+ clearTimeout(timeout);
5369
+ clearTimeout(killTimer);
5370
+ resolve();
5371
+ }
5167
5372
  });
5168
5373
  });
5169
5374
  if (inputTokens + outputTokens > 0) {
@@ -5219,66 +5424,84 @@ async function extractAndLogSignals(rawText, chatId, adapterId, model2) {
5219
5424
  }
5220
5425
  }
5221
5426
  async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend, clearLogAfter = true) {
5222
- const pairCount = getMessagePairCount(chatId);
5223
- if (pairCount < MIN_PAIRS) {
5224
- if (clearLogAfter) clearLog(chatId);
5427
+ const existing = summarizationLocks.get(chatId);
5428
+ if (existing) {
5429
+ warn(`[summarize] Already summarizing ${chatId}, skipping concurrent call`);
5430
+ await existing;
5225
5431
  return false;
5226
5432
  }
5227
- const entries = getLog(chatId);
5228
- if (entries.length === 0) return false;
5229
- const tried = /* @__PURE__ */ new Set();
5230
- if (excludeBackend) {
5231
- const excluded = getAdapter(excludeBackend);
5232
- tried.add(`${excluded.id}:${excluded.summarizerModel}`);
5233
- }
5234
- try {
5235
- const config2 = getSummarizer(chatId);
5236
- if (config2.backend !== "off") {
5237
- const adapter = config2.backend ? getAdapter(config2.backend) : getAdapterForChat(chatId);
5238
- const model2 = config2.model ?? adapter.summarizerModel;
5239
- const key = `${adapter.id}:${model2}`;
5240
- tried.add(key);
5241
- const result = await attemptSummarize(chatId, adapter, model2, entries);
5242
- if (result.success) {
5243
- await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
5244
- if (clearLogAfter) clearLog(chatId);
5245
- return true;
5246
- }
5433
+ const doSummarize = async () => {
5434
+ const pairCount = getMessagePairCount(chatId);
5435
+ if (pairCount < MIN_PAIRS) {
5436
+ return false;
5437
+ }
5438
+ const entries = getLog(chatId);
5439
+ if (entries.length === 0) return false;
5440
+ const tried = /* @__PURE__ */ new Set();
5441
+ if (excludeBackend) {
5442
+ const excluded = getAdapter(excludeBackend);
5443
+ tried.add(`${excluded.id}:${excluded.summarizerModel}`);
5247
5444
  }
5248
- } catch {
5249
- }
5250
- if (targetBackendId) {
5251
5445
  try {
5252
- const targetAdapter = getAdapter(targetBackendId);
5253
- const model2 = targetAdapter.summarizerModel;
5254
- const key = `${targetAdapter.id}:${model2}`;
5255
- if (!tried.has(key)) {
5446
+ const config2 = getSummarizer(chatId);
5447
+ if (config2.backend !== "off") {
5448
+ const adapter = config2.backend ? getAdapter(config2.backend) : getAdapterForChat(chatId);
5449
+ const model2 = config2.model ?? adapter.summarizerModel;
5450
+ const key = `${adapter.id}:${model2}`;
5256
5451
  tried.add(key);
5257
- const result = await attemptSummarize(chatId, targetAdapter, model2, entries);
5452
+ const result = await attemptSummarize(chatId, adapter, model2, entries);
5258
5453
  if (result.success) {
5259
- await extractAndLogSignals(result.rawText, chatId, targetAdapter.id, model2);
5454
+ await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
5260
5455
  if (clearLogAfter) clearLog(chatId);
5261
5456
  return true;
5262
5457
  }
5263
5458
  }
5264
5459
  } catch {
5265
5460
  }
5266
- }
5267
- for (const adapter of getAllAdapters()) {
5268
- const model2 = adapter.summarizerModel;
5269
- const key = `${adapter.id}:${model2}`;
5270
- if (!tried.has(key)) {
5271
- tried.add(key);
5272
- const result = await attemptSummarize(chatId, adapter, model2, entries);
5273
- if (result.success) {
5274
- await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
5275
- if (clearLogAfter) clearLog(chatId);
5276
- return true;
5461
+ if (targetBackendId) {
5462
+ try {
5463
+ const targetAdapter = getAdapter(targetBackendId);
5464
+ const model2 = targetAdapter.summarizerModel;
5465
+ const key = `${targetAdapter.id}:${model2}`;
5466
+ if (!tried.has(key)) {
5467
+ tried.add(key);
5468
+ const result = await attemptSummarize(chatId, targetAdapter, model2, entries);
5469
+ if (result.success) {
5470
+ await extractAndLogSignals(result.rawText, chatId, targetAdapter.id, model2);
5471
+ if (clearLogAfter) clearLog(chatId);
5472
+ return true;
5473
+ }
5474
+ }
5475
+ } catch {
5476
+ }
5477
+ }
5478
+ let fallbackCount = 0;
5479
+ const MAX_FALLBACK_ATTEMPTS = 3;
5480
+ for (const adapter of getAllAdapters()) {
5481
+ if (fallbackCount >= MAX_FALLBACK_ATTEMPTS) break;
5482
+ const model2 = adapter.summarizerModel;
5483
+ const key = `${adapter.id}:${model2}`;
5484
+ if (!tried.has(key)) {
5485
+ tried.add(key);
5486
+ fallbackCount++;
5487
+ const result = await attemptSummarize(chatId, adapter, model2, entries);
5488
+ if (result.success) {
5489
+ await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
5490
+ if (clearLogAfter) clearLog(chatId);
5491
+ return true;
5492
+ }
5277
5493
  }
5278
5494
  }
5495
+ warn(`[summarize] All fallback attempts failed for chat ${chatId} \u2014 raw log preserved`);
5496
+ return false;
5497
+ };
5498
+ const promise = doSummarize();
5499
+ summarizationLocks.set(chatId, promise);
5500
+ try {
5501
+ return await promise;
5502
+ } finally {
5503
+ summarizationLocks.delete(chatId);
5279
5504
  }
5280
- warn(`[summarize] All fallback attempts failed for chat ${chatId} \u2014 raw log preserved`);
5281
- return false;
5282
5505
  }
5283
5506
  async function summarizeSession(chatId, clearLogAfter = true) {
5284
5507
  return summarizeWithFallbackChain(chatId, void 0, void 0, clearLogAfter);
@@ -5299,7 +5522,7 @@ async function summarizeAllPending() {
5299
5522
  await summarizeSession(chatId);
5300
5523
  }
5301
5524
  }
5302
- var MIN_PAIRS, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP, SUMMARIZE_TIMEOUT_MS, SUMMARIZE_PROMPT, CONTEXT_BRIDGE_PROMPT, VALID_SIGNAL_TYPES;
5525
+ var MIN_PAIRS, summarizationLocks, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP_DEFAULT, TRANSCRIPT_CAP_FLASH, SUMMARIZE_TIMEOUT_MS, SUMMARIZE_PROMPT, CONTEXT_BRIDGE_PROMPT, VALID_SIGNAL_TYPES;
5303
5526
  var init_summarize = __esm({
5304
5527
  "src/memory/summarize.ts"() {
5305
5528
  "use strict";
@@ -5307,10 +5530,13 @@ var init_summarize = __esm({
5307
5530
  init_store5();
5308
5531
  init_session_log();
5309
5532
  init_backends();
5533
+ init_text_utils();
5310
5534
  MIN_PAIRS = 2;
5535
+ summarizationLocks = /* @__PURE__ */ new Map();
5311
5536
  USER_MSG_LIMIT = 4e3;
5312
5537
  AGENT_MSG_LIMIT = 6e3;
5313
- TRANSCRIPT_CAP = 1e5;
5538
+ TRANSCRIPT_CAP_DEFAULT = 1e5;
5539
+ TRANSCRIPT_CAP_FLASH = 5e4;
5314
5540
  SUMMARIZE_TIMEOUT_MS = 6e4;
5315
5541
  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.
5316
5542
 
@@ -5358,7 +5584,7 @@ Be specific. The new assistant has no prior context. Cover any domain \u2014 per
5358
5584
  // src/gemini/quota.ts
5359
5585
  function classifyGeminiQuota(errorText) {
5360
5586
  for (const p of CAPACITY_PATTERNS) {
5361
- if (p.test(errorText)) return "rate_limited";
5587
+ if (p.test(errorText)) return "capacity";
5362
5588
  }
5363
5589
  for (const p of RATE_LIMITED_PATTERNS) {
5364
5590
  if (p.test(errorText)) return "rate_limited";
@@ -5389,8 +5615,9 @@ var init_quota = __esm({
5389
5615
  ];
5390
5616
  DAILY_QUOTA_PATTERNS = [
5391
5617
  /quota exceeded.*per day/i,
5392
- /resource.?exhausted/i,
5393
- /check.?quota/i,
5618
+ // Audit C39: Tightened — require both "resource exhausted" AND "check quota" together
5619
+ // to avoid matching generic 500s that mention "exhausted" in a different context
5620
+ /resource.?exhausted.*check.?quota/i,
5394
5621
  /RESOURCE_EXHAUSTED/,
5395
5622
  /exhausted.*quota/i
5396
5623
  ];
@@ -5497,9 +5724,15 @@ import { spawn as spawn2 } from "child_process";
5497
5724
  import { createInterface as createInterface2 } from "readline";
5498
5725
  import { readFileSync as readFileSync4 } from "fs";
5499
5726
  function stripFrontmatter(text) {
5500
- const match = text.match(/^---\s*\n[\s\S]*?\n---\s*\n?/m);
5501
- if (!match || match.index !== 0) return text;
5502
- return text.slice(match[0].length);
5727
+ const lines = text.split("\n");
5728
+ if (lines.length < 2 || lines[0].trim() !== "---") return text;
5729
+ for (let i = 1; i < lines.length; i++) {
5730
+ if (lines[i].trim() === "---") {
5731
+ const start = i + 1 < lines.length && lines[i + 1].trim() === "" ? i + 2 : i + 1;
5732
+ return lines.slice(start).join("\n");
5733
+ }
5734
+ }
5735
+ return text;
5503
5736
  }
5504
5737
  function buildAgentPrompt(opts, runnerSkillPath) {
5505
5738
  const parts = [];
@@ -5527,7 +5760,11 @@ function buildSpawnEnv(runner, isSubAgent = false) {
5527
5760
  const runnerEnv = runner.getEnv();
5528
5761
  const env = { ...base, ...runnerEnv };
5529
5762
  if (isSubAgent) {
5530
- for (const key of SENSITIVE_ENV_KEYS) delete env[key];
5763
+ for (const key of SENSITIVE_ENV_KEYS) {
5764
+ if (!(key in runnerEnv)) {
5765
+ delete env[key];
5766
+ }
5767
+ }
5531
5768
  }
5532
5769
  return env;
5533
5770
  }
@@ -5609,7 +5846,11 @@ var init_spawn = __esm({
5609
5846
  "ELEVENLABS_API_KEY",
5610
5847
  "DASHBOARD_TOKEN",
5611
5848
  "ALLOWED_CHAT_ID",
5612
- "CC_CLAW_DASHBOARD_TOKEN"
5849
+ "CC_CLAW_DASHBOARD_TOKEN",
5850
+ // Audit C15: API keys that should not leak to sub-agents
5851
+ "ANTHROPIC_API_KEY",
5852
+ "GEMINI_API_KEY",
5853
+ "OPENAI_API_KEY"
5613
5854
  ];
5614
5855
  }
5615
5856
  });
@@ -5619,7 +5860,7 @@ var MAX_CONCURRENT_AGENTS, IDLE_THRESHOLD_MS, DEFAULT_MAX_RUNTIME_MS, BUDGET_WAR
5619
5860
  var init_types = __esm({
5620
5861
  "src/agents/types.ts"() {
5621
5862
  "use strict";
5622
- MAX_CONCURRENT_AGENTS = 4;
5863
+ MAX_CONCURRENT_AGENTS = parseInt(process.env.CC_CLAW_MAX_AGENTS ?? "4", 10);
5623
5864
  IDLE_THRESHOLD_MS = 6e4;
5624
5865
  DEFAULT_MAX_RUNTIME_MS = 6e5;
5625
5866
  BUDGET_WARNING_PERCENT = 0.8;
@@ -5640,6 +5881,9 @@ function checkBudget(db3, orchestrationId) {
5640
5881
  if (budgetLimit === null || budgetLimit === void 0) {
5641
5882
  return { totalCost, budgetLimit: null, percentUsed: null, exceeded: false, warning: false };
5642
5883
  }
5884
+ if (budgetLimit <= 0) {
5885
+ return { totalCost, budgetLimit, percentUsed: 100, exceeded: true, warning: true };
5886
+ }
5643
5887
  const percentUsed = totalCost / budgetLimit * 100;
5644
5888
  const exceeded = totalCost >= budgetLimit;
5645
5889
  const warning4 = percentUsed >= BUDGET_WARNING_PERCENT * 100;
@@ -5669,18 +5913,40 @@ async function discoverExistingMcps(runner) {
5669
5913
  encoding: "utf-8",
5670
5914
  env: runner.getEnv(),
5671
5915
  cwd: homedir3(),
5672
- timeout: 3e4
5916
+ // Audit O20: Reduced from 30s — mcp list should be fast
5917
+ timeout: 1e4
5673
5918
  });
5674
5919
  const stdout = typeof result === "string" ? result : Array.isArray(result) ? result[0] : result?.stdout ?? null;
5675
5920
  if (stdout == null) return [];
5921
+ try {
5922
+ const parsed = JSON.parse(stdout);
5923
+ const servers = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.servers) ? parsed.servers : Array.isArray(parsed?.mcpServers) ? parsed.mcpServers : null;
5924
+ if (servers) {
5925
+ return servers.map((s) => s.name ?? s.id).filter((n) => typeof n === "string" && n.length > 0);
5926
+ }
5927
+ } catch {
5928
+ }
5929
+ const NAME_PATTERNS = [
5930
+ /^([a-zA-Z0-9_-]+)\s*:/,
5931
+ // "name: description" format
5932
+ /^([a-zA-Z0-9_-]+)\s*\(/,
5933
+ // "name (transport)" format
5934
+ /^\s*"?([a-zA-Z0-9_-]+)"?\s*$/,
5935
+ // bare name on its own line
5936
+ /name["\s:=]+([a-zA-Z0-9_-]+)/i
5937
+ // key-value: name = "foo"
5938
+ ];
5676
5939
  const seen = /* @__PURE__ */ new Set();
5677
5940
  for (const line of stdout.split("\n")) {
5678
5941
  const trimmed = line.trim();
5679
5942
  if (!trimmed || SKIP_LINE.test(trimmed)) continue;
5680
5943
  const cleaned = trimmed.replace(/^[✓✗•\-●]\s*/, "");
5681
- const match = cleaned.match(/^([a-zA-Z0-9_-]+):/);
5682
- if (match && !seen.has(match[1])) {
5683
- seen.add(match[1]);
5944
+ for (const pattern of NAME_PATTERNS) {
5945
+ const match = cleaned.match(pattern);
5946
+ if (match && match[1] && !seen.has(match[1])) {
5947
+ seen.add(match[1]);
5948
+ break;
5949
+ }
5684
5950
  }
5685
5951
  }
5686
5952
  return [...seen];
@@ -5693,31 +5959,47 @@ function diffMcps(needed, existing) {
5693
5959
  return needed.filter((name) => !existingSet.has(name.toLowerCase()));
5694
5960
  }
5695
5961
  function defToConfig(def) {
5696
- const config2 = {
5697
- name: def.name,
5698
- transport: def.transport
5699
- };
5700
- if (def.command) config2.command = def.command;
5701
- if (def.args) config2.args = JSON.parse(def.args);
5702
- if (def.url) config2.url = def.url;
5703
- if (def.env) config2.env = JSON.parse(def.env);
5704
- return config2;
5962
+ try {
5963
+ const config2 = {
5964
+ name: def.name,
5965
+ transport: def.transport
5966
+ };
5967
+ if (def.command) config2.command = def.command;
5968
+ if (def.args) config2.args = JSON.parse(def.args);
5969
+ if (def.url) config2.url = def.url;
5970
+ if (def.env) config2.env = JSON.parse(def.env);
5971
+ return config2;
5972
+ } catch (err) {
5973
+ const { warn: warn2 } = (init_log(), __toCommonJS(log_exports));
5974
+ warn2(`[mcp] Skipping MCP "${def.name}" \u2014 malformed JSON in args/env:`, err);
5975
+ return null;
5976
+ }
5705
5977
  }
5706
5978
  async function injectMcps(runner, mcpNames, db3, scope) {
5707
5979
  const added = [];
5708
5980
  const exe = runner.getExecutablePath();
5709
5981
  const runnerId = runner.id;
5710
- for (const name of mcpNames) {
5711
- const def = getMcpServer(db3, name);
5712
- if (!def) continue;
5713
- try {
5714
- const config2 = defToConfig(def);
5715
- const addCmd = runner.getMcpAddCommand(config2);
5716
- const args = addCmd.slice(1);
5717
- await execFileAsync(exe, args, { encoding: "utf-8" });
5718
- addPropagation(db3, name, runnerId, scope);
5719
- added.push(name);
5720
- } catch {
5982
+ const results = await Promise.allSettled(
5983
+ mcpNames.map(async (name) => {
5984
+ const def = getMcpServer(db3, name);
5985
+ if (!def) return null;
5986
+ try {
5987
+ const config2 = defToConfig(def);
5988
+ if (!config2) return null;
5989
+ const addCmd = runner.getMcpAddCommand(config2);
5990
+ const args = addCmd.slice(1);
5991
+ await execFileAsync(exe, args, { encoding: "utf-8" });
5992
+ addPropagation(db3, name, runnerId, scope);
5993
+ return name;
5994
+ } catch (err) {
5995
+ warn(`[mcp] inject ${name} \u2192 ${runnerId} failed:`, err);
5996
+ return null;
5997
+ }
5998
+ })
5999
+ );
6000
+ for (const result of results) {
6001
+ if (result.status === "fulfilled" && result.value) {
6002
+ added.push(result.value);
5721
6003
  }
5722
6004
  }
5723
6005
  return added;
@@ -5730,9 +6012,11 @@ async function cleanupMcps(runner, mcps2, db3, scope) {
5730
6012
  const removeCmd = runner.getMcpRemoveCommand(name);
5731
6013
  const args = removeCmd.slice(1);
5732
6014
  await execFileAsync(exe, args, { encoding: "utf-8" });
6015
+ removePropagation(db3, name, runnerId, scope);
5733
6016
  } catch {
6017
+ const { warn: warn2 } = (init_log(), __toCommonJS(log_exports));
6018
+ warn2(`[propagate] Failed to remove MCP "${name}" from ${runnerId} \u2014 record kept for retry`);
5734
6019
  }
5735
- removePropagation(db3, name, runnerId, scope);
5736
6020
  }
5737
6021
  }
5738
6022
  var execFileAsync, SKIP_LINE;
@@ -5740,6 +6024,7 @@ var init_propagate = __esm({
5740
6024
  "src/mcps/propagate.ts"() {
5741
6025
  "use strict";
5742
6026
  init_store2();
6027
+ init_log();
5743
6028
  execFileAsync = promisify(execFile);
5744
6029
  SKIP_LINE = /^(Checking|Configured|No MCP|Try |──)/i;
5745
6030
  }
@@ -6011,7 +6296,7 @@ var init_agent_log = __esm({
6011
6296
  import { existsSync as existsSync10 } from "fs";
6012
6297
  async function withRunnerLock(runnerId, fn) {
6013
6298
  const prev = runnerLocks.get(runnerId) ?? Promise.resolve();
6014
- const next = prev.then(fn, fn);
6299
+ const next = prev.then(fn, () => fn());
6015
6300
  runnerLocks.set(runnerId, next.then(() => {
6016
6301
  }, () => {
6017
6302
  }));
@@ -6176,12 +6461,13 @@ async function startAgent(agentId, chatId, opts) {
6176
6461
  warn(`[orchestrator] MCP injection failed for agent ${agentId}:`, err);
6177
6462
  }
6178
6463
  }
6464
+ const dashboardEnabled = process.env.DASHBOARD_ENABLED === "1";
6179
6465
  let mcpExtraArgs = [];
6180
6466
  let orchestratorMcpName = "";
6181
- if (process.env.DASHBOARD_ENABLED !== "1") {
6467
+ if (!dashboardEnabled) {
6182
6468
  warn(`[orchestrator] DASHBOARD_ENABLED is not set \u2014 agent ${agentId.slice(0, 8)} will NOT have orchestrator MCP tools (set_state, send_message, etc.)`);
6183
6469
  }
6184
- if (process.env.DASHBOARD_ENABLED === "1") {
6470
+ if (dashboardEnabled) {
6185
6471
  try {
6186
6472
  const { createSubAgentToken: createSubAgentToken2 } = await Promise.resolve().then(() => (init_server(), server_exports));
6187
6473
  const scopedToken = createSubAgentToken2(agentId);
@@ -6193,10 +6479,12 @@ async function startAgent(agentId, chatId, opts) {
6193
6479
  } else {
6194
6480
  await withRunnerLock(runner.id, async () => {
6195
6481
  const addCmd = runner.getMcpAddCommand(mcpConfig);
6196
- const { execFileSync: execFileSync5 } = await import("child_process");
6482
+ const { execFile: execFile5 } = await import("child_process");
6483
+ const { promisify: promisify3 } = await import("util");
6484
+ const execFileAsync3 = promisify3(execFile5);
6197
6485
  const exe = addCmd[0];
6198
6486
  const args = addCmd.slice(1);
6199
- execFileSync5(exe, args, { encoding: "utf-8", timeout: 15e3, env: runner.getEnv() });
6487
+ await execFileAsync3(exe, args, { encoding: "utf-8", timeout: 15e3, env: runner.getEnv() });
6200
6488
  });
6201
6489
  mcpsAdded = [...mcpsAdded, orchestratorMcpName];
6202
6490
  updateAgentMcpsAdded(db3, agentId, mcpsAdded);
@@ -6211,6 +6499,7 @@ async function startAgent(agentId, chatId, opts) {
6211
6499
  }
6212
6500
  updateAgentStatus(db3, agentId, "starting");
6213
6501
  const stderrChunks = [];
6502
+ const MAX_STDERR_CHUNKS = 50;
6214
6503
  const rawStdoutLines = [];
6215
6504
  let gotResult = false;
6216
6505
  const startedAt = Date.now();
@@ -6268,7 +6557,9 @@ async function startAgent(agentId, chatId, opts) {
6268
6557
  );
6269
6558
  startNextQueued(chatId);
6270
6559
  },
6271
- onStderr: (chunk) => stderrChunks.push(chunk)
6560
+ onStderr: (chunk) => {
6561
+ if (stderrChunks.length < MAX_STDERR_CHUNKS) stderrChunks.push(chunk);
6562
+ }
6272
6563
  });
6273
6564
  if (child.pid) {
6274
6565
  updateAgentPid(db3, agentId, child.pid);
@@ -6295,6 +6586,9 @@ async function startAgent(agentId, chatId, opts) {
6295
6586
  agentId
6296
6587
  );
6297
6588
  child.on("close", (code, signal) => {
6589
+ clearTimeout(timeoutTimers.get(agentId));
6590
+ timeoutTimers.delete(agentId);
6591
+ activeProcesses.delete(agentId);
6298
6592
  if (gotResult) return;
6299
6593
  const agent = getAgent(db3, agentId);
6300
6594
  if (!agent || agent.status === "completed" || agent.status === "failed") return;
@@ -6322,10 +6616,10 @@ async function startAgent(agentId, chatId, opts) {
6322
6616
  }
6323
6617
  let exitCode = code ?? 1;
6324
6618
  let exitCodeForced = false;
6325
- if (exitCode === 0) {
6619
+ if (exitCode === 0 && durationMs < 5e3) {
6326
6620
  exitCode = 1;
6327
6621
  exitCodeForced = true;
6328
- warn(`[orchestrator] Agent ${agentId.slice(0, 8)}: exit code 0 with no output \u2014 forced to 1 (likely auth/model issue)`);
6622
+ warn(`[orchestrator] Agent ${agentId.slice(0, 8)}: exit code 0 with no output in ${durationMs}ms \u2014 forced to 1 (likely auth/model issue)`);
6329
6623
  }
6330
6624
  const errMsg = exitCodeForced ? `CLI exited cleanly but produced no output (${durationMs}ms) \u2014 likely auth failure or invalid model${stderr ? `: ${stderr.slice(0, 200)}` : ""}` : `CLI exited with code ${exitCode}${stderr ? `: ${stderr.slice(0, 200)}` : ""}`;
6331
6625
  updateAgentResult(db3, agentId, {
@@ -6356,11 +6650,11 @@ async function startAgent(agentId, chatId, opts) {
6356
6650
  if (crashedAgent) {
6357
6651
  const mcpsCrashed = crashedAgent.mcpsAdded ? JSON.parse(crashedAgent.mcpsAdded) : [];
6358
6652
  if (mcpsCrashed.length > 0) {
6359
- const runner2 = getRunner(crashedAgent.runnerId);
6360
- if (runner2) {
6361
- const cleanupFn = () => cleanupMcps(runner2, mcpsCrashed, db3, `agent:${agentId}`);
6362
- if (runner2.capabilities.mcpInjection === "add-remove") {
6363
- withRunnerLock(runner2.id, cleanupFn).catch((err) => {
6653
+ const cleanupRunner = getRunner(crashedAgent.runnerId);
6654
+ if (cleanupRunner) {
6655
+ const cleanupFn = () => cleanupMcps(cleanupRunner, mcpsCrashed, db3, `agent:${agentId}`);
6656
+ if (cleanupRunner.capabilities.mcpInjection === "add-remove") {
6657
+ withRunnerLock(cleanupRunner.id, cleanupFn).catch((err) => {
6364
6658
  warn(`[orchestrator] MCP cleanup failed for crashed agent ${agentId.slice(0, 8)}:`, err);
6365
6659
  });
6366
6660
  } else {
@@ -6528,22 +6822,32 @@ async function handleAgentComplete(agentId, chatId, resultText, usage2, mcpsAdde
6528
6822
  }
6529
6823
  function startNextQueued(chatId) {
6530
6824
  const db3 = getDb();
6531
- const orch = getActiveOrchestration(db3, chatId);
6532
- if (!orch) return;
6533
6825
  const runningCount = getRunningAgentCount();
6534
6826
  if (runningCount >= MAX_CONCURRENT_AGENTS) return;
6535
- const next = getNextQueuedAgent(db3, orch.id);
6827
+ const orch = getActiveOrchestration(db3, chatId);
6828
+ let next = null;
6829
+ let resolvedChatId = chatId;
6830
+ if (orch) {
6831
+ next = getNextQueuedAgent(db3, orch.id);
6832
+ }
6833
+ if (!next) {
6834
+ next = getNextQueuedAgentGlobal(db3);
6835
+ if (next) {
6836
+ const agentOrch = getOrchestration(db3, next.orchestrationId);
6837
+ if (agentOrch) resolvedChatId = agentOrch.chatId;
6838
+ }
6839
+ }
6536
6840
  if (!next) return;
6537
6841
  const agent = getAgent(db3, next.id);
6538
6842
  if (!agent) return;
6539
- startAgent(next.id, chatId, {
6843
+ startAgent(next.id, resolvedChatId, {
6540
6844
  runner: agent.runnerId,
6541
6845
  task: agent.task ?? "",
6542
6846
  name: agent.name ?? void 0,
6543
6847
  description: agent.description ?? void 0,
6544
6848
  model: agent.model ?? void 0,
6545
6849
  skills: JSON.parse(agent.skills),
6546
- permMode: agent.permMode === "inherit" ? getMode(chatId) : agent.permMode,
6850
+ permMode: agent.permMode === "inherit" ? getMode(resolvedChatId) : agent.permMode,
6547
6851
  role: agent.role ?? "worker",
6548
6852
  persona: agent.persona ?? void 0,
6549
6853
  maxRuntimeMs: agent.maxRuntimeMs,
@@ -6604,6 +6908,10 @@ function cancelAgent(agentId, reason = "user_cancelled") {
6604
6908
  }).catch(() => {
6605
6909
  });
6606
6910
  log(`[orchestrator] Agent ${agentId} cancelled (reason: ${reason})`);
6911
+ const cancelOrch = getOrchestration(db3, agent.orchestrationId);
6912
+ if (cancelOrch) {
6913
+ startNextQueued(cancelOrch.chatId);
6914
+ }
6607
6915
  return true;
6608
6916
  }
6609
6917
  function cancelAllAgents(chatId, reason = "user_cancelled") {
@@ -6708,8 +7016,10 @@ function registerMcp(db3, opts) {
6708
7016
  propagateChange(db3, opts.name, "add").catch(() => {
6709
7017
  });
6710
7018
  }
6711
- function unregisterMcp(db3, name) {
6712
- propagateChange(db3, name, "remove").catch(() => {
7019
+ async function unregisterMcp(db3, name) {
7020
+ await propagateChange(db3, name, "remove").catch((err) => {
7021
+ const { warn: warn2 } = (init_log(), __toCommonJS(log_exports));
7022
+ warn2(`[mcp] Propagation removal failed for "${name}":`, err);
6713
7023
  });
6714
7024
  removeMcpServer(db3, name);
6715
7025
  }
@@ -7057,6 +7367,12 @@ import { createInterface as createInterface3 } from "readline";
7057
7367
  import { readFileSync as readFileSync6, existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
7058
7368
  import { join as join11 } from "path";
7059
7369
  import { homedir as homedir4 } from "os";
7370
+ function applySignalDecay(confidence, createdAt) {
7371
+ const ageMs = Date.now() - (/* @__PURE__ */ new Date(createdAt + (createdAt.endsWith("Z") ? "" : "Z"))).getTime();
7372
+ const ageDays = ageMs / (24 * 60 * 60 * 1e3);
7373
+ const decayed = confidence * Math.pow(0.5, ageDays / SIGNAL_DECAY_HALF_LIFE_DAYS);
7374
+ return decayed >= SIGNAL_DECAY_MIN_CONFIDENCE ? Math.round(decayed * 100) / 100 : 0;
7375
+ }
7060
7376
  function discoverReflectionTargets() {
7061
7377
  const ccClawHome = join11(homedir4(), ".cc-claw");
7062
7378
  const targets = [];
@@ -7070,7 +7386,7 @@ function discoverReflectionTargets() {
7070
7386
  if (!existsSync11(skillFile)) continue;
7071
7387
  let desc = "skill";
7072
7388
  try {
7073
- const content = readFileSync6(skillFile, "utf-8").slice(0, 500);
7389
+ const content = readFileSync6(skillFile, "utf-8");
7074
7390
  const descMatch = content.match(/description:\s*["']?([^"'\n]+)/);
7075
7391
  if (descMatch) desc = descMatch[1].trim().slice(0, 80);
7076
7392
  } catch {
@@ -7138,6 +7454,13 @@ ${categoryList}`);
7138
7454
  sections.push(soulMd || "(empty)");
7139
7455
  sections.push("[Current USER.md]");
7140
7456
  sections.push(userMd || "(empty)");
7457
+ if (params.skillContents && params.skillContents.length > 0) {
7458
+ sections.push("[Current Skill File Contents]");
7459
+ for (const skill of params.skillContents) {
7460
+ sections.push(`--- ${skill.path} ---`);
7461
+ sections.push(skill.content);
7462
+ }
7463
+ }
7141
7464
  sections.push("[Previously Applied Insights]");
7142
7465
  if (appliedInsights.length === 0) {
7143
7466
  sections.push("(none)");
@@ -7170,13 +7493,13 @@ ${categoryList}`);
7170
7493
  Available targets:
7171
7494
  ${targetList.map((t) => `- ${t}`).join("\n")}
7172
7495
 
7173
- Choose the MOST SPECIFIC target for each insight:
7174
- - Workflow or process improvements \u2192 the relevant skill file
7175
- - Tool usage patterns or domain-specific steps \u2192 the relevant skill file
7176
- - User preference or profile info \u2192 USER.md
7177
- - Personality, tone, general behavior \u2192 SOUL.md
7178
- - Domain knowledge or reference material \u2192 a context file
7179
- - If no matching skill/context file exists, fall back to SOUL.md or USER.md`);
7496
+ Routing priority (MOST SPECIFIC first \u2014 SOUL.md is a LAST RESORT):
7497
+ 1. Workflow, process, or tool usage \u2192 the relevant skill file
7498
+ 2. Domain knowledge or reference material \u2192 a context file (workspace/context/)
7499
+ 3. User preference, profile, personal info \u2192 USER.md
7500
+ 4. Personality, tone, general behavior \u2192 SOUL.md (ONLY if no skill/context file fits)
7501
+
7502
+ IMPORTANT: Before routing to SOUL.md, check if an existing skill or context file covers the topic. If one exists, route there instead. SOUL.md should only receive broad personality or behavioral rules that don't fit anywhere else.`);
7180
7503
  const targetOptions = targetList.map((t) => t.split(" \u2014 ")[0]).join(" | ");
7181
7504
  sections.push(`[Output Format]
7182
7505
  For each insight, output EXACTLY this format. Separate multiple insights with "---" on its own line.
@@ -7350,7 +7673,7 @@ async function spawnAnalysis(adapter, model2, prompt, timeoutMs = ANALYSIS_TIMEO
7350
7673
  }
7351
7674
  const events = adapter.parseLine(msg);
7352
7675
  for (const ev of events) {
7353
- if (ev.type === "text" && ev.text) accumulatedText += ev.text;
7676
+ if (ev.type === "text" && ev.text) accumulatedText = appendTextChunk(accumulatedText, ev.text);
7354
7677
  if (ev.type === "result") {
7355
7678
  resultText = ev.resultText || accumulatedText;
7356
7679
  if (adapter.shouldKillOnResult()) {
@@ -7373,6 +7696,18 @@ async function spawnAnalysis(adapter, model2, prompt, timeoutMs = ANALYSIS_TIMEO
7373
7696
  return resultText;
7374
7697
  }
7375
7698
  async function runAnalysis(chatId, opts = {}) {
7699
+ if (analysisLocks.has(chatId)) {
7700
+ log(`[reflection] Skipping analysis for ${chatId} \u2014 another analysis is already in progress`);
7701
+ return [];
7702
+ }
7703
+ analysisLocks.add(chatId);
7704
+ try {
7705
+ return await runAnalysisImpl(chatId, opts);
7706
+ } finally {
7707
+ analysisLocks.delete(chatId);
7708
+ }
7709
+ }
7710
+ async function runAnalysisImpl(chatId, opts) {
7376
7711
  const db3 = getDb();
7377
7712
  const { force = false } = opts;
7378
7713
  if (!force) {
@@ -7398,13 +7733,37 @@ async function runAnalysis(chatId, opts = {}) {
7398
7733
  const rejected = getRejectedInsights(db3, chatId);
7399
7734
  const codebaseEnabled = process.env.REFLECTION_CODEBASE_RECOMMENDATIONS === "1";
7400
7735
  const availableTargets = discoverReflectionTargets();
7736
+ const SKILL_CONTENT_CAP = 15e3;
7737
+ const skillContents = [];
7738
+ const ccClawHome = join11(homedir4(), ".cc-claw");
7739
+ const signalCorpus = signals.map((s) => s.trigger).join(" ").toLowerCase();
7740
+ const convCorpus = (conversations ?? "").toLowerCase();
7741
+ const searchCorpus = signalCorpus + " " + convCorpus;
7742
+ let totalSkillChars = 0;
7743
+ for (const target of availableTargets) {
7744
+ if (!target.path.startsWith("workspace/skills/")) continue;
7745
+ const skillName = target.path.split("/")[2] ?? "";
7746
+ const skillNameNorm = skillName.toLowerCase().replace(/-/g, " ");
7747
+ const descNorm = target.description.toLowerCase();
7748
+ const isRelevant = searchCorpus.includes(skillNameNorm) || searchCorpus.includes(skillName.toLowerCase()) || descNorm.split(/\s+/).some((word) => word.length > 4 && searchCorpus.includes(word));
7749
+ if (!isRelevant) continue;
7750
+ try {
7751
+ const fullPath = join11(ccClawHome, target.path);
7752
+ if (existsSync11(fullPath)) {
7753
+ const content = readFileSync6(fullPath, "utf-8");
7754
+ if (totalSkillChars + content.length > SKILL_CONTENT_CAP) break;
7755
+ skillContents.push({ path: target.path, content });
7756
+ totalSkillChars += content.length;
7757
+ }
7758
+ } catch {
7759
+ }
7760
+ }
7401
7761
  const prompt = buildAnalysisPrompt({
7402
- signals: signals.map((s) => ({
7403
- signalType: s.signalType,
7404
- trigger: s.trigger,
7405
- confidence: s.confidence,
7406
- created_at: s.created_at
7407
- })),
7762
+ signals: signals.map((s) => {
7763
+ const decayedConfidence = applySignalDecay(s.confidence, s.created_at);
7764
+ return { signalType: s.signalType, trigger: s.trigger, confidence: decayedConfidence, created_at: s.created_at };
7765
+ }).filter((s) => s.confidence > 0),
7766
+ // drop signals that decayed below threshold
7408
7767
  conversations,
7409
7768
  soulMd,
7410
7769
  userMd,
@@ -7420,7 +7779,8 @@ async function runAnalysis(chatId, opts = {}) {
7420
7779
  category: i.category
7421
7780
  })),
7422
7781
  codebaseEnabled,
7423
- availableTargets
7782
+ availableTargets,
7783
+ skillContents
7424
7784
  });
7425
7785
  const resolved = resolveReflectionAdapter(chatId);
7426
7786
  if (!resolved) {
@@ -7442,6 +7802,7 @@ async function runAnalysis(chatId, opts = {}) {
7442
7802
  return [];
7443
7803
  }
7444
7804
  const parsed = parseAnalysisOutput(rawOutput);
7805
+ const hasUsableOutput = parsed.length > 0;
7445
7806
  const signalIdStr = signals.length > 0 ? signals.map((s) => s.id).join(",") : "manual";
7446
7807
  for (const insight of parsed) {
7447
7808
  let conflictsWithId = null;
@@ -7464,7 +7825,11 @@ async function runAnalysis(chatId, opts = {}) {
7464
7825
  model: model2
7465
7826
  });
7466
7827
  }
7467
- if (signals.length > 0) markSignalsProcessed(db3, signals.map((s) => s.id));
7828
+ if (hasUsableOutput && signals.length > 0) {
7829
+ markSignalsProcessed(db3, signals.map((s) => s.id));
7830
+ } else if (!hasUsableOutput && signals.length > 0) {
7831
+ log(`[reflection] Analysis returned unparseable output for ${chatId} \u2014 signals preserved for retry`);
7832
+ }
7468
7833
  aggregateDailyMetrics(db3, chatId);
7469
7834
  logActivity(db3, {
7470
7835
  chatId,
@@ -7545,11 +7910,12 @@ async function runNightlyReflection(opts = {}) {
7545
7910
  log(`[reflection] Nightly reflection complete: ${results.length} chat(s) produced insights`);
7546
7911
  return { results };
7547
7912
  }
7548
- var ANALYSIS_TIMEOUT_MS, CONVERSATIONS_CAP, MAX_INSIGHTS, VALID_CATEGORIES, VALID_CATEGORIES_NO_CODEBASE;
7913
+ var ANALYSIS_TIMEOUT_MS, CONVERSATIONS_CAP, MAX_INSIGHTS, SIGNAL_DECAY_HALF_LIFE_DAYS, SIGNAL_DECAY_MIN_CONFIDENCE, VALID_CATEGORIES, VALID_CATEGORIES_NO_CODEBASE, analysisLocks;
7549
7914
  var init_analyze = __esm({
7550
7915
  "src/reflection/analyze.ts"() {
7551
7916
  "use strict";
7552
7917
  init_log();
7918
+ init_text_utils();
7553
7919
  init_paths();
7554
7920
  init_store5();
7555
7921
  init_backends();
@@ -7559,6 +7925,8 @@ var init_analyze = __esm({
7559
7925
  ANALYSIS_TIMEOUT_MS = 12e4;
7560
7926
  CONVERSATIONS_CAP = 4e3;
7561
7927
  MAX_INSIGHTS = 5;
7928
+ SIGNAL_DECAY_HALF_LIFE_DAYS = 7;
7929
+ SIGNAL_DECAY_MIN_CONFIDENCE = 0.1;
7562
7930
  VALID_CATEGORIES = [
7563
7931
  "behavior",
7564
7932
  "preference",
@@ -7574,6 +7942,7 @@ var init_analyze = __esm({
7574
7942
  "domain",
7575
7943
  "error_pattern"
7576
7944
  ];
7945
+ analysisLocks = /* @__PURE__ */ new Set();
7577
7946
  }
7578
7947
  });
7579
7948
 
@@ -7862,12 +8231,12 @@ import { randomBytes } from "crypto";
7862
8231
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync13 } from "fs";
7863
8232
  function createSubAgentToken(agentId) {
7864
8233
  const token = `sub:${agentId.slice(0, 8)}:${randomBytes(16).toString("hex")}`;
7865
- subAgentTokens.set(token, agentId);
8234
+ subAgentTokens.set(token, { agentId, expiresAt: Date.now() + SUB_AGENT_TOKEN_TTL_MS });
7866
8235
  return token;
7867
8236
  }
7868
8237
  function revokeSubAgentToken(agentId) {
7869
- for (const [token, id] of subAgentTokens) {
7870
- if (id === agentId) {
8238
+ for (const [token, entry] of subAgentTokens) {
8239
+ if (entry.agentId === agentId) {
7871
8240
  subAgentTokens.delete(token);
7872
8241
  break;
7873
8242
  }
@@ -7881,6 +8250,14 @@ function htmlResponse(res, html) {
7881
8250
  res.writeHead(200, { "Content-Type": "text/html" });
7882
8251
  res.end(html);
7883
8252
  }
8253
+ function validateAgentIdentity(req, body) {
8254
+ const boundAgentId = req.__boundAgentId;
8255
+ if (!boundAgentId) return;
8256
+ const callerAgentId = body.agentId ?? body.fromAgentId ?? body.message?.fromAgentId;
8257
+ if (callerAgentId && callerAgentId !== boundAgentId) {
8258
+ throw new Error(`IDENTITY_MISMATCH: Token bound to agent ${boundAgentId}, but request claims ${callerAgentId}`);
8259
+ }
8260
+ }
7884
8261
  function readBody(req) {
7885
8262
  return new Promise((resolve, reject) => {
7886
8263
  const chunks = [];
@@ -7903,22 +8280,31 @@ function startDashboard() {
7903
8280
  const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
7904
8281
  const authHeader = req.headers.authorization ?? "";
7905
8282
  const bearerToken = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
7906
- const queryToken = url.searchParams.get("token") ?? "";
7907
- const presentedToken = bearerToken || queryToken;
8283
+ const presentedToken = bearerToken;
7908
8284
  const isMainToken = presentedToken === DASHBOARD_TOKEN;
7909
- const isSubAgentToken = subAgentTokens.has(presentedToken);
8285
+ const subEntry = subAgentTokens.get(presentedToken);
8286
+ const isSubAgentToken = !!subEntry && subEntry.expiresAt > Date.now();
7910
8287
  if (!isMainToken && !isSubAgentToken) {
7911
8288
  res.writeHead(401, { "Content-Type": "text/plain" });
7912
8289
  res.end("Unauthorized");
7913
8290
  return;
7914
8291
  }
7915
- const MAIN_ONLY_PATHS = /* @__PURE__ */ new Set([
7916
- "/api/orchestrator/spawn",
7917
- "/api/orchestrator/cancel",
7918
- "/api/orchestrator/create-task"
8292
+ const SUB_AGENT_ALLOWED_PATHS = /* @__PURE__ */ new Set([
8293
+ "/api/orchestrator/update-task",
8294
+ "/api/orchestrator/set-state",
8295
+ "/api/orchestrator/send-message",
8296
+ "/api/orchestrator/read-inbox",
8297
+ "/api/orchestrator/broadcast",
8298
+ "/api/orchestrator/get-state",
8299
+ "/api/orchestrator/list-state",
8300
+ "/api/orchestrator/check-agent",
8301
+ "/api/health"
7919
8302
  ]);
7920
- if (isSubAgentToken && MAIN_ONLY_PATHS.has(url.pathname)) {
7921
- return jsonResponse(res, { error: "Forbidden: sub-agent tokens cannot access this endpoint" }, 403);
8303
+ if (isSubAgentToken && !SUB_AGENT_ALLOWED_PATHS.has(url.pathname)) {
8304
+ return jsonResponse(res, { error: "Forbidden: sub-agent tokens can only access designated endpoints" }, 403);
8305
+ }
8306
+ if (isSubAgentToken && req.method === "POST" && url.pathname.startsWith("/api/orchestrator/") && url.pathname !== "/api/health") {
8307
+ req.__boundAgentId = subEntry.agentId;
7922
8308
  }
7923
8309
  if (url.pathname === "/api/health") {
7924
8310
  return jsonResponse(res, { status: "ok", version: VERSION, uptime: process.uptime() });
@@ -8013,8 +8399,8 @@ function startDashboard() {
8013
8399
  if (url.pathname.startsWith("/api/mcps/") && url.pathname !== "/api/mcps/sync" && url.pathname !== "/api/mcps/import" && req.method === "DELETE") {
8014
8400
  try {
8015
8401
  const name = decodeURIComponent(url.pathname.slice("/api/mcps/".length));
8016
- const { removeMcpServer: removeMcpServer2 } = await Promise.resolve().then(() => (init_store2(), store_exports2));
8017
- removeMcpServer2(getDb(), name);
8402
+ const { unregisterMcp: unregisterMcp2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports2));
8403
+ await unregisterMcp2(getDb(), name);
8018
8404
  return jsonResponse(res, { success: true });
8019
8405
  } catch (err) {
8020
8406
  return jsonResponse(res, { error: errorMessage(err) }, 400);
@@ -8085,6 +8471,7 @@ function startDashboard() {
8085
8471
  if (url.pathname === "/api/orchestrator/send-message" && req.method === "POST") {
8086
8472
  try {
8087
8473
  const body = JSON.parse(await readBody(req));
8474
+ validateAgentIdentity(req, body);
8088
8475
  const db3 = getDb();
8089
8476
  const orchId = getOrCreateOrchestration(body.chatId);
8090
8477
  const msgId = sendInboxMessage(db3, { orchestrationId: orchId, ...body.message });
@@ -8096,6 +8483,7 @@ function startDashboard() {
8096
8483
  if (url.pathname === "/api/orchestrator/read-inbox" && req.method === "POST") {
8097
8484
  try {
8098
8485
  const body = JSON.parse(await readBody(req));
8486
+ validateAgentIdentity(req, body);
8099
8487
  const db3 = getDb();
8100
8488
  const orchId = getOrCreateOrchestration(body.chatId);
8101
8489
  const messages = getUnreadMessages(db3, orchId, body.agentId);
@@ -8110,6 +8498,7 @@ function startDashboard() {
8110
8498
  if (url.pathname === "/api/orchestrator/set-state" && req.method === "POST") {
8111
8499
  try {
8112
8500
  const body = JSON.parse(await readBody(req));
8501
+ validateAgentIdentity(req, body);
8113
8502
  const db3 = getDb();
8114
8503
  const orchId = getOrCreateOrchestration(body.chatId);
8115
8504
  setState(db3, orchId, body.key, body.value, body.setBy);
@@ -8144,6 +8533,7 @@ function startDashboard() {
8144
8533
  if (url.pathname === "/api/orchestrator/broadcast" && req.method === "POST") {
8145
8534
  try {
8146
8535
  const body = JSON.parse(await readBody(req));
8536
+ validateAgentIdentity(req, body);
8147
8537
  const db3 = getDb();
8148
8538
  const orchId = getOrCreateOrchestration(body.chatId);
8149
8539
  const agents2 = listActiveAgents(db3).filter((a) => a.orchestrationId === orchId);
@@ -8306,11 +8696,17 @@ function startDashboard() {
8306
8696
  "Cache-Control": "no-cache",
8307
8697
  Connection: "keep-alive"
8308
8698
  });
8699
+ let clientDisconnected = false;
8700
+ res.on("close", () => {
8701
+ clientDisconnected = true;
8702
+ });
8309
8703
  const sendSSE = (event, data) => {
8310
- res.write(`event: ${event}
8704
+ if (!clientDisconnected && !res.writableEnded) {
8705
+ res.write(`event: ${event}
8311
8706
  data: ${JSON.stringify(data)}
8312
8707
 
8313
8708
  `);
8709
+ }
8314
8710
  };
8315
8711
  try {
8316
8712
  const response = await askAgent2(chatId, body.message, {
@@ -8323,10 +8719,10 @@ data: ${JSON.stringify(data)}
8323
8719
  });
8324
8720
  if (response.usage) addUsage2(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, model2 ?? "unknown", void 0, response.usage.contextSize);
8325
8721
  sendSSE("done", JSON.stringify({ text: response.text, usage: response.usage }));
8326
- res.end();
8722
+ if (!res.writableEnded) res.end();
8327
8723
  } catch (err) {
8328
8724
  sendSSE("error", errorMessage(err));
8329
- res.end();
8725
+ if (!res.writableEnded) res.end();
8330
8726
  }
8331
8727
  } else {
8332
8728
  const response = await askAgent2(chatId, body.message, { cwd, model: model2, permMode: mode });
@@ -8577,6 +8973,75 @@ data: ${JSON.stringify(data)}
8577
8973
  const result = await triggerJob2(id);
8578
8974
  return jsonResponse(res, { success: true, result });
8579
8975
  }
8976
+ if (action === "create") {
8977
+ const { insertJob: insertJob2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8978
+ const { startSingleJob: startSingleJob2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
8979
+ const job = insertJob2(body);
8980
+ startSingleJob2(job);
8981
+ return jsonResponse(res, { id: job.id, success: true });
8982
+ }
8983
+ if (action === "edit") {
8984
+ const { getDb: getDb2, getJobById: getJobById2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8985
+ const { startSingleJob: startSingleJob2, stopJobTimer: stopJobTimer2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
8986
+ const db3 = getDb2();
8987
+ const updates = [];
8988
+ const values = [];
8989
+ if (body.title !== void 0) {
8990
+ updates.push("title = ?");
8991
+ values.push(body.title);
8992
+ }
8993
+ if (body.description !== void 0) {
8994
+ updates.push("description = ?");
8995
+ values.push(body.description);
8996
+ }
8997
+ if (body.cron !== void 0) {
8998
+ updates.push("cron = ?, schedule_type = 'cron', at_time = NULL, every_ms = NULL");
8999
+ values.push(body.cron);
9000
+ }
9001
+ if (body.atTime !== void 0) {
9002
+ updates.push("at_time = ?, schedule_type = 'at', cron = NULL, every_ms = NULL");
9003
+ values.push(body.atTime);
9004
+ }
9005
+ if (body.everyMs !== void 0) {
9006
+ updates.push("every_ms = ?, schedule_type = 'every', cron = NULL, at_time = NULL");
9007
+ values.push(body.everyMs);
9008
+ }
9009
+ if (body.backend !== void 0) {
9010
+ updates.push("backend = ?");
9011
+ values.push(body.backend);
9012
+ }
9013
+ if (body.model !== void 0) {
9014
+ updates.push("model = ?");
9015
+ values.push(body.model);
9016
+ }
9017
+ if (body.thinking !== void 0) {
9018
+ updates.push("thinking = ?");
9019
+ values.push(body.thinking);
9020
+ }
9021
+ if (body.timeout !== void 0) {
9022
+ updates.push("timeout = ?");
9023
+ values.push(body.timeout);
9024
+ }
9025
+ if (body.timezone !== void 0) {
9026
+ updates.push("timezone = ?");
9027
+ values.push(body.timezone);
9028
+ }
9029
+ if (body.fallbacks !== void 0) {
9030
+ updates.push("fallbacks = ?");
9031
+ values.push(JSON.stringify(body.fallbacks));
9032
+ }
9033
+ if (updates.length === 0) {
9034
+ return jsonResponse(res, { error: "No fields to update" }, 400);
9035
+ }
9036
+ values.push(body.id);
9037
+ db3.prepare(`UPDATE jobs SET ${updates.join(", ")} WHERE id = ?`).run(...values);
9038
+ const fresh = getJobById2(body.id);
9039
+ if (fresh && fresh.active && fresh.enabled) {
9040
+ stopJobTimer2(body.id);
9041
+ startSingleJob2(fresh);
9042
+ }
9043
+ return jsonResponse(res, { id: body.id, success: true });
9044
+ }
8580
9045
  return jsonResponse(res, { error: "Unknown cron action" }, 400);
8581
9046
  } catch (err) {
8582
9047
  return jsonResponse(res, { error: errorMessage(err) }, 400);
@@ -8709,7 +9174,7 @@ data: ${JSON.stringify(data)}
8709
9174
  function getDashboardToken() {
8710
9175
  return DASHBOARD_TOKEN;
8711
9176
  }
8712
- var PORT, DASHBOARD_TOKEN, subAgentTokens, MAX_BODY_BYTES, DASHBOARD_HTML;
9177
+ var PORT, DASHBOARD_TOKEN, SUB_AGENT_TOKEN_TTL_MS, subAgentTokens, MAX_BODY_BYTES, DASHBOARD_HTML;
8713
9178
  var init_server = __esm({
8714
9179
  "src/dashboard/server.ts"() {
8715
9180
  "use strict";
@@ -8729,7 +9194,14 @@ var init_server = __esm({
8729
9194
  init_store3();
8730
9195
  PORT = parseInt(process.env.DASHBOARD_PORT ?? "3141", 10);
8731
9196
  DASHBOARD_TOKEN = process.env.DASHBOARD_TOKEN || randomBytes(32).toString("hex");
9197
+ SUB_AGENT_TOKEN_TTL_MS = 36e5;
8732
9198
  subAgentTokens = /* @__PURE__ */ new Map();
9199
+ setInterval(() => {
9200
+ const now = Date.now();
9201
+ for (const [token, entry] of subAgentTokens) {
9202
+ if (entry.expiresAt < now) subAgentTokens.delete(token);
9203
+ }
9204
+ }, 3e5);
8733
9205
  MAX_BODY_BYTES = 1048576;
8734
9206
  DASHBOARD_HTML = `<!DOCTYPE html>
8735
9207
  <html lang="en">
@@ -9106,7 +9578,8 @@ var init_detect = __esm({
9106
9578
  init_store4();
9107
9579
  init_store5();
9108
9580
  CORRECTION_PATTERNS = [
9109
- /^no[,.\s!]/i,
9581
+ // Audit O36: Tightened — require "no" followed by correction context (not "No problem", "No worries")
9582
+ /^no[,.\s!]+\s*(that'?s|it'?s|i\s|you|this|the|not|don'?t|stop|wrong)/i,
9110
9583
  /\bwrong\b/i,
9111
9584
  /\bthat's not (right|what|correct)/i,
9112
9585
  /\bi (meant|said)\b/i,
@@ -9148,6 +9621,7 @@ var init_detect = __esm({
9148
9621
  // src/agent.ts
9149
9622
  var agent_exports = {};
9150
9623
  __export(agent_exports, {
9624
+ FIRST_RESPONSE_TIMEOUT_ERROR: () => FIRST_RESPONSE_TIMEOUT_ERROR,
9151
9625
  askAgent: () => askAgent,
9152
9626
  getInFlightMessage: () => getInFlightMessage,
9153
9627
  isChatBusy: () => isChatBusy,
@@ -9166,7 +9640,8 @@ function getInFlightMessage(chatId) {
9166
9640
  function killProcessGroup(proc, signal = "SIGTERM") {
9167
9641
  try {
9168
9642
  if (proc.pid) process.kill(-proc.pid, signal);
9169
- } catch {
9643
+ } catch (err) {
9644
+ warn(`[agent] Group kill (-${proc.pid}) failed: ${err} \u2014 falling back to direct kill`);
9170
9645
  try {
9171
9646
  proc.kill(signal);
9172
9647
  } catch {
@@ -9179,7 +9654,7 @@ function withChatLock(chatId, fn) {
9179
9654
  if (isBlocked) {
9180
9655
  warn(`[agent] Chat ${chatId} is busy \u2014 queuing request (cross-channel head-of-line blocking)`);
9181
9656
  }
9182
- const next = prev.then(fn, fn);
9657
+ const next = prev.then(fn, () => fn());
9183
9658
  chatLocks.set(chatId, next.then(() => {
9184
9659
  chatLocks.delete(chatId);
9185
9660
  }, () => {
@@ -9250,11 +9725,28 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
9250
9725
  proc.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
9251
9726
  const rl2 = createInterface4({ input: proc.stdout });
9252
9727
  let firstLine = true;
9728
+ const frTimeoutMs = opts?.firstResponseTimeoutMs ?? FIRST_RESPONSE_TIMEOUT_MS;
9729
+ let firstResponseTimer;
9730
+ if (frTimeoutMs > 0) {
9731
+ firstResponseTimer = setTimeout(() => {
9732
+ if (firstLine) {
9733
+ warn(`[agent] First-response timeout after ${frTimeoutMs}ms for ${adapter.id} \u2014 no NDJSON output received, killing`);
9734
+ killProcessGroup(proc, "SIGTERM");
9735
+ timedOut = true;
9736
+ cancelState.__firstResponseTimeout = true;
9737
+ sigkillTimer = setTimeout(() => killProcessGroup(proc, "SIGKILL"), 3e3);
9738
+ }
9739
+ }, frTimeoutMs);
9740
+ }
9253
9741
  rl2.on("line", (line) => {
9254
9742
  if (!line.trim()) return;
9255
9743
  if (firstLine) {
9256
9744
  log(`[agent] First CLI message after ${elapsed()}`);
9257
9745
  firstLine = false;
9746
+ if (firstResponseTimer) {
9747
+ clearTimeout(firstResponseTimer);
9748
+ firstResponseTimer = void 0;
9749
+ }
9258
9750
  }
9259
9751
  let msg;
9260
9752
  try {
@@ -9275,7 +9767,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
9275
9767
  break;
9276
9768
  case "text":
9277
9769
  if (ev.text) {
9278
- accumulatedText += ev.text;
9770
+ accumulatedText = appendTextChunk(accumulatedText, ev.text);
9279
9771
  if (opts?.onStream) opts.onStream(ev.text);
9280
9772
  }
9281
9773
  break;
@@ -9351,6 +9843,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
9351
9843
  });
9352
9844
  proc.on("error", (err) => {
9353
9845
  clearTimeout(spawnTimeout);
9846
+ if (firstResponseTimer) clearTimeout(firstResponseTimer);
9354
9847
  if (sigkillTimer) clearTimeout(sigkillTimer);
9355
9848
  rl2.close();
9356
9849
  cancelState.process = void 0;
@@ -9358,6 +9851,10 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
9358
9851
  });
9359
9852
  proc.on("close", (code, signal) => {
9360
9853
  clearTimeout(spawnTimeout);
9854
+ if (firstResponseTimer) {
9855
+ clearTimeout(firstResponseTimer);
9856
+ firstResponseTimer = void 0;
9857
+ }
9361
9858
  if (sigkillTimer) {
9362
9859
  clearTimeout(sigkillTimer);
9363
9860
  sigkillTimer = void 0;
@@ -9372,6 +9869,12 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
9372
9869
  if (stderr) warn(`[agent] stderr: ${stderr.slice(0, 500)}`);
9373
9870
  }
9374
9871
  if (timedOut) {
9872
+ if (cancelState.__firstResponseTimeout) {
9873
+ delete cancelState.__firstResponseTimeout;
9874
+ const stderr = stderrChunks.length > 0 ? Buffer.concat(stderrChunks).toString().trim() : "";
9875
+ reject(new Error(`${FIRST_RESPONSE_TIMEOUT_ERROR}: No response from ${adapter.id} within ${frTimeoutMs / 1e3}s${stderr ? ` \u2014 ${stderr.slice(-300)}` : ""}`));
9876
+ return;
9877
+ }
9375
9878
  let msg = `Spawn timeout after ${effectiveTimeout / 1e3}s`;
9376
9879
  if (pendingTools.size > 0) {
9377
9880
  const tools2 = Array.from(pendingTools.values()).map((t) => typeof t === "string" ? t : t.name).join(", ");
@@ -9393,11 +9896,12 @@ Partial output: ${accumulatedText.slice(-500)}`;
9393
9896
  reject(new Error(`CLI exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}`));
9394
9897
  return;
9395
9898
  }
9396
- resolve({ resultText, sessionId, input, output: output2, cacheRead, contextSize, sawToolEvents, sawResultEvent });
9899
+ const cleanedResult = stripThinkingContent(resultText || accumulatedText);
9900
+ resolve({ resultText: cleanedResult, sessionId, input, output: output2, cacheRead, contextSize, sawToolEvents, sawResultEvent });
9397
9901
  });
9398
9902
  });
9399
9903
  }
9400
- async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, opts, onSlotRotation, parentChatId) {
9904
+ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, opts, onSlotRotation, parentChatId, onModelDowngrade) {
9401
9905
  const geminiAdapter = adapter;
9402
9906
  const slots = getEligibleGeminiSlots(rotationMode);
9403
9907
  if (slots.length === 0) {
@@ -9407,8 +9911,9 @@ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSe
9407
9911
  const { env } = geminiAdapter.getEnvForSlot(parentChatId, void 0, rotationMode);
9408
9912
  return await spawnQuery(adapter, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env });
9409
9913
  }
9410
- const maxAttempts = Math.min(slots.length, 10);
9914
+ const maxAttempts = slots.length;
9411
9915
  let lastError;
9916
+ let unknownRetried = false;
9412
9917
  for (let i = 0; i < maxAttempts; i++) {
9413
9918
  const { env, slot } = geminiAdapter.getEnvForSlot(chatId, void 0, rotationMode);
9414
9919
  if (!slot) break;
@@ -9418,18 +9923,52 @@ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSe
9418
9923
  const effectiveConfig = i === 0 ? configWithSession : baseConfig;
9419
9924
  try {
9420
9925
  const result = await spawnQuery(adapter, effectiveConfig, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env });
9421
- const combinedText = result.resultText || "";
9422
- if (combinedText && /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(combinedText)) {
9423
- throw new Error(combinedText);
9926
+ const checkText = (result.resultText || "").slice(0, 500);
9927
+ if (checkText && /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(checkText)) {
9928
+ throw new Error(checkText);
9424
9929
  }
9425
9930
  markSlotSuccess(slot.id);
9426
9931
  return result;
9427
9932
  } catch (err) {
9428
9933
  const errMsg = err instanceof Error ? err.message : String(err);
9934
+ const isTimeout = errMsg.startsWith(FIRST_RESPONSE_TIMEOUT_ERROR);
9935
+ const isExhausted = /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(errMsg);
9936
+ if ((isTimeout || isExhausted) && GEMINI_DOWNGRADE_MODELS.has(model2)) {
9937
+ const reason = isTimeout ? "No response within timeout" : "Resource exhausted";
9938
+ warn(`[agent:gemini-rotation] ${reason} on ${model2} (${slotLabel}) \u2014 downgrading to ${GEMINI_FALLBACK_MODEL}`);
9939
+ onModelDowngrade?.(model2, GEMINI_FALLBACK_MODEL, `${reason} \u2014 model likely overloaded`);
9940
+ const fallbackConfig = adapter.buildSpawnConfig({
9941
+ prompt: effectiveConfig.args[effectiveConfig.args.indexOf("-p") + 1],
9942
+ model: GEMINI_FALLBACK_MODEL,
9943
+ permMode: "yolo",
9944
+ allowedTools: [],
9945
+ cwd: effectiveConfig.cwd,
9946
+ thinkingLevel,
9947
+ maxTurns
9948
+ });
9949
+ for (const arg of effectiveConfig.args) {
9950
+ if (arg.startsWith("--mcp-config") || arg.startsWith("--resume") || arg.startsWith("--allowed-mcp-server-names")) {
9951
+ const idx = effectiveConfig.args.indexOf(arg);
9952
+ if (idx >= 0 && idx + 1 < effectiveConfig.args.length && !effectiveConfig.args[idx].includes("=")) {
9953
+ fallbackConfig.args.push(arg, effectiveConfig.args[idx + 1]);
9954
+ } else {
9955
+ fallbackConfig.args.push(arg);
9956
+ }
9957
+ }
9958
+ }
9959
+ return await spawnQuery(adapter, fallbackConfig, GEMINI_FALLBACK_MODEL, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env, firstResponseTimeoutMs: 0 });
9960
+ }
9429
9961
  const quotaClass = classifyGeminiQuota(errMsg);
9430
9962
  if (quotaClass === "rate_limited") {
9431
9963
  throw err;
9432
9964
  }
9965
+ if (quotaClass === "unknown" && !unknownRetried) {
9966
+ unknownRetried = true;
9967
+ warn(`[agent:gemini-rotation] Unknown error on ${slotLabel}, retrying once: ${errMsg.slice(0, 200)}`);
9968
+ lastError = err instanceof Error ? err : new Error(errMsg);
9969
+ continue;
9970
+ }
9971
+ unknownRetried = false;
9433
9972
  const effectiveClass = quotaClass === "unknown" ? "daily_quota" : quotaClass;
9434
9973
  warn(`[agent:gemini-rotation] Slot ${slotLabel} exhausted (${effectiveClass}): ${errMsg.slice(0, 200)}`);
9435
9974
  markSlotExhausted(slot.id, effectiveClass);
@@ -9467,7 +10006,7 @@ function askAgent(chatId, userMessage, opts) {
9467
10006
  return withChatLock(chatId, () => askAgentImpl(chatId, userMessage, opts));
9468
10007
  }
9469
10008
  async function askAgentImpl(chatId, userMessage, opts) {
9470
- const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs, maxTurns, onSlotRotation, agentMode: optsAgentMode, onSubagentActivity, settingsSourceChatId } = opts ?? {};
10009
+ const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs, maxTurns, onSlotRotation, onModelDowngrade, agentMode: optsAgentMode, onSubagentActivity, settingsSourceChatId } = opts ?? {};
9471
10010
  const settingsChat = settingsSourceChatId ?? chatId;
9472
10011
  const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(settingsChat);
9473
10012
  const mode = permMode ?? getMode(settingsChat);
@@ -9519,9 +10058,36 @@ async function askAgentImpl(chatId, userMessage, opts) {
9519
10058
  try {
9520
10059
  if (useGeminiRotation) {
9521
10060
  const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
9522
- result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId);
10061
+ const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
10062
+ result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
9523
10063
  } else {
9524
- result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
10064
+ try {
10065
+ result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
10066
+ } catch (err) {
10067
+ const errMsg = err instanceof Error ? err.message : String(err);
10068
+ const isTimeout = errMsg.startsWith(FIRST_RESPONSE_TIMEOUT_ERROR);
10069
+ const isExhausted = /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(errMsg);
10070
+ if (adapter.id === "gemini" && (isTimeout || isExhausted) && GEMINI_DOWNGRADE_MODELS.has(resolvedModel)) {
10071
+ const reason = isTimeout ? "No response within timeout" : "Resource exhausted";
10072
+ warn(`[agent] ${reason} on ${resolvedModel} (no rotation) \u2014 downgrading to ${GEMINI_FALLBACK_MODEL}`);
10073
+ onModelDowngrade?.(chatId, resolvedModel, GEMINI_FALLBACK_MODEL, `${reason} \u2014 model likely overloaded`);
10074
+ const fallbackConfig = adapter.buildSpawnConfig({
10075
+ prompt: configWithSession.args[configWithSession.args.indexOf("-p") + 1],
10076
+ model: GEMINI_FALLBACK_MODEL,
10077
+ permMode: mode,
10078
+ allowedTools,
10079
+ cwd: resolvedCwd,
10080
+ thinkingLevel,
10081
+ maxTurns
10082
+ });
10083
+ if (mcpConfigPath) {
10084
+ fallbackConfig.args = injectMcpConfig(adapter.id, fallbackConfig.args, mcpConfigPath);
10085
+ }
10086
+ result = await spawnQuery(adapter, fallbackConfig, GEMINI_FALLBACK_MODEL, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...spawnOpts, firstResponseTimeoutMs: 0 });
10087
+ } else {
10088
+ throw err;
10089
+ }
10090
+ }
9525
10091
  }
9526
10092
  const wasEmptyResponse = !result.resultText && !result.sawToolEvents && !result.sawResultEvent;
9527
10093
  if (wasEmptyResponse && !cancelState.cancelled && existingSessionId) {
@@ -9530,7 +10096,8 @@ async function askAgentImpl(chatId, userMessage, opts) {
9530
10096
  clearSession(chatId);
9531
10097
  if (useGeminiRotation) {
9532
10098
  const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
9533
- result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId);
10099
+ const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
10100
+ result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
9534
10101
  } else {
9535
10102
  result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
9536
10103
  }
@@ -9601,7 +10168,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
9601
10168
  if (!flag) return args;
9602
10169
  return [...args, ...flag, mcpConfigPath];
9603
10170
  }
9604
- var activeChats, chatLocks, SPAWN_TIMEOUT_MS, MCP_CONFIG_FLAG;
10171
+ var activeChats, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_ERROR, GEMINI_FALLBACK_MODEL, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
9605
10172
  var init_agent = __esm({
9606
10173
  "src/agent.ts"() {
9607
10174
  "use strict";
@@ -9611,6 +10178,8 @@ var init_agent = __esm({
9611
10178
  init_paths();
9612
10179
  init_loader();
9613
10180
  init_log();
10181
+ init_strip_thinking();
10182
+ init_text_utils();
9614
10183
  init_session_log();
9615
10184
  init_summarize();
9616
10185
  init_quota();
@@ -9622,6 +10191,10 @@ var init_agent = __esm({
9622
10191
  activeChats = /* @__PURE__ */ new Map();
9623
10192
  chatLocks = /* @__PURE__ */ new Map();
9624
10193
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
10194
+ FIRST_RESPONSE_TIMEOUT_MS = parseInt(process.env.GEMINI_FIRST_RESPONSE_TIMEOUT_MS ?? "15000", 10);
10195
+ FIRST_RESPONSE_TIMEOUT_ERROR = "FIRST_RESPONSE_TIMEOUT";
10196
+ GEMINI_FALLBACK_MODEL = "gemini-2.5-pro";
10197
+ GEMINI_DOWNGRADE_MODELS = /* @__PURE__ */ new Set(["gemini-3.1-pro-preview"]);
9625
10198
  MCP_CONFIG_FLAG = {
9626
10199
  claude: ["--mcp-config"]
9627
10200
  };
@@ -9646,10 +10219,19 @@ function getChannelRegistry() {
9646
10219
  return registry;
9647
10220
  }
9648
10221
  function isPathAllowed(filePath) {
10222
+ const normalized = filePath.replace(/\.\.\/|\.\.$/g, "");
10223
+ if (normalized !== filePath) return false;
9649
10224
  for (const pattern of BLOCKED_PATH_PATTERNS) {
9650
- if (pattern.test(filePath)) return false;
10225
+ if (pattern.test(normalized)) return false;
9651
10226
  }
9652
- return true;
10227
+ for (const prefix of ALLOWED_PATH_PREFIXES) {
10228
+ if (normalized.startsWith(prefix)) return true;
10229
+ }
10230
+ const ccClawWorkspace = `${process.env.HOME ?? "/root"}/.cc-claw/workspace/`;
10231
+ if (normalized.startsWith(ccClawWorkspace)) return true;
10232
+ const homePath = process.env.HOME ?? "/root";
10233
+ if (normalized.startsWith(homePath) && !normalized.includes("/.")) return true;
10234
+ return false;
9653
10235
  }
9654
10236
  async function processFileSends(chatId, channel, text) {
9655
10237
  const fileSendPattern = /\[SEND_FILE:(.+?)\]/g;
@@ -9698,9 +10280,11 @@ ${responseText.slice(0, 500)}`);
9698
10280
  try {
9699
10281
  const text = responseText.replace(/\s*\[REACT:[^\]]*\]\s*/g, " ").trim();
9700
10282
  if (job.deliveryMode === "webhook") {
9701
- await deliverWebhook(job, text);
9702
- appendToLog(job.chatId, `[cron:#${job.id}] ${job.description}`, text, job.backend ?? "claude", job.model, null);
9703
- return true;
10283
+ const webhookOk = await deliverWebhook(job, text);
10284
+ if (webhookOk) {
10285
+ appendToLog(job.chatId, `[cron:#${job.id}] ${job.description}`, text, job.backend ?? "claude", job.model, null);
10286
+ }
10287
+ return webhookOk;
9704
10288
  }
9705
10289
  const channelName = job.channel ?? "telegram";
9706
10290
  const channel = registry?.get(channelName);
@@ -9728,7 +10312,7 @@ async function deliverWebhook(job, responseText) {
9728
10312
  const url = job.target;
9729
10313
  if (!url) {
9730
10314
  error(`[delivery] Job #${job.id}: webhook mode but no target URL`);
9731
- return;
10315
+ return false;
9732
10316
  }
9733
10317
  try {
9734
10318
  const resp = await fetch(url, {
@@ -9744,11 +10328,13 @@ async function deliverWebhook(job, responseText) {
9744
10328
  });
9745
10329
  if (!resp.ok) {
9746
10330
  error(`[delivery] Webhook POST to ${url} failed: ${resp.status} ${resp.statusText}`);
9747
- } else {
9748
- log(`[delivery] Webhook POST to ${url}: ${resp.status}`);
10331
+ return false;
9749
10332
  }
10333
+ log(`[delivery] Webhook POST to ${url}: ${resp.status}`);
10334
+ return true;
9750
10335
  } catch (err) {
9751
10336
  error(`[delivery] Webhook POST to ${url} error: ${errorMessage(err)}`);
10337
+ return false;
9752
10338
  }
9753
10339
  }
9754
10340
  async function notifyJobFailure(job, errorMessage2, retryInfo) {
@@ -9776,25 +10362,30 @@ async function notifyJobAutoPaused(job) {
9776
10362
  } catch {
9777
10363
  }
9778
10364
  }
9779
- var registry, BLOCKED_PATH_PATTERNS;
10365
+ var registry, ALLOWED_PATH_PREFIXES, BLOCKED_PATH_PATTERNS;
9780
10366
  var init_delivery = __esm({
9781
10367
  "src/scheduler/delivery.ts"() {
9782
10368
  "use strict";
9783
10369
  init_log();
9784
10370
  init_session_log();
9785
10371
  registry = null;
10372
+ ALLOWED_PATH_PREFIXES = [
10373
+ "/tmp/",
10374
+ "/var/tmp/"
10375
+ ];
9786
10376
  BLOCKED_PATH_PATTERNS = [
9787
10377
  /\/\.ssh\//,
9788
10378
  /\/\.gnupg\//,
9789
10379
  /\/\.aws\//,
9790
10380
  /\/\.env$/,
9791
10381
  /\/\.env\./,
9792
- /\/\.cc-claw\/\.env$/,
9793
10382
  /\/credentials\.json$/,
9794
10383
  /\/\.gitconfig$/,
9795
10384
  /\/\.netrc$/,
9796
10385
  /\/etc\/shadow$/,
9797
- /\/etc\/passwd$/
10386
+ /\/etc\/passwd$/,
10387
+ /\/secret/i,
10388
+ /\/token/i
9798
10389
  ];
9799
10390
  }
9800
10391
  });
@@ -9873,12 +10464,14 @@ var init_retry = __esm({
9873
10464
  });
9874
10465
 
9875
10466
  // src/scheduler/health.ts
10467
+ import * as os from "os";
9876
10468
  function startHealthMonitor2() {
9877
10469
  lastHeartbeat = /* @__PURE__ */ new Date();
9878
10470
  heartbeatTimer = setInterval(() => {
9879
10471
  lastHeartbeat = /* @__PURE__ */ new Date();
9880
10472
  log(`[health] Scheduler heartbeat at ${lastHeartbeat.toISOString()}`);
9881
10473
  }, HEARTBEAT_INTERVAL_MS);
10474
+ heartbeatTimer.unref();
9882
10475
  }
9883
10476
  function stopHealthMonitor2() {
9884
10477
  if (heartbeatTimer) {
@@ -9900,7 +10493,10 @@ function getHealthReport() {
9900
10493
  activeJobs: activeJobs.length,
9901
10494
  pausedJobs: pausedJobs.length,
9902
10495
  failingJobs,
9903
- totalJobs: allJobs.length
10496
+ totalJobs: allJobs.length,
10497
+ // Audit O39: Include system metrics for operator visibility
10498
+ memoryMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
10499
+ loadAvg: os.loadavg()[0]
9904
10500
  };
9905
10501
  }
9906
10502
  function formatHealthReport(report) {
@@ -9926,7 +10522,11 @@ function computeStaggerMs(jobId, cronExpr) {
9926
10522
  if (parts[0] !== "0") return 0;
9927
10523
  if (!/[*/]/.test(parts[1])) return 0;
9928
10524
  const maxStagger = 12e4;
9929
- return jobId * 7919 % maxStagger;
10525
+ let h = jobId * 2654435761;
10526
+ h = (h >>> 16 ^ h) * 73244475;
10527
+ h = (h >>> 16 ^ h) * 73244475;
10528
+ h = h >>> 16 ^ h;
10529
+ return Math.abs(h) % maxStagger;
9930
10530
  }
9931
10531
  var lastHeartbeat, heartbeatTimer, HEARTBEAT_INTERVAL_MS;
9932
10532
  var init_health2 = __esm({
@@ -10050,6 +10650,7 @@ function buildProposalKeyboard(insightId, category) {
10050
10650
  return [
10051
10651
  [
10052
10652
  { label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
10653
+ { label: "Preview", data: `evolve:preview:${insightId}` },
10053
10654
  { label: "Discuss", data: `evolve:discuss:${insightId}` }
10054
10655
  ],
10055
10656
  [
@@ -10239,7 +10840,8 @@ function mergeAndDeduplicate(raw) {
10239
10840
  });
10240
10841
  }
10241
10842
  function parseFrontmatter2(content, fallbackName) {
10242
- const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
10843
+ const cleaned = content.replace(/^\uFEFF/, "");
10844
+ const fmMatch = cleaned.match(/^---\s*\n([\s\S]*?)\n---/);
10243
10845
  if (!fmMatch) {
10244
10846
  return { name: fallbackName, description: "" };
10245
10847
  }
@@ -10808,9 +11410,19 @@ var init_format_time = __esm({
10808
11410
  });
10809
11411
 
10810
11412
  // src/intent/classify.ts
11413
+ var classify_exports = {};
11414
+ __export(classify_exports, {
11415
+ classifyIntent: () => classifyIntent,
11416
+ getIntentStats: () => getIntentStats,
11417
+ resetIntentStats: () => resetIntentStats
11418
+ });
10811
11419
  function getIntentStats() {
10812
11420
  return { ...intentCounts };
10813
11421
  }
11422
+ function resetIntentStats() {
11423
+ intentCounts.chat = 0;
11424
+ intentCounts.agentic = 0;
11425
+ }
10814
11426
  function classifyIntent(text, chatId) {
10815
11427
  const trimmed = text.trim();
10816
11428
  if (trimmed.startsWith(">>")) return "agentic";
@@ -11013,9 +11625,20 @@ var init_image_gen = __esm({
11013
11625
 
11014
11626
  // src/voice/stt.ts
11015
11627
  import crypto from "crypto";
11016
- import { execFile as execFile2 } from "child_process";
11628
+ import { execFile as execFile2, execFileSync } from "child_process";
11017
11629
  import { readFile as readFile4, unlink } from "fs/promises";
11018
11630
  import { promisify as promisify2 } from "util";
11631
+ function ensureFfmpeg() {
11632
+ if (ffmpegAvailable === true) return;
11633
+ if (ffmpegAvailable === false) throw new Error("ffmpeg is required for voice replies. Install it: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)");
11634
+ try {
11635
+ execFileSync("ffmpeg", ["-version"], { stdio: "ignore" });
11636
+ ffmpegAvailable = true;
11637
+ } catch {
11638
+ ffmpegAvailable = false;
11639
+ throw new Error("ffmpeg is required for voice replies. Install it: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)");
11640
+ }
11641
+ }
11019
11642
  function isVoiceEnabled(chatId) {
11020
11643
  const db3 = getDb();
11021
11644
  const row = db3.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
@@ -11149,6 +11772,7 @@ async function grokTts(text, apiKey, voiceId) {
11149
11772
  return await mp3ToOgg(mp3Buffer);
11150
11773
  }
11151
11774
  async function mp3ToOgg(mp3Buffer) {
11775
+ ensureFfmpeg();
11152
11776
  const id = crypto.randomUUID();
11153
11777
  const tmpMp3 = `/tmp/cc-claw-tts-${id}.mp3`;
11154
11778
  const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
@@ -11165,6 +11789,7 @@ async function mp3ToOgg(mp3Buffer) {
11165
11789
  return oggBuffer;
11166
11790
  }
11167
11791
  async function macOsTts(text, voice2 = "Samantha") {
11792
+ ensureFfmpeg();
11168
11793
  const id = crypto.randomUUID();
11169
11794
  const tmpAiff = `/tmp/cc-claw-tts-${id}.aiff`;
11170
11795
  const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
@@ -11179,13 +11804,14 @@ async function macOsTts(text, voice2 = "Samantha") {
11179
11804
  });
11180
11805
  return oggBuffer;
11181
11806
  }
11182
- var execFileAsync2, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES;
11807
+ var execFileAsync2, ffmpegAvailable, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES;
11183
11808
  var init_stt = __esm({
11184
11809
  "src/voice/stt.ts"() {
11185
11810
  "use strict";
11186
11811
  init_log();
11187
11812
  init_store5();
11188
11813
  execFileAsync2 = promisify2(execFile2);
11814
+ ffmpegAvailable = null;
11189
11815
  ELEVENLABS_VOICES = {
11190
11816
  "21m00Tcm4TlvDq8ikWAM": { name: "Rachel", gender: "F" },
11191
11817
  "EXAVITQu4vr4xnSDxMaL": { name: "Sarah", gender: "F" },
@@ -12051,11 +12677,21 @@ var init_guard = __esm({
12051
12677
  /\bdd\b.*\bof=/,
12052
12678
  /\b(shutdown|reboot|halt|poweroff)\b/,
12053
12679
  />\s*\/dev\/sd/,
12054
- /\bchmod\s+-R\s+777\b/,
12680
+ /\bchmod\b.*\b777\b/,
12681
+ // catch chmod 777 regardless of flag order
12055
12682
  /\bchown\s+-R\b/,
12056
12683
  /\bkillall\b/,
12057
12684
  /\blaunchctl\s+(unload|remove)\b/,
12058
- /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*:/
12685
+ /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*:/,
12686
+ // Audit C02: indirect destructive commands
12687
+ /\bfind\b.*\b-delete\b/,
12688
+ // find / -delete
12689
+ /\b(curl|wget)\b.*\|\s*(ba)?sh\b/,
12690
+ // remote code execution
12691
+ /\bbase64\b.*\|\s*(ba)?sh\b/,
12692
+ // encoded payload execution
12693
+ /\btruncate\s+-s\s*0\b/
12694
+ // file wipe via truncate
12059
12695
  ];
12060
12696
  pendingCommands = /* @__PURE__ */ new Map();
12061
12697
  PENDING_TTL_MS = 5 * 60 * 1e3;
@@ -12066,8 +12702,9 @@ var init_guard = __esm({
12066
12702
  import { execFile as execFile3 } from "child_process";
12067
12703
  function executeShell(command, cwd, timeoutMs = SHELL_TIMEOUT_MS) {
12068
12704
  return new Promise((resolve) => {
12705
+ const shell = process.env.SHELL || "/bin/sh";
12069
12706
  execFile3(
12070
- "/bin/zsh",
12707
+ shell,
12071
12708
  ["-c", command],
12072
12709
  { cwd, timeout: timeoutMs, maxBuffer: 5 * 1024 * 1024 },
12073
12710
  (error3, stdout, stderr) => {
@@ -12090,9 +12727,12 @@ function executeShell(command, cwd, timeoutMs = SHELL_TIMEOUT_MS) {
12090
12727
  );
12091
12728
  });
12092
12729
  }
12730
+ function escapeHtml(s) {
12731
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
12732
+ }
12093
12733
  function formatCodeBlock(command, output2, exitCode) {
12094
- return `<pre>$ ${command}
12095
- ${output2}
12734
+ return `<pre>$ ${escapeHtml(command)}
12735
+ ${escapeHtml(output2)}
12096
12736
  [exit ${exitCode}]</pre>`;
12097
12737
  }
12098
12738
  function formatRaw(output2) {
@@ -12253,7 +12893,6 @@ __export(router_exports, {
12253
12893
  stopAllSideQuests: () => stopAllSideQuests
12254
12894
  });
12255
12895
  import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2, mkdir as mkdir2, readdir as readdir3, stat } from "fs/promises";
12256
- import { existsSync as existsSync18 } from "fs";
12257
12896
  import { randomUUID as randomUUID3 } from "crypto";
12258
12897
  import { resolve as resolvePath, join as join18 } from "path";
12259
12898
  import { homedir as homedir6 } from "os";
@@ -12700,6 +13339,7 @@ ${cmds.map((c) => ` ${c.cmd} \u2014 ${c.desc}`).join("\n")}`
12700
13339
  case "stop": {
12701
13340
  const stopped = stopAgent(chatId);
12702
13341
  stopAllSideQuests(chatId);
13342
+ cancelAllAgents(chatId);
12703
13343
  await channel.sendText(
12704
13344
  chatId,
12705
13345
  stopped ? "Stopping current task..." : "Nothing is running.",
@@ -14170,13 +14810,17 @@ Message: "${testMsg}"`, { parseMode: "plain" });
14170
14810
  else if (diffHours < 24) lastText = `${diffHours}h ago`;
14171
14811
  else lastText = `${Math.floor(diffHours / 24)}d ago`;
14172
14812
  }
14813
+ const { getReflectionModelConfig: getReflectionModelConfig2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
14814
+ const modelConfig = getReflectionModelConfig2(getDb(), chatId);
14815
+ const modelLabel = modelConfig.mode === "pinned" && modelConfig.backend && modelConfig.model ? `Pinned: ${modelConfig.backend}/${modelConfig.model}` : modelConfig.mode === "cheap" ? "Cheap" : "Auto";
14173
14816
  const dashText = [
14174
14817
  "Self-Learning & Evolution",
14175
14818
  "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
14176
14819
  "",
14177
14820
  `\u2705 Active`,
14178
14821
  `Signals: ${signals} pending \xB7 Proposals: ${pending}`,
14179
- `Last analysis: ${lastText}`
14822
+ `Last analysis: ${lastText}`,
14823
+ `Model: ${modelLabel}`
14180
14824
  ].join("\n");
14181
14825
  if (typeof channel.sendKeyboard === "function") {
14182
14826
  const { buildEvolveMenuKeyboard: buildEvolveMenuKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
@@ -14187,47 +14831,6 @@ Message: "${testMsg}"`, { parseMode: "plain" });
14187
14831
  }
14188
14832
  break;
14189
14833
  }
14190
- case "reflect": {
14191
- const lastReflect = lastReflectTime.get(chatId);
14192
- if (lastReflect && Date.now() - lastReflect < REFLECT_COOLDOWN_MS) {
14193
- const remaining = Math.ceil((REFLECT_COOLDOWN_MS - (Date.now() - lastReflect)) / 1e3);
14194
- await channel.sendText(chatId, `Please wait ${remaining}s before reflecting again.`, { parseMode: "plain" });
14195
- break;
14196
- }
14197
- const { getMessagePairCount: getMessagePairCount2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
14198
- const pairCount = getMessagePairCount2(chatId);
14199
- if (pairCount < 2) {
14200
- await channel.sendText(chatId, "Need more conversation to analyze \u2014 at least 2 message exchanges.", { parseMode: "plain" });
14201
- break;
14202
- }
14203
- lastReflectTime.set(chatId, Date.now());
14204
- await channel.sendText(chatId, "Step 1/3: Summarizing conversation...", { parseMode: "plain" });
14205
- try {
14206
- const { summarizeSession: summarizeSession2 } = await Promise.resolve().then(() => (init_summarize(), summarize_exports));
14207
- await summarizeSession2(chatId, false);
14208
- } catch (e) {
14209
- await channel.sendText(chatId, `Summarization failed: ${e}`, { parseMode: "plain" });
14210
- break;
14211
- }
14212
- await channel.sendText(chatId, "Step 2/3: Analyzing for insights...", { parseMode: "plain" });
14213
- try {
14214
- const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
14215
- const insights = await runAnalysis2(chatId, { force: true });
14216
- if (insights.length === 0) {
14217
- await channel.sendText(chatId, "Step 3/3: No actionable improvements found in this session.", { parseMode: "plain" });
14218
- } else {
14219
- await channel.sendText(chatId, `Step 3/3: Found ${insights.length} proposal(s). Let's review them one by one.`, { parseMode: "plain" });
14220
- const { getPendingInsights: getPendingInsights2, createReviewSession: createReviewSession2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
14221
- const pending = getPendingInsights2(getDb(), chatId);
14222
- const insightIds = pending.slice(0, 5).map((p) => p.id);
14223
- createReviewSession2(getDb(), chatId, insightIds);
14224
- await sendCurrentProposal(chatId, channel);
14225
- }
14226
- } catch (e) {
14227
- await channel.sendText(chatId, `Analysis failed: ${e}. You can review any pending proposals with /evolve.`, { parseMode: "plain" });
14228
- }
14229
- break;
14230
- }
14231
14834
  default:
14232
14835
  await channel.sendText(chatId, `Unknown command: /${command}. Type /help for available commands.`, { parseMode: "plain" });
14233
14836
  }
@@ -14236,6 +14839,10 @@ async function handleVoice(msg, channel) {
14236
14839
  const { chatId, fileName } = msg;
14237
14840
  await channel.sendText(chatId, "Transcribing voice message...", { parseMode: "plain" });
14238
14841
  try {
14842
+ if (!fileName) {
14843
+ await channel.sendText(chatId, "Could not process voice message \u2014 file not available.", { parseMode: "plain" });
14844
+ return;
14845
+ }
14239
14846
  const audioBuffer = await channel.downloadFile(fileName);
14240
14847
  const transcript = await transcribeAudio(audioBuffer);
14241
14848
  if (!transcript) {
@@ -14326,6 +14933,10 @@ Acknowledge receipt. Do NOT analyze the video unless they ask you to.`;
14326
14933
  await sendResponse(chatId, channel, vidResponse, msg.messageId);
14327
14934
  return;
14328
14935
  }
14936
+ if (!fileName) {
14937
+ await channel.sendText(chatId, "Could not process file \u2014 file not available.", { parseMode: "plain" });
14938
+ return;
14939
+ }
14329
14940
  const fileBuffer = await channel.downloadFile(fileName);
14330
14941
  const originalName = fileName ?? "file";
14331
14942
  const ext = originalName.split(".").pop()?.toLowerCase() ?? "";
@@ -14342,8 +14953,18 @@ Please read the image file and respond to their caption/question.` : `The user s
14342
14953
 
14343
14954
  Please read the image file and describe what you see.`;
14344
14955
  } else if (isTextExt(ext)) {
14345
- const content = fileBuffer.toString("utf-8");
14346
- prompt = caption ? `Here's a file "${originalName}":
14956
+ const MAX_TEXT_FILE_BYTES = 1048576;
14957
+ if (fileBuffer.length > MAX_TEXT_FILE_BYTES) {
14958
+ await channel.sendText(chatId, `\u26A0\uFE0F Text file too large for inline processing (${(fileBuffer.length / 1024 / 1024).toFixed(1)}MB, max 1MB). Saving to disk instead.`, { parseMode: "plain" });
14959
+ tempFilePath = await saveMedia(fileBuffer, "file", ext || "txt");
14960
+ prompt = caption ? `The user sent a large text file "${originalName}" (${fileBuffer.length} bytes), saved at: ${tempFilePath}
14961
+
14962
+ ${caption}` : `The user sent a large text file "${originalName}" (${fileBuffer.length} bytes), saved at: ${tempFilePath}
14963
+
14964
+ Please read and analyze the file.`;
14965
+ } else {
14966
+ const content = fileBuffer.toString("utf-8");
14967
+ prompt = caption ? `Here's a file "${originalName}":
14347
14968
 
14348
14969
  \`\`\`
14349
14970
  ${content}
@@ -14354,6 +14975,7 @@ ${caption}` : `Here's a file "${originalName}". Please analyze its contents:
14354
14975
  \`\`\`
14355
14976
  ${content}
14356
14977
  \`\`\``;
14978
+ }
14357
14979
  } else {
14358
14980
  prompt = `I've shared a file "${originalName}" (${fileBuffer.length} bytes). ${caption || "What can you tell me about it?"}`;
14359
14981
  }
@@ -14440,6 +15062,64 @@ async function handleText(msg, channel) {
14440
15062
  if (hasSqPrefix) {
14441
15063
  text = sqCleanText;
14442
15064
  }
15065
+ {
15066
+ const { getDiscussionMode: getDiscussionMode2, clearDiscussionMode: clearDiscussionMode2, setDiscussionMode: reenterDiscussion, getInsightById: getInsightById2, updateInsightProposal: updateInsightProposal2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
15067
+ const discussInsightId = getDiscussionMode2(chatId);
15068
+ if (discussInsightId !== null) {
15069
+ const insight = getInsightById2(getDb(), discussInsightId);
15070
+ if (!insight) {
15071
+ clearDiscussionMode2(chatId);
15072
+ await channel.sendText(chatId, "Proposal not found \u2014 discussion cancelled.", { parseMode: "plain" });
15073
+ return;
15074
+ }
15075
+ await channel.sendTyping?.(chatId);
15076
+ const discussPrompt = [
15077
+ `The user is discussing an evolve proposal. Here is the proposal context:`,
15078
+ ``,
15079
+ `Insight: "${insight.insight}"`,
15080
+ `Target file: ${insight.targetFile ?? "none"}`,
15081
+ `Category: ${insight.category}`,
15082
+ `Proposed diff: ${insight.proposedDiff ?? "none"}`,
15083
+ `Proposed action: ${insight.proposedAction ?? "none"}`,
15084
+ ``,
15085
+ `The user says: "${text}"`,
15086
+ ``,
15087
+ `Respond to their request. If they ask to retarget, output "RETARGET: <new_path>" on a line by itself.`,
15088
+ `If they ask to revise the diff, output the full revised diff prefixed with "REVISED_DIFF:" on a line by itself, followed by the diff lines.`,
15089
+ `Otherwise, answer their question concisely.`
15090
+ ].join("\n");
15091
+ try {
15092
+ const discussResp = await askAgent(chatId, discussPrompt, { bootstrapTier: "chat", maxTurns: 1, timeoutMs: 6e4 });
15093
+ const respText = discussResp.text?.trim() ?? "";
15094
+ clearDiscussionMode2(chatId);
15095
+ const retargetMatch = respText.match(/^RETARGET:\s*(.+)$/m);
15096
+ const diffMatch = respText.match(/^REVISED_DIFF:\s*\n([\s\S]+)$/m);
15097
+ if (retargetMatch) {
15098
+ const newPath = retargetMatch[1].trim();
15099
+ updateInsightProposal2(getDb(), discussInsightId, newPath, insight.proposedDiff, insight.proposedAction);
15100
+ await channel.sendText(chatId, `\u2705 Retargeted to: ${newPath}`);
15101
+ } else if (diffMatch) {
15102
+ const newDiff = diffMatch[1].trim();
15103
+ updateInsightProposal2(getDb(), discussInsightId, insight.targetFile ?? "", newDiff, insight.proposedAction);
15104
+ await channel.sendText(chatId, `\u2705 Diff revised.`);
15105
+ } else {
15106
+ await channel.sendText(chatId, respText);
15107
+ }
15108
+ const updatedInsight = getInsightById2(getDb(), discussInsightId);
15109
+ if (updatedInsight && updatedInsight.status === "pending") {
15110
+ const { formatProposalCard: formatProposalCard2, buildProposalKeyboard: buildProposalKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
15111
+ const card = formatProposalCard2(updatedInsight);
15112
+ const kb = buildProposalKeyboard2(updatedInsight.id, updatedInsight.category);
15113
+ await channel.sendKeyboard(chatId, card, kb);
15114
+ }
15115
+ } catch (e) {
15116
+ await channel.sendText(chatId, `Discussion error: ${e.message}
15117
+
15118
+ You're still in discussion mode \u2014 try again or click a button to exit.`, { parseMode: "plain" });
15119
+ }
15120
+ return;
15121
+ }
15122
+ }
14443
15123
  if (isChatBusy(chatId) && !bypassBusyCheck.delete(chatId)) {
14444
15124
  if (typeof channel.sendKeyboard === "function") {
14445
15125
  pendingInterrupts.set(chatId, { msg, channel });
@@ -14508,6 +15188,12 @@ async function handleText(msg, channel) {
14508
15188
  const toIcon = toSlot?.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
14509
15189
  channel.sendText(cid, `\u26A0\uFE0F Quota reached on ${fromIcon} ${from} \u2014 switched to ${toIcon} ${to}. Context saved.`, { parseMode: "plain" }).catch(() => {
14510
15190
  });
15191
+ },
15192
+ onModelDowngrade: (cid, fromModel, toModel, reason) => {
15193
+ const shortFrom = formatModelShort(fromModel);
15194
+ const shortTo = formatModelShort(toModel);
15195
+ channel.sendText(cid, `\u26A0\uFE0F ${shortFrom} unresponsive \u2014 downgrading to ${shortTo}. (${reason})`, { parseMode: "plain" }).catch(() => {
15196
+ });
14511
15197
  }
14512
15198
  });
14513
15199
  const elapsedSec = ((Date.now() - sigT0) / 1e3).toFixed(1);
@@ -15903,6 +16589,10 @@ Result: ${task.result.slice(0, 500)}` : ""
15903
16589
  const parts = data.split(":");
15904
16590
  const action = parts[1];
15905
16591
  const idStr = parts[2];
16592
+ if (action !== "discuss") {
16593
+ const { clearDiscussionMode: clearStale } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16594
+ clearStale(chatId);
16595
+ }
15906
16596
  switch (action) {
15907
16597
  case "menu": {
15908
16598
  const { getReflectionStatus: getRefStatus, getUnprocessedSignalCount: getUnprocessedSignalCount2, getPendingInsightCount: getPendingInsightCount2, getLastAnalysisTime: getLastAnalysisTime2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
@@ -15980,6 +16670,65 @@ Result: ${task.result.slice(0, 500)}` : ""
15980
16670
  }
15981
16671
  break;
15982
16672
  }
16673
+ case "preview": {
16674
+ const { getInsightById: pvIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16675
+ const pvInsight = pvIns(getDb(), parseInt(idStr, 10));
16676
+ if (!pvInsight) {
16677
+ await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
16678
+ break;
16679
+ }
16680
+ if (!pvInsight.targetFile || !pvInsight.proposedDiff) {
16681
+ await channel.sendText(chatId, "No diff available for this proposal.", { parseMode: "plain" });
16682
+ break;
16683
+ }
16684
+ const { readFileSync: pvRead, existsSync: pvExists } = await import("fs");
16685
+ const { join: pvJoin } = await import("path");
16686
+ const pvTargetPath = pvJoin(homedir6(), ".cc-claw", pvInsight.targetFile);
16687
+ let previewLines;
16688
+ if (pvExists(pvTargetPath)) {
16689
+ const currentContent = pvRead(pvTargetPath, "utf-8");
16690
+ const diffLines = pvInsight.proposedDiff.split("\n");
16691
+ const additions = diffLines.filter((l) => l.startsWith("+")).map((l) => l.slice(1).trim());
16692
+ const removals = diffLines.filter((l) => l.startsWith("-")).map((l) => l.slice(1).trim());
16693
+ previewLines = [
16694
+ `\u{1F4CB} Dry-run preview: ${pvInsight.targetFile}`,
16695
+ `${"\u2500".repeat(40)}`,
16696
+ ""
16697
+ ];
16698
+ if (removals.length > 0) {
16699
+ previewLines.push("Lines to REMOVE:");
16700
+ for (const r of removals) {
16701
+ previewLines.push(` - ${r}`);
16702
+ const idx = currentContent.indexOf(r);
16703
+ if (idx >= 0) {
16704
+ const lineNum = currentContent.slice(0, idx).split("\n").length;
16705
+ previewLines.push(` (line ~${lineNum})`);
16706
+ }
16707
+ }
16708
+ previewLines.push("");
16709
+ }
16710
+ if (additions.length > 0) {
16711
+ previewLines.push("Lines to ADD:");
16712
+ for (const a of additions) {
16713
+ previewLines.push(` + ${a}`);
16714
+ }
16715
+ previewLines.push("");
16716
+ }
16717
+ previewLines.push(`File size: ${currentContent.length} chars`);
16718
+ } else {
16719
+ previewLines = [
16720
+ `\u{1F4CB} Dry-run preview: ${pvInsight.targetFile}`,
16721
+ `${"\u2500".repeat(40)}`,
16722
+ "",
16723
+ "(File does not exist yet \u2014 will be created)",
16724
+ "",
16725
+ "Content to add:",
16726
+ pvInsight.proposedDiff
16727
+ ];
16728
+ }
16729
+ await channel.sendText(chatId, previewLines.join("\n"), { parseMode: "plain" });
16730
+ break;
16731
+ }
15983
16732
  case "diff": {
15984
16733
  const { getInsightById: getInsightById2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
15985
16734
  const { formatDiffCodeBlock: formatDiffCodeBlock2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
@@ -16008,166 +16757,15 @@ Result: ${task.result.slice(0, 500)}` : ""
16008
16757
  }
16009
16758
  case "discuss": {
16010
16759
  const insId = parseInt(idStr, 10);
16011
- await channel.sendKeyboard(chatId, [
16012
- "What would you like to do with this proposal?",
16760
+ const { setDiscussionMode: setDiscussionMode2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16761
+ setDiscussionMode2(chatId, insId);
16762
+ await channel.sendText(chatId, [
16763
+ "\u{1F4AC} Discussion mode \u2014 tell me what you'd like to change about this proposal.",
16013
16764
  "",
16014
- "Why this file? \u2014 ask the AI to explain its reasoning",
16015
- "Retarget \u2014 move this change to a different file (e.g., a skill instead of SOUL.md)",
16016
- "Revise \u2014 ask the AI to rewrite the proposed change"
16017
- ].join("\n"), [
16018
- [
16019
- { label: "Why this file?", data: `evolve:discuss-why:${insId}`, style: "primary" },
16020
- { label: "Retarget", data: `evolve:discuss-retarget:${insId}` }
16021
- ],
16022
- [
16023
- { label: "Revise the change", data: `evolve:discuss-revise:${insId}` },
16024
- { label: "Back", data: `evolve:discuss-back:${insId}` }
16025
- ]
16026
- ]);
16027
- break;
16028
- }
16029
- case "discuss-why": {
16030
- const { getInsightById: gwIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16031
- const { getDb: gwDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16032
- const gwInsight = gwIns(gwDb(), parseInt(idStr, 10));
16033
- if (!gwInsight) {
16034
- await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
16035
- break;
16036
- }
16037
- await channel.sendTyping?.(chatId);
16038
- const whyPrompt = [
16039
- `Explain in 2-3 sentences why you chose "${gwInsight.targetFile}" as the target file for this insight:`,
16040
- `"${gwInsight.insight}"`,
16041
- `Consider what other files could work (skills, context files, USER.md) and explain your reasoning.`
16042
- ].join("\n");
16043
- try {
16044
- const whyResp = await askAgent(chatId, whyPrompt, { bootstrapTier: "chat", maxTurns: 1, timeoutMs: 6e4 });
16045
- await channel.sendText(chatId, whyResp.text ?? "No response.");
16046
- } catch (e) {
16047
- await channel.sendText(chatId, `Error: ${e.message}`, { parseMode: "plain" });
16048
- }
16049
- await channel.sendKeyboard(chatId, "What next?", [
16050
- [
16051
- { label: "OK, Apply", data: `evolve:apply:${idStr}`, style: "success" },
16052
- { label: "Retarget instead", data: `evolve:discuss-retarget:${idStr}` }
16053
- ],
16054
- [
16055
- { label: "Reject", data: `evolve:reject:${idStr}`, style: "danger" },
16056
- { label: "Back", data: `evolve:discuss-back:${idStr}` }
16057
- ]
16058
- ]);
16059
- break;
16060
- }
16061
- case "discuss-retarget": {
16062
- const { getInsightById: rtIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16063
- const { getDb: rtDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16064
- const rtInsight = rtIns(rtDb(), parseInt(idStr, 10));
16065
- if (!rtInsight) {
16066
- await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
16067
- break;
16068
- }
16069
- const currentTarget = rtInsight.targetFile ?? "unknown";
16070
- const targets = [
16071
- { label: "SOUL.md", path: "identity/SOUL.md" },
16072
- { label: "USER.md", path: "identity/USER.md" }
16073
- ];
16074
- const skillDirs = [
16075
- join18(homedir6(), ".cc-claw", "workspace", "skills")
16076
- ];
16077
- try {
16078
- const { readdirSync: readdirSync9, statSync: statSync10 } = await import("fs");
16079
- for (const dir of skillDirs) {
16080
- if (!existsSync18(dir)) continue;
16081
- for (const entry of readdirSync9(dir)) {
16082
- if (statSync10(join18(dir, entry)).isDirectory()) {
16083
- targets.push({ label: `skills/${entry}`, path: `workspace/skills/${entry}/SKILL.md` });
16084
- }
16085
- }
16086
- }
16087
- } catch {
16088
- }
16089
- const rows = targets.filter((t) => t.path !== currentTarget).map((t) => [{ label: t.label, data: `evolve:discuss-retarget-to:${idStr}:${t.path}` }]);
16090
- rows.push([{ label: "Cancel", data: `evolve:discuss-back:${idStr}` }]);
16091
- await channel.sendKeyboard(chatId, `Current target: ${currentTarget}
16092
-
16093
- Pick a different file for this change. Identity files (SOUL/USER) shape personality; skills teach specific workflows.`, rows);
16094
- break;
16095
- }
16096
- case "discuss-retarget-to": {
16097
- const retargetParts = data.split(":");
16098
- const rtId = parseInt(retargetParts[2], 10);
16099
- const newPath = retargetParts.slice(3).join(":");
16100
- const { getInsightById: rt2Ins, updateInsightProposal: rt2Update } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16101
- const { formatProposalCard: rt2Card, buildProposalKeyboard: rt2Kb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16102
- const { getDb: rt2Db } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16103
- const rt2Insight = rt2Ins(rt2Db(), rtId);
16104
- if (!rt2Insight) {
16105
- await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
16106
- break;
16107
- }
16108
- rt2Update(rt2Db(), rtId, newPath, rt2Insight.proposedDiff, rt2Insight.proposedAction);
16109
- const updatedIns = rt2Ins(rt2Db(), rtId);
16110
- if (updatedIns) {
16111
- const card = rt2Card(updatedIns);
16112
- const kb = rt2Kb(updatedIns.id, updatedIns.category);
16113
- await channel.sendKeyboard(chatId, `Retargeted to ${newPath}. Updated proposal:
16114
-
16115
- ` + card, kb);
16116
- }
16117
- break;
16118
- }
16119
- case "discuss-revise": {
16120
- const { getInsightById: rvIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16121
- const { getDb: rvDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16122
- const rvInsight = rvIns(rvDb(), parseInt(idStr, 10));
16123
- if (!rvInsight) {
16124
- await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
16125
- break;
16126
- }
16127
- await channel.sendTyping?.(chatId);
16128
- const revisePrompt = [
16129
- `Revise this proposed change to be more specific and actionable:`,
16130
- `Current insight: "${rvInsight.insight}"`,
16131
- `Current diff: ${rvInsight.proposedDiff ?? "none"}`,
16132
- `Target: ${rvInsight.targetFile}`,
16133
- ``,
16134
- `Output ONLY the revised diff lines (starting with + for additions, - for removals). No explanation.`
16135
- ].join("\n");
16136
- try {
16137
- const rvResp = await askAgent(chatId, revisePrompt, { bootstrapTier: "chat", maxTurns: 1, timeoutMs: 6e4 });
16138
- const newDiff = rvResp.text?.trim() ?? rvInsight.proposedDiff;
16139
- if (newDiff) {
16140
- const { updateInsightProposal: rvUpdate } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16141
- rvUpdate(rvDb(), parseInt(idStr, 10), rvInsight.targetFile ?? "", newDiff, rvInsight.proposedAction);
16142
- }
16143
- const updatedRv = rvIns(rvDb(), parseInt(idStr, 10));
16144
- if (updatedRv) {
16145
- const { formatProposalCard: rvCard, buildProposalKeyboard: rvKb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16146
- const card = rvCard(updatedRv);
16147
- const kb = rvKb(updatedRv.id, updatedRv.category);
16148
- await channel.sendKeyboard(chatId, "Revised proposal:\n\n" + card, kb);
16149
- }
16150
- } catch (e) {
16151
- await channel.sendText(chatId, `Revision error: ${e.message}`, { parseMode: "plain" });
16152
- }
16153
- break;
16154
- }
16155
- case "discuss-back": {
16156
- const { getInsightById: bkIns, getReviewSession: bkSession } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16157
- const { formatProposalCardWithProgress: formatProposalCardWithProgress2, formatProposalCard: bkCardFn, buildProposalKeyboard: bkKb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16158
- const { getDb: bkDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16159
- const bkInsight = bkIns(bkDb(), parseInt(idStr, 10));
16160
- if (bkInsight && bkInsight.status === "pending") {
16161
- const session2 = bkSession(bkDb(), chatId);
16162
- const kb = bkKb(bkInsight.id, bkInsight.category);
16163
- if (session2) {
16164
- const card = formatProposalCardWithProgress2(bkInsight, session2.currentIndex, session2.insightIds.length);
16165
- await channel.sendKeyboard(chatId, card, kb);
16166
- } else {
16167
- const card = bkCardFn(bkInsight);
16168
- await channel.sendKeyboard(chatId, card, kb);
16169
- }
16170
- }
16765
+ 'Examples: "Move this to a skill instead", "Rewrite the diff", "Why this file?", "Make it shorter"',
16766
+ "",
16767
+ "Type your reply and I'll handle it:"
16768
+ ].join("\n"), { parseMode: "plain" });
16171
16769
  break;
16172
16770
  }
16173
16771
  case "reject": {
@@ -16231,13 +16829,13 @@ Pick a different file for this change. Identity files (SOUL/USER) shape personal
16231
16829
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
16232
16830
  const current = getReflectionStatus2(getDb2(), chatId);
16233
16831
  if (current === "frozen") {
16234
- const { readFileSync: readFileSync21, existsSync: existsSync47 } = await import("fs");
16832
+ const { readFileSync: readFileSync21, existsSync: existsSync46 } = await import("fs");
16235
16833
  const { join: join28 } = await import("path");
16236
16834
  const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
16237
16835
  const soulPath = join28(CC_CLAW_HOME3, "identity/SOUL.md");
16238
16836
  const userPath = join28(CC_CLAW_HOME3, "identity/USER.md");
16239
- const soul = existsSync47(soulPath) ? readFileSync21(soulPath, "utf-8") : "";
16240
- const user = existsSync47(userPath) ? readFileSync21(userPath, "utf-8") : "";
16837
+ const soul = existsSync46(soulPath) ? readFileSync21(soulPath, "utf-8") : "";
16838
+ const user = existsSync46(userPath) ? readFileSync21(userPath, "utf-8") : "";
16241
16839
  setReflectionStatus2(getDb2(), chatId, "active", soul, user);
16242
16840
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
16243
16841
  logActivity2(getDb2(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
@@ -16831,7 +17429,7 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
16831
17429
  const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
16832
17430
  await channel.sendKeyboard(chatId, header2, buttons);
16833
17431
  }
16834
- var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, activeSideQuests, MAX_SIDE_QUESTS, pendingFallbackMessages, dashboardClawWarnings, lastReflectTime, REFLECT_COOLDOWN_MS, pendingSummaryUndo, pendingNewchatUndo, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
17432
+ var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, activeSideQuests, MAX_SIDE_QUESTS, pendingFallbackMessages, dashboardClawWarnings, pendingSummaryUndo, pendingNewchatUndo, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
16835
17433
  var init_router = __esm({
16836
17434
  "src/router.ts"() {
16837
17435
  "use strict";
@@ -16974,8 +17572,6 @@ var init_router = __esm({
16974
17572
  MAX_SIDE_QUESTS = 2;
16975
17573
  pendingFallbackMessages = /* @__PURE__ */ new Map();
16976
17574
  dashboardClawWarnings = /* @__PURE__ */ new Map();
16977
- lastReflectTime = /* @__PURE__ */ new Map();
16978
- REFLECT_COOLDOWN_MS = 12e4;
16979
17575
  pendingSummaryUndo = /* @__PURE__ */ new Map();
16980
17576
  pendingNewchatUndo = /* @__PURE__ */ new Map();
16981
17577
  CLI_INSTALL_HINTS = {
@@ -17101,7 +17697,8 @@ function initScheduler(channelReg) {
17101
17697
  if (orphaned.changes > 0) {
17102
17698
  log(`[scheduler] Cleaned up ${orphaned.changes} orphaned run(s)`);
17103
17699
  }
17104
- } catch {
17700
+ } catch (err) {
17701
+ log(`[scheduler] Orphaned run cleanup skipped: ${err}`);
17105
17702
  }
17106
17703
  try {
17107
17704
  const jobs = getActiveJobs();
@@ -17254,7 +17851,6 @@ async function executeJob(job) {
17254
17851
  const contentStatus = isEmpty ? "no_content" : "success";
17255
17852
  const durationMs = Date.now() - t0;
17256
17853
  updateJobLastRun(job.id, (/* @__PURE__ */ new Date()).toISOString());
17257
- resetJobFailures(job.id);
17258
17854
  if (response.usage) {
17259
17855
  addUsage(job.chatId, response.usage.input, response.usage.output, response.usage.cacheRead, resolvedModel, void 0, response.usage.contextSize);
17260
17856
  }
@@ -17266,6 +17862,16 @@ async function executeJob(job) {
17266
17862
  cacheRead: response.usage?.cacheRead,
17267
17863
  durationMs
17268
17864
  });
17865
+ if (finalStatus === "delivery_failed") {
17866
+ const failures = incrementJobFailures(job.id);
17867
+ error(`[scheduler] Job #${job.id} delivery failed (failures=${failures})`);
17868
+ if (failures >= AUTO_PAUSE_THRESHOLD) {
17869
+ pauseJob(job.id);
17870
+ await notifyJobAutoPaused({ ...job, consecutiveFailures: failures });
17871
+ }
17872
+ } else if (finalStatus === "success") {
17873
+ resetJobFailures(job.id);
17874
+ }
17269
17875
  } catch (err) {
17270
17876
  const durationMs = Date.now() - t0;
17271
17877
  const errorClass = classifyError(err);
@@ -17558,18 +18164,18 @@ var init_wrap_backend = __esm({
17558
18164
  });
17559
18165
 
17560
18166
  // src/agents/runners/config-loader.ts
17561
- import { readFileSync as readFileSync10, readdirSync as readdirSync8, existsSync as existsSync19, mkdirSync as mkdirSync8, watchFile, unwatchFile } from "fs";
18167
+ import { readFileSync as readFileSync10, readdirSync as readdirSync8, existsSync as existsSync18, mkdirSync as mkdirSync8, watchFile, unwatchFile } from "fs";
17562
18168
  import { join as join20 } from "path";
17563
- import { execFileSync } from "child_process";
18169
+ import { execFileSync as execFileSync2 } from "child_process";
17564
18170
  function resolveExecutable(config2) {
17565
- if (existsSync19(config2.executable)) return config2.executable;
18171
+ if (existsSync18(config2.executable)) return config2.executable;
17566
18172
  try {
17567
- return execFileSync("which", [config2.executable], { encoding: "utf-8" }).trim();
18173
+ return execFileSync2("which", [config2.executable], { encoding: "utf-8" }).trim();
17568
18174
  } catch {
17569
18175
  }
17570
18176
  for (const fallback of config2.executableFallbacks ?? []) {
17571
18177
  const resolved = fallback.replace(/^~/, process.env.HOME ?? "");
17572
- if (existsSync19(resolved)) return resolved;
18178
+ if (existsSync18(resolved)) return resolved;
17573
18179
  }
17574
18180
  return config2.executable;
17575
18181
  }
@@ -17708,7 +18314,7 @@ function loadRunnerConfig(filePath) {
17708
18314
  }
17709
18315
  }
17710
18316
  function loadAllRunnerConfigs() {
17711
- if (!existsSync19(RUNNERS_PATH)) {
18317
+ if (!existsSync18(RUNNERS_PATH)) {
17712
18318
  mkdirSync8(RUNNERS_PATH, { recursive: true });
17713
18319
  return [];
17714
18320
  }
@@ -17736,9 +18342,9 @@ function registerConfigRunners() {
17736
18342
  return count;
17737
18343
  }
17738
18344
  function watchRunnerConfigs(onChange) {
17739
- if (!existsSync19(RUNNERS_PATH)) return;
18345
+ if (!existsSync18(RUNNERS_PATH)) return;
17740
18346
  for (const prev of watchedFiles) {
17741
- if (!existsSync19(prev)) {
18347
+ if (!existsSync18(prev)) {
17742
18348
  unwatchFile(prev);
17743
18349
  watchedFiles.delete(prev);
17744
18350
  }
@@ -17795,11 +18401,12 @@ var init_runners = __esm({
17795
18401
  function checkIdleAgents(agents2, now, thresholdMs = IDLE_THRESHOLD_MS) {
17796
18402
  return agents2.filter((agent) => {
17797
18403
  if (agent.status !== "running" || !agent.lastOutputAt) return false;
17798
- const lastOutput = new Date(agent.lastOutputAt).getTime();
18404
+ const ts2 = agent.lastOutputAt;
18405
+ const lastOutput = (/* @__PURE__ */ new Date(ts2 + (ts2.includes("Z") ? "" : "Z"))).getTime();
17799
18406
  return now - lastOutput > thresholdMs;
17800
18407
  });
17801
18408
  }
17802
- function startMonitor(callbacks, intervalMs = 15e3) {
18409
+ function startMonitor(callbacks, intervalMs = MONITOR_INTERVAL_MS) {
17803
18410
  if (monitorInterval) return;
17804
18411
  monitorInterval = setInterval(() => {
17805
18412
  try {
@@ -17821,7 +18428,7 @@ function stopMonitor() {
17821
18428
  monitorInterval = null;
17822
18429
  }
17823
18430
  }
17824
- var monitorInterval;
18431
+ var monitorInterval, MONITOR_INTERVAL_MS;
17825
18432
  var init_monitor = __esm({
17826
18433
  "src/agents/monitor.ts"() {
17827
18434
  "use strict";
@@ -17830,6 +18437,7 @@ var init_monitor = __esm({
17830
18437
  init_types();
17831
18438
  init_log();
17832
18439
  monitorInterval = null;
18440
+ MONITOR_INTERVAL_MS = parseInt(process.env.CC_CLAW_MONITOR_INTERVAL_MS ?? "30000", 10);
17833
18441
  }
17834
18442
  });
17835
18443
 
@@ -17952,6 +18560,18 @@ var init_telegram = __esm({
17952
18560
 
17953
18561
  // src/channels/telegram.ts
17954
18562
  import { Bot, InlineKeyboard, InputFile } from "grammy";
18563
+ function isFastPathMessage(msg) {
18564
+ if (msg.type === "command" && msg.command && FAST_PATH_COMMANDS.has(msg.command)) {
18565
+ return true;
18566
+ }
18567
+ if (msg.type === "text" && msg.text) {
18568
+ const trimmed = msg.text.trim();
18569
+ if (trimmed.startsWith("!!") || trimmed.startsWith("!") && trimmed.length > 1) {
18570
+ return true;
18571
+ }
18572
+ }
18573
+ return false;
18574
+ }
17955
18575
  function numericChatId(chatId) {
17956
18576
  if (chatId.startsWith("sq:") || chatId.startsWith("cron:")) {
17957
18577
  throw new Error(`Synthetic chatId "${chatId}" passed to Telegram API`);
@@ -17959,13 +18579,14 @@ function numericChatId(chatId) {
17959
18579
  const raw = chatId.includes(":") ? chatId.split(":").pop() : chatId;
17960
18580
  return parseInt(raw);
17961
18581
  }
17962
- var TelegramChannel;
18582
+ var FAST_PATH_COMMANDS, TelegramChannel;
17963
18583
  var init_telegram2 = __esm({
17964
18584
  "src/channels/telegram.ts"() {
17965
18585
  "use strict";
17966
18586
  init_telegram();
17967
18587
  init_log();
17968
18588
  init_store5();
18589
+ FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
17969
18590
  TelegramChannel = class {
17970
18591
  name = "telegram";
17971
18592
  bot;
@@ -18077,41 +18698,48 @@ var init_telegram2 = __esm({
18077
18698
  { command: "evolve", description: "Self-learning & evolution controls" },
18078
18699
  { command: "reflect", description: "Trigger reflection analysis" }
18079
18700
  ]);
18080
- this.bot.on("message", (ctx) => {
18701
+ this.bot.on("message", async (ctx) => {
18081
18702
  const chatId = ctx.chat.id.toString();
18082
18703
  const senderId = ctx.from?.id?.toString() ?? "";
18083
18704
  const authorized = this.isAuthorized(chatId) || this.isAuthorized(senderId);
18084
18705
  log(`[telegram] Message from chat ${chatId} sender ${senderId} (authorized: ${authorized})`);
18085
18706
  if (!authorized) return;
18086
18707
  const msg = this.buildIncomingMessage(ctx);
18087
- if (msg) {
18088
- log(`[telegram] Processing ${msg.type}: "${msg.text?.slice(0, 50) || msg.command || "(media)"}"`);
18089
- handler(msg, this).catch((err) => {
18090
- error("[telegram] Handler error:", err);
18091
- });
18092
- } else {
18708
+ if (!msg) {
18093
18709
  this.sendText(chatId, "I can handle text, voice, photos, documents, and videos. This message type isn't supported yet.", { parseMode: "plain" }).catch(() => {
18094
18710
  });
18711
+ return;
18712
+ }
18713
+ log(`[telegram] Processing ${msg.type}: "${msg.text?.slice(0, 50) || msg.command || "(media)"}"`);
18714
+ if (isFastPathMessage(msg)) {
18715
+ handler(msg, this).catch((err) => {
18716
+ error("[telegram] Fast-path handler error:", err);
18717
+ });
18718
+ return;
18095
18719
  }
18720
+ handler(msg, this).catch((err) => {
18721
+ error("[telegram] Handler error:", err);
18722
+ });
18096
18723
  });
18097
- this.bot.on("callback_query:data", async (ctx) => {
18724
+ this.bot.on("callback_query:data", (ctx) => {
18098
18725
  const userId = ctx.from.id.toString();
18099
18726
  const chatId = ctx.callbackQuery.message?.chat?.id?.toString() ?? userId;
18100
18727
  log(`[telegram] Callback from user ${userId} in chat ${chatId}: ${ctx.callbackQuery.data}`);
18101
18728
  if (!this.isAuthorized(userId) && !this.isAuthorized(chatId)) {
18102
- await ctx.answerCallbackQuery("Unauthorized");
18729
+ ctx.answerCallbackQuery("Unauthorized").catch(() => {
18730
+ });
18103
18731
  return;
18104
18732
  }
18105
18733
  const data = ctx.callbackQuery.data;
18106
- await ctx.answerCallbackQuery().catch(() => {
18734
+ ctx.answerCallbackQuery().catch(() => {
18107
18735
  });
18108
- try {
18736
+ (async () => {
18109
18737
  for (const handler2 of this.callbackHandlers) {
18110
18738
  await handler2(chatId, data, this);
18111
18739
  }
18112
- } catch (err) {
18740
+ })().catch((err) => {
18113
18741
  error("[telegram] Callback handler error:", err);
18114
- }
18742
+ });
18115
18743
  });
18116
18744
  this.bot.on("message_reaction", async (ctx) => {
18117
18745
  const chatId = String(ctx.chat.id);
@@ -18444,19 +19072,19 @@ var init_telegram2 = __esm({
18444
19072
  });
18445
19073
 
18446
19074
  // src/skills/bootstrap.ts
18447
- import { existsSync as existsSync20 } from "fs";
19075
+ import { existsSync as existsSync19 } from "fs";
18448
19076
  import { readdir as readdir4, readFile as readFile6, writeFile as writeFile4, copyFile } from "fs/promises";
18449
19077
  import { join as join21, dirname as dirname3 } from "path";
18450
19078
  import { fileURLToPath as fileURLToPath2 } from "url";
18451
19079
  async function copyAgentManifestSkills() {
18452
- if (!existsSync20(PKG_SKILLS)) return;
19080
+ if (!existsSync19(PKG_SKILLS)) return;
18453
19081
  try {
18454
19082
  const entries = await readdir4(PKG_SKILLS, { withFileTypes: true });
18455
19083
  for (const entry of entries) {
18456
19084
  if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
18457
19085
  const src = join21(PKG_SKILLS, entry.name);
18458
19086
  const dest = join21(SKILLS_PATH, entry.name);
18459
- if (existsSync20(dest)) continue;
19087
+ if (existsSync19(dest)) continue;
18460
19088
  await copyFile(src, dest);
18461
19089
  log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
18462
19090
  }
@@ -18467,7 +19095,7 @@ async function copyAgentManifestSkills() {
18467
19095
  async function bootstrapSkills() {
18468
19096
  await copyAgentManifestSkills();
18469
19097
  const usmDir = join21(SKILLS_PATH, USM_DIR_NAME);
18470
- if (existsSync20(usmDir)) return;
19098
+ if (existsSync19(usmDir)) return;
18471
19099
  try {
18472
19100
  const entries = await readdir4(SKILLS_PATH);
18473
19101
  const dirs = entries.filter((e) => !e.startsWith("."));
@@ -18490,7 +19118,7 @@ async function bootstrapSkills() {
18490
19118
  }
18491
19119
  async function patchUsmForCcClaw(usmDir) {
18492
19120
  const skillPath = join21(usmDir, "SKILL.md");
18493
- if (!existsSync20(skillPath)) return;
19121
+ if (!existsSync19(skillPath)) return;
18494
19122
  try {
18495
19123
  let content = await readFile6(skillPath, "utf-8");
18496
19124
  let patched = false;
@@ -18750,13 +19378,13 @@ __export(ai_skill_exports, {
18750
19378
  generateAiSkill: () => generateAiSkill,
18751
19379
  installAiSkill: () => installAiSkill
18752
19380
  });
18753
- import { existsSync as existsSync21, writeFileSync as writeFileSync7, mkdirSync as mkdirSync9 } from "fs";
19381
+ import { existsSync as existsSync20, writeFileSync as writeFileSync7, mkdirSync as mkdirSync9 } from "fs";
18754
19382
  import { join as join22 } from "path";
18755
19383
  import { homedir as homedir7 } from "os";
18756
19384
  function generateAiSkill() {
18757
19385
  const version = VERSION;
18758
19386
  let systemState = "";
18759
- if (existsSync21(DB_PATH)) {
19387
+ if (existsSync20(DB_PATH)) {
18760
19388
  try {
18761
19389
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store5(), __toCommonJS(store_exports5));
18762
19390
  const readDb = openDatabaseReadOnly2();
@@ -18805,6 +19433,14 @@ Use the CC-Claw CLI when you need to:
18805
19433
  - Every command supports \`--json\` for machine-parseable output
18806
19434
  - Use \`--chat <id>\` to target a specific chat context
18807
19435
 
19436
+ ## CRITICAL: CC-Claw Is Your API Layer
19437
+
19438
+ - **NEVER** use native CLI memory/storage tools (Gemini's \`save_memory\`, Claude's memory, etc.) \u2014 they save to backend-local storage, invisible to other backends
19439
+ - **NEVER** modify the SQLite database directly (no INSERT/UPDATE/DELETE SQL)
19440
+ - **ALWAYS** use CC-Claw slash commands (\`/remember\`, \`/evolve\`, etc.) or the \`cc-claw\` CLI for state changes
19441
+ - Memory MUST be cross-backend persistent \u2014 only CC-Claw's own commands guarantee this
19442
+ - If you don't know the right command, ask the user or run \`cc-claw --ai\`
19443
+
18808
19444
  ## Telegram Quick Reference
18809
19445
 
18810
19446
  **Navigation & help:**
@@ -18861,8 +19497,7 @@ Use the CC-Claw CLI when you need to:
18861
19497
  - \`/imagine <prompt>\` (\`/image\`) \u2014 Generate image (requires GEMINI_API_KEY)
18862
19498
  - \`/skills\` \u2014 List skills
18863
19499
  - \`/skill-install <url>\` \u2014 Install skill from GitHub
18864
- - \`/evolve\` \u2014 Self-learning interactive keyboard
18865
- - \`/reflect\` \u2014 Trigger reflection analysis
19500
+ - \`/evolve\` \u2014 Self-learning interactive keyboard (includes quick analyze)
18866
19501
  - \`/gemini_accounts\` \u2014 Gemini credential rotation management
18867
19502
  - \`/setup-profile\` \u2014 User profile setup wizard
18868
19503
 
@@ -18905,8 +19540,9 @@ cc-claw memory list --json # All memories with salience
18905
19540
  cc-claw memory search "topic" --json # Search memories
18906
19541
  cc-claw memory history --json # Session summaries
18907
19542
  cc-claw memory history --limit 20 --json # Limit results
18908
- cc-claw memory add "key" "value" # Save a memory (needs daemon)
18909
- cc-claw memory forget "keyword" # Delete memories (needs daemon)
19543
+ # NOTE: To add/delete memories, use Telegram commands:
19544
+ # /remember <text> \u2014 Save a memory
19545
+ # /forget <keyword> \u2014 Delete memories
18910
19546
  \`\`\`
18911
19547
 
18912
19548
  ### Session
@@ -19186,7 +19822,7 @@ var index_exports = {};
19186
19822
  __export(index_exports, {
19187
19823
  main: () => main
19188
19824
  });
19189
- import { mkdirSync as mkdirSync10, existsSync as existsSync22, renameSync, statSync as statSync5, readFileSync as readFileSync12 } from "fs";
19825
+ import { mkdirSync as mkdirSync10, existsSync as existsSync21, renameSync, statSync as statSync5, readFileSync as readFileSync12 } from "fs";
19190
19826
  import { join as join23 } from "path";
19191
19827
  import dotenv from "dotenv";
19192
19828
  function migrateLayout() {
@@ -19200,7 +19836,7 @@ function migrateLayout() {
19200
19836
  [join23(CC_CLAW_HOME, "cc-claw.error.log.1"), join23(LOGS_PATH, "cc-claw.error.log.1")]
19201
19837
  ];
19202
19838
  for (const [from, to] of moves) {
19203
- if (existsSync22(from) && !existsSync22(to)) {
19839
+ if (existsSync21(from) && !existsSync21(to)) {
19204
19840
  try {
19205
19841
  renameSync(from, to);
19206
19842
  } catch {
@@ -19236,6 +19872,11 @@ async function main() {
19236
19872
  initDatabase();
19237
19873
  pruneMessageLog(30, 2e3);
19238
19874
  bootstrapBuiltinMcps(getDb());
19875
+ try {
19876
+ const { resetIntentStats: resetIntentStats2 } = await Promise.resolve().then(() => (init_classify(), classify_exports));
19877
+ resetIntentStats2();
19878
+ } catch {
19879
+ }
19239
19880
  try {
19240
19881
  const { getAdapter: getAdapter2, probeBackendAvailability: probeBackendAvailability2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
19241
19882
  probeBackendAvailability2();
@@ -19409,10 +20050,10 @@ var init_index = __esm({
19409
20050
  init_bootstrap2();
19410
20051
  init_health3();
19411
20052
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
19412
- if (!existsSync22(dir)) mkdirSync10(dir, { recursive: true });
20053
+ if (!existsSync21(dir)) mkdirSync10(dir, { recursive: true });
19413
20054
  }
19414
20055
  migrateLayout();
19415
- if (existsSync22(ENV_PATH)) {
20056
+ if (existsSync21(ENV_PATH)) {
19416
20057
  dotenv.config({ path: ENV_PATH });
19417
20058
  } else {
19418
20059
  console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
@@ -19433,12 +20074,12 @@ __export(api_client_exports, {
19433
20074
  apiPost: () => apiPost,
19434
20075
  isDaemonRunning: () => isDaemonRunning
19435
20076
  });
19436
- import { readFileSync as readFileSync13, existsSync as existsSync23 } from "fs";
20077
+ import { readFileSync as readFileSync13, existsSync as existsSync22 } from "fs";
19437
20078
  import { request as httpRequest } from "http";
19438
20079
  function getToken() {
19439
20080
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
19440
20081
  try {
19441
- if (existsSync23(TOKEN_PATH)) return readFileSync13(TOKEN_PATH, "utf-8").trim();
20082
+ if (existsSync22(TOKEN_PATH)) return readFileSync13(TOKEN_PATH, "utf-8").trim();
19442
20083
  } catch {
19443
20084
  }
19444
20085
  return null;
@@ -19533,15 +20174,20 @@ __export(service_exports, {
19533
20174
  serviceStatus: () => serviceStatus,
19534
20175
  uninstallService: () => uninstallService
19535
20176
  });
19536
- import { existsSync as existsSync24, mkdirSync as mkdirSync11, writeFileSync as writeFileSync8, unlinkSync as unlinkSync6 } from "fs";
19537
- import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
20177
+ import { existsSync as existsSync23, mkdirSync as mkdirSync11, writeFileSync as writeFileSync8, unlinkSync as unlinkSync6 } from "fs";
20178
+ import { execFileSync as execFileSync3, execSync as execSync7 } from "child_process";
19538
20179
  import { homedir as homedir8, platform } from "os";
19539
20180
  import { join as join24, dirname as dirname4 } from "path";
20181
+ function xmlEscape(s) {
20182
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
20183
+ }
19540
20184
  function resolveExecutable2(name) {
19541
20185
  try {
19542
- return execFileSync2("which", [name], { encoding: "utf-8" }).trim();
20186
+ return execFileSync3("which", [name], { encoding: "utf-8" }).trim();
19543
20187
  } catch {
19544
- return process.argv[1];
20188
+ const fallback = process.argv[1];
20189
+ if (fallback && existsSync23(fallback)) return fallback;
20190
+ throw new Error(`Cannot find '${name}' executable. Install globally: npm install -g cc-claw`);
19545
20191
  }
19546
20192
  }
19547
20193
  function getPathDirs() {
@@ -19565,21 +20211,28 @@ function generatePlist() {
19565
20211
  const ccClawBin = resolveExecutable2("cc-claw");
19566
20212
  const pathDirs = getPathDirs();
19567
20213
  const home = homedir8();
20214
+ const safeBin = xmlEscape(ccClawBin);
20215
+ const safePaths = xmlEscape(pathDirs);
20216
+ const safeHome = xmlEscape(home);
20217
+ const safeLabel = xmlEscape(PLIST_LABEL);
20218
+ const safeCCHome = xmlEscape(CC_CLAW_HOME);
20219
+ const safeLog = xmlEscape(LOG_PATH);
20220
+ const safeErrLog = xmlEscape(ERROR_LOG_PATH);
19568
20221
  return `<?xml version="1.0" encoding="UTF-8"?>
19569
20222
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
19570
20223
  <plist version="1.0">
19571
20224
  <dict>
19572
20225
  <key>Label</key>
19573
- <string>${PLIST_LABEL}</string>
20226
+ <string>${safeLabel}</string>
19574
20227
 
19575
20228
  <key>ProgramArguments</key>
19576
20229
  <array>
19577
- <string>${ccClawBin}</string>
20230
+ <string>${safeBin}</string>
19578
20231
  <string>start</string>
19579
20232
  </array>
19580
20233
 
19581
20234
  <key>WorkingDirectory</key>
19582
- <string>${CC_CLAW_HOME}</string>
20235
+ <string>${safeCCHome}</string>
19583
20236
 
19584
20237
  <key>RunAtLoad</key>
19585
20238
  <true/>
@@ -19588,17 +20241,17 @@ function generatePlist() {
19588
20241
  <true/>
19589
20242
 
19590
20243
  <key>StandardOutPath</key>
19591
- <string>${LOG_PATH}</string>
20244
+ <string>${safeLog}</string>
19592
20245
 
19593
20246
  <key>StandardErrorPath</key>
19594
- <string>${ERROR_LOG_PATH}</string>
20247
+ <string>${safeErrLog}</string>
19595
20248
 
19596
20249
  <key>EnvironmentVariables</key>
19597
20250
  <dict>
19598
20251
  <key>PATH</key>
19599
- <string>${pathDirs}</string>
20252
+ <string>${safePaths}</string>
19600
20253
  <key>HOME</key>
19601
- <string>${home}</string>
20254
+ <string>${safeHome}</string>
19602
20255
  </dict>
19603
20256
 
19604
20257
  <key>ThrottleInterval</key>
@@ -19608,26 +20261,26 @@ function generatePlist() {
19608
20261
  }
19609
20262
  function installMacOS() {
19610
20263
  const agentsDir = dirname4(PLIST_PATH);
19611
- if (!existsSync24(agentsDir)) mkdirSync11(agentsDir, { recursive: true });
19612
- if (!existsSync24(LOGS_PATH)) mkdirSync11(LOGS_PATH, { recursive: true });
19613
- if (existsSync24(PLIST_PATH)) {
20264
+ if (!existsSync23(agentsDir)) mkdirSync11(agentsDir, { recursive: true });
20265
+ if (!existsSync23(LOGS_PATH)) mkdirSync11(LOGS_PATH, { recursive: true });
20266
+ if (existsSync23(PLIST_PATH)) {
19614
20267
  try {
19615
- execFileSync2("launchctl", ["unload", PLIST_PATH]);
20268
+ execFileSync3("launchctl", ["unload", PLIST_PATH]);
19616
20269
  } catch {
19617
20270
  }
19618
20271
  }
19619
20272
  writeFileSync8(PLIST_PATH, generatePlist());
19620
20273
  console.log(` Installed: ${PLIST_PATH}`);
19621
- execFileSync2("launchctl", ["load", PLIST_PATH]);
20274
+ execFileSync3("launchctl", ["load", PLIST_PATH]);
19622
20275
  console.log(" Service loaded and starting.");
19623
20276
  }
19624
20277
  function uninstallMacOS() {
19625
- if (!existsSync24(PLIST_PATH)) {
20278
+ if (!existsSync23(PLIST_PATH)) {
19626
20279
  console.log(" No service found to uninstall.");
19627
20280
  return;
19628
20281
  }
19629
20282
  try {
19630
- execFileSync2("launchctl", ["unload", PLIST_PATH]);
20283
+ execFileSync3("launchctl", ["unload", PLIST_PATH]);
19631
20284
  } catch {
19632
20285
  }
19633
20286
  unlinkSync6(PLIST_PATH);
@@ -19643,24 +20296,25 @@ function formatUptime(seconds) {
19643
20296
  async function getUptimeFromDaemon() {
19644
20297
  try {
19645
20298
  const { isDaemonRunning: isDaemonRunning2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
19646
- if (!await isDaemonRunning2()) return null;
20299
+ if (!await isDaemonRunning2()) return { status: "down" };
19647
20300
  const res = await apiGet2("/api/health");
19648
20301
  const sec = res.data?.uptime;
19649
- return sec ? formatUptime(sec) : null;
20302
+ return sec ? { status: "ok", uptime: formatUptime(sec) } : { status: "down" };
19650
20303
  } catch {
19651
- return null;
20304
+ return { status: "error" };
19652
20305
  }
19653
20306
  }
19654
20307
  function statusMacOS() {
19655
20308
  try {
19656
- const out = execSync7("launchctl list | grep cc-claw", { shell: "/bin/sh", encoding: "utf-8" }).trim();
19657
- if (out) {
19658
- const parts = out.split(/\s+/);
20309
+ const out = execFileSync3("launchctl", ["list"], { encoding: "utf-8" });
20310
+ const line = out.split("\n").find((l) => l.includes("cc-claw"));
20311
+ if (line) {
20312
+ const parts = line.trim().split(/\s+/);
19659
20313
  const pid = parts[0];
19660
20314
  const exitCode = parts[1];
19661
20315
  if (pid !== "-") {
19662
- getUptimeFromDaemon().then((uptime) => {
19663
- const uptimeStr = uptime ? `, uptime ${uptime}` : "";
20316
+ getUptimeFromDaemon().then((result) => {
20317
+ const uptimeStr = result.status === "ok" ? `, uptime ${result.uptime}` : "";
19664
20318
  console.log(` Running (PID ${pid}${uptimeStr})`);
19665
20319
  }).catch(() => {
19666
20320
  console.log(` Running (PID ${pid})`);
@@ -19696,30 +20350,30 @@ WantedBy=default.target
19696
20350
  `;
19697
20351
  }
19698
20352
  function installLinux() {
19699
- if (!existsSync24(SYSTEMD_DIR)) mkdirSync11(SYSTEMD_DIR, { recursive: true });
19700
- if (!existsSync24(LOGS_PATH)) mkdirSync11(LOGS_PATH, { recursive: true });
20353
+ if (!existsSync23(SYSTEMD_DIR)) mkdirSync11(SYSTEMD_DIR, { recursive: true });
20354
+ if (!existsSync23(LOGS_PATH)) mkdirSync11(LOGS_PATH, { recursive: true });
19701
20355
  writeFileSync8(UNIT_PATH, generateUnit());
19702
20356
  console.log(` Installed: ${UNIT_PATH}`);
19703
- execFileSync2("systemctl", ["--user", "daemon-reload"]);
19704
- execFileSync2("systemctl", ["--user", "enable", "cc-claw"]);
19705
- execFileSync2("systemctl", ["--user", "start", "cc-claw"]);
20357
+ execFileSync3("systemctl", ["--user", "daemon-reload"]);
20358
+ execFileSync3("systemctl", ["--user", "enable", "cc-claw"]);
20359
+ execFileSync3("systemctl", ["--user", "start", "cc-claw"]);
19706
20360
  console.log(" Service enabled and started.");
19707
20361
  }
19708
20362
  function uninstallLinux() {
19709
- if (!existsSync24(UNIT_PATH)) {
20363
+ if (!existsSync23(UNIT_PATH)) {
19710
20364
  console.log(" No service found to uninstall.");
19711
20365
  return;
19712
20366
  }
19713
20367
  try {
19714
- execFileSync2("systemctl", ["--user", "stop", "cc-claw"]);
20368
+ execFileSync3("systemctl", ["--user", "stop", "cc-claw"]);
19715
20369
  } catch {
19716
20370
  }
19717
20371
  try {
19718
- execFileSync2("systemctl", ["--user", "disable", "cc-claw"]);
20372
+ execFileSync3("systemctl", ["--user", "disable", "cc-claw"]);
19719
20373
  } catch {
19720
20374
  }
19721
20375
  unlinkSync6(UNIT_PATH);
19722
- execFileSync2("systemctl", ["--user", "daemon-reload"]);
20376
+ execFileSync3("systemctl", ["--user", "daemon-reload"]);
19723
20377
  console.log(" Service uninstalled.");
19724
20378
  }
19725
20379
  function statusLinux() {
@@ -19731,28 +20385,28 @@ function statusLinux() {
19731
20385
  }
19732
20386
  }
19733
20387
  function installService() {
19734
- if (!existsSync24(join24(CC_CLAW_HOME, ".env"))) {
20388
+ if (!existsSync23(join24(CC_CLAW_HOME, ".env"))) {
19735
20389
  console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
19736
20390
  console.error(" Run 'cc-claw setup' before installing the service.");
19737
20391
  process.exitCode = 1;
19738
20392
  return;
19739
20393
  }
19740
- const os = platform();
19741
- if (os === "darwin") installMacOS();
19742
- else if (os === "linux") installLinux();
19743
- else console.error(` Unsupported platform: ${os}. Only macOS and Linux are supported.`);
20394
+ const os2 = platform();
20395
+ if (os2 === "darwin") installMacOS();
20396
+ else if (os2 === "linux") installLinux();
20397
+ else console.error(` Unsupported platform: ${os2}. Only macOS and Linux are supported.`);
19744
20398
  }
19745
20399
  function uninstallService() {
19746
- const os = platform();
19747
- if (os === "darwin") uninstallMacOS();
19748
- else if (os === "linux") uninstallLinux();
19749
- else console.error(` Unsupported platform: ${os}.`);
20400
+ const os2 = platform();
20401
+ if (os2 === "darwin") uninstallMacOS();
20402
+ else if (os2 === "linux") uninstallLinux();
20403
+ else console.error(` Unsupported platform: ${os2}.`);
19750
20404
  }
19751
20405
  function serviceStatus() {
19752
- const os = platform();
19753
- if (os === "darwin") statusMacOS();
19754
- else if (os === "linux") statusLinux();
19755
- else console.error(` Unsupported platform: ${os}.`);
20406
+ const os2 = platform();
20407
+ if (os2 === "darwin") statusMacOS();
20408
+ else if (os2 === "linux") statusLinux();
20409
+ else console.error(` Unsupported platform: ${os2}.`);
19756
20410
  }
19757
20411
  var PLIST_LABEL, PLIST_PATH, SYSTEMD_DIR, UNIT_PATH;
19758
20412
  var init_service = __esm({
@@ -19875,20 +20529,20 @@ __export(daemon_exports, {
19875
20529
  import { execSync as execSync8 } from "child_process";
19876
20530
  import { platform as platform2 } from "os";
19877
20531
  async function stopService() {
19878
- const os = platform2();
20532
+ const os2 = platform2();
19879
20533
  try {
19880
- if (os === "darwin") {
20534
+ if (os2 === "darwin") {
19881
20535
  execSync8("launchctl stop com.cc-claw");
19882
20536
  console.log(`
19883
20537
  ${success("Daemon stopped.")}
19884
20538
  `);
19885
- } else if (os === "linux") {
20539
+ } else if (os2 === "linux") {
19886
20540
  execSync8("systemctl --user stop cc-claw");
19887
20541
  console.log(`
19888
20542
  ${success("Daemon stopped.")}
19889
20543
  `);
19890
20544
  } else {
19891
- outputError("UNSUPPORTED_PLATFORM", `Platform ${os} not supported for service management.`);
20545
+ outputError("UNSUPPORTED_PLATFORM", `Platform ${os2} not supported for service management.`);
19892
20546
  process.exit(1);
19893
20547
  }
19894
20548
  } catch (err) {
@@ -19898,21 +20552,21 @@ async function stopService() {
19898
20552
  }
19899
20553
  }
19900
20554
  async function restartService() {
19901
- const os = platform2();
20555
+ const os2 = platform2();
19902
20556
  try {
19903
- if (os === "darwin") {
20557
+ if (os2 === "darwin") {
19904
20558
  const uid = process.getuid?.() ?? execSync8("id -u", { encoding: "utf-8" }).trim();
19905
20559
  execSync8(`launchctl kickstart -k gui/${uid}/com.cc-claw`);
19906
20560
  console.log(`
19907
20561
  ${success("Daemon restarted.")}
19908
20562
  `);
19909
- } else if (os === "linux") {
20563
+ } else if (os2 === "linux") {
19910
20564
  execSync8("systemctl --user restart cc-claw");
19911
20565
  console.log(`
19912
20566
  ${success("Daemon restarted.")}
19913
20567
  `);
19914
20568
  } else {
19915
- outputError("UNSUPPORTED_PLATFORM", `Platform ${os} not supported for service management.`);
20569
+ outputError("UNSUPPORTED_PLATFORM", `Platform ${os2} not supported for service management.`);
19916
20570
  process.exit(1);
19917
20571
  }
19918
20572
  } catch (err) {
@@ -19959,7 +20613,7 @@ var status_exports = {};
19959
20613
  __export(status_exports, {
19960
20614
  statusCommand: () => statusCommand
19961
20615
  });
19962
- import { existsSync as existsSync25, statSync as statSync6 } from "fs";
20616
+ import { existsSync as existsSync24, statSync as statSync6 } from "fs";
19963
20617
  async function statusCommand(globalOpts, localOpts) {
19964
20618
  try {
19965
20619
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
@@ -19999,7 +20653,7 @@ async function statusCommand(globalOpts, localOpts) {
19999
20653
  const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
20000
20654
  const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
20001
20655
  const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
20002
- const dbStat = existsSync25(DB_PATH) ? statSync6(DB_PATH) : null;
20656
+ const dbStat = existsSync24(DB_PATH) ? statSync6(DB_PATH) : null;
20003
20657
  let daemonRunning = false;
20004
20658
  let daemonInfo = {};
20005
20659
  if (localOpts.deep) {
@@ -20090,11 +20744,11 @@ var doctor_exports = {};
20090
20744
  __export(doctor_exports, {
20091
20745
  doctorCommand: () => doctorCommand
20092
20746
  });
20093
- import { existsSync as existsSync26, statSync as statSync7, accessSync, constants } from "fs";
20094
- import { execFileSync as execFileSync3 } from "child_process";
20747
+ import { existsSync as existsSync25, statSync as statSync7, accessSync, constants } from "fs";
20748
+ import { execFileSync as execFileSync4 } from "child_process";
20095
20749
  async function doctorCommand(globalOpts, localOpts) {
20096
20750
  const checks = [];
20097
- if (existsSync26(DB_PATH)) {
20751
+ if (existsSync25(DB_PATH)) {
20098
20752
  const size = statSync7(DB_PATH).size;
20099
20753
  checks.push({ name: "Database", status: "ok", message: `${DB_PATH} (${(size / 1024).toFixed(0)}KB)` });
20100
20754
  try {
@@ -20124,7 +20778,7 @@ async function doctorCommand(globalOpts, localOpts) {
20124
20778
  } else {
20125
20779
  checks.push({ name: "Database", status: "error", message: `Not found at ${DB_PATH}`, fix: "cc-claw setup" });
20126
20780
  }
20127
- if (existsSync26(ENV_PATH)) {
20781
+ if (existsSync25(ENV_PATH)) {
20128
20782
  checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
20129
20783
  } else {
20130
20784
  checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
@@ -20133,7 +20787,7 @@ async function doctorCommand(globalOpts, localOpts) {
20133
20787
  let installedBackends = 0;
20134
20788
  for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
20135
20789
  try {
20136
- const path = execFileSync3("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
20790
+ const path = execFileSync4("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
20137
20791
  checks.push({ name: `${label2} CLI`, status: "ok", message: path });
20138
20792
  installedBackends++;
20139
20793
  } catch {
@@ -20172,14 +20826,14 @@ async function doctorCommand(globalOpts, localOpts) {
20172
20826
  checks.push({ name: "Daemon", status: "warning", message: "could not probe" });
20173
20827
  }
20174
20828
  try {
20175
- const latest = execFileSync3("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
20829
+ const latest = execFileSync4("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
20176
20830
  if (latest && latest !== VERSION) {
20177
20831
  checks.push({ name: "Update available", status: "warning", message: `v${latest} available (current: v${VERSION})`, fix: "npm install -g cc-claw@latest" });
20178
20832
  }
20179
20833
  } catch {
20180
20834
  }
20181
20835
  const tokenPath = `${DATA_PATH}/api-token`;
20182
- if (existsSync26(tokenPath)) {
20836
+ if (existsSync25(tokenPath)) {
20183
20837
  try {
20184
20838
  accessSync(tokenPath, constants.R_OK);
20185
20839
  checks.push({ name: "API token", status: "ok", message: "token file readable" });
@@ -20204,7 +20858,7 @@ async function doctorCommand(globalOpts, localOpts) {
20204
20858
  }
20205
20859
  } catch {
20206
20860
  }
20207
- if (existsSync26(ERROR_LOG_PATH)) {
20861
+ if (existsSync25(ERROR_LOG_PATH)) {
20208
20862
  try {
20209
20863
  const { readFileSync: readFileSync21 } = await import("fs");
20210
20864
  const logContent = readFileSync21(ERROR_LOG_PATH, "utf-8");
@@ -20232,15 +20886,15 @@ async function doctorCommand(globalOpts, localOpts) {
20232
20886
  if (check.name === "Daemon" && check.fix?.includes("service start")) {
20233
20887
  try {
20234
20888
  const { execSync: execSync9 } = await import("child_process");
20235
- const os = (await import("os")).platform();
20236
- if (os === "darwin") {
20889
+ const os2 = (await import("os")).platform();
20890
+ if (os2 === "darwin") {
20237
20891
  try {
20238
20892
  execSync9("launchctl start com.cc-claw");
20239
20893
  check.status = "ok";
20240
20894
  check.message = "restarted";
20241
20895
  } catch {
20242
20896
  }
20243
- } else if (os === "linux") {
20897
+ } else if (os2 === "linux") {
20244
20898
  try {
20245
20899
  execSync9("systemctl --user start cc-claw");
20246
20900
  check.status = "ok";
@@ -20330,10 +20984,10 @@ var logs_exports = {};
20330
20984
  __export(logs_exports, {
20331
20985
  logsCommand: () => logsCommand
20332
20986
  });
20333
- import { existsSync as existsSync27, readFileSync as readFileSync16, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
20987
+ import { existsSync as existsSync26, readFileSync as readFileSync16, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
20334
20988
  async function logsCommand(opts) {
20335
20989
  const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
20336
- if (!existsSync27(logFile)) {
20990
+ if (!existsSync26(logFile)) {
20337
20991
  outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
20338
20992
  process.exit(1);
20339
20993
  }
@@ -20385,11 +21039,11 @@ __export(gemini_exports, {
20385
21039
  geminiReorder: () => geminiReorder,
20386
21040
  geminiRotation: () => geminiRotation
20387
21041
  });
20388
- import { existsSync as existsSync28, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readFileSync as readFileSync17, chmodSync } from "fs";
21042
+ import { existsSync as existsSync27, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readFileSync as readFileSync17, chmodSync } from "fs";
20389
21043
  import { join as join25 } from "path";
20390
21044
  import { createInterface as createInterface5 } from "readline";
20391
21045
  function requireDb() {
20392
- if (!existsSync28(DB_PATH)) {
21046
+ if (!existsSync27(DB_PATH)) {
20393
21047
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
20394
21048
  process.exit(1);
20395
21049
  }
@@ -20415,7 +21069,7 @@ function resolveOAuthEmail(configHome) {
20415
21069
  if (!configHome) return null;
20416
21070
  try {
20417
21071
  const accountsPath = join25(configHome, ".gemini", "google_accounts.json");
20418
- if (!existsSync28(accountsPath)) return null;
21072
+ if (!existsSync27(accountsPath)) return null;
20419
21073
  const accounts = JSON.parse(readFileSync17(accountsPath, "utf-8"));
20420
21074
  return accounts.active || null;
20421
21075
  } catch {
@@ -20499,7 +21153,7 @@ async function geminiAddKey(globalOpts, opts) {
20499
21153
  async function geminiAddAccount(globalOpts, opts) {
20500
21154
  await requireWriteDb();
20501
21155
  const slotsDir = join25(CC_CLAW_HOME, "gemini-slots");
20502
- if (!existsSync28(slotsDir)) mkdirSync12(slotsDir, { recursive: true });
21156
+ if (!existsSync27(slotsDir)) mkdirSync12(slotsDir, { recursive: true });
20503
21157
  const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
20504
21158
  const tempId = Date.now();
20505
21159
  const slotDir = join25(slotsDir, `slot-${tempId}`);
@@ -20523,7 +21177,7 @@ async function geminiAddAccount(globalOpts, opts) {
20523
21177
  } catch {
20524
21178
  }
20525
21179
  const oauthPath = join25(slotDir, ".gemini", "oauth_creds.json");
20526
- if (!existsSync28(oauthPath)) {
21180
+ if (!existsSync27(oauthPath)) {
20527
21181
  console.log(error2("\n No OAuth credentials found. Sign-in may have failed."));
20528
21182
  console.log(" The slot directory is preserved at: " + slotDir);
20529
21183
  console.log(" Re-run: cc-claw gemini add-account\n");
@@ -20637,12 +21291,12 @@ __export(backend_exports, {
20637
21291
  backendList: () => backendList,
20638
21292
  backendSet: () => backendSet
20639
21293
  });
20640
- import { existsSync as existsSync29 } from "fs";
21294
+ import { existsSync as existsSync28 } from "fs";
20641
21295
  async function backendList(globalOpts) {
20642
21296
  const { getAvailableAdapters: getAvailableAdapters2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
20643
21297
  const chatId = resolveChatId(globalOpts);
20644
21298
  let activeBackend = null;
20645
- if (existsSync29(DB_PATH)) {
21299
+ if (existsSync28(DB_PATH)) {
20646
21300
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
20647
21301
  const readDb = openDatabaseReadOnly2();
20648
21302
  try {
@@ -20673,7 +21327,7 @@ async function backendList(globalOpts) {
20673
21327
  }
20674
21328
  async function backendGet(globalOpts) {
20675
21329
  const chatId = resolveChatId(globalOpts);
20676
- if (!existsSync29(DB_PATH)) {
21330
+ if (!existsSync28(DB_PATH)) {
20677
21331
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
20678
21332
  process.exit(1);
20679
21333
  }
@@ -20717,13 +21371,13 @@ __export(model_exports, {
20717
21371
  modelList: () => modelList,
20718
21372
  modelSet: () => modelSet
20719
21373
  });
20720
- import { existsSync as existsSync30 } from "fs";
21374
+ import { existsSync as existsSync29 } from "fs";
20721
21375
  async function modelList(globalOpts) {
20722
21376
  const chatId = resolveChatId(globalOpts);
20723
21377
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
20724
21378
  const { getAdapter: getAdapter2, getAllAdapters: getAllAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
20725
21379
  let backendId = "claude";
20726
- if (existsSync30(DB_PATH)) {
21380
+ if (existsSync29(DB_PATH)) {
20727
21381
  const readDb = openDatabaseReadOnly2();
20728
21382
  try {
20729
21383
  const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
@@ -20756,7 +21410,7 @@ async function modelList(globalOpts) {
20756
21410
  }
20757
21411
  async function modelGet(globalOpts) {
20758
21412
  const chatId = resolveChatId(globalOpts);
20759
- if (!existsSync30(DB_PATH)) {
21413
+ if (!existsSync29(DB_PATH)) {
20760
21414
  outputError("DB_NOT_FOUND", "Database not found.");
20761
21415
  process.exit(1);
20762
21416
  }
@@ -20800,9 +21454,9 @@ __export(memory_exports, {
20800
21454
  memoryList: () => memoryList,
20801
21455
  memorySearch: () => memorySearch
20802
21456
  });
20803
- import { existsSync as existsSync31 } from "fs";
21457
+ import { existsSync as existsSync30 } from "fs";
20804
21458
  async function memoryList(globalOpts) {
20805
- if (!existsSync31(DB_PATH)) {
21459
+ if (!existsSync30(DB_PATH)) {
20806
21460
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
20807
21461
  process.exit(1);
20808
21462
  }
@@ -20826,7 +21480,7 @@ async function memoryList(globalOpts) {
20826
21480
  });
20827
21481
  }
20828
21482
  async function memorySearch(globalOpts, query) {
20829
- if (!existsSync31(DB_PATH)) {
21483
+ if (!existsSync30(DB_PATH)) {
20830
21484
  outputError("DB_NOT_FOUND", "Database not found.");
20831
21485
  process.exit(1);
20832
21486
  }
@@ -20848,7 +21502,7 @@ async function memorySearch(globalOpts, query) {
20848
21502
  });
20849
21503
  }
20850
21504
  async function memoryHistory(globalOpts, opts) {
20851
- if (!existsSync31(DB_PATH)) {
21505
+ if (!existsSync30(DB_PATH)) {
20852
21506
  outputError("DB_NOT_FOUND", "Database not found.");
20853
21507
  process.exit(1);
20854
21508
  }
@@ -20896,7 +21550,7 @@ __export(cron_exports2, {
20896
21550
  cronList: () => cronList,
20897
21551
  cronRuns: () => cronRuns
20898
21552
  });
20899
- import { existsSync as existsSync32 } from "fs";
21553
+ import { existsSync as existsSync31 } from "fs";
20900
21554
  function parseFallbacks(raw) {
20901
21555
  return raw.slice(0, 3).map((f) => {
20902
21556
  const [backend2, ...rest] = f.split(":");
@@ -20917,7 +21571,7 @@ function parseAndValidateTimeout(raw) {
20917
21571
  return val;
20918
21572
  }
20919
21573
  async function cronList(globalOpts) {
20920
- if (!existsSync32(DB_PATH)) {
21574
+ if (!existsSync31(DB_PATH)) {
20921
21575
  outputError("DB_NOT_FOUND", "Database not found.");
20922
21576
  process.exit(1);
20923
21577
  }
@@ -20955,7 +21609,7 @@ async function cronList(globalOpts) {
20955
21609
  });
20956
21610
  }
20957
21611
  async function cronHealth(globalOpts) {
20958
- if (!existsSync32(DB_PATH)) {
21612
+ if (!existsSync31(DB_PATH)) {
20959
21613
  outputError("DB_NOT_FOUND", "Database not found.");
20960
21614
  process.exit(1);
20961
21615
  }
@@ -20984,7 +21638,7 @@ async function cronHealth(globalOpts) {
20984
21638
  });
20985
21639
  }
20986
21640
  async function cronCreate(globalOpts, opts) {
20987
- const { isDaemonRunning: isDaemonRunning2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
21641
+ const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
20988
21642
  if (!await isDaemonRunning2()) {
20989
21643
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
20990
21644
  process.exit(1);
@@ -20997,8 +21651,6 @@ async function cronCreate(globalOpts, opts) {
20997
21651
  const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
20998
21652
  const timeout = parseAndValidateTimeout(opts.timeout);
20999
21653
  try {
21000
- const { initDatabase: initDatabase2, insertJob: insertJob2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
21001
- initDatabase2();
21002
21654
  const schedType = opts.cron ? "cron" : opts.at ? "at" : "every";
21003
21655
  let everyMs = null;
21004
21656
  if (opts.every) {
@@ -21010,7 +21662,7 @@ async function cronCreate(globalOpts, opts) {
21010
21662
  }
21011
21663
  }
21012
21664
  const fallbacks = opts.fallback?.length ? parseFallbacks(opts.fallback) : void 0;
21013
- const job = insertJob2({
21665
+ const res = await apiPost2("/api/cron/create", {
21014
21666
  scheduleType: schedType,
21015
21667
  cron: opts.cron ?? null,
21016
21668
  atTime: opts.at ?? null,
@@ -21029,9 +21681,14 @@ async function cronCreate(globalOpts, opts) {
21029
21681
  target: opts.target ?? null,
21030
21682
  timezone: opts.timezone ?? "UTC"
21031
21683
  });
21032
- output({ id: job.id, success: true }, () => `
21033
- ${successFmt(`Job #${job.id} created.`)}
21684
+ if (res.ok) {
21685
+ output({ id: res.data.id, success: true }, () => `
21686
+ ${successFmt(`Job #${res.data.id} created.`)}
21034
21687
  `);
21688
+ } else {
21689
+ outputError("CREATE_FAILED", `Failed: ${JSON.stringify(res.data)}`);
21690
+ process.exit(1);
21691
+ }
21035
21692
  } catch (err) {
21036
21693
  outputError("CREATE_FAILED", err.message);
21037
21694
  process.exit(1);
@@ -21057,7 +21714,7 @@ async function cronAction(globalOpts, action, id) {
21057
21714
  }
21058
21715
  }
21059
21716
  async function cronEdit(globalOpts, id, opts) {
21060
- const { isDaemonRunning: isDaemonRunning2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
21717
+ const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
21061
21718
  if (!await isDaemonRunning2()) {
21062
21719
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
21063
21720
  process.exit(1);
@@ -21065,82 +21722,53 @@ async function cronEdit(globalOpts, id, opts) {
21065
21722
  const jobId = parseInt(id, 10);
21066
21723
  const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
21067
21724
  try {
21068
- const { initDatabase: initDatabase2, getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
21069
- initDatabase2();
21070
- const db3 = getDb2();
21071
- const updates = [];
21072
- const values = [];
21073
- if (opts.title) {
21074
- updates.push("title = ?");
21075
- values.push(opts.title);
21076
- }
21077
- if (opts.description) {
21078
- updates.push("description = ?");
21079
- values.push(opts.description);
21080
- }
21081
- if (opts.cron) {
21082
- updates.push("cron = ?, schedule_type = 'cron'");
21083
- values.push(opts.cron);
21084
- }
21085
- if (opts.at) {
21086
- updates.push("at_time = ?, schedule_type = 'at'");
21087
- values.push(opts.at);
21088
- }
21725
+ const payload = { id: jobId };
21726
+ if (opts.title) payload.title = opts.title;
21727
+ if (opts.description) payload.description = opts.description;
21728
+ if (opts.cron) payload.cron = opts.cron;
21729
+ if (opts.at) payload.atTime = opts.at;
21089
21730
  if (opts.every) {
21090
21731
  const m = opts.every.match(/^(\d+)\s*(m|min|h|hr|s|sec)$/i);
21091
21732
  if (m) {
21092
21733
  const num = parseInt(m[1], 10);
21093
21734
  const unit = m[2].toLowerCase();
21094
- const ms = unit.startsWith("h") ? num * 36e5 : unit.startsWith("m") ? num * 6e4 : num * 1e3;
21095
- updates.push("every_ms = ?, schedule_type = 'every'");
21096
- values.push(ms);
21735
+ payload.everyMs = unit.startsWith("h") ? num * 36e5 : unit.startsWith("m") ? num * 6e4 : num * 1e3;
21097
21736
  } else {
21098
- updates.push("every_ms = ?, schedule_type = 'every'");
21099
- values.push(parseInt(opts.every, 10) || 0);
21737
+ payload.everyMs = parseInt(opts.every, 10) || 0;
21100
21738
  }
21101
21739
  }
21102
- if (opts.backend) {
21103
- updates.push("backend = ?");
21104
- values.push(opts.backend);
21105
- }
21106
- if (opts.model) {
21107
- updates.push("model = ?");
21108
- values.push(opts.model);
21109
- }
21110
- if (opts.thinking) {
21111
- updates.push("thinking = ?");
21112
- values.push(opts.thinking);
21113
- }
21740
+ if (opts.backend) payload.backend = opts.backend;
21741
+ if (opts.model) payload.model = opts.model;
21742
+ if (opts.thinking) payload.thinking = opts.thinking;
21114
21743
  if (opts.timeout) {
21115
21744
  const timeout = parseAndValidateTimeout(opts.timeout);
21116
- updates.push("timeout = ?");
21117
- values.push(timeout);
21118
- }
21119
- if (opts.timezone) {
21120
- updates.push("timezone = ?");
21121
- values.push(opts.timezone);
21745
+ payload.timeout = timeout;
21122
21746
  }
21747
+ if (opts.timezone) payload.timezone = opts.timezone;
21123
21748
  if (opts.fallback?.length) {
21124
- const fallbacks = parseFallbacks(opts.fallback);
21125
- updates.push("fallbacks = ?");
21126
- values.push(JSON.stringify(fallbacks));
21749
+ payload.fallbacks = parseFallbacks(opts.fallback);
21127
21750
  }
21128
- if (updates.length === 0) {
21751
+ const fieldCount = Object.keys(payload).length - 1;
21752
+ if (fieldCount === 0) {
21129
21753
  outputError("NO_CHANGES", "No fields to update. Specify fields with flags (e.g. --description, --cron).");
21130
21754
  process.exit(1);
21131
21755
  }
21132
- values.push(jobId);
21133
- db3.prepare(`UPDATE jobs SET ${updates.join(", ")} WHERE id = ?`).run(...values);
21134
- output({ id: jobId, success: true }, () => `
21756
+ const res = await apiPost2("/api/cron/edit", payload);
21757
+ if (res.ok) {
21758
+ output({ id: jobId, success: true }, () => `
21135
21759
  ${successFmt(`Job #${jobId} updated.`)}
21136
21760
  `);
21761
+ } else {
21762
+ outputError("EDIT_FAILED", `Failed: ${JSON.stringify(res.data)}`);
21763
+ process.exit(1);
21764
+ }
21137
21765
  } catch (err) {
21138
21766
  outputError("EDIT_FAILED", err.message);
21139
21767
  process.exit(1);
21140
21768
  }
21141
21769
  }
21142
21770
  async function cronRuns(globalOpts, jobId, opts) {
21143
- if (!existsSync32(DB_PATH)) {
21771
+ if (!existsSync31(DB_PATH)) {
21144
21772
  outputError("DB_NOT_FOUND", "Database not found.");
21145
21773
  process.exit(1);
21146
21774
  }
@@ -21187,16 +21815,16 @@ __export(agents_exports, {
21187
21815
  runnersList: () => runnersList,
21188
21816
  tasksList: () => tasksList
21189
21817
  });
21190
- import { existsSync as existsSync33 } from "fs";
21818
+ import { existsSync as existsSync32 } from "fs";
21191
21819
  async function agentsList(globalOpts) {
21192
- if (!existsSync33(DB_PATH)) {
21820
+ if (!existsSync32(DB_PATH)) {
21193
21821
  outputError("DB_NOT_FOUND", "Database not found.");
21194
21822
  process.exit(1);
21195
21823
  }
21196
21824
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
21197
21825
  const readDb = openDatabaseReadOnly2();
21198
21826
  const agents2 = readDb.prepare(
21199
- "SELECT * FROM agents WHERE status IN ('running', 'queued', 'starting', 'idle') ORDER BY created_at DESC"
21827
+ "SELECT * FROM agents WHERE status IN ('running', 'queued', 'starting', 'idle') ORDER BY createdAt DESC"
21200
21828
  ).all();
21201
21829
  readDb.close();
21202
21830
  output(agents2, (d) => {
@@ -21207,10 +21835,10 @@ async function agentsList(globalOpts) {
21207
21835
  const lines = ["", divider(`Active Agents (${list.length})`), ""];
21208
21836
  for (const a of list) {
21209
21837
  const shortId = a.id?.slice(0, 8) ?? "?";
21210
- lines.push(` ${statusDot(a.status)} ${shortId} (${a.runner_id ?? a.runnerId}) \u2014 ${a.status}`);
21838
+ lines.push(` ${statusDot(a.status)} ${shortId} (${a.runnerId}) \u2014 ${a.status}`);
21211
21839
  if (a.task) lines.push(` Task: ${a.task.slice(0, 80)}${a.task.length > 80 ? "\u2026" : ""}`);
21212
- const inK = ((a.token_input ?? a.tokenInput ?? 0) / 1e3).toFixed(1);
21213
- const outK = ((a.token_output ?? a.tokenOutput ?? 0) / 1e3).toFixed(1);
21840
+ const inK = ((a.tokenInput ?? 0) / 1e3).toFixed(1);
21841
+ const outK = ((a.tokenOutput ?? 0) / 1e3).toFixed(1);
21214
21842
  if (parseFloat(inK) > 0 || parseFloat(outK) > 0) {
21215
21843
  lines.push(` Tokens: ${inK}k in / ${outK}k out`);
21216
21844
  }
@@ -21220,14 +21848,14 @@ async function agentsList(globalOpts) {
21220
21848
  });
21221
21849
  }
21222
21850
  async function tasksList(globalOpts) {
21223
- if (!existsSync33(DB_PATH)) {
21851
+ if (!existsSync32(DB_PATH)) {
21224
21852
  outputError("DB_NOT_FOUND", "Database not found.");
21225
21853
  process.exit(1);
21226
21854
  }
21227
21855
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
21228
21856
  const readDb = openDatabaseReadOnly2();
21229
21857
  const tasks = readDb.prepare(
21230
- "SELECT t.* FROM tasks t JOIN orchestrations o ON t.orchestration_id = o.id WHERE o.status = 'active' ORDER BY t.id"
21858
+ "SELECT t.* FROM agent_tasks t JOIN orchestrations o ON t.orchestrationId = o.id WHERE o.status = 'active' ORDER BY t.id"
21231
21859
  ).all();
21232
21860
  readDb.close();
21233
21861
  output(tasks, (d) => {
@@ -21348,10 +21976,10 @@ __export(db_exports, {
21348
21976
  dbPath: () => dbPath,
21349
21977
  dbStats: () => dbStats
21350
21978
  });
21351
- import { existsSync as existsSync34, statSync as statSync8, copyFileSync as copyFileSync2, mkdirSync as mkdirSync13 } from "fs";
21979
+ import { existsSync as existsSync33, statSync as statSync8, copyFileSync as copyFileSync2, mkdirSync as mkdirSync13 } from "fs";
21352
21980
  import { dirname as dirname5 } from "path";
21353
21981
  async function dbStats(globalOpts) {
21354
- if (!existsSync34(DB_PATH)) {
21982
+ if (!existsSync33(DB_PATH)) {
21355
21983
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
21356
21984
  process.exit(1);
21357
21985
  }
@@ -21359,7 +21987,7 @@ async function dbStats(globalOpts) {
21359
21987
  const readDb = openDatabaseReadOnly2();
21360
21988
  const mainSize = statSync8(DB_PATH).size;
21361
21989
  const walPath = DB_PATH + "-wal";
21362
- const walSize = existsSync34(walPath) ? statSync8(walPath).size : 0;
21990
+ const walSize = existsSync33(walPath) ? statSync8(walPath).size : 0;
21363
21991
  const tableNames = readDb.prepare(
21364
21992
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
21365
21993
  ).all();
@@ -21393,7 +22021,7 @@ async function dbPath(globalOpts) {
21393
22021
  output({ path: DB_PATH }, (d) => d.path);
21394
22022
  }
21395
22023
  async function dbBackup(globalOpts, destPath) {
21396
- if (!existsSync34(DB_PATH)) {
22024
+ if (!existsSync33(DB_PATH)) {
21397
22025
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
21398
22026
  process.exit(1);
21399
22027
  }
@@ -21402,7 +22030,7 @@ async function dbBackup(globalOpts, destPath) {
21402
22030
  mkdirSync13(dirname5(dest), { recursive: true });
21403
22031
  copyFileSync2(DB_PATH, dest);
21404
22032
  const walPath = DB_PATH + "-wal";
21405
- if (existsSync34(walPath)) copyFileSync2(walPath, dest + "-wal");
22033
+ if (existsSync33(walPath)) copyFileSync2(walPath, dest + "-wal");
21406
22034
  output({ path: dest, sizeBytes: statSync8(dest).size }, (d) => {
21407
22035
  const b = d;
21408
22036
  return `
@@ -21431,9 +22059,9 @@ __export(usage_exports, {
21431
22059
  usageCost: () => usageCost,
21432
22060
  usageTokens: () => usageTokens
21433
22061
  });
21434
- import { existsSync as existsSync35 } from "fs";
22062
+ import { existsSync as existsSync34 } from "fs";
21435
22063
  function ensureDb() {
21436
- if (!existsSync35(DB_PATH)) {
22064
+ if (!existsSync34(DB_PATH)) {
21437
22065
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
21438
22066
  process.exit(1);
21439
22067
  }
@@ -21623,9 +22251,9 @@ __export(config_exports, {
21623
22251
  configList: () => configList,
21624
22252
  configSet: () => configSet
21625
22253
  });
21626
- import { existsSync as existsSync36, readFileSync as readFileSync18 } from "fs";
22254
+ import { existsSync as existsSync35, readFileSync as readFileSync18 } from "fs";
21627
22255
  async function configList(globalOpts) {
21628
- if (!existsSync36(DB_PATH)) {
22256
+ if (!existsSync35(DB_PATH)) {
21629
22257
  outputError("DB_NOT_FOUND", "Database not found.");
21630
22258
  process.exit(1);
21631
22259
  }
@@ -21659,7 +22287,7 @@ async function configGet(globalOpts, key) {
21659
22287
  outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
21660
22288
  process.exit(1);
21661
22289
  }
21662
- if (!existsSync36(DB_PATH)) {
22290
+ if (!existsSync35(DB_PATH)) {
21663
22291
  outputError("DB_NOT_FOUND", "Database not found.");
21664
22292
  process.exit(1);
21665
22293
  }
@@ -21705,7 +22333,7 @@ async function configSet(globalOpts, key, value) {
21705
22333
  }
21706
22334
  }
21707
22335
  async function configEnv(_globalOpts) {
21708
- if (!existsSync36(ENV_PATH)) {
22336
+ if (!existsSync35(ENV_PATH)) {
21709
22337
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
21710
22338
  process.exit(1);
21711
22339
  }
@@ -21759,9 +22387,9 @@ __export(session_exports, {
21759
22387
  sessionGet: () => sessionGet,
21760
22388
  sessionNew: () => sessionNew
21761
22389
  });
21762
- import { existsSync as existsSync37 } from "fs";
22390
+ import { existsSync as existsSync36 } from "fs";
21763
22391
  async function sessionGet(globalOpts) {
21764
- if (!existsSync37(DB_PATH)) {
22392
+ if (!existsSync36(DB_PATH)) {
21765
22393
  outputError("DB_NOT_FOUND", "Database not found.");
21766
22394
  process.exit(1);
21767
22395
  }
@@ -21822,9 +22450,9 @@ __export(permissions_exports, {
21822
22450
  verboseGet: () => verboseGet,
21823
22451
  verboseSet: () => verboseSet
21824
22452
  });
21825
- import { existsSync as existsSync38 } from "fs";
22453
+ import { existsSync as existsSync37 } from "fs";
21826
22454
  function ensureDb2() {
21827
- if (!existsSync38(DB_PATH)) {
22455
+ if (!existsSync37(DB_PATH)) {
21828
22456
  outputError("DB_NOT_FOUND", "Database not found.");
21829
22457
  process.exit(1);
21830
22458
  }
@@ -21971,9 +22599,9 @@ __export(cwd_exports, {
21971
22599
  cwdGet: () => cwdGet,
21972
22600
  cwdSet: () => cwdSet
21973
22601
  });
21974
- import { existsSync as existsSync39 } from "fs";
22602
+ import { existsSync as existsSync38 } from "fs";
21975
22603
  async function cwdGet(globalOpts) {
21976
- if (!existsSync39(DB_PATH)) {
22604
+ if (!existsSync38(DB_PATH)) {
21977
22605
  outputError("DB_NOT_FOUND", "Database not found.");
21978
22606
  process.exit(1);
21979
22607
  }
@@ -22035,9 +22663,9 @@ __export(voice_exports, {
22035
22663
  voiceGet: () => voiceGet,
22036
22664
  voiceSet: () => voiceSet
22037
22665
  });
22038
- import { existsSync as existsSync40 } from "fs";
22666
+ import { existsSync as existsSync39 } from "fs";
22039
22667
  async function voiceGet(globalOpts) {
22040
- if (!existsSync40(DB_PATH)) {
22668
+ if (!existsSync39(DB_PATH)) {
22041
22669
  outputError("DB_NOT_FOUND", "Database not found.");
22042
22670
  process.exit(1);
22043
22671
  }
@@ -22086,9 +22714,9 @@ __export(heartbeat_exports, {
22086
22714
  heartbeatGet: () => heartbeatGet,
22087
22715
  heartbeatSet: () => heartbeatSet
22088
22716
  });
22089
- import { existsSync as existsSync41 } from "fs";
22717
+ import { existsSync as existsSync40 } from "fs";
22090
22718
  async function heartbeatGet(globalOpts) {
22091
- if (!existsSync41(DB_PATH)) {
22719
+ if (!existsSync40(DB_PATH)) {
22092
22720
  outputError("DB_NOT_FOUND", "Database not found.");
22093
22721
  process.exit(1);
22094
22722
  }
@@ -22198,9 +22826,9 @@ __export(chats_exports, {
22198
22826
  chatsList: () => chatsList,
22199
22827
  chatsRemoveAlias: () => chatsRemoveAlias
22200
22828
  });
22201
- import { existsSync as existsSync42 } from "fs";
22829
+ import { existsSync as existsSync41 } from "fs";
22202
22830
  async function chatsList(_globalOpts) {
22203
- if (!existsSync42(DB_PATH)) {
22831
+ if (!existsSync41(DB_PATH)) {
22204
22832
  outputError("DB_NOT_FOUND", "Database not found.");
22205
22833
  process.exit(1);
22206
22834
  }
@@ -22328,9 +22956,9 @@ var mcps_exports = {};
22328
22956
  __export(mcps_exports, {
22329
22957
  mcpsList: () => mcpsList
22330
22958
  });
22331
- import { existsSync as existsSync43 } from "fs";
22959
+ import { existsSync as existsSync42 } from "fs";
22332
22960
  async function mcpsList(_globalOpts) {
22333
- if (!existsSync43(DB_PATH)) {
22961
+ if (!existsSync42(DB_PATH)) {
22334
22962
  outputError("DB_NOT_FOUND", "Database not found.");
22335
22963
  process.exit(1);
22336
22964
  }
@@ -22367,11 +22995,11 @@ __export(chat_exports, {
22367
22995
  chatSend: () => chatSend
22368
22996
  });
22369
22997
  import { request as httpRequest2 } from "http";
22370
- import { readFileSync as readFileSync19, existsSync as existsSync44 } from "fs";
22998
+ import { readFileSync as readFileSync19, existsSync as existsSync43 } from "fs";
22371
22999
  function getToken2() {
22372
23000
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
22373
23001
  try {
22374
- if (existsSync44(TOKEN_PATH2)) return readFileSync19(TOKEN_PATH2, "utf-8").trim();
23002
+ if (existsSync43(TOKEN_PATH2)) return readFileSync19(TOKEN_PATH2, "utf-8").trim();
22375
23003
  } catch {
22376
23004
  }
22377
23005
  return null;
@@ -22841,9 +23469,9 @@ __export(evolve_exports, {
22841
23469
  evolveStatus: () => evolveStatus,
22842
23470
  evolveUndo: () => evolveUndo
22843
23471
  });
22844
- import { existsSync as existsSync45 } from "fs";
23472
+ import { existsSync as existsSync44 } from "fs";
22845
23473
  function ensureDb3() {
22846
- if (!existsSync45(DB_PATH)) {
23474
+ if (!existsSync44(DB_PATH)) {
22847
23475
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
22848
23476
  process.exit(1);
22849
23477
  }
@@ -23259,8 +23887,8 @@ var init_evolve = __esm({
23259
23887
 
23260
23888
  // src/setup.ts
23261
23889
  var setup_exports = {};
23262
- import { existsSync as existsSync46, writeFileSync as writeFileSync11, readFileSync as readFileSync20, copyFileSync as copyFileSync3, mkdirSync as mkdirSync15, statSync as statSync9 } from "fs";
23263
- import { execFileSync as execFileSync4 } from "child_process";
23890
+ import { existsSync as existsSync45, writeFileSync as writeFileSync11, readFileSync as readFileSync20, copyFileSync as copyFileSync3, mkdirSync as mkdirSync15, statSync as statSync9 } from "fs";
23891
+ import { execFileSync as execFileSync5 } from "child_process";
23264
23892
  import { createInterface as createInterface7 } from "readline";
23265
23893
  import { join as join27 } from "path";
23266
23894
  function divider2() {
@@ -23324,7 +23952,7 @@ async function setup() {
23324
23952
  let foundAnyBackend = false;
23325
23953
  for (const bk of backends) {
23326
23954
  try {
23327
- const path = execFileSync4("which", [bk.cmd], { encoding: "utf-8" }).trim();
23955
+ const path = execFileSync5("which", [bk.cmd], { encoding: "utf-8" }).trim();
23328
23956
  console.log(green(` ${bk.name} CLI found: ${path}`));
23329
23957
  foundAnyBackend = true;
23330
23958
  } catch {
@@ -23337,10 +23965,10 @@ async function setup() {
23337
23965
  }
23338
23966
  console.log("");
23339
23967
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
23340
- if (!existsSync46(dir)) mkdirSync15(dir, { recursive: true });
23968
+ if (!existsSync45(dir)) mkdirSync15(dir, { recursive: true });
23341
23969
  }
23342
23970
  const env = {};
23343
- const envSource = existsSync46(ENV_PATH) ? ENV_PATH : existsSync46(".env") ? ".env" : null;
23971
+ const envSource = existsSync45(ENV_PATH) ? ENV_PATH : existsSync45(".env") ? ".env" : null;
23344
23972
  if (envSource) {
23345
23973
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
23346
23974
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
@@ -23351,7 +23979,7 @@ async function setup() {
23351
23979
  }
23352
23980
  }
23353
23981
  const cwdDb = join27(process.cwd(), "cc-claw.db");
23354
- if (existsSync46(cwdDb) && !existsSync46(DB_PATH)) {
23982
+ if (existsSync45(cwdDb) && !existsSync45(DB_PATH)) {
23355
23983
  const { size } = statSync9(cwdDb);
23356
23984
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
23357
23985
  const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
@@ -24184,8 +24812,8 @@ async function run(argv = process.argv) {
24184
24812
  if (argv.includes("--version") || argv.includes("-V")) {
24185
24813
  console.log(VERSION);
24186
24814
  try {
24187
- const { execFileSync: execFileSync5 } = await import("child_process");
24188
- const latest = execFileSync5("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
24815
+ const { execFileSync: execFileSync6 } = await import("child_process");
24816
+ const latest = execFileSync6("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
24189
24817
  if (latest && latest !== VERSION) {
24190
24818
  console.log(`
24191
24819
  Update available: v${latest} (current: v${VERSION})`);