cc-claw 0.12.8 → 0.13.1
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 +1392 -730
- 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.1" : (() => {
|
|
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",
|
|
@@ -4262,7 +4402,7 @@ function extractKeyField(input) {
|
|
|
4262
4402
|
}
|
|
4263
4403
|
const sorted = Object.keys(input).sort();
|
|
4264
4404
|
const parts = sorted.map((k) => `${k}:${JSON.stringify(input[k])}`);
|
|
4265
|
-
return parts.join("|");
|
|
4405
|
+
return parts.join("|").slice(0, 500);
|
|
4266
4406
|
}
|
|
4267
4407
|
function fingerprint(toolName, input) {
|
|
4268
4408
|
const key = extractKeyField(input);
|
|
@@ -4272,8 +4412,8 @@ var DEFAULT_WINDOW_SIZE, DEFAULT_THRESHOLD, HARD_TURN_CAP, ToolLoopDetector;
|
|
|
4272
4412
|
var init_tool_loop_detector = __esm({
|
|
4273
4413
|
"src/tool-loop-detector.ts"() {
|
|
4274
4414
|
"use strict";
|
|
4275
|
-
DEFAULT_WINDOW_SIZE = 15;
|
|
4276
|
-
DEFAULT_THRESHOLD = 3;
|
|
4415
|
+
DEFAULT_WINDOW_SIZE = parseInt(process.env.CC_CLAW_LOOP_WINDOW_SIZE ?? "15", 10);
|
|
4416
|
+
DEFAULT_THRESHOLD = parseInt(process.env.CC_CLAW_LOOP_THRESHOLD ?? "3", 10);
|
|
4277
4417
|
HARD_TURN_CAP = 200;
|
|
4278
4418
|
ToolLoopDetector = class {
|
|
4279
4419
|
windowSize;
|
|
@@ -4320,15 +4460,24 @@ var init_tool_loop_detector = __esm({
|
|
|
4320
4460
|
});
|
|
4321
4461
|
|
|
4322
4462
|
// src/memory/inject.ts
|
|
4463
|
+
function getTopK(query) {
|
|
4464
|
+
const wordCount = query.split(/\s+/).length;
|
|
4465
|
+
const scale = wordCount <= 3 ? 0.5 : wordCount <= 8 ? 0.75 : 1;
|
|
4466
|
+
return {
|
|
4467
|
+
vectorK: Math.max(5, Math.round(BASE_VECTOR_TOP_K * scale)),
|
|
4468
|
+
ftsK: Math.max(5, Math.round(BASE_FTS_TOP_K * scale))
|
|
4469
|
+
};
|
|
4470
|
+
}
|
|
4323
4471
|
function daysSince(dateStr) {
|
|
4324
|
-
const
|
|
4472
|
+
const trimmed = dateStr.trim();
|
|
4473
|
+
const date = /* @__PURE__ */ new Date(trimmed.replace(" ", "T") + (trimmed.includes("Z") ? "" : "Z"));
|
|
4325
4474
|
return Math.max(0, (Date.now() - date.getTime()) / (1e3 * 60 * 60 * 24));
|
|
4326
4475
|
}
|
|
4327
4476
|
function normalizeFtsScores(items) {
|
|
4328
4477
|
const scores = /* @__PURE__ */ new Map();
|
|
4329
4478
|
if (items.length === 0) return scores;
|
|
4330
4479
|
const ranks = items.map((it) => Math.abs(it._ftsRank ?? 0));
|
|
4331
|
-
const maxRank =
|
|
4480
|
+
const maxRank = ranks.reduce((max, r) => r > max ? r : max, 1e-3);
|
|
4332
4481
|
for (let i = 0; i < items.length; i++) {
|
|
4333
4482
|
scores.set(items[i].id, ranks[i] / maxRank);
|
|
4334
4483
|
}
|
|
@@ -4398,14 +4547,15 @@ async function injectMemoryContext(userMessage) {
|
|
|
4398
4547
|
queryEmbedding = await embedQuery(userMessage);
|
|
4399
4548
|
}
|
|
4400
4549
|
const hasVectors = queryEmbedding !== null;
|
|
4550
|
+
const { vectorK, ftsK } = getTopK(userMessage);
|
|
4401
4551
|
let ftsMemories = [];
|
|
4402
4552
|
let ftsSessions = [];
|
|
4403
4553
|
try {
|
|
4404
|
-
ftsMemories = searchMemories(userMessage,
|
|
4554
|
+
ftsMemories = searchMemories(userMessage, ftsK);
|
|
4405
4555
|
} catch {
|
|
4406
4556
|
}
|
|
4407
4557
|
try {
|
|
4408
|
-
ftsSessions = searchSessionSummaries(userMessage,
|
|
4558
|
+
ftsSessions = searchSessionSummaries(userMessage, ftsK);
|
|
4409
4559
|
} catch {
|
|
4410
4560
|
}
|
|
4411
4561
|
let combinedMemories;
|
|
@@ -4413,8 +4563,8 @@ async function injectMemoryContext(userMessage) {
|
|
|
4413
4563
|
if (hasVectors) {
|
|
4414
4564
|
const allEmbeddedMemories = getAllMemoriesWithEmbeddings();
|
|
4415
4565
|
const allEmbeddedSessions = getAllSessionSummariesWithEmbeddings();
|
|
4416
|
-
const memVectorMatches = vectorSearch(queryEmbedding, allEmbeddedMemories,
|
|
4417
|
-
const sesVectorMatches = vectorSearch(queryEmbedding, allEmbeddedSessions,
|
|
4566
|
+
const memVectorMatches = vectorSearch(queryEmbedding, allEmbeddedMemories, vectorK);
|
|
4567
|
+
const sesVectorMatches = vectorSearch(queryEmbedding, allEmbeddedSessions, vectorK);
|
|
4418
4568
|
const memMap = /* @__PURE__ */ new Map();
|
|
4419
4569
|
for (const m of ftsMemories) memMap.set(m.id, m);
|
|
4420
4570
|
for (const vm of memVectorMatches) {
|
|
@@ -4483,16 +4633,16 @@ async function injectMemoryContext(userMessage) {
|
|
|
4483
4633
|
${lines.join("\n")}
|
|
4484
4634
|
[End memory context]`;
|
|
4485
4635
|
}
|
|
4486
|
-
var MEMORY_DECAY_RATE, SESSION_DECAY_RATE,
|
|
4636
|
+
var MEMORY_DECAY_RATE, SESSION_DECAY_RATE, BASE_VECTOR_TOP_K, BASE_FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS, MAX_MEMORY_CHARS, MAX_SESSION_CHARS, MAX_BRIDGE_CHARS, pendingContextBridges;
|
|
4487
4637
|
var init_inject = __esm({
|
|
4488
4638
|
"src/memory/inject.ts"() {
|
|
4489
4639
|
"use strict";
|
|
4490
4640
|
init_store5();
|
|
4491
4641
|
init_embeddings();
|
|
4492
|
-
MEMORY_DECAY_RATE = 0.02;
|
|
4493
|
-
SESSION_DECAY_RATE =
|
|
4494
|
-
|
|
4495
|
-
|
|
4642
|
+
MEMORY_DECAY_RATE = parseFloat(process.env.CC_CLAW_MEMORY_DECAY_RATE ?? "0.02");
|
|
4643
|
+
SESSION_DECAY_RATE = parseFloat(process.env.CC_CLAW_SESSION_DECAY_RATE ?? "0.005");
|
|
4644
|
+
BASE_VECTOR_TOP_K = 20;
|
|
4645
|
+
BASE_FTS_TOP_K = 20;
|
|
4496
4646
|
FINAL_TOP_K_MEMORIES = 5;
|
|
4497
4647
|
FINAL_TOP_K_SESSIONS = 3;
|
|
4498
4648
|
MAX_MEMORY_CHARS = 500;
|
|
@@ -4587,6 +4737,16 @@ You are an expert user and operator of the CC-Claw architecture. Because you are
|
|
|
4587
4737
|
- **Files**: You can send physical files to the user across Telegram by simply writing \`[SEND_FILE:/absolute/path/to/file]\` in your response.
|
|
4588
4738
|
- **MCP Ecosystem**: You are deeply natively integrated with Model Context Protocol (MCP) servers (like Perplexity, NotebookLM, Context7) granting you immense external reach.
|
|
4589
4739
|
|
|
4740
|
+
## 7. CLI Reference
|
|
4741
|
+
- Run \\\`cc-claw --ai\\\` for a comprehensive command reference covering all CLI and Telegram commands
|
|
4742
|
+
- Use \\\`cc-claw --ai --install\\\` to install the skill to all backend skill directories
|
|
4743
|
+
|
|
4744
|
+
## 8. CRITICAL RULES \u2014 Memory & State
|
|
4745
|
+
- NEVER use native CLI memory tools (Gemini's save_memory, Claude's memory, etc.) \u2014 they are backend-local
|
|
4746
|
+
- NEVER modify the SQLite database directly (no INSERT/UPDATE/DELETE SQL)
|
|
4747
|
+
- ALWAYS use CC-Claw slash commands (/remember, /evolve, etc.) or the cc-claw CLI
|
|
4748
|
+
- Memory MUST be cross-backend persistent \u2014 only CC-Claw's own API guarantees this
|
|
4749
|
+
|
|
4590
4750
|
If the user asks *how* to do something with CC-Claw, use this expertise to suggest the most native, idiomatic approach available in the architecture.
|
|
4591
4751
|
`;
|
|
4592
4752
|
}
|
|
@@ -4740,6 +4900,14 @@ function syncNativeCliFiles() {
|
|
|
4740
4900
|
"- For heartbeat checks: respond with exactly HEARTBEAT_OK if nothing needs attention",
|
|
4741
4901
|
"- Your working directory is ~/.cc-claw/workspace/",
|
|
4742
4902
|
"- Skills are in ~/.cc-claw/workspace/skills/",
|
|
4903
|
+
"",
|
|
4904
|
+
"## CRITICAL: Tool Usage Rules",
|
|
4905
|
+
"",
|
|
4906
|
+
"- Do NOT use native CLI memory/storage tools (save_memory, etc.) \u2014 they are backend-local and invisible to other backends",
|
|
4907
|
+
"- Do NOT modify the database directly with SQL (no INSERT/UPDATE/DELETE) \u2014 use CC-Claw commands only",
|
|
4908
|
+
"- To save a memory: use /remember in Telegram, or tell the user to use it",
|
|
4909
|
+
"- To learn your full CLI: run `cc-claw --ai`",
|
|
4910
|
+
"- All state changes must go through the CC-Claw API layer for cross-backend persistence",
|
|
4743
4911
|
""
|
|
4744
4912
|
].join("\n");
|
|
4745
4913
|
const ccClawPath = join6(IDENTITY_PATH, "CC-CLAW.md");
|
|
@@ -4997,6 +5165,19 @@ var init_loader = __esm({
|
|
|
4997
5165
|
}
|
|
4998
5166
|
});
|
|
4999
5167
|
|
|
5168
|
+
// src/text-utils.ts
|
|
5169
|
+
function appendTextChunk(accumulated, chunk) {
|
|
5170
|
+
if (!accumulated) return chunk;
|
|
5171
|
+
if (!chunk) return accumulated;
|
|
5172
|
+
const needsSpace = !/\s$/.test(accumulated) && !/^\s/.test(chunk);
|
|
5173
|
+
return needsSpace ? accumulated + " " + chunk : accumulated + chunk;
|
|
5174
|
+
}
|
|
5175
|
+
var init_text_utils = __esm({
|
|
5176
|
+
"src/text-utils.ts"() {
|
|
5177
|
+
"use strict";
|
|
5178
|
+
}
|
|
5179
|
+
});
|
|
5180
|
+
|
|
5000
5181
|
// src/memory/session-log.ts
|
|
5001
5182
|
var session_log_exports = {};
|
|
5002
5183
|
__export(session_log_exports, {
|
|
@@ -5006,8 +5187,13 @@ __export(session_log_exports, {
|
|
|
5006
5187
|
getLastMessageTimestamp: () => getLastMessageTimestamp,
|
|
5007
5188
|
getLog: () => getLog,
|
|
5008
5189
|
getLoggedChatIds: () => getLoggedChatIds,
|
|
5009
|
-
getMessagePairCount: () => getMessagePairCount
|
|
5190
|
+
getMessagePairCount: () => getMessagePairCount,
|
|
5191
|
+
parseDbTimestamp: () => parseDbTimestamp
|
|
5010
5192
|
});
|
|
5193
|
+
function parseDbTimestamp(ts2) {
|
|
5194
|
+
const trimmed = ts2.trim();
|
|
5195
|
+
return (/* @__PURE__ */ new Date(trimmed + (trimmed.includes("Z") ? "" : "Z"))).getTime();
|
|
5196
|
+
}
|
|
5011
5197
|
function appendToLog(chatId, userMessage, assistantResponse, backend2, model2, sessionId) {
|
|
5012
5198
|
const now = Date.now();
|
|
5013
5199
|
appendMessageLog(chatId, "user", userMessage, backend2 ?? null, model2 ?? null, sessionId ?? null);
|
|
@@ -5027,7 +5213,8 @@ function getLog(chatId) {
|
|
|
5027
5213
|
return rows.map((r) => ({
|
|
5028
5214
|
role: r.role,
|
|
5029
5215
|
text: r.content,
|
|
5030
|
-
|
|
5216
|
+
// Audit C26: Use shared timestamp parser for consistency with inject.ts
|
|
5217
|
+
timestamp: parseDbTimestamp(r.created_at)
|
|
5031
5218
|
}));
|
|
5032
5219
|
}
|
|
5033
5220
|
function getMessagePairCount(chatId) {
|
|
@@ -5035,7 +5222,8 @@ function getMessagePairCount(chatId) {
|
|
|
5035
5222
|
if (cached && cached.length > 0) {
|
|
5036
5223
|
return Math.floor(cached.length / 2);
|
|
5037
5224
|
}
|
|
5038
|
-
|
|
5225
|
+
const dbRows = getUnsummarizedLog(chatId);
|
|
5226
|
+
return Math.floor(dbRows.length / 2);
|
|
5039
5227
|
}
|
|
5040
5228
|
function clearLog(chatId) {
|
|
5041
5229
|
markMessageLogSummarized(chatId);
|
|
@@ -5049,7 +5237,7 @@ function getLastMessageTimestamp(chatId) {
|
|
|
5049
5237
|
const rows = getUnsummarizedLog(chatId);
|
|
5050
5238
|
if (rows.length === 0) return null;
|
|
5051
5239
|
const last = rows[rows.length - 1];
|
|
5052
|
-
return (
|
|
5240
|
+
return parseDbTimestamp(last.created_at);
|
|
5053
5241
|
}
|
|
5054
5242
|
function getCachedLog(chatId) {
|
|
5055
5243
|
return cache.get(chatId) ?? [];
|
|
@@ -5062,7 +5250,7 @@ var init_session_log = __esm({
|
|
|
5062
5250
|
"src/memory/session-log.ts"() {
|
|
5063
5251
|
"use strict";
|
|
5064
5252
|
init_store5();
|
|
5065
|
-
CACHE_SIZE =
|
|
5253
|
+
CACHE_SIZE = 50;
|
|
5066
5254
|
cache = /* @__PURE__ */ new Map();
|
|
5067
5255
|
}
|
|
5068
5256
|
});
|
|
@@ -5077,21 +5265,27 @@ __export(summarize_exports, {
|
|
|
5077
5265
|
});
|
|
5078
5266
|
import { spawn } from "child_process";
|
|
5079
5267
|
import { createInterface } from "readline";
|
|
5268
|
+
function getTranscriptCap(model2) {
|
|
5269
|
+
if (model2 && /flash/i.test(model2)) return TRANSCRIPT_CAP_FLASH;
|
|
5270
|
+
return TRANSCRIPT_CAP_DEFAULT;
|
|
5271
|
+
}
|
|
5080
5272
|
function buildTranscript(entries) {
|
|
5081
5273
|
const lines = [];
|
|
5082
5274
|
let totalLen = 0;
|
|
5083
|
-
for (
|
|
5275
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
5276
|
+
const e = entries[i];
|
|
5084
5277
|
const limit = e.role === "user" ? USER_MSG_LIMIT : AGENT_MSG_LIMIT;
|
|
5085
5278
|
const label2 = e.role === "user" ? "User" : "Agent";
|
|
5086
5279
|
const text = e.text.length > limit ? e.text.slice(0, limit) + "\n[...truncated]" : e.text;
|
|
5087
5280
|
const line = `[${label2}] ${text}`;
|
|
5088
|
-
if (totalLen + line.length >
|
|
5281
|
+
if (totalLen + line.length > getTranscriptCap()) {
|
|
5089
5282
|
lines.push("[...earlier messages truncated for length]");
|
|
5090
5283
|
break;
|
|
5091
5284
|
}
|
|
5092
5285
|
lines.push(line);
|
|
5093
5286
|
totalLen += line.length;
|
|
5094
5287
|
}
|
|
5288
|
+
lines.reverse();
|
|
5095
5289
|
return lines.join("\n\n");
|
|
5096
5290
|
}
|
|
5097
5291
|
async function attemptSummarize(chatId, adapter, model2, entries) {
|
|
@@ -5121,11 +5315,12 @@ ${transcript}`;
|
|
|
5121
5315
|
});
|
|
5122
5316
|
proc.stderr?.resume();
|
|
5123
5317
|
const rl2 = createInterface({ input: proc.stdout });
|
|
5318
|
+
let killTimer;
|
|
5124
5319
|
const timeout = setTimeout(() => {
|
|
5125
5320
|
warn(`[summarize] Timeout (${adapter.id}:${model2}) for chat ${chatId}`);
|
|
5126
5321
|
rl2.close();
|
|
5127
5322
|
proc.kill("SIGTERM");
|
|
5128
|
-
setTimeout(() => proc.kill("SIGKILL"), 2e3);
|
|
5323
|
+
killTimer = setTimeout(() => proc.kill("SIGKILL"), 2e3);
|
|
5129
5324
|
}, SUMMARIZE_TIMEOUT_MS);
|
|
5130
5325
|
rl2.on("line", (line) => {
|
|
5131
5326
|
if (!line.trim()) return;
|
|
@@ -5137,7 +5332,7 @@ ${transcript}`;
|
|
|
5137
5332
|
}
|
|
5138
5333
|
const events = adapter.parseLine(msg);
|
|
5139
5334
|
for (const ev of events) {
|
|
5140
|
-
if (ev.type === "text" && ev.text) accumulatedText
|
|
5335
|
+
if (ev.type === "text" && ev.text) accumulatedText = appendTextChunk(accumulatedText, ev.text);
|
|
5141
5336
|
if (ev.type === "usage" && ev.usage) {
|
|
5142
5337
|
inputTokens += ev.usage.input;
|
|
5143
5338
|
outputTokens += ev.usage.output;
|
|
@@ -5157,13 +5352,23 @@ ${transcript}`;
|
|
|
5157
5352
|
}
|
|
5158
5353
|
}
|
|
5159
5354
|
});
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5355
|
+
let settled = false;
|
|
5356
|
+
proc.on("error", (err) => {
|
|
5357
|
+
warn(`[summarize] Spawn error (${adapter.id}:${model2}): ${err}`);
|
|
5358
|
+
if (!settled) {
|
|
5359
|
+
settled = true;
|
|
5360
|
+
clearTimeout(timeout);
|
|
5361
|
+
clearTimeout(killTimer);
|
|
5362
|
+
resolve();
|
|
5363
|
+
}
|
|
5163
5364
|
});
|
|
5164
5365
|
proc.on("close", () => {
|
|
5165
|
-
|
|
5166
|
-
|
|
5366
|
+
if (!settled) {
|
|
5367
|
+
settled = true;
|
|
5368
|
+
clearTimeout(timeout);
|
|
5369
|
+
clearTimeout(killTimer);
|
|
5370
|
+
resolve();
|
|
5371
|
+
}
|
|
5167
5372
|
});
|
|
5168
5373
|
});
|
|
5169
5374
|
if (inputTokens + outputTokens > 0) {
|
|
@@ -5219,66 +5424,84 @@ async function extractAndLogSignals(rawText, chatId, adapterId, model2) {
|
|
|
5219
5424
|
}
|
|
5220
5425
|
}
|
|
5221
5426
|
async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend, clearLogAfter = true) {
|
|
5222
|
-
const
|
|
5223
|
-
if (
|
|
5224
|
-
|
|
5427
|
+
const existing = summarizationLocks.get(chatId);
|
|
5428
|
+
if (existing) {
|
|
5429
|
+
warn(`[summarize] Already summarizing ${chatId}, skipping concurrent call`);
|
|
5430
|
+
await existing;
|
|
5225
5431
|
return false;
|
|
5226
5432
|
}
|
|
5227
|
-
const
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
const model2 = config2.model ?? adapter.summarizerModel;
|
|
5239
|
-
const key = `${adapter.id}:${model2}`;
|
|
5240
|
-
tried.add(key);
|
|
5241
|
-
const result = await attemptSummarize(chatId, adapter, model2, entries);
|
|
5242
|
-
if (result.success) {
|
|
5243
|
-
await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
|
|
5244
|
-
if (clearLogAfter) clearLog(chatId);
|
|
5245
|
-
return true;
|
|
5246
|
-
}
|
|
5433
|
+
const doSummarize = async () => {
|
|
5434
|
+
const pairCount = getMessagePairCount(chatId);
|
|
5435
|
+
if (pairCount < MIN_PAIRS) {
|
|
5436
|
+
return false;
|
|
5437
|
+
}
|
|
5438
|
+
const entries = getLog(chatId);
|
|
5439
|
+
if (entries.length === 0) return false;
|
|
5440
|
+
const tried = /* @__PURE__ */ new Set();
|
|
5441
|
+
if (excludeBackend) {
|
|
5442
|
+
const excluded = getAdapter(excludeBackend);
|
|
5443
|
+
tried.add(`${excluded.id}:${excluded.summarizerModel}`);
|
|
5247
5444
|
}
|
|
5248
|
-
} catch {
|
|
5249
|
-
}
|
|
5250
|
-
if (targetBackendId) {
|
|
5251
5445
|
try {
|
|
5252
|
-
const
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5446
|
+
const config2 = getSummarizer(chatId);
|
|
5447
|
+
if (config2.backend !== "off") {
|
|
5448
|
+
const adapter = config2.backend ? getAdapter(config2.backend) : getAdapterForChat(chatId);
|
|
5449
|
+
const model2 = config2.model ?? adapter.summarizerModel;
|
|
5450
|
+
const key = `${adapter.id}:${model2}`;
|
|
5256
5451
|
tried.add(key);
|
|
5257
|
-
const result = await attemptSummarize(chatId,
|
|
5452
|
+
const result = await attemptSummarize(chatId, adapter, model2, entries);
|
|
5258
5453
|
if (result.success) {
|
|
5259
|
-
await extractAndLogSignals(result.rawText, chatId,
|
|
5454
|
+
await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
|
|
5260
5455
|
if (clearLogAfter) clearLog(chatId);
|
|
5261
5456
|
return true;
|
|
5262
5457
|
}
|
|
5263
5458
|
}
|
|
5264
5459
|
} catch {
|
|
5265
5460
|
}
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5461
|
+
if (targetBackendId) {
|
|
5462
|
+
try {
|
|
5463
|
+
const targetAdapter = getAdapter(targetBackendId);
|
|
5464
|
+
const model2 = targetAdapter.summarizerModel;
|
|
5465
|
+
const key = `${targetAdapter.id}:${model2}`;
|
|
5466
|
+
if (!tried.has(key)) {
|
|
5467
|
+
tried.add(key);
|
|
5468
|
+
const result = await attemptSummarize(chatId, targetAdapter, model2, entries);
|
|
5469
|
+
if (result.success) {
|
|
5470
|
+
await extractAndLogSignals(result.rawText, chatId, targetAdapter.id, model2);
|
|
5471
|
+
if (clearLogAfter) clearLog(chatId);
|
|
5472
|
+
return true;
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5475
|
+
} catch {
|
|
5476
|
+
}
|
|
5477
|
+
}
|
|
5478
|
+
let fallbackCount = 0;
|
|
5479
|
+
const MAX_FALLBACK_ATTEMPTS = 3;
|
|
5480
|
+
for (const adapter of getAllAdapters()) {
|
|
5481
|
+
if (fallbackCount >= MAX_FALLBACK_ATTEMPTS) break;
|
|
5482
|
+
const model2 = adapter.summarizerModel;
|
|
5483
|
+
const key = `${adapter.id}:${model2}`;
|
|
5484
|
+
if (!tried.has(key)) {
|
|
5485
|
+
tried.add(key);
|
|
5486
|
+
fallbackCount++;
|
|
5487
|
+
const result = await attemptSummarize(chatId, adapter, model2, entries);
|
|
5488
|
+
if (result.success) {
|
|
5489
|
+
await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
|
|
5490
|
+
if (clearLogAfter) clearLog(chatId);
|
|
5491
|
+
return true;
|
|
5492
|
+
}
|
|
5277
5493
|
}
|
|
5278
5494
|
}
|
|
5495
|
+
warn(`[summarize] All fallback attempts failed for chat ${chatId} \u2014 raw log preserved`);
|
|
5496
|
+
return false;
|
|
5497
|
+
};
|
|
5498
|
+
const promise = doSummarize();
|
|
5499
|
+
summarizationLocks.set(chatId, promise);
|
|
5500
|
+
try {
|
|
5501
|
+
return await promise;
|
|
5502
|
+
} finally {
|
|
5503
|
+
summarizationLocks.delete(chatId);
|
|
5279
5504
|
}
|
|
5280
|
-
warn(`[summarize] All fallback attempts failed for chat ${chatId} \u2014 raw log preserved`);
|
|
5281
|
-
return false;
|
|
5282
5505
|
}
|
|
5283
5506
|
async function summarizeSession(chatId, clearLogAfter = true) {
|
|
5284
5507
|
return summarizeWithFallbackChain(chatId, void 0, void 0, clearLogAfter);
|
|
@@ -5299,7 +5522,7 @@ async function summarizeAllPending() {
|
|
|
5299
5522
|
await summarizeSession(chatId);
|
|
5300
5523
|
}
|
|
5301
5524
|
}
|
|
5302
|
-
var MIN_PAIRS, USER_MSG_LIMIT, AGENT_MSG_LIMIT,
|
|
5525
|
+
var MIN_PAIRS, summarizationLocks, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP_DEFAULT, TRANSCRIPT_CAP_FLASH, SUMMARIZE_TIMEOUT_MS, SUMMARIZE_PROMPT, CONTEXT_BRIDGE_PROMPT, VALID_SIGNAL_TYPES;
|
|
5303
5526
|
var init_summarize = __esm({
|
|
5304
5527
|
"src/memory/summarize.ts"() {
|
|
5305
5528
|
"use strict";
|
|
@@ -5307,10 +5530,13 @@ var init_summarize = __esm({
|
|
|
5307
5530
|
init_store5();
|
|
5308
5531
|
init_session_log();
|
|
5309
5532
|
init_backends();
|
|
5533
|
+
init_text_utils();
|
|
5310
5534
|
MIN_PAIRS = 2;
|
|
5535
|
+
summarizationLocks = /* @__PURE__ */ new Map();
|
|
5311
5536
|
USER_MSG_LIMIT = 4e3;
|
|
5312
5537
|
AGENT_MSG_LIMIT = 6e3;
|
|
5313
|
-
|
|
5538
|
+
TRANSCRIPT_CAP_DEFAULT = 1e5;
|
|
5539
|
+
TRANSCRIPT_CAP_FLASH = 5e4;
|
|
5314
5540
|
SUMMARIZE_TIMEOUT_MS = 6e4;
|
|
5315
5541
|
SUMMARIZE_PROMPT = `You are summarizing a conversation session for long-term episodic memory. This summary will be injected into future conversations to provide context, so it must contain enough specific detail to be useful weeks or months later.
|
|
5316
5542
|
|
|
@@ -5358,7 +5584,7 @@ Be specific. The new assistant has no prior context. Cover any domain \u2014 per
|
|
|
5358
5584
|
// src/gemini/quota.ts
|
|
5359
5585
|
function classifyGeminiQuota(errorText) {
|
|
5360
5586
|
for (const p of CAPACITY_PATTERNS) {
|
|
5361
|
-
if (p.test(errorText)) return "
|
|
5587
|
+
if (p.test(errorText)) return "capacity";
|
|
5362
5588
|
}
|
|
5363
5589
|
for (const p of RATE_LIMITED_PATTERNS) {
|
|
5364
5590
|
if (p.test(errorText)) return "rate_limited";
|
|
@@ -5389,8 +5615,9 @@ var init_quota = __esm({
|
|
|
5389
5615
|
];
|
|
5390
5616
|
DAILY_QUOTA_PATTERNS = [
|
|
5391
5617
|
/quota exceeded.*per day/i,
|
|
5392
|
-
|
|
5393
|
-
|
|
5618
|
+
// Audit C39: Tightened — require both "resource exhausted" AND "check quota" together
|
|
5619
|
+
// to avoid matching generic 500s that mention "exhausted" in a different context
|
|
5620
|
+
/resource.?exhausted.*check.?quota/i,
|
|
5394
5621
|
/RESOURCE_EXHAUSTED/,
|
|
5395
5622
|
/exhausted.*quota/i
|
|
5396
5623
|
];
|
|
@@ -5497,9 +5724,15 @@ import { spawn as spawn2 } from "child_process";
|
|
|
5497
5724
|
import { createInterface as createInterface2 } from "readline";
|
|
5498
5725
|
import { readFileSync as readFileSync4 } from "fs";
|
|
5499
5726
|
function stripFrontmatter(text) {
|
|
5500
|
-
const
|
|
5501
|
-
if (
|
|
5502
|
-
|
|
5727
|
+
const lines = text.split("\n");
|
|
5728
|
+
if (lines.length < 2 || lines[0].trim() !== "---") return text;
|
|
5729
|
+
for (let i = 1; i < lines.length; i++) {
|
|
5730
|
+
if (lines[i].trim() === "---") {
|
|
5731
|
+
const start = i + 1 < lines.length && lines[i + 1].trim() === "" ? i + 2 : i + 1;
|
|
5732
|
+
return lines.slice(start).join("\n");
|
|
5733
|
+
}
|
|
5734
|
+
}
|
|
5735
|
+
return text;
|
|
5503
5736
|
}
|
|
5504
5737
|
function buildAgentPrompt(opts, runnerSkillPath) {
|
|
5505
5738
|
const parts = [];
|
|
@@ -5527,7 +5760,11 @@ function buildSpawnEnv(runner, isSubAgent = false) {
|
|
|
5527
5760
|
const runnerEnv = runner.getEnv();
|
|
5528
5761
|
const env = { ...base, ...runnerEnv };
|
|
5529
5762
|
if (isSubAgent) {
|
|
5530
|
-
for (const key of SENSITIVE_ENV_KEYS)
|
|
5763
|
+
for (const key of SENSITIVE_ENV_KEYS) {
|
|
5764
|
+
if (!(key in runnerEnv)) {
|
|
5765
|
+
delete env[key];
|
|
5766
|
+
}
|
|
5767
|
+
}
|
|
5531
5768
|
}
|
|
5532
5769
|
return env;
|
|
5533
5770
|
}
|
|
@@ -5609,7 +5846,11 @@ var init_spawn = __esm({
|
|
|
5609
5846
|
"ELEVENLABS_API_KEY",
|
|
5610
5847
|
"DASHBOARD_TOKEN",
|
|
5611
5848
|
"ALLOWED_CHAT_ID",
|
|
5612
|
-
"CC_CLAW_DASHBOARD_TOKEN"
|
|
5849
|
+
"CC_CLAW_DASHBOARD_TOKEN",
|
|
5850
|
+
// Audit C15: API keys that should not leak to sub-agents
|
|
5851
|
+
"ANTHROPIC_API_KEY",
|
|
5852
|
+
"GEMINI_API_KEY",
|
|
5853
|
+
"OPENAI_API_KEY"
|
|
5613
5854
|
];
|
|
5614
5855
|
}
|
|
5615
5856
|
});
|
|
@@ -5619,7 +5860,7 @@ var MAX_CONCURRENT_AGENTS, IDLE_THRESHOLD_MS, DEFAULT_MAX_RUNTIME_MS, BUDGET_WAR
|
|
|
5619
5860
|
var init_types = __esm({
|
|
5620
5861
|
"src/agents/types.ts"() {
|
|
5621
5862
|
"use strict";
|
|
5622
|
-
MAX_CONCURRENT_AGENTS = 4;
|
|
5863
|
+
MAX_CONCURRENT_AGENTS = parseInt(process.env.CC_CLAW_MAX_AGENTS ?? "4", 10);
|
|
5623
5864
|
IDLE_THRESHOLD_MS = 6e4;
|
|
5624
5865
|
DEFAULT_MAX_RUNTIME_MS = 6e5;
|
|
5625
5866
|
BUDGET_WARNING_PERCENT = 0.8;
|
|
@@ -5640,6 +5881,9 @@ function checkBudget(db3, orchestrationId) {
|
|
|
5640
5881
|
if (budgetLimit === null || budgetLimit === void 0) {
|
|
5641
5882
|
return { totalCost, budgetLimit: null, percentUsed: null, exceeded: false, warning: false };
|
|
5642
5883
|
}
|
|
5884
|
+
if (budgetLimit <= 0) {
|
|
5885
|
+
return { totalCost, budgetLimit, percentUsed: 100, exceeded: true, warning: true };
|
|
5886
|
+
}
|
|
5643
5887
|
const percentUsed = totalCost / budgetLimit * 100;
|
|
5644
5888
|
const exceeded = totalCost >= budgetLimit;
|
|
5645
5889
|
const warning4 = percentUsed >= BUDGET_WARNING_PERCENT * 100;
|
|
@@ -5669,18 +5913,40 @@ async function discoverExistingMcps(runner) {
|
|
|
5669
5913
|
encoding: "utf-8",
|
|
5670
5914
|
env: runner.getEnv(),
|
|
5671
5915
|
cwd: homedir3(),
|
|
5672
|
-
|
|
5916
|
+
// Audit O20: Reduced from 30s — mcp list should be fast
|
|
5917
|
+
timeout: 1e4
|
|
5673
5918
|
});
|
|
5674
5919
|
const stdout = typeof result === "string" ? result : Array.isArray(result) ? result[0] : result?.stdout ?? null;
|
|
5675
5920
|
if (stdout == null) return [];
|
|
5921
|
+
try {
|
|
5922
|
+
const parsed = JSON.parse(stdout);
|
|
5923
|
+
const servers = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.servers) ? parsed.servers : Array.isArray(parsed?.mcpServers) ? parsed.mcpServers : null;
|
|
5924
|
+
if (servers) {
|
|
5925
|
+
return servers.map((s) => s.name ?? s.id).filter((n) => typeof n === "string" && n.length > 0);
|
|
5926
|
+
}
|
|
5927
|
+
} catch {
|
|
5928
|
+
}
|
|
5929
|
+
const NAME_PATTERNS = [
|
|
5930
|
+
/^([a-zA-Z0-9_-]+)\s*:/,
|
|
5931
|
+
// "name: description" format
|
|
5932
|
+
/^([a-zA-Z0-9_-]+)\s*\(/,
|
|
5933
|
+
// "name (transport)" format
|
|
5934
|
+
/^\s*"?([a-zA-Z0-9_-]+)"?\s*$/,
|
|
5935
|
+
// bare name on its own line
|
|
5936
|
+
/name["\s:=]+([a-zA-Z0-9_-]+)/i
|
|
5937
|
+
// key-value: name = "foo"
|
|
5938
|
+
];
|
|
5676
5939
|
const seen = /* @__PURE__ */ new Set();
|
|
5677
5940
|
for (const line of stdout.split("\n")) {
|
|
5678
5941
|
const trimmed = line.trim();
|
|
5679
5942
|
if (!trimmed || SKIP_LINE.test(trimmed)) continue;
|
|
5680
5943
|
const cleaned = trimmed.replace(/^[✓✗•\-●]\s*/, "");
|
|
5681
|
-
const
|
|
5682
|
-
|
|
5683
|
-
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
|
+
}
|
|
5684
5950
|
}
|
|
5685
5951
|
}
|
|
5686
5952
|
return [...seen];
|
|
@@ -5693,31 +5959,47 @@ function diffMcps(needed, existing) {
|
|
|
5693
5959
|
return needed.filter((name) => !existingSet.has(name.toLowerCase()));
|
|
5694
5960
|
}
|
|
5695
5961
|
function defToConfig(def) {
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5962
|
+
try {
|
|
5963
|
+
const config2 = {
|
|
5964
|
+
name: def.name,
|
|
5965
|
+
transport: def.transport
|
|
5966
|
+
};
|
|
5967
|
+
if (def.command) config2.command = def.command;
|
|
5968
|
+
if (def.args) config2.args = JSON.parse(def.args);
|
|
5969
|
+
if (def.url) config2.url = def.url;
|
|
5970
|
+
if (def.env) config2.env = JSON.parse(def.env);
|
|
5971
|
+
return config2;
|
|
5972
|
+
} catch (err) {
|
|
5973
|
+
const { warn: warn2 } = (init_log(), __toCommonJS(log_exports));
|
|
5974
|
+
warn2(`[mcp] Skipping MCP "${def.name}" \u2014 malformed JSON in args/env:`, err);
|
|
5975
|
+
return null;
|
|
5976
|
+
}
|
|
5705
5977
|
}
|
|
5706
5978
|
async function injectMcps(runner, mcpNames, db3, scope) {
|
|
5707
5979
|
const added = [];
|
|
5708
5980
|
const exe = runner.getExecutablePath();
|
|
5709
5981
|
const runnerId = runner.id;
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5982
|
+
const results = await Promise.allSettled(
|
|
5983
|
+
mcpNames.map(async (name) => {
|
|
5984
|
+
const def = getMcpServer(db3, name);
|
|
5985
|
+
if (!def) return null;
|
|
5986
|
+
try {
|
|
5987
|
+
const config2 = defToConfig(def);
|
|
5988
|
+
if (!config2) return null;
|
|
5989
|
+
const addCmd = runner.getMcpAddCommand(config2);
|
|
5990
|
+
const args = addCmd.slice(1);
|
|
5991
|
+
await execFileAsync(exe, args, { encoding: "utf-8" });
|
|
5992
|
+
addPropagation(db3, name, runnerId, scope);
|
|
5993
|
+
return name;
|
|
5994
|
+
} catch (err) {
|
|
5995
|
+
warn(`[mcp] inject ${name} \u2192 ${runnerId} failed:`, err);
|
|
5996
|
+
return null;
|
|
5997
|
+
}
|
|
5998
|
+
})
|
|
5999
|
+
);
|
|
6000
|
+
for (const result of results) {
|
|
6001
|
+
if (result.status === "fulfilled" && result.value) {
|
|
6002
|
+
added.push(result.value);
|
|
5721
6003
|
}
|
|
5722
6004
|
}
|
|
5723
6005
|
return added;
|
|
@@ -5730,9 +6012,11 @@ async function cleanupMcps(runner, mcps2, db3, scope) {
|
|
|
5730
6012
|
const removeCmd = runner.getMcpRemoveCommand(name);
|
|
5731
6013
|
const args = removeCmd.slice(1);
|
|
5732
6014
|
await execFileAsync(exe, args, { encoding: "utf-8" });
|
|
6015
|
+
removePropagation(db3, name, runnerId, scope);
|
|
5733
6016
|
} catch {
|
|
6017
|
+
const { warn: warn2 } = (init_log(), __toCommonJS(log_exports));
|
|
6018
|
+
warn2(`[propagate] Failed to remove MCP "${name}" from ${runnerId} \u2014 record kept for retry`);
|
|
5734
6019
|
}
|
|
5735
|
-
removePropagation(db3, name, runnerId, scope);
|
|
5736
6020
|
}
|
|
5737
6021
|
}
|
|
5738
6022
|
var execFileAsync, SKIP_LINE;
|
|
@@ -5740,6 +6024,7 @@ var init_propagate = __esm({
|
|
|
5740
6024
|
"src/mcps/propagate.ts"() {
|
|
5741
6025
|
"use strict";
|
|
5742
6026
|
init_store2();
|
|
6027
|
+
init_log();
|
|
5743
6028
|
execFileAsync = promisify(execFile);
|
|
5744
6029
|
SKIP_LINE = /^(Checking|Configured|No MCP|Try |──)/i;
|
|
5745
6030
|
}
|
|
@@ -6011,7 +6296,7 @@ var init_agent_log = __esm({
|
|
|
6011
6296
|
import { existsSync as existsSync10 } from "fs";
|
|
6012
6297
|
async function withRunnerLock(runnerId, fn) {
|
|
6013
6298
|
const prev = runnerLocks.get(runnerId) ?? Promise.resolve();
|
|
6014
|
-
const next = prev.then(fn, fn);
|
|
6299
|
+
const next = prev.then(fn, () => fn());
|
|
6015
6300
|
runnerLocks.set(runnerId, next.then(() => {
|
|
6016
6301
|
}, () => {
|
|
6017
6302
|
}));
|
|
@@ -6176,12 +6461,13 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6176
6461
|
warn(`[orchestrator] MCP injection failed for agent ${agentId}:`, err);
|
|
6177
6462
|
}
|
|
6178
6463
|
}
|
|
6464
|
+
const dashboardEnabled = process.env.DASHBOARD_ENABLED === "1";
|
|
6179
6465
|
let mcpExtraArgs = [];
|
|
6180
6466
|
let orchestratorMcpName = "";
|
|
6181
|
-
if (
|
|
6467
|
+
if (!dashboardEnabled) {
|
|
6182
6468
|
warn(`[orchestrator] DASHBOARD_ENABLED is not set \u2014 agent ${agentId.slice(0, 8)} will NOT have orchestrator MCP tools (set_state, send_message, etc.)`);
|
|
6183
6469
|
}
|
|
6184
|
-
if (
|
|
6470
|
+
if (dashboardEnabled) {
|
|
6185
6471
|
try {
|
|
6186
6472
|
const { createSubAgentToken: createSubAgentToken2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
6187
6473
|
const scopedToken = createSubAgentToken2(agentId);
|
|
@@ -6193,10 +6479,12 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6193
6479
|
} else {
|
|
6194
6480
|
await withRunnerLock(runner.id, async () => {
|
|
6195
6481
|
const addCmd = runner.getMcpAddCommand(mcpConfig);
|
|
6196
|
-
const {
|
|
6482
|
+
const { execFile: execFile5 } = await import("child_process");
|
|
6483
|
+
const { promisify: promisify3 } = await import("util");
|
|
6484
|
+
const execFileAsync3 = promisify3(execFile5);
|
|
6197
6485
|
const exe = addCmd[0];
|
|
6198
6486
|
const args = addCmd.slice(1);
|
|
6199
|
-
|
|
6487
|
+
await execFileAsync3(exe, args, { encoding: "utf-8", timeout: 15e3, env: runner.getEnv() });
|
|
6200
6488
|
});
|
|
6201
6489
|
mcpsAdded = [...mcpsAdded, orchestratorMcpName];
|
|
6202
6490
|
updateAgentMcpsAdded(db3, agentId, mcpsAdded);
|
|
@@ -6211,6 +6499,7 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6211
6499
|
}
|
|
6212
6500
|
updateAgentStatus(db3, agentId, "starting");
|
|
6213
6501
|
const stderrChunks = [];
|
|
6502
|
+
const MAX_STDERR_CHUNKS = 50;
|
|
6214
6503
|
const rawStdoutLines = [];
|
|
6215
6504
|
let gotResult = false;
|
|
6216
6505
|
const startedAt = Date.now();
|
|
@@ -6268,7 +6557,9 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6268
6557
|
);
|
|
6269
6558
|
startNextQueued(chatId);
|
|
6270
6559
|
},
|
|
6271
|
-
onStderr: (chunk) =>
|
|
6560
|
+
onStderr: (chunk) => {
|
|
6561
|
+
if (stderrChunks.length < MAX_STDERR_CHUNKS) stderrChunks.push(chunk);
|
|
6562
|
+
}
|
|
6272
6563
|
});
|
|
6273
6564
|
if (child.pid) {
|
|
6274
6565
|
updateAgentPid(db3, agentId, child.pid);
|
|
@@ -6295,6 +6586,9 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6295
6586
|
agentId
|
|
6296
6587
|
);
|
|
6297
6588
|
child.on("close", (code, signal) => {
|
|
6589
|
+
clearTimeout(timeoutTimers.get(agentId));
|
|
6590
|
+
timeoutTimers.delete(agentId);
|
|
6591
|
+
activeProcesses.delete(agentId);
|
|
6298
6592
|
if (gotResult) return;
|
|
6299
6593
|
const agent = getAgent(db3, agentId);
|
|
6300
6594
|
if (!agent || agent.status === "completed" || agent.status === "failed") return;
|
|
@@ -6322,10 +6616,10 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6322
6616
|
}
|
|
6323
6617
|
let exitCode = code ?? 1;
|
|
6324
6618
|
let exitCodeForced = false;
|
|
6325
|
-
if (exitCode === 0) {
|
|
6619
|
+
if (exitCode === 0 && durationMs < 5e3) {
|
|
6326
6620
|
exitCode = 1;
|
|
6327
6621
|
exitCodeForced = true;
|
|
6328
|
-
warn(`[orchestrator] Agent ${agentId.slice(0, 8)}: exit code 0 with no output \u2014 forced to 1 (likely auth/model issue)`);
|
|
6622
|
+
warn(`[orchestrator] Agent ${agentId.slice(0, 8)}: exit code 0 with no output in ${durationMs}ms \u2014 forced to 1 (likely auth/model issue)`);
|
|
6329
6623
|
}
|
|
6330
6624
|
const errMsg = exitCodeForced ? `CLI exited cleanly but produced no output (${durationMs}ms) \u2014 likely auth failure or invalid model${stderr ? `: ${stderr.slice(0, 200)}` : ""}` : `CLI exited with code ${exitCode}${stderr ? `: ${stderr.slice(0, 200)}` : ""}`;
|
|
6331
6625
|
updateAgentResult(db3, agentId, {
|
|
@@ -6356,11 +6650,11 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
6356
6650
|
if (crashedAgent) {
|
|
6357
6651
|
const mcpsCrashed = crashedAgent.mcpsAdded ? JSON.parse(crashedAgent.mcpsAdded) : [];
|
|
6358
6652
|
if (mcpsCrashed.length > 0) {
|
|
6359
|
-
const
|
|
6360
|
-
if (
|
|
6361
|
-
const cleanupFn = () => cleanupMcps(
|
|
6362
|
-
if (
|
|
6363
|
-
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) => {
|
|
6364
6658
|
warn(`[orchestrator] MCP cleanup failed for crashed agent ${agentId.slice(0, 8)}:`, err);
|
|
6365
6659
|
});
|
|
6366
6660
|
} else {
|
|
@@ -6528,22 +6822,32 @@ async function handleAgentComplete(agentId, chatId, resultText, usage2, mcpsAdde
|
|
|
6528
6822
|
}
|
|
6529
6823
|
function startNextQueued(chatId) {
|
|
6530
6824
|
const db3 = getDb();
|
|
6531
|
-
const orch = getActiveOrchestration(db3, chatId);
|
|
6532
|
-
if (!orch) return;
|
|
6533
6825
|
const runningCount = getRunningAgentCount();
|
|
6534
6826
|
if (runningCount >= MAX_CONCURRENT_AGENTS) return;
|
|
6535
|
-
const
|
|
6827
|
+
const orch = getActiveOrchestration(db3, chatId);
|
|
6828
|
+
let next = null;
|
|
6829
|
+
let resolvedChatId = chatId;
|
|
6830
|
+
if (orch) {
|
|
6831
|
+
next = getNextQueuedAgent(db3, orch.id);
|
|
6832
|
+
}
|
|
6833
|
+
if (!next) {
|
|
6834
|
+
next = getNextQueuedAgentGlobal(db3);
|
|
6835
|
+
if (next) {
|
|
6836
|
+
const agentOrch = getOrchestration(db3, next.orchestrationId);
|
|
6837
|
+
if (agentOrch) resolvedChatId = agentOrch.chatId;
|
|
6838
|
+
}
|
|
6839
|
+
}
|
|
6536
6840
|
if (!next) return;
|
|
6537
6841
|
const agent = getAgent(db3, next.id);
|
|
6538
6842
|
if (!agent) return;
|
|
6539
|
-
startAgent(next.id,
|
|
6843
|
+
startAgent(next.id, resolvedChatId, {
|
|
6540
6844
|
runner: agent.runnerId,
|
|
6541
6845
|
task: agent.task ?? "",
|
|
6542
6846
|
name: agent.name ?? void 0,
|
|
6543
6847
|
description: agent.description ?? void 0,
|
|
6544
6848
|
model: agent.model ?? void 0,
|
|
6545
6849
|
skills: JSON.parse(agent.skills),
|
|
6546
|
-
permMode: agent.permMode === "inherit" ? getMode(
|
|
6850
|
+
permMode: agent.permMode === "inherit" ? getMode(resolvedChatId) : agent.permMode,
|
|
6547
6851
|
role: agent.role ?? "worker",
|
|
6548
6852
|
persona: agent.persona ?? void 0,
|
|
6549
6853
|
maxRuntimeMs: agent.maxRuntimeMs,
|
|
@@ -6604,6 +6908,10 @@ function cancelAgent(agentId, reason = "user_cancelled") {
|
|
|
6604
6908
|
}).catch(() => {
|
|
6605
6909
|
});
|
|
6606
6910
|
log(`[orchestrator] Agent ${agentId} cancelled (reason: ${reason})`);
|
|
6911
|
+
const cancelOrch = getOrchestration(db3, agent.orchestrationId);
|
|
6912
|
+
if (cancelOrch) {
|
|
6913
|
+
startNextQueued(cancelOrch.chatId);
|
|
6914
|
+
}
|
|
6607
6915
|
return true;
|
|
6608
6916
|
}
|
|
6609
6917
|
function cancelAllAgents(chatId, reason = "user_cancelled") {
|
|
@@ -6708,8 +7016,10 @@ function registerMcp(db3, opts) {
|
|
|
6708
7016
|
propagateChange(db3, opts.name, "add").catch(() => {
|
|
6709
7017
|
});
|
|
6710
7018
|
}
|
|
6711
|
-
function unregisterMcp(db3, name) {
|
|
6712
|
-
propagateChange(db3, name, "remove").catch(() => {
|
|
7019
|
+
async function unregisterMcp(db3, name) {
|
|
7020
|
+
await propagateChange(db3, name, "remove").catch((err) => {
|
|
7021
|
+
const { warn: warn2 } = (init_log(), __toCommonJS(log_exports));
|
|
7022
|
+
warn2(`[mcp] Propagation removal failed for "${name}":`, err);
|
|
6713
7023
|
});
|
|
6714
7024
|
removeMcpServer(db3, name);
|
|
6715
7025
|
}
|
|
@@ -7057,6 +7367,12 @@ import { createInterface as createInterface3 } from "readline";
|
|
|
7057
7367
|
import { readFileSync as readFileSync6, existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
7058
7368
|
import { join as join11 } from "path";
|
|
7059
7369
|
import { homedir as homedir4 } from "os";
|
|
7370
|
+
function applySignalDecay(confidence, createdAt) {
|
|
7371
|
+
const ageMs = Date.now() - (/* @__PURE__ */ new Date(createdAt + (createdAt.endsWith("Z") ? "" : "Z"))).getTime();
|
|
7372
|
+
const ageDays = ageMs / (24 * 60 * 60 * 1e3);
|
|
7373
|
+
const decayed = confidence * Math.pow(0.5, ageDays / SIGNAL_DECAY_HALF_LIFE_DAYS);
|
|
7374
|
+
return decayed >= SIGNAL_DECAY_MIN_CONFIDENCE ? Math.round(decayed * 100) / 100 : 0;
|
|
7375
|
+
}
|
|
7060
7376
|
function discoverReflectionTargets() {
|
|
7061
7377
|
const ccClawHome = join11(homedir4(), ".cc-claw");
|
|
7062
7378
|
const targets = [];
|
|
@@ -7070,7 +7386,7 @@ function discoverReflectionTargets() {
|
|
|
7070
7386
|
if (!existsSync11(skillFile)) continue;
|
|
7071
7387
|
let desc = "skill";
|
|
7072
7388
|
try {
|
|
7073
|
-
const content = readFileSync6(skillFile, "utf-8")
|
|
7389
|
+
const content = readFileSync6(skillFile, "utf-8");
|
|
7074
7390
|
const descMatch = content.match(/description:\s*["']?([^"'\n]+)/);
|
|
7075
7391
|
if (descMatch) desc = descMatch[1].trim().slice(0, 80);
|
|
7076
7392
|
} catch {
|
|
@@ -7138,6 +7454,13 @@ ${categoryList}`);
|
|
|
7138
7454
|
sections.push(soulMd || "(empty)");
|
|
7139
7455
|
sections.push("[Current USER.md]");
|
|
7140
7456
|
sections.push(userMd || "(empty)");
|
|
7457
|
+
if (params.skillContents && params.skillContents.length > 0) {
|
|
7458
|
+
sections.push("[Current Skill File Contents]");
|
|
7459
|
+
for (const skill of params.skillContents) {
|
|
7460
|
+
sections.push(`--- ${skill.path} ---`);
|
|
7461
|
+
sections.push(skill.content);
|
|
7462
|
+
}
|
|
7463
|
+
}
|
|
7141
7464
|
sections.push("[Previously Applied Insights]");
|
|
7142
7465
|
if (appliedInsights.length === 0) {
|
|
7143
7466
|
sections.push("(none)");
|
|
@@ -7170,13 +7493,13 @@ ${categoryList}`);
|
|
|
7170
7493
|
Available targets:
|
|
7171
7494
|
${targetList.map((t) => `- ${t}`).join("\n")}
|
|
7172
7495
|
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7496
|
+
Routing priority (MOST SPECIFIC first \u2014 SOUL.md is a LAST RESORT):
|
|
7497
|
+
1. Workflow, process, or tool usage \u2192 the relevant skill file
|
|
7498
|
+
2. Domain knowledge or reference material \u2192 a context file (workspace/context/)
|
|
7499
|
+
3. User preference, profile, personal info \u2192 USER.md
|
|
7500
|
+
4. Personality, tone, general behavior \u2192 SOUL.md (ONLY if no skill/context file fits)
|
|
7501
|
+
|
|
7502
|
+
IMPORTANT: Before routing to SOUL.md, check if an existing skill or context file covers the topic. If one exists, route there instead. SOUL.md should only receive broad personality or behavioral rules that don't fit anywhere else.`);
|
|
7180
7503
|
const targetOptions = targetList.map((t) => t.split(" \u2014 ")[0]).join(" | ");
|
|
7181
7504
|
sections.push(`[Output Format]
|
|
7182
7505
|
For each insight, output EXACTLY this format. Separate multiple insights with "---" on its own line.
|
|
@@ -7350,7 +7673,7 @@ async function spawnAnalysis(adapter, model2, prompt, timeoutMs = ANALYSIS_TIMEO
|
|
|
7350
7673
|
}
|
|
7351
7674
|
const events = adapter.parseLine(msg);
|
|
7352
7675
|
for (const ev of events) {
|
|
7353
|
-
if (ev.type === "text" && ev.text) accumulatedText
|
|
7676
|
+
if (ev.type === "text" && ev.text) accumulatedText = appendTextChunk(accumulatedText, ev.text);
|
|
7354
7677
|
if (ev.type === "result") {
|
|
7355
7678
|
resultText = ev.resultText || accumulatedText;
|
|
7356
7679
|
if (adapter.shouldKillOnResult()) {
|
|
@@ -7373,6 +7696,18 @@ async function spawnAnalysis(adapter, model2, prompt, timeoutMs = ANALYSIS_TIMEO
|
|
|
7373
7696
|
return resultText;
|
|
7374
7697
|
}
|
|
7375
7698
|
async function runAnalysis(chatId, opts = {}) {
|
|
7699
|
+
if (analysisLocks.has(chatId)) {
|
|
7700
|
+
log(`[reflection] Skipping analysis for ${chatId} \u2014 another analysis is already in progress`);
|
|
7701
|
+
return [];
|
|
7702
|
+
}
|
|
7703
|
+
analysisLocks.add(chatId);
|
|
7704
|
+
try {
|
|
7705
|
+
return await runAnalysisImpl(chatId, opts);
|
|
7706
|
+
} finally {
|
|
7707
|
+
analysisLocks.delete(chatId);
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7710
|
+
async function runAnalysisImpl(chatId, opts) {
|
|
7376
7711
|
const db3 = getDb();
|
|
7377
7712
|
const { force = false } = opts;
|
|
7378
7713
|
if (!force) {
|
|
@@ -7398,13 +7733,37 @@ async function runAnalysis(chatId, opts = {}) {
|
|
|
7398
7733
|
const rejected = getRejectedInsights(db3, chatId);
|
|
7399
7734
|
const codebaseEnabled = process.env.REFLECTION_CODEBASE_RECOMMENDATIONS === "1";
|
|
7400
7735
|
const availableTargets = discoverReflectionTargets();
|
|
7736
|
+
const SKILL_CONTENT_CAP = 15e3;
|
|
7737
|
+
const skillContents = [];
|
|
7738
|
+
const ccClawHome = join11(homedir4(), ".cc-claw");
|
|
7739
|
+
const signalCorpus = signals.map((s) => s.trigger).join(" ").toLowerCase();
|
|
7740
|
+
const convCorpus = (conversations ?? "").toLowerCase();
|
|
7741
|
+
const searchCorpus = signalCorpus + " " + convCorpus;
|
|
7742
|
+
let totalSkillChars = 0;
|
|
7743
|
+
for (const target of availableTargets) {
|
|
7744
|
+
if (!target.path.startsWith("workspace/skills/")) continue;
|
|
7745
|
+
const skillName = target.path.split("/")[2] ?? "";
|
|
7746
|
+
const skillNameNorm = skillName.toLowerCase().replace(/-/g, " ");
|
|
7747
|
+
const descNorm = target.description.toLowerCase();
|
|
7748
|
+
const isRelevant = searchCorpus.includes(skillNameNorm) || searchCorpus.includes(skillName.toLowerCase()) || descNorm.split(/\s+/).some((word) => word.length > 4 && searchCorpus.includes(word));
|
|
7749
|
+
if (!isRelevant) continue;
|
|
7750
|
+
try {
|
|
7751
|
+
const fullPath = join11(ccClawHome, target.path);
|
|
7752
|
+
if (existsSync11(fullPath)) {
|
|
7753
|
+
const content = readFileSync6(fullPath, "utf-8");
|
|
7754
|
+
if (totalSkillChars + content.length > SKILL_CONTENT_CAP) break;
|
|
7755
|
+
skillContents.push({ path: target.path, content });
|
|
7756
|
+
totalSkillChars += content.length;
|
|
7757
|
+
}
|
|
7758
|
+
} catch {
|
|
7759
|
+
}
|
|
7760
|
+
}
|
|
7401
7761
|
const prompt = buildAnalysisPrompt({
|
|
7402
|
-
signals: signals.map((s) =>
|
|
7403
|
-
|
|
7404
|
-
trigger: s.trigger,
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
})),
|
|
7762
|
+
signals: signals.map((s) => {
|
|
7763
|
+
const decayedConfidence = applySignalDecay(s.confidence, s.created_at);
|
|
7764
|
+
return { signalType: s.signalType, trigger: s.trigger, confidence: decayedConfidence, created_at: s.created_at };
|
|
7765
|
+
}).filter((s) => s.confidence > 0),
|
|
7766
|
+
// drop signals that decayed below threshold
|
|
7408
7767
|
conversations,
|
|
7409
7768
|
soulMd,
|
|
7410
7769
|
userMd,
|
|
@@ -7420,7 +7779,8 @@ async function runAnalysis(chatId, opts = {}) {
|
|
|
7420
7779
|
category: i.category
|
|
7421
7780
|
})),
|
|
7422
7781
|
codebaseEnabled,
|
|
7423
|
-
availableTargets
|
|
7782
|
+
availableTargets,
|
|
7783
|
+
skillContents
|
|
7424
7784
|
});
|
|
7425
7785
|
const resolved = resolveReflectionAdapter(chatId);
|
|
7426
7786
|
if (!resolved) {
|
|
@@ -7442,6 +7802,7 @@ async function runAnalysis(chatId, opts = {}) {
|
|
|
7442
7802
|
return [];
|
|
7443
7803
|
}
|
|
7444
7804
|
const parsed = parseAnalysisOutput(rawOutput);
|
|
7805
|
+
const hasUsableOutput = parsed.length > 0;
|
|
7445
7806
|
const signalIdStr = signals.length > 0 ? signals.map((s) => s.id).join(",") : "manual";
|
|
7446
7807
|
for (const insight of parsed) {
|
|
7447
7808
|
let conflictsWithId = null;
|
|
@@ -7464,7 +7825,11 @@ async function runAnalysis(chatId, opts = {}) {
|
|
|
7464
7825
|
model: model2
|
|
7465
7826
|
});
|
|
7466
7827
|
}
|
|
7467
|
-
if (signals.length > 0)
|
|
7828
|
+
if (hasUsableOutput && signals.length > 0) {
|
|
7829
|
+
markSignalsProcessed(db3, signals.map((s) => s.id));
|
|
7830
|
+
} else if (!hasUsableOutput && signals.length > 0) {
|
|
7831
|
+
log(`[reflection] Analysis returned unparseable output for ${chatId} \u2014 signals preserved for retry`);
|
|
7832
|
+
}
|
|
7468
7833
|
aggregateDailyMetrics(db3, chatId);
|
|
7469
7834
|
logActivity(db3, {
|
|
7470
7835
|
chatId,
|
|
@@ -7545,11 +7910,12 @@ async function runNightlyReflection(opts = {}) {
|
|
|
7545
7910
|
log(`[reflection] Nightly reflection complete: ${results.length} chat(s) produced insights`);
|
|
7546
7911
|
return { results };
|
|
7547
7912
|
}
|
|
7548
|
-
var ANALYSIS_TIMEOUT_MS, CONVERSATIONS_CAP, MAX_INSIGHTS, VALID_CATEGORIES, VALID_CATEGORIES_NO_CODEBASE;
|
|
7913
|
+
var ANALYSIS_TIMEOUT_MS, CONVERSATIONS_CAP, MAX_INSIGHTS, SIGNAL_DECAY_HALF_LIFE_DAYS, SIGNAL_DECAY_MIN_CONFIDENCE, VALID_CATEGORIES, VALID_CATEGORIES_NO_CODEBASE, analysisLocks;
|
|
7549
7914
|
var init_analyze = __esm({
|
|
7550
7915
|
"src/reflection/analyze.ts"() {
|
|
7551
7916
|
"use strict";
|
|
7552
7917
|
init_log();
|
|
7918
|
+
init_text_utils();
|
|
7553
7919
|
init_paths();
|
|
7554
7920
|
init_store5();
|
|
7555
7921
|
init_backends();
|
|
@@ -7559,6 +7925,8 @@ var init_analyze = __esm({
|
|
|
7559
7925
|
ANALYSIS_TIMEOUT_MS = 12e4;
|
|
7560
7926
|
CONVERSATIONS_CAP = 4e3;
|
|
7561
7927
|
MAX_INSIGHTS = 5;
|
|
7928
|
+
SIGNAL_DECAY_HALF_LIFE_DAYS = 7;
|
|
7929
|
+
SIGNAL_DECAY_MIN_CONFIDENCE = 0.1;
|
|
7562
7930
|
VALID_CATEGORIES = [
|
|
7563
7931
|
"behavior",
|
|
7564
7932
|
"preference",
|
|
@@ -7574,6 +7942,7 @@ var init_analyze = __esm({
|
|
|
7574
7942
|
"domain",
|
|
7575
7943
|
"error_pattern"
|
|
7576
7944
|
];
|
|
7945
|
+
analysisLocks = /* @__PURE__ */ new Set();
|
|
7577
7946
|
}
|
|
7578
7947
|
});
|
|
7579
7948
|
|
|
@@ -7862,12 +8231,12 @@ import { randomBytes } from "crypto";
|
|
|
7862
8231
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync13 } from "fs";
|
|
7863
8232
|
function createSubAgentToken(agentId) {
|
|
7864
8233
|
const token = `sub:${agentId.slice(0, 8)}:${randomBytes(16).toString("hex")}`;
|
|
7865
|
-
subAgentTokens.set(token, agentId);
|
|
8234
|
+
subAgentTokens.set(token, { agentId, expiresAt: Date.now() + SUB_AGENT_TOKEN_TTL_MS });
|
|
7866
8235
|
return token;
|
|
7867
8236
|
}
|
|
7868
8237
|
function revokeSubAgentToken(agentId) {
|
|
7869
|
-
for (const [token,
|
|
7870
|
-
if (
|
|
8238
|
+
for (const [token, entry] of subAgentTokens) {
|
|
8239
|
+
if (entry.agentId === agentId) {
|
|
7871
8240
|
subAgentTokens.delete(token);
|
|
7872
8241
|
break;
|
|
7873
8242
|
}
|
|
@@ -7881,6 +8250,14 @@ function htmlResponse(res, html) {
|
|
|
7881
8250
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
7882
8251
|
res.end(html);
|
|
7883
8252
|
}
|
|
8253
|
+
function validateAgentIdentity(req, body) {
|
|
8254
|
+
const boundAgentId = req.__boundAgentId;
|
|
8255
|
+
if (!boundAgentId) return;
|
|
8256
|
+
const callerAgentId = body.agentId ?? body.fromAgentId ?? body.message?.fromAgentId;
|
|
8257
|
+
if (callerAgentId && callerAgentId !== boundAgentId) {
|
|
8258
|
+
throw new Error(`IDENTITY_MISMATCH: Token bound to agent ${boundAgentId}, but request claims ${callerAgentId}`);
|
|
8259
|
+
}
|
|
8260
|
+
}
|
|
7884
8261
|
function readBody(req) {
|
|
7885
8262
|
return new Promise((resolve, reject) => {
|
|
7886
8263
|
const chunks = [];
|
|
@@ -7903,22 +8280,31 @@ function startDashboard() {
|
|
|
7903
8280
|
const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
|
|
7904
8281
|
const authHeader = req.headers.authorization ?? "";
|
|
7905
8282
|
const bearerToken = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
7906
|
-
const
|
|
7907
|
-
const presentedToken = bearerToken || queryToken;
|
|
8283
|
+
const presentedToken = bearerToken;
|
|
7908
8284
|
const isMainToken = presentedToken === DASHBOARD_TOKEN;
|
|
7909
|
-
const
|
|
8285
|
+
const subEntry = subAgentTokens.get(presentedToken);
|
|
8286
|
+
const isSubAgentToken = !!subEntry && subEntry.expiresAt > Date.now();
|
|
7910
8287
|
if (!isMainToken && !isSubAgentToken) {
|
|
7911
8288
|
res.writeHead(401, { "Content-Type": "text/plain" });
|
|
7912
8289
|
res.end("Unauthorized");
|
|
7913
8290
|
return;
|
|
7914
8291
|
}
|
|
7915
|
-
const
|
|
7916
|
-
"/api/orchestrator/
|
|
7917
|
-
"/api/orchestrator/
|
|
7918
|
-
"/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"
|
|
7919
8302
|
]);
|
|
7920
|
-
if (isSubAgentToken &&
|
|
7921
|
-
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;
|
|
7922
8308
|
}
|
|
7923
8309
|
if (url.pathname === "/api/health") {
|
|
7924
8310
|
return jsonResponse(res, { status: "ok", version: VERSION, uptime: process.uptime() });
|
|
@@ -8013,8 +8399,8 @@ function startDashboard() {
|
|
|
8013
8399
|
if (url.pathname.startsWith("/api/mcps/") && url.pathname !== "/api/mcps/sync" && url.pathname !== "/api/mcps/import" && req.method === "DELETE") {
|
|
8014
8400
|
try {
|
|
8015
8401
|
const name = decodeURIComponent(url.pathname.slice("/api/mcps/".length));
|
|
8016
|
-
const {
|
|
8017
|
-
|
|
8402
|
+
const { unregisterMcp: unregisterMcp2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports2));
|
|
8403
|
+
await unregisterMcp2(getDb(), name);
|
|
8018
8404
|
return jsonResponse(res, { success: true });
|
|
8019
8405
|
} catch (err) {
|
|
8020
8406
|
return jsonResponse(res, { error: errorMessage(err) }, 400);
|
|
@@ -8085,6 +8471,7 @@ function startDashboard() {
|
|
|
8085
8471
|
if (url.pathname === "/api/orchestrator/send-message" && req.method === "POST") {
|
|
8086
8472
|
try {
|
|
8087
8473
|
const body = JSON.parse(await readBody(req));
|
|
8474
|
+
validateAgentIdentity(req, body);
|
|
8088
8475
|
const db3 = getDb();
|
|
8089
8476
|
const orchId = getOrCreateOrchestration(body.chatId);
|
|
8090
8477
|
const msgId = sendInboxMessage(db3, { orchestrationId: orchId, ...body.message });
|
|
@@ -8096,6 +8483,7 @@ function startDashboard() {
|
|
|
8096
8483
|
if (url.pathname === "/api/orchestrator/read-inbox" && req.method === "POST") {
|
|
8097
8484
|
try {
|
|
8098
8485
|
const body = JSON.parse(await readBody(req));
|
|
8486
|
+
validateAgentIdentity(req, body);
|
|
8099
8487
|
const db3 = getDb();
|
|
8100
8488
|
const orchId = getOrCreateOrchestration(body.chatId);
|
|
8101
8489
|
const messages = getUnreadMessages(db3, orchId, body.agentId);
|
|
@@ -8110,6 +8498,7 @@ function startDashboard() {
|
|
|
8110
8498
|
if (url.pathname === "/api/orchestrator/set-state" && req.method === "POST") {
|
|
8111
8499
|
try {
|
|
8112
8500
|
const body = JSON.parse(await readBody(req));
|
|
8501
|
+
validateAgentIdentity(req, body);
|
|
8113
8502
|
const db3 = getDb();
|
|
8114
8503
|
const orchId = getOrCreateOrchestration(body.chatId);
|
|
8115
8504
|
setState(db3, orchId, body.key, body.value, body.setBy);
|
|
@@ -8144,6 +8533,7 @@ function startDashboard() {
|
|
|
8144
8533
|
if (url.pathname === "/api/orchestrator/broadcast" && req.method === "POST") {
|
|
8145
8534
|
try {
|
|
8146
8535
|
const body = JSON.parse(await readBody(req));
|
|
8536
|
+
validateAgentIdentity(req, body);
|
|
8147
8537
|
const db3 = getDb();
|
|
8148
8538
|
const orchId = getOrCreateOrchestration(body.chatId);
|
|
8149
8539
|
const agents2 = listActiveAgents(db3).filter((a) => a.orchestrationId === orchId);
|
|
@@ -8306,11 +8696,17 @@ function startDashboard() {
|
|
|
8306
8696
|
"Cache-Control": "no-cache",
|
|
8307
8697
|
Connection: "keep-alive"
|
|
8308
8698
|
});
|
|
8699
|
+
let clientDisconnected = false;
|
|
8700
|
+
res.on("close", () => {
|
|
8701
|
+
clientDisconnected = true;
|
|
8702
|
+
});
|
|
8309
8703
|
const sendSSE = (event, data) => {
|
|
8310
|
-
res.
|
|
8704
|
+
if (!clientDisconnected && !res.writableEnded) {
|
|
8705
|
+
res.write(`event: ${event}
|
|
8311
8706
|
data: ${JSON.stringify(data)}
|
|
8312
8707
|
|
|
8313
8708
|
`);
|
|
8709
|
+
}
|
|
8314
8710
|
};
|
|
8315
8711
|
try {
|
|
8316
8712
|
const response = await askAgent2(chatId, body.message, {
|
|
@@ -8323,10 +8719,10 @@ data: ${JSON.stringify(data)}
|
|
|
8323
8719
|
});
|
|
8324
8720
|
if (response.usage) addUsage2(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, model2 ?? "unknown", void 0, response.usage.contextSize);
|
|
8325
8721
|
sendSSE("done", JSON.stringify({ text: response.text, usage: response.usage }));
|
|
8326
|
-
res.end();
|
|
8722
|
+
if (!res.writableEnded) res.end();
|
|
8327
8723
|
} catch (err) {
|
|
8328
8724
|
sendSSE("error", errorMessage(err));
|
|
8329
|
-
res.end();
|
|
8725
|
+
if (!res.writableEnded) res.end();
|
|
8330
8726
|
}
|
|
8331
8727
|
} else {
|
|
8332
8728
|
const response = await askAgent2(chatId, body.message, { cwd, model: model2, permMode: mode });
|
|
@@ -8577,6 +8973,75 @@ data: ${JSON.stringify(data)}
|
|
|
8577
8973
|
const result = await triggerJob2(id);
|
|
8578
8974
|
return jsonResponse(res, { success: true, result });
|
|
8579
8975
|
}
|
|
8976
|
+
if (action === "create") {
|
|
8977
|
+
const { insertJob: insertJob2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
8978
|
+
const { startSingleJob: startSingleJob2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
8979
|
+
const job = insertJob2(body);
|
|
8980
|
+
startSingleJob2(job);
|
|
8981
|
+
return jsonResponse(res, { id: job.id, success: true });
|
|
8982
|
+
}
|
|
8983
|
+
if (action === "edit") {
|
|
8984
|
+
const { getDb: getDb2, getJobById: getJobById2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
8985
|
+
const { startSingleJob: startSingleJob2, stopJobTimer: stopJobTimer2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
8986
|
+
const db3 = getDb2();
|
|
8987
|
+
const updates = [];
|
|
8988
|
+
const values = [];
|
|
8989
|
+
if (body.title !== void 0) {
|
|
8990
|
+
updates.push("title = ?");
|
|
8991
|
+
values.push(body.title);
|
|
8992
|
+
}
|
|
8993
|
+
if (body.description !== void 0) {
|
|
8994
|
+
updates.push("description = ?");
|
|
8995
|
+
values.push(body.description);
|
|
8996
|
+
}
|
|
8997
|
+
if (body.cron !== void 0) {
|
|
8998
|
+
updates.push("cron = ?, schedule_type = 'cron', at_time = NULL, every_ms = NULL");
|
|
8999
|
+
values.push(body.cron);
|
|
9000
|
+
}
|
|
9001
|
+
if (body.atTime !== void 0) {
|
|
9002
|
+
updates.push("at_time = ?, schedule_type = 'at', cron = NULL, every_ms = NULL");
|
|
9003
|
+
values.push(body.atTime);
|
|
9004
|
+
}
|
|
9005
|
+
if (body.everyMs !== void 0) {
|
|
9006
|
+
updates.push("every_ms = ?, schedule_type = 'every', cron = NULL, at_time = NULL");
|
|
9007
|
+
values.push(body.everyMs);
|
|
9008
|
+
}
|
|
9009
|
+
if (body.backend !== void 0) {
|
|
9010
|
+
updates.push("backend = ?");
|
|
9011
|
+
values.push(body.backend);
|
|
9012
|
+
}
|
|
9013
|
+
if (body.model !== void 0) {
|
|
9014
|
+
updates.push("model = ?");
|
|
9015
|
+
values.push(body.model);
|
|
9016
|
+
}
|
|
9017
|
+
if (body.thinking !== void 0) {
|
|
9018
|
+
updates.push("thinking = ?");
|
|
9019
|
+
values.push(body.thinking);
|
|
9020
|
+
}
|
|
9021
|
+
if (body.timeout !== void 0) {
|
|
9022
|
+
updates.push("timeout = ?");
|
|
9023
|
+
values.push(body.timeout);
|
|
9024
|
+
}
|
|
9025
|
+
if (body.timezone !== void 0) {
|
|
9026
|
+
updates.push("timezone = ?");
|
|
9027
|
+
values.push(body.timezone);
|
|
9028
|
+
}
|
|
9029
|
+
if (body.fallbacks !== void 0) {
|
|
9030
|
+
updates.push("fallbacks = ?");
|
|
9031
|
+
values.push(JSON.stringify(body.fallbacks));
|
|
9032
|
+
}
|
|
9033
|
+
if (updates.length === 0) {
|
|
9034
|
+
return jsonResponse(res, { error: "No fields to update" }, 400);
|
|
9035
|
+
}
|
|
9036
|
+
values.push(body.id);
|
|
9037
|
+
db3.prepare(`UPDATE jobs SET ${updates.join(", ")} WHERE id = ?`).run(...values);
|
|
9038
|
+
const fresh = getJobById2(body.id);
|
|
9039
|
+
if (fresh && fresh.active && fresh.enabled) {
|
|
9040
|
+
stopJobTimer2(body.id);
|
|
9041
|
+
startSingleJob2(fresh);
|
|
9042
|
+
}
|
|
9043
|
+
return jsonResponse(res, { id: body.id, success: true });
|
|
9044
|
+
}
|
|
8580
9045
|
return jsonResponse(res, { error: "Unknown cron action" }, 400);
|
|
8581
9046
|
} catch (err) {
|
|
8582
9047
|
return jsonResponse(res, { error: errorMessage(err) }, 400);
|
|
@@ -8709,7 +9174,7 @@ data: ${JSON.stringify(data)}
|
|
|
8709
9174
|
function getDashboardToken() {
|
|
8710
9175
|
return DASHBOARD_TOKEN;
|
|
8711
9176
|
}
|
|
8712
|
-
var PORT, DASHBOARD_TOKEN, subAgentTokens, MAX_BODY_BYTES, DASHBOARD_HTML;
|
|
9177
|
+
var PORT, DASHBOARD_TOKEN, SUB_AGENT_TOKEN_TTL_MS, subAgentTokens, MAX_BODY_BYTES, DASHBOARD_HTML;
|
|
8713
9178
|
var init_server = __esm({
|
|
8714
9179
|
"src/dashboard/server.ts"() {
|
|
8715
9180
|
"use strict";
|
|
@@ -8729,7 +9194,14 @@ var init_server = __esm({
|
|
|
8729
9194
|
init_store3();
|
|
8730
9195
|
PORT = parseInt(process.env.DASHBOARD_PORT ?? "3141", 10);
|
|
8731
9196
|
DASHBOARD_TOKEN = process.env.DASHBOARD_TOKEN || randomBytes(32).toString("hex");
|
|
9197
|
+
SUB_AGENT_TOKEN_TTL_MS = 36e5;
|
|
8732
9198
|
subAgentTokens = /* @__PURE__ */ new Map();
|
|
9199
|
+
setInterval(() => {
|
|
9200
|
+
const now = Date.now();
|
|
9201
|
+
for (const [token, entry] of subAgentTokens) {
|
|
9202
|
+
if (entry.expiresAt < now) subAgentTokens.delete(token);
|
|
9203
|
+
}
|
|
9204
|
+
}, 3e5);
|
|
8733
9205
|
MAX_BODY_BYTES = 1048576;
|
|
8734
9206
|
DASHBOARD_HTML = `<!DOCTYPE html>
|
|
8735
9207
|
<html lang="en">
|
|
@@ -9106,7 +9578,8 @@ var init_detect = __esm({
|
|
|
9106
9578
|
init_store4();
|
|
9107
9579
|
init_store5();
|
|
9108
9580
|
CORRECTION_PATTERNS = [
|
|
9109
|
-
|
|
9581
|
+
// Audit O36: Tightened — require "no" followed by correction context (not "No problem", "No worries")
|
|
9582
|
+
/^no[,.\s!]+\s*(that'?s|it'?s|i\s|you|this|the|not|don'?t|stop|wrong)/i,
|
|
9110
9583
|
/\bwrong\b/i,
|
|
9111
9584
|
/\bthat's not (right|what|correct)/i,
|
|
9112
9585
|
/\bi (meant|said)\b/i,
|
|
@@ -9148,6 +9621,7 @@ var init_detect = __esm({
|
|
|
9148
9621
|
// src/agent.ts
|
|
9149
9622
|
var agent_exports = {};
|
|
9150
9623
|
__export(agent_exports, {
|
|
9624
|
+
FIRST_RESPONSE_TIMEOUT_ERROR: () => FIRST_RESPONSE_TIMEOUT_ERROR,
|
|
9151
9625
|
askAgent: () => askAgent,
|
|
9152
9626
|
getInFlightMessage: () => getInFlightMessage,
|
|
9153
9627
|
isChatBusy: () => isChatBusy,
|
|
@@ -9166,7 +9640,8 @@ function getInFlightMessage(chatId) {
|
|
|
9166
9640
|
function killProcessGroup(proc, signal = "SIGTERM") {
|
|
9167
9641
|
try {
|
|
9168
9642
|
if (proc.pid) process.kill(-proc.pid, signal);
|
|
9169
|
-
} catch {
|
|
9643
|
+
} catch (err) {
|
|
9644
|
+
warn(`[agent] Group kill (-${proc.pid}) failed: ${err} \u2014 falling back to direct kill`);
|
|
9170
9645
|
try {
|
|
9171
9646
|
proc.kill(signal);
|
|
9172
9647
|
} catch {
|
|
@@ -9179,7 +9654,7 @@ function withChatLock(chatId, fn) {
|
|
|
9179
9654
|
if (isBlocked) {
|
|
9180
9655
|
warn(`[agent] Chat ${chatId} is busy \u2014 queuing request (cross-channel head-of-line blocking)`);
|
|
9181
9656
|
}
|
|
9182
|
-
const next = prev.then(fn, fn);
|
|
9657
|
+
const next = prev.then(fn, () => fn());
|
|
9183
9658
|
chatLocks.set(chatId, next.then(() => {
|
|
9184
9659
|
chatLocks.delete(chatId);
|
|
9185
9660
|
}, () => {
|
|
@@ -9250,6 +9725,20 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9250
9725
|
proc.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
|
|
9251
9726
|
const rl2 = createInterface4({ input: proc.stdout });
|
|
9252
9727
|
let firstLine = true;
|
|
9728
|
+
const frTimeoutMs = opts?.firstResponseTimeoutMs ?? FIRST_RESPONSE_TIMEOUT_MS;
|
|
9729
|
+
let firstResponseTimer;
|
|
9730
|
+
let gotModelContent = false;
|
|
9731
|
+
if (frTimeoutMs > 0) {
|
|
9732
|
+
firstResponseTimer = setTimeout(() => {
|
|
9733
|
+
if (!gotModelContent) {
|
|
9734
|
+
warn(`[agent] First-response timeout after ${frTimeoutMs}ms for ${adapter.id} \u2014 no model content received (init may have arrived), killing`);
|
|
9735
|
+
killProcessGroup(proc, "SIGTERM");
|
|
9736
|
+
timedOut = true;
|
|
9737
|
+
cancelState.__firstResponseTimeout = true;
|
|
9738
|
+
sigkillTimer = setTimeout(() => killProcessGroup(proc, "SIGKILL"), 3e3);
|
|
9739
|
+
}
|
|
9740
|
+
}, frTimeoutMs);
|
|
9741
|
+
}
|
|
9253
9742
|
rl2.on("line", (line) => {
|
|
9254
9743
|
if (!line.trim()) return;
|
|
9255
9744
|
if (firstLine) {
|
|
@@ -9274,12 +9763,26 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9274
9763
|
if (ev.sessionId) sessionId = ev.sessionId;
|
|
9275
9764
|
break;
|
|
9276
9765
|
case "text":
|
|
9766
|
+
if (!gotModelContent) {
|
|
9767
|
+
gotModelContent = true;
|
|
9768
|
+
if (firstResponseTimer) {
|
|
9769
|
+
clearTimeout(firstResponseTimer);
|
|
9770
|
+
firstResponseTimer = void 0;
|
|
9771
|
+
}
|
|
9772
|
+
}
|
|
9277
9773
|
if (ev.text) {
|
|
9278
|
-
accumulatedText
|
|
9774
|
+
accumulatedText = appendTextChunk(accumulatedText, ev.text);
|
|
9279
9775
|
if (opts?.onStream) opts.onStream(ev.text);
|
|
9280
9776
|
}
|
|
9281
9777
|
break;
|
|
9282
9778
|
case "tool_start":
|
|
9779
|
+
if (!gotModelContent) {
|
|
9780
|
+
gotModelContent = true;
|
|
9781
|
+
if (firstResponseTimer) {
|
|
9782
|
+
clearTimeout(firstResponseTimer);
|
|
9783
|
+
firstResponseTimer = void 0;
|
|
9784
|
+
}
|
|
9785
|
+
}
|
|
9283
9786
|
sawToolEvents = true;
|
|
9284
9787
|
if (opts?.onToolAction && ev.toolName) {
|
|
9285
9788
|
const toolInput = ev.toolInput ?? {};
|
|
@@ -9329,6 +9832,13 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9329
9832
|
}
|
|
9330
9833
|
break;
|
|
9331
9834
|
case "result":
|
|
9835
|
+
if (!gotModelContent) {
|
|
9836
|
+
gotModelContent = true;
|
|
9837
|
+
if (firstResponseTimer) {
|
|
9838
|
+
clearTimeout(firstResponseTimer);
|
|
9839
|
+
firstResponseTimer = void 0;
|
|
9840
|
+
}
|
|
9841
|
+
}
|
|
9332
9842
|
sawResultEvent = true;
|
|
9333
9843
|
log(`[agent] Result received at ${elapsed()}`);
|
|
9334
9844
|
resultText = ev.resultText || accumulatedText;
|
|
@@ -9351,6 +9861,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9351
9861
|
});
|
|
9352
9862
|
proc.on("error", (err) => {
|
|
9353
9863
|
clearTimeout(spawnTimeout);
|
|
9864
|
+
if (firstResponseTimer) clearTimeout(firstResponseTimer);
|
|
9354
9865
|
if (sigkillTimer) clearTimeout(sigkillTimer);
|
|
9355
9866
|
rl2.close();
|
|
9356
9867
|
cancelState.process = void 0;
|
|
@@ -9358,6 +9869,10 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9358
9869
|
});
|
|
9359
9870
|
proc.on("close", (code, signal) => {
|
|
9360
9871
|
clearTimeout(spawnTimeout);
|
|
9872
|
+
if (firstResponseTimer) {
|
|
9873
|
+
clearTimeout(firstResponseTimer);
|
|
9874
|
+
firstResponseTimer = void 0;
|
|
9875
|
+
}
|
|
9361
9876
|
if (sigkillTimer) {
|
|
9362
9877
|
clearTimeout(sigkillTimer);
|
|
9363
9878
|
sigkillTimer = void 0;
|
|
@@ -9372,6 +9887,12 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
9372
9887
|
if (stderr) warn(`[agent] stderr: ${stderr.slice(0, 500)}`);
|
|
9373
9888
|
}
|
|
9374
9889
|
if (timedOut) {
|
|
9890
|
+
if (cancelState.__firstResponseTimeout) {
|
|
9891
|
+
delete cancelState.__firstResponseTimeout;
|
|
9892
|
+
const stderr = stderrChunks.length > 0 ? Buffer.concat(stderrChunks).toString().trim() : "";
|
|
9893
|
+
reject(new Error(`${FIRST_RESPONSE_TIMEOUT_ERROR}: No response from ${adapter.id} within ${frTimeoutMs / 1e3}s${stderr ? ` \u2014 ${stderr.slice(-300)}` : ""}`));
|
|
9894
|
+
return;
|
|
9895
|
+
}
|
|
9375
9896
|
let msg = `Spawn timeout after ${effectiveTimeout / 1e3}s`;
|
|
9376
9897
|
if (pendingTools.size > 0) {
|
|
9377
9898
|
const tools2 = Array.from(pendingTools.values()).map((t) => typeof t === "string" ? t : t.name).join(", ");
|
|
@@ -9393,11 +9914,12 @@ Partial output: ${accumulatedText.slice(-500)}`;
|
|
|
9393
9914
|
reject(new Error(`CLI exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}`));
|
|
9394
9915
|
return;
|
|
9395
9916
|
}
|
|
9396
|
-
|
|
9917
|
+
const cleanedResult = stripThinkingContent(resultText || accumulatedText);
|
|
9918
|
+
resolve({ resultText: cleanedResult, sessionId, input, output: output2, cacheRead, contextSize, sawToolEvents, sawResultEvent });
|
|
9397
9919
|
});
|
|
9398
9920
|
});
|
|
9399
9921
|
}
|
|
9400
|
-
async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, opts, onSlotRotation, parentChatId) {
|
|
9922
|
+
async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, opts, onSlotRotation, parentChatId, onModelDowngrade) {
|
|
9401
9923
|
const geminiAdapter = adapter;
|
|
9402
9924
|
const slots = getEligibleGeminiSlots(rotationMode);
|
|
9403
9925
|
if (slots.length === 0) {
|
|
@@ -9407,8 +9929,9 @@ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSe
|
|
|
9407
9929
|
const { env } = geminiAdapter.getEnvForSlot(parentChatId, void 0, rotationMode);
|
|
9408
9930
|
return await spawnQuery(adapter, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env });
|
|
9409
9931
|
}
|
|
9410
|
-
const maxAttempts =
|
|
9932
|
+
const maxAttempts = slots.length;
|
|
9411
9933
|
let lastError;
|
|
9934
|
+
let unknownRetried = false;
|
|
9412
9935
|
for (let i = 0; i < maxAttempts; i++) {
|
|
9413
9936
|
const { env, slot } = geminiAdapter.getEnvForSlot(chatId, void 0, rotationMode);
|
|
9414
9937
|
if (!slot) break;
|
|
@@ -9418,18 +9941,54 @@ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSe
|
|
|
9418
9941
|
const effectiveConfig = i === 0 ? configWithSession : baseConfig;
|
|
9419
9942
|
try {
|
|
9420
9943
|
const result = await spawnQuery(adapter, effectiveConfig, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env });
|
|
9421
|
-
const
|
|
9422
|
-
if (
|
|
9423
|
-
throw new Error(
|
|
9944
|
+
const checkText = (result.resultText || "").slice(0, 500);
|
|
9945
|
+
if (checkText && /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(checkText)) {
|
|
9946
|
+
throw new Error(checkText);
|
|
9424
9947
|
}
|
|
9425
9948
|
markSlotSuccess(slot.id);
|
|
9426
9949
|
return result;
|
|
9427
9950
|
} catch (err) {
|
|
9428
9951
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
9952
|
+
const isTimeout = errMsg.startsWith(FIRST_RESPONSE_TIMEOUT_ERROR);
|
|
9953
|
+
const isExhausted = /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(errMsg);
|
|
9954
|
+
if ((isTimeout || isExhausted) && GEMINI_DOWNGRADE_MODELS.has(model2)) {
|
|
9955
|
+
const reason = isTimeout ? "No response within timeout" : "Resource exhausted";
|
|
9956
|
+
warn(`[agent:gemini-rotation] ${reason} on ${model2} (${slotLabel}) \u2014 downgrading to ${GEMINI_FALLBACK_MODEL}`);
|
|
9957
|
+
onModelDowngrade?.(model2, GEMINI_FALLBACK_MODEL, `${reason} \u2014 model likely overloaded`);
|
|
9958
|
+
const fallbackConfig = adapter.buildSpawnConfig({
|
|
9959
|
+
prompt: effectiveConfig.args[effectiveConfig.args.indexOf("-p") + 1],
|
|
9960
|
+
model: GEMINI_FALLBACK_MODEL,
|
|
9961
|
+
permMode: "yolo",
|
|
9962
|
+
allowedTools: [],
|
|
9963
|
+
cwd: effectiveConfig.cwd,
|
|
9964
|
+
thinkingLevel,
|
|
9965
|
+
maxTurns
|
|
9966
|
+
});
|
|
9967
|
+
for (const arg of effectiveConfig.args) {
|
|
9968
|
+
if (arg.startsWith("--mcp-config") || arg.startsWith("--resume") || arg.startsWith("--allowed-mcp-server-names")) {
|
|
9969
|
+
const idx = effectiveConfig.args.indexOf(arg);
|
|
9970
|
+
if (idx >= 0 && idx + 1 < effectiveConfig.args.length && !effectiveConfig.args[idx].includes("=")) {
|
|
9971
|
+
fallbackConfig.args.push(arg, effectiveConfig.args[idx + 1]);
|
|
9972
|
+
} else {
|
|
9973
|
+
fallbackConfig.args.push(arg);
|
|
9974
|
+
}
|
|
9975
|
+
}
|
|
9976
|
+
}
|
|
9977
|
+
const fallbackResult = await spawnQuery(adapter, fallbackConfig, GEMINI_FALLBACK_MODEL, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env, firstResponseTimeoutMs: 0 });
|
|
9978
|
+
fallbackResult.resolvedModel = GEMINI_FALLBACK_MODEL;
|
|
9979
|
+
return fallbackResult;
|
|
9980
|
+
}
|
|
9429
9981
|
const quotaClass = classifyGeminiQuota(errMsg);
|
|
9430
9982
|
if (quotaClass === "rate_limited") {
|
|
9431
9983
|
throw err;
|
|
9432
9984
|
}
|
|
9985
|
+
if (quotaClass === "unknown" && !unknownRetried) {
|
|
9986
|
+
unknownRetried = true;
|
|
9987
|
+
warn(`[agent:gemini-rotation] Unknown error on ${slotLabel}, retrying once: ${errMsg.slice(0, 200)}`);
|
|
9988
|
+
lastError = err instanceof Error ? err : new Error(errMsg);
|
|
9989
|
+
continue;
|
|
9990
|
+
}
|
|
9991
|
+
unknownRetried = false;
|
|
9433
9992
|
const effectiveClass = quotaClass === "unknown" ? "daily_quota" : quotaClass;
|
|
9434
9993
|
warn(`[agent:gemini-rotation] Slot ${slotLabel} exhausted (${effectiveClass}): ${errMsg.slice(0, 200)}`);
|
|
9435
9994
|
markSlotExhausted(slot.id, effectiveClass);
|
|
@@ -9467,7 +10026,7 @@ function askAgent(chatId, userMessage, opts) {
|
|
|
9467
10026
|
return withChatLock(chatId, () => askAgentImpl(chatId, userMessage, opts));
|
|
9468
10027
|
}
|
|
9469
10028
|
async function askAgentImpl(chatId, userMessage, opts) {
|
|
9470
|
-
const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs, maxTurns, onSlotRotation, agentMode: optsAgentMode, onSubagentActivity, settingsSourceChatId } = opts ?? {};
|
|
10029
|
+
const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs, maxTurns, onSlotRotation, onModelDowngrade, agentMode: optsAgentMode, onSubagentActivity, settingsSourceChatId } = opts ?? {};
|
|
9471
10030
|
const settingsChat = settingsSourceChatId ?? chatId;
|
|
9472
10031
|
const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(settingsChat);
|
|
9473
10032
|
const mode = permMode ?? getMode(settingsChat);
|
|
@@ -9519,9 +10078,37 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
9519
10078
|
try {
|
|
9520
10079
|
if (useGeminiRotation) {
|
|
9521
10080
|
const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
|
|
9522
|
-
|
|
10081
|
+
const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
|
|
10082
|
+
result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
|
|
9523
10083
|
} else {
|
|
9524
|
-
|
|
10084
|
+
try {
|
|
10085
|
+
result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
|
|
10086
|
+
} catch (err) {
|
|
10087
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
10088
|
+
const isTimeout = errMsg.startsWith(FIRST_RESPONSE_TIMEOUT_ERROR);
|
|
10089
|
+
const isExhausted = /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(errMsg);
|
|
10090
|
+
if (adapter.id === "gemini" && (isTimeout || isExhausted) && GEMINI_DOWNGRADE_MODELS.has(resolvedModel)) {
|
|
10091
|
+
const reason = isTimeout ? "No response within timeout" : "Resource exhausted";
|
|
10092
|
+
warn(`[agent] ${reason} on ${resolvedModel} (no rotation) \u2014 downgrading to ${GEMINI_FALLBACK_MODEL}`);
|
|
10093
|
+
onModelDowngrade?.(chatId, resolvedModel, GEMINI_FALLBACK_MODEL, `${reason} \u2014 model likely overloaded`);
|
|
10094
|
+
const fallbackConfig = adapter.buildSpawnConfig({
|
|
10095
|
+
prompt: configWithSession.args[configWithSession.args.indexOf("-p") + 1],
|
|
10096
|
+
model: GEMINI_FALLBACK_MODEL,
|
|
10097
|
+
permMode: mode,
|
|
10098
|
+
allowedTools,
|
|
10099
|
+
cwd: resolvedCwd,
|
|
10100
|
+
thinkingLevel,
|
|
10101
|
+
maxTurns
|
|
10102
|
+
});
|
|
10103
|
+
if (mcpConfigPath) {
|
|
10104
|
+
fallbackConfig.args = injectMcpConfig(adapter.id, fallbackConfig.args, mcpConfigPath);
|
|
10105
|
+
}
|
|
10106
|
+
result = await spawnQuery(adapter, fallbackConfig, GEMINI_FALLBACK_MODEL, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...spawnOpts, firstResponseTimeoutMs: 0 });
|
|
10107
|
+
result.resolvedModel = GEMINI_FALLBACK_MODEL;
|
|
10108
|
+
} else {
|
|
10109
|
+
throw err;
|
|
10110
|
+
}
|
|
10111
|
+
}
|
|
9525
10112
|
}
|
|
9526
10113
|
const wasEmptyResponse = !result.resultText && !result.sawToolEvents && !result.sawResultEvent;
|
|
9527
10114
|
if (wasEmptyResponse && !cancelState.cancelled && existingSessionId) {
|
|
@@ -9530,7 +10117,8 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
9530
10117
|
clearSession(chatId);
|
|
9531
10118
|
if (useGeminiRotation) {
|
|
9532
10119
|
const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
|
|
9533
|
-
|
|
10120
|
+
const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
|
|
10121
|
+
result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
|
|
9534
10122
|
} else {
|
|
9535
10123
|
result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
|
|
9536
10124
|
}
|
|
@@ -9587,7 +10175,8 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
9587
10175
|
return {
|
|
9588
10176
|
text: result.resultText || `(No response from ${adapter.displayName})`,
|
|
9589
10177
|
sessionId: result.sessionId,
|
|
9590
|
-
usage: { input: result.input, output: result.output, cacheRead: result.cacheRead, contextSize: result.contextSize }
|
|
10178
|
+
usage: { input: result.input, output: result.output, cacheRead: result.cacheRead, contextSize: result.contextSize },
|
|
10179
|
+
resolvedModel: result.resolvedModel
|
|
9591
10180
|
};
|
|
9592
10181
|
}
|
|
9593
10182
|
function getMcpConfigPath(chatId) {
|
|
@@ -9601,7 +10190,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
|
|
|
9601
10190
|
if (!flag) return args;
|
|
9602
10191
|
return [...args, ...flag, mcpConfigPath];
|
|
9603
10192
|
}
|
|
9604
|
-
var activeChats, chatLocks, SPAWN_TIMEOUT_MS, MCP_CONFIG_FLAG;
|
|
10193
|
+
var activeChats, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_ERROR, GEMINI_FALLBACK_MODEL, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
|
|
9605
10194
|
var init_agent = __esm({
|
|
9606
10195
|
"src/agent.ts"() {
|
|
9607
10196
|
"use strict";
|
|
@@ -9611,6 +10200,8 @@ var init_agent = __esm({
|
|
|
9611
10200
|
init_paths();
|
|
9612
10201
|
init_loader();
|
|
9613
10202
|
init_log();
|
|
10203
|
+
init_strip_thinking();
|
|
10204
|
+
init_text_utils();
|
|
9614
10205
|
init_session_log();
|
|
9615
10206
|
init_summarize();
|
|
9616
10207
|
init_quota();
|
|
@@ -9622,6 +10213,10 @@ var init_agent = __esm({
|
|
|
9622
10213
|
activeChats = /* @__PURE__ */ new Map();
|
|
9623
10214
|
chatLocks = /* @__PURE__ */ new Map();
|
|
9624
10215
|
SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
10216
|
+
FIRST_RESPONSE_TIMEOUT_MS = parseInt(process.env.GEMINI_FIRST_RESPONSE_TIMEOUT_MS ?? "30000", 10);
|
|
10217
|
+
FIRST_RESPONSE_TIMEOUT_ERROR = "FIRST_RESPONSE_TIMEOUT";
|
|
10218
|
+
GEMINI_FALLBACK_MODEL = "gemini-2.5-pro";
|
|
10219
|
+
GEMINI_DOWNGRADE_MODELS = /* @__PURE__ */ new Set(["gemini-3.1-pro-preview"]);
|
|
9625
10220
|
MCP_CONFIG_FLAG = {
|
|
9626
10221
|
claude: ["--mcp-config"]
|
|
9627
10222
|
};
|
|
@@ -9646,10 +10241,19 @@ function getChannelRegistry() {
|
|
|
9646
10241
|
return registry;
|
|
9647
10242
|
}
|
|
9648
10243
|
function isPathAllowed(filePath) {
|
|
10244
|
+
const normalized = filePath.replace(/\.\.\/|\.\.$/g, "");
|
|
10245
|
+
if (normalized !== filePath) return false;
|
|
9649
10246
|
for (const pattern of BLOCKED_PATH_PATTERNS) {
|
|
9650
|
-
if (pattern.test(
|
|
10247
|
+
if (pattern.test(normalized)) return false;
|
|
9651
10248
|
}
|
|
9652
|
-
|
|
10249
|
+
for (const prefix of ALLOWED_PATH_PREFIXES) {
|
|
10250
|
+
if (normalized.startsWith(prefix)) return true;
|
|
10251
|
+
}
|
|
10252
|
+
const ccClawWorkspace = `${process.env.HOME ?? "/root"}/.cc-claw/workspace/`;
|
|
10253
|
+
if (normalized.startsWith(ccClawWorkspace)) return true;
|
|
10254
|
+
const homePath = process.env.HOME ?? "/root";
|
|
10255
|
+
if (normalized.startsWith(homePath) && !normalized.includes("/.")) return true;
|
|
10256
|
+
return false;
|
|
9653
10257
|
}
|
|
9654
10258
|
async function processFileSends(chatId, channel, text) {
|
|
9655
10259
|
const fileSendPattern = /\[SEND_FILE:(.+?)\]/g;
|
|
@@ -9698,9 +10302,11 @@ ${responseText.slice(0, 500)}`);
|
|
|
9698
10302
|
try {
|
|
9699
10303
|
const text = responseText.replace(/\s*\[REACT:[^\]]*\]\s*/g, " ").trim();
|
|
9700
10304
|
if (job.deliveryMode === "webhook") {
|
|
9701
|
-
await deliverWebhook(job, text);
|
|
9702
|
-
|
|
9703
|
-
|
|
10305
|
+
const webhookOk = await deliverWebhook(job, text);
|
|
10306
|
+
if (webhookOk) {
|
|
10307
|
+
appendToLog(job.chatId, `[cron:#${job.id}] ${job.description}`, text, job.backend ?? "claude", job.model, null);
|
|
10308
|
+
}
|
|
10309
|
+
return webhookOk;
|
|
9704
10310
|
}
|
|
9705
10311
|
const channelName = job.channel ?? "telegram";
|
|
9706
10312
|
const channel = registry?.get(channelName);
|
|
@@ -9728,7 +10334,7 @@ async function deliverWebhook(job, responseText) {
|
|
|
9728
10334
|
const url = job.target;
|
|
9729
10335
|
if (!url) {
|
|
9730
10336
|
error(`[delivery] Job #${job.id}: webhook mode but no target URL`);
|
|
9731
|
-
return;
|
|
10337
|
+
return false;
|
|
9732
10338
|
}
|
|
9733
10339
|
try {
|
|
9734
10340
|
const resp = await fetch(url, {
|
|
@@ -9744,11 +10350,13 @@ async function deliverWebhook(job, responseText) {
|
|
|
9744
10350
|
});
|
|
9745
10351
|
if (!resp.ok) {
|
|
9746
10352
|
error(`[delivery] Webhook POST to ${url} failed: ${resp.status} ${resp.statusText}`);
|
|
9747
|
-
|
|
9748
|
-
log(`[delivery] Webhook POST to ${url}: ${resp.status}`);
|
|
10353
|
+
return false;
|
|
9749
10354
|
}
|
|
10355
|
+
log(`[delivery] Webhook POST to ${url}: ${resp.status}`);
|
|
10356
|
+
return true;
|
|
9750
10357
|
} catch (err) {
|
|
9751
10358
|
error(`[delivery] Webhook POST to ${url} error: ${errorMessage(err)}`);
|
|
10359
|
+
return false;
|
|
9752
10360
|
}
|
|
9753
10361
|
}
|
|
9754
10362
|
async function notifyJobFailure(job, errorMessage2, retryInfo) {
|
|
@@ -9776,25 +10384,30 @@ async function notifyJobAutoPaused(job) {
|
|
|
9776
10384
|
} catch {
|
|
9777
10385
|
}
|
|
9778
10386
|
}
|
|
9779
|
-
var registry, BLOCKED_PATH_PATTERNS;
|
|
10387
|
+
var registry, ALLOWED_PATH_PREFIXES, BLOCKED_PATH_PATTERNS;
|
|
9780
10388
|
var init_delivery = __esm({
|
|
9781
10389
|
"src/scheduler/delivery.ts"() {
|
|
9782
10390
|
"use strict";
|
|
9783
10391
|
init_log();
|
|
9784
10392
|
init_session_log();
|
|
9785
10393
|
registry = null;
|
|
10394
|
+
ALLOWED_PATH_PREFIXES = [
|
|
10395
|
+
"/tmp/",
|
|
10396
|
+
"/var/tmp/"
|
|
10397
|
+
];
|
|
9786
10398
|
BLOCKED_PATH_PATTERNS = [
|
|
9787
10399
|
/\/\.ssh\//,
|
|
9788
10400
|
/\/\.gnupg\//,
|
|
9789
10401
|
/\/\.aws\//,
|
|
9790
10402
|
/\/\.env$/,
|
|
9791
10403
|
/\/\.env\./,
|
|
9792
|
-
/\/\.cc-claw\/\.env$/,
|
|
9793
10404
|
/\/credentials\.json$/,
|
|
9794
10405
|
/\/\.gitconfig$/,
|
|
9795
10406
|
/\/\.netrc$/,
|
|
9796
10407
|
/\/etc\/shadow$/,
|
|
9797
|
-
/\/etc\/passwd
|
|
10408
|
+
/\/etc\/passwd$/,
|
|
10409
|
+
/\/secret/i,
|
|
10410
|
+
/\/token/i
|
|
9798
10411
|
];
|
|
9799
10412
|
}
|
|
9800
10413
|
});
|
|
@@ -9873,12 +10486,14 @@ var init_retry = __esm({
|
|
|
9873
10486
|
});
|
|
9874
10487
|
|
|
9875
10488
|
// src/scheduler/health.ts
|
|
10489
|
+
import * as os from "os";
|
|
9876
10490
|
function startHealthMonitor2() {
|
|
9877
10491
|
lastHeartbeat = /* @__PURE__ */ new Date();
|
|
9878
10492
|
heartbeatTimer = setInterval(() => {
|
|
9879
10493
|
lastHeartbeat = /* @__PURE__ */ new Date();
|
|
9880
10494
|
log(`[health] Scheduler heartbeat at ${lastHeartbeat.toISOString()}`);
|
|
9881
10495
|
}, HEARTBEAT_INTERVAL_MS);
|
|
10496
|
+
heartbeatTimer.unref();
|
|
9882
10497
|
}
|
|
9883
10498
|
function stopHealthMonitor2() {
|
|
9884
10499
|
if (heartbeatTimer) {
|
|
@@ -9900,7 +10515,10 @@ function getHealthReport() {
|
|
|
9900
10515
|
activeJobs: activeJobs.length,
|
|
9901
10516
|
pausedJobs: pausedJobs.length,
|
|
9902
10517
|
failingJobs,
|
|
9903
|
-
totalJobs: allJobs.length
|
|
10518
|
+
totalJobs: allJobs.length,
|
|
10519
|
+
// Audit O39: Include system metrics for operator visibility
|
|
10520
|
+
memoryMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
|
10521
|
+
loadAvg: os.loadavg()[0]
|
|
9904
10522
|
};
|
|
9905
10523
|
}
|
|
9906
10524
|
function formatHealthReport(report) {
|
|
@@ -9926,7 +10544,11 @@ function computeStaggerMs(jobId, cronExpr) {
|
|
|
9926
10544
|
if (parts[0] !== "0") return 0;
|
|
9927
10545
|
if (!/[*/]/.test(parts[1])) return 0;
|
|
9928
10546
|
const maxStagger = 12e4;
|
|
9929
|
-
|
|
10547
|
+
let h = jobId * 2654435761;
|
|
10548
|
+
h = (h >>> 16 ^ h) * 73244475;
|
|
10549
|
+
h = (h >>> 16 ^ h) * 73244475;
|
|
10550
|
+
h = h >>> 16 ^ h;
|
|
10551
|
+
return Math.abs(h) % maxStagger;
|
|
9930
10552
|
}
|
|
9931
10553
|
var lastHeartbeat, heartbeatTimer, HEARTBEAT_INTERVAL_MS;
|
|
9932
10554
|
var init_health2 = __esm({
|
|
@@ -10050,6 +10672,7 @@ function buildProposalKeyboard(insightId, category) {
|
|
|
10050
10672
|
return [
|
|
10051
10673
|
[
|
|
10052
10674
|
{ label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
|
|
10675
|
+
{ label: "Preview", data: `evolve:preview:${insightId}` },
|
|
10053
10676
|
{ label: "Discuss", data: `evolve:discuss:${insightId}` }
|
|
10054
10677
|
],
|
|
10055
10678
|
[
|
|
@@ -10239,7 +10862,8 @@ function mergeAndDeduplicate(raw) {
|
|
|
10239
10862
|
});
|
|
10240
10863
|
}
|
|
10241
10864
|
function parseFrontmatter2(content, fallbackName) {
|
|
10242
|
-
const
|
|
10865
|
+
const cleaned = content.replace(/^\uFEFF/, "");
|
|
10866
|
+
const fmMatch = cleaned.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
10243
10867
|
if (!fmMatch) {
|
|
10244
10868
|
return { name: fallbackName, description: "" };
|
|
10245
10869
|
}
|
|
@@ -10808,9 +11432,19 @@ var init_format_time = __esm({
|
|
|
10808
11432
|
});
|
|
10809
11433
|
|
|
10810
11434
|
// src/intent/classify.ts
|
|
11435
|
+
var classify_exports = {};
|
|
11436
|
+
__export(classify_exports, {
|
|
11437
|
+
classifyIntent: () => classifyIntent,
|
|
11438
|
+
getIntentStats: () => getIntentStats,
|
|
11439
|
+
resetIntentStats: () => resetIntentStats
|
|
11440
|
+
});
|
|
10811
11441
|
function getIntentStats() {
|
|
10812
11442
|
return { ...intentCounts };
|
|
10813
11443
|
}
|
|
11444
|
+
function resetIntentStats() {
|
|
11445
|
+
intentCounts.chat = 0;
|
|
11446
|
+
intentCounts.agentic = 0;
|
|
11447
|
+
}
|
|
10814
11448
|
function classifyIntent(text, chatId) {
|
|
10815
11449
|
const trimmed = text.trim();
|
|
10816
11450
|
if (trimmed.startsWith(">>")) return "agentic";
|
|
@@ -11013,9 +11647,20 @@ var init_image_gen = __esm({
|
|
|
11013
11647
|
|
|
11014
11648
|
// src/voice/stt.ts
|
|
11015
11649
|
import crypto from "crypto";
|
|
11016
|
-
import { execFile as execFile2 } from "child_process";
|
|
11650
|
+
import { execFile as execFile2, execFileSync } from "child_process";
|
|
11017
11651
|
import { readFile as readFile4, unlink } from "fs/promises";
|
|
11018
11652
|
import { promisify as promisify2 } from "util";
|
|
11653
|
+
function ensureFfmpeg() {
|
|
11654
|
+
if (ffmpegAvailable === true) return;
|
|
11655
|
+
if (ffmpegAvailable === false) throw new Error("ffmpeg is required for voice replies. Install it: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)");
|
|
11656
|
+
try {
|
|
11657
|
+
execFileSync("ffmpeg", ["-version"], { stdio: "ignore" });
|
|
11658
|
+
ffmpegAvailable = true;
|
|
11659
|
+
} catch {
|
|
11660
|
+
ffmpegAvailable = false;
|
|
11661
|
+
throw new Error("ffmpeg is required for voice replies. Install it: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)");
|
|
11662
|
+
}
|
|
11663
|
+
}
|
|
11019
11664
|
function isVoiceEnabled(chatId) {
|
|
11020
11665
|
const db3 = getDb();
|
|
11021
11666
|
const row = db3.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
|
|
@@ -11149,6 +11794,7 @@ async function grokTts(text, apiKey, voiceId) {
|
|
|
11149
11794
|
return await mp3ToOgg(mp3Buffer);
|
|
11150
11795
|
}
|
|
11151
11796
|
async function mp3ToOgg(mp3Buffer) {
|
|
11797
|
+
ensureFfmpeg();
|
|
11152
11798
|
const id = crypto.randomUUID();
|
|
11153
11799
|
const tmpMp3 = `/tmp/cc-claw-tts-${id}.mp3`;
|
|
11154
11800
|
const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
|
|
@@ -11165,6 +11811,7 @@ async function mp3ToOgg(mp3Buffer) {
|
|
|
11165
11811
|
return oggBuffer;
|
|
11166
11812
|
}
|
|
11167
11813
|
async function macOsTts(text, voice2 = "Samantha") {
|
|
11814
|
+
ensureFfmpeg();
|
|
11168
11815
|
const id = crypto.randomUUID();
|
|
11169
11816
|
const tmpAiff = `/tmp/cc-claw-tts-${id}.aiff`;
|
|
11170
11817
|
const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
|
|
@@ -11179,13 +11826,14 @@ async function macOsTts(text, voice2 = "Samantha") {
|
|
|
11179
11826
|
});
|
|
11180
11827
|
return oggBuffer;
|
|
11181
11828
|
}
|
|
11182
|
-
var execFileAsync2, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES;
|
|
11829
|
+
var execFileAsync2, ffmpegAvailable, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES;
|
|
11183
11830
|
var init_stt = __esm({
|
|
11184
11831
|
"src/voice/stt.ts"() {
|
|
11185
11832
|
"use strict";
|
|
11186
11833
|
init_log();
|
|
11187
11834
|
init_store5();
|
|
11188
11835
|
execFileAsync2 = promisify2(execFile2);
|
|
11836
|
+
ffmpegAvailable = null;
|
|
11189
11837
|
ELEVENLABS_VOICES = {
|
|
11190
11838
|
"21m00Tcm4TlvDq8ikWAM": { name: "Rachel", gender: "F" },
|
|
11191
11839
|
"EXAVITQu4vr4xnSDxMaL": { name: "Sarah", gender: "F" },
|
|
@@ -12051,11 +12699,21 @@ var init_guard = __esm({
|
|
|
12051
12699
|
/\bdd\b.*\bof=/,
|
|
12052
12700
|
/\b(shutdown|reboot|halt|poweroff)\b/,
|
|
12053
12701
|
/>\s*\/dev\/sd/,
|
|
12054
|
-
/\bchmod\
|
|
12702
|
+
/\bchmod\b.*\b777\b/,
|
|
12703
|
+
// catch chmod 777 regardless of flag order
|
|
12055
12704
|
/\bchown\s+-R\b/,
|
|
12056
12705
|
/\bkillall\b/,
|
|
12057
12706
|
/\blaunchctl\s+(unload|remove)\b/,
|
|
12058
|
-
/:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s
|
|
12707
|
+
/:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*:/,
|
|
12708
|
+
// Audit C02: indirect destructive commands
|
|
12709
|
+
/\bfind\b.*\b-delete\b/,
|
|
12710
|
+
// find / -delete
|
|
12711
|
+
/\b(curl|wget)\b.*\|\s*(ba)?sh\b/,
|
|
12712
|
+
// remote code execution
|
|
12713
|
+
/\bbase64\b.*\|\s*(ba)?sh\b/,
|
|
12714
|
+
// encoded payload execution
|
|
12715
|
+
/\btruncate\s+-s\s*0\b/
|
|
12716
|
+
// file wipe via truncate
|
|
12059
12717
|
];
|
|
12060
12718
|
pendingCommands = /* @__PURE__ */ new Map();
|
|
12061
12719
|
PENDING_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -12066,8 +12724,9 @@ var init_guard = __esm({
|
|
|
12066
12724
|
import { execFile as execFile3 } from "child_process";
|
|
12067
12725
|
function executeShell(command, cwd, timeoutMs = SHELL_TIMEOUT_MS) {
|
|
12068
12726
|
return new Promise((resolve) => {
|
|
12727
|
+
const shell = process.env.SHELL || "/bin/sh";
|
|
12069
12728
|
execFile3(
|
|
12070
|
-
|
|
12729
|
+
shell,
|
|
12071
12730
|
["-c", command],
|
|
12072
12731
|
{ cwd, timeout: timeoutMs, maxBuffer: 5 * 1024 * 1024 },
|
|
12073
12732
|
(error3, stdout, stderr) => {
|
|
@@ -12090,9 +12749,12 @@ function executeShell(command, cwd, timeoutMs = SHELL_TIMEOUT_MS) {
|
|
|
12090
12749
|
);
|
|
12091
12750
|
});
|
|
12092
12751
|
}
|
|
12752
|
+
function escapeHtml(s) {
|
|
12753
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
12754
|
+
}
|
|
12093
12755
|
function formatCodeBlock(command, output2, exitCode) {
|
|
12094
|
-
return `<pre>$ ${command}
|
|
12095
|
-
${output2}
|
|
12756
|
+
return `<pre>$ ${escapeHtml(command)}
|
|
12757
|
+
${escapeHtml(output2)}
|
|
12096
12758
|
[exit ${exitCode}]</pre>`;
|
|
12097
12759
|
}
|
|
12098
12760
|
function formatRaw(output2) {
|
|
@@ -12253,7 +12915,6 @@ __export(router_exports, {
|
|
|
12253
12915
|
stopAllSideQuests: () => stopAllSideQuests
|
|
12254
12916
|
});
|
|
12255
12917
|
import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2, mkdir as mkdir2, readdir as readdir3, stat } from "fs/promises";
|
|
12256
|
-
import { existsSync as existsSync18 } from "fs";
|
|
12257
12918
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12258
12919
|
import { resolve as resolvePath, join as join18 } from "path";
|
|
12259
12920
|
import { homedir as homedir6 } from "os";
|
|
@@ -12700,6 +13361,7 @@ ${cmds.map((c) => ` ${c.cmd} \u2014 ${c.desc}`).join("\n")}`
|
|
|
12700
13361
|
case "stop": {
|
|
12701
13362
|
const stopped = stopAgent(chatId);
|
|
12702
13363
|
stopAllSideQuests(chatId);
|
|
13364
|
+
cancelAllAgents(chatId);
|
|
12703
13365
|
await channel.sendText(
|
|
12704
13366
|
chatId,
|
|
12705
13367
|
stopped ? "Stopping current task..." : "Nothing is running.",
|
|
@@ -14170,13 +14832,17 @@ Message: "${testMsg}"`, { parseMode: "plain" });
|
|
|
14170
14832
|
else if (diffHours < 24) lastText = `${diffHours}h ago`;
|
|
14171
14833
|
else lastText = `${Math.floor(diffHours / 24)}d ago`;
|
|
14172
14834
|
}
|
|
14835
|
+
const { getReflectionModelConfig: getReflectionModelConfig2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
14836
|
+
const modelConfig = getReflectionModelConfig2(getDb(), chatId);
|
|
14837
|
+
const modelLabel = modelConfig.mode === "pinned" && modelConfig.backend && modelConfig.model ? `Pinned: ${modelConfig.backend}/${modelConfig.model}` : modelConfig.mode === "cheap" ? "Cheap" : "Auto";
|
|
14173
14838
|
const dashText = [
|
|
14174
14839
|
"Self-Learning & Evolution",
|
|
14175
14840
|
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
14176
14841
|
"",
|
|
14177
14842
|
`\u2705 Active`,
|
|
14178
14843
|
`Signals: ${signals} pending \xB7 Proposals: ${pending}`,
|
|
14179
|
-
`Last analysis: ${lastText}
|
|
14844
|
+
`Last analysis: ${lastText}`,
|
|
14845
|
+
`Model: ${modelLabel}`
|
|
14180
14846
|
].join("\n");
|
|
14181
14847
|
if (typeof channel.sendKeyboard === "function") {
|
|
14182
14848
|
const { buildEvolveMenuKeyboard: buildEvolveMenuKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
@@ -14187,47 +14853,6 @@ Message: "${testMsg}"`, { parseMode: "plain" });
|
|
|
14187
14853
|
}
|
|
14188
14854
|
break;
|
|
14189
14855
|
}
|
|
14190
|
-
case "reflect": {
|
|
14191
|
-
const lastReflect = lastReflectTime.get(chatId);
|
|
14192
|
-
if (lastReflect && Date.now() - lastReflect < REFLECT_COOLDOWN_MS) {
|
|
14193
|
-
const remaining = Math.ceil((REFLECT_COOLDOWN_MS - (Date.now() - lastReflect)) / 1e3);
|
|
14194
|
-
await channel.sendText(chatId, `Please wait ${remaining}s before reflecting again.`, { parseMode: "plain" });
|
|
14195
|
-
break;
|
|
14196
|
-
}
|
|
14197
|
-
const { getMessagePairCount: getMessagePairCount2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
|
|
14198
|
-
const pairCount = getMessagePairCount2(chatId);
|
|
14199
|
-
if (pairCount < 2) {
|
|
14200
|
-
await channel.sendText(chatId, "Need more conversation to analyze \u2014 at least 2 message exchanges.", { parseMode: "plain" });
|
|
14201
|
-
break;
|
|
14202
|
-
}
|
|
14203
|
-
lastReflectTime.set(chatId, Date.now());
|
|
14204
|
-
await channel.sendText(chatId, "Step 1/3: Summarizing conversation...", { parseMode: "plain" });
|
|
14205
|
-
try {
|
|
14206
|
-
const { summarizeSession: summarizeSession2 } = await Promise.resolve().then(() => (init_summarize(), summarize_exports));
|
|
14207
|
-
await summarizeSession2(chatId, false);
|
|
14208
|
-
} catch (e) {
|
|
14209
|
-
await channel.sendText(chatId, `Summarization failed: ${e}`, { parseMode: "plain" });
|
|
14210
|
-
break;
|
|
14211
|
-
}
|
|
14212
|
-
await channel.sendText(chatId, "Step 2/3: Analyzing for insights...", { parseMode: "plain" });
|
|
14213
|
-
try {
|
|
14214
|
-
const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
|
|
14215
|
-
const insights = await runAnalysis2(chatId, { force: true });
|
|
14216
|
-
if (insights.length === 0) {
|
|
14217
|
-
await channel.sendText(chatId, "Step 3/3: No actionable improvements found in this session.", { parseMode: "plain" });
|
|
14218
|
-
} else {
|
|
14219
|
-
await channel.sendText(chatId, `Step 3/3: Found ${insights.length} proposal(s). Let's review them one by one.`, { parseMode: "plain" });
|
|
14220
|
-
const { getPendingInsights: getPendingInsights2, createReviewSession: createReviewSession2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
14221
|
-
const pending = getPendingInsights2(getDb(), chatId);
|
|
14222
|
-
const insightIds = pending.slice(0, 5).map((p) => p.id);
|
|
14223
|
-
createReviewSession2(getDb(), chatId, insightIds);
|
|
14224
|
-
await sendCurrentProposal(chatId, channel);
|
|
14225
|
-
}
|
|
14226
|
-
} catch (e) {
|
|
14227
|
-
await channel.sendText(chatId, `Analysis failed: ${e}. You can review any pending proposals with /evolve.`, { parseMode: "plain" });
|
|
14228
|
-
}
|
|
14229
|
-
break;
|
|
14230
|
-
}
|
|
14231
14856
|
default:
|
|
14232
14857
|
await channel.sendText(chatId, `Unknown command: /${command}. Type /help for available commands.`, { parseMode: "plain" });
|
|
14233
14858
|
}
|
|
@@ -14236,6 +14861,10 @@ async function handleVoice(msg, channel) {
|
|
|
14236
14861
|
const { chatId, fileName } = msg;
|
|
14237
14862
|
await channel.sendText(chatId, "Transcribing voice message...", { parseMode: "plain" });
|
|
14238
14863
|
try {
|
|
14864
|
+
if (!fileName) {
|
|
14865
|
+
await channel.sendText(chatId, "Could not process voice message \u2014 file not available.", { parseMode: "plain" });
|
|
14866
|
+
return;
|
|
14867
|
+
}
|
|
14239
14868
|
const audioBuffer = await channel.downloadFile(fileName);
|
|
14240
14869
|
const transcript = await transcribeAudio(audioBuffer);
|
|
14241
14870
|
if (!transcript) {
|
|
@@ -14326,6 +14955,10 @@ Acknowledge receipt. Do NOT analyze the video unless they ask you to.`;
|
|
|
14326
14955
|
await sendResponse(chatId, channel, vidResponse, msg.messageId);
|
|
14327
14956
|
return;
|
|
14328
14957
|
}
|
|
14958
|
+
if (!fileName) {
|
|
14959
|
+
await channel.sendText(chatId, "Could not process file \u2014 file not available.", { parseMode: "plain" });
|
|
14960
|
+
return;
|
|
14961
|
+
}
|
|
14329
14962
|
const fileBuffer = await channel.downloadFile(fileName);
|
|
14330
14963
|
const originalName = fileName ?? "file";
|
|
14331
14964
|
const ext = originalName.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -14342,8 +14975,18 @@ Please read the image file and respond to their caption/question.` : `The user s
|
|
|
14342
14975
|
|
|
14343
14976
|
Please read the image file and describe what you see.`;
|
|
14344
14977
|
} else if (isTextExt(ext)) {
|
|
14345
|
-
const
|
|
14346
|
-
|
|
14978
|
+
const MAX_TEXT_FILE_BYTES = 1048576;
|
|
14979
|
+
if (fileBuffer.length > MAX_TEXT_FILE_BYTES) {
|
|
14980
|
+
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" });
|
|
14981
|
+
tempFilePath = await saveMedia(fileBuffer, "file", ext || "txt");
|
|
14982
|
+
prompt = caption ? `The user sent a large text file "${originalName}" (${fileBuffer.length} bytes), saved at: ${tempFilePath}
|
|
14983
|
+
|
|
14984
|
+
${caption}` : `The user sent a large text file "${originalName}" (${fileBuffer.length} bytes), saved at: ${tempFilePath}
|
|
14985
|
+
|
|
14986
|
+
Please read and analyze the file.`;
|
|
14987
|
+
} else {
|
|
14988
|
+
const content = fileBuffer.toString("utf-8");
|
|
14989
|
+
prompt = caption ? `Here's a file "${originalName}":
|
|
14347
14990
|
|
|
14348
14991
|
\`\`\`
|
|
14349
14992
|
${content}
|
|
@@ -14354,6 +14997,7 @@ ${caption}` : `Here's a file "${originalName}". Please analyze its contents:
|
|
|
14354
14997
|
\`\`\`
|
|
14355
14998
|
${content}
|
|
14356
14999
|
\`\`\``;
|
|
15000
|
+
}
|
|
14357
15001
|
} else {
|
|
14358
15002
|
prompt = `I've shared a file "${originalName}" (${fileBuffer.length} bytes). ${caption || "What can you tell me about it?"}`;
|
|
14359
15003
|
}
|
|
@@ -14440,6 +15084,64 @@ async function handleText(msg, channel) {
|
|
|
14440
15084
|
if (hasSqPrefix) {
|
|
14441
15085
|
text = sqCleanText;
|
|
14442
15086
|
}
|
|
15087
|
+
{
|
|
15088
|
+
const { getDiscussionMode: getDiscussionMode2, clearDiscussionMode: clearDiscussionMode2, setDiscussionMode: reenterDiscussion, getInsightById: getInsightById2, updateInsightProposal: updateInsightProposal2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
15089
|
+
const discussInsightId = getDiscussionMode2(chatId);
|
|
15090
|
+
if (discussInsightId !== null) {
|
|
15091
|
+
const insight = getInsightById2(getDb(), discussInsightId);
|
|
15092
|
+
if (!insight) {
|
|
15093
|
+
clearDiscussionMode2(chatId);
|
|
15094
|
+
await channel.sendText(chatId, "Proposal not found \u2014 discussion cancelled.", { parseMode: "plain" });
|
|
15095
|
+
return;
|
|
15096
|
+
}
|
|
15097
|
+
await channel.sendTyping?.(chatId);
|
|
15098
|
+
const discussPrompt = [
|
|
15099
|
+
`The user is discussing an evolve proposal. Here is the proposal context:`,
|
|
15100
|
+
``,
|
|
15101
|
+
`Insight: "${insight.insight}"`,
|
|
15102
|
+
`Target file: ${insight.targetFile ?? "none"}`,
|
|
15103
|
+
`Category: ${insight.category}`,
|
|
15104
|
+
`Proposed diff: ${insight.proposedDiff ?? "none"}`,
|
|
15105
|
+
`Proposed action: ${insight.proposedAction ?? "none"}`,
|
|
15106
|
+
``,
|
|
15107
|
+
`The user says: "${text}"`,
|
|
15108
|
+
``,
|
|
15109
|
+
`Respond to their request. If they ask to retarget, output "RETARGET: <new_path>" on a line by itself.`,
|
|
15110
|
+
`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.`,
|
|
15111
|
+
`Otherwise, answer their question concisely.`
|
|
15112
|
+
].join("\n");
|
|
15113
|
+
try {
|
|
15114
|
+
const discussResp = await askAgent(chatId, discussPrompt, { bootstrapTier: "chat", maxTurns: 1, timeoutMs: 6e4 });
|
|
15115
|
+
const respText = discussResp.text?.trim() ?? "";
|
|
15116
|
+
clearDiscussionMode2(chatId);
|
|
15117
|
+
const retargetMatch = respText.match(/^RETARGET:\s*(.+)$/m);
|
|
15118
|
+
const diffMatch = respText.match(/^REVISED_DIFF:\s*\n([\s\S]+)$/m);
|
|
15119
|
+
if (retargetMatch) {
|
|
15120
|
+
const newPath = retargetMatch[1].trim();
|
|
15121
|
+
updateInsightProposal2(getDb(), discussInsightId, newPath, insight.proposedDiff, insight.proposedAction);
|
|
15122
|
+
await channel.sendText(chatId, `\u2705 Retargeted to: ${newPath}`);
|
|
15123
|
+
} else if (diffMatch) {
|
|
15124
|
+
const newDiff = diffMatch[1].trim();
|
|
15125
|
+
updateInsightProposal2(getDb(), discussInsightId, insight.targetFile ?? "", newDiff, insight.proposedAction);
|
|
15126
|
+
await channel.sendText(chatId, `\u2705 Diff revised.`);
|
|
15127
|
+
} else {
|
|
15128
|
+
await channel.sendText(chatId, respText);
|
|
15129
|
+
}
|
|
15130
|
+
const updatedInsight = getInsightById2(getDb(), discussInsightId);
|
|
15131
|
+
if (updatedInsight && updatedInsight.status === "pending") {
|
|
15132
|
+
const { formatProposalCard: formatProposalCard2, buildProposalKeyboard: buildProposalKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
15133
|
+
const card = formatProposalCard2(updatedInsight);
|
|
15134
|
+
const kb = buildProposalKeyboard2(updatedInsight.id, updatedInsight.category);
|
|
15135
|
+
await channel.sendKeyboard(chatId, card, kb);
|
|
15136
|
+
}
|
|
15137
|
+
} catch (e) {
|
|
15138
|
+
await channel.sendText(chatId, `Discussion error: ${e.message}
|
|
15139
|
+
|
|
15140
|
+
You're still in discussion mode \u2014 try again or click a button to exit.`, { parseMode: "plain" });
|
|
15141
|
+
}
|
|
15142
|
+
return;
|
|
15143
|
+
}
|
|
15144
|
+
}
|
|
14443
15145
|
if (isChatBusy(chatId) && !bypassBusyCheck.delete(chatId)) {
|
|
14444
15146
|
if (typeof channel.sendKeyboard === "function") {
|
|
14445
15147
|
pendingInterrupts.set(chatId, { msg, channel });
|
|
@@ -14508,6 +15210,12 @@ async function handleText(msg, channel) {
|
|
|
14508
15210
|
const toIcon = toSlot?.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
14509
15211
|
channel.sendText(cid, `\u26A0\uFE0F Quota reached on ${fromIcon} ${from} \u2014 switched to ${toIcon} ${to}. Context saved.`, { parseMode: "plain" }).catch(() => {
|
|
14510
15212
|
});
|
|
15213
|
+
},
|
|
15214
|
+
onModelDowngrade: (cid, fromModel, toModel, reason) => {
|
|
15215
|
+
const shortFrom = formatModelShort(fromModel);
|
|
15216
|
+
const shortTo = formatModelShort(toModel);
|
|
15217
|
+
channel.sendText(cid, `\u26A0\uFE0F ${shortFrom} unresponsive \u2014 downgrading to ${shortTo}. (${reason})`, { parseMode: "plain" }).catch(() => {
|
|
15218
|
+
});
|
|
14511
15219
|
}
|
|
14512
15220
|
});
|
|
14513
15221
|
const elapsedSec = ((Date.now() - sigT0) / 1e3).toFixed(1);
|
|
@@ -14516,12 +15224,20 @@ async function handleText(msg, channel) {
|
|
|
14516
15224
|
const sigEnabled = getModelSignature(chatId);
|
|
14517
15225
|
if (sigEnabled === "on" && responseText && !responseText.startsWith("(No response")) {
|
|
14518
15226
|
const adapter = getAdapterForChat(chatId);
|
|
14519
|
-
const modelId = model2 ?? adapter.defaultModel;
|
|
15227
|
+
const modelId = response.resolvedModel ?? model2 ?? adapter.defaultModel;
|
|
14520
15228
|
const thinking2 = getThinkingLevel(chatId) || "auto";
|
|
14521
15229
|
const shortModel = formatModelShort(modelId);
|
|
15230
|
+
let slotTag = "";
|
|
15231
|
+
if (adapter.id === "gemini") {
|
|
15232
|
+
const slotId = getChatGeminiSlotId(chatId);
|
|
15233
|
+
if (slotId) {
|
|
15234
|
+
const slot = getGeminiSlots().find((s) => s.id === slotId);
|
|
15235
|
+
if (slot) slotTag = ` \xB7 ${slot.label || `slot-${slot.id}`}`;
|
|
15236
|
+
}
|
|
15237
|
+
}
|
|
14522
15238
|
responseText += `
|
|
14523
15239
|
|
|
14524
|
-
\u{1F9E0} [${shortModel} | ${capitalize(thinking2)}] \u23F1\uFE0F ${elapsedSec}s`;
|
|
15240
|
+
\u{1F9E0} [${shortModel} | ${capitalize(thinking2)}${slotTag}] \u23F1\uFE0F ${elapsedSec}s`;
|
|
14525
15241
|
}
|
|
14526
15242
|
if (observedSubagents.size > 0) {
|
|
14527
15243
|
const names = [...observedSubagents].join(", ");
|
|
@@ -15788,7 +16504,11 @@ Result: ${task.result.slice(0, 500)}` : ""
|
|
|
15788
16504
|
pinChatGeminiSlot(chatId, slotId);
|
|
15789
16505
|
const label2 = slot.label || `slot-${slot.id}`;
|
|
15790
16506
|
const icon = slot.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
15791
|
-
|
|
16507
|
+
const rotationMode = getGeminiRotationMode();
|
|
16508
|
+
const rotationLabels = { off: "off", all: "all slots", accounts: "accounts only", keys: "keys only" };
|
|
16509
|
+
const rotationNote = rotationMode === "off" ? "Rotation is off \u2014 only this account will be used." : `Rotation: ${rotationLabels[rotationMode]} (this is the starting slot).`;
|
|
16510
|
+
await channel.sendText(chatId, `${icon} Account set to <b>${label2}</b>
|
|
16511
|
+
${rotationNote}`, { parseMode: "html" });
|
|
15792
16512
|
}
|
|
15793
16513
|
}
|
|
15794
16514
|
return;
|
|
@@ -15903,6 +16623,10 @@ Result: ${task.result.slice(0, 500)}` : ""
|
|
|
15903
16623
|
const parts = data.split(":");
|
|
15904
16624
|
const action = parts[1];
|
|
15905
16625
|
const idStr = parts[2];
|
|
16626
|
+
if (action !== "discuss") {
|
|
16627
|
+
const { clearDiscussionMode: clearStale } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16628
|
+
clearStale(chatId);
|
|
16629
|
+
}
|
|
15906
16630
|
switch (action) {
|
|
15907
16631
|
case "menu": {
|
|
15908
16632
|
const { getReflectionStatus: getRefStatus, getUnprocessedSignalCount: getUnprocessedSignalCount2, getPendingInsightCount: getPendingInsightCount2, getLastAnalysisTime: getLastAnalysisTime2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
@@ -15980,6 +16704,65 @@ Result: ${task.result.slice(0, 500)}` : ""
|
|
|
15980
16704
|
}
|
|
15981
16705
|
break;
|
|
15982
16706
|
}
|
|
16707
|
+
case "preview": {
|
|
16708
|
+
const { getInsightById: pvIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16709
|
+
const pvInsight = pvIns(getDb(), parseInt(idStr, 10));
|
|
16710
|
+
if (!pvInsight) {
|
|
16711
|
+
await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
|
|
16712
|
+
break;
|
|
16713
|
+
}
|
|
16714
|
+
if (!pvInsight.targetFile || !pvInsight.proposedDiff) {
|
|
16715
|
+
await channel.sendText(chatId, "No diff available for this proposal.", { parseMode: "plain" });
|
|
16716
|
+
break;
|
|
16717
|
+
}
|
|
16718
|
+
const { readFileSync: pvRead, existsSync: pvExists } = await import("fs");
|
|
16719
|
+
const { join: pvJoin } = await import("path");
|
|
16720
|
+
const pvTargetPath = pvJoin(homedir6(), ".cc-claw", pvInsight.targetFile);
|
|
16721
|
+
let previewLines;
|
|
16722
|
+
if (pvExists(pvTargetPath)) {
|
|
16723
|
+
const currentContent = pvRead(pvTargetPath, "utf-8");
|
|
16724
|
+
const diffLines = pvInsight.proposedDiff.split("\n");
|
|
16725
|
+
const additions = diffLines.filter((l) => l.startsWith("+")).map((l) => l.slice(1).trim());
|
|
16726
|
+
const removals = diffLines.filter((l) => l.startsWith("-")).map((l) => l.slice(1).trim());
|
|
16727
|
+
previewLines = [
|
|
16728
|
+
`\u{1F4CB} Dry-run preview: ${pvInsight.targetFile}`,
|
|
16729
|
+
`${"\u2500".repeat(40)}`,
|
|
16730
|
+
""
|
|
16731
|
+
];
|
|
16732
|
+
if (removals.length > 0) {
|
|
16733
|
+
previewLines.push("Lines to REMOVE:");
|
|
16734
|
+
for (const r of removals) {
|
|
16735
|
+
previewLines.push(` - ${r}`);
|
|
16736
|
+
const idx = currentContent.indexOf(r);
|
|
16737
|
+
if (idx >= 0) {
|
|
16738
|
+
const lineNum = currentContent.slice(0, idx).split("\n").length;
|
|
16739
|
+
previewLines.push(` (line ~${lineNum})`);
|
|
16740
|
+
}
|
|
16741
|
+
}
|
|
16742
|
+
previewLines.push("");
|
|
16743
|
+
}
|
|
16744
|
+
if (additions.length > 0) {
|
|
16745
|
+
previewLines.push("Lines to ADD:");
|
|
16746
|
+
for (const a of additions) {
|
|
16747
|
+
previewLines.push(` + ${a}`);
|
|
16748
|
+
}
|
|
16749
|
+
previewLines.push("");
|
|
16750
|
+
}
|
|
16751
|
+
previewLines.push(`File size: ${currentContent.length} chars`);
|
|
16752
|
+
} else {
|
|
16753
|
+
previewLines = [
|
|
16754
|
+
`\u{1F4CB} Dry-run preview: ${pvInsight.targetFile}`,
|
|
16755
|
+
`${"\u2500".repeat(40)}`,
|
|
16756
|
+
"",
|
|
16757
|
+
"(File does not exist yet \u2014 will be created)",
|
|
16758
|
+
"",
|
|
16759
|
+
"Content to add:",
|
|
16760
|
+
pvInsight.proposedDiff
|
|
16761
|
+
];
|
|
16762
|
+
}
|
|
16763
|
+
await channel.sendText(chatId, previewLines.join("\n"), { parseMode: "plain" });
|
|
16764
|
+
break;
|
|
16765
|
+
}
|
|
15983
16766
|
case "diff": {
|
|
15984
16767
|
const { getInsightById: getInsightById2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
15985
16768
|
const { formatDiffCodeBlock: formatDiffCodeBlock2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
@@ -16008,166 +16791,15 @@ Result: ${task.result.slice(0, 500)}` : ""
|
|
|
16008
16791
|
}
|
|
16009
16792
|
case "discuss": {
|
|
16010
16793
|
const insId = parseInt(idStr, 10);
|
|
16011
|
-
await
|
|
16012
|
-
|
|
16794
|
+
const { setDiscussionMode: setDiscussionMode2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16795
|
+
setDiscussionMode2(chatId, insId);
|
|
16796
|
+
await channel.sendText(chatId, [
|
|
16797
|
+
"\u{1F4AC} Discussion mode \u2014 tell me what you'd like to change about this proposal.",
|
|
16013
16798
|
"",
|
|
16014
|
-
"
|
|
16015
|
-
"
|
|
16016
|
-
"
|
|
16017
|
-
].join("\n"),
|
|
16018
|
-
[
|
|
16019
|
-
{ label: "Why this file?", data: `evolve:discuss-why:${insId}`, style: "primary" },
|
|
16020
|
-
{ label: "Retarget", data: `evolve:discuss-retarget:${insId}` }
|
|
16021
|
-
],
|
|
16022
|
-
[
|
|
16023
|
-
{ label: "Revise the change", data: `evolve:discuss-revise:${insId}` },
|
|
16024
|
-
{ label: "Back", data: `evolve:discuss-back:${insId}` }
|
|
16025
|
-
]
|
|
16026
|
-
]);
|
|
16027
|
-
break;
|
|
16028
|
-
}
|
|
16029
|
-
case "discuss-why": {
|
|
16030
|
-
const { getInsightById: gwIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16031
|
-
const { getDb: gwDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
16032
|
-
const gwInsight = gwIns(gwDb(), parseInt(idStr, 10));
|
|
16033
|
-
if (!gwInsight) {
|
|
16034
|
-
await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
|
|
16035
|
-
break;
|
|
16036
|
-
}
|
|
16037
|
-
await channel.sendTyping?.(chatId);
|
|
16038
|
-
const whyPrompt = [
|
|
16039
|
-
`Explain in 2-3 sentences why you chose "${gwInsight.targetFile}" as the target file for this insight:`,
|
|
16040
|
-
`"${gwInsight.insight}"`,
|
|
16041
|
-
`Consider what other files could work (skills, context files, USER.md) and explain your reasoning.`
|
|
16042
|
-
].join("\n");
|
|
16043
|
-
try {
|
|
16044
|
-
const whyResp = await askAgent(chatId, whyPrompt, { bootstrapTier: "chat", maxTurns: 1, timeoutMs: 6e4 });
|
|
16045
|
-
await channel.sendText(chatId, whyResp.text ?? "No response.");
|
|
16046
|
-
} catch (e) {
|
|
16047
|
-
await channel.sendText(chatId, `Error: ${e.message}`, { parseMode: "plain" });
|
|
16048
|
-
}
|
|
16049
|
-
await channel.sendKeyboard(chatId, "What next?", [
|
|
16050
|
-
[
|
|
16051
|
-
{ label: "OK, Apply", data: `evolve:apply:${idStr}`, style: "success" },
|
|
16052
|
-
{ label: "Retarget instead", data: `evolve:discuss-retarget:${idStr}` }
|
|
16053
|
-
],
|
|
16054
|
-
[
|
|
16055
|
-
{ label: "Reject", data: `evolve:reject:${idStr}`, style: "danger" },
|
|
16056
|
-
{ label: "Back", data: `evolve:discuss-back:${idStr}` }
|
|
16057
|
-
]
|
|
16058
|
-
]);
|
|
16059
|
-
break;
|
|
16060
|
-
}
|
|
16061
|
-
case "discuss-retarget": {
|
|
16062
|
-
const { getInsightById: rtIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16063
|
-
const { getDb: rtDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
16064
|
-
const rtInsight = rtIns(rtDb(), parseInt(idStr, 10));
|
|
16065
|
-
if (!rtInsight) {
|
|
16066
|
-
await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
|
|
16067
|
-
break;
|
|
16068
|
-
}
|
|
16069
|
-
const currentTarget = rtInsight.targetFile ?? "unknown";
|
|
16070
|
-
const targets = [
|
|
16071
|
-
{ label: "SOUL.md", path: "identity/SOUL.md" },
|
|
16072
|
-
{ label: "USER.md", path: "identity/USER.md" }
|
|
16073
|
-
];
|
|
16074
|
-
const skillDirs = [
|
|
16075
|
-
join18(homedir6(), ".cc-claw", "workspace", "skills")
|
|
16076
|
-
];
|
|
16077
|
-
try {
|
|
16078
|
-
const { readdirSync: readdirSync9, statSync: statSync10 } = await import("fs");
|
|
16079
|
-
for (const dir of skillDirs) {
|
|
16080
|
-
if (!existsSync18(dir)) continue;
|
|
16081
|
-
for (const entry of readdirSync9(dir)) {
|
|
16082
|
-
if (statSync10(join18(dir, entry)).isDirectory()) {
|
|
16083
|
-
targets.push({ label: `skills/${entry}`, path: `workspace/skills/${entry}/SKILL.md` });
|
|
16084
|
-
}
|
|
16085
|
-
}
|
|
16086
|
-
}
|
|
16087
|
-
} catch {
|
|
16088
|
-
}
|
|
16089
|
-
const rows = targets.filter((t) => t.path !== currentTarget).map((t) => [{ label: t.label, data: `evolve:discuss-retarget-to:${idStr}:${t.path}` }]);
|
|
16090
|
-
rows.push([{ label: "Cancel", data: `evolve:discuss-back:${idStr}` }]);
|
|
16091
|
-
await channel.sendKeyboard(chatId, `Current target: ${currentTarget}
|
|
16092
|
-
|
|
16093
|
-
Pick a different file for this change. Identity files (SOUL/USER) shape personality; skills teach specific workflows.`, rows);
|
|
16094
|
-
break;
|
|
16095
|
-
}
|
|
16096
|
-
case "discuss-retarget-to": {
|
|
16097
|
-
const retargetParts = data.split(":");
|
|
16098
|
-
const rtId = parseInt(retargetParts[2], 10);
|
|
16099
|
-
const newPath = retargetParts.slice(3).join(":");
|
|
16100
|
-
const { getInsightById: rt2Ins, updateInsightProposal: rt2Update } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16101
|
-
const { formatProposalCard: rt2Card, buildProposalKeyboard: rt2Kb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
16102
|
-
const { getDb: rt2Db } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
16103
|
-
const rt2Insight = rt2Ins(rt2Db(), rtId);
|
|
16104
|
-
if (!rt2Insight) {
|
|
16105
|
-
await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
|
|
16106
|
-
break;
|
|
16107
|
-
}
|
|
16108
|
-
rt2Update(rt2Db(), rtId, newPath, rt2Insight.proposedDiff, rt2Insight.proposedAction);
|
|
16109
|
-
const updatedIns = rt2Ins(rt2Db(), rtId);
|
|
16110
|
-
if (updatedIns) {
|
|
16111
|
-
const card = rt2Card(updatedIns);
|
|
16112
|
-
const kb = rt2Kb(updatedIns.id, updatedIns.category);
|
|
16113
|
-
await channel.sendKeyboard(chatId, `Retargeted to ${newPath}. Updated proposal:
|
|
16114
|
-
|
|
16115
|
-
` + card, kb);
|
|
16116
|
-
}
|
|
16117
|
-
break;
|
|
16118
|
-
}
|
|
16119
|
-
case "discuss-revise": {
|
|
16120
|
-
const { getInsightById: rvIns } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16121
|
-
const { getDb: rvDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
16122
|
-
const rvInsight = rvIns(rvDb(), parseInt(idStr, 10));
|
|
16123
|
-
if (!rvInsight) {
|
|
16124
|
-
await channel.sendText(chatId, "Proposal not found.", { parseMode: "plain" });
|
|
16125
|
-
break;
|
|
16126
|
-
}
|
|
16127
|
-
await channel.sendTyping?.(chatId);
|
|
16128
|
-
const revisePrompt = [
|
|
16129
|
-
`Revise this proposed change to be more specific and actionable:`,
|
|
16130
|
-
`Current insight: "${rvInsight.insight}"`,
|
|
16131
|
-
`Current diff: ${rvInsight.proposedDiff ?? "none"}`,
|
|
16132
|
-
`Target: ${rvInsight.targetFile}`,
|
|
16133
|
-
``,
|
|
16134
|
-
`Output ONLY the revised diff lines (starting with + for additions, - for removals). No explanation.`
|
|
16135
|
-
].join("\n");
|
|
16136
|
-
try {
|
|
16137
|
-
const rvResp = await askAgent(chatId, revisePrompt, { bootstrapTier: "chat", maxTurns: 1, timeoutMs: 6e4 });
|
|
16138
|
-
const newDiff = rvResp.text?.trim() ?? rvInsight.proposedDiff;
|
|
16139
|
-
if (newDiff) {
|
|
16140
|
-
const { updateInsightProposal: rvUpdate } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16141
|
-
rvUpdate(rvDb(), parseInt(idStr, 10), rvInsight.targetFile ?? "", newDiff, rvInsight.proposedAction);
|
|
16142
|
-
}
|
|
16143
|
-
const updatedRv = rvIns(rvDb(), parseInt(idStr, 10));
|
|
16144
|
-
if (updatedRv) {
|
|
16145
|
-
const { formatProposalCard: rvCard, buildProposalKeyboard: rvKb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
16146
|
-
const card = rvCard(updatedRv);
|
|
16147
|
-
const kb = rvKb(updatedRv.id, updatedRv.category);
|
|
16148
|
-
await channel.sendKeyboard(chatId, "Revised proposal:\n\n" + card, kb);
|
|
16149
|
-
}
|
|
16150
|
-
} catch (e) {
|
|
16151
|
-
await channel.sendText(chatId, `Revision error: ${e.message}`, { parseMode: "plain" });
|
|
16152
|
-
}
|
|
16153
|
-
break;
|
|
16154
|
-
}
|
|
16155
|
-
case "discuss-back": {
|
|
16156
|
-
const { getInsightById: bkIns, getReviewSession: bkSession } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
16157
|
-
const { formatProposalCardWithProgress: formatProposalCardWithProgress2, formatProposalCard: bkCardFn, buildProposalKeyboard: bkKb } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
16158
|
-
const { getDb: bkDb } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
16159
|
-
const bkInsight = bkIns(bkDb(), parseInt(idStr, 10));
|
|
16160
|
-
if (bkInsight && bkInsight.status === "pending") {
|
|
16161
|
-
const session2 = bkSession(bkDb(), chatId);
|
|
16162
|
-
const kb = bkKb(bkInsight.id, bkInsight.category);
|
|
16163
|
-
if (session2) {
|
|
16164
|
-
const card = formatProposalCardWithProgress2(bkInsight, session2.currentIndex, session2.insightIds.length);
|
|
16165
|
-
await channel.sendKeyboard(chatId, card, kb);
|
|
16166
|
-
} else {
|
|
16167
|
-
const card = bkCardFn(bkInsight);
|
|
16168
|
-
await channel.sendKeyboard(chatId, card, kb);
|
|
16169
|
-
}
|
|
16170
|
-
}
|
|
16799
|
+
'Examples: "Move this to a skill instead", "Rewrite the diff", "Why this file?", "Make it shorter"',
|
|
16800
|
+
"",
|
|
16801
|
+
"Type your reply and I'll handle it:"
|
|
16802
|
+
].join("\n"), { parseMode: "plain" });
|
|
16171
16803
|
break;
|
|
16172
16804
|
}
|
|
16173
16805
|
case "reject": {
|
|
@@ -16231,13 +16863,13 @@ Pick a different file for this change. Identity files (SOUL/USER) shape personal
|
|
|
16231
16863
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
16232
16864
|
const current = getReflectionStatus2(getDb2(), chatId);
|
|
16233
16865
|
if (current === "frozen") {
|
|
16234
|
-
const { readFileSync: readFileSync21, existsSync:
|
|
16866
|
+
const { readFileSync: readFileSync21, existsSync: existsSync46 } = await import("fs");
|
|
16235
16867
|
const { join: join28 } = await import("path");
|
|
16236
16868
|
const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
16237
16869
|
const soulPath = join28(CC_CLAW_HOME3, "identity/SOUL.md");
|
|
16238
16870
|
const userPath = join28(CC_CLAW_HOME3, "identity/USER.md");
|
|
16239
|
-
const soul =
|
|
16240
|
-
const user =
|
|
16871
|
+
const soul = existsSync46(soulPath) ? readFileSync21(soulPath, "utf-8") : "";
|
|
16872
|
+
const user = existsSync46(userPath) ? readFileSync21(userPath, "utf-8") : "";
|
|
16241
16873
|
setReflectionStatus2(getDb2(), chatId, "active", soul, user);
|
|
16242
16874
|
const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
|
|
16243
16875
|
logActivity2(getDb2(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
|
|
@@ -16831,7 +17463,7 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
|
|
|
16831
17463
|
const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
|
|
16832
17464
|
await channel.sendKeyboard(chatId, header2, buttons);
|
|
16833
17465
|
}
|
|
16834
|
-
var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, activeSideQuests, MAX_SIDE_QUESTS, pendingFallbackMessages, dashboardClawWarnings,
|
|
17466
|
+
var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, activeSideQuests, MAX_SIDE_QUESTS, pendingFallbackMessages, dashboardClawWarnings, pendingSummaryUndo, pendingNewchatUndo, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
|
|
16835
17467
|
var init_router = __esm({
|
|
16836
17468
|
"src/router.ts"() {
|
|
16837
17469
|
"use strict";
|
|
@@ -16974,8 +17606,6 @@ var init_router = __esm({
|
|
|
16974
17606
|
MAX_SIDE_QUESTS = 2;
|
|
16975
17607
|
pendingFallbackMessages = /* @__PURE__ */ new Map();
|
|
16976
17608
|
dashboardClawWarnings = /* @__PURE__ */ new Map();
|
|
16977
|
-
lastReflectTime = /* @__PURE__ */ new Map();
|
|
16978
|
-
REFLECT_COOLDOWN_MS = 12e4;
|
|
16979
17609
|
pendingSummaryUndo = /* @__PURE__ */ new Map();
|
|
16980
17610
|
pendingNewchatUndo = /* @__PURE__ */ new Map();
|
|
16981
17611
|
CLI_INSTALL_HINTS = {
|
|
@@ -17101,7 +17731,8 @@ function initScheduler(channelReg) {
|
|
|
17101
17731
|
if (orphaned.changes > 0) {
|
|
17102
17732
|
log(`[scheduler] Cleaned up ${orphaned.changes} orphaned run(s)`);
|
|
17103
17733
|
}
|
|
17104
|
-
} catch {
|
|
17734
|
+
} catch (err) {
|
|
17735
|
+
log(`[scheduler] Orphaned run cleanup skipped: ${err}`);
|
|
17105
17736
|
}
|
|
17106
17737
|
try {
|
|
17107
17738
|
const jobs = getActiveJobs();
|
|
@@ -17254,7 +17885,6 @@ async function executeJob(job) {
|
|
|
17254
17885
|
const contentStatus = isEmpty ? "no_content" : "success";
|
|
17255
17886
|
const durationMs = Date.now() - t0;
|
|
17256
17887
|
updateJobLastRun(job.id, (/* @__PURE__ */ new Date()).toISOString());
|
|
17257
|
-
resetJobFailures(job.id);
|
|
17258
17888
|
if (response.usage) {
|
|
17259
17889
|
addUsage(job.chatId, response.usage.input, response.usage.output, response.usage.cacheRead, resolvedModel, void 0, response.usage.contextSize);
|
|
17260
17890
|
}
|
|
@@ -17266,6 +17896,16 @@ async function executeJob(job) {
|
|
|
17266
17896
|
cacheRead: response.usage?.cacheRead,
|
|
17267
17897
|
durationMs
|
|
17268
17898
|
});
|
|
17899
|
+
if (finalStatus === "delivery_failed") {
|
|
17900
|
+
const failures = incrementJobFailures(job.id);
|
|
17901
|
+
error(`[scheduler] Job #${job.id} delivery failed (failures=${failures})`);
|
|
17902
|
+
if (failures >= AUTO_PAUSE_THRESHOLD) {
|
|
17903
|
+
pauseJob(job.id);
|
|
17904
|
+
await notifyJobAutoPaused({ ...job, consecutiveFailures: failures });
|
|
17905
|
+
}
|
|
17906
|
+
} else if (finalStatus === "success") {
|
|
17907
|
+
resetJobFailures(job.id);
|
|
17908
|
+
}
|
|
17269
17909
|
} catch (err) {
|
|
17270
17910
|
const durationMs = Date.now() - t0;
|
|
17271
17911
|
const errorClass = classifyError(err);
|
|
@@ -17558,18 +18198,18 @@ var init_wrap_backend = __esm({
|
|
|
17558
18198
|
});
|
|
17559
18199
|
|
|
17560
18200
|
// src/agents/runners/config-loader.ts
|
|
17561
|
-
import { readFileSync as readFileSync10, readdirSync as readdirSync8, existsSync as
|
|
18201
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync8, existsSync as existsSync18, mkdirSync as mkdirSync8, watchFile, unwatchFile } from "fs";
|
|
17562
18202
|
import { join as join20 } from "path";
|
|
17563
|
-
import { execFileSync } from "child_process";
|
|
18203
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
17564
18204
|
function resolveExecutable(config2) {
|
|
17565
|
-
if (
|
|
18205
|
+
if (existsSync18(config2.executable)) return config2.executable;
|
|
17566
18206
|
try {
|
|
17567
|
-
return
|
|
18207
|
+
return execFileSync2("which", [config2.executable], { encoding: "utf-8" }).trim();
|
|
17568
18208
|
} catch {
|
|
17569
18209
|
}
|
|
17570
18210
|
for (const fallback of config2.executableFallbacks ?? []) {
|
|
17571
18211
|
const resolved = fallback.replace(/^~/, process.env.HOME ?? "");
|
|
17572
|
-
if (
|
|
18212
|
+
if (existsSync18(resolved)) return resolved;
|
|
17573
18213
|
}
|
|
17574
18214
|
return config2.executable;
|
|
17575
18215
|
}
|
|
@@ -17708,7 +18348,7 @@ function loadRunnerConfig(filePath) {
|
|
|
17708
18348
|
}
|
|
17709
18349
|
}
|
|
17710
18350
|
function loadAllRunnerConfigs() {
|
|
17711
|
-
if (!
|
|
18351
|
+
if (!existsSync18(RUNNERS_PATH)) {
|
|
17712
18352
|
mkdirSync8(RUNNERS_PATH, { recursive: true });
|
|
17713
18353
|
return [];
|
|
17714
18354
|
}
|
|
@@ -17736,9 +18376,9 @@ function registerConfigRunners() {
|
|
|
17736
18376
|
return count;
|
|
17737
18377
|
}
|
|
17738
18378
|
function watchRunnerConfigs(onChange) {
|
|
17739
|
-
if (!
|
|
18379
|
+
if (!existsSync18(RUNNERS_PATH)) return;
|
|
17740
18380
|
for (const prev of watchedFiles) {
|
|
17741
|
-
if (!
|
|
18381
|
+
if (!existsSync18(prev)) {
|
|
17742
18382
|
unwatchFile(prev);
|
|
17743
18383
|
watchedFiles.delete(prev);
|
|
17744
18384
|
}
|
|
@@ -17795,11 +18435,12 @@ var init_runners = __esm({
|
|
|
17795
18435
|
function checkIdleAgents(agents2, now, thresholdMs = IDLE_THRESHOLD_MS) {
|
|
17796
18436
|
return agents2.filter((agent) => {
|
|
17797
18437
|
if (agent.status !== "running" || !agent.lastOutputAt) return false;
|
|
17798
|
-
const
|
|
18438
|
+
const ts2 = agent.lastOutputAt;
|
|
18439
|
+
const lastOutput = (/* @__PURE__ */ new Date(ts2 + (ts2.includes("Z") ? "" : "Z"))).getTime();
|
|
17799
18440
|
return now - lastOutput > thresholdMs;
|
|
17800
18441
|
});
|
|
17801
18442
|
}
|
|
17802
|
-
function startMonitor(callbacks, intervalMs =
|
|
18443
|
+
function startMonitor(callbacks, intervalMs = MONITOR_INTERVAL_MS) {
|
|
17803
18444
|
if (monitorInterval) return;
|
|
17804
18445
|
monitorInterval = setInterval(() => {
|
|
17805
18446
|
try {
|
|
@@ -17821,7 +18462,7 @@ function stopMonitor() {
|
|
|
17821
18462
|
monitorInterval = null;
|
|
17822
18463
|
}
|
|
17823
18464
|
}
|
|
17824
|
-
var monitorInterval;
|
|
18465
|
+
var monitorInterval, MONITOR_INTERVAL_MS;
|
|
17825
18466
|
var init_monitor = __esm({
|
|
17826
18467
|
"src/agents/monitor.ts"() {
|
|
17827
18468
|
"use strict";
|
|
@@ -17830,6 +18471,7 @@ var init_monitor = __esm({
|
|
|
17830
18471
|
init_types();
|
|
17831
18472
|
init_log();
|
|
17832
18473
|
monitorInterval = null;
|
|
18474
|
+
MONITOR_INTERVAL_MS = parseInt(process.env.CC_CLAW_MONITOR_INTERVAL_MS ?? "30000", 10);
|
|
17833
18475
|
}
|
|
17834
18476
|
});
|
|
17835
18477
|
|
|
@@ -17952,6 +18594,18 @@ var init_telegram = __esm({
|
|
|
17952
18594
|
|
|
17953
18595
|
// src/channels/telegram.ts
|
|
17954
18596
|
import { Bot, InlineKeyboard, InputFile } from "grammy";
|
|
18597
|
+
function isFastPathMessage(msg) {
|
|
18598
|
+
if (msg.type === "command" && msg.command && FAST_PATH_COMMANDS.has(msg.command)) {
|
|
18599
|
+
return true;
|
|
18600
|
+
}
|
|
18601
|
+
if (msg.type === "text" && msg.text) {
|
|
18602
|
+
const trimmed = msg.text.trim();
|
|
18603
|
+
if (trimmed.startsWith("!!") || trimmed.startsWith("!") && trimmed.length > 1) {
|
|
18604
|
+
return true;
|
|
18605
|
+
}
|
|
18606
|
+
}
|
|
18607
|
+
return false;
|
|
18608
|
+
}
|
|
17955
18609
|
function numericChatId(chatId) {
|
|
17956
18610
|
if (chatId.startsWith("sq:") || chatId.startsWith("cron:")) {
|
|
17957
18611
|
throw new Error(`Synthetic chatId "${chatId}" passed to Telegram API`);
|
|
@@ -17959,13 +18613,14 @@ function numericChatId(chatId) {
|
|
|
17959
18613
|
const raw = chatId.includes(":") ? chatId.split(":").pop() : chatId;
|
|
17960
18614
|
return parseInt(raw);
|
|
17961
18615
|
}
|
|
17962
|
-
var TelegramChannel;
|
|
18616
|
+
var FAST_PATH_COMMANDS, TelegramChannel;
|
|
17963
18617
|
var init_telegram2 = __esm({
|
|
17964
18618
|
"src/channels/telegram.ts"() {
|
|
17965
18619
|
"use strict";
|
|
17966
18620
|
init_telegram();
|
|
17967
18621
|
init_log();
|
|
17968
18622
|
init_store5();
|
|
18623
|
+
FAST_PATH_COMMANDS = /* @__PURE__ */ new Set(["stop", "status", "new", "newchat"]);
|
|
17969
18624
|
TelegramChannel = class {
|
|
17970
18625
|
name = "telegram";
|
|
17971
18626
|
bot;
|
|
@@ -18077,41 +18732,48 @@ var init_telegram2 = __esm({
|
|
|
18077
18732
|
{ command: "evolve", description: "Self-learning & evolution controls" },
|
|
18078
18733
|
{ command: "reflect", description: "Trigger reflection analysis" }
|
|
18079
18734
|
]);
|
|
18080
|
-
this.bot.on("message", (ctx) => {
|
|
18735
|
+
this.bot.on("message", async (ctx) => {
|
|
18081
18736
|
const chatId = ctx.chat.id.toString();
|
|
18082
18737
|
const senderId = ctx.from?.id?.toString() ?? "";
|
|
18083
18738
|
const authorized = this.isAuthorized(chatId) || this.isAuthorized(senderId);
|
|
18084
18739
|
log(`[telegram] Message from chat ${chatId} sender ${senderId} (authorized: ${authorized})`);
|
|
18085
18740
|
if (!authorized) return;
|
|
18086
18741
|
const msg = this.buildIncomingMessage(ctx);
|
|
18087
|
-
if (msg) {
|
|
18088
|
-
log(`[telegram] Processing ${msg.type}: "${msg.text?.slice(0, 50) || msg.command || "(media)"}"`);
|
|
18089
|
-
handler(msg, this).catch((err) => {
|
|
18090
|
-
error("[telegram] Handler error:", err);
|
|
18091
|
-
});
|
|
18092
|
-
} else {
|
|
18742
|
+
if (!msg) {
|
|
18093
18743
|
this.sendText(chatId, "I can handle text, voice, photos, documents, and videos. This message type isn't supported yet.", { parseMode: "plain" }).catch(() => {
|
|
18094
18744
|
});
|
|
18745
|
+
return;
|
|
18746
|
+
}
|
|
18747
|
+
log(`[telegram] Processing ${msg.type}: "${msg.text?.slice(0, 50) || msg.command || "(media)"}"`);
|
|
18748
|
+
if (isFastPathMessage(msg)) {
|
|
18749
|
+
handler(msg, this).catch((err) => {
|
|
18750
|
+
error("[telegram] Fast-path handler error:", err);
|
|
18751
|
+
});
|
|
18752
|
+
return;
|
|
18095
18753
|
}
|
|
18754
|
+
handler(msg, this).catch((err) => {
|
|
18755
|
+
error("[telegram] Handler error:", err);
|
|
18756
|
+
});
|
|
18096
18757
|
});
|
|
18097
|
-
this.bot.on("callback_query:data",
|
|
18758
|
+
this.bot.on("callback_query:data", (ctx) => {
|
|
18098
18759
|
const userId = ctx.from.id.toString();
|
|
18099
18760
|
const chatId = ctx.callbackQuery.message?.chat?.id?.toString() ?? userId;
|
|
18100
18761
|
log(`[telegram] Callback from user ${userId} in chat ${chatId}: ${ctx.callbackQuery.data}`);
|
|
18101
18762
|
if (!this.isAuthorized(userId) && !this.isAuthorized(chatId)) {
|
|
18102
|
-
|
|
18763
|
+
ctx.answerCallbackQuery("Unauthorized").catch(() => {
|
|
18764
|
+
});
|
|
18103
18765
|
return;
|
|
18104
18766
|
}
|
|
18105
18767
|
const data = ctx.callbackQuery.data;
|
|
18106
|
-
|
|
18768
|
+
ctx.answerCallbackQuery().catch(() => {
|
|
18107
18769
|
});
|
|
18108
|
-
|
|
18770
|
+
(async () => {
|
|
18109
18771
|
for (const handler2 of this.callbackHandlers) {
|
|
18110
18772
|
await handler2(chatId, data, this);
|
|
18111
18773
|
}
|
|
18112
|
-
}
|
|
18774
|
+
})().catch((err) => {
|
|
18113
18775
|
error("[telegram] Callback handler error:", err);
|
|
18114
|
-
}
|
|
18776
|
+
});
|
|
18115
18777
|
});
|
|
18116
18778
|
this.bot.on("message_reaction", async (ctx) => {
|
|
18117
18779
|
const chatId = String(ctx.chat.id);
|
|
@@ -18444,19 +19106,19 @@ var init_telegram2 = __esm({
|
|
|
18444
19106
|
});
|
|
18445
19107
|
|
|
18446
19108
|
// src/skills/bootstrap.ts
|
|
18447
|
-
import { existsSync as
|
|
19109
|
+
import { existsSync as existsSync19 } from "fs";
|
|
18448
19110
|
import { readdir as readdir4, readFile as readFile6, writeFile as writeFile4, copyFile } from "fs/promises";
|
|
18449
19111
|
import { join as join21, dirname as dirname3 } from "path";
|
|
18450
19112
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18451
19113
|
async function copyAgentManifestSkills() {
|
|
18452
|
-
if (!
|
|
19114
|
+
if (!existsSync19(PKG_SKILLS)) return;
|
|
18453
19115
|
try {
|
|
18454
19116
|
const entries = await readdir4(PKG_SKILLS, { withFileTypes: true });
|
|
18455
19117
|
for (const entry of entries) {
|
|
18456
19118
|
if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
|
|
18457
19119
|
const src = join21(PKG_SKILLS, entry.name);
|
|
18458
19120
|
const dest = join21(SKILLS_PATH, entry.name);
|
|
18459
|
-
if (
|
|
19121
|
+
if (existsSync19(dest)) continue;
|
|
18460
19122
|
await copyFile(src, dest);
|
|
18461
19123
|
log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
|
|
18462
19124
|
}
|
|
@@ -18467,7 +19129,7 @@ async function copyAgentManifestSkills() {
|
|
|
18467
19129
|
async function bootstrapSkills() {
|
|
18468
19130
|
await copyAgentManifestSkills();
|
|
18469
19131
|
const usmDir = join21(SKILLS_PATH, USM_DIR_NAME);
|
|
18470
|
-
if (
|
|
19132
|
+
if (existsSync19(usmDir)) return;
|
|
18471
19133
|
try {
|
|
18472
19134
|
const entries = await readdir4(SKILLS_PATH);
|
|
18473
19135
|
const dirs = entries.filter((e) => !e.startsWith("."));
|
|
@@ -18490,7 +19152,7 @@ async function bootstrapSkills() {
|
|
|
18490
19152
|
}
|
|
18491
19153
|
async function patchUsmForCcClaw(usmDir) {
|
|
18492
19154
|
const skillPath = join21(usmDir, "SKILL.md");
|
|
18493
|
-
if (!
|
|
19155
|
+
if (!existsSync19(skillPath)) return;
|
|
18494
19156
|
try {
|
|
18495
19157
|
let content = await readFile6(skillPath, "utf-8");
|
|
18496
19158
|
let patched = false;
|
|
@@ -18750,13 +19412,13 @@ __export(ai_skill_exports, {
|
|
|
18750
19412
|
generateAiSkill: () => generateAiSkill,
|
|
18751
19413
|
installAiSkill: () => installAiSkill
|
|
18752
19414
|
});
|
|
18753
|
-
import { existsSync as
|
|
19415
|
+
import { existsSync as existsSync20, writeFileSync as writeFileSync7, mkdirSync as mkdirSync9 } from "fs";
|
|
18754
19416
|
import { join as join22 } from "path";
|
|
18755
19417
|
import { homedir as homedir7 } from "os";
|
|
18756
19418
|
function generateAiSkill() {
|
|
18757
19419
|
const version = VERSION;
|
|
18758
19420
|
let systemState = "";
|
|
18759
|
-
if (
|
|
19421
|
+
if (existsSync20(DB_PATH)) {
|
|
18760
19422
|
try {
|
|
18761
19423
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store5(), __toCommonJS(store_exports5));
|
|
18762
19424
|
const readDb = openDatabaseReadOnly2();
|
|
@@ -18805,6 +19467,14 @@ Use the CC-Claw CLI when you need to:
|
|
|
18805
19467
|
- Every command supports \`--json\` for machine-parseable output
|
|
18806
19468
|
- Use \`--chat <id>\` to target a specific chat context
|
|
18807
19469
|
|
|
19470
|
+
## CRITICAL: CC-Claw Is Your API Layer
|
|
19471
|
+
|
|
19472
|
+
- **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
|
|
19473
|
+
- **NEVER** modify the SQLite database directly (no INSERT/UPDATE/DELETE SQL)
|
|
19474
|
+
- **ALWAYS** use CC-Claw slash commands (\`/remember\`, \`/evolve\`, etc.) or the \`cc-claw\` CLI for state changes
|
|
19475
|
+
- Memory MUST be cross-backend persistent \u2014 only CC-Claw's own commands guarantee this
|
|
19476
|
+
- If you don't know the right command, ask the user or run \`cc-claw --ai\`
|
|
19477
|
+
|
|
18808
19478
|
## Telegram Quick Reference
|
|
18809
19479
|
|
|
18810
19480
|
**Navigation & help:**
|
|
@@ -18861,8 +19531,7 @@ Use the CC-Claw CLI when you need to:
|
|
|
18861
19531
|
- \`/imagine <prompt>\` (\`/image\`) \u2014 Generate image (requires GEMINI_API_KEY)
|
|
18862
19532
|
- \`/skills\` \u2014 List skills
|
|
18863
19533
|
- \`/skill-install <url>\` \u2014 Install skill from GitHub
|
|
18864
|
-
- \`/evolve\` \u2014 Self-learning interactive keyboard
|
|
18865
|
-
- \`/reflect\` \u2014 Trigger reflection analysis
|
|
19534
|
+
- \`/evolve\` \u2014 Self-learning interactive keyboard (includes quick analyze)
|
|
18866
19535
|
- \`/gemini_accounts\` \u2014 Gemini credential rotation management
|
|
18867
19536
|
- \`/setup-profile\` \u2014 User profile setup wizard
|
|
18868
19537
|
|
|
@@ -18905,8 +19574,9 @@ cc-claw memory list --json # All memories with salience
|
|
|
18905
19574
|
cc-claw memory search "topic" --json # Search memories
|
|
18906
19575
|
cc-claw memory history --json # Session summaries
|
|
18907
19576
|
cc-claw memory history --limit 20 --json # Limit results
|
|
18908
|
-
|
|
18909
|
-
|
|
19577
|
+
# NOTE: To add/delete memories, use Telegram commands:
|
|
19578
|
+
# /remember <text> \u2014 Save a memory
|
|
19579
|
+
# /forget <keyword> \u2014 Delete memories
|
|
18910
19580
|
\`\`\`
|
|
18911
19581
|
|
|
18912
19582
|
### Session
|
|
@@ -19186,7 +19856,7 @@ var index_exports = {};
|
|
|
19186
19856
|
__export(index_exports, {
|
|
19187
19857
|
main: () => main
|
|
19188
19858
|
});
|
|
19189
|
-
import { mkdirSync as mkdirSync10, existsSync as
|
|
19859
|
+
import { mkdirSync as mkdirSync10, existsSync as existsSync21, renameSync, statSync as statSync5, readFileSync as readFileSync12 } from "fs";
|
|
19190
19860
|
import { join as join23 } from "path";
|
|
19191
19861
|
import dotenv from "dotenv";
|
|
19192
19862
|
function migrateLayout() {
|
|
@@ -19200,7 +19870,7 @@ function migrateLayout() {
|
|
|
19200
19870
|
[join23(CC_CLAW_HOME, "cc-claw.error.log.1"), join23(LOGS_PATH, "cc-claw.error.log.1")]
|
|
19201
19871
|
];
|
|
19202
19872
|
for (const [from, to] of moves) {
|
|
19203
|
-
if (
|
|
19873
|
+
if (existsSync21(from) && !existsSync21(to)) {
|
|
19204
19874
|
try {
|
|
19205
19875
|
renameSync(from, to);
|
|
19206
19876
|
} catch {
|
|
@@ -19236,6 +19906,11 @@ async function main() {
|
|
|
19236
19906
|
initDatabase();
|
|
19237
19907
|
pruneMessageLog(30, 2e3);
|
|
19238
19908
|
bootstrapBuiltinMcps(getDb());
|
|
19909
|
+
try {
|
|
19910
|
+
const { resetIntentStats: resetIntentStats2 } = await Promise.resolve().then(() => (init_classify(), classify_exports));
|
|
19911
|
+
resetIntentStats2();
|
|
19912
|
+
} catch {
|
|
19913
|
+
}
|
|
19239
19914
|
try {
|
|
19240
19915
|
const { getAdapter: getAdapter2, probeBackendAvailability: probeBackendAvailability2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
19241
19916
|
probeBackendAvailability2();
|
|
@@ -19409,10 +20084,10 @@ var init_index = __esm({
|
|
|
19409
20084
|
init_bootstrap2();
|
|
19410
20085
|
init_health3();
|
|
19411
20086
|
for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
|
|
19412
|
-
if (!
|
|
20087
|
+
if (!existsSync21(dir)) mkdirSync10(dir, { recursive: true });
|
|
19413
20088
|
}
|
|
19414
20089
|
migrateLayout();
|
|
19415
|
-
if (
|
|
20090
|
+
if (existsSync21(ENV_PATH)) {
|
|
19416
20091
|
dotenv.config({ path: ENV_PATH });
|
|
19417
20092
|
} else {
|
|
19418
20093
|
console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
|
|
@@ -19433,12 +20108,12 @@ __export(api_client_exports, {
|
|
|
19433
20108
|
apiPost: () => apiPost,
|
|
19434
20109
|
isDaemonRunning: () => isDaemonRunning
|
|
19435
20110
|
});
|
|
19436
|
-
import { readFileSync as readFileSync13, existsSync as
|
|
20111
|
+
import { readFileSync as readFileSync13, existsSync as existsSync22 } from "fs";
|
|
19437
20112
|
import { request as httpRequest } from "http";
|
|
19438
20113
|
function getToken() {
|
|
19439
20114
|
if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
|
|
19440
20115
|
try {
|
|
19441
|
-
if (
|
|
20116
|
+
if (existsSync22(TOKEN_PATH)) return readFileSync13(TOKEN_PATH, "utf-8").trim();
|
|
19442
20117
|
} catch {
|
|
19443
20118
|
}
|
|
19444
20119
|
return null;
|
|
@@ -19533,15 +20208,20 @@ __export(service_exports, {
|
|
|
19533
20208
|
serviceStatus: () => serviceStatus,
|
|
19534
20209
|
uninstallService: () => uninstallService
|
|
19535
20210
|
});
|
|
19536
|
-
import { existsSync as
|
|
19537
|
-
import { execFileSync as
|
|
20211
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync11, writeFileSync as writeFileSync8, unlinkSync as unlinkSync6 } from "fs";
|
|
20212
|
+
import { execFileSync as execFileSync3, execSync as execSync7 } from "child_process";
|
|
19538
20213
|
import { homedir as homedir8, platform } from "os";
|
|
19539
20214
|
import { join as join24, dirname as dirname4 } from "path";
|
|
20215
|
+
function xmlEscape(s) {
|
|
20216
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
20217
|
+
}
|
|
19540
20218
|
function resolveExecutable2(name) {
|
|
19541
20219
|
try {
|
|
19542
|
-
return
|
|
20220
|
+
return execFileSync3("which", [name], { encoding: "utf-8" }).trim();
|
|
19543
20221
|
} catch {
|
|
19544
|
-
|
|
20222
|
+
const fallback = process.argv[1];
|
|
20223
|
+
if (fallback && existsSync23(fallback)) return fallback;
|
|
20224
|
+
throw new Error(`Cannot find '${name}' executable. Install globally: npm install -g cc-claw`);
|
|
19545
20225
|
}
|
|
19546
20226
|
}
|
|
19547
20227
|
function getPathDirs() {
|
|
@@ -19565,21 +20245,28 @@ function generatePlist() {
|
|
|
19565
20245
|
const ccClawBin = resolveExecutable2("cc-claw");
|
|
19566
20246
|
const pathDirs = getPathDirs();
|
|
19567
20247
|
const home = homedir8();
|
|
20248
|
+
const safeBin = xmlEscape(ccClawBin);
|
|
20249
|
+
const safePaths = xmlEscape(pathDirs);
|
|
20250
|
+
const safeHome = xmlEscape(home);
|
|
20251
|
+
const safeLabel = xmlEscape(PLIST_LABEL);
|
|
20252
|
+
const safeCCHome = xmlEscape(CC_CLAW_HOME);
|
|
20253
|
+
const safeLog = xmlEscape(LOG_PATH);
|
|
20254
|
+
const safeErrLog = xmlEscape(ERROR_LOG_PATH);
|
|
19568
20255
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
19569
20256
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
19570
20257
|
<plist version="1.0">
|
|
19571
20258
|
<dict>
|
|
19572
20259
|
<key>Label</key>
|
|
19573
|
-
<string>${
|
|
20260
|
+
<string>${safeLabel}</string>
|
|
19574
20261
|
|
|
19575
20262
|
<key>ProgramArguments</key>
|
|
19576
20263
|
<array>
|
|
19577
|
-
<string>${
|
|
20264
|
+
<string>${safeBin}</string>
|
|
19578
20265
|
<string>start</string>
|
|
19579
20266
|
</array>
|
|
19580
20267
|
|
|
19581
20268
|
<key>WorkingDirectory</key>
|
|
19582
|
-
<string>${
|
|
20269
|
+
<string>${safeCCHome}</string>
|
|
19583
20270
|
|
|
19584
20271
|
<key>RunAtLoad</key>
|
|
19585
20272
|
<true/>
|
|
@@ -19588,17 +20275,17 @@ function generatePlist() {
|
|
|
19588
20275
|
<true/>
|
|
19589
20276
|
|
|
19590
20277
|
<key>StandardOutPath</key>
|
|
19591
|
-
<string>${
|
|
20278
|
+
<string>${safeLog}</string>
|
|
19592
20279
|
|
|
19593
20280
|
<key>StandardErrorPath</key>
|
|
19594
|
-
<string>${
|
|
20281
|
+
<string>${safeErrLog}</string>
|
|
19595
20282
|
|
|
19596
20283
|
<key>EnvironmentVariables</key>
|
|
19597
20284
|
<dict>
|
|
19598
20285
|
<key>PATH</key>
|
|
19599
|
-
<string>${
|
|
20286
|
+
<string>${safePaths}</string>
|
|
19600
20287
|
<key>HOME</key>
|
|
19601
|
-
<string>${
|
|
20288
|
+
<string>${safeHome}</string>
|
|
19602
20289
|
</dict>
|
|
19603
20290
|
|
|
19604
20291
|
<key>ThrottleInterval</key>
|
|
@@ -19608,26 +20295,26 @@ function generatePlist() {
|
|
|
19608
20295
|
}
|
|
19609
20296
|
function installMacOS() {
|
|
19610
20297
|
const agentsDir = dirname4(PLIST_PATH);
|
|
19611
|
-
if (!
|
|
19612
|
-
if (!
|
|
19613
|
-
if (
|
|
20298
|
+
if (!existsSync23(agentsDir)) mkdirSync11(agentsDir, { recursive: true });
|
|
20299
|
+
if (!existsSync23(LOGS_PATH)) mkdirSync11(LOGS_PATH, { recursive: true });
|
|
20300
|
+
if (existsSync23(PLIST_PATH)) {
|
|
19614
20301
|
try {
|
|
19615
|
-
|
|
20302
|
+
execFileSync3("launchctl", ["unload", PLIST_PATH]);
|
|
19616
20303
|
} catch {
|
|
19617
20304
|
}
|
|
19618
20305
|
}
|
|
19619
20306
|
writeFileSync8(PLIST_PATH, generatePlist());
|
|
19620
20307
|
console.log(` Installed: ${PLIST_PATH}`);
|
|
19621
|
-
|
|
20308
|
+
execFileSync3("launchctl", ["load", PLIST_PATH]);
|
|
19622
20309
|
console.log(" Service loaded and starting.");
|
|
19623
20310
|
}
|
|
19624
20311
|
function uninstallMacOS() {
|
|
19625
|
-
if (!
|
|
20312
|
+
if (!existsSync23(PLIST_PATH)) {
|
|
19626
20313
|
console.log(" No service found to uninstall.");
|
|
19627
20314
|
return;
|
|
19628
20315
|
}
|
|
19629
20316
|
try {
|
|
19630
|
-
|
|
20317
|
+
execFileSync3("launchctl", ["unload", PLIST_PATH]);
|
|
19631
20318
|
} catch {
|
|
19632
20319
|
}
|
|
19633
20320
|
unlinkSync6(PLIST_PATH);
|
|
@@ -19643,24 +20330,25 @@ function formatUptime(seconds) {
|
|
|
19643
20330
|
async function getUptimeFromDaemon() {
|
|
19644
20331
|
try {
|
|
19645
20332
|
const { isDaemonRunning: isDaemonRunning2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
19646
|
-
if (!await isDaemonRunning2()) return
|
|
20333
|
+
if (!await isDaemonRunning2()) return { status: "down" };
|
|
19647
20334
|
const res = await apiGet2("/api/health");
|
|
19648
20335
|
const sec = res.data?.uptime;
|
|
19649
|
-
return sec ? formatUptime(sec) :
|
|
20336
|
+
return sec ? { status: "ok", uptime: formatUptime(sec) } : { status: "down" };
|
|
19650
20337
|
} catch {
|
|
19651
|
-
return
|
|
20338
|
+
return { status: "error" };
|
|
19652
20339
|
}
|
|
19653
20340
|
}
|
|
19654
20341
|
function statusMacOS() {
|
|
19655
20342
|
try {
|
|
19656
|
-
const out =
|
|
19657
|
-
|
|
19658
|
-
|
|
20343
|
+
const out = execFileSync3("launchctl", ["list"], { encoding: "utf-8" });
|
|
20344
|
+
const line = out.split("\n").find((l) => l.includes("cc-claw"));
|
|
20345
|
+
if (line) {
|
|
20346
|
+
const parts = line.trim().split(/\s+/);
|
|
19659
20347
|
const pid = parts[0];
|
|
19660
20348
|
const exitCode = parts[1];
|
|
19661
20349
|
if (pid !== "-") {
|
|
19662
|
-
getUptimeFromDaemon().then((
|
|
19663
|
-
const uptimeStr =
|
|
20350
|
+
getUptimeFromDaemon().then((result) => {
|
|
20351
|
+
const uptimeStr = result.status === "ok" ? `, uptime ${result.uptime}` : "";
|
|
19664
20352
|
console.log(` Running (PID ${pid}${uptimeStr})`);
|
|
19665
20353
|
}).catch(() => {
|
|
19666
20354
|
console.log(` Running (PID ${pid})`);
|
|
@@ -19696,30 +20384,30 @@ WantedBy=default.target
|
|
|
19696
20384
|
`;
|
|
19697
20385
|
}
|
|
19698
20386
|
function installLinux() {
|
|
19699
|
-
if (!
|
|
19700
|
-
if (!
|
|
20387
|
+
if (!existsSync23(SYSTEMD_DIR)) mkdirSync11(SYSTEMD_DIR, { recursive: true });
|
|
20388
|
+
if (!existsSync23(LOGS_PATH)) mkdirSync11(LOGS_PATH, { recursive: true });
|
|
19701
20389
|
writeFileSync8(UNIT_PATH, generateUnit());
|
|
19702
20390
|
console.log(` Installed: ${UNIT_PATH}`);
|
|
19703
|
-
|
|
19704
|
-
|
|
19705
|
-
|
|
20391
|
+
execFileSync3("systemctl", ["--user", "daemon-reload"]);
|
|
20392
|
+
execFileSync3("systemctl", ["--user", "enable", "cc-claw"]);
|
|
20393
|
+
execFileSync3("systemctl", ["--user", "start", "cc-claw"]);
|
|
19706
20394
|
console.log(" Service enabled and started.");
|
|
19707
20395
|
}
|
|
19708
20396
|
function uninstallLinux() {
|
|
19709
|
-
if (!
|
|
20397
|
+
if (!existsSync23(UNIT_PATH)) {
|
|
19710
20398
|
console.log(" No service found to uninstall.");
|
|
19711
20399
|
return;
|
|
19712
20400
|
}
|
|
19713
20401
|
try {
|
|
19714
|
-
|
|
20402
|
+
execFileSync3("systemctl", ["--user", "stop", "cc-claw"]);
|
|
19715
20403
|
} catch {
|
|
19716
20404
|
}
|
|
19717
20405
|
try {
|
|
19718
|
-
|
|
20406
|
+
execFileSync3("systemctl", ["--user", "disable", "cc-claw"]);
|
|
19719
20407
|
} catch {
|
|
19720
20408
|
}
|
|
19721
20409
|
unlinkSync6(UNIT_PATH);
|
|
19722
|
-
|
|
20410
|
+
execFileSync3("systemctl", ["--user", "daemon-reload"]);
|
|
19723
20411
|
console.log(" Service uninstalled.");
|
|
19724
20412
|
}
|
|
19725
20413
|
function statusLinux() {
|
|
@@ -19731,28 +20419,28 @@ function statusLinux() {
|
|
|
19731
20419
|
}
|
|
19732
20420
|
}
|
|
19733
20421
|
function installService() {
|
|
19734
|
-
if (!
|
|
20422
|
+
if (!existsSync23(join24(CC_CLAW_HOME, ".env"))) {
|
|
19735
20423
|
console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
|
|
19736
20424
|
console.error(" Run 'cc-claw setup' before installing the service.");
|
|
19737
20425
|
process.exitCode = 1;
|
|
19738
20426
|
return;
|
|
19739
20427
|
}
|
|
19740
|
-
const
|
|
19741
|
-
if (
|
|
19742
|
-
else if (
|
|
19743
|
-
else console.error(` Unsupported platform: ${
|
|
20428
|
+
const os2 = platform();
|
|
20429
|
+
if (os2 === "darwin") installMacOS();
|
|
20430
|
+
else if (os2 === "linux") installLinux();
|
|
20431
|
+
else console.error(` Unsupported platform: ${os2}. Only macOS and Linux are supported.`);
|
|
19744
20432
|
}
|
|
19745
20433
|
function uninstallService() {
|
|
19746
|
-
const
|
|
19747
|
-
if (
|
|
19748
|
-
else if (
|
|
19749
|
-
else console.error(` Unsupported platform: ${
|
|
20434
|
+
const os2 = platform();
|
|
20435
|
+
if (os2 === "darwin") uninstallMacOS();
|
|
20436
|
+
else if (os2 === "linux") uninstallLinux();
|
|
20437
|
+
else console.error(` Unsupported platform: ${os2}.`);
|
|
19750
20438
|
}
|
|
19751
20439
|
function serviceStatus() {
|
|
19752
|
-
const
|
|
19753
|
-
if (
|
|
19754
|
-
else if (
|
|
19755
|
-
else console.error(` Unsupported platform: ${
|
|
20440
|
+
const os2 = platform();
|
|
20441
|
+
if (os2 === "darwin") statusMacOS();
|
|
20442
|
+
else if (os2 === "linux") statusLinux();
|
|
20443
|
+
else console.error(` Unsupported platform: ${os2}.`);
|
|
19756
20444
|
}
|
|
19757
20445
|
var PLIST_LABEL, PLIST_PATH, SYSTEMD_DIR, UNIT_PATH;
|
|
19758
20446
|
var init_service = __esm({
|
|
@@ -19875,20 +20563,20 @@ __export(daemon_exports, {
|
|
|
19875
20563
|
import { execSync as execSync8 } from "child_process";
|
|
19876
20564
|
import { platform as platform2 } from "os";
|
|
19877
20565
|
async function stopService() {
|
|
19878
|
-
const
|
|
20566
|
+
const os2 = platform2();
|
|
19879
20567
|
try {
|
|
19880
|
-
if (
|
|
20568
|
+
if (os2 === "darwin") {
|
|
19881
20569
|
execSync8("launchctl stop com.cc-claw");
|
|
19882
20570
|
console.log(`
|
|
19883
20571
|
${success("Daemon stopped.")}
|
|
19884
20572
|
`);
|
|
19885
|
-
} else if (
|
|
20573
|
+
} else if (os2 === "linux") {
|
|
19886
20574
|
execSync8("systemctl --user stop cc-claw");
|
|
19887
20575
|
console.log(`
|
|
19888
20576
|
${success("Daemon stopped.")}
|
|
19889
20577
|
`);
|
|
19890
20578
|
} else {
|
|
19891
|
-
outputError("UNSUPPORTED_PLATFORM", `Platform ${
|
|
20579
|
+
outputError("UNSUPPORTED_PLATFORM", `Platform ${os2} not supported for service management.`);
|
|
19892
20580
|
process.exit(1);
|
|
19893
20581
|
}
|
|
19894
20582
|
} catch (err) {
|
|
@@ -19898,21 +20586,21 @@ async function stopService() {
|
|
|
19898
20586
|
}
|
|
19899
20587
|
}
|
|
19900
20588
|
async function restartService() {
|
|
19901
|
-
const
|
|
20589
|
+
const os2 = platform2();
|
|
19902
20590
|
try {
|
|
19903
|
-
if (
|
|
20591
|
+
if (os2 === "darwin") {
|
|
19904
20592
|
const uid = process.getuid?.() ?? execSync8("id -u", { encoding: "utf-8" }).trim();
|
|
19905
20593
|
execSync8(`launchctl kickstart -k gui/${uid}/com.cc-claw`);
|
|
19906
20594
|
console.log(`
|
|
19907
20595
|
${success("Daemon restarted.")}
|
|
19908
20596
|
`);
|
|
19909
|
-
} else if (
|
|
20597
|
+
} else if (os2 === "linux") {
|
|
19910
20598
|
execSync8("systemctl --user restart cc-claw");
|
|
19911
20599
|
console.log(`
|
|
19912
20600
|
${success("Daemon restarted.")}
|
|
19913
20601
|
`);
|
|
19914
20602
|
} else {
|
|
19915
|
-
outputError("UNSUPPORTED_PLATFORM", `Platform ${
|
|
20603
|
+
outputError("UNSUPPORTED_PLATFORM", `Platform ${os2} not supported for service management.`);
|
|
19916
20604
|
process.exit(1);
|
|
19917
20605
|
}
|
|
19918
20606
|
} catch (err) {
|
|
@@ -19959,7 +20647,7 @@ var status_exports = {};
|
|
|
19959
20647
|
__export(status_exports, {
|
|
19960
20648
|
statusCommand: () => statusCommand
|
|
19961
20649
|
});
|
|
19962
|
-
import { existsSync as
|
|
20650
|
+
import { existsSync as existsSync24, statSync as statSync6 } from "fs";
|
|
19963
20651
|
async function statusCommand(globalOpts, localOpts) {
|
|
19964
20652
|
try {
|
|
19965
20653
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
@@ -19999,7 +20687,7 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
19999
20687
|
const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
|
|
20000
20688
|
const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
|
|
20001
20689
|
const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
|
|
20002
|
-
const dbStat =
|
|
20690
|
+
const dbStat = existsSync24(DB_PATH) ? statSync6(DB_PATH) : null;
|
|
20003
20691
|
let daemonRunning = false;
|
|
20004
20692
|
let daemonInfo = {};
|
|
20005
20693
|
if (localOpts.deep) {
|
|
@@ -20090,11 +20778,11 @@ var doctor_exports = {};
|
|
|
20090
20778
|
__export(doctor_exports, {
|
|
20091
20779
|
doctorCommand: () => doctorCommand
|
|
20092
20780
|
});
|
|
20093
|
-
import { existsSync as
|
|
20094
|
-
import { execFileSync as
|
|
20781
|
+
import { existsSync as existsSync25, statSync as statSync7, accessSync, constants } from "fs";
|
|
20782
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
20095
20783
|
async function doctorCommand(globalOpts, localOpts) {
|
|
20096
20784
|
const checks = [];
|
|
20097
|
-
if (
|
|
20785
|
+
if (existsSync25(DB_PATH)) {
|
|
20098
20786
|
const size = statSync7(DB_PATH).size;
|
|
20099
20787
|
checks.push({ name: "Database", status: "ok", message: `${DB_PATH} (${(size / 1024).toFixed(0)}KB)` });
|
|
20100
20788
|
try {
|
|
@@ -20124,7 +20812,7 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
20124
20812
|
} else {
|
|
20125
20813
|
checks.push({ name: "Database", status: "error", message: `Not found at ${DB_PATH}`, fix: "cc-claw setup" });
|
|
20126
20814
|
}
|
|
20127
|
-
if (
|
|
20815
|
+
if (existsSync25(ENV_PATH)) {
|
|
20128
20816
|
checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
|
|
20129
20817
|
} else {
|
|
20130
20818
|
checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
|
|
@@ -20133,7 +20821,7 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
20133
20821
|
let installedBackends = 0;
|
|
20134
20822
|
for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
|
|
20135
20823
|
try {
|
|
20136
|
-
const path =
|
|
20824
|
+
const path = execFileSync4("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
20137
20825
|
checks.push({ name: `${label2} CLI`, status: "ok", message: path });
|
|
20138
20826
|
installedBackends++;
|
|
20139
20827
|
} catch {
|
|
@@ -20172,14 +20860,14 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
20172
20860
|
checks.push({ name: "Daemon", status: "warning", message: "could not probe" });
|
|
20173
20861
|
}
|
|
20174
20862
|
try {
|
|
20175
|
-
const latest =
|
|
20863
|
+
const latest = execFileSync4("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
20176
20864
|
if (latest && latest !== VERSION) {
|
|
20177
20865
|
checks.push({ name: "Update available", status: "warning", message: `v${latest} available (current: v${VERSION})`, fix: "npm install -g cc-claw@latest" });
|
|
20178
20866
|
}
|
|
20179
20867
|
} catch {
|
|
20180
20868
|
}
|
|
20181
20869
|
const tokenPath = `${DATA_PATH}/api-token`;
|
|
20182
|
-
if (
|
|
20870
|
+
if (existsSync25(tokenPath)) {
|
|
20183
20871
|
try {
|
|
20184
20872
|
accessSync(tokenPath, constants.R_OK);
|
|
20185
20873
|
checks.push({ name: "API token", status: "ok", message: "token file readable" });
|
|
@@ -20204,7 +20892,7 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
20204
20892
|
}
|
|
20205
20893
|
} catch {
|
|
20206
20894
|
}
|
|
20207
|
-
if (
|
|
20895
|
+
if (existsSync25(ERROR_LOG_PATH)) {
|
|
20208
20896
|
try {
|
|
20209
20897
|
const { readFileSync: readFileSync21 } = await import("fs");
|
|
20210
20898
|
const logContent = readFileSync21(ERROR_LOG_PATH, "utf-8");
|
|
@@ -20232,15 +20920,15 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
20232
20920
|
if (check.name === "Daemon" && check.fix?.includes("service start")) {
|
|
20233
20921
|
try {
|
|
20234
20922
|
const { execSync: execSync9 } = await import("child_process");
|
|
20235
|
-
const
|
|
20236
|
-
if (
|
|
20923
|
+
const os2 = (await import("os")).platform();
|
|
20924
|
+
if (os2 === "darwin") {
|
|
20237
20925
|
try {
|
|
20238
20926
|
execSync9("launchctl start com.cc-claw");
|
|
20239
20927
|
check.status = "ok";
|
|
20240
20928
|
check.message = "restarted";
|
|
20241
20929
|
} catch {
|
|
20242
20930
|
}
|
|
20243
|
-
} else if (
|
|
20931
|
+
} else if (os2 === "linux") {
|
|
20244
20932
|
try {
|
|
20245
20933
|
execSync9("systemctl --user start cc-claw");
|
|
20246
20934
|
check.status = "ok";
|
|
@@ -20330,10 +21018,10 @@ var logs_exports = {};
|
|
|
20330
21018
|
__export(logs_exports, {
|
|
20331
21019
|
logsCommand: () => logsCommand
|
|
20332
21020
|
});
|
|
20333
|
-
import { existsSync as
|
|
21021
|
+
import { existsSync as existsSync26, readFileSync as readFileSync16, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
|
|
20334
21022
|
async function logsCommand(opts) {
|
|
20335
21023
|
const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
|
|
20336
|
-
if (!
|
|
21024
|
+
if (!existsSync26(logFile)) {
|
|
20337
21025
|
outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
|
|
20338
21026
|
process.exit(1);
|
|
20339
21027
|
}
|
|
@@ -20385,11 +21073,11 @@ __export(gemini_exports, {
|
|
|
20385
21073
|
geminiReorder: () => geminiReorder,
|
|
20386
21074
|
geminiRotation: () => geminiRotation
|
|
20387
21075
|
});
|
|
20388
|
-
import { existsSync as
|
|
21076
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readFileSync as readFileSync17, chmodSync } from "fs";
|
|
20389
21077
|
import { join as join25 } from "path";
|
|
20390
21078
|
import { createInterface as createInterface5 } from "readline";
|
|
20391
21079
|
function requireDb() {
|
|
20392
|
-
if (!
|
|
21080
|
+
if (!existsSync27(DB_PATH)) {
|
|
20393
21081
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
20394
21082
|
process.exit(1);
|
|
20395
21083
|
}
|
|
@@ -20415,7 +21103,7 @@ function resolveOAuthEmail(configHome) {
|
|
|
20415
21103
|
if (!configHome) return null;
|
|
20416
21104
|
try {
|
|
20417
21105
|
const accountsPath = join25(configHome, ".gemini", "google_accounts.json");
|
|
20418
|
-
if (!
|
|
21106
|
+
if (!existsSync27(accountsPath)) return null;
|
|
20419
21107
|
const accounts = JSON.parse(readFileSync17(accountsPath, "utf-8"));
|
|
20420
21108
|
return accounts.active || null;
|
|
20421
21109
|
} catch {
|
|
@@ -20499,7 +21187,7 @@ async function geminiAddKey(globalOpts, opts) {
|
|
|
20499
21187
|
async function geminiAddAccount(globalOpts, opts) {
|
|
20500
21188
|
await requireWriteDb();
|
|
20501
21189
|
const slotsDir = join25(CC_CLAW_HOME, "gemini-slots");
|
|
20502
|
-
if (!
|
|
21190
|
+
if (!existsSync27(slotsDir)) mkdirSync12(slotsDir, { recursive: true });
|
|
20503
21191
|
const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
20504
21192
|
const tempId = Date.now();
|
|
20505
21193
|
const slotDir = join25(slotsDir, `slot-${tempId}`);
|
|
@@ -20523,7 +21211,7 @@ async function geminiAddAccount(globalOpts, opts) {
|
|
|
20523
21211
|
} catch {
|
|
20524
21212
|
}
|
|
20525
21213
|
const oauthPath = join25(slotDir, ".gemini", "oauth_creds.json");
|
|
20526
|
-
if (!
|
|
21214
|
+
if (!existsSync27(oauthPath)) {
|
|
20527
21215
|
console.log(error2("\n No OAuth credentials found. Sign-in may have failed."));
|
|
20528
21216
|
console.log(" The slot directory is preserved at: " + slotDir);
|
|
20529
21217
|
console.log(" Re-run: cc-claw gemini add-account\n");
|
|
@@ -20637,12 +21325,12 @@ __export(backend_exports, {
|
|
|
20637
21325
|
backendList: () => backendList,
|
|
20638
21326
|
backendSet: () => backendSet
|
|
20639
21327
|
});
|
|
20640
|
-
import { existsSync as
|
|
21328
|
+
import { existsSync as existsSync28 } from "fs";
|
|
20641
21329
|
async function backendList(globalOpts) {
|
|
20642
21330
|
const { getAvailableAdapters: getAvailableAdapters2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
20643
21331
|
const chatId = resolveChatId(globalOpts);
|
|
20644
21332
|
let activeBackend = null;
|
|
20645
|
-
if (
|
|
21333
|
+
if (existsSync28(DB_PATH)) {
|
|
20646
21334
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
20647
21335
|
const readDb = openDatabaseReadOnly2();
|
|
20648
21336
|
try {
|
|
@@ -20673,7 +21361,7 @@ async function backendList(globalOpts) {
|
|
|
20673
21361
|
}
|
|
20674
21362
|
async function backendGet(globalOpts) {
|
|
20675
21363
|
const chatId = resolveChatId(globalOpts);
|
|
20676
|
-
if (!
|
|
21364
|
+
if (!existsSync28(DB_PATH)) {
|
|
20677
21365
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
20678
21366
|
process.exit(1);
|
|
20679
21367
|
}
|
|
@@ -20717,13 +21405,13 @@ __export(model_exports, {
|
|
|
20717
21405
|
modelList: () => modelList,
|
|
20718
21406
|
modelSet: () => modelSet
|
|
20719
21407
|
});
|
|
20720
|
-
import { existsSync as
|
|
21408
|
+
import { existsSync as existsSync29 } from "fs";
|
|
20721
21409
|
async function modelList(globalOpts) {
|
|
20722
21410
|
const chatId = resolveChatId(globalOpts);
|
|
20723
21411
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
20724
21412
|
const { getAdapter: getAdapter2, getAllAdapters: getAllAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
20725
21413
|
let backendId = "claude";
|
|
20726
|
-
if (
|
|
21414
|
+
if (existsSync29(DB_PATH)) {
|
|
20727
21415
|
const readDb = openDatabaseReadOnly2();
|
|
20728
21416
|
try {
|
|
20729
21417
|
const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
|
|
@@ -20756,7 +21444,7 @@ async function modelList(globalOpts) {
|
|
|
20756
21444
|
}
|
|
20757
21445
|
async function modelGet(globalOpts) {
|
|
20758
21446
|
const chatId = resolveChatId(globalOpts);
|
|
20759
|
-
if (!
|
|
21447
|
+
if (!existsSync29(DB_PATH)) {
|
|
20760
21448
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
20761
21449
|
process.exit(1);
|
|
20762
21450
|
}
|
|
@@ -20800,9 +21488,9 @@ __export(memory_exports, {
|
|
|
20800
21488
|
memoryList: () => memoryList,
|
|
20801
21489
|
memorySearch: () => memorySearch
|
|
20802
21490
|
});
|
|
20803
|
-
import { existsSync as
|
|
21491
|
+
import { existsSync as existsSync30 } from "fs";
|
|
20804
21492
|
async function memoryList(globalOpts) {
|
|
20805
|
-
if (!
|
|
21493
|
+
if (!existsSync30(DB_PATH)) {
|
|
20806
21494
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
20807
21495
|
process.exit(1);
|
|
20808
21496
|
}
|
|
@@ -20826,7 +21514,7 @@ async function memoryList(globalOpts) {
|
|
|
20826
21514
|
});
|
|
20827
21515
|
}
|
|
20828
21516
|
async function memorySearch(globalOpts, query) {
|
|
20829
|
-
if (!
|
|
21517
|
+
if (!existsSync30(DB_PATH)) {
|
|
20830
21518
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
20831
21519
|
process.exit(1);
|
|
20832
21520
|
}
|
|
@@ -20848,7 +21536,7 @@ async function memorySearch(globalOpts, query) {
|
|
|
20848
21536
|
});
|
|
20849
21537
|
}
|
|
20850
21538
|
async function memoryHistory(globalOpts, opts) {
|
|
20851
|
-
if (!
|
|
21539
|
+
if (!existsSync30(DB_PATH)) {
|
|
20852
21540
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
20853
21541
|
process.exit(1);
|
|
20854
21542
|
}
|
|
@@ -20896,7 +21584,7 @@ __export(cron_exports2, {
|
|
|
20896
21584
|
cronList: () => cronList,
|
|
20897
21585
|
cronRuns: () => cronRuns
|
|
20898
21586
|
});
|
|
20899
|
-
import { existsSync as
|
|
21587
|
+
import { existsSync as existsSync31 } from "fs";
|
|
20900
21588
|
function parseFallbacks(raw) {
|
|
20901
21589
|
return raw.slice(0, 3).map((f) => {
|
|
20902
21590
|
const [backend2, ...rest] = f.split(":");
|
|
@@ -20917,7 +21605,7 @@ function parseAndValidateTimeout(raw) {
|
|
|
20917
21605
|
return val;
|
|
20918
21606
|
}
|
|
20919
21607
|
async function cronList(globalOpts) {
|
|
20920
|
-
if (!
|
|
21608
|
+
if (!existsSync31(DB_PATH)) {
|
|
20921
21609
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
20922
21610
|
process.exit(1);
|
|
20923
21611
|
}
|
|
@@ -20955,7 +21643,7 @@ async function cronList(globalOpts) {
|
|
|
20955
21643
|
});
|
|
20956
21644
|
}
|
|
20957
21645
|
async function cronHealth(globalOpts) {
|
|
20958
|
-
if (!
|
|
21646
|
+
if (!existsSync31(DB_PATH)) {
|
|
20959
21647
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
20960
21648
|
process.exit(1);
|
|
20961
21649
|
}
|
|
@@ -20984,7 +21672,7 @@ async function cronHealth(globalOpts) {
|
|
|
20984
21672
|
});
|
|
20985
21673
|
}
|
|
20986
21674
|
async function cronCreate(globalOpts, opts) {
|
|
20987
|
-
const { isDaemonRunning: isDaemonRunning2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
21675
|
+
const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
20988
21676
|
if (!await isDaemonRunning2()) {
|
|
20989
21677
|
outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
|
|
20990
21678
|
process.exit(1);
|
|
@@ -20997,8 +21685,6 @@ async function cronCreate(globalOpts, opts) {
|
|
|
20997
21685
|
const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20998
21686
|
const timeout = parseAndValidateTimeout(opts.timeout);
|
|
20999
21687
|
try {
|
|
21000
|
-
const { initDatabase: initDatabase2, insertJob: insertJob2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
21001
|
-
initDatabase2();
|
|
21002
21688
|
const schedType = opts.cron ? "cron" : opts.at ? "at" : "every";
|
|
21003
21689
|
let everyMs = null;
|
|
21004
21690
|
if (opts.every) {
|
|
@@ -21010,7 +21696,7 @@ async function cronCreate(globalOpts, opts) {
|
|
|
21010
21696
|
}
|
|
21011
21697
|
}
|
|
21012
21698
|
const fallbacks = opts.fallback?.length ? parseFallbacks(opts.fallback) : void 0;
|
|
21013
|
-
const
|
|
21699
|
+
const res = await apiPost2("/api/cron/create", {
|
|
21014
21700
|
scheduleType: schedType,
|
|
21015
21701
|
cron: opts.cron ?? null,
|
|
21016
21702
|
atTime: opts.at ?? null,
|
|
@@ -21029,9 +21715,14 @@ async function cronCreate(globalOpts, opts) {
|
|
|
21029
21715
|
target: opts.target ?? null,
|
|
21030
21716
|
timezone: opts.timezone ?? "UTC"
|
|
21031
21717
|
});
|
|
21032
|
-
|
|
21033
|
-
|
|
21718
|
+
if (res.ok) {
|
|
21719
|
+
output({ id: res.data.id, success: true }, () => `
|
|
21720
|
+
${successFmt(`Job #${res.data.id} created.`)}
|
|
21034
21721
|
`);
|
|
21722
|
+
} else {
|
|
21723
|
+
outputError("CREATE_FAILED", `Failed: ${JSON.stringify(res.data)}`);
|
|
21724
|
+
process.exit(1);
|
|
21725
|
+
}
|
|
21035
21726
|
} catch (err) {
|
|
21036
21727
|
outputError("CREATE_FAILED", err.message);
|
|
21037
21728
|
process.exit(1);
|
|
@@ -21057,7 +21748,7 @@ async function cronAction(globalOpts, action, id) {
|
|
|
21057
21748
|
}
|
|
21058
21749
|
}
|
|
21059
21750
|
async function cronEdit(globalOpts, id, opts) {
|
|
21060
|
-
const { isDaemonRunning: isDaemonRunning2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
21751
|
+
const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
21061
21752
|
if (!await isDaemonRunning2()) {
|
|
21062
21753
|
outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
|
|
21063
21754
|
process.exit(1);
|
|
@@ -21065,82 +21756,53 @@ async function cronEdit(globalOpts, id, opts) {
|
|
|
21065
21756
|
const jobId = parseInt(id, 10);
|
|
21066
21757
|
const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
21067
21758
|
try {
|
|
21068
|
-
const
|
|
21069
|
-
|
|
21070
|
-
|
|
21071
|
-
|
|
21072
|
-
|
|
21073
|
-
if (opts.title) {
|
|
21074
|
-
updates.push("title = ?");
|
|
21075
|
-
values.push(opts.title);
|
|
21076
|
-
}
|
|
21077
|
-
if (opts.description) {
|
|
21078
|
-
updates.push("description = ?");
|
|
21079
|
-
values.push(opts.description);
|
|
21080
|
-
}
|
|
21081
|
-
if (opts.cron) {
|
|
21082
|
-
updates.push("cron = ?, schedule_type = 'cron'");
|
|
21083
|
-
values.push(opts.cron);
|
|
21084
|
-
}
|
|
21085
|
-
if (opts.at) {
|
|
21086
|
-
updates.push("at_time = ?, schedule_type = 'at'");
|
|
21087
|
-
values.push(opts.at);
|
|
21088
|
-
}
|
|
21759
|
+
const payload = { id: jobId };
|
|
21760
|
+
if (opts.title) payload.title = opts.title;
|
|
21761
|
+
if (opts.description) payload.description = opts.description;
|
|
21762
|
+
if (opts.cron) payload.cron = opts.cron;
|
|
21763
|
+
if (opts.at) payload.atTime = opts.at;
|
|
21089
21764
|
if (opts.every) {
|
|
21090
21765
|
const m = opts.every.match(/^(\d+)\s*(m|min|h|hr|s|sec)$/i);
|
|
21091
21766
|
if (m) {
|
|
21092
21767
|
const num = parseInt(m[1], 10);
|
|
21093
21768
|
const unit = m[2].toLowerCase();
|
|
21094
|
-
|
|
21095
|
-
updates.push("every_ms = ?, schedule_type = 'every'");
|
|
21096
|
-
values.push(ms);
|
|
21769
|
+
payload.everyMs = unit.startsWith("h") ? num * 36e5 : unit.startsWith("m") ? num * 6e4 : num * 1e3;
|
|
21097
21770
|
} else {
|
|
21098
|
-
|
|
21099
|
-
values.push(parseInt(opts.every, 10) || 0);
|
|
21771
|
+
payload.everyMs = parseInt(opts.every, 10) || 0;
|
|
21100
21772
|
}
|
|
21101
21773
|
}
|
|
21102
|
-
if (opts.backend)
|
|
21103
|
-
|
|
21104
|
-
|
|
21105
|
-
}
|
|
21106
|
-
if (opts.model) {
|
|
21107
|
-
updates.push("model = ?");
|
|
21108
|
-
values.push(opts.model);
|
|
21109
|
-
}
|
|
21110
|
-
if (opts.thinking) {
|
|
21111
|
-
updates.push("thinking = ?");
|
|
21112
|
-
values.push(opts.thinking);
|
|
21113
|
-
}
|
|
21774
|
+
if (opts.backend) payload.backend = opts.backend;
|
|
21775
|
+
if (opts.model) payload.model = opts.model;
|
|
21776
|
+
if (opts.thinking) payload.thinking = opts.thinking;
|
|
21114
21777
|
if (opts.timeout) {
|
|
21115
21778
|
const timeout = parseAndValidateTimeout(opts.timeout);
|
|
21116
|
-
|
|
21117
|
-
values.push(timeout);
|
|
21118
|
-
}
|
|
21119
|
-
if (opts.timezone) {
|
|
21120
|
-
updates.push("timezone = ?");
|
|
21121
|
-
values.push(opts.timezone);
|
|
21779
|
+
payload.timeout = timeout;
|
|
21122
21780
|
}
|
|
21781
|
+
if (opts.timezone) payload.timezone = opts.timezone;
|
|
21123
21782
|
if (opts.fallback?.length) {
|
|
21124
|
-
|
|
21125
|
-
updates.push("fallbacks = ?");
|
|
21126
|
-
values.push(JSON.stringify(fallbacks));
|
|
21783
|
+
payload.fallbacks = parseFallbacks(opts.fallback);
|
|
21127
21784
|
}
|
|
21128
|
-
|
|
21785
|
+
const fieldCount = Object.keys(payload).length - 1;
|
|
21786
|
+
if (fieldCount === 0) {
|
|
21129
21787
|
outputError("NO_CHANGES", "No fields to update. Specify fields with flags (e.g. --description, --cron).");
|
|
21130
21788
|
process.exit(1);
|
|
21131
21789
|
}
|
|
21132
|
-
|
|
21133
|
-
|
|
21134
|
-
|
|
21790
|
+
const res = await apiPost2("/api/cron/edit", payload);
|
|
21791
|
+
if (res.ok) {
|
|
21792
|
+
output({ id: jobId, success: true }, () => `
|
|
21135
21793
|
${successFmt(`Job #${jobId} updated.`)}
|
|
21136
21794
|
`);
|
|
21795
|
+
} else {
|
|
21796
|
+
outputError("EDIT_FAILED", `Failed: ${JSON.stringify(res.data)}`);
|
|
21797
|
+
process.exit(1);
|
|
21798
|
+
}
|
|
21137
21799
|
} catch (err) {
|
|
21138
21800
|
outputError("EDIT_FAILED", err.message);
|
|
21139
21801
|
process.exit(1);
|
|
21140
21802
|
}
|
|
21141
21803
|
}
|
|
21142
21804
|
async function cronRuns(globalOpts, jobId, opts) {
|
|
21143
|
-
if (!
|
|
21805
|
+
if (!existsSync31(DB_PATH)) {
|
|
21144
21806
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21145
21807
|
process.exit(1);
|
|
21146
21808
|
}
|
|
@@ -21187,16 +21849,16 @@ __export(agents_exports, {
|
|
|
21187
21849
|
runnersList: () => runnersList,
|
|
21188
21850
|
tasksList: () => tasksList
|
|
21189
21851
|
});
|
|
21190
|
-
import { existsSync as
|
|
21852
|
+
import { existsSync as existsSync32 } from "fs";
|
|
21191
21853
|
async function agentsList(globalOpts) {
|
|
21192
|
-
if (!
|
|
21854
|
+
if (!existsSync32(DB_PATH)) {
|
|
21193
21855
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21194
21856
|
process.exit(1);
|
|
21195
21857
|
}
|
|
21196
21858
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
21197
21859
|
const readDb = openDatabaseReadOnly2();
|
|
21198
21860
|
const agents2 = readDb.prepare(
|
|
21199
|
-
"SELECT * FROM agents WHERE status IN ('running', 'queued', 'starting', 'idle') ORDER BY
|
|
21861
|
+
"SELECT * FROM agents WHERE status IN ('running', 'queued', 'starting', 'idle') ORDER BY createdAt DESC"
|
|
21200
21862
|
).all();
|
|
21201
21863
|
readDb.close();
|
|
21202
21864
|
output(agents2, (d) => {
|
|
@@ -21207,10 +21869,10 @@ async function agentsList(globalOpts) {
|
|
|
21207
21869
|
const lines = ["", divider(`Active Agents (${list.length})`), ""];
|
|
21208
21870
|
for (const a of list) {
|
|
21209
21871
|
const shortId = a.id?.slice(0, 8) ?? "?";
|
|
21210
|
-
lines.push(` ${statusDot(a.status)} ${shortId} (${a.
|
|
21872
|
+
lines.push(` ${statusDot(a.status)} ${shortId} (${a.runnerId}) \u2014 ${a.status}`);
|
|
21211
21873
|
if (a.task) lines.push(` Task: ${a.task.slice(0, 80)}${a.task.length > 80 ? "\u2026" : ""}`);
|
|
21212
|
-
const inK = ((a.
|
|
21213
|
-
const outK = ((a.
|
|
21874
|
+
const inK = ((a.tokenInput ?? 0) / 1e3).toFixed(1);
|
|
21875
|
+
const outK = ((a.tokenOutput ?? 0) / 1e3).toFixed(1);
|
|
21214
21876
|
if (parseFloat(inK) > 0 || parseFloat(outK) > 0) {
|
|
21215
21877
|
lines.push(` Tokens: ${inK}k in / ${outK}k out`);
|
|
21216
21878
|
}
|
|
@@ -21220,14 +21882,14 @@ async function agentsList(globalOpts) {
|
|
|
21220
21882
|
});
|
|
21221
21883
|
}
|
|
21222
21884
|
async function tasksList(globalOpts) {
|
|
21223
|
-
if (!
|
|
21885
|
+
if (!existsSync32(DB_PATH)) {
|
|
21224
21886
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21225
21887
|
process.exit(1);
|
|
21226
21888
|
}
|
|
21227
21889
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
21228
21890
|
const readDb = openDatabaseReadOnly2();
|
|
21229
21891
|
const tasks = readDb.prepare(
|
|
21230
|
-
"SELECT t.* FROM
|
|
21892
|
+
"SELECT t.* FROM agent_tasks t JOIN orchestrations o ON t.orchestrationId = o.id WHERE o.status = 'active' ORDER BY t.id"
|
|
21231
21893
|
).all();
|
|
21232
21894
|
readDb.close();
|
|
21233
21895
|
output(tasks, (d) => {
|
|
@@ -21348,10 +22010,10 @@ __export(db_exports, {
|
|
|
21348
22010
|
dbPath: () => dbPath,
|
|
21349
22011
|
dbStats: () => dbStats
|
|
21350
22012
|
});
|
|
21351
|
-
import { existsSync as
|
|
22013
|
+
import { existsSync as existsSync33, statSync as statSync8, copyFileSync as copyFileSync2, mkdirSync as mkdirSync13 } from "fs";
|
|
21352
22014
|
import { dirname as dirname5 } from "path";
|
|
21353
22015
|
async function dbStats(globalOpts) {
|
|
21354
|
-
if (!
|
|
22016
|
+
if (!existsSync33(DB_PATH)) {
|
|
21355
22017
|
outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
|
|
21356
22018
|
process.exit(1);
|
|
21357
22019
|
}
|
|
@@ -21359,7 +22021,7 @@ async function dbStats(globalOpts) {
|
|
|
21359
22021
|
const readDb = openDatabaseReadOnly2();
|
|
21360
22022
|
const mainSize = statSync8(DB_PATH).size;
|
|
21361
22023
|
const walPath = DB_PATH + "-wal";
|
|
21362
|
-
const walSize =
|
|
22024
|
+
const walSize = existsSync33(walPath) ? statSync8(walPath).size : 0;
|
|
21363
22025
|
const tableNames = readDb.prepare(
|
|
21364
22026
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
|
|
21365
22027
|
).all();
|
|
@@ -21393,7 +22055,7 @@ async function dbPath(globalOpts) {
|
|
|
21393
22055
|
output({ path: DB_PATH }, (d) => d.path);
|
|
21394
22056
|
}
|
|
21395
22057
|
async function dbBackup(globalOpts, destPath) {
|
|
21396
|
-
if (!
|
|
22058
|
+
if (!existsSync33(DB_PATH)) {
|
|
21397
22059
|
outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
|
|
21398
22060
|
process.exit(1);
|
|
21399
22061
|
}
|
|
@@ -21402,7 +22064,7 @@ async function dbBackup(globalOpts, destPath) {
|
|
|
21402
22064
|
mkdirSync13(dirname5(dest), { recursive: true });
|
|
21403
22065
|
copyFileSync2(DB_PATH, dest);
|
|
21404
22066
|
const walPath = DB_PATH + "-wal";
|
|
21405
|
-
if (
|
|
22067
|
+
if (existsSync33(walPath)) copyFileSync2(walPath, dest + "-wal");
|
|
21406
22068
|
output({ path: dest, sizeBytes: statSync8(dest).size }, (d) => {
|
|
21407
22069
|
const b = d;
|
|
21408
22070
|
return `
|
|
@@ -21431,9 +22093,9 @@ __export(usage_exports, {
|
|
|
21431
22093
|
usageCost: () => usageCost,
|
|
21432
22094
|
usageTokens: () => usageTokens
|
|
21433
22095
|
});
|
|
21434
|
-
import { existsSync as
|
|
22096
|
+
import { existsSync as existsSync34 } from "fs";
|
|
21435
22097
|
function ensureDb() {
|
|
21436
|
-
if (!
|
|
22098
|
+
if (!existsSync34(DB_PATH)) {
|
|
21437
22099
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
21438
22100
|
process.exit(1);
|
|
21439
22101
|
}
|
|
@@ -21623,9 +22285,9 @@ __export(config_exports, {
|
|
|
21623
22285
|
configList: () => configList,
|
|
21624
22286
|
configSet: () => configSet
|
|
21625
22287
|
});
|
|
21626
|
-
import { existsSync as
|
|
22288
|
+
import { existsSync as existsSync35, readFileSync as readFileSync18 } from "fs";
|
|
21627
22289
|
async function configList(globalOpts) {
|
|
21628
|
-
if (!
|
|
22290
|
+
if (!existsSync35(DB_PATH)) {
|
|
21629
22291
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21630
22292
|
process.exit(1);
|
|
21631
22293
|
}
|
|
@@ -21659,7 +22321,7 @@ async function configGet(globalOpts, key) {
|
|
|
21659
22321
|
outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
|
|
21660
22322
|
process.exit(1);
|
|
21661
22323
|
}
|
|
21662
|
-
if (!
|
|
22324
|
+
if (!existsSync35(DB_PATH)) {
|
|
21663
22325
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21664
22326
|
process.exit(1);
|
|
21665
22327
|
}
|
|
@@ -21705,7 +22367,7 @@ async function configSet(globalOpts, key, value) {
|
|
|
21705
22367
|
}
|
|
21706
22368
|
}
|
|
21707
22369
|
async function configEnv(_globalOpts) {
|
|
21708
|
-
if (!
|
|
22370
|
+
if (!existsSync35(ENV_PATH)) {
|
|
21709
22371
|
outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
|
|
21710
22372
|
process.exit(1);
|
|
21711
22373
|
}
|
|
@@ -21759,9 +22421,9 @@ __export(session_exports, {
|
|
|
21759
22421
|
sessionGet: () => sessionGet,
|
|
21760
22422
|
sessionNew: () => sessionNew
|
|
21761
22423
|
});
|
|
21762
|
-
import { existsSync as
|
|
22424
|
+
import { existsSync as existsSync36 } from "fs";
|
|
21763
22425
|
async function sessionGet(globalOpts) {
|
|
21764
|
-
if (!
|
|
22426
|
+
if (!existsSync36(DB_PATH)) {
|
|
21765
22427
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21766
22428
|
process.exit(1);
|
|
21767
22429
|
}
|
|
@@ -21822,9 +22484,9 @@ __export(permissions_exports, {
|
|
|
21822
22484
|
verboseGet: () => verboseGet,
|
|
21823
22485
|
verboseSet: () => verboseSet
|
|
21824
22486
|
});
|
|
21825
|
-
import { existsSync as
|
|
22487
|
+
import { existsSync as existsSync37 } from "fs";
|
|
21826
22488
|
function ensureDb2() {
|
|
21827
|
-
if (!
|
|
22489
|
+
if (!existsSync37(DB_PATH)) {
|
|
21828
22490
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21829
22491
|
process.exit(1);
|
|
21830
22492
|
}
|
|
@@ -21971,9 +22633,9 @@ __export(cwd_exports, {
|
|
|
21971
22633
|
cwdGet: () => cwdGet,
|
|
21972
22634
|
cwdSet: () => cwdSet
|
|
21973
22635
|
});
|
|
21974
|
-
import { existsSync as
|
|
22636
|
+
import { existsSync as existsSync38 } from "fs";
|
|
21975
22637
|
async function cwdGet(globalOpts) {
|
|
21976
|
-
if (!
|
|
22638
|
+
if (!existsSync38(DB_PATH)) {
|
|
21977
22639
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
21978
22640
|
process.exit(1);
|
|
21979
22641
|
}
|
|
@@ -22035,9 +22697,9 @@ __export(voice_exports, {
|
|
|
22035
22697
|
voiceGet: () => voiceGet,
|
|
22036
22698
|
voiceSet: () => voiceSet
|
|
22037
22699
|
});
|
|
22038
|
-
import { existsSync as
|
|
22700
|
+
import { existsSync as existsSync39 } from "fs";
|
|
22039
22701
|
async function voiceGet(globalOpts) {
|
|
22040
|
-
if (!
|
|
22702
|
+
if (!existsSync39(DB_PATH)) {
|
|
22041
22703
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
22042
22704
|
process.exit(1);
|
|
22043
22705
|
}
|
|
@@ -22086,9 +22748,9 @@ __export(heartbeat_exports, {
|
|
|
22086
22748
|
heartbeatGet: () => heartbeatGet,
|
|
22087
22749
|
heartbeatSet: () => heartbeatSet
|
|
22088
22750
|
});
|
|
22089
|
-
import { existsSync as
|
|
22751
|
+
import { existsSync as existsSync40 } from "fs";
|
|
22090
22752
|
async function heartbeatGet(globalOpts) {
|
|
22091
|
-
if (!
|
|
22753
|
+
if (!existsSync40(DB_PATH)) {
|
|
22092
22754
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
22093
22755
|
process.exit(1);
|
|
22094
22756
|
}
|
|
@@ -22198,9 +22860,9 @@ __export(chats_exports, {
|
|
|
22198
22860
|
chatsList: () => chatsList,
|
|
22199
22861
|
chatsRemoveAlias: () => chatsRemoveAlias
|
|
22200
22862
|
});
|
|
22201
|
-
import { existsSync as
|
|
22863
|
+
import { existsSync as existsSync41 } from "fs";
|
|
22202
22864
|
async function chatsList(_globalOpts) {
|
|
22203
|
-
if (!
|
|
22865
|
+
if (!existsSync41(DB_PATH)) {
|
|
22204
22866
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
22205
22867
|
process.exit(1);
|
|
22206
22868
|
}
|
|
@@ -22328,9 +22990,9 @@ var mcps_exports = {};
|
|
|
22328
22990
|
__export(mcps_exports, {
|
|
22329
22991
|
mcpsList: () => mcpsList
|
|
22330
22992
|
});
|
|
22331
|
-
import { existsSync as
|
|
22993
|
+
import { existsSync as existsSync42 } from "fs";
|
|
22332
22994
|
async function mcpsList(_globalOpts) {
|
|
22333
|
-
if (!
|
|
22995
|
+
if (!existsSync42(DB_PATH)) {
|
|
22334
22996
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
22335
22997
|
process.exit(1);
|
|
22336
22998
|
}
|
|
@@ -22367,11 +23029,11 @@ __export(chat_exports, {
|
|
|
22367
23029
|
chatSend: () => chatSend
|
|
22368
23030
|
});
|
|
22369
23031
|
import { request as httpRequest2 } from "http";
|
|
22370
|
-
import { readFileSync as readFileSync19, existsSync as
|
|
23032
|
+
import { readFileSync as readFileSync19, existsSync as existsSync43 } from "fs";
|
|
22371
23033
|
function getToken2() {
|
|
22372
23034
|
if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
|
|
22373
23035
|
try {
|
|
22374
|
-
if (
|
|
23036
|
+
if (existsSync43(TOKEN_PATH2)) return readFileSync19(TOKEN_PATH2, "utf-8").trim();
|
|
22375
23037
|
} catch {
|
|
22376
23038
|
}
|
|
22377
23039
|
return null;
|
|
@@ -22841,9 +23503,9 @@ __export(evolve_exports, {
|
|
|
22841
23503
|
evolveStatus: () => evolveStatus,
|
|
22842
23504
|
evolveUndo: () => evolveUndo
|
|
22843
23505
|
});
|
|
22844
|
-
import { existsSync as
|
|
23506
|
+
import { existsSync as existsSync44 } from "fs";
|
|
22845
23507
|
function ensureDb3() {
|
|
22846
|
-
if (!
|
|
23508
|
+
if (!existsSync44(DB_PATH)) {
|
|
22847
23509
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
22848
23510
|
process.exit(1);
|
|
22849
23511
|
}
|
|
@@ -23259,8 +23921,8 @@ var init_evolve = __esm({
|
|
|
23259
23921
|
|
|
23260
23922
|
// src/setup.ts
|
|
23261
23923
|
var setup_exports = {};
|
|
23262
|
-
import { existsSync as
|
|
23263
|
-
import { execFileSync as
|
|
23924
|
+
import { existsSync as existsSync45, writeFileSync as writeFileSync11, readFileSync as readFileSync20, copyFileSync as copyFileSync3, mkdirSync as mkdirSync15, statSync as statSync9 } from "fs";
|
|
23925
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
23264
23926
|
import { createInterface as createInterface7 } from "readline";
|
|
23265
23927
|
import { join as join27 } from "path";
|
|
23266
23928
|
function divider2() {
|
|
@@ -23324,7 +23986,7 @@ async function setup() {
|
|
|
23324
23986
|
let foundAnyBackend = false;
|
|
23325
23987
|
for (const bk of backends) {
|
|
23326
23988
|
try {
|
|
23327
|
-
const path =
|
|
23989
|
+
const path = execFileSync5("which", [bk.cmd], { encoding: "utf-8" }).trim();
|
|
23328
23990
|
console.log(green(` ${bk.name} CLI found: ${path}`));
|
|
23329
23991
|
foundAnyBackend = true;
|
|
23330
23992
|
} catch {
|
|
@@ -23337,10 +23999,10 @@ async function setup() {
|
|
|
23337
23999
|
}
|
|
23338
24000
|
console.log("");
|
|
23339
24001
|
for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
|
|
23340
|
-
if (!
|
|
24002
|
+
if (!existsSync45(dir)) mkdirSync15(dir, { recursive: true });
|
|
23341
24003
|
}
|
|
23342
24004
|
const env = {};
|
|
23343
|
-
const envSource =
|
|
24005
|
+
const envSource = existsSync45(ENV_PATH) ? ENV_PATH : existsSync45(".env") ? ".env" : null;
|
|
23344
24006
|
if (envSource) {
|
|
23345
24007
|
console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
|
|
23346
24008
|
console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
|
|
@@ -23351,7 +24013,7 @@ async function setup() {
|
|
|
23351
24013
|
}
|
|
23352
24014
|
}
|
|
23353
24015
|
const cwdDb = join27(process.cwd(), "cc-claw.db");
|
|
23354
|
-
if (
|
|
24016
|
+
if (existsSync45(cwdDb) && !existsSync45(DB_PATH)) {
|
|
23355
24017
|
const { size } = statSync9(cwdDb);
|
|
23356
24018
|
console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
|
|
23357
24019
|
const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
|
|
@@ -24184,8 +24846,8 @@ async function run(argv = process.argv) {
|
|
|
24184
24846
|
if (argv.includes("--version") || argv.includes("-V")) {
|
|
24185
24847
|
console.log(VERSION);
|
|
24186
24848
|
try {
|
|
24187
|
-
const { execFileSync:
|
|
24188
|
-
const latest =
|
|
24849
|
+
const { execFileSync: execFileSync6 } = await import("child_process");
|
|
24850
|
+
const latest = execFileSync6("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
24189
24851
|
if (latest && latest !== VERSION) {
|
|
24190
24852
|
console.log(`
|
|
24191
24853
|
Update available: v${latest} (current: v${VERSION})`);
|