cc-claw 0.12.7 → 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.
- package/dist/cli.js +1437 -727
- 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.
|
|
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
|
-
|
|
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.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
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
|
|
309
|
-
`)
|
|
310
|
-
|
|
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
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
`)
|
|
654
|
-
|
|
655
|
-
|
|
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 =
|
|
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 =
|
|
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 (
|
|
1717
|
-
SELECT
|
|
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', '
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
@@ -4247,16 +4387,97 @@ var init_backends = __esm({
|
|
|
4247
4387
|
}
|
|
4248
4388
|
});
|
|
4249
4389
|
|
|
4390
|
+
// src/tool-loop-detector.ts
|
|
4391
|
+
function djb2Hash(str) {
|
|
4392
|
+
let hash = 5381;
|
|
4393
|
+
for (let i = 0; i < str.length; i++) {
|
|
4394
|
+
hash = (hash << 5) + hash + str.charCodeAt(i) & 4294967295;
|
|
4395
|
+
}
|
|
4396
|
+
return hash.toString(36);
|
|
4397
|
+
}
|
|
4398
|
+
function extractKeyField(input) {
|
|
4399
|
+
const key = input.file_path ?? input.path ?? input.file ?? input.command ?? input.cmd ?? input.url ?? input.query ?? input.search_query ?? input.pattern ?? input.content;
|
|
4400
|
+
if (key !== void 0 && key !== null) {
|
|
4401
|
+
return String(key);
|
|
4402
|
+
}
|
|
4403
|
+
const sorted = Object.keys(input).sort();
|
|
4404
|
+
const parts = sorted.map((k) => `${k}:${JSON.stringify(input[k])}`);
|
|
4405
|
+
return parts.join("|").slice(0, 500);
|
|
4406
|
+
}
|
|
4407
|
+
function fingerprint(toolName, input) {
|
|
4408
|
+
const key = extractKeyField(input);
|
|
4409
|
+
return `${toolName}:${djb2Hash(key)}`;
|
|
4410
|
+
}
|
|
4411
|
+
var DEFAULT_WINDOW_SIZE, DEFAULT_THRESHOLD, HARD_TURN_CAP, ToolLoopDetector;
|
|
4412
|
+
var init_tool_loop_detector = __esm({
|
|
4413
|
+
"src/tool-loop-detector.ts"() {
|
|
4414
|
+
"use strict";
|
|
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);
|
|
4417
|
+
HARD_TURN_CAP = 200;
|
|
4418
|
+
ToolLoopDetector = class {
|
|
4419
|
+
windowSize;
|
|
4420
|
+
threshold;
|
|
4421
|
+
window = [];
|
|
4422
|
+
constructor(windowSize = DEFAULT_WINDOW_SIZE, threshold = DEFAULT_THRESHOLD) {
|
|
4423
|
+
this.windowSize = windowSize;
|
|
4424
|
+
this.threshold = threshold;
|
|
4425
|
+
}
|
|
4426
|
+
/**
|
|
4427
|
+
* Record a tool call and check for loops.
|
|
4428
|
+
*
|
|
4429
|
+
* @returns `{ isLoop: true, reason }` if the same fingerprint appears
|
|
4430
|
+
* `threshold` or more times in the sliding window.
|
|
4431
|
+
*/
|
|
4432
|
+
addCall(toolName, input) {
|
|
4433
|
+
const fp = fingerprint(toolName, input);
|
|
4434
|
+
this.window.push(fp);
|
|
4435
|
+
while (this.window.length > this.windowSize) {
|
|
4436
|
+
this.window.shift();
|
|
4437
|
+
}
|
|
4438
|
+
let count = 0;
|
|
4439
|
+
for (const entry of this.window) {
|
|
4440
|
+
if (entry === fp) count++;
|
|
4441
|
+
}
|
|
4442
|
+
if (count >= this.threshold) {
|
|
4443
|
+
return {
|
|
4444
|
+
isLoop: true,
|
|
4445
|
+
reason: `Tool "${toolName}" called ${count}\xD7 with same input in last ${this.window.length} calls`
|
|
4446
|
+
};
|
|
4447
|
+
}
|
|
4448
|
+
return { isLoop: false };
|
|
4449
|
+
}
|
|
4450
|
+
/** Number of calls tracked so far. */
|
|
4451
|
+
get callCount() {
|
|
4452
|
+
return this.window.length;
|
|
4453
|
+
}
|
|
4454
|
+
/** Reset the detector (e.g., after a retry with a fresh session). */
|
|
4455
|
+
reset() {
|
|
4456
|
+
this.window.length = 0;
|
|
4457
|
+
}
|
|
4458
|
+
};
|
|
4459
|
+
}
|
|
4460
|
+
});
|
|
4461
|
+
|
|
4250
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
|
+
}
|
|
4251
4471
|
function daysSince(dateStr) {
|
|
4252
|
-
const
|
|
4472
|
+
const trimmed = dateStr.trim();
|
|
4473
|
+
const date = /* @__PURE__ */ new Date(trimmed.replace(" ", "T") + (trimmed.includes("Z") ? "" : "Z"));
|
|
4253
4474
|
return Math.max(0, (Date.now() - date.getTime()) / (1e3 * 60 * 60 * 24));
|
|
4254
4475
|
}
|
|
4255
4476
|
function normalizeFtsScores(items) {
|
|
4256
4477
|
const scores = /* @__PURE__ */ new Map();
|
|
4257
4478
|
if (items.length === 0) return scores;
|
|
4258
4479
|
const ranks = items.map((it) => Math.abs(it._ftsRank ?? 0));
|
|
4259
|
-
const maxRank =
|
|
4480
|
+
const maxRank = ranks.reduce((max, r) => r > max ? r : max, 1e-3);
|
|
4260
4481
|
for (let i = 0; i < items.length; i++) {
|
|
4261
4482
|
scores.set(items[i].id, ranks[i] / maxRank);
|
|
4262
4483
|
}
|
|
@@ -4326,14 +4547,15 @@ async function injectMemoryContext(userMessage) {
|
|
|
4326
4547
|
queryEmbedding = await embedQuery(userMessage);
|
|
4327
4548
|
}
|
|
4328
4549
|
const hasVectors = queryEmbedding !== null;
|
|
4550
|
+
const { vectorK, ftsK } = getTopK(userMessage);
|
|
4329
4551
|
let ftsMemories = [];
|
|
4330
4552
|
let ftsSessions = [];
|
|
4331
4553
|
try {
|
|
4332
|
-
ftsMemories = searchMemories(userMessage,
|
|
4554
|
+
ftsMemories = searchMemories(userMessage, ftsK);
|
|
4333
4555
|
} catch {
|
|
4334
4556
|
}
|
|
4335
4557
|
try {
|
|
4336
|
-
ftsSessions = searchSessionSummaries(userMessage,
|
|
4558
|
+
ftsSessions = searchSessionSummaries(userMessage, ftsK);
|
|
4337
4559
|
} catch {
|
|
4338
4560
|
}
|
|
4339
4561
|
let combinedMemories;
|
|
@@ -4341,8 +4563,8 @@ async function injectMemoryContext(userMessage) {
|
|
|
4341
4563
|
if (hasVectors) {
|
|
4342
4564
|
const allEmbeddedMemories = getAllMemoriesWithEmbeddings();
|
|
4343
4565
|
const allEmbeddedSessions = getAllSessionSummariesWithEmbeddings();
|
|
4344
|
-
const memVectorMatches = vectorSearch(queryEmbedding, allEmbeddedMemories,
|
|
4345
|
-
const sesVectorMatches = vectorSearch(queryEmbedding, allEmbeddedSessions,
|
|
4566
|
+
const memVectorMatches = vectorSearch(queryEmbedding, allEmbeddedMemories, vectorK);
|
|
4567
|
+
const sesVectorMatches = vectorSearch(queryEmbedding, allEmbeddedSessions, vectorK);
|
|
4346
4568
|
const memMap = /* @__PURE__ */ new Map();
|
|
4347
4569
|
for (const m of ftsMemories) memMap.set(m.id, m);
|
|
4348
4570
|
for (const vm of memVectorMatches) {
|
|
@@ -4411,16 +4633,16 @@ async function injectMemoryContext(userMessage) {
|
|
|
4411
4633
|
${lines.join("\n")}
|
|
4412
4634
|
[End memory context]`;
|
|
4413
4635
|
}
|
|
4414
|
-
var MEMORY_DECAY_RATE, SESSION_DECAY_RATE,
|
|
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;
|
|
4415
4637
|
var init_inject = __esm({
|
|
4416
4638
|
"src/memory/inject.ts"() {
|
|
4417
4639
|
"use strict";
|
|
4418
4640
|
init_store5();
|
|
4419
4641
|
init_embeddings();
|
|
4420
|
-
MEMORY_DECAY_RATE = 0.02;
|
|
4421
|
-
SESSION_DECAY_RATE =
|
|
4422
|
-
|
|
4423
|
-
|
|
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;
|
|
4424
4646
|
FINAL_TOP_K_MEMORIES = 5;
|
|
4425
4647
|
FINAL_TOP_K_SESSIONS = 3;
|
|
4426
4648
|
MAX_MEMORY_CHARS = 500;
|
|
@@ -4515,6 +4737,16 @@ You are an expert user and operator of the CC-Claw architecture. Because you are
|
|
|
4515
4737
|
- **Files**: You can send physical files to the user across Telegram by simply writing \`[SEND_FILE:/absolute/path/to/file]\` in your response.
|
|
4516
4738
|
- **MCP Ecosystem**: You are deeply natively integrated with Model Context Protocol (MCP) servers (like Perplexity, NotebookLM, Context7) granting you immense external reach.
|
|
4517
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
|
+
|
|
4518
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.
|
|
4519
4751
|
`;
|
|
4520
4752
|
}
|
|
@@ -4668,6 +4900,14 @@ function syncNativeCliFiles() {
|
|
|
4668
4900
|
"- For heartbeat checks: respond with exactly HEARTBEAT_OK if nothing needs attention",
|
|
4669
4901
|
"- Your working directory is ~/.cc-claw/workspace/",
|
|
4670
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",
|
|
4671
4911
|
""
|
|
4672
4912
|
].join("\n");
|
|
4673
4913
|
const ccClawPath = join6(IDENTITY_PATH, "CC-CLAW.md");
|
|
@@ -4925,6 +5165,19 @@ var init_loader = __esm({
|
|
|
4925
5165
|
}
|
|
4926
5166
|
});
|
|
4927
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
|
+
|
|
4928
5181
|
// src/memory/session-log.ts
|
|
4929
5182
|
var session_log_exports = {};
|
|
4930
5183
|
__export(session_log_exports, {
|
|
@@ -4934,8 +5187,13 @@ __export(session_log_exports, {
|
|
|
4934
5187
|
getLastMessageTimestamp: () => getLastMessageTimestamp,
|
|
4935
5188
|
getLog: () => getLog,
|
|
4936
5189
|
getLoggedChatIds: () => getLoggedChatIds,
|
|
4937
|
-
getMessagePairCount: () => getMessagePairCount
|
|
5190
|
+
getMessagePairCount: () => getMessagePairCount,
|
|
5191
|
+
parseDbTimestamp: () => parseDbTimestamp
|
|
4938
5192
|
});
|
|
5193
|
+
function parseDbTimestamp(ts2) {
|
|
5194
|
+
const trimmed = ts2.trim();
|
|
5195
|
+
return (/* @__PURE__ */ new Date(trimmed + (trimmed.includes("Z") ? "" : "Z"))).getTime();
|
|
5196
|
+
}
|
|
4939
5197
|
function appendToLog(chatId, userMessage, assistantResponse, backend2, model2, sessionId) {
|
|
4940
5198
|
const now = Date.now();
|
|
4941
5199
|
appendMessageLog(chatId, "user", userMessage, backend2 ?? null, model2 ?? null, sessionId ?? null);
|
|
@@ -4955,7 +5213,8 @@ function getLog(chatId) {
|
|
|
4955
5213
|
return rows.map((r) => ({
|
|
4956
5214
|
role: r.role,
|
|
4957
5215
|
text: r.content,
|
|
4958
|
-
|
|
5216
|
+
// Audit C26: Use shared timestamp parser for consistency with inject.ts
|
|
5217
|
+
timestamp: parseDbTimestamp(r.created_at)
|
|
4959
5218
|
}));
|
|
4960
5219
|
}
|
|
4961
5220
|
function getMessagePairCount(chatId) {
|
|
@@ -4963,7 +5222,8 @@ function getMessagePairCount(chatId) {
|
|
|
4963
5222
|
if (cached && cached.length > 0) {
|
|
4964
5223
|
return Math.floor(cached.length / 2);
|
|
4965
5224
|
}
|
|
4966
|
-
|
|
5225
|
+
const dbRows = getUnsummarizedLog(chatId);
|
|
5226
|
+
return Math.floor(dbRows.length / 2);
|
|
4967
5227
|
}
|
|
4968
5228
|
function clearLog(chatId) {
|
|
4969
5229
|
markMessageLogSummarized(chatId);
|
|
@@ -4977,7 +5237,7 @@ function getLastMessageTimestamp(chatId) {
|
|
|
4977
5237
|
const rows = getUnsummarizedLog(chatId);
|
|
4978
5238
|
if (rows.length === 0) return null;
|
|
4979
5239
|
const last = rows[rows.length - 1];
|
|
4980
|
-
return (
|
|
5240
|
+
return parseDbTimestamp(last.created_at);
|
|
4981
5241
|
}
|
|
4982
5242
|
function getCachedLog(chatId) {
|
|
4983
5243
|
return cache.get(chatId) ?? [];
|
|
@@ -4990,7 +5250,7 @@ var init_session_log = __esm({
|
|
|
4990
5250
|
"src/memory/session-log.ts"() {
|
|
4991
5251
|
"use strict";
|
|
4992
5252
|
init_store5();
|
|
4993
|
-
CACHE_SIZE =
|
|
5253
|
+
CACHE_SIZE = 50;
|
|
4994
5254
|
cache = /* @__PURE__ */ new Map();
|
|
4995
5255
|
}
|
|
4996
5256
|
});
|
|
@@ -5005,21 +5265,27 @@ __export(summarize_exports, {
|
|
|
5005
5265
|
});
|
|
5006
5266
|
import { spawn } from "child_process";
|
|
5007
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
|
+
}
|
|
5008
5272
|
function buildTranscript(entries) {
|
|
5009
5273
|
const lines = [];
|
|
5010
5274
|
let totalLen = 0;
|
|
5011
|
-
for (
|
|
5275
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
5276
|
+
const e = entries[i];
|
|
5012
5277
|
const limit = e.role === "user" ? USER_MSG_LIMIT : AGENT_MSG_LIMIT;
|
|
5013
5278
|
const label2 = e.role === "user" ? "User" : "Agent";
|
|
5014
5279
|
const text = e.text.length > limit ? e.text.slice(0, limit) + "\n[...truncated]" : e.text;
|
|
5015
5280
|
const line = `[${label2}] ${text}`;
|
|
5016
|
-
if (totalLen + line.length >
|
|
5281
|
+
if (totalLen + line.length > getTranscriptCap()) {
|
|
5017
5282
|
lines.push("[...earlier messages truncated for length]");
|
|
5018
5283
|
break;
|
|
5019
5284
|
}
|
|
5020
5285
|
lines.push(line);
|
|
5021
5286
|
totalLen += line.length;
|
|
5022
5287
|
}
|
|
5288
|
+
lines.reverse();
|
|
5023
5289
|
return lines.join("\n\n");
|
|
5024
5290
|
}
|
|
5025
5291
|
async function attemptSummarize(chatId, adapter, model2, entries) {
|
|
@@ -5049,11 +5315,12 @@ ${transcript}`;
|
|
|
5049
5315
|
});
|
|
5050
5316
|
proc.stderr?.resume();
|
|
5051
5317
|
const rl2 = createInterface({ input: proc.stdout });
|
|
5318
|
+
let killTimer;
|
|
5052
5319
|
const timeout = setTimeout(() => {
|
|
5053
5320
|
warn(`[summarize] Timeout (${adapter.id}:${model2}) for chat ${chatId}`);
|
|
5054
5321
|
rl2.close();
|
|
5055
5322
|
proc.kill("SIGTERM");
|
|
5056
|
-
setTimeout(() => proc.kill("SIGKILL"), 2e3);
|
|
5323
|
+
killTimer = setTimeout(() => proc.kill("SIGKILL"), 2e3);
|
|
5057
5324
|
}, SUMMARIZE_TIMEOUT_MS);
|
|
5058
5325
|
rl2.on("line", (line) => {
|
|
5059
5326
|
if (!line.trim()) return;
|
|
@@ -5065,7 +5332,7 @@ ${transcript}`;
|
|
|
5065
5332
|
}
|
|
5066
5333
|
const events = adapter.parseLine(msg);
|
|
5067
5334
|
for (const ev of events) {
|
|
5068
|
-
if (ev.type === "text" && ev.text) accumulatedText
|
|
5335
|
+
if (ev.type === "text" && ev.text) accumulatedText = appendTextChunk(accumulatedText, ev.text);
|
|
5069
5336
|
if (ev.type === "usage" && ev.usage) {
|
|
5070
5337
|
inputTokens += ev.usage.input;
|
|
5071
5338
|
outputTokens += ev.usage.output;
|
|
@@ -5085,13 +5352,23 @@ ${transcript}`;
|
|
|
5085
5352
|
}
|
|
5086
5353
|
}
|
|
5087
5354
|
});
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
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
|
+
}
|
|
5091
5364
|
});
|
|
5092
5365
|
proc.on("close", () => {
|
|
5093
|
-
|
|
5094
|
-
|
|
5366
|
+
if (!settled) {
|
|
5367
|
+
settled = true;
|
|
5368
|
+
clearTimeout(timeout);
|
|
5369
|
+
clearTimeout(killTimer);
|
|
5370
|
+
resolve();
|
|
5371
|
+
}
|
|
5095
5372
|
});
|
|
5096
5373
|
});
|
|
5097
5374
|
if (inputTokens + outputTokens > 0) {
|
|
@@ -5147,66 +5424,84 @@ async function extractAndLogSignals(rawText, chatId, adapterId, model2) {
|
|
|
5147
5424
|
}
|
|
5148
5425
|
}
|
|
5149
5426
|
async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend, clearLogAfter = true) {
|
|
5150
|
-
const
|
|
5151
|
-
if (
|
|
5152
|
-
|
|
5427
|
+
const existing = summarizationLocks.get(chatId);
|
|
5428
|
+
if (existing) {
|
|
5429
|
+
warn(`[summarize] Already summarizing ${chatId}, skipping concurrent call`);
|
|
5430
|
+
await existing;
|
|
5153
5431
|
return false;
|
|
5154
5432
|
}
|
|
5155
|
-
const
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
const model2 = config2.model ?? adapter.summarizerModel;
|
|
5167
|
-
const key = `${adapter.id}:${model2}`;
|
|
5168
|
-
tried.add(key);
|
|
5169
|
-
const result = await attemptSummarize(chatId, adapter, model2, entries);
|
|
5170
|
-
if (result.success) {
|
|
5171
|
-
await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
|
|
5172
|
-
if (clearLogAfter) clearLog(chatId);
|
|
5173
|
-
return true;
|
|
5174
|
-
}
|
|
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}`);
|
|
5175
5444
|
}
|
|
5176
|
-
} catch {
|
|
5177
|
-
}
|
|
5178
|
-
if (targetBackendId) {
|
|
5179
5445
|
try {
|
|
5180
|
-
const
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
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}`;
|
|
5184
5451
|
tried.add(key);
|
|
5185
|
-
const result = await attemptSummarize(chatId,
|
|
5452
|
+
const result = await attemptSummarize(chatId, adapter, model2, entries);
|
|
5186
5453
|
if (result.success) {
|
|
5187
|
-
await extractAndLogSignals(result.rawText, chatId,
|
|
5454
|
+
await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
|
|
5188
5455
|
if (clearLogAfter) clearLog(chatId);
|
|
5189
5456
|
return true;
|
|
5190
5457
|
}
|
|
5191
5458
|
}
|
|
5192
5459
|
} catch {
|
|
5193
5460
|
}
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
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
|
+
}
|
|
5205
5493
|
}
|
|
5206
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);
|
|
5207
5504
|
}
|
|
5208
|
-
warn(`[summarize] All fallback attempts failed for chat ${chatId} \u2014 raw log preserved`);
|
|
5209
|
-
return false;
|
|
5210
5505
|
}
|
|
5211
5506
|
async function summarizeSession(chatId, clearLogAfter = true) {
|
|
5212
5507
|
return summarizeWithFallbackChain(chatId, void 0, void 0, clearLogAfter);
|
|
@@ -5227,7 +5522,7 @@ async function summarizeAllPending() {
|
|
|
5227
5522
|
await summarizeSession(chatId);
|
|
5228
5523
|
}
|
|
5229
5524
|
}
|
|
5230
|
-
var MIN_PAIRS, USER_MSG_LIMIT, AGENT_MSG_LIMIT,
|
|
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;
|
|
5231
5526
|
var init_summarize = __esm({
|
|
5232
5527
|
"src/memory/summarize.ts"() {
|
|
5233
5528
|
"use strict";
|
|
@@ -5235,10 +5530,13 @@ var init_summarize = __esm({
|
|
|
5235
5530
|
init_store5();
|
|
5236
5531
|
init_session_log();
|
|
5237
5532
|
init_backends();
|
|
5533
|
+
init_text_utils();
|
|
5238
5534
|
MIN_PAIRS = 2;
|
|
5535
|
+
summarizationLocks = /* @__PURE__ */ new Map();
|
|
5239
5536
|
USER_MSG_LIMIT = 4e3;
|
|
5240
5537
|
AGENT_MSG_LIMIT = 6e3;
|
|
5241
|
-
|
|
5538
|
+
TRANSCRIPT_CAP_DEFAULT = 1e5;
|
|
5539
|
+
TRANSCRIPT_CAP_FLASH = 5e4;
|
|
5242
5540
|
SUMMARIZE_TIMEOUT_MS = 6e4;
|
|
5243
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.
|
|
5244
5542
|
|
|
@@ -5286,7 +5584,7 @@ Be specific. The new assistant has no prior context. Cover any domain \u2014 per
|
|
|
5286
5584
|
// src/gemini/quota.ts
|
|
5287
5585
|
function classifyGeminiQuota(errorText) {
|
|
5288
5586
|
for (const p of CAPACITY_PATTERNS) {
|
|
5289
|
-
if (p.test(errorText)) return "
|
|
5587
|
+
if (p.test(errorText)) return "capacity";
|
|
5290
5588
|
}
|
|
5291
5589
|
for (const p of RATE_LIMITED_PATTERNS) {
|
|
5292
5590
|
if (p.test(errorText)) return "rate_limited";
|
|
@@ -5317,8 +5615,9 @@ var init_quota = __esm({
|
|
|
5317
5615
|
];
|
|
5318
5616
|
DAILY_QUOTA_PATTERNS = [
|
|
5319
5617
|
/quota exceeded.*per day/i,
|
|
5320
|
-
|
|
5321
|
-
|
|
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,
|
|
5322
5621
|
/RESOURCE_EXHAUSTED/,
|
|
5323
5622
|
/exhausted.*quota/i
|
|
5324
5623
|
];
|
|
@@ -5425,9 +5724,15 @@ import { spawn as spawn2 } from "child_process";
|
|
|
5425
5724
|
import { createInterface as createInterface2 } from "readline";
|
|
5426
5725
|
import { readFileSync as readFileSync4 } from "fs";
|
|
5427
5726
|
function stripFrontmatter(text) {
|
|
5428
|
-
const
|
|
5429
|
-
if (
|
|
5430
|
-
|
|
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;
|
|
5431
5736
|
}
|
|
5432
5737
|
function buildAgentPrompt(opts, runnerSkillPath) {
|
|
5433
5738
|
const parts = [];
|
|
@@ -5455,7 +5760,11 @@ function buildSpawnEnv(runner, isSubAgent = false) {
|
|
|
5455
5760
|
const runnerEnv = runner.getEnv();
|
|
5456
5761
|
const env = { ...base, ...runnerEnv };
|
|
5457
5762
|
if (isSubAgent) {
|
|
5458
|
-
for (const key of SENSITIVE_ENV_KEYS)
|
|
5763
|
+
for (const key of SENSITIVE_ENV_KEYS) {
|
|
5764
|
+
if (!(key in runnerEnv)) {
|
|
5765
|
+
delete env[key];
|
|
5766
|
+
}
|
|
5767
|
+
}
|
|
5459
5768
|
}
|
|
5460
5769
|
return env;
|
|
5461
5770
|
}
|
|
@@ -5537,7 +5846,11 @@ var init_spawn = __esm({
|
|
|
5537
5846
|
"ELEVENLABS_API_KEY",
|
|
5538
5847
|
"DASHBOARD_TOKEN",
|
|
5539
5848
|
"ALLOWED_CHAT_ID",
|
|
5540
|
-
"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"
|
|
5541
5854
|
];
|
|
5542
5855
|
}
|
|
5543
5856
|
});
|
|
@@ -5547,7 +5860,7 @@ var MAX_CONCURRENT_AGENTS, IDLE_THRESHOLD_MS, DEFAULT_MAX_RUNTIME_MS, BUDGET_WAR
|
|
|
5547
5860
|
var init_types = __esm({
|
|
5548
5861
|
"src/agents/types.ts"() {
|
|
5549
5862
|
"use strict";
|
|
5550
|
-
MAX_CONCURRENT_AGENTS = 4;
|
|
5863
|
+
MAX_CONCURRENT_AGENTS = parseInt(process.env.CC_CLAW_MAX_AGENTS ?? "4", 10);
|
|
5551
5864
|
IDLE_THRESHOLD_MS = 6e4;
|
|
5552
5865
|
DEFAULT_MAX_RUNTIME_MS = 6e5;
|
|
5553
5866
|
BUDGET_WARNING_PERCENT = 0.8;
|
|
@@ -5568,6 +5881,9 @@ function checkBudget(db3, orchestrationId) {
|
|
|
5568
5881
|
if (budgetLimit === null || budgetLimit === void 0) {
|
|
5569
5882
|
return { totalCost, budgetLimit: null, percentUsed: null, exceeded: false, warning: false };
|
|
5570
5883
|
}
|
|
5884
|
+
if (budgetLimit <= 0) {
|
|
5885
|
+
return { totalCost, budgetLimit, percentUsed: 100, exceeded: true, warning: true };
|
|
5886
|
+
}
|
|
5571
5887
|
const percentUsed = totalCost / budgetLimit * 100;
|
|
5572
5888
|
const exceeded = totalCost >= budgetLimit;
|
|
5573
5889
|
const warning4 = percentUsed >= BUDGET_WARNING_PERCENT * 100;
|
|
@@ -5597,18 +5913,40 @@ async function discoverExistingMcps(runner) {
|
|
|
5597
5913
|
encoding: "utf-8",
|
|
5598
5914
|
env: runner.getEnv(),
|
|
5599
5915
|
cwd: homedir3(),
|
|
5600
|
-
|
|
5916
|
+
// Audit O20: Reduced from 30s — mcp list should be fast
|
|
5917
|
+
timeout: 1e4
|
|
5601
5918
|
});
|
|
5602
5919
|
const stdout = typeof result === "string" ? result : Array.isArray(result) ? result[0] : result?.stdout ?? null;
|
|
5603
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
|
+
];
|
|
5604
5939
|
const seen = /* @__PURE__ */ new Set();
|
|
5605
5940
|
for (const line of stdout.split("\n")) {
|
|
5606
5941
|
const trimmed = line.trim();
|
|
5607
5942
|
if (!trimmed || SKIP_LINE.test(trimmed)) continue;
|
|
5608
5943
|
const cleaned = trimmed.replace(/^[✓✗•\-●]\s*/, "");
|
|
5609
|
-
const
|
|
5610
|
-
|
|
5611
|
-
seen.
|
|
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
|
+
}
|
|
5612
5950
|
}
|
|
5613
5951
|
}
|
|
5614
5952
|
return [...seen];
|
|
@@ -5621,31 +5959,47 @@ function diffMcps(needed, existing) {
|
|
|
5621
5959
|
return needed.filter((name) => !existingSet.has(name.toLowerCase()));
|
|
5622
5960
|
}
|
|
5623
5961
|
function defToConfig(def) {
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
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
|
+
}
|
|
5633
5977
|
}
|
|
5634
5978
|
async function injectMcps(runner, mcpNames, db3, scope) {
|
|
5635
5979
|
const added = [];
|
|
5636
5980
|
const exe = runner.getExecutablePath();
|
|
5637
5981
|
const runnerId = runner.id;
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
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);
|
|
5649
6003
|
}
|
|
5650
6004
|
}
|
|
5651
6005
|
return added;
|
|
@@ -5658,9 +6012,11 @@ async function cleanupMcps(runner, mcps2, db3, scope) {
|
|
|
5658
6012
|
const removeCmd = runner.getMcpRemoveCommand(name);
|
|
5659
6013
|
const args = removeCmd.slice(1);
|
|
5660
6014
|
await execFileAsync(exe, args, { encoding: "utf-8" });
|
|
6015
|
+
removePropagation(db3, name, runnerId, scope);
|
|
5661
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`);
|
|
5662
6019
|
}
|
|
5663
|
-
removePropagation(db3, name, runnerId, scope);
|
|
5664
6020
|
}
|
|
5665
6021
|
}
|
|
5666
6022
|
var execFileAsync, SKIP_LINE;
|
|
@@ -5668,6 +6024,7 @@ var init_propagate = __esm({
|
|
|
5668
6024
|
"src/mcps/propagate.ts"() {
|
|
5669
6025
|
"use strict";
|
|
5670
6026
|
init_store2();
|
|
6027
|
+
init_log();
|
|
5671
6028
|
execFileAsync = promisify(execFile);
|
|
5672
6029
|
SKIP_LINE = /^(Checking|Configured|No MCP|Try |──)/i;
|
|
5673
6030
|
}
|
|
@@ -5939,7 +6296,7 @@ var init_agent_log = __esm({
|
|
|
5939
6296
|
import { existsSync as existsSync10 } from "fs";
|
|
5940
6297
|
async function withRunnerLock(runnerId, fn) {
|
|
5941
6298
|
const prev = runnerLocks.get(runnerId) ?? Promise.resolve();
|
|
5942
|
-
const next = prev.then(fn, fn);
|
|
6299
|
+
const next = prev.then(fn, () => fn());
|
|
5943
6300
|
runnerLocks.set(runnerId, next.then(() => {
|
|
5944
6301
|
}, () => {
|
|
5945
6302
|
}));
|
|
@@ -6104,12 +6461,13 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6104
6461
|
warn(`[orchestrator] MCP injection failed for agent ${agentId}:`, err);
|
|
6105
6462
|
}
|
|
6106
6463
|
}
|
|
6464
|
+
const dashboardEnabled = process.env.DASHBOARD_ENABLED === "1";
|
|
6107
6465
|
let mcpExtraArgs = [];
|
|
6108
6466
|
let orchestratorMcpName = "";
|
|
6109
|
-
if (
|
|
6467
|
+
if (!dashboardEnabled) {
|
|
6110
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.)`);
|
|
6111
6469
|
}
|
|
6112
|
-
if (
|
|
6470
|
+
if (dashboardEnabled) {
|
|
6113
6471
|
try {
|
|
6114
6472
|
const { createSubAgentToken: createSubAgentToken2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
6115
6473
|
const scopedToken = createSubAgentToken2(agentId);
|
|
@@ -6121,10 +6479,12 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6121
6479
|
} else {
|
|
6122
6480
|
await withRunnerLock(runner.id, async () => {
|
|
6123
6481
|
const addCmd = runner.getMcpAddCommand(mcpConfig);
|
|
6124
|
-
const {
|
|
6482
|
+
const { execFile: execFile5 } = await import("child_process");
|
|
6483
|
+
const { promisify: promisify3 } = await import("util");
|
|
6484
|
+
const execFileAsync3 = promisify3(execFile5);
|
|
6125
6485
|
const exe = addCmd[0];
|
|
6126
6486
|
const args = addCmd.slice(1);
|
|
6127
|
-
|
|
6487
|
+
await execFileAsync3(exe, args, { encoding: "utf-8", timeout: 15e3, env: runner.getEnv() });
|
|
6128
6488
|
});
|
|
6129
6489
|
mcpsAdded = [...mcpsAdded, orchestratorMcpName];
|
|
6130
6490
|
updateAgentMcpsAdded(db3, agentId, mcpsAdded);
|
|
@@ -6139,6 +6499,7 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6139
6499
|
}
|
|
6140
6500
|
updateAgentStatus(db3, agentId, "starting");
|
|
6141
6501
|
const stderrChunks = [];
|
|
6502
|
+
const MAX_STDERR_CHUNKS = 50;
|
|
6142
6503
|
const rawStdoutLines = [];
|
|
6143
6504
|
let gotResult = false;
|
|
6144
6505
|
const startedAt = Date.now();
|
|
@@ -6196,7 +6557,9 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6196
6557
|
);
|
|
6197
6558
|
startNextQueued(chatId);
|
|
6198
6559
|
},
|
|
6199
|
-
onStderr: (chunk) =>
|
|
6560
|
+
onStderr: (chunk) => {
|
|
6561
|
+
if (stderrChunks.length < MAX_STDERR_CHUNKS) stderrChunks.push(chunk);
|
|
6562
|
+
}
|
|
6200
6563
|
});
|
|
6201
6564
|
if (child.pid) {
|
|
6202
6565
|
updateAgentPid(db3, agentId, child.pid);
|
|
@@ -6223,6 +6586,9 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6223
6586
|
agentId
|
|
6224
6587
|
);
|
|
6225
6588
|
child.on("close", (code, signal) => {
|
|
6589
|
+
clearTimeout(timeoutTimers.get(agentId));
|
|
6590
|
+
timeoutTimers.delete(agentId);
|
|
6591
|
+
activeProcesses.delete(agentId);
|
|
6226
6592
|
if (gotResult) return;
|
|
6227
6593
|
const agent = getAgent(db3, agentId);
|
|
6228
6594
|
if (!agent || agent.status === "completed" || agent.status === "failed") return;
|
|
@@ -6250,10 +6616,10 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6250
6616
|
}
|
|
6251
6617
|
let exitCode = code ?? 1;
|
|
6252
6618
|
let exitCodeForced = false;
|
|
6253
|
-
if (exitCode === 0) {
|
|
6619
|
+
if (exitCode === 0 && durationMs < 5e3) {
|
|
6254
6620
|
exitCode = 1;
|
|
6255
6621
|
exitCodeForced = true;
|
|
6256
|
-
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)`);
|
|
6257
6623
|
}
|
|
6258
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)}` : ""}`;
|
|
6259
6625
|
updateAgentResult(db3, agentId, {
|
|
@@ -6284,11 +6650,11 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6284
6650
|
if (crashedAgent) {
|
|
6285
6651
|
const mcpsCrashed = crashedAgent.mcpsAdded ? JSON.parse(crashedAgent.mcpsAdded) : [];
|
|
6286
6652
|
if (mcpsCrashed.length > 0) {
|
|
6287
|
-
const
|
|
6288
|
-
if (
|
|
6289
|
-
const cleanupFn = () => cleanupMcps(
|
|
6290
|
-
if (
|
|
6291
|
-
withRunnerLock(
|
|
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) => {
|
|
6292
6658
|
warn(`[orchestrator] MCP cleanup failed for crashed agent ${agentId.slice(0, 8)}:`, err);
|
|
6293
6659
|
});
|
|
6294
6660
|
} else {
|
|
@@ -6456,22 +6822,32 @@ async function handleAgentComplete(agentId, chatId, resultText, usage2, mcpsAdde
|
|
|
6456
6822
|
}
|
|
6457
6823
|
function startNextQueued(chatId) {
|
|
6458
6824
|
const db3 = getDb();
|
|
6459
|
-
const orch = getActiveOrchestration(db3, chatId);
|
|
6460
|
-
if (!orch) return;
|
|
6461
6825
|
const runningCount = getRunningAgentCount();
|
|
6462
6826
|
if (runningCount >= MAX_CONCURRENT_AGENTS) return;
|
|
6463
|
-
const
|
|
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
|
+
}
|
|
6464
6840
|
if (!next) return;
|
|
6465
6841
|
const agent = getAgent(db3, next.id);
|
|
6466
6842
|
if (!agent) return;
|
|
6467
|
-
startAgent(next.id,
|
|
6843
|
+
startAgent(next.id, resolvedChatId, {
|
|
6468
6844
|
runner: agent.runnerId,
|
|
6469
6845
|
task: agent.task ?? "",
|
|
6470
6846
|
name: agent.name ?? void 0,
|
|
6471
6847
|
description: agent.description ?? void 0,
|
|
6472
6848
|
model: agent.model ?? void 0,
|
|
6473
6849
|
skills: JSON.parse(agent.skills),
|
|
6474
|
-
permMode: agent.permMode === "inherit" ? getMode(
|
|
6850
|
+
permMode: agent.permMode === "inherit" ? getMode(resolvedChatId) : agent.permMode,
|
|
6475
6851
|
role: agent.role ?? "worker",
|
|
6476
6852
|
persona: agent.persona ?? void 0,
|
|
6477
6853
|
maxRuntimeMs: agent.maxRuntimeMs,
|
|
@@ -6532,6 +6908,10 @@ function cancelAgent(agentId, reason = "user_cancelled") {
|
|
|
6532
6908
|
}).catch(() => {
|
|
6533
6909
|
});
|
|
6534
6910
|
log(`[orchestrator] Agent ${agentId} cancelled (reason: ${reason})`);
|
|
6911
|
+
const cancelOrch = getOrchestration(db3, agent.orchestrationId);
|
|
6912
|
+
if (cancelOrch) {
|
|
6913
|
+
startNextQueued(cancelOrch.chatId);
|
|
6914
|
+
}
|
|
6535
6915
|
return true;
|
|
6536
6916
|
}
|
|
6537
6917
|
function cancelAllAgents(chatId, reason = "user_cancelled") {
|
|
@@ -6636,8 +7016,10 @@ function registerMcp(db3, opts) {
|
|
|
6636
7016
|
propagateChange(db3, opts.name, "add").catch(() => {
|
|
6637
7017
|
});
|
|
6638
7018
|
}
|
|
6639
|
-
function unregisterMcp(db3, name) {
|
|
6640
|
-
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);
|
|
6641
7023
|
});
|
|
6642
7024
|
removeMcpServer(db3, name);
|
|
6643
7025
|
}
|
|
@@ -6985,6 +7367,12 @@ import { createInterface as createInterface3 } from "readline";
|
|
|
6985
7367
|
import { readFileSync as readFileSync6, existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
6986
7368
|
import { join as join11 } from "path";
|
|
6987
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
|
+
}
|
|
6988
7376
|
function discoverReflectionTargets() {
|
|
6989
7377
|
const ccClawHome = join11(homedir4(), ".cc-claw");
|
|
6990
7378
|
const targets = [];
|
|
@@ -6998,7 +7386,7 @@ function discoverReflectionTargets() {
|
|
|
6998
7386
|
if (!existsSync11(skillFile)) continue;
|
|
6999
7387
|
let desc = "skill";
|
|
7000
7388
|
try {
|
|
7001
|
-
const content = readFileSync6(skillFile, "utf-8")
|
|
7389
|
+
const content = readFileSync6(skillFile, "utf-8");
|
|
7002
7390
|
const descMatch = content.match(/description:\s*["']?([^"'\n]+)/);
|
|
7003
7391
|
if (descMatch) desc = descMatch[1].trim().slice(0, 80);
|
|
7004
7392
|
} catch {
|
|
@@ -7066,6 +7454,13 @@ ${categoryList}`);
|
|
|
7066
7454
|
sections.push(soulMd || "(empty)");
|
|
7067
7455
|
sections.push("[Current USER.md]");
|
|
7068
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
|
+
}
|
|
7069
7464
|
sections.push("[Previously Applied Insights]");
|
|
7070
7465
|
if (appliedInsights.length === 0) {
|
|
7071
7466
|
sections.push("(none)");
|
|
@@ -7098,13 +7493,13 @@ ${categoryList}`);
|
|
|
7098
7493
|
Available targets:
|
|
7099
7494
|
${targetList.map((t) => `- ${t}`).join("\n")}
|
|
7100
7495
|
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
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.`);
|
|
7108
7503
|
const targetOptions = targetList.map((t) => t.split(" \u2014 ")[0]).join(" | ");
|
|
7109
7504
|
sections.push(`[Output Format]
|
|
7110
7505
|
For each insight, output EXACTLY this format. Separate multiple insights with "---" on its own line.
|
|
@@ -7278,7 +7673,7 @@ async function spawnAnalysis(adapter, model2, prompt, timeoutMs = ANALYSIS_TIMEO
|
|
|
7278
7673
|
}
|
|
7279
7674
|
const events = adapter.parseLine(msg);
|
|
7280
7675
|
for (const ev of events) {
|
|
7281
|
-
if (ev.type === "text" && ev.text) accumulatedText
|
|
7676
|
+
if (ev.type === "text" && ev.text) accumulatedText = appendTextChunk(accumulatedText, ev.text);
|
|
7282
7677
|
if (ev.type === "result") {
|
|
7283
7678
|
resultText = ev.resultText || accumulatedText;
|
|
7284
7679
|
if (adapter.shouldKillOnResult()) {
|
|
@@ -7301,6 +7696,18 @@ async function spawnAnalysis(adapter, model2, prompt, timeoutMs = ANALYSIS_TIMEO
|
|
|
7301
7696
|
return resultText;
|
|
7302
7697
|
}
|
|
7303
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) {
|
|
7304
7711
|
const db3 = getDb();
|
|
7305
7712
|
const { force = false } = opts;
|
|
7306
7713
|
if (!force) {
|
|
@@ -7326,13 +7733,37 @@ async function runAnalysis(chatId, opts = {}) {
|
|
|
7326
7733
|
const rejected = getRejectedInsights(db3, chatId);
|
|
7327
7734
|
const codebaseEnabled = process.env.REFLECTION_CODEBASE_RECOMMENDATIONS === "1";
|
|
7328
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
|
+
}
|
|
7329
7761
|
const prompt = buildAnalysisPrompt({
|
|
7330
|
-
signals: signals.map((s) =>
|
|
7331
|
-
|
|
7332
|
-
trigger: s.trigger,
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
})),
|
|
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
|
|
7336
7767
|
conversations,
|
|
7337
7768
|
soulMd,
|
|
7338
7769
|
userMd,
|
|
@@ -7348,7 +7779,8 @@ async function runAnalysis(chatId, opts = {}) {
|
|
|
7348
7779
|
category: i.category
|
|
7349
7780
|
})),
|
|
7350
7781
|
codebaseEnabled,
|
|
7351
|
-
availableTargets
|
|
7782
|
+
availableTargets,
|
|
7783
|
+
skillContents
|
|
7352
7784
|
});
|
|
7353
7785
|
const resolved = resolveReflectionAdapter(chatId);
|
|
7354
7786
|
if (!resolved) {
|
|
@@ -7370,6 +7802,7 @@ async function runAnalysis(chatId, opts = {}) {
|
|
|
7370
7802
|
return [];
|
|
7371
7803
|
}
|
|
7372
7804
|
const parsed = parseAnalysisOutput(rawOutput);
|
|
7805
|
+
const hasUsableOutput = parsed.length > 0;
|
|
7373
7806
|
const signalIdStr = signals.length > 0 ? signals.map((s) => s.id).join(",") : "manual";
|
|
7374
7807
|
for (const insight of parsed) {
|
|
7375
7808
|
let conflictsWithId = null;
|
|
@@ -7392,7 +7825,11 @@ async function runAnalysis(chatId, opts = {}) {
|
|
|
7392
7825
|
model: model2
|
|
7393
7826
|
});
|
|
7394
7827
|
}
|
|
7395
|
-
if (signals.length > 0)
|
|
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
|
+
}
|
|
7396
7833
|
aggregateDailyMetrics(db3, chatId);
|
|
7397
7834
|
logActivity(db3, {
|
|
7398
7835
|
chatId,
|
|
@@ -7473,11 +7910,12 @@ async function runNightlyReflection(opts = {}) {
|
|
|
7473
7910
|
log(`[reflection] Nightly reflection complete: ${results.length} chat(s) produced insights`);
|
|
7474
7911
|
return { results };
|
|
7475
7912
|
}
|
|
7476
|
-
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;
|
|
7477
7914
|
var init_analyze = __esm({
|
|
7478
7915
|
"src/reflection/analyze.ts"() {
|
|
7479
7916
|
"use strict";
|
|
7480
7917
|
init_log();
|
|
7918
|
+
init_text_utils();
|
|
7481
7919
|
init_paths();
|
|
7482
7920
|
init_store5();
|
|
7483
7921
|
init_backends();
|
|
@@ -7487,6 +7925,8 @@ var init_analyze = __esm({
|
|
|
7487
7925
|
ANALYSIS_TIMEOUT_MS = 12e4;
|
|
7488
7926
|
CONVERSATIONS_CAP = 4e3;
|
|
7489
7927
|
MAX_INSIGHTS = 5;
|
|
7928
|
+
SIGNAL_DECAY_HALF_LIFE_DAYS = 7;
|
|
7929
|
+
SIGNAL_DECAY_MIN_CONFIDENCE = 0.1;
|
|
7490
7930
|
VALID_CATEGORIES = [
|
|
7491
7931
|
"behavior",
|
|
7492
7932
|
"preference",
|
|
@@ -7502,6 +7942,7 @@ var init_analyze = __esm({
|
|
|
7502
7942
|
"domain",
|
|
7503
7943
|
"error_pattern"
|
|
7504
7944
|
];
|
|
7945
|
+
analysisLocks = /* @__PURE__ */ new Set();
|
|
7505
7946
|
}
|
|
7506
7947
|
});
|
|
7507
7948
|
|
|
@@ -7790,12 +8231,12 @@ import { randomBytes } from "crypto";
|
|
|
7790
8231
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync13 } from "fs";
|
|
7791
8232
|
function createSubAgentToken(agentId) {
|
|
7792
8233
|
const token = `sub:${agentId.slice(0, 8)}:${randomBytes(16).toString("hex")}`;
|
|
7793
|
-
subAgentTokens.set(token, agentId);
|
|
8234
|
+
subAgentTokens.set(token, { agentId, expiresAt: Date.now() + SUB_AGENT_TOKEN_TTL_MS });
|
|
7794
8235
|
return token;
|
|
7795
8236
|
}
|
|
7796
8237
|
function revokeSubAgentToken(agentId) {
|
|
7797
|
-
for (const [token,
|
|
7798
|
-
if (
|
|
8238
|
+
for (const [token, entry] of subAgentTokens) {
|
|
8239
|
+
if (entry.agentId === agentId) {
|
|
7799
8240
|
subAgentTokens.delete(token);
|
|
7800
8241
|
break;
|
|
7801
8242
|
}
|
|
@@ -7809,6 +8250,14 @@ function htmlResponse(res, html) {
|
|
|
7809
8250
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
7810
8251
|
res.end(html);
|
|
7811
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
|
+
}
|
|
7812
8261
|
function readBody(req) {
|
|
7813
8262
|
return new Promise((resolve, reject) => {
|
|
7814
8263
|
const chunks = [];
|
|
@@ -7831,22 +8280,31 @@ function startDashboard() {
|
|
|
7831
8280
|
const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
|
|
7832
8281
|
const authHeader = req.headers.authorization ?? "";
|
|
7833
8282
|
const bearerToken = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
7834
|
-
const
|
|
7835
|
-
const presentedToken = bearerToken || queryToken;
|
|
8283
|
+
const presentedToken = bearerToken;
|
|
7836
8284
|
const isMainToken = presentedToken === DASHBOARD_TOKEN;
|
|
7837
|
-
const
|
|
8285
|
+
const subEntry = subAgentTokens.get(presentedToken);
|
|
8286
|
+
const isSubAgentToken = !!subEntry && subEntry.expiresAt > Date.now();
|
|
7838
8287
|
if (!isMainToken && !isSubAgentToken) {
|
|
7839
8288
|
res.writeHead(401, { "Content-Type": "text/plain" });
|
|
7840
8289
|
res.end("Unauthorized");
|
|
7841
8290
|
return;
|
|
7842
8291
|
}
|
|
7843
|
-
const
|
|
7844
|
-
"/api/orchestrator/
|
|
7845
|
-
"/api/orchestrator/
|
|
7846
|
-
"/api/orchestrator/
|
|
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"
|
|
7847
8302
|
]);
|
|
7848
|
-
if (isSubAgentToken &&
|
|
7849
|
-
return jsonResponse(res, { error: "Forbidden: sub-agent tokens
|
|
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;
|
|
7850
8308
|
}
|
|
7851
8309
|
if (url.pathname === "/api/health") {
|
|
7852
8310
|
return jsonResponse(res, { status: "ok", version: VERSION, uptime: process.uptime() });
|
|
@@ -7941,8 +8399,8 @@ function startDashboard() {
|
|
|
7941
8399
|
if (url.pathname.startsWith("/api/mcps/") && url.pathname !== "/api/mcps/sync" && url.pathname !== "/api/mcps/import" && req.method === "DELETE") {
|
|
7942
8400
|
try {
|
|
7943
8401
|
const name = decodeURIComponent(url.pathname.slice("/api/mcps/".length));
|
|
7944
|
-
const {
|
|
7945
|
-
|
|
8402
|
+
const { unregisterMcp: unregisterMcp2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports2));
|
|
8403
|
+
await unregisterMcp2(getDb(), name);
|
|
7946
8404
|
return jsonResponse(res, { success: true });
|
|
7947
8405
|
} catch (err) {
|
|
7948
8406
|
return jsonResponse(res, { error: errorMessage(err) }, 400);
|
|
@@ -8013,6 +8471,7 @@ function startDashboard() {
|
|
|
8013
8471
|
if (url.pathname === "/api/orchestrator/send-message" && req.method === "POST") {
|
|
8014
8472
|
try {
|
|
8015
8473
|
const body = JSON.parse(await readBody(req));
|
|
8474
|
+
validateAgentIdentity(req, body);
|
|
8016
8475
|
const db3 = getDb();
|
|
8017
8476
|
const orchId = getOrCreateOrchestration(body.chatId);
|
|
8018
8477
|
const msgId = sendInboxMessage(db3, { orchestrationId: orchId, ...body.message });
|
|
@@ -8024,6 +8483,7 @@ function startDashboard() {
|
|
|
8024
8483
|
if (url.pathname === "/api/orchestrator/read-inbox" && req.method === "POST") {
|
|
8025
8484
|
try {
|
|
8026
8485
|
const body = JSON.parse(await readBody(req));
|
|
8486
|
+
validateAgentIdentity(req, body);
|
|
8027
8487
|
const db3 = getDb();
|
|
8028
8488
|
const orchId = getOrCreateOrchestration(body.chatId);
|
|
8029
8489
|
const messages = getUnreadMessages(db3, orchId, body.agentId);
|
|
@@ -8038,6 +8498,7 @@ function startDashboard() {
|
|
|
8038
8498
|
if (url.pathname === "/api/orchestrator/set-state" && req.method === "POST") {
|
|
8039
8499
|
try {
|
|
8040
8500
|
const body = JSON.parse(await readBody(req));
|
|
8501
|
+
validateAgentIdentity(req, body);
|
|
8041
8502
|
const db3 = getDb();
|
|
8042
8503
|
const orchId = getOrCreateOrchestration(body.chatId);
|
|
8043
8504
|
setState(db3, orchId, body.key, body.value, body.setBy);
|
|
@@ -8072,6 +8533,7 @@ function startDashboard() {
|
|
|
8072
8533
|
if (url.pathname === "/api/orchestrator/broadcast" && req.method === "POST") {
|
|
8073
8534
|
try {
|
|
8074
8535
|
const body = JSON.parse(await readBody(req));
|
|
8536
|
+
validateAgentIdentity(req, body);
|
|
8075
8537
|
const db3 = getDb();
|
|
8076
8538
|
const orchId = getOrCreateOrchestration(body.chatId);
|
|
8077
8539
|
const agents2 = listActiveAgents(db3).filter((a) => a.orchestrationId === orchId);
|
|
@@ -8234,11 +8696,17 @@ function startDashboard() {
|
|
|
8234
8696
|
"Cache-Control": "no-cache",
|
|
8235
8697
|
Connection: "keep-alive"
|
|
8236
8698
|
});
|
|
8699
|
+
let clientDisconnected = false;
|
|
8700
|
+
res.on("close", () => {
|
|
8701
|
+
clientDisconnected = true;
|
|
8702
|
+
});
|
|
8237
8703
|
const sendSSE = (event, data) => {
|
|
8238
|
-
res.
|
|
8704
|
+
if (!clientDisconnected && !res.writableEnded) {
|
|
8705
|
+
res.write(`event: ${event}
|
|
8239
8706
|
data: ${JSON.stringify(data)}
|
|
8240
8707
|
|
|
8241
8708
|
`);
|
|
8709
|
+
}
|
|
8242
8710
|
};
|
|
8243
8711
|
try {
|
|
8244
8712
|
const response = await askAgent2(chatId, body.message, {
|
|
@@ -8251,10 +8719,10 @@ data: ${JSON.stringify(data)}
|
|
|
8251
8719
|
});
|
|
8252
8720
|
if (response.usage) addUsage2(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, model2 ?? "unknown", void 0, response.usage.contextSize);
|
|
8253
8721
|
sendSSE("done", JSON.stringify({ text: response.text, usage: response.usage }));
|
|
8254
|
-
res.end();
|
|
8722
|
+
if (!res.writableEnded) res.end();
|
|
8255
8723
|
} catch (err) {
|
|
8256
8724
|
sendSSE("error", errorMessage(err));
|
|
8257
|
-
res.end();
|
|
8725
|
+
if (!res.writableEnded) res.end();
|
|
8258
8726
|
}
|
|
8259
8727
|
} else {
|
|
8260
8728
|
const response = await askAgent2(chatId, body.message, { cwd, model: model2, permMode: mode });
|
|
@@ -8505,6 +8973,75 @@ data: ${JSON.stringify(data)}
|
|
|
8505
8973
|
const result = await triggerJob2(id);
|
|
8506
8974
|
return jsonResponse(res, { success: true, result });
|
|
8507
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
|
+
}
|
|
8508
9045
|
return jsonResponse(res, { error: "Unknown cron action" }, 400);
|
|
8509
9046
|
} catch (err) {
|
|
8510
9047
|
return jsonResponse(res, { error: errorMessage(err) }, 400);
|
|
@@ -8637,7 +9174,7 @@ data: ${JSON.stringify(data)}
|
|
|
8637
9174
|
function getDashboardToken() {
|
|
8638
9175
|
return DASHBOARD_TOKEN;
|
|
8639
9176
|
}
|
|
8640
|
-
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;
|
|
8641
9178
|
var init_server = __esm({
|
|
8642
9179
|
"src/dashboard/server.ts"() {
|
|
8643
9180
|
"use strict";
|
|
@@ -8657,7 +9194,14 @@ var init_server = __esm({
|
|
|
8657
9194
|
init_store3();
|
|
8658
9195
|
PORT = parseInt(process.env.DASHBOARD_PORT ?? "3141", 10);
|
|
8659
9196
|
DASHBOARD_TOKEN = process.env.DASHBOARD_TOKEN || randomBytes(32).toString("hex");
|
|
9197
|
+
SUB_AGENT_TOKEN_TTL_MS = 36e5;
|
|
8660
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);
|
|
8661
9205
|
MAX_BODY_BYTES = 1048576;
|
|
8662
9206
|
DASHBOARD_HTML = `<!DOCTYPE html>
|
|
8663
9207
|
<html lang="en">
|
|
@@ -9034,7 +9578,8 @@ var init_detect = __esm({
|
|
|
9034
9578
|
init_store4();
|
|
9035
9579
|
init_store5();
|
|
9036
9580
|
CORRECTION_PATTERNS = [
|
|
9037
|
-
|
|
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,
|
|
9038
9583
|
/\bwrong\b/i,
|
|
9039
9584
|
/\bthat's not (right|what|correct)/i,
|
|
9040
9585
|
/\bi (meant|said)\b/i,
|
|
@@ -9076,6 +9621,7 @@ var init_detect = __esm({
|
|
|
9076
9621
|
// src/agent.ts
|
|
9077
9622
|
var agent_exports = {};
|
|
9078
9623
|
__export(agent_exports, {
|
|
9624
|
+
FIRST_RESPONSE_TIMEOUT_ERROR: () => FIRST_RESPONSE_TIMEOUT_ERROR,
|
|
9079
9625
|
askAgent: () => askAgent,
|
|
9080
9626
|
getInFlightMessage: () => getInFlightMessage,
|
|
9081
9627
|
isChatBusy: () => isChatBusy,
|
|
@@ -9094,7 +9640,8 @@ function getInFlightMessage(chatId) {
|
|
|
9094
9640
|
function killProcessGroup(proc, signal = "SIGTERM") {
|
|
9095
9641
|
try {
|
|
9096
9642
|
if (proc.pid) process.kill(-proc.pid, signal);
|
|
9097
|
-
} catch {
|
|
9643
|
+
} catch (err) {
|
|
9644
|
+
warn(`[agent] Group kill (-${proc.pid}) failed: ${err} \u2014 falling back to direct kill`);
|
|
9098
9645
|
try {
|
|
9099
9646
|
proc.kill(signal);
|
|
9100
9647
|
} catch {
|
|
@@ -9107,7 +9654,7 @@ function withChatLock(chatId, fn) {
|
|
|
9107
9654
|
if (isBlocked) {
|
|
9108
9655
|
warn(`[agent] Chat ${chatId} is busy \u2014 queuing request (cross-channel head-of-line blocking)`);
|
|
9109
9656
|
}
|
|
9110
|
-
const next = prev.then(fn, fn);
|
|
9657
|
+
const next = prev.then(fn, () => fn());
|
|
9111
9658
|
chatLocks.set(chatId, next.then(() => {
|
|
9112
9659
|
chatLocks.delete(chatId);
|
|
9113
9660
|
}, () => {
|
|
@@ -9170,6 +9717,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9170
9717
|
let sawToolEvents = false;
|
|
9171
9718
|
let sawResultEvent = false;
|
|
9172
9719
|
let toolTurnCount = 0;
|
|
9720
|
+
const loopDetector = new ToolLoopDetector();
|
|
9173
9721
|
const t0 = Date.now();
|
|
9174
9722
|
const elapsed = () => `${((Date.now() - t0) / 1e3).toFixed(1)}s`;
|
|
9175
9723
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
@@ -9177,11 +9725,28 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9177
9725
|
proc.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
|
|
9178
9726
|
const rl2 = createInterface4({ input: proc.stdout });
|
|
9179
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
|
+
}
|
|
9180
9741
|
rl2.on("line", (line) => {
|
|
9181
9742
|
if (!line.trim()) return;
|
|
9182
9743
|
if (firstLine) {
|
|
9183
9744
|
log(`[agent] First CLI message after ${elapsed()}`);
|
|
9184
9745
|
firstLine = false;
|
|
9746
|
+
if (firstResponseTimer) {
|
|
9747
|
+
clearTimeout(firstResponseTimer);
|
|
9748
|
+
firstResponseTimer = void 0;
|
|
9749
|
+
}
|
|
9185
9750
|
}
|
|
9186
9751
|
let msg;
|
|
9187
9752
|
try {
|
|
@@ -9202,7 +9767,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9202
9767
|
break;
|
|
9203
9768
|
case "text":
|
|
9204
9769
|
if (ev.text) {
|
|
9205
|
-
accumulatedText
|
|
9770
|
+
accumulatedText = appendTextChunk(accumulatedText, ev.text);
|
|
9206
9771
|
if (opts?.onStream) opts.onStream(ev.text);
|
|
9207
9772
|
}
|
|
9208
9773
|
break;
|
|
@@ -9215,10 +9780,19 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9215
9780
|
error("[agent] tool action error:", err);
|
|
9216
9781
|
});
|
|
9217
9782
|
}
|
|
9218
|
-
if (
|
|
9783
|
+
if (adapter.id !== "claude") {
|
|
9219
9784
|
toolTurnCount++;
|
|
9220
|
-
if (
|
|
9221
|
-
|
|
9785
|
+
if (ev.toolName) {
|
|
9786
|
+
const check = loopDetector.addCall(ev.toolName, ev.toolInput ?? {});
|
|
9787
|
+
if (check.isLoop) {
|
|
9788
|
+
warn(`[agent] Loop detected for ${adapter.id}: ${check.reason} \u2014 stopping`);
|
|
9789
|
+
killProcessGroup(proc, "SIGTERM");
|
|
9790
|
+
}
|
|
9791
|
+
}
|
|
9792
|
+
const effectiveCap = maxTurns ?? HARD_TURN_CAP;
|
|
9793
|
+
if (toolTurnCount >= effectiveCap) {
|
|
9794
|
+
const label2 = maxTurns ? `Turn limit ${maxTurns}` : `Hard cap ${HARD_TURN_CAP}`;
|
|
9795
|
+
warn(`[agent] ${label2} reached for ${adapter.id} \u2014 stopping`);
|
|
9222
9796
|
killProcessGroup(proc, "SIGTERM");
|
|
9223
9797
|
}
|
|
9224
9798
|
}
|
|
@@ -9269,6 +9843,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9269
9843
|
});
|
|
9270
9844
|
proc.on("error", (err) => {
|
|
9271
9845
|
clearTimeout(spawnTimeout);
|
|
9846
|
+
if (firstResponseTimer) clearTimeout(firstResponseTimer);
|
|
9272
9847
|
if (sigkillTimer) clearTimeout(sigkillTimer);
|
|
9273
9848
|
rl2.close();
|
|
9274
9849
|
cancelState.process = void 0;
|
|
@@ -9276,6 +9851,10 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9276
9851
|
});
|
|
9277
9852
|
proc.on("close", (code, signal) => {
|
|
9278
9853
|
clearTimeout(spawnTimeout);
|
|
9854
|
+
if (firstResponseTimer) {
|
|
9855
|
+
clearTimeout(firstResponseTimer);
|
|
9856
|
+
firstResponseTimer = void 0;
|
|
9857
|
+
}
|
|
9279
9858
|
if (sigkillTimer) {
|
|
9280
9859
|
clearTimeout(sigkillTimer);
|
|
9281
9860
|
sigkillTimer = void 0;
|
|
@@ -9290,6 +9869,12 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9290
9869
|
if (stderr) warn(`[agent] stderr: ${stderr.slice(0, 500)}`);
|
|
9291
9870
|
}
|
|
9292
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
|
+
}
|
|
9293
9878
|
let msg = `Spawn timeout after ${effectiveTimeout / 1e3}s`;
|
|
9294
9879
|
if (pendingTools.size > 0) {
|
|
9295
9880
|
const tools2 = Array.from(pendingTools.values()).map((t) => typeof t === "string" ? t : t.name).join(", ");
|
|
@@ -9311,11 +9896,12 @@ Partial output: ${accumulatedText.slice(-500)}`;
|
|
|
9311
9896
|
reject(new Error(`CLI exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}`));
|
|
9312
9897
|
return;
|
|
9313
9898
|
}
|
|
9314
|
-
|
|
9899
|
+
const cleanedResult = stripThinkingContent(resultText || accumulatedText);
|
|
9900
|
+
resolve({ resultText: cleanedResult, sessionId, input, output: output2, cacheRead, contextSize, sawToolEvents, sawResultEvent });
|
|
9315
9901
|
});
|
|
9316
9902
|
});
|
|
9317
9903
|
}
|
|
9318
|
-
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) {
|
|
9319
9905
|
const geminiAdapter = adapter;
|
|
9320
9906
|
const slots = getEligibleGeminiSlots(rotationMode);
|
|
9321
9907
|
if (slots.length === 0) {
|
|
@@ -9325,8 +9911,9 @@ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSe
|
|
|
9325
9911
|
const { env } = geminiAdapter.getEnvForSlot(parentChatId, void 0, rotationMode);
|
|
9326
9912
|
return await spawnQuery(adapter, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env });
|
|
9327
9913
|
}
|
|
9328
|
-
const maxAttempts =
|
|
9914
|
+
const maxAttempts = slots.length;
|
|
9329
9915
|
let lastError;
|
|
9916
|
+
let unknownRetried = false;
|
|
9330
9917
|
for (let i = 0; i < maxAttempts; i++) {
|
|
9331
9918
|
const { env, slot } = geminiAdapter.getEnvForSlot(chatId, void 0, rotationMode);
|
|
9332
9919
|
if (!slot) break;
|
|
@@ -9336,18 +9923,52 @@ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSe
|
|
|
9336
9923
|
const effectiveConfig = i === 0 ? configWithSession : baseConfig;
|
|
9337
9924
|
try {
|
|
9338
9925
|
const result = await spawnQuery(adapter, effectiveConfig, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env });
|
|
9339
|
-
const
|
|
9340
|
-
if (
|
|
9341
|
-
throw new Error(
|
|
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);
|
|
9342
9929
|
}
|
|
9343
9930
|
markSlotSuccess(slot.id);
|
|
9344
9931
|
return result;
|
|
9345
9932
|
} catch (err) {
|
|
9346
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
|
+
}
|
|
9347
9961
|
const quotaClass = classifyGeminiQuota(errMsg);
|
|
9348
9962
|
if (quotaClass === "rate_limited") {
|
|
9349
9963
|
throw err;
|
|
9350
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;
|
|
9351
9972
|
const effectiveClass = quotaClass === "unknown" ? "daily_quota" : quotaClass;
|
|
9352
9973
|
warn(`[agent:gemini-rotation] Slot ${slotLabel} exhausted (${effectiveClass}): ${errMsg.slice(0, 200)}`);
|
|
9353
9974
|
markSlotExhausted(slot.id, effectiveClass);
|
|
@@ -9385,7 +10006,7 @@ function askAgent(chatId, userMessage, opts) {
|
|
|
9385
10006
|
return withChatLock(chatId, () => askAgentImpl(chatId, userMessage, opts));
|
|
9386
10007
|
}
|
|
9387
10008
|
async function askAgentImpl(chatId, userMessage, opts) {
|
|
9388
|
-
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 ?? {};
|
|
9389
10010
|
const settingsChat = settingsSourceChatId ?? chatId;
|
|
9390
10011
|
const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(settingsChat);
|
|
9391
10012
|
const mode = permMode ?? getMode(settingsChat);
|
|
@@ -9437,9 +10058,36 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
9437
10058
|
try {
|
|
9438
10059
|
if (useGeminiRotation) {
|
|
9439
10060
|
const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
|
|
9440
|
-
|
|
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);
|
|
9441
10063
|
} else {
|
|
9442
|
-
|
|
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
|
+
}
|
|
9443
10091
|
}
|
|
9444
10092
|
const wasEmptyResponse = !result.resultText && !result.sawToolEvents && !result.sawResultEvent;
|
|
9445
10093
|
if (wasEmptyResponse && !cancelState.cancelled && existingSessionId) {
|
|
@@ -9448,7 +10096,8 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
9448
10096
|
clearSession(chatId);
|
|
9449
10097
|
if (useGeminiRotation) {
|
|
9450
10098
|
const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
|
|
9451
|
-
|
|
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);
|
|
9452
10101
|
} else {
|
|
9453
10102
|
result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
|
|
9454
10103
|
}
|
|
@@ -9519,15 +10168,18 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
|
|
|
9519
10168
|
if (!flag) return args;
|
|
9520
10169
|
return [...args, ...flag, mcpConfigPath];
|
|
9521
10170
|
}
|
|
9522
|
-
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;
|
|
9523
10172
|
var init_agent = __esm({
|
|
9524
10173
|
"src/agent.ts"() {
|
|
9525
10174
|
"use strict";
|
|
10175
|
+
init_tool_loop_detector();
|
|
9526
10176
|
init_store5();
|
|
9527
10177
|
init_backends();
|
|
9528
10178
|
init_paths();
|
|
9529
10179
|
init_loader();
|
|
9530
10180
|
init_log();
|
|
10181
|
+
init_strip_thinking();
|
|
10182
|
+
init_text_utils();
|
|
9531
10183
|
init_session_log();
|
|
9532
10184
|
init_summarize();
|
|
9533
10185
|
init_quota();
|
|
@@ -9539,6 +10191,10 @@ var init_agent = __esm({
|
|
|
9539
10191
|
activeChats = /* @__PURE__ */ new Map();
|
|
9540
10192
|
chatLocks = /* @__PURE__ */ new Map();
|
|
9541
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"]);
|
|
9542
10198
|
MCP_CONFIG_FLAG = {
|
|
9543
10199
|
claude: ["--mcp-config"]
|
|
9544
10200
|
};
|
|
@@ -9563,10 +10219,19 @@ function getChannelRegistry() {
|
|
|
9563
10219
|
return registry;
|
|
9564
10220
|
}
|
|
9565
10221
|
function isPathAllowed(filePath) {
|
|
10222
|
+
const normalized = filePath.replace(/\.\.\/|\.\.$/g, "");
|
|
10223
|
+
if (normalized !== filePath) return false;
|
|
9566
10224
|
for (const pattern of BLOCKED_PATH_PATTERNS) {
|
|
9567
|
-
if (pattern.test(
|
|
10225
|
+
if (pattern.test(normalized)) return false;
|
|
9568
10226
|
}
|
|
9569
|
-
|
|
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;
|
|
9570
10235
|
}
|
|
9571
10236
|
async function processFileSends(chatId, channel, text) {
|
|
9572
10237
|
const fileSendPattern = /\[SEND_FILE:(.+?)\]/g;
|
|
@@ -9615,9 +10280,11 @@ ${responseText.slice(0, 500)}`);
|
|
|
9615
10280
|
try {
|
|
9616
10281
|
const text = responseText.replace(/\s*\[REACT:[^\]]*\]\s*/g, " ").trim();
|
|
9617
10282
|
if (job.deliveryMode === "webhook") {
|
|
9618
|
-
await deliverWebhook(job, text);
|
|
9619
|
-
|
|
9620
|
-
|
|
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;
|
|
9621
10288
|
}
|
|
9622
10289
|
const channelName = job.channel ?? "telegram";
|
|
9623
10290
|
const channel = registry?.get(channelName);
|
|
@@ -9645,7 +10312,7 @@ async function deliverWebhook(job, responseText) {
|
|
|
9645
10312
|
const url = job.target;
|
|
9646
10313
|
if (!url) {
|
|
9647
10314
|
error(`[delivery] Job #${job.id}: webhook mode but no target URL`);
|
|
9648
|
-
return;
|
|
10315
|
+
return false;
|
|
9649
10316
|
}
|
|
9650
10317
|
try {
|
|
9651
10318
|
const resp = await fetch(url, {
|
|
@@ -9661,11 +10328,13 @@ async function deliverWebhook(job, responseText) {
|
|
|
9661
10328
|
});
|
|
9662
10329
|
if (!resp.ok) {
|
|
9663
10330
|
error(`[delivery] Webhook POST to ${url} failed: ${resp.status} ${resp.statusText}`);
|
|
9664
|
-
|
|
9665
|
-
log(`[delivery] Webhook POST to ${url}: ${resp.status}`);
|
|
10331
|
+
return false;
|
|
9666
10332
|
}
|
|
10333
|
+
log(`[delivery] Webhook POST to ${url}: ${resp.status}`);
|
|
10334
|
+
return true;
|
|
9667
10335
|
} catch (err) {
|
|
9668
10336
|
error(`[delivery] Webhook POST to ${url} error: ${errorMessage(err)}`);
|
|
10337
|
+
return false;
|
|
9669
10338
|
}
|
|
9670
10339
|
}
|
|
9671
10340
|
async function notifyJobFailure(job, errorMessage2, retryInfo) {
|
|
@@ -9693,25 +10362,30 @@ async function notifyJobAutoPaused(job) {
|
|
|
9693
10362
|
} catch {
|
|
9694
10363
|
}
|
|
9695
10364
|
}
|
|
9696
|
-
var registry, BLOCKED_PATH_PATTERNS;
|
|
10365
|
+
var registry, ALLOWED_PATH_PREFIXES, BLOCKED_PATH_PATTERNS;
|
|
9697
10366
|
var init_delivery = __esm({
|
|
9698
10367
|
"src/scheduler/delivery.ts"() {
|
|
9699
10368
|
"use strict";
|
|
9700
10369
|
init_log();
|
|
9701
10370
|
init_session_log();
|
|
9702
10371
|
registry = null;
|
|
10372
|
+
ALLOWED_PATH_PREFIXES = [
|
|
10373
|
+
"/tmp/",
|
|
10374
|
+
"/var/tmp/"
|
|
10375
|
+
];
|
|
9703
10376
|
BLOCKED_PATH_PATTERNS = [
|
|
9704
10377
|
/\/\.ssh\//,
|
|
9705
10378
|
/\/\.gnupg\//,
|
|
9706
10379
|
/\/\.aws\//,
|
|
9707
10380
|
/\/\.env$/,
|
|
9708
10381
|
/\/\.env\./,
|
|
9709
|
-
/\/\.cc-claw\/\.env$/,
|
|
9710
10382
|
/\/credentials\.json$/,
|
|
9711
10383
|
/\/\.gitconfig$/,
|
|
9712
10384
|
/\/\.netrc$/,
|
|
9713
10385
|
/\/etc\/shadow$/,
|
|
9714
|
-
/\/etc\/passwd
|
|
10386
|
+
/\/etc\/passwd$/,
|
|
10387
|
+
/\/secret/i,
|
|
10388
|
+
/\/token/i
|
|
9715
10389
|
];
|
|
9716
10390
|
}
|
|
9717
10391
|
});
|
|
@@ -9790,12 +10464,14 @@ var init_retry = __esm({
|
|
|
9790
10464
|
});
|
|
9791
10465
|
|
|
9792
10466
|
// src/scheduler/health.ts
|
|
10467
|
+
import * as os from "os";
|
|
9793
10468
|
function startHealthMonitor2() {
|
|
9794
10469
|
lastHeartbeat = /* @__PURE__ */ new Date();
|
|
9795
10470
|
heartbeatTimer = setInterval(() => {
|
|
9796
10471
|
lastHeartbeat = /* @__PURE__ */ new Date();
|
|
9797
10472
|
log(`[health] Scheduler heartbeat at ${lastHeartbeat.toISOString()}`);
|
|
9798
10473
|
}, HEARTBEAT_INTERVAL_MS);
|
|
10474
|
+
heartbeatTimer.unref();
|
|
9799
10475
|
}
|
|
9800
10476
|
function stopHealthMonitor2() {
|
|
9801
10477
|
if (heartbeatTimer) {
|
|
@@ -9817,7 +10493,10 @@ function getHealthReport() {
|
|
|
9817
10493
|
activeJobs: activeJobs.length,
|
|
9818
10494
|
pausedJobs: pausedJobs.length,
|
|
9819
10495
|
failingJobs,
|
|
9820
|
-
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]
|
|
9821
10500
|
};
|
|
9822
10501
|
}
|
|
9823
10502
|
function formatHealthReport(report) {
|
|
@@ -9843,7 +10522,11 @@ function computeStaggerMs(jobId, cronExpr) {
|
|
|
9843
10522
|
if (parts[0] !== "0") return 0;
|
|
9844
10523
|
if (!/[*/]/.test(parts[1])) return 0;
|
|
9845
10524
|
const maxStagger = 12e4;
|
|
9846
|
-
|
|
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;
|
|
9847
10530
|
}
|
|
9848
10531
|
var lastHeartbeat, heartbeatTimer, HEARTBEAT_INTERVAL_MS;
|
|
9849
10532
|
var init_health2 = __esm({
|
|
@@ -9967,6 +10650,7 @@ function buildProposalKeyboard(insightId, category) {
|
|
|
9967
10650
|
return [
|
|
9968
10651
|
[
|
|
9969
10652
|
{ label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
|
|
10653
|
+
{ label: "Preview", data: `evolve:preview:${insightId}` },
|
|
9970
10654
|
{ label: "Discuss", data: `evolve:discuss:${insightId}` }
|
|
9971
10655
|
],
|
|
9972
10656
|
[
|
|
@@ -10156,7 +10840,8 @@ function mergeAndDeduplicate(raw) {
|
|
|
10156
10840
|
});
|
|
10157
10841
|
}
|
|
10158
10842
|
function parseFrontmatter2(content, fallbackName) {
|
|
10159
|
-
const
|
|
10843
|
+
const cleaned = content.replace(/^\uFEFF/, "");
|
|
10844
|
+
const fmMatch = cleaned.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
10160
10845
|
if (!fmMatch) {
|
|
10161
10846
|
return { name: fallbackName, description: "" };
|
|
10162
10847
|
}
|
|
@@ -10725,9 +11410,19 @@ var init_format_time = __esm({
|
|
|
10725
11410
|
});
|
|
10726
11411
|
|
|
10727
11412
|
// src/intent/classify.ts
|
|
11413
|
+
var classify_exports = {};
|
|
11414
|
+
__export(classify_exports, {
|
|
11415
|
+
classifyIntent: () => classifyIntent,
|
|
11416
|
+
getIntentStats: () => getIntentStats,
|
|
11417
|
+
resetIntentStats: () => resetIntentStats
|
|
11418
|
+
});
|
|
10728
11419
|
function getIntentStats() {
|
|
10729
11420
|
return { ...intentCounts };
|
|
10730
11421
|
}
|
|
11422
|
+
function resetIntentStats() {
|
|
11423
|
+
intentCounts.chat = 0;
|
|
11424
|
+
intentCounts.agentic = 0;
|
|
11425
|
+
}
|
|
10731
11426
|
function classifyIntent(text, chatId) {
|
|
10732
11427
|
const trimmed = text.trim();
|
|
10733
11428
|
if (trimmed.startsWith(">>")) return "agentic";
|
|
@@ -10930,9 +11625,20 @@ var init_image_gen = __esm({
|
|
|
10930
11625
|
|
|
10931
11626
|
// src/voice/stt.ts
|
|
10932
11627
|
import crypto from "crypto";
|
|
10933
|
-
import { execFile as execFile2 } from "child_process";
|
|
11628
|
+
import { execFile as execFile2, execFileSync } from "child_process";
|
|
10934
11629
|
import { readFile as readFile4, unlink } from "fs/promises";
|
|
10935
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
|
+
}
|
|
10936
11642
|
function isVoiceEnabled(chatId) {
|
|
10937
11643
|
const db3 = getDb();
|
|
10938
11644
|
const row = db3.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
|
|
@@ -11066,6 +11772,7 @@ async function grokTts(text, apiKey, voiceId) {
|
|
|
11066
11772
|
return await mp3ToOgg(mp3Buffer);
|
|
11067
11773
|
}
|
|
11068
11774
|
async function mp3ToOgg(mp3Buffer) {
|
|
11775
|
+
ensureFfmpeg();
|
|
11069
11776
|
const id = crypto.randomUUID();
|
|
11070
11777
|
const tmpMp3 = `/tmp/cc-claw-tts-${id}.mp3`;
|
|
11071
11778
|
const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
|
|
@@ -11082,6 +11789,7 @@ async function mp3ToOgg(mp3Buffer) {
|
|
|
11082
11789
|
return oggBuffer;
|
|
11083
11790
|
}
|
|
11084
11791
|
async function macOsTts(text, voice2 = "Samantha") {
|
|
11792
|
+
ensureFfmpeg();
|
|
11085
11793
|
const id = crypto.randomUUID();
|
|
11086
11794
|
const tmpAiff = `/tmp/cc-claw-tts-${id}.aiff`;
|
|
11087
11795
|
const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
|
|
@@ -11096,13 +11804,14 @@ async function macOsTts(text, voice2 = "Samantha") {
|
|
|
11096
11804
|
});
|
|
11097
11805
|
return oggBuffer;
|
|
11098
11806
|
}
|
|
11099
|
-
var execFileAsync2, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES;
|
|
11807
|
+
var execFileAsync2, ffmpegAvailable, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES;
|
|
11100
11808
|
var init_stt = __esm({
|
|
11101
11809
|
"src/voice/stt.ts"() {
|
|
11102
11810
|
"use strict";
|
|
11103
11811
|
init_log();
|
|
11104
11812
|
init_store5();
|
|
11105
11813
|
execFileAsync2 = promisify2(execFile2);
|
|
11814
|
+
ffmpegAvailable = null;
|
|
11106
11815
|
ELEVENLABS_VOICES = {
|
|
11107
11816
|
"21m00Tcm4TlvDq8ikWAM": { name: "Rachel", gender: "F" },
|
|
11108
11817
|
"EXAVITQu4vr4xnSDxMaL": { name: "Sarah", gender: "F" },
|
|
@@ -11968,11 +12677,21 @@ var init_guard = __esm({
|
|
|
11968
12677
|
/\bdd\b.*\bof=/,
|
|
11969
12678
|
/\b(shutdown|reboot|halt|poweroff)\b/,
|
|
11970
12679
|
/>\s*\/dev\/sd/,
|
|
11971
|
-
/\bchmod\
|
|
12680
|
+
/\bchmod\b.*\b777\b/,
|
|
12681
|
+
// catch chmod 777 regardless of flag order
|
|
11972
12682
|
/\bchown\s+-R\b/,
|
|
11973
12683
|
/\bkillall\b/,
|
|
11974
12684
|
/\blaunchctl\s+(unload|remove)\b/,
|
|
11975
|
-
/:\(\)\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
|
|
11976
12695
|
];
|
|
11977
12696
|
pendingCommands = /* @__PURE__ */ new Map();
|
|
11978
12697
|
PENDING_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -11983,8 +12702,9 @@ var init_guard = __esm({
|
|
|
11983
12702
|
import { execFile as execFile3 } from "child_process";
|
|
11984
12703
|
function executeShell(command, cwd, timeoutMs = SHELL_TIMEOUT_MS) {
|
|
11985
12704
|
return new Promise((resolve) => {
|
|
12705
|
+
const shell = process.env.SHELL || "/bin/sh";
|
|
11986
12706
|
execFile3(
|
|
11987
|
-
|
|
12707
|
+
shell,
|
|
11988
12708
|
["-c", command],
|
|
11989
12709
|
{ cwd, timeout: timeoutMs, maxBuffer: 5 * 1024 * 1024 },
|
|
11990
12710
|
(error3, stdout, stderr) => {
|
|
@@ -12007,9 +12727,12 @@ function executeShell(command, cwd, timeoutMs = SHELL_TIMEOUT_MS) {
|
|
|
12007
12727
|
);
|
|
12008
12728
|
});
|
|
12009
12729
|
}
|
|
12730
|
+
function escapeHtml(s) {
|
|
12731
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
12732
|
+
}
|
|
12010
12733
|
function formatCodeBlock(command, output2, exitCode) {
|
|
12011
|
-
return `<pre>$ ${command}
|
|
12012
|
-
${output2}
|
|
12734
|
+
return `<pre>$ ${escapeHtml(command)}
|
|
12735
|
+
${escapeHtml(output2)}
|
|
12013
12736
|
[exit ${exitCode}]</pre>`;
|
|
12014
12737
|
}
|
|
12015
12738
|
function formatRaw(output2) {
|
|
@@ -12170,7 +12893,6 @@ __export(router_exports, {
|
|
|
12170
12893
|
stopAllSideQuests: () => stopAllSideQuests
|
|
12171
12894
|
});
|
|
12172
12895
|
import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2, mkdir as mkdir2, readdir as readdir3, stat } from "fs/promises";
|
|
12173
|
-
import { existsSync as existsSync18 } from "fs";
|
|
12174
12896
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12175
12897
|
import { resolve as resolvePath, join as join18 } from "path";
|
|
12176
12898
|
import { homedir as homedir6 } from "os";
|
|
@@ -12617,6 +13339,7 @@ ${cmds.map((c) => ` ${c.cmd} \u2014 ${c.desc}`).join("\n")}`
|
|
|
12617
13339
|
case "stop": {
|
|
12618
13340
|
const stopped = stopAgent(chatId);
|
|
12619
13341
|
stopAllSideQuests(chatId);
|
|
13342
|
+
cancelAllAgents(chatId);
|
|
12620
13343
|
await channel.sendText(
|
|
12621
13344
|
chatId,
|
|
12622
13345
|
stopped ? "Stopping current task..." : "Nothing is running.",
|
|
@@ -14087,13 +14810,17 @@ Message: "${testMsg}"`, { parseMode: "plain" });
|
|
|
14087
14810
|
else if (diffHours < 24) lastText = `${diffHours}h ago`;
|
|
14088
14811
|
else lastText = `${Math.floor(diffHours / 24)}d ago`;
|
|
14089
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";
|
|
14090
14816
|
const dashText = [
|
|
14091
14817
|
"Self-Learning & Evolution",
|
|
14092
14818
|
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
14093
14819
|
"",
|
|
14094
14820
|
`\u2705 Active`,
|
|
14095
14821
|
`Signals: ${signals} pending \xB7 Proposals: ${pending}`,
|
|
14096
|
-
`Last analysis: ${lastText}
|
|
14822
|
+
`Last analysis: ${lastText}`,
|
|
14823
|
+
`Model: ${modelLabel}`
|
|
14097
14824
|
].join("\n");
|
|
14098
14825
|
if (typeof channel.sendKeyboard === "function") {
|
|
14099
14826
|
const { buildEvolveMenuKeyboard: buildEvolveMenuKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
@@ -14104,47 +14831,6 @@ Message: "${testMsg}"`, { parseMode: "plain" });
|
|
|
14104
14831
|
}
|
|
14105
14832
|
break;
|
|
14106
14833
|
}
|
|
14107
|
-
case "reflect": {
|
|
14108
|
-
const lastReflect = lastReflectTime.get(chatId);
|
|
14109
|
-
if (lastReflect && Date.now() - lastReflect < REFLECT_COOLDOWN_MS) {
|
|
14110
|
-
const remaining = Math.ceil((REFLECT_COOLDOWN_MS - (Date.now() - lastReflect)) / 1e3);
|
|
14111
|
-
await channel.sendText(chatId, `Please wait ${remaining}s before reflecting again.`, { parseMode: "plain" });
|
|
14112
|
-
break;
|
|
14113
|
-
}
|
|
14114
|
-
const { getMessagePairCount: getMessagePairCount2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
|
|
14115
|
-
const pairCount = getMessagePairCount2(chatId);
|
|
14116
|
-
if (pairCount < 2) {
|
|
14117
|
-
await channel.sendText(chatId, "Need more conversation to analyze \u2014 at least 2 message exchanges.", { parseMode: "plain" });
|
|
14118
|
-
break;
|
|
14119
|
-
}
|
|
14120
|
-
lastReflectTime.set(chatId, Date.now());
|
|
14121
|
-
await channel.sendText(chatId, "Step 1/3: Summarizing conversation...", { parseMode: "plain" });
|
|
14122
|
-
try {
|
|
14123
|
-
const { summarizeSession: summarizeSession2 } = await Promise.resolve().then(() => (init_summarize(), summarize_exports));
|
|
14124
|
-
await summarizeSession2(chatId, false);
|
|
14125
|
-
} catch (e) {
|
|
14126
|
-
await channel.sendText(chatId, `Summarization failed: ${e}`, { parseMode: "plain" });
|
|
14127
|
-
break;
|
|
14128
|
-
}
|
|
14129
|
-
await channel.sendText(chatId, "Step 2/3: Analyzing for insights...", { parseMode: "plain" });
|
|
14130
|
-
try {
|
|
14131
|
-
const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
|
|
14132
|
-
const insights = await runAnalysis2(chatId, { force: true });
|
|
14133
|
-
if (insights.length === 0) {
|
|
14134
|
-
await channel.sendText(chatId, "Step 3/3: No actionable improvements found in this session.", { parseMode: "plain" });
|
|
14135
|
-
} else {
|
|
14136
|
-
await channel.sendText(chatId, `Step 3/3: Found ${insights.length} proposal(s). Let's review them one by one.`, { parseMode: "plain" });
|
|
14137
|
-
const { getPendingInsights: getPendingInsights2, createReviewSession: createReviewSession2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
14138
|
-
const pending = getPendingInsights2(getDb(), chatId);
|
|
14139
|
-
const insightIds = pending.slice(0, 5).map((p) => p.id);
|
|
14140
|
-
createReviewSession2(getDb(), chatId, insightIds);
|
|
14141
|
-
await sendCurrentProposal(chatId, channel);
|
|
14142
|
-
}
|
|
14143
|
-
} catch (e) {
|
|
14144
|
-
await channel.sendText(chatId, `Analysis failed: ${e}. You can review any pending proposals with /evolve.`, { parseMode: "plain" });
|
|
14145
|
-
}
|
|
14146
|
-
break;
|
|
14147
|
-
}
|
|
14148
14834
|
default:
|
|
14149
14835
|
await channel.sendText(chatId, `Unknown command: /${command}. Type /help for available commands.`, { parseMode: "plain" });
|
|
14150
14836
|
}
|
|
@@ -14153,6 +14839,10 @@ async function handleVoice(msg, channel) {
|
|
|
14153
14839
|
const { chatId, fileName } = msg;
|
|
14154
14840
|
await channel.sendText(chatId, "Transcribing voice message...", { parseMode: "plain" });
|
|
14155
14841
|
try {
|
|
14842
|
+
if (!fileName) {
|
|
14843
|
+
await channel.sendText(chatId, "Could not process voice message \u2014 file not available.", { parseMode: "plain" });
|
|
14844
|
+
return;
|
|
14845
|
+
}
|
|
14156
14846
|
const audioBuffer = await channel.downloadFile(fileName);
|
|
14157
14847
|
const transcript = await transcribeAudio(audioBuffer);
|
|
14158
14848
|
if (!transcript) {
|
|
@@ -14243,6 +14933,10 @@ Acknowledge receipt. Do NOT analyze the video unless they ask you to.`;
|
|
|
14243
14933
|
await sendResponse(chatId, channel, vidResponse, msg.messageId);
|
|
14244
14934
|
return;
|
|
14245
14935
|
}
|
|
14936
|
+
if (!fileName) {
|
|
14937
|
+
await channel.sendText(chatId, "Could not process file \u2014 file not available.", { parseMode: "plain" });
|
|
14938
|
+
return;
|
|
14939
|
+
}
|
|
14246
14940
|
const fileBuffer = await channel.downloadFile(fileName);
|
|
14247
14941
|
const originalName = fileName ?? "file";
|
|
14248
14942
|
const ext = originalName.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -14259,8 +14953,18 @@ Please read the image file and respond to their caption/question.` : `The user s
|
|
|
14259
14953
|
|
|
14260
14954
|
Please read the image file and describe what you see.`;
|
|
14261
14955
|
} else if (isTextExt(ext)) {
|
|
14262
|
-
const
|
|
14263
|
-
|
|
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}":
|
|
14264
14968
|
|
|
14265
14969
|
\`\`\`
|
|
14266
14970
|
${content}
|
|
@@ -14271,6 +14975,7 @@ ${caption}` : `Here's a file "${originalName}". Please analyze its contents:
|
|
|
14271
14975
|
\`\`\`
|
|
14272
14976
|
${content}
|
|
14273
14977
|
\`\`\``;
|
|
14978
|
+
}
|
|
14274
14979
|
} else {
|
|
14275
14980
|
prompt = `I've shared a file "${originalName}" (${fileBuffer.length} bytes). ${caption || "What can you tell me about it?"}`;
|
|
14276
14981
|
}
|
|
@@ -14357,6 +15062,64 @@ async function handleText(msg, channel) {
|
|
|
14357
15062
|
if (hasSqPrefix) {
|
|
14358
15063
|
text = sqCleanText;
|
|
14359
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
|
+
}
|
|
14360
15123
|
if (isChatBusy(chatId) && !bypassBusyCheck.delete(chatId)) {
|
|
14361
15124
|
if (typeof channel.sendKeyboard === "function") {
|
|
14362
15125
|
pendingInterrupts.set(chatId, { msg, channel });
|
|
@@ -14425,6 +15188,12 @@ async function handleText(msg, channel) {
|
|
|
14425
15188
|
const toIcon = toSlot?.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
14426
15189
|
channel.sendText(cid, `\u26A0\uFE0F Quota reached on ${fromIcon} ${from} \u2014 switched to ${toIcon} ${to}. Context saved.`, { parseMode: "plain" }).catch(() => {
|
|
14427
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
|
+
});
|
|
14428
15197
|
}
|
|
14429
15198
|
});
|
|
14430
15199
|
const elapsedSec = ((Date.now() - sigT0) / 1e3).toFixed(1);
|
|
@@ -14571,7 +15340,6 @@ async function handleSideQuest(parentChatId, msg, channel) {
|
|
|
14571
15340
|
backend: backend2 ?? void 0,
|
|
14572
15341
|
settingsSourceChatId: parentChatId,
|
|
14573
15342
|
agentMode: "native",
|
|
14574
|
-
maxTurns: 10,
|
|
14575
15343
|
timeoutMs: 3e5,
|
|
14576
15344
|
bootstrapTier: "full"
|
|
14577
15345
|
});
|
|
@@ -15821,6 +16589,10 @@ Result: ${task.result.slice(0, 500)}` : ""
|
|
|
15821
16589
|
const parts = data.split(":");
|
|
15822
16590
|
const action = parts[1];
|
|
15823
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
|
+
}
|
|
15824
16596
|
switch (action) {
|
|
15825
16597
|
case "menu": {
|
|
15826
16598
|
const { getReflectionStatus: getRefStatus, getUnprocessedSignalCount: getUnprocessedSignalCount2, getPendingInsightCount: getPendingInsightCount2, getLastAnalysisTime: getLastAnalysisTime2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
@@ -15898,6 +16670,65 @@ Result: ${task.result.slice(0, 500)}` : ""
|
|
|
15898
16670
|
}
|
|
15899
16671
|
break;
|
|
15900
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
|
+
}
|
|
15901
16732
|
case "diff": {
|
|
15902
16733
|
const { getInsightById: getInsightById2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
15903
16734
|
const { formatDiffCodeBlock: formatDiffCodeBlock2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
@@ -15926,166 +16757,15 @@ Result: ${task.result.slice(0, 500)}` : ""
|
|
|
15926
16757
|
}
|
|
15927
16758
|
case "discuss": {
|
|
15928
16759
|
const insId = parseInt(idStr, 10);
|
|
15929
|
-
await
|
|
15930
|
-
|
|
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.",
|
|
15931
16764
|
"",
|
|
15932
|
-
"
|
|
15933
|
-
"
|
|
15934
|
-
"
|
|
15935
|
-
].join("\n"),
|
|
15936
|
-
[
|
|
15937
|
-
{ label: "Why this file?", data: `evolve:discuss-why:${insId}`, style: "primary" },
|
|
15938
|
-
{ label: "Retarget", data: `evolve:discuss-retarget:${insId}` }
|
|
15939
|
-
],
|
|
15940
|
-
[
|
|
15941
|
-
{ label: "Revise the change", data: `evolve:discuss-revise:${insId}` },
|
|
15942
|
-
{ label: "Back", data: `evolve:discuss-back:${insId}` }
|
|
15943
|
-
]
|
|
15944
|
-
]);
|
|
15945
|
-
break;
|
|
15946
|
-
}
|
|
15947
|
-
case "discuss-why": {
|
|
15948
|
-
const { getInsightById: gwIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
15949
|
-
const { getDb: gwDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
15950
|
-
const gwInsight = gwIns(gwDb(), parseInt(idStr, 10));
|
|
15951
|
-
if (!gwInsight) {
|
|
15952
|
-
await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
|
|
15953
|
-
break;
|
|
15954
|
-
}
|
|
15955
|
-
await channel.sendTyping?.(chatId);
|
|
15956
|
-
const whyPrompt = [
|
|
15957
|
-
`Explain in 2-3 sentences why you chose "${gwInsight.targetFile}" as the target file for this insight:`,
|
|
15958
|
-
`"${gwInsight.insight}"`,
|
|
15959
|
-
`Consider what other files could work (skills, context files, USER.md) and explain your reasoning.`
|
|
15960
|
-
].join("\n");
|
|
15961
|
-
try {
|
|
15962
|
-
const whyResp = await askAgent(chatId, whyPrompt, { bootstrapTier: "chat", maxTurns: 1, timeoutMs: 6e4 });
|
|
15963
|
-
await channel.sendText(chatId, whyResp.text ?? "No response.");
|
|
15964
|
-
} catch (e) {
|
|
15965
|
-
await channel.sendText(chatId, `Error: ${e.message}`, { parseMode: "plain" });
|
|
15966
|
-
}
|
|
15967
|
-
await channel.sendKeyboard(chatId, "What next?", [
|
|
15968
|
-
[
|
|
15969
|
-
{ label: "OK, Apply", data: `evolve:apply:${idStr}`, style: "success" },
|
|
15970
|
-
{ label: "Retarget instead", data: `evolve:discuss-retarget:${idStr}` }
|
|
15971
|
-
],
|
|
15972
|
-
[
|
|
15973
|
-
{ label: "Reject", data: `evolve:reject:${idStr}`, style: "danger" },
|
|
15974
|
-
{ label: "Back", data: `evolve:discuss-back:${idStr}` }
|
|
15975
|
-
]
|
|
15976
|
-
]);
|
|
15977
|
-
break;
|
|
15978
|
-
}
|
|
15979
|
-
case "discuss-retarget": {
|
|
15980
|
-
const { getInsightById: rtIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
15981
|
-
const { getDb: rtDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
15982
|
-
const rtInsight = rtIns(rtDb(), parseInt(idStr, 10));
|
|
15983
|
-
if (!rtInsight) {
|
|
15984
|
-
await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
|
|
15985
|
-
break;
|
|
15986
|
-
}
|
|
15987
|
-
const currentTarget = rtInsight.targetFile ?? "unknown";
|
|
15988
|
-
const targets = [
|
|
15989
|
-
{ label: "SOUL.md", path: "identity/SOUL.md" },
|
|
15990
|
-
{ label: "USER.md", path: "identity/USER.md" }
|
|
15991
|
-
];
|
|
15992
|
-
const skillDirs = [
|
|
15993
|
-
join18(homedir6(), ".cc-claw", "workspace", "skills")
|
|
15994
|
-
];
|
|
15995
|
-
try {
|
|
15996
|
-
const { readdirSync: readdirSync9, statSync: statSync10 } = await import("fs");
|
|
15997
|
-
for (const dir of skillDirs) {
|
|
15998
|
-
if (!existsSync18(dir)) continue;
|
|
15999
|
-
for (const entry of readdirSync9(dir)) {
|
|
16000
|
-
if (statSync10(join18(dir, entry)).isDirectory()) {
|
|
16001
|
-
targets.push({ label: `skills/${entry}`, path: `workspace/skills/${entry}/SKILL.md` });
|
|
16002
|
-
}
|
|
16003
|
-
}
|
|
16004
|
-
}
|
|
16005
|
-
} catch {
|
|
16006
|
-
}
|
|
16007
|
-
const rows = targets.filter((t) => t.path !== currentTarget).map((t) => [{ label: t.label, data: `evolve:discuss-retarget-to:${idStr}:${t.path}` }]);
|
|
16008
|
-
rows.push([{ label: "Cancel", data: `evolve:discuss-back:${idStr}` }]);
|
|
16009
|
-
await channel.sendKeyboard(chatId, `Current target: ${currentTarget}
|
|
16010
|
-
|
|
16011
|
-
Pick a different file for this change. Identity files (SOUL/USER) shape personality; skills teach specific workflows.`, rows);
|
|
16012
|
-
break;
|
|
16013
|
-
}
|
|
16014
|
-
case "discuss-retarget-to": {
|
|
16015
|
-
const retargetParts = data.split(":");
|
|
16016
|
-
const rtId = parseInt(retargetParts[2], 10);
|
|
16017
|
-
const newPath = retargetParts.slice(3).join(":");
|
|
16018
|
-
const { getInsightById: rt2Ins, updateInsightProposal: rt2Update } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16019
|
-
const { formatProposalCard: rt2Card, buildProposalKeyboard: rt2Kb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
16020
|
-
const { getDb: rt2Db } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
16021
|
-
const rt2Insight = rt2Ins(rt2Db(), rtId);
|
|
16022
|
-
if (!rt2Insight) {
|
|
16023
|
-
await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
|
|
16024
|
-
break;
|
|
16025
|
-
}
|
|
16026
|
-
rt2Update(rt2Db(), rtId, newPath, rt2Insight.proposedDiff, rt2Insight.proposedAction);
|
|
16027
|
-
const updatedIns = rt2Ins(rt2Db(), rtId);
|
|
16028
|
-
if (updatedIns) {
|
|
16029
|
-
const card = rt2Card(updatedIns);
|
|
16030
|
-
const kb = rt2Kb(updatedIns.id, updatedIns.category);
|
|
16031
|
-
await channel.sendKeyboard(chatId, `Retargeted to ${newPath}. Updated proposal:
|
|
16032
|
-
|
|
16033
|
-
` + card, kb);
|
|
16034
|
-
}
|
|
16035
|
-
break;
|
|
16036
|
-
}
|
|
16037
|
-
case "discuss-revise": {
|
|
16038
|
-
const { getInsightById: rvIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16039
|
-
const { getDb: rvDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
16040
|
-
const rvInsight = rvIns(rvDb(), parseInt(idStr, 10));
|
|
16041
|
-
if (!rvInsight) {
|
|
16042
|
-
await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
|
|
16043
|
-
break;
|
|
16044
|
-
}
|
|
16045
|
-
await channel.sendTyping?.(chatId);
|
|
16046
|
-
const revisePrompt = [
|
|
16047
|
-
`Revise this proposed change to be more specific and actionable:`,
|
|
16048
|
-
`Current insight: "${rvInsight.insight}"`,
|
|
16049
|
-
`Current diff: ${rvInsight.proposedDiff ?? "none"}`,
|
|
16050
|
-
`Target: ${rvInsight.targetFile}`,
|
|
16051
|
-
``,
|
|
16052
|
-
`Output ONLY the revised diff lines (starting with + for additions, - for removals). No explanation.`
|
|
16053
|
-
].join("\n");
|
|
16054
|
-
try {
|
|
16055
|
-
const rvResp = await askAgent(chatId, revisePrompt, { bootstrapTier: "chat", maxTurns: 1, timeoutMs: 6e4 });
|
|
16056
|
-
const newDiff = rvResp.text?.trim() ?? rvInsight.proposedDiff;
|
|
16057
|
-
if (newDiff) {
|
|
16058
|
-
const { updateInsightProposal: rvUpdate } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16059
|
-
rvUpdate(rvDb(), parseInt(idStr, 10), rvInsight.targetFile ?? "", newDiff, rvInsight.proposedAction);
|
|
16060
|
-
}
|
|
16061
|
-
const updatedRv = rvIns(rvDb(), parseInt(idStr, 10));
|
|
16062
|
-
if (updatedRv) {
|
|
16063
|
-
const { formatProposalCard: rvCard, buildProposalKeyboard: rvKb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
16064
|
-
const card = rvCard(updatedRv);
|
|
16065
|
-
const kb = rvKb(updatedRv.id, updatedRv.category);
|
|
16066
|
-
await channel.sendKeyboard(chatId, "Revised proposal:\n\n" + card, kb);
|
|
16067
|
-
}
|
|
16068
|
-
} catch (e) {
|
|
16069
|
-
await channel.sendText(chatId, `Revision error: ${e.message}`, { parseMode: "plain" });
|
|
16070
|
-
}
|
|
16071
|
-
break;
|
|
16072
|
-
}
|
|
16073
|
-
case "discuss-back": {
|
|
16074
|
-
const { getInsightById: bkIns, getReviewSession: bkSession } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16075
|
-
const { formatProposalCardWithProgress: formatProposalCardWithProgress2, formatProposalCard: bkCardFn, buildProposalKeyboard: bkKb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
16076
|
-
const { getDb: bkDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
16077
|
-
const bkInsight = bkIns(bkDb(), parseInt(idStr, 10));
|
|
16078
|
-
if (bkInsight && bkInsight.status === "pending") {
|
|
16079
|
-
const session2 = bkSession(bkDb(), chatId);
|
|
16080
|
-
const kb = bkKb(bkInsight.id, bkInsight.category);
|
|
16081
|
-
if (session2) {
|
|
16082
|
-
const card = formatProposalCardWithProgress2(bkInsight, session2.currentIndex, session2.insightIds.length);
|
|
16083
|
-
await channel.sendKeyboard(chatId, card, kb);
|
|
16084
|
-
} else {
|
|
16085
|
-
const card = bkCardFn(bkInsight);
|
|
16086
|
-
await channel.sendKeyboard(chatId, card, kb);
|
|
16087
|
-
}
|
|
16088
|
-
}
|
|
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" });
|
|
16089
16769
|
break;
|
|
16090
16770
|
}
|
|
16091
16771
|
case "reject": {
|
|
@@ -16149,13 +16829,13 @@ Pick a different file for this change. Identity files (SOUL/USER) shape personal
|
|
|
16149
16829
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
16150
16830
|
const current = getReflectionStatus2(getDb2(), chatId);
|
|
16151
16831
|
if (current === "frozen") {
|
|
16152
|
-
const { readFileSync: readFileSync21, existsSync:
|
|
16832
|
+
const { readFileSync: readFileSync21, existsSync: existsSync46 } = await import("fs");
|
|
16153
16833
|
const { join: join28 } = await import("path");
|
|
16154
16834
|
const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
16155
16835
|
const soulPath = join28(CC_CLAW_HOME3, "identity/SOUL.md");
|
|
16156
16836
|
const userPath = join28(CC_CLAW_HOME3, "identity/USER.md");
|
|
16157
|
-
const soul =
|
|
16158
|
-
const user =
|
|
16837
|
+
const soul = existsSync46(soulPath) ? readFileSync21(soulPath, "utf-8") : "";
|
|
16838
|
+
const user = existsSync46(userPath) ? readFileSync21(userPath, "utf-8") : "";
|
|
16159
16839
|
setReflectionStatus2(getDb2(), chatId, "active", soul, user);
|
|
16160
16840
|
const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
|
|
16161
16841
|
logActivity2(getDb2(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
|
|
@@ -16749,7 +17429,7 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
|
|
|
16749
17429
|
const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
|
|
16750
17430
|
await channel.sendKeyboard(chatId, header2, buttons);
|
|
16751
17431
|
}
|
|
16752
|
-
var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, activeSideQuests, MAX_SIDE_QUESTS, pendingFallbackMessages, dashboardClawWarnings,
|
|
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;
|
|
16753
17433
|
var init_router = __esm({
|
|
16754
17434
|
"src/router.ts"() {
|
|
16755
17435
|
"use strict";
|
|
@@ -16892,8 +17572,6 @@ var init_router = __esm({
|
|
|
16892
17572
|
MAX_SIDE_QUESTS = 2;
|
|
16893
17573
|
pendingFallbackMessages = /* @__PURE__ */ new Map();
|
|
16894
17574
|
dashboardClawWarnings = /* @__PURE__ */ new Map();
|
|
16895
|
-
lastReflectTime = /* @__PURE__ */ new Map();
|
|
16896
|
-
REFLECT_COOLDOWN_MS = 12e4;
|
|
16897
17575
|
pendingSummaryUndo = /* @__PURE__ */ new Map();
|
|
16898
17576
|
pendingNewchatUndo = /* @__PURE__ */ new Map();
|
|
16899
17577
|
CLI_INSTALL_HINTS = {
|
|
@@ -17019,7 +17697,8 @@ function initScheduler(channelReg) {
|
|
|
17019
17697
|
if (orphaned.changes > 0) {
|
|
17020
17698
|
log(`[scheduler] Cleaned up ${orphaned.changes} orphaned run(s)`);
|
|
17021
17699
|
}
|
|
17022
|
-
} catch {
|
|
17700
|
+
} catch (err) {
|
|
17701
|
+
log(`[scheduler] Orphaned run cleanup skipped: ${err}`);
|
|
17023
17702
|
}
|
|
17024
17703
|
try {
|
|
17025
17704
|
const jobs = getActiveJobs();
|
|
@@ -17172,7 +17851,6 @@ async function executeJob(job) {
|
|
|
17172
17851
|
const contentStatus = isEmpty ? "no_content" : "success";
|
|
17173
17852
|
const durationMs = Date.now() - t0;
|
|
17174
17853
|
updateJobLastRun(job.id, (/* @__PURE__ */ new Date()).toISOString());
|
|
17175
|
-
resetJobFailures(job.id);
|
|
17176
17854
|
if (response.usage) {
|
|
17177
17855
|
addUsage(job.chatId, response.usage.input, response.usage.output, response.usage.cacheRead, resolvedModel, void 0, response.usage.contextSize);
|
|
17178
17856
|
}
|
|
@@ -17184,6 +17862,16 @@ async function executeJob(job) {
|
|
|
17184
17862
|
cacheRead: response.usage?.cacheRead,
|
|
17185
17863
|
durationMs
|
|
17186
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
|
+
}
|
|
17187
17875
|
} catch (err) {
|
|
17188
17876
|
const durationMs = Date.now() - t0;
|
|
17189
17877
|
const errorClass = classifyError(err);
|
|
@@ -17476,18 +18164,18 @@ var init_wrap_backend = __esm({
|
|
|
17476
18164
|
});
|
|
17477
18165
|
|
|
17478
18166
|
// src/agents/runners/config-loader.ts
|
|
17479
|
-
import { readFileSync as readFileSync10, readdirSync as readdirSync8, existsSync as
|
|
18167
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync8, existsSync as existsSync18, mkdirSync as mkdirSync8, watchFile, unwatchFile } from "fs";
|
|
17480
18168
|
import { join as join20 } from "path";
|
|
17481
|
-
import { execFileSync } from "child_process";
|
|
18169
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
17482
18170
|
function resolveExecutable(config2) {
|
|
17483
|
-
if (
|
|
18171
|
+
if (existsSync18(config2.executable)) return config2.executable;
|
|
17484
18172
|
try {
|
|
17485
|
-
return
|
|
18173
|
+
return execFileSync2("which", [config2.executable], { encoding: "utf-8" }).trim();
|
|
17486
18174
|
} catch {
|
|
17487
18175
|
}
|
|
17488
18176
|
for (const fallback of config2.executableFallbacks ?? []) {
|
|
17489
18177
|
const resolved = fallback.replace(/^~/, process.env.HOME ?? "");
|
|
17490
|
-
if (
|
|
18178
|
+
if (existsSync18(resolved)) return resolved;
|
|
17491
18179
|
}
|
|
17492
18180
|
return config2.executable;
|
|
17493
18181
|
}
|
|
@@ -17626,7 +18314,7 @@ function loadRunnerConfig(filePath) {
|
|
|
17626
18314
|
}
|
|
17627
18315
|
}
|
|
17628
18316
|
function loadAllRunnerConfigs() {
|
|
17629
|
-
if (!
|
|
18317
|
+
if (!existsSync18(RUNNERS_PATH)) {
|
|
17630
18318
|
mkdirSync8(RUNNERS_PATH, { recursive: true });
|
|
17631
18319
|
return [];
|
|
17632
18320
|
}
|
|
@@ -17654,9 +18342,9 @@ function registerConfigRunners() {
|
|
|
17654
18342
|
return count;
|
|
17655
18343
|
}
|
|
17656
18344
|
function watchRunnerConfigs(onChange) {
|
|
17657
|
-
if (!
|
|
18345
|
+
if (!existsSync18(RUNNERS_PATH)) return;
|
|
17658
18346
|
for (const prev of watchedFiles) {
|
|
17659
|
-
if (!
|
|
18347
|
+
if (!existsSync18(prev)) {
|
|
17660
18348
|
unwatchFile(prev);
|
|
17661
18349
|
watchedFiles.delete(prev);
|
|
17662
18350
|
}
|
|
@@ -17713,11 +18401,12 @@ var init_runners = __esm({
|
|
|
17713
18401
|
function checkIdleAgents(agents2, now, thresholdMs = IDLE_THRESHOLD_MS) {
|
|
17714
18402
|
return agents2.filter((agent) => {
|
|
17715
18403
|
if (agent.status !== "running" || !agent.lastOutputAt) return false;
|
|
17716
|
-
const
|
|
18404
|
+
const ts2 = agent.lastOutputAt;
|
|
18405
|
+
const lastOutput = (/* @__PURE__ */ new Date(ts2 + (ts2.includes("Z") ? "" : "Z"))).getTime();
|
|
17717
18406
|
return now - lastOutput > thresholdMs;
|
|
17718
18407
|
});
|
|
17719
18408
|
}
|
|
17720
|
-
function startMonitor(callbacks, intervalMs =
|
|
18409
|
+
function startMonitor(callbacks, intervalMs = MONITOR_INTERVAL_MS) {
|
|
17721
18410
|
if (monitorInterval) return;
|
|
17722
18411
|
monitorInterval = setInterval(() => {
|
|
17723
18412
|
try {
|
|
@@ -17739,7 +18428,7 @@ function stopMonitor() {
|
|
|
17739
18428
|
monitorInterval = null;
|
|
17740
18429
|
}
|
|
17741
18430
|
}
|
|
17742
|
-
var monitorInterval;
|
|
18431
|
+
var monitorInterval, MONITOR_INTERVAL_MS;
|
|
17743
18432
|
var init_monitor = __esm({
|
|
17744
18433
|
"src/agents/monitor.ts"() {
|
|
17745
18434
|
"use strict";
|
|
@@ -17748,6 +18437,7 @@ var init_monitor = __esm({
|
|
|
17748
18437
|
init_types();
|
|
17749
18438
|
init_log();
|
|
17750
18439
|
monitorInterval = null;
|
|
18440
|
+
MONITOR_INTERVAL_MS = parseInt(process.env.CC_CLAW_MONITOR_INTERVAL_MS ?? "30000", 10);
|
|
17751
18441
|
}
|
|
17752
18442
|
});
|
|
17753
18443
|
|
|
@@ -17870,6 +18560,18 @@ var init_telegram = __esm({
|
|
|
17870
18560
|
|
|
17871
18561
|
// src/channels/telegram.ts
|
|
17872
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
|
+
}
|
|
17873
18575
|
function numericChatId(chatId) {
|
|
17874
18576
|
if (chatId.startsWith("sq:") || chatId.startsWith("cron:")) {
|
|
17875
18577
|
throw new Error(`Synthetic chatId "${chatId}" passed to Telegram API`);
|
|
@@ -17877,13 +18579,14 @@ function numericChatId(chatId) {
|
|
|
17877
18579
|
const raw = chatId.includes(":") ? chatId.split(":").pop() : chatId;
|
|
17878
18580
|
return parseInt(raw);
|
|
17879
18581
|
}
|
|
17880
|
-
var TelegramChannel;
|
|
18582
|
+
var FAST_PATH_COMMANDS, TelegramChannel;
|
|
17881
18583
|
var init_telegram2 = __esm({
|
|
17882
18584
|
"src/channels/telegram.ts"() {
|
|
17883
18585
|
"use strict";
|
|
17884
18586
|
init_telegram();
|
|
17885
18587
|
init_log();
|
|
17886
18588
|
init_store5();
|
|
18589
|
+
FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
|
|
17887
18590
|
TelegramChannel = class {
|
|
17888
18591
|
name = "telegram";
|
|
17889
18592
|
bot;
|
|
@@ -17995,41 +18698,48 @@ var init_telegram2 = __esm({
|
|
|
17995
18698
|
{ command: "evolve", description: "Self-learning & evolution controls" },
|
|
17996
18699
|
{ command: "reflect", description: "Trigger reflection analysis" }
|
|
17997
18700
|
]);
|
|
17998
|
-
this.bot.on("message", (ctx) => {
|
|
18701
|
+
this.bot.on("message", async (ctx) => {
|
|
17999
18702
|
const chatId = ctx.chat.id.toString();
|
|
18000
18703
|
const senderId = ctx.from?.id?.toString() ?? "";
|
|
18001
18704
|
const authorized = this.isAuthorized(chatId) || this.isAuthorized(senderId);
|
|
18002
18705
|
log(`[telegram] Message from chat ${chatId} sender ${senderId} (authorized: ${authorized})`);
|
|
18003
18706
|
if (!authorized) return;
|
|
18004
18707
|
const msg = this.buildIncomingMessage(ctx);
|
|
18005
|
-
if (msg) {
|
|
18006
|
-
log(`[telegram] Processing ${msg.type}: "${msg.text?.slice(0, 50) || msg.command || "(media)"}"`);
|
|
18007
|
-
handler(msg, this).catch((err) => {
|
|
18008
|
-
error("[telegram] Handler error:", err);
|
|
18009
|
-
});
|
|
18010
|
-
} else {
|
|
18708
|
+
if (!msg) {
|
|
18011
18709
|
this.sendText(chatId, "I can handle text, voice, photos, documents, and videos. This message type isn't supported yet.", { parseMode: "plain" }).catch(() => {
|
|
18012
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;
|
|
18013
18719
|
}
|
|
18720
|
+
handler(msg, this).catch((err) => {
|
|
18721
|
+
error("[telegram] Handler error:", err);
|
|
18722
|
+
});
|
|
18014
18723
|
});
|
|
18015
|
-
this.bot.on("callback_query:data",
|
|
18724
|
+
this.bot.on("callback_query:data", (ctx) => {
|
|
18016
18725
|
const userId = ctx.from.id.toString();
|
|
18017
18726
|
const chatId = ctx.callbackQuery.message?.chat?.id?.toString() ?? userId;
|
|
18018
18727
|
log(`[telegram] Callback from user ${userId} in chat ${chatId}: ${ctx.callbackQuery.data}`);
|
|
18019
18728
|
if (!this.isAuthorized(userId) && !this.isAuthorized(chatId)) {
|
|
18020
|
-
|
|
18729
|
+
ctx.answerCallbackQuery("Unauthorized").catch(() => {
|
|
18730
|
+
});
|
|
18021
18731
|
return;
|
|
18022
18732
|
}
|
|
18023
18733
|
const data = ctx.callbackQuery.data;
|
|
18024
|
-
|
|
18734
|
+
ctx.answerCallbackQuery().catch(() => {
|
|
18025
18735
|
});
|
|
18026
|
-
|
|
18736
|
+
(async () => {
|
|
18027
18737
|
for (const handler2 of this.callbackHandlers) {
|
|
18028
18738
|
await handler2(chatId, data, this);
|
|
18029
18739
|
}
|
|
18030
|
-
}
|
|
18740
|
+
})().catch((err) => {
|
|
18031
18741
|
error("[telegram] Callback handler error:", err);
|
|
18032
|
-
}
|
|
18742
|
+
});
|
|
18033
18743
|
});
|
|
18034
18744
|
this.bot.on("message_reaction", async (ctx) => {
|
|
18035
18745
|
const chatId = String(ctx.chat.id);
|
|
@@ -18362,19 +19072,19 @@ var init_telegram2 = __esm({
|
|
|
18362
19072
|
});
|
|
18363
19073
|
|
|
18364
19074
|
// src/skills/bootstrap.ts
|
|
18365
|
-
import { existsSync as
|
|
19075
|
+
import { existsSync as existsSync19 } from "fs";
|
|
18366
19076
|
import { readdir as readdir4, readFile as readFile6, writeFile as writeFile4, copyFile } from "fs/promises";
|
|
18367
19077
|
import { join as join21, dirname as dirname3 } from "path";
|
|
18368
19078
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18369
19079
|
async function copyAgentManifestSkills() {
|
|
18370
|
-
if (!
|
|
19080
|
+
if (!existsSync19(PKG_SKILLS)) return;
|
|
18371
19081
|
try {
|
|
18372
19082
|
const entries = await readdir4(PKG_SKILLS, { withFileTypes: true });
|
|
18373
19083
|
for (const entry of entries) {
|
|
18374
19084
|
if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
|
|
18375
19085
|
const src = join21(PKG_SKILLS, entry.name);
|
|
18376
19086
|
const dest = join21(SKILLS_PATH, entry.name);
|
|
18377
|
-
if (
|
|
19087
|
+
if (existsSync19(dest)) continue;
|
|
18378
19088
|
await copyFile(src, dest);
|
|
18379
19089
|
log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
|
|
18380
19090
|
}
|
|
@@ -18385,7 +19095,7 @@ async function copyAgentManifestSkills() {
|
|
|
18385
19095
|
async function bootstrapSkills() {
|
|
18386
19096
|
await copyAgentManifestSkills();
|
|
18387
19097
|
const usmDir = join21(SKILLS_PATH, USM_DIR_NAME);
|
|
18388
|
-
if (
|
|
19098
|
+
if (existsSync19(usmDir)) return;
|
|
18389
19099
|
try {
|
|
18390
19100
|
const entries = await readdir4(SKILLS_PATH);
|
|
18391
19101
|
const dirs = entries.filter((e) => !e.startsWith("."));
|
|
@@ -18408,7 +19118,7 @@ async function bootstrapSkills() {
|
|
|
18408
19118
|
}
|
|
18409
19119
|
async function patchUsmForCcClaw(usmDir) {
|
|
18410
19120
|
const skillPath = join21(usmDir, "SKILL.md");
|
|
18411
|
-
if (!
|
|
19121
|
+
if (!existsSync19(skillPath)) return;
|
|
18412
19122
|
try {
|
|
18413
19123
|
let content = await readFile6(skillPath, "utf-8");
|
|
18414
19124
|
let patched = false;
|
|
@@ -18668,13 +19378,13 @@ __export(ai_skill_exports, {
|
|
|
18668
19378
|
generateAiSkill: () => generateAiSkill,
|
|
18669
19379
|
installAiSkill: () => installAiSkill
|
|
18670
19380
|
});
|
|
18671
|
-
import { existsSync as
|
|
19381
|
+
import { existsSync as existsSync20, writeFileSync as writeFileSync7, mkdirSync as mkdirSync9 } from "fs";
|
|
18672
19382
|
import { join as join22 } from "path";
|
|
18673
19383
|
import { homedir as homedir7 } from "os";
|
|
18674
19384
|
function generateAiSkill() {
|
|
18675
19385
|
const version = VERSION;
|
|
18676
19386
|
let systemState = "";
|
|
18677
|
-
if (
|
|
19387
|
+
if (existsSync20(DB_PATH)) {
|
|
18678
19388
|
try {
|
|
18679
19389
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store5(), __toCommonJS(store_exports5));
|
|
18680
19390
|
const readDb = openDatabaseReadOnly2();
|
|
@@ -18723,6 +19433,14 @@ Use the CC-Claw CLI when you need to:
|
|
|
18723
19433
|
- Every command supports \`--json\` for machine-parseable output
|
|
18724
19434
|
- Use \`--chat <id>\` to target a specific chat context
|
|
18725
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
|
+
|
|
18726
19444
|
## Telegram Quick Reference
|
|
18727
19445
|
|
|
18728
19446
|
**Navigation & help:**
|
|
@@ -18779,8 +19497,7 @@ Use the CC-Claw CLI when you need to:
|
|
|
18779
19497
|
- \`/imagine <prompt>\` (\`/image\`) \u2014 Generate image (requires GEMINI_API_KEY)
|
|
18780
19498
|
- \`/skills\` \u2014 List skills
|
|
18781
19499
|
- \`/skill-install <url>\` \u2014 Install skill from GitHub
|
|
18782
|
-
- \`/evolve\` \u2014 Self-learning interactive keyboard
|
|
18783
|
-
- \`/reflect\` \u2014 Trigger reflection analysis
|
|
19500
|
+
- \`/evolve\` \u2014 Self-learning interactive keyboard (includes quick analyze)
|
|
18784
19501
|
- \`/gemini_accounts\` \u2014 Gemini credential rotation management
|
|
18785
19502
|
- \`/setup-profile\` \u2014 User profile setup wizard
|
|
18786
19503
|
|
|
@@ -18823,8 +19540,9 @@ cc-claw memory list --json # All memories with salience
|
|
|
18823
19540
|
cc-claw memory search "topic" --json # Search memories
|
|
18824
19541
|
cc-claw memory history --json # Session summaries
|
|
18825
19542
|
cc-claw memory history --limit 20 --json # Limit results
|
|
18826
|
-
|
|
18827
|
-
|
|
19543
|
+
# NOTE: To add/delete memories, use Telegram commands:
|
|
19544
|
+
# /remember <text> \u2014 Save a memory
|
|
19545
|
+
# /forget <keyword> \u2014 Delete memories
|
|
18828
19546
|
\`\`\`
|
|
18829
19547
|
|
|
18830
19548
|
### Session
|
|
@@ -19104,7 +19822,7 @@ var index_exports = {};
|
|
|
19104
19822
|
__export(index_exports, {
|
|
19105
19823
|
main: () => main
|
|
19106
19824
|
});
|
|
19107
|
-
import { mkdirSync as mkdirSync10, existsSync as
|
|
19825
|
+
import { mkdirSync as mkdirSync10, existsSync as existsSync21, renameSync, statSync as statSync5, readFileSync as readFileSync12 } from "fs";
|
|
19108
19826
|
import { join as join23 } from "path";
|
|
19109
19827
|
import dotenv from "dotenv";
|
|
19110
19828
|
function migrateLayout() {
|
|
@@ -19118,7 +19836,7 @@ function migrateLayout() {
|
|
|
19118
19836
|
[join23(CC_CLAW_HOME, "cc-claw.error.log.1"), join23(LOGS_PATH, "cc-claw.error.log.1")]
|
|
19119
19837
|
];
|
|
19120
19838
|
for (const [from, to] of moves) {
|
|
19121
|
-
if (
|
|
19839
|
+
if (existsSync21(from) && !existsSync21(to)) {
|
|
19122
19840
|
try {
|
|
19123
19841
|
renameSync(from, to);
|
|
19124
19842
|
} catch {
|
|
@@ -19154,6 +19872,11 @@ async function main() {
|
|
|
19154
19872
|
initDatabase();
|
|
19155
19873
|
pruneMessageLog(30, 2e3);
|
|
19156
19874
|
bootstrapBuiltinMcps(getDb());
|
|
19875
|
+
try {
|
|
19876
|
+
const { resetIntentStats: resetIntentStats2 } = await Promise.resolve().then(() => (init_classify(), classify_exports));
|
|
19877
|
+
resetIntentStats2();
|
|
19878
|
+
} catch {
|
|
19879
|
+
}
|
|
19157
19880
|
try {
|
|
19158
19881
|
const { getAdapter: getAdapter2, probeBackendAvailability: probeBackendAvailability2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
19159
19882
|
probeBackendAvailability2();
|
|
@@ -19327,10 +20050,10 @@ var init_index = __esm({
|
|
|
19327
20050
|
init_bootstrap2();
|
|
19328
20051
|
init_health3();
|
|
19329
20052
|
for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
|
|
19330
|
-
if (!
|
|
20053
|
+
if (!existsSync21(dir)) mkdirSync10(dir, { recursive: true });
|
|
19331
20054
|
}
|
|
19332
20055
|
migrateLayout();
|
|
19333
|
-
if (
|
|
20056
|
+
if (existsSync21(ENV_PATH)) {
|
|
19334
20057
|
dotenv.config({ path: ENV_PATH });
|
|
19335
20058
|
} else {
|
|
19336
20059
|
console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
|
|
@@ -19351,12 +20074,12 @@ __export(api_client_exports, {
|
|
|
19351
20074
|
apiPost: () => apiPost,
|
|
19352
20075
|
isDaemonRunning: () => isDaemonRunning
|
|
19353
20076
|
});
|
|
19354
|
-
import { readFileSync as readFileSync13, existsSync as
|
|
20077
|
+
import { readFileSync as readFileSync13, existsSync as existsSync22 } from "fs";
|
|
19355
20078
|
import { request as httpRequest } from "http";
|
|
19356
20079
|
function getToken() {
|
|
19357
20080
|
if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
|
|
19358
20081
|
try {
|
|
19359
|
-
if (
|
|
20082
|
+
if (existsSync22(TOKEN_PATH)) return readFileSync13(TOKEN_PATH, "utf-8").trim();
|
|
19360
20083
|
} catch {
|
|
19361
20084
|
}
|
|
19362
20085
|
return null;
|
|
@@ -19451,15 +20174,20 @@ __export(service_exports, {
|
|
|
19451
20174
|
serviceStatus: () => serviceStatus,
|
|
19452
20175
|
uninstallService: () => uninstallService
|
|
19453
20176
|
});
|
|
19454
|
-
import { existsSync as
|
|
19455
|
-
import { execFileSync as
|
|
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";
|
|
19456
20179
|
import { homedir as homedir8, platform } from "os";
|
|
19457
20180
|
import { join as join24, dirname as dirname4 } from "path";
|
|
20181
|
+
function xmlEscape(s) {
|
|
20182
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
20183
|
+
}
|
|
19458
20184
|
function resolveExecutable2(name) {
|
|
19459
20185
|
try {
|
|
19460
|
-
return
|
|
20186
|
+
return execFileSync3("which", [name], { encoding: "utf-8" }).trim();
|
|
19461
20187
|
} catch {
|
|
19462
|
-
|
|
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`);
|
|
19463
20191
|
}
|
|
19464
20192
|
}
|
|
19465
20193
|
function getPathDirs() {
|
|
@@ -19483,21 +20211,28 @@ function generatePlist() {
|
|
|
19483
20211
|
const ccClawBin = resolveExecutable2("cc-claw");
|
|
19484
20212
|
const pathDirs = getPathDirs();
|
|
19485
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);
|
|
19486
20221
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
19487
20222
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
19488
20223
|
<plist version="1.0">
|
|
19489
20224
|
<dict>
|
|
19490
20225
|
<key>Label</key>
|
|
19491
|
-
<string>${
|
|
20226
|
+
<string>${safeLabel}</string>
|
|
19492
20227
|
|
|
19493
20228
|
<key>ProgramArguments</key>
|
|
19494
20229
|
<array>
|
|
19495
|
-
<string>${
|
|
20230
|
+
<string>${safeBin}</string>
|
|
19496
20231
|
<string>start</string>
|
|
19497
20232
|
</array>
|
|
19498
20233
|
|
|
19499
20234
|
<key>WorkingDirectory</key>
|
|
19500
|
-
<string>${
|
|
20235
|
+
<string>${safeCCHome}</string>
|
|
19501
20236
|
|
|
19502
20237
|
<key>RunAtLoad</key>
|
|
19503
20238
|
<true/>
|
|
@@ -19506,17 +20241,17 @@ function generatePlist() {
|
|
|
19506
20241
|
<true/>
|
|
19507
20242
|
|
|
19508
20243
|
<key>StandardOutPath</key>
|
|
19509
|
-
<string>${
|
|
20244
|
+
<string>${safeLog}</string>
|
|
19510
20245
|
|
|
19511
20246
|
<key>StandardErrorPath</key>
|
|
19512
|
-
<string>${
|
|
20247
|
+
<string>${safeErrLog}</string>
|
|
19513
20248
|
|
|
19514
20249
|
<key>EnvironmentVariables</key>
|
|
19515
20250
|
<dict>
|
|
19516
20251
|
<key>PATH</key>
|
|
19517
|
-
<string>${
|
|
20252
|
+
<string>${safePaths}</string>
|
|
19518
20253
|
<key>HOME</key>
|
|
19519
|
-
<string>${
|
|
20254
|
+
<string>${safeHome}</string>
|
|
19520
20255
|
</dict>
|
|
19521
20256
|
|
|
19522
20257
|
<key>ThrottleInterval</key>
|
|
@@ -19526,26 +20261,26 @@ function generatePlist() {
|
|
|
19526
20261
|
}
|
|
19527
20262
|
function installMacOS() {
|
|
19528
20263
|
const agentsDir = dirname4(PLIST_PATH);
|
|
19529
|
-
if (!
|
|
19530
|
-
if (!
|
|
19531
|
-
if (
|
|
20264
|
+
if (!existsSync23(agentsDir)) mkdirSync11(agentsDir, { recursive: true });
|
|
20265
|
+
if (!existsSync23(LOGS_PATH)) mkdirSync11(LOGS_PATH, { recursive: true });
|
|
20266
|
+
if (existsSync23(PLIST_PATH)) {
|
|
19532
20267
|
try {
|
|
19533
|
-
|
|
20268
|
+
execFileSync3("launchctl", ["unload", PLIST_PATH]);
|
|
19534
20269
|
} catch {
|
|
19535
20270
|
}
|
|
19536
20271
|
}
|
|
19537
20272
|
writeFileSync8(PLIST_PATH, generatePlist());
|
|
19538
20273
|
console.log(` Installed: ${PLIST_PATH}`);
|
|
19539
|
-
|
|
20274
|
+
execFileSync3("launchctl", ["load", PLIST_PATH]);
|
|
19540
20275
|
console.log(" Service loaded and starting.");
|
|
19541
20276
|
}
|
|
19542
20277
|
function uninstallMacOS() {
|
|
19543
|
-
if (!
|
|
20278
|
+
if (!existsSync23(PLIST_PATH)) {
|
|
19544
20279
|
console.log(" No service found to uninstall.");
|
|
19545
20280
|
return;
|
|
19546
20281
|
}
|
|
19547
20282
|
try {
|
|
19548
|
-
|
|
20283
|
+
execFileSync3("launchctl", ["unload", PLIST_PATH]);
|
|
19549
20284
|
} catch {
|
|
19550
20285
|
}
|
|
19551
20286
|
unlinkSync6(PLIST_PATH);
|
|
@@ -19561,24 +20296,25 @@ function formatUptime(seconds) {
|
|
|
19561
20296
|
async function getUptimeFromDaemon() {
|
|
19562
20297
|
try {
|
|
19563
20298
|
const { isDaemonRunning: isDaemonRunning2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
19564
|
-
if (!await isDaemonRunning2()) return
|
|
20299
|
+
if (!await isDaemonRunning2()) return { status: "down" };
|
|
19565
20300
|
const res = await apiGet2("/api/health");
|
|
19566
20301
|
const sec = res.data?.uptime;
|
|
19567
|
-
return sec ? formatUptime(sec) :
|
|
20302
|
+
return sec ? { status: "ok", uptime: formatUptime(sec) } : { status: "down" };
|
|
19568
20303
|
} catch {
|
|
19569
|
-
return
|
|
20304
|
+
return { status: "error" };
|
|
19570
20305
|
}
|
|
19571
20306
|
}
|
|
19572
20307
|
function statusMacOS() {
|
|
19573
20308
|
try {
|
|
19574
|
-
const out =
|
|
19575
|
-
|
|
19576
|
-
|
|
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+/);
|
|
19577
20313
|
const pid = parts[0];
|
|
19578
20314
|
const exitCode = parts[1];
|
|
19579
20315
|
if (pid !== "-") {
|
|
19580
|
-
getUptimeFromDaemon().then((
|
|
19581
|
-
const uptimeStr =
|
|
20316
|
+
getUptimeFromDaemon().then((result) => {
|
|
20317
|
+
const uptimeStr = result.status === "ok" ? `, uptime ${result.uptime}` : "";
|
|
19582
20318
|
console.log(` Running (PID ${pid}${uptimeStr})`);
|
|
19583
20319
|
}).catch(() => {
|
|
19584
20320
|
console.log(` Running (PID ${pid})`);
|
|
@@ -19614,30 +20350,30 @@ WantedBy=default.target
|
|
|
19614
20350
|
`;
|
|
19615
20351
|
}
|
|
19616
20352
|
function installLinux() {
|
|
19617
|
-
if (!
|
|
19618
|
-
if (!
|
|
20353
|
+
if (!existsSync23(SYSTEMD_DIR)) mkdirSync11(SYSTEMD_DIR, { recursive: true });
|
|
20354
|
+
if (!existsSync23(LOGS_PATH)) mkdirSync11(LOGS_PATH, { recursive: true });
|
|
19619
20355
|
writeFileSync8(UNIT_PATH, generateUnit());
|
|
19620
20356
|
console.log(` Installed: ${UNIT_PATH}`);
|
|
19621
|
-
|
|
19622
|
-
|
|
19623
|
-
|
|
20357
|
+
execFileSync3("systemctl", ["--user", "daemon-reload"]);
|
|
20358
|
+
execFileSync3("systemctl", ["--user", "enable", "cc-claw"]);
|
|
20359
|
+
execFileSync3("systemctl", ["--user", "start", "cc-claw"]);
|
|
19624
20360
|
console.log(" Service enabled and started.");
|
|
19625
20361
|
}
|
|
19626
20362
|
function uninstallLinux() {
|
|
19627
|
-
if (!
|
|
20363
|
+
if (!existsSync23(UNIT_PATH)) {
|
|
19628
20364
|
console.log(" No service found to uninstall.");
|
|
19629
20365
|
return;
|
|
19630
20366
|
}
|
|
19631
20367
|
try {
|
|
19632
|
-
|
|
20368
|
+
execFileSync3("systemctl", ["--user", "stop", "cc-claw"]);
|
|
19633
20369
|
} catch {
|
|
19634
20370
|
}
|
|
19635
20371
|
try {
|
|
19636
|
-
|
|
20372
|
+
execFileSync3("systemctl", ["--user", "disable", "cc-claw"]);
|
|
19637
20373
|
} catch {
|
|
19638
20374
|
}
|
|
19639
20375
|
unlinkSync6(UNIT_PATH);
|
|
19640
|
-
|
|
20376
|
+
execFileSync3("systemctl", ["--user", "daemon-reload"]);
|
|
19641
20377
|
console.log(" Service uninstalled.");
|
|
19642
20378
|
}
|
|
19643
20379
|
function statusLinux() {
|
|
@@ -19649,28 +20385,28 @@ function statusLinux() {
|
|
|
19649
20385
|
}
|
|
19650
20386
|
}
|
|
19651
20387
|
function installService() {
|
|
19652
|
-
if (!
|
|
20388
|
+
if (!existsSync23(join24(CC_CLAW_HOME, ".env"))) {
|
|
19653
20389
|
console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
|
|
19654
20390
|
console.error(" Run 'cc-claw setup' before installing the service.");
|
|
19655
20391
|
process.exitCode = 1;
|
|
19656
20392
|
return;
|
|
19657
20393
|
}
|
|
19658
|
-
const
|
|
19659
|
-
if (
|
|
19660
|
-
else if (
|
|
19661
|
-
else console.error(` Unsupported platform: ${
|
|
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.`);
|
|
19662
20398
|
}
|
|
19663
20399
|
function uninstallService() {
|
|
19664
|
-
const
|
|
19665
|
-
if (
|
|
19666
|
-
else if (
|
|
19667
|
-
else console.error(` Unsupported platform: ${
|
|
20400
|
+
const os2 = platform();
|
|
20401
|
+
if (os2 === "darwin") uninstallMacOS();
|
|
20402
|
+
else if (os2 === "linux") uninstallLinux();
|
|
20403
|
+
else console.error(` Unsupported platform: ${os2}.`);
|
|
19668
20404
|
}
|
|
19669
20405
|
function serviceStatus() {
|
|
19670
|
-
const
|
|
19671
|
-
if (
|
|
19672
|
-
else if (
|
|
19673
|
-
else console.error(` Unsupported platform: ${
|
|
20406
|
+
const os2 = platform();
|
|
20407
|
+
if (os2 === "darwin") statusMacOS();
|
|
20408
|
+
else if (os2 === "linux") statusLinux();
|
|
20409
|
+
else console.error(` Unsupported platform: ${os2}.`);
|
|
19674
20410
|
}
|
|
19675
20411
|
var PLIST_LABEL, PLIST_PATH, SYSTEMD_DIR, UNIT_PATH;
|
|
19676
20412
|
var init_service = __esm({
|
|
@@ -19793,20 +20529,20 @@ __export(daemon_exports, {
|
|
|
19793
20529
|
import { execSync as execSync8 } from "child_process";
|
|
19794
20530
|
import { platform as platform2 } from "os";
|
|
19795
20531
|
async function stopService() {
|
|
19796
|
-
const
|
|
20532
|
+
const os2 = platform2();
|
|
19797
20533
|
try {
|
|
19798
|
-
if (
|
|
20534
|
+
if (os2 === "darwin") {
|
|
19799
20535
|
execSync8("launchctl stop com.cc-claw");
|
|
19800
20536
|
console.log(`
|
|
19801
20537
|
${success("Daemon stopped.")}
|
|
19802
20538
|
`);
|
|
19803
|
-
} else if (
|
|
20539
|
+
} else if (os2 === "linux") {
|
|
19804
20540
|
execSync8("systemctl --user stop cc-claw");
|
|
19805
20541
|
console.log(`
|
|
19806
20542
|
${success("Daemon stopped.")}
|
|
19807
20543
|
`);
|
|
19808
20544
|
} else {
|
|
19809
|
-
outputError("UNSUPPORTED_PLATFORM", `Platform ${
|
|
20545
|
+
outputError("UNSUPPORTED_PLATFORM", `Platform ${os2} not supported for service management.`);
|
|
19810
20546
|
process.exit(1);
|
|
19811
20547
|
}
|
|
19812
20548
|
} catch (err) {
|
|
@@ -19816,21 +20552,21 @@ async function stopService() {
|
|
|
19816
20552
|
}
|
|
19817
20553
|
}
|
|
19818
20554
|
async function restartService() {
|
|
19819
|
-
const
|
|
20555
|
+
const os2 = platform2();
|
|
19820
20556
|
try {
|
|
19821
|
-
if (
|
|
20557
|
+
if (os2 === "darwin") {
|
|
19822
20558
|
const uid = process.getuid?.() ?? execSync8("id -u", { encoding: "utf-8" }).trim();
|
|
19823
20559
|
execSync8(`launchctl kickstart -k gui/${uid}/com.cc-claw`);
|
|
19824
20560
|
console.log(`
|
|
19825
20561
|
${success("Daemon restarted.")}
|
|
19826
20562
|
`);
|
|
19827
|
-
} else if (
|
|
20563
|
+
} else if (os2 === "linux") {
|
|
19828
20564
|
execSync8("systemctl --user restart cc-claw");
|
|
19829
20565
|
console.log(`
|
|
19830
20566
|
${success("Daemon restarted.")}
|
|
19831
20567
|
`);
|
|
19832
20568
|
} else {
|
|
19833
|
-
outputError("UNSUPPORTED_PLATFORM", `Platform ${
|
|
20569
|
+
outputError("UNSUPPORTED_PLATFORM", `Platform ${os2} not supported for service management.`);
|
|
19834
20570
|
process.exit(1);
|
|
19835
20571
|
}
|
|
19836
20572
|
} catch (err) {
|
|
@@ -19877,7 +20613,7 @@ var status_exports = {};
|
|
|
19877
20613
|
__export(status_exports, {
|
|
19878
20614
|
statusCommand: () => statusCommand
|
|
19879
20615
|
});
|
|
19880
|
-
import { existsSync as
|
|
20616
|
+
import { existsSync as existsSync24, statSync as statSync6 } from "fs";
|
|
19881
20617
|
async function statusCommand(globalOpts, localOpts) {
|
|
19882
20618
|
try {
|
|
19883
20619
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
@@ -19917,7 +20653,7 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
19917
20653
|
const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
|
|
19918
20654
|
const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
|
|
19919
20655
|
const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
|
|
19920
|
-
const dbStat =
|
|
20656
|
+
const dbStat = existsSync24(DB_PATH) ? statSync6(DB_PATH) : null;
|
|
19921
20657
|
let daemonRunning = false;
|
|
19922
20658
|
let daemonInfo = {};
|
|
19923
20659
|
if (localOpts.deep) {
|
|
@@ -20008,11 +20744,11 @@ var doctor_exports = {};
|
|
|
20008
20744
|
__export(doctor_exports, {
|
|
20009
20745
|
doctorCommand: () => doctorCommand
|
|
20010
20746
|
});
|
|
20011
|
-
import { existsSync as
|
|
20012
|
-
import { execFileSync as
|
|
20747
|
+
import { existsSync as existsSync25, statSync as statSync7, accessSync, constants } from "fs";
|
|
20748
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
20013
20749
|
async function doctorCommand(globalOpts, localOpts) {
|
|
20014
20750
|
const checks = [];
|
|
20015
|
-
if (
|
|
20751
|
+
if (existsSync25(DB_PATH)) {
|
|
20016
20752
|
const size = statSync7(DB_PATH).size;
|
|
20017
20753
|
checks.push({ name: "Database", status: "ok", message: `${DB_PATH} (${(size / 1024).toFixed(0)}KB)` });
|
|
20018
20754
|
try {
|
|
@@ -20042,7 +20778,7 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
20042
20778
|
} else {
|
|
20043
20779
|
checks.push({ name: "Database", status: "error", message: `Not found at ${DB_PATH}`, fix: "cc-claw setup" });
|
|
20044
20780
|
}
|
|
20045
|
-
if (
|
|
20781
|
+
if (existsSync25(ENV_PATH)) {
|
|
20046
20782
|
checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
|
|
20047
20783
|
} else {
|
|
20048
20784
|
checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
|
|
@@ -20051,7 +20787,7 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
20051
20787
|
let installedBackends = 0;
|
|
20052
20788
|
for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
|
|
20053
20789
|
try {
|
|
20054
|
-
const path =
|
|
20790
|
+
const path = execFileSync4("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
20055
20791
|
checks.push({ name: `${label2} CLI`, status: "ok", message: path });
|
|
20056
20792
|
installedBackends++;
|
|
20057
20793
|
} catch {
|
|
@@ -20090,14 +20826,14 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
20090
20826
|
checks.push({ name: "Daemon", status: "warning", message: "could not probe" });
|
|
20091
20827
|
}
|
|
20092
20828
|
try {
|
|
20093
|
-
const latest =
|
|
20829
|
+
const latest = execFileSync4("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
20094
20830
|
if (latest && latest !== VERSION) {
|
|
20095
20831
|
checks.push({ name: "Update available", status: "warning", message: `v${latest} available (current: v${VERSION})`, fix: "npm install -g cc-claw@latest" });
|
|
20096
20832
|
}
|
|
20097
20833
|
} catch {
|
|
20098
20834
|
}
|
|
20099
20835
|
const tokenPath = `${DATA_PATH}/api-token`;
|
|
20100
|
-
if (
|
|
20836
|
+
if (existsSync25(tokenPath)) {
|
|
20101
20837
|
try {
|
|
20102
20838
|
accessSync(tokenPath, constants.R_OK);
|
|
20103
20839
|
checks.push({ name: "API token", status: "ok", message: "token file readable" });
|
|
@@ -20122,7 +20858,7 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
20122
20858
|
}
|
|
20123
20859
|
} catch {
|
|
20124
20860
|
}
|
|
20125
|
-
if (
|
|
20861
|
+
if (existsSync25(ERROR_LOG_PATH)) {
|
|
20126
20862
|
try {
|
|
20127
20863
|
const { readFileSync: readFileSync21 } = await import("fs");
|
|
20128
20864
|
const logContent = readFileSync21(ERROR_LOG_PATH, "utf-8");
|
|
@@ -20150,15 +20886,15 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
20150
20886
|
if (check.name === "Daemon" && check.fix?.includes("service start")) {
|
|
20151
20887
|
try {
|
|
20152
20888
|
const { execSync: execSync9 } = await import("child_process");
|
|
20153
|
-
const
|
|
20154
|
-
if (
|
|
20889
|
+
const os2 = (await import("os")).platform();
|
|
20890
|
+
if (os2 === "darwin") {
|
|
20155
20891
|
try {
|
|
20156
20892
|
execSync9("launchctl start com.cc-claw");
|
|
20157
20893
|
check.status = "ok";
|
|
20158
20894
|
check.message = "restarted";
|
|
20159
20895
|
} catch {
|
|
20160
20896
|
}
|
|
20161
|
-
} else if (
|
|
20897
|
+
} else if (os2 === "linux") {
|
|
20162
20898
|
try {
|
|
20163
20899
|
execSync9("systemctl --user start cc-claw");
|
|
20164
20900
|
check.status = "ok";
|
|
@@ -20248,10 +20984,10 @@ var logs_exports = {};
|
|
|
20248
20984
|
__export(logs_exports, {
|
|
20249
20985
|
logsCommand: () => logsCommand
|
|
20250
20986
|
});
|
|
20251
|
-
import { existsSync as
|
|
20987
|
+
import { existsSync as existsSync26, readFileSync as readFileSync16, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
|
|
20252
20988
|
async function logsCommand(opts) {
|
|
20253
20989
|
const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
|
|
20254
|
-
if (!
|
|
20990
|
+
if (!existsSync26(logFile)) {
|
|
20255
20991
|
outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
|
|
20256
20992
|
process.exit(1);
|
|
20257
20993
|
}
|
|
@@ -20303,11 +21039,11 @@ __export(gemini_exports, {
|
|
|
20303
21039
|
geminiReorder: () => geminiReorder,
|
|
20304
21040
|
geminiRotation: () => geminiRotation
|
|
20305
21041
|
});
|
|
20306
|
-
import { existsSync as
|
|
21042
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readFileSync as readFileSync17, chmodSync } from "fs";
|
|
20307
21043
|
import { join as join25 } from "path";
|
|
20308
21044
|
import { createInterface as createInterface5 } from "readline";
|
|
20309
21045
|
function requireDb() {
|
|
20310
|
-
if (!
|
|
21046
|
+
if (!existsSync27(DB_PATH)) {
|
|
20311
21047
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
20312
21048
|
process.exit(1);
|
|
20313
21049
|
}
|
|
@@ -20333,7 +21069,7 @@ function resolveOAuthEmail(configHome) {
|
|
|
20333
21069
|
if (!configHome) return null;
|
|
20334
21070
|
try {
|
|
20335
21071
|
const accountsPath = join25(configHome, ".gemini", "google_accounts.json");
|
|
20336
|
-
if (!
|
|
21072
|
+
if (!existsSync27(accountsPath)) return null;
|
|
20337
21073
|
const accounts = JSON.parse(readFileSync17(accountsPath, "utf-8"));
|
|
20338
21074
|
return accounts.active || null;
|
|
20339
21075
|
} catch {
|
|
@@ -20417,7 +21153,7 @@ async function geminiAddKey(globalOpts, opts) {
|
|
|
20417
21153
|
async function geminiAddAccount(globalOpts, opts) {
|
|
20418
21154
|
await requireWriteDb();
|
|
20419
21155
|
const slotsDir = join25(CC_CLAW_HOME, "gemini-slots");
|
|
20420
|
-
if (!
|
|
21156
|
+
if (!existsSync27(slotsDir)) mkdirSync12(slotsDir, { recursive: true });
|
|
20421
21157
|
const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
20422
21158
|
const tempId = Date.now();
|
|
20423
21159
|
const slotDir = join25(slotsDir, `slot-${tempId}`);
|
|
@@ -20441,7 +21177,7 @@ async function geminiAddAccount(globalOpts, opts) {
|
|
|
20441
21177
|
} catch {
|
|
20442
21178
|
}
|
|
20443
21179
|
const oauthPath = join25(slotDir, ".gemini", "oauth_creds.json");
|
|
20444
|
-
if (!
|
|
21180
|
+
if (!existsSync27(oauthPath)) {
|
|
20445
21181
|
console.log(error2("\n No OAuth credentials found. Sign-in may have failed."));
|
|
20446
21182
|
console.log(" The slot directory is preserved at: " + slotDir);
|
|
20447
21183
|
console.log(" Re-run: cc-claw gemini add-account\n");
|
|
@@ -20555,12 +21291,12 @@ __export(backend_exports, {
|
|
|
20555
21291
|
backendList: () => backendList,
|
|
20556
21292
|
backendSet: () => backendSet
|
|
20557
21293
|
});
|
|
20558
|
-
import { existsSync as
|
|
21294
|
+
import { existsSync as existsSync28 } from "fs";
|
|
20559
21295
|
async function backendList(globalOpts) {
|
|
20560
21296
|
const { getAvailableAdapters: getAvailableAdapters2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
20561
21297
|
const chatId = resolveChatId(globalOpts);
|
|
20562
21298
|
let activeBackend = null;
|
|
20563
|
-
if (
|
|
21299
|
+
if (existsSync28(DB_PATH)) {
|
|
20564
21300
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
20565
21301
|
const readDb = openDatabaseReadOnly2();
|
|
20566
21302
|
try {
|
|
@@ -20591,7 +21327,7 @@ async function backendList(globalOpts) {
|
|
|
20591
21327
|
}
|
|
20592
21328
|
async function backendGet(globalOpts) {
|
|
20593
21329
|
const chatId = resolveChatId(globalOpts);
|
|
20594
|
-
if (!
|
|
21330
|
+
if (!existsSync28(DB_PATH)) {
|
|
20595
21331
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
20596
21332
|
process.exit(1);
|
|
20597
21333
|
}
|
|
@@ -20635,13 +21371,13 @@ __export(model_exports, {
|
|
|
20635
21371
|
modelList: () => modelList,
|
|
20636
21372
|
modelSet: () => modelSet
|
|
20637
21373
|
});
|
|
20638
|
-
import { existsSync as
|
|
21374
|
+
import { existsSync as existsSync29 } from "fs";
|
|
20639
21375
|
async function modelList(globalOpts) {
|
|
20640
21376
|
const chatId = resolveChatId(globalOpts);
|
|
20641
21377
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
20642
21378
|
const { getAdapter: getAdapter2, getAllAdapters: getAllAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
20643
21379
|
let backendId = "claude";
|
|
20644
|
-
if (
|
|
21380
|
+
if (existsSync29(DB_PATH)) {
|
|
20645
21381
|
const readDb = openDatabaseReadOnly2();
|
|
20646
21382
|
try {
|
|
20647
21383
|
const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
|
|
@@ -20674,7 +21410,7 @@ async function modelList(globalOpts) {
|
|
|
20674
21410
|
}
|
|
20675
21411
|
async function modelGet(globalOpts) {
|
|
20676
21412
|
const chatId = resolveChatId(globalOpts);
|
|
20677
|
-
if (!
|
|
21413
|
+
if (!existsSync29(DB_PATH)) {
|
|
20678
21414
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
20679
21415
|
process.exit(1);
|
|
20680
21416
|
}
|
|
@@ -20718,9 +21454,9 @@ __export(memory_exports, {
|
|
|
20718
21454
|
memoryList: () => memoryList,
|
|
20719
21455
|
memorySearch: () => memorySearch
|
|
20720
21456
|
});
|
|
20721
|
-
import { existsSync as
|
|
21457
|
+
import { existsSync as existsSync30 } from "fs";
|
|
20722
21458
|
async function memoryList(globalOpts) {
|
|
20723
|
-
if (!
|
|
21459
|
+
if (!existsSync30(DB_PATH)) {
|
|
20724
21460
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
20725
21461
|
process.exit(1);
|
|
20726
21462
|
}
|
|
@@ -20744,7 +21480,7 @@ async function memoryList(globalOpts) {
|
|
|
20744
21480
|
});
|
|
20745
21481
|
}
|
|
20746
21482
|
async function memorySearch(globalOpts, query) {
|
|
20747
|
-
if (!
|
|
21483
|
+
if (!existsSync30(DB_PATH)) {
|
|
20748
21484
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
20749
21485
|
process.exit(1);
|
|
20750
21486
|
}
|
|
@@ -20766,7 +21502,7 @@ async function memorySearch(globalOpts, query) {
|
|
|
20766
21502
|
});
|
|
20767
21503
|
}
|
|
20768
21504
|
async function memoryHistory(globalOpts, opts) {
|
|
20769
|
-
if (!
|
|
21505
|
+
if (!existsSync30(DB_PATH)) {
|
|
20770
21506
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
20771
21507
|
process.exit(1);
|
|
20772
21508
|
}
|
|
@@ -20814,7 +21550,7 @@ __export(cron_exports2, {
|
|
|
20814
21550
|
cronList: () => cronList,
|
|
20815
21551
|
cronRuns: () => cronRuns
|
|
20816
21552
|
});
|
|
20817
|
-
import { existsSync as
|
|
21553
|
+
import { existsSync as existsSync31 } from "fs";
|
|
20818
21554
|
function parseFallbacks(raw) {
|
|
20819
21555
|
return raw.slice(0, 3).map((f) => {
|
|
20820
21556
|
const [backend2, ...rest] = f.split(":");
|
|
@@ -20835,7 +21571,7 @@ function parseAndValidateTimeout(raw) {
|
|
|
20835
21571
|
return val;
|
|
20836
21572
|
}
|
|
20837
21573
|
async function cronList(globalOpts) {
|
|
20838
|
-
if (!
|
|
21574
|
+
if (!existsSync31(DB_PATH)) {
|
|
20839
21575
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
20840
21576
|
process.exit(1);
|
|
20841
21577
|
}
|
|
@@ -20873,7 +21609,7 @@ async function cronList(globalOpts) {
|
|
|
20873
21609
|
});
|
|
20874
21610
|
}
|
|
20875
21611
|
async function cronHealth(globalOpts) {
|
|
20876
|
-
if (!
|
|
21612
|
+
if (!existsSync31(DB_PATH)) {
|
|
20877
21613
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
20878
21614
|
process.exit(1);
|
|
20879
21615
|
}
|
|
@@ -20902,7 +21638,7 @@ async function cronHealth(globalOpts) {
|
|
|
20902
21638
|
});
|
|
20903
21639
|
}
|
|
20904
21640
|
async function cronCreate(globalOpts, opts) {
|
|
20905
|
-
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));
|
|
20906
21642
|
if (!await isDaemonRunning2()) {
|
|
20907
21643
|
outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
|
|
20908
21644
|
process.exit(1);
|
|
@@ -20915,8 +21651,6 @@ async function cronCreate(globalOpts, opts) {
|
|
|
20915
21651
|
const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20916
21652
|
const timeout = parseAndValidateTimeout(opts.timeout);
|
|
20917
21653
|
try {
|
|
20918
|
-
const { initDatabase: initDatabase2, insertJob: insertJob2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
20919
|
-
initDatabase2();
|
|
20920
21654
|
const schedType = opts.cron ? "cron" : opts.at ? "at" : "every";
|
|
20921
21655
|
let everyMs = null;
|
|
20922
21656
|
if (opts.every) {
|
|
@@ -20928,7 +21662,7 @@ async function cronCreate(globalOpts, opts) {
|
|
|
20928
21662
|
}
|
|
20929
21663
|
}
|
|
20930
21664
|
const fallbacks = opts.fallback?.length ? parseFallbacks(opts.fallback) : void 0;
|
|
20931
|
-
const
|
|
21665
|
+
const res = await apiPost2("/api/cron/create", {
|
|
20932
21666
|
scheduleType: schedType,
|
|
20933
21667
|
cron: opts.cron ?? null,
|
|
20934
21668
|
atTime: opts.at ?? null,
|
|
@@ -20947,9 +21681,14 @@ async function cronCreate(globalOpts, opts) {
|
|
|
20947
21681
|
target: opts.target ?? null,
|
|
20948
21682
|
timezone: opts.timezone ?? "UTC"
|
|
20949
21683
|
});
|
|
20950
|
-
|
|
20951
|
-
|
|
21684
|
+
if (res.ok) {
|
|
21685
|
+
output({ id: res.data.id, success: true }, () => `
|
|
21686
|
+
${successFmt(`Job #${res.data.id} created.`)}
|
|
20952
21687
|
`);
|
|
21688
|
+
} else {
|
|
21689
|
+
outputError("CREATE_FAILED", `Failed: ${JSON.stringify(res.data)}`);
|
|
21690
|
+
process.exit(1);
|
|
21691
|
+
}
|
|
20953
21692
|
} catch (err) {
|
|
20954
21693
|
outputError("CREATE_FAILED", err.message);
|
|
20955
21694
|
process.exit(1);
|
|
@@ -20975,7 +21714,7 @@ async function cronAction(globalOpts, action, id) {
|
|
|
20975
21714
|
}
|
|
20976
21715
|
}
|
|
20977
21716
|
async function cronEdit(globalOpts, id, opts) {
|
|
20978
|
-
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));
|
|
20979
21718
|
if (!await isDaemonRunning2()) {
|
|
20980
21719
|
outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
|
|
20981
21720
|
process.exit(1);
|
|
@@ -20983,82 +21722,53 @@ async function cronEdit(globalOpts, id, opts) {
|
|
|
20983
21722
|
const jobId = parseInt(id, 10);
|
|
20984
21723
|
const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20985
21724
|
try {
|
|
20986
|
-
const
|
|
20987
|
-
|
|
20988
|
-
|
|
20989
|
-
|
|
20990
|
-
|
|
20991
|
-
if (opts.title) {
|
|
20992
|
-
updates.push("title = ?");
|
|
20993
|
-
values.push(opts.title);
|
|
20994
|
-
}
|
|
20995
|
-
if (opts.description) {
|
|
20996
|
-
updates.push("description = ?");
|
|
20997
|
-
values.push(opts.description);
|
|
20998
|
-
}
|
|
20999
|
-
if (opts.cron) {
|
|
21000
|
-
updates.push("cron = ?, schedule_type = 'cron'");
|
|
21001
|
-
values.push(opts.cron);
|
|
21002
|
-
}
|
|
21003
|
-
if (opts.at) {
|
|
21004
|
-
updates.push("at_time = ?, schedule_type = 'at'");
|
|
21005
|
-
values.push(opts.at);
|
|
21006
|
-
}
|
|
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;
|
|
21007
21730
|
if (opts.every) {
|
|
21008
21731
|
const m = opts.every.match(/^(\d+)\s*(m|min|h|hr|s|sec)$/i);
|
|
21009
21732
|
if (m) {
|
|
21010
21733
|
const num = parseInt(m[1], 10);
|
|
21011
21734
|
const unit = m[2].toLowerCase();
|
|
21012
|
-
|
|
21013
|
-
updates.push("every_ms = ?, schedule_type = 'every'");
|
|
21014
|
-
values.push(ms);
|
|
21735
|
+
payload.everyMs = unit.startsWith("h") ? num * 36e5 : unit.startsWith("m") ? num * 6e4 : num * 1e3;
|
|
21015
21736
|
} else {
|
|
21016
|
-
|
|
21017
|
-
values.push(parseInt(opts.every, 10) || 0);
|
|
21737
|
+
payload.everyMs = parseInt(opts.every, 10) || 0;
|
|
21018
21738
|
}
|
|
21019
21739
|
}
|
|
21020
|
-
if (opts.backend)
|
|
21021
|
-
|
|
21022
|
-
|
|
21023
|
-
}
|
|
21024
|
-
if (opts.model) {
|
|
21025
|
-
updates.push("model = ?");
|
|
21026
|
-
values.push(opts.model);
|
|
21027
|
-
}
|
|
21028
|
-
if (opts.thinking) {
|
|
21029
|
-
updates.push("thinking = ?");
|
|
21030
|
-
values.push(opts.thinking);
|
|
21031
|
-
}
|
|
21740
|
+
if (opts.backend) payload.backend = opts.backend;
|
|
21741
|
+
if (opts.model) payload.model = opts.model;
|
|
21742
|
+
if (opts.thinking) payload.thinking = opts.thinking;
|
|
21032
21743
|
if (opts.timeout) {
|
|
21033
21744
|
const timeout = parseAndValidateTimeout(opts.timeout);
|
|
21034
|
-
|
|
21035
|
-
values.push(timeout);
|
|
21036
|
-
}
|
|
21037
|
-
if (opts.timezone) {
|
|
21038
|
-
updates.push("timezone = ?");
|
|
21039
|
-
values.push(opts.timezone);
|
|
21745
|
+
payload.timeout = timeout;
|
|
21040
21746
|
}
|
|
21747
|
+
if (opts.timezone) payload.timezone = opts.timezone;
|
|
21041
21748
|
if (opts.fallback?.length) {
|
|
21042
|
-
|
|
21043
|
-
updates.push("fallbacks = ?");
|
|
21044
|
-
values.push(JSON.stringify(fallbacks));
|
|
21749
|
+
payload.fallbacks = parseFallbacks(opts.fallback);
|
|
21045
21750
|
}
|
|
21046
|
-
|
|
21751
|
+
const fieldCount = Object.keys(payload).length - 1;
|
|
21752
|
+
if (fieldCount === 0) {
|
|
21047
21753
|
outputError("NO_CHANGES", "No fields to update. Specify fields with flags (e.g. --description, --cron).");
|
|
21048
21754
|
process.exit(1);
|
|
21049
21755
|
}
|
|
21050
|
-
|
|
21051
|
-
|
|
21052
|
-
|
|
21756
|
+
const res = await apiPost2("/api/cron/edit", payload);
|
|
21757
|
+
if (res.ok) {
|
|
21758
|
+
output({ id: jobId, success: true }, () => `
|
|
21053
21759
|
${successFmt(`Job #${jobId} updated.`)}
|
|
21054
21760
|
`);
|
|
21761
|
+
} else {
|
|
21762
|
+
outputError("EDIT_FAILED", `Failed: ${JSON.stringify(res.data)}`);
|
|
21763
|
+
process.exit(1);
|
|
21764
|
+
}
|
|
21055
21765
|
} catch (err) {
|
|
21056
21766
|
outputError("EDIT_FAILED", err.message);
|
|
21057
21767
|
process.exit(1);
|
|
21058
21768
|
}
|
|
21059
21769
|
}
|
|
21060
21770
|
async function cronRuns(globalOpts, jobId, opts) {
|
|
21061
|
-
if (!
|
|
21771
|
+
if (!existsSync31(DB_PATH)) {
|
|
21062
21772
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21063
21773
|
process.exit(1);
|
|
21064
21774
|
}
|
|
@@ -21105,16 +21815,16 @@ __export(agents_exports, {
|
|
|
21105
21815
|
runnersList: () => runnersList,
|
|
21106
21816
|
tasksList: () => tasksList
|
|
21107
21817
|
});
|
|
21108
|
-
import { existsSync as
|
|
21818
|
+
import { existsSync as existsSync32 } from "fs";
|
|
21109
21819
|
async function agentsList(globalOpts) {
|
|
21110
|
-
if (!
|
|
21820
|
+
if (!existsSync32(DB_PATH)) {
|
|
21111
21821
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21112
21822
|
process.exit(1);
|
|
21113
21823
|
}
|
|
21114
21824
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
21115
21825
|
const readDb = openDatabaseReadOnly2();
|
|
21116
21826
|
const agents2 = readDb.prepare(
|
|
21117
|
-
"SELECT * FROM agents WHERE status IN ('running', 'queued', 'starting', 'idle') ORDER BY
|
|
21827
|
+
"SELECT * FROM agents WHERE status IN ('running', 'queued', 'starting', 'idle') ORDER BY createdAt DESC"
|
|
21118
21828
|
).all();
|
|
21119
21829
|
readDb.close();
|
|
21120
21830
|
output(agents2, (d) => {
|
|
@@ -21125,10 +21835,10 @@ async function agentsList(globalOpts) {
|
|
|
21125
21835
|
const lines = ["", divider(`Active Agents (${list.length})`), ""];
|
|
21126
21836
|
for (const a of list) {
|
|
21127
21837
|
const shortId = a.id?.slice(0, 8) ?? "?";
|
|
21128
|
-
lines.push(` ${statusDot(a.status)} ${shortId} (${a.
|
|
21838
|
+
lines.push(` ${statusDot(a.status)} ${shortId} (${a.runnerId}) \u2014 ${a.status}`);
|
|
21129
21839
|
if (a.task) lines.push(` Task: ${a.task.slice(0, 80)}${a.task.length > 80 ? "\u2026" : ""}`);
|
|
21130
|
-
const inK = ((a.
|
|
21131
|
-
const outK = ((a.
|
|
21840
|
+
const inK = ((a.tokenInput ?? 0) / 1e3).toFixed(1);
|
|
21841
|
+
const outK = ((a.tokenOutput ?? 0) / 1e3).toFixed(1);
|
|
21132
21842
|
if (parseFloat(inK) > 0 || parseFloat(outK) > 0) {
|
|
21133
21843
|
lines.push(` Tokens: ${inK}k in / ${outK}k out`);
|
|
21134
21844
|
}
|
|
@@ -21138,14 +21848,14 @@ async function agentsList(globalOpts) {
|
|
|
21138
21848
|
});
|
|
21139
21849
|
}
|
|
21140
21850
|
async function tasksList(globalOpts) {
|
|
21141
|
-
if (!
|
|
21851
|
+
if (!existsSync32(DB_PATH)) {
|
|
21142
21852
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21143
21853
|
process.exit(1);
|
|
21144
21854
|
}
|
|
21145
21855
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
21146
21856
|
const readDb = openDatabaseReadOnly2();
|
|
21147
21857
|
const tasks = readDb.prepare(
|
|
21148
|
-
"SELECT t.* FROM
|
|
21858
|
+
"SELECT t.* FROM agent_tasks t JOIN orchestrations o ON t.orchestrationId = o.id WHERE o.status = 'active' ORDER BY t.id"
|
|
21149
21859
|
).all();
|
|
21150
21860
|
readDb.close();
|
|
21151
21861
|
output(tasks, (d) => {
|
|
@@ -21266,10 +21976,10 @@ __export(db_exports, {
|
|
|
21266
21976
|
dbPath: () => dbPath,
|
|
21267
21977
|
dbStats: () => dbStats
|
|
21268
21978
|
});
|
|
21269
|
-
import { existsSync as
|
|
21979
|
+
import { existsSync as existsSync33, statSync as statSync8, copyFileSync as copyFileSync2, mkdirSync as mkdirSync13 } from "fs";
|
|
21270
21980
|
import { dirname as dirname5 } from "path";
|
|
21271
21981
|
async function dbStats(globalOpts) {
|
|
21272
|
-
if (!
|
|
21982
|
+
if (!existsSync33(DB_PATH)) {
|
|
21273
21983
|
outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
|
|
21274
21984
|
process.exit(1);
|
|
21275
21985
|
}
|
|
@@ -21277,7 +21987,7 @@ async function dbStats(globalOpts) {
|
|
|
21277
21987
|
const readDb = openDatabaseReadOnly2();
|
|
21278
21988
|
const mainSize = statSync8(DB_PATH).size;
|
|
21279
21989
|
const walPath = DB_PATH + "-wal";
|
|
21280
|
-
const walSize =
|
|
21990
|
+
const walSize = existsSync33(walPath) ? statSync8(walPath).size : 0;
|
|
21281
21991
|
const tableNames = readDb.prepare(
|
|
21282
21992
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
|
|
21283
21993
|
).all();
|
|
@@ -21311,7 +22021,7 @@ async function dbPath(globalOpts) {
|
|
|
21311
22021
|
output({ path: DB_PATH }, (d) => d.path);
|
|
21312
22022
|
}
|
|
21313
22023
|
async function dbBackup(globalOpts, destPath) {
|
|
21314
|
-
if (!
|
|
22024
|
+
if (!existsSync33(DB_PATH)) {
|
|
21315
22025
|
outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
|
|
21316
22026
|
process.exit(1);
|
|
21317
22027
|
}
|
|
@@ -21320,7 +22030,7 @@ async function dbBackup(globalOpts, destPath) {
|
|
|
21320
22030
|
mkdirSync13(dirname5(dest), { recursive: true });
|
|
21321
22031
|
copyFileSync2(DB_PATH, dest);
|
|
21322
22032
|
const walPath = DB_PATH + "-wal";
|
|
21323
|
-
if (
|
|
22033
|
+
if (existsSync33(walPath)) copyFileSync2(walPath, dest + "-wal");
|
|
21324
22034
|
output({ path: dest, sizeBytes: statSync8(dest).size }, (d) => {
|
|
21325
22035
|
const b = d;
|
|
21326
22036
|
return `
|
|
@@ -21349,9 +22059,9 @@ __export(usage_exports, {
|
|
|
21349
22059
|
usageCost: () => usageCost,
|
|
21350
22060
|
usageTokens: () => usageTokens
|
|
21351
22061
|
});
|
|
21352
|
-
import { existsSync as
|
|
22062
|
+
import { existsSync as existsSync34 } from "fs";
|
|
21353
22063
|
function ensureDb() {
|
|
21354
|
-
if (!
|
|
22064
|
+
if (!existsSync34(DB_PATH)) {
|
|
21355
22065
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
21356
22066
|
process.exit(1);
|
|
21357
22067
|
}
|
|
@@ -21541,9 +22251,9 @@ __export(config_exports, {
|
|
|
21541
22251
|
configList: () => configList,
|
|
21542
22252
|
configSet: () => configSet
|
|
21543
22253
|
});
|
|
21544
|
-
import { existsSync as
|
|
22254
|
+
import { existsSync as existsSync35, readFileSync as readFileSync18 } from "fs";
|
|
21545
22255
|
async function configList(globalOpts) {
|
|
21546
|
-
if (!
|
|
22256
|
+
if (!existsSync35(DB_PATH)) {
|
|
21547
22257
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21548
22258
|
process.exit(1);
|
|
21549
22259
|
}
|
|
@@ -21577,7 +22287,7 @@ async function configGet(globalOpts, key) {
|
|
|
21577
22287
|
outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
|
|
21578
22288
|
process.exit(1);
|
|
21579
22289
|
}
|
|
21580
|
-
if (!
|
|
22290
|
+
if (!existsSync35(DB_PATH)) {
|
|
21581
22291
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21582
22292
|
process.exit(1);
|
|
21583
22293
|
}
|
|
@@ -21623,7 +22333,7 @@ async function configSet(globalOpts, key, value) {
|
|
|
21623
22333
|
}
|
|
21624
22334
|
}
|
|
21625
22335
|
async function configEnv(_globalOpts) {
|
|
21626
|
-
if (!
|
|
22336
|
+
if (!existsSync35(ENV_PATH)) {
|
|
21627
22337
|
outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
|
|
21628
22338
|
process.exit(1);
|
|
21629
22339
|
}
|
|
@@ -21677,9 +22387,9 @@ __export(session_exports, {
|
|
|
21677
22387
|
sessionGet: () => sessionGet,
|
|
21678
22388
|
sessionNew: () => sessionNew
|
|
21679
22389
|
});
|
|
21680
|
-
import { existsSync as
|
|
22390
|
+
import { existsSync as existsSync36 } from "fs";
|
|
21681
22391
|
async function sessionGet(globalOpts) {
|
|
21682
|
-
if (!
|
|
22392
|
+
if (!existsSync36(DB_PATH)) {
|
|
21683
22393
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21684
22394
|
process.exit(1);
|
|
21685
22395
|
}
|
|
@@ -21740,9 +22450,9 @@ __export(permissions_exports, {
|
|
|
21740
22450
|
verboseGet: () => verboseGet,
|
|
21741
22451
|
verboseSet: () => verboseSet
|
|
21742
22452
|
});
|
|
21743
|
-
import { existsSync as
|
|
22453
|
+
import { existsSync as existsSync37 } from "fs";
|
|
21744
22454
|
function ensureDb2() {
|
|
21745
|
-
if (!
|
|
22455
|
+
if (!existsSync37(DB_PATH)) {
|
|
21746
22456
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21747
22457
|
process.exit(1);
|
|
21748
22458
|
}
|
|
@@ -21889,9 +22599,9 @@ __export(cwd_exports, {
|
|
|
21889
22599
|
cwdGet: () => cwdGet,
|
|
21890
22600
|
cwdSet: () => cwdSet
|
|
21891
22601
|
});
|
|
21892
|
-
import { existsSync as
|
|
22602
|
+
import { existsSync as existsSync38 } from "fs";
|
|
21893
22603
|
async function cwdGet(globalOpts) {
|
|
21894
|
-
if (!
|
|
22604
|
+
if (!existsSync38(DB_PATH)) {
|
|
21895
22605
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21896
22606
|
process.exit(1);
|
|
21897
22607
|
}
|
|
@@ -21953,9 +22663,9 @@ __export(voice_exports, {
|
|
|
21953
22663
|
voiceGet: () => voiceGet,
|
|
21954
22664
|
voiceSet: () => voiceSet
|
|
21955
22665
|
});
|
|
21956
|
-
import { existsSync as
|
|
22666
|
+
import { existsSync as existsSync39 } from "fs";
|
|
21957
22667
|
async function voiceGet(globalOpts) {
|
|
21958
|
-
if (!
|
|
22668
|
+
if (!existsSync39(DB_PATH)) {
|
|
21959
22669
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21960
22670
|
process.exit(1);
|
|
21961
22671
|
}
|
|
@@ -22004,9 +22714,9 @@ __export(heartbeat_exports, {
|
|
|
22004
22714
|
heartbeatGet: () => heartbeatGet,
|
|
22005
22715
|
heartbeatSet: () => heartbeatSet
|
|
22006
22716
|
});
|
|
22007
|
-
import { existsSync as
|
|
22717
|
+
import { existsSync as existsSync40 } from "fs";
|
|
22008
22718
|
async function heartbeatGet(globalOpts) {
|
|
22009
|
-
if (!
|
|
22719
|
+
if (!existsSync40(DB_PATH)) {
|
|
22010
22720
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
22011
22721
|
process.exit(1);
|
|
22012
22722
|
}
|
|
@@ -22116,9 +22826,9 @@ __export(chats_exports, {
|
|
|
22116
22826
|
chatsList: () => chatsList,
|
|
22117
22827
|
chatsRemoveAlias: () => chatsRemoveAlias
|
|
22118
22828
|
});
|
|
22119
|
-
import { existsSync as
|
|
22829
|
+
import { existsSync as existsSync41 } from "fs";
|
|
22120
22830
|
async function chatsList(_globalOpts) {
|
|
22121
|
-
if (!
|
|
22831
|
+
if (!existsSync41(DB_PATH)) {
|
|
22122
22832
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
22123
22833
|
process.exit(1);
|
|
22124
22834
|
}
|
|
@@ -22246,9 +22956,9 @@ var mcps_exports = {};
|
|
|
22246
22956
|
__export(mcps_exports, {
|
|
22247
22957
|
mcpsList: () => mcpsList
|
|
22248
22958
|
});
|
|
22249
|
-
import { existsSync as
|
|
22959
|
+
import { existsSync as existsSync42 } from "fs";
|
|
22250
22960
|
async function mcpsList(_globalOpts) {
|
|
22251
|
-
if (!
|
|
22961
|
+
if (!existsSync42(DB_PATH)) {
|
|
22252
22962
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
22253
22963
|
process.exit(1);
|
|
22254
22964
|
}
|
|
@@ -22285,11 +22995,11 @@ __export(chat_exports, {
|
|
|
22285
22995
|
chatSend: () => chatSend
|
|
22286
22996
|
});
|
|
22287
22997
|
import { request as httpRequest2 } from "http";
|
|
22288
|
-
import { readFileSync as readFileSync19, existsSync as
|
|
22998
|
+
import { readFileSync as readFileSync19, existsSync as existsSync43 } from "fs";
|
|
22289
22999
|
function getToken2() {
|
|
22290
23000
|
if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
|
|
22291
23001
|
try {
|
|
22292
|
-
if (
|
|
23002
|
+
if (existsSync43(TOKEN_PATH2)) return readFileSync19(TOKEN_PATH2, "utf-8").trim();
|
|
22293
23003
|
} catch {
|
|
22294
23004
|
}
|
|
22295
23005
|
return null;
|
|
@@ -22759,9 +23469,9 @@ __export(evolve_exports, {
|
|
|
22759
23469
|
evolveStatus: () => evolveStatus,
|
|
22760
23470
|
evolveUndo: () => evolveUndo
|
|
22761
23471
|
});
|
|
22762
|
-
import { existsSync as
|
|
23472
|
+
import { existsSync as existsSync44 } from "fs";
|
|
22763
23473
|
function ensureDb3() {
|
|
22764
|
-
if (!
|
|
23474
|
+
if (!existsSync44(DB_PATH)) {
|
|
22765
23475
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
22766
23476
|
process.exit(1);
|
|
22767
23477
|
}
|
|
@@ -23177,8 +23887,8 @@ var init_evolve = __esm({
|
|
|
23177
23887
|
|
|
23178
23888
|
// src/setup.ts
|
|
23179
23889
|
var setup_exports = {};
|
|
23180
|
-
import { existsSync as
|
|
23181
|
-
import { execFileSync as
|
|
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";
|
|
23182
23892
|
import { createInterface as createInterface7 } from "readline";
|
|
23183
23893
|
import { join as join27 } from "path";
|
|
23184
23894
|
function divider2() {
|
|
@@ -23242,7 +23952,7 @@ async function setup() {
|
|
|
23242
23952
|
let foundAnyBackend = false;
|
|
23243
23953
|
for (const bk of backends) {
|
|
23244
23954
|
try {
|
|
23245
|
-
const path =
|
|
23955
|
+
const path = execFileSync5("which", [bk.cmd], { encoding: "utf-8" }).trim();
|
|
23246
23956
|
console.log(green(` ${bk.name} CLI found: ${path}`));
|
|
23247
23957
|
foundAnyBackend = true;
|
|
23248
23958
|
} catch {
|
|
@@ -23255,10 +23965,10 @@ async function setup() {
|
|
|
23255
23965
|
}
|
|
23256
23966
|
console.log("");
|
|
23257
23967
|
for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
|
|
23258
|
-
if (!
|
|
23968
|
+
if (!existsSync45(dir)) mkdirSync15(dir, { recursive: true });
|
|
23259
23969
|
}
|
|
23260
23970
|
const env = {};
|
|
23261
|
-
const envSource =
|
|
23971
|
+
const envSource = existsSync45(ENV_PATH) ? ENV_PATH : existsSync45(".env") ? ".env" : null;
|
|
23262
23972
|
if (envSource) {
|
|
23263
23973
|
console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
|
|
23264
23974
|
console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
|
|
@@ -23269,7 +23979,7 @@ async function setup() {
|
|
|
23269
23979
|
}
|
|
23270
23980
|
}
|
|
23271
23981
|
const cwdDb = join27(process.cwd(), "cc-claw.db");
|
|
23272
|
-
if (
|
|
23982
|
+
if (existsSync45(cwdDb) && !existsSync45(DB_PATH)) {
|
|
23273
23983
|
const { size } = statSync9(cwdDb);
|
|
23274
23984
|
console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
|
|
23275
23985
|
const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
|
|
@@ -24102,8 +24812,8 @@ async function run(argv = process.argv) {
|
|
|
24102
24812
|
if (argv.includes("--version") || argv.includes("-V")) {
|
|
24103
24813
|
console.log(VERSION);
|
|
24104
24814
|
try {
|
|
24105
|
-
const { execFileSync:
|
|
24106
|
-
const latest =
|
|
24815
|
+
const { execFileSync: execFileSync6 } = await import("child_process");
|
|
24816
|
+
const latest = execFileSync6("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
24107
24817
|
if (latest && latest !== VERSION) {
|
|
24108
24818
|
console.log(`
|
|
24109
24819
|
Update available: v${latest} (current: v${VERSION})`);
|