cc-claw 0.3.10 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/mcp-server.js +1 -1
- package/dist/cli.js +1094 -394
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -48,7 +48,7 @@ var VERSION;
|
|
|
48
48
|
var init_version = __esm({
|
|
49
49
|
"src/version.ts"() {
|
|
50
50
|
"use strict";
|
|
51
|
-
VERSION = true ? "0.
|
|
51
|
+
VERSION = true ? "0.4.0" : (() => {
|
|
52
52
|
try {
|
|
53
53
|
return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
|
|
54
54
|
} catch {
|
|
@@ -922,12 +922,14 @@ __export(store_exports3, {
|
|
|
922
922
|
ALL_TOOLS: () => ALL_TOOLS,
|
|
923
923
|
addHeartbeatWatch: () => addHeartbeatWatch,
|
|
924
924
|
addUsage: () => addUsage,
|
|
925
|
+
appendMessageLog: () => appendMessageLog,
|
|
925
926
|
cancelJobById: () => cancelJobById,
|
|
926
927
|
checkBackendLimits: () => checkBackendLimits,
|
|
927
928
|
cleanExpiredWatches: () => cleanExpiredWatches,
|
|
928
929
|
clearAllSessions: () => clearAllSessions,
|
|
929
930
|
clearBackendLimit: () => clearBackendLimit,
|
|
930
931
|
clearCwd: () => clearCwd,
|
|
932
|
+
clearMessageLog: () => clearMessageLog,
|
|
931
933
|
clearModel: () => clearModel,
|
|
932
934
|
clearSession: () => clearSession,
|
|
933
935
|
clearSummarizer: () => clearSummarizer,
|
|
@@ -935,6 +937,7 @@ __export(store_exports3, {
|
|
|
935
937
|
clearUsage: () => clearUsage,
|
|
936
938
|
completeJobRun: () => completeJobRun,
|
|
937
939
|
deleteBookmark: () => deleteBookmark,
|
|
940
|
+
determineEscalationTarget: () => determineEscalationTarget,
|
|
938
941
|
findBookmarksByPrefix: () => findBookmarksByPrefix,
|
|
939
942
|
forgetMemory: () => forgetMemory,
|
|
940
943
|
getActiveJobs: () => getActiveJobs,
|
|
@@ -961,14 +964,18 @@ __export(store_exports3, {
|
|
|
961
964
|
getMemoriesWithoutEmbeddings: () => getMemoriesWithoutEmbeddings,
|
|
962
965
|
getMode: () => getMode,
|
|
963
966
|
getModel: () => getModel,
|
|
967
|
+
getPendingEscalation: () => getPendingEscalation,
|
|
964
968
|
getRecentBookmarks: () => getRecentBookmarks,
|
|
965
969
|
getRecentMemories: () => getRecentMemories,
|
|
970
|
+
getRecentMessageLog: () => getRecentMessageLog,
|
|
966
971
|
getSessionId: () => getSessionId,
|
|
967
972
|
getSessionStartedAt: () => getSessionStartedAt,
|
|
968
973
|
getSessionSummariesWithoutEmbeddings: () => getSessionSummariesWithoutEmbeddings,
|
|
969
974
|
getSummarizer: () => getSummarizer,
|
|
970
975
|
getThinkingLevel: () => getThinkingLevel,
|
|
971
976
|
getToolsMap: () => getToolsMap,
|
|
977
|
+
getUnsummarizedChatIds: () => getUnsummarizedChatIds,
|
|
978
|
+
getUnsummarizedLog: () => getUnsummarizedLog,
|
|
972
979
|
getUsage: () => getUsage,
|
|
973
980
|
getVerboseLevel: () => getVerboseLevel,
|
|
974
981
|
incrementJobFailures: () => incrementJobFailures,
|
|
@@ -977,10 +984,13 @@ __export(store_exports3, {
|
|
|
977
984
|
insertJobRun: () => insertJobRun,
|
|
978
985
|
listMemories: () => listMemories,
|
|
979
986
|
listSessionSummaries: () => listSessionSummaries,
|
|
987
|
+
markMessageLogSummarized: () => markMessageLogSummarized,
|
|
980
988
|
openDatabaseReadOnly: () => openDatabaseReadOnly,
|
|
981
989
|
pruneJobRuns: () => pruneJobRuns,
|
|
990
|
+
pruneMessageLog: () => pruneMessageLog,
|
|
982
991
|
removeChatAlias: () => removeChatAlias,
|
|
983
992
|
removeHeartbeatWatch: () => removeHeartbeatWatch,
|
|
993
|
+
removePendingEscalation: () => removePendingEscalation,
|
|
984
994
|
resetJobFailures: () => resetJobFailures,
|
|
985
995
|
resetTools: () => resetTools,
|
|
986
996
|
saveMemory: () => saveMemory,
|
|
@@ -989,6 +999,7 @@ __export(store_exports3, {
|
|
|
989
999
|
saveSessionSummaryWithEmbedding: () => saveSessionSummaryWithEmbedding,
|
|
990
1000
|
searchMemories: () => searchMemories,
|
|
991
1001
|
searchMemoriesReadOnly: () => searchMemoriesReadOnly,
|
|
1002
|
+
searchMessageLog: () => searchMessageLog,
|
|
992
1003
|
searchSessionSummaries: () => searchSessionSummaries,
|
|
993
1004
|
setBackend: () => setBackend,
|
|
994
1005
|
setBackendLimit: () => setBackendLimit,
|
|
@@ -1003,6 +1014,8 @@ __export(store_exports3, {
|
|
|
1003
1014
|
setSummarizer: () => setSummarizer,
|
|
1004
1015
|
setThinkingLevel: () => setThinkingLevel,
|
|
1005
1016
|
setVerboseLevel: () => setVerboseLevel,
|
|
1017
|
+
storePendingEscalation: () => storePendingEscalation,
|
|
1018
|
+
toFts5Query: () => toFts5Query,
|
|
1006
1019
|
toggleTool: () => toggleTool,
|
|
1007
1020
|
touchBookmark: () => touchBookmark,
|
|
1008
1021
|
updateHeartbeatTimestamps: () => updateHeartbeatTimestamps,
|
|
@@ -1084,6 +1097,7 @@ function initDatabase() {
|
|
|
1084
1097
|
mode TEXT NOT NULL DEFAULT 'yolo'
|
|
1085
1098
|
);
|
|
1086
1099
|
`);
|
|
1100
|
+
db.prepare("UPDATE chat_mode SET mode = 'plan' WHERE mode = 'readonly'").run();
|
|
1087
1101
|
db.exec(`
|
|
1088
1102
|
CREATE TABLE IF NOT EXISTS chat_verbose (
|
|
1089
1103
|
chat_id TEXT PRIMARY KEY,
|
|
@@ -1144,6 +1158,7 @@ function initDatabase() {
|
|
|
1144
1158
|
model TEXT,
|
|
1145
1159
|
thinking TEXT,
|
|
1146
1160
|
timeout INTEGER,
|
|
1161
|
+
fallbacks TEXT,
|
|
1147
1162
|
session_type TEXT NOT NULL DEFAULT 'isolated',
|
|
1148
1163
|
channel TEXT,
|
|
1149
1164
|
target TEXT,
|
|
@@ -1263,6 +1278,10 @@ function initDatabase() {
|
|
|
1263
1278
|
db.exec("ALTER TABLE jobs ADD COLUMN timeout INTEGER");
|
|
1264
1279
|
} catch {
|
|
1265
1280
|
}
|
|
1281
|
+
try {
|
|
1282
|
+
db.exec("ALTER TABLE jobs ADD COLUMN fallbacks TEXT");
|
|
1283
|
+
} catch {
|
|
1284
|
+
}
|
|
1266
1285
|
}
|
|
1267
1286
|
} catch {
|
|
1268
1287
|
}
|
|
@@ -1390,6 +1409,52 @@ function initDatabase() {
|
|
|
1390
1409
|
PRIMARY KEY (chatId, alias)
|
|
1391
1410
|
)
|
|
1392
1411
|
`);
|
|
1412
|
+
db.exec(`
|
|
1413
|
+
CREATE TABLE IF NOT EXISTS message_log (
|
|
1414
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1415
|
+
chat_id TEXT NOT NULL,
|
|
1416
|
+
role TEXT NOT NULL,
|
|
1417
|
+
content TEXT NOT NULL,
|
|
1418
|
+
backend TEXT,
|
|
1419
|
+
model TEXT,
|
|
1420
|
+
session_id TEXT,
|
|
1421
|
+
summarized INTEGER NOT NULL DEFAULT 0,
|
|
1422
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1423
|
+
)
|
|
1424
|
+
`);
|
|
1425
|
+
db.exec(`
|
|
1426
|
+
CREATE INDEX IF NOT EXISTS idx_message_log_chat
|
|
1427
|
+
ON message_log (chat_id, created_at DESC)
|
|
1428
|
+
`);
|
|
1429
|
+
db.exec(`
|
|
1430
|
+
CREATE INDEX IF NOT EXISTS idx_message_log_unsummarized
|
|
1431
|
+
ON message_log (chat_id, summarized, created_at ASC)
|
|
1432
|
+
`);
|
|
1433
|
+
db.exec(`
|
|
1434
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS message_log_fts
|
|
1435
|
+
USING fts5(content, content='message_log', content_rowid='id')
|
|
1436
|
+
`);
|
|
1437
|
+
db.exec(`
|
|
1438
|
+
CREATE TRIGGER IF NOT EXISTS message_log_ai
|
|
1439
|
+
AFTER INSERT ON message_log BEGIN
|
|
1440
|
+
INSERT INTO message_log_fts(rowid, content) VALUES (new.id, new.content);
|
|
1441
|
+
END
|
|
1442
|
+
`);
|
|
1443
|
+
db.exec(`
|
|
1444
|
+
CREATE TRIGGER IF NOT EXISTS message_log_ad
|
|
1445
|
+
AFTER DELETE ON message_log BEGIN
|
|
1446
|
+
INSERT INTO message_log_fts(message_log_fts, rowid, content)
|
|
1447
|
+
VALUES ('delete', old.id, old.content);
|
|
1448
|
+
END
|
|
1449
|
+
`);
|
|
1450
|
+
db.exec(`
|
|
1451
|
+
CREATE TRIGGER IF NOT EXISTS message_log_au
|
|
1452
|
+
AFTER UPDATE ON message_log BEGIN
|
|
1453
|
+
INSERT INTO message_log_fts(message_log_fts, rowid, content)
|
|
1454
|
+
VALUES ('delete', old.id, old.content);
|
|
1455
|
+
INSERT INTO message_log_fts(rowid, content) VALUES (new.id, new.content);
|
|
1456
|
+
END
|
|
1457
|
+
`);
|
|
1393
1458
|
try {
|
|
1394
1459
|
db.exec("ALTER TABLE memories ADD COLUMN embedding TEXT");
|
|
1395
1460
|
} catch {
|
|
@@ -1669,7 +1734,8 @@ function getMode(chatId) {
|
|
|
1669
1734
|
const row = db.prepare(
|
|
1670
1735
|
"SELECT mode FROM chat_mode WHERE chat_id = ?"
|
|
1671
1736
|
).get(chatId);
|
|
1672
|
-
|
|
1737
|
+
const raw = row?.mode ?? "yolo";
|
|
1738
|
+
return raw === "readonly" ? "plan" : raw;
|
|
1673
1739
|
}
|
|
1674
1740
|
function setMode(chatId, mode) {
|
|
1675
1741
|
db.prepare(`
|
|
@@ -1678,6 +1744,30 @@ function setMode(chatId, mode) {
|
|
|
1678
1744
|
ON CONFLICT(chat_id) DO UPDATE SET mode = ?
|
|
1679
1745
|
`).run(chatId, mode, mode);
|
|
1680
1746
|
}
|
|
1747
|
+
function storePendingEscalation(chatId, message) {
|
|
1748
|
+
pendingEscalations.set(chatId, { message, createdAt: Date.now() });
|
|
1749
|
+
setTimeout(() => pendingEscalations.delete(chatId), ESCALATION_TTL_MS);
|
|
1750
|
+
}
|
|
1751
|
+
function getPendingEscalation(chatId) {
|
|
1752
|
+
const entry = pendingEscalations.get(chatId);
|
|
1753
|
+
if (!entry) return void 0;
|
|
1754
|
+
if (Date.now() - entry.createdAt > ESCALATION_TTL_MS) {
|
|
1755
|
+
pendingEscalations.delete(chatId);
|
|
1756
|
+
return void 0;
|
|
1757
|
+
}
|
|
1758
|
+
return entry.message;
|
|
1759
|
+
}
|
|
1760
|
+
function removePendingEscalation(chatId) {
|
|
1761
|
+
pendingEscalations.delete(chatId);
|
|
1762
|
+
}
|
|
1763
|
+
function determineEscalationTarget(chatId, currentMode) {
|
|
1764
|
+
if (currentMode === "plan") {
|
|
1765
|
+
const tools2 = getEnabledTools(chatId);
|
|
1766
|
+
if (tools2.length > 0 && tools2.length < ALL_TOOLS.length) return "safe";
|
|
1767
|
+
return "yolo";
|
|
1768
|
+
}
|
|
1769
|
+
return "yolo";
|
|
1770
|
+
}
|
|
1681
1771
|
function getUsage(chatId) {
|
|
1682
1772
|
const row = db.prepare(
|
|
1683
1773
|
"SELECT input_tokens, output_tokens, cache_read_tokens, request_count, last_input_tokens, last_cache_read_tokens FROM chat_usage WHERE chat_id = ?"
|
|
@@ -1955,8 +2045,8 @@ function getBackendUsageInWindow(backend2, windowType) {
|
|
|
1955
2045
|
function insertJob(params) {
|
|
1956
2046
|
const result = db.prepare(`
|
|
1957
2047
|
INSERT INTO jobs (schedule_type, cron, at_time, every_ms, description, chat_id,
|
|
1958
|
-
backend, model, thinking, timeout, session_type, channel, target, delivery_mode, timezone)
|
|
1959
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2048
|
+
backend, model, thinking, timeout, fallbacks, session_type, channel, target, delivery_mode, timezone)
|
|
2049
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1960
2050
|
`).run(
|
|
1961
2051
|
params.scheduleType,
|
|
1962
2052
|
params.cron ?? null,
|
|
@@ -1968,6 +2058,7 @@ function insertJob(params) {
|
|
|
1968
2058
|
params.model ?? null,
|
|
1969
2059
|
params.thinking ?? null,
|
|
1970
2060
|
params.timeout ?? null,
|
|
2061
|
+
params.fallbacks?.length ? JSON.stringify(params.fallbacks) : null,
|
|
1971
2062
|
params.sessionType ?? "isolated",
|
|
1972
2063
|
params.channel ?? null,
|
|
1973
2064
|
params.target ?? null,
|
|
@@ -1976,14 +2067,19 @@ function insertJob(params) {
|
|
|
1976
2067
|
);
|
|
1977
2068
|
return getJobById(Number(result.lastInsertRowid));
|
|
1978
2069
|
}
|
|
2070
|
+
function mapJobRow(row) {
|
|
2071
|
+
if (!row) return void 0;
|
|
2072
|
+
row.fallbacks = row.fallbacks ? JSON.parse(row.fallbacks) : [];
|
|
2073
|
+
return row;
|
|
2074
|
+
}
|
|
1979
2075
|
function getJobById(id) {
|
|
1980
|
-
return db.prepare(`${JOB_SELECT} WHERE id = ?`).get(id);
|
|
2076
|
+
return mapJobRow(db.prepare(`${JOB_SELECT} WHERE id = ?`).get(id));
|
|
1981
2077
|
}
|
|
1982
2078
|
function getActiveJobs() {
|
|
1983
|
-
return db.prepare(`${JOB_SELECT} WHERE active = 1 AND enabled = 1`).all();
|
|
2079
|
+
return db.prepare(`${JOB_SELECT} WHERE active = 1 AND enabled = 1`).all().map((r) => mapJobRow(r));
|
|
1984
2080
|
}
|
|
1985
2081
|
function getAllJobs() {
|
|
1986
|
-
return db.prepare(`${JOB_SELECT} WHERE active = 1 ORDER BY id`).all();
|
|
2082
|
+
return db.prepare(`${JOB_SELECT} WHERE active = 1 ORDER BY id`).all().map((r) => mapJobRow(r));
|
|
1987
2083
|
}
|
|
1988
2084
|
function updateJobEnabled(id, enabled) {
|
|
1989
2085
|
const result = db.prepare("UPDATE jobs SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, id);
|
|
@@ -2155,7 +2251,77 @@ function getAllChatAliases() {
|
|
|
2155
2251
|
function removeChatAlias(alias) {
|
|
2156
2252
|
return db.prepare("DELETE FROM chat_aliases WHERE alias = ?").run(alias.toLowerCase()).changes > 0;
|
|
2157
2253
|
}
|
|
2158
|
-
|
|
2254
|
+
function appendMessageLog(chatId, role, content, backend2, model2, sessionId) {
|
|
2255
|
+
getDb().prepare(`
|
|
2256
|
+
INSERT INTO message_log (chat_id, role, content, backend, model, session_id)
|
|
2257
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2258
|
+
`).run(chatId, role, content, backend2, model2, sessionId);
|
|
2259
|
+
}
|
|
2260
|
+
function getRecentMessageLog(chatId, limit) {
|
|
2261
|
+
return getDb().prepare(`
|
|
2262
|
+
SELECT * FROM message_log
|
|
2263
|
+
WHERE chat_id = ?
|
|
2264
|
+
ORDER BY created_at DESC, id DESC
|
|
2265
|
+
LIMIT ?
|
|
2266
|
+
`).all(chatId, limit);
|
|
2267
|
+
}
|
|
2268
|
+
function getUnsummarizedLog(chatId) {
|
|
2269
|
+
return getDb().prepare(`
|
|
2270
|
+
SELECT * FROM message_log
|
|
2271
|
+
WHERE chat_id = ? AND summarized = 0
|
|
2272
|
+
ORDER BY created_at ASC, id ASC
|
|
2273
|
+
`).all(chatId);
|
|
2274
|
+
}
|
|
2275
|
+
function searchMessageLog(chatId, query, limit = 20) {
|
|
2276
|
+
const sanitized = toFts5Query(query);
|
|
2277
|
+
if (!sanitized) return [];
|
|
2278
|
+
try {
|
|
2279
|
+
return getDb().prepare(`
|
|
2280
|
+
SELECT ml.* FROM message_log ml
|
|
2281
|
+
JOIN message_log_fts fts ON fts.rowid = ml.id
|
|
2282
|
+
WHERE ml.chat_id = ? AND message_log_fts MATCH ?
|
|
2283
|
+
ORDER BY fts.rank
|
|
2284
|
+
LIMIT ?
|
|
2285
|
+
`).all(chatId, sanitized, limit);
|
|
2286
|
+
} catch {
|
|
2287
|
+
return [];
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
function markMessageLogSummarized(chatId) {
|
|
2291
|
+
getDb().prepare(`
|
|
2292
|
+
UPDATE message_log SET summarized = 1
|
|
2293
|
+
WHERE chat_id = ? AND summarized = 0
|
|
2294
|
+
`).run(chatId);
|
|
2295
|
+
}
|
|
2296
|
+
function clearMessageLog(chatId) {
|
|
2297
|
+
getDb().prepare(`DELETE FROM message_log WHERE chat_id = ?`).run(chatId);
|
|
2298
|
+
}
|
|
2299
|
+
function getUnsummarizedChatIds() {
|
|
2300
|
+
const rows = getDb().prepare(
|
|
2301
|
+
`SELECT DISTINCT chat_id FROM message_log WHERE summarized = 0`
|
|
2302
|
+
).all();
|
|
2303
|
+
return rows.map((r) => r.chat_id);
|
|
2304
|
+
}
|
|
2305
|
+
function pruneMessageLog(retentionDays = 30, rowCapPerChat = 2e3) {
|
|
2306
|
+
const db3 = getDb();
|
|
2307
|
+
db3.prepare(`
|
|
2308
|
+
DELETE FROM message_log
|
|
2309
|
+
WHERE created_at < datetime('now', '-' || ? || ' days')
|
|
2310
|
+
`).run(retentionDays);
|
|
2311
|
+
const chats2 = db3.prepare(
|
|
2312
|
+
`SELECT DISTINCT chat_id FROM message_log`
|
|
2313
|
+
).all();
|
|
2314
|
+
for (const { chat_id } of chats2) {
|
|
2315
|
+
db3.prepare(`
|
|
2316
|
+
DELETE FROM message_log
|
|
2317
|
+
WHERE chat_id = ? AND id NOT IN (
|
|
2318
|
+
SELECT id FROM message_log WHERE chat_id = ?
|
|
2319
|
+
ORDER BY created_at DESC, id DESC LIMIT ?
|
|
2320
|
+
)
|
|
2321
|
+
`).run(chat_id, chat_id, rowCapPerChat);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
var db, STOP_WORDS, pendingEscalations, ESCALATION_TTL_MS, ALL_TOOLS, JOB_SELECT;
|
|
2159
2325
|
var init_store4 = __esm({
|
|
2160
2326
|
"src/memory/store.ts"() {
|
|
2161
2327
|
"use strict";
|
|
@@ -2280,10 +2446,12 @@ var init_store4 = __esm({
|
|
|
2280
2446
|
"near",
|
|
2281
2447
|
"or"
|
|
2282
2448
|
]);
|
|
2449
|
+
pendingEscalations = /* @__PURE__ */ new Map();
|
|
2450
|
+
ESCALATION_TTL_MS = 5 * 60 * 1e3;
|
|
2283
2451
|
ALL_TOOLS = ["Read", "Glob", "Grep", "Bash", "Write", "Edit", "WebFetch", "WebSearch", "Agent", "AskUserQuestion"];
|
|
2284
2452
|
JOB_SELECT = `
|
|
2285
2453
|
SELECT id, schedule_type as scheduleType, cron, at_time as atTime, every_ms as everyMs,
|
|
2286
|
-
description, chat_id as chatId, backend, model, thinking, timeout,
|
|
2454
|
+
description, chat_id as chatId, backend, model, thinking, timeout, fallbacks,
|
|
2287
2455
|
session_type as sessionType, channel, target, delivery_mode as deliveryMode,
|
|
2288
2456
|
timezone, enabled, active, created_at as createdAt, last_run_at as lastRunAt,
|
|
2289
2457
|
next_run_at as nextRunAt, consecutive_failures as consecutiveFailures
|
|
@@ -2433,15 +2601,14 @@ var init_claude = __esm({
|
|
|
2433
2601
|
switch (opts.permMode) {
|
|
2434
2602
|
case "plan":
|
|
2435
2603
|
args.push("--permission-mode", "plan");
|
|
2436
|
-
|
|
2437
|
-
case "readonly":
|
|
2438
|
-
args.push("--permission-mode", "bypassPermissions");
|
|
2439
|
-
args.push("--allowedTools", "Read,Glob,Grep");
|
|
2604
|
+
args.push("--strict-mcp-config");
|
|
2440
2605
|
break;
|
|
2441
2606
|
case "safe":
|
|
2442
2607
|
args.push("--permission-mode", "bypassPermissions");
|
|
2443
2608
|
if (opts.allowedTools.length > 0) {
|
|
2444
|
-
args.push("--
|
|
2609
|
+
args.push("--tools", opts.allowedTools.join(","));
|
|
2610
|
+
} else {
|
|
2611
|
+
args.push("--tools", "Read,Glob,Grep");
|
|
2445
2612
|
}
|
|
2446
2613
|
break;
|
|
2447
2614
|
case "yolo":
|
|
@@ -2600,11 +2767,10 @@ var init_gemini = __esm({
|
|
|
2600
2767
|
if (opts.model) args.push("-m", opts.model);
|
|
2601
2768
|
switch (opts.permMode) {
|
|
2602
2769
|
case "plan":
|
|
2603
|
-
case "readonly":
|
|
2604
2770
|
args.push("--approval-mode", "plan");
|
|
2605
2771
|
break;
|
|
2606
2772
|
case "safe":
|
|
2607
|
-
args.push("--approval-mode", "
|
|
2773
|
+
args.push("--approval-mode", "plan");
|
|
2608
2774
|
break;
|
|
2609
2775
|
case "yolo":
|
|
2610
2776
|
default:
|
|
@@ -2686,21 +2852,34 @@ var init_codex = __esm({
|
|
|
2686
2852
|
thinking: "adjustable",
|
|
2687
2853
|
thinkingLevels: ["low", "medium", "high", "extra_high"],
|
|
2688
2854
|
defaultThinkingLevel: "medium"
|
|
2855
|
+
},
|
|
2856
|
+
"gpt-5.1-codex-max": {
|
|
2857
|
+
label: "GPT-5.1 Codex Max \u2014 deep reasoning",
|
|
2858
|
+
thinking: "adjustable",
|
|
2859
|
+
thinkingLevels: ["low", "medium", "high", "extra_high"],
|
|
2860
|
+
defaultThinkingLevel: "medium"
|
|
2861
|
+
},
|
|
2862
|
+
"gpt-5.1-codex-mini": {
|
|
2863
|
+
label: "GPT-5.1 Codex Mini \u2014 cheaper, faster",
|
|
2864
|
+
thinking: "adjustable",
|
|
2865
|
+
thinkingLevels: ["low", "medium", "high", "extra_high"],
|
|
2866
|
+
defaultThinkingLevel: "low"
|
|
2689
2867
|
}
|
|
2690
2868
|
};
|
|
2691
2869
|
defaultModel = "gpt-5.4";
|
|
2692
|
-
// Internal-only model for summarization — not shown in /model picker
|
|
2693
2870
|
summarizerModel = "gpt-5.1-codex-mini";
|
|
2694
2871
|
pricing = {
|
|
2695
2872
|
"gpt-5.4": { in: 2.5, out: 10, cache: 0.63 },
|
|
2696
2873
|
"gpt-5.3-codex": { in: 2, out: 8, cache: 0.5 },
|
|
2697
2874
|
"gpt-5.2-codex": { in: 2, out: 8, cache: 0.5 },
|
|
2875
|
+
"gpt-5.1-codex-max": { in: 1.25, out: 10, cache: 0.125 },
|
|
2698
2876
|
"gpt-5.1-codex-mini": { in: 0.3, out: 1.2, cache: 0.08 }
|
|
2699
2877
|
};
|
|
2700
2878
|
contextWindow = {
|
|
2701
2879
|
"gpt-5.4": 2e5,
|
|
2702
2880
|
"gpt-5.3-codex": 2e5,
|
|
2703
2881
|
"gpt-5.2-codex": 2e5,
|
|
2882
|
+
"gpt-5.1-codex-max": 4e5,
|
|
2704
2883
|
"gpt-5.1-codex-mini": 2e5
|
|
2705
2884
|
};
|
|
2706
2885
|
// Codex has no single result event with final text -- the caller (spawnQuery)
|
|
@@ -2742,11 +2921,10 @@ var init_codex = __esm({
|
|
|
2742
2921
|
if (opts.model) args.push("-m", opts.model);
|
|
2743
2922
|
switch (opts.permMode) {
|
|
2744
2923
|
case "plan":
|
|
2745
|
-
case "readonly":
|
|
2746
2924
|
args.push("--sandbox", "read-only");
|
|
2747
2925
|
break;
|
|
2748
2926
|
case "safe":
|
|
2749
|
-
args.push("--
|
|
2927
|
+
args.push("--sandbox", "read-only");
|
|
2750
2928
|
break;
|
|
2751
2929
|
case "yolo":
|
|
2752
2930
|
default:
|
|
@@ -2904,6 +3082,31 @@ function mergeAndScore(allItems, vectorScores, ftsScores, getDays, decayRate, to
|
|
|
2904
3082
|
results.sort((a, b) => b.score - a.score);
|
|
2905
3083
|
return results.slice(0, topK);
|
|
2906
3084
|
}
|
|
3085
|
+
function setPendingContextBridge(chatId, bridge) {
|
|
3086
|
+
pendingContextBridges.set(chatId, bridge);
|
|
3087
|
+
}
|
|
3088
|
+
function consumeContextBridge(chatId) {
|
|
3089
|
+
const bridge = pendingContextBridges.get(chatId) ?? null;
|
|
3090
|
+
pendingContextBridges.delete(chatId);
|
|
3091
|
+
return bridge;
|
|
3092
|
+
}
|
|
3093
|
+
function buildContextBridge(chatId, pairs = 15) {
|
|
3094
|
+
const rows = getRecentMessageLog(chatId, pairs * 2).reverse();
|
|
3095
|
+
if (rows.length === 0) return null;
|
|
3096
|
+
const lines = [`[Conversation history \u2014 continued from previous session]`];
|
|
3097
|
+
let totalChars = lines[0].length;
|
|
3098
|
+
for (const row of rows) {
|
|
3099
|
+
const backendLabel = row.backend ?? "unknown";
|
|
3100
|
+
const date = row.created_at.slice(0, 10);
|
|
3101
|
+
const roleLabel = row.role === "user" ? "You" : "Assistant";
|
|
3102
|
+
const line = `[${backendLabel} \xB7 ${date}] ${roleLabel}: ${row.content}`;
|
|
3103
|
+
if (totalChars + line.length > MAX_BRIDGE_CHARS) break;
|
|
3104
|
+
lines.push(line);
|
|
3105
|
+
totalChars += line.length;
|
|
3106
|
+
}
|
|
3107
|
+
lines.push(`[End of recent history \u2014 you are continuing this conversation]`);
|
|
3108
|
+
return lines.join("\n");
|
|
3109
|
+
}
|
|
2907
3110
|
async function injectMemoryContext(userMessage) {
|
|
2908
3111
|
const provider = resolveProvider();
|
|
2909
3112
|
const useVectors = !!provider;
|
|
@@ -2982,17 +3185,21 @@ async function injectMemoryContext(userMessage) {
|
|
|
2982
3185
|
if (combinedMemories.length === 0 && combinedSessions.length === 0) return null;
|
|
2983
3186
|
const lines = [];
|
|
2984
3187
|
for (const m of combinedMemories) {
|
|
2985
|
-
|
|
3188
|
+
let text = `- [${m.type}] ${m.trigger}: ${m.content}`;
|
|
3189
|
+
if (text.length > MAX_MEMORY_CHARS) text = text.slice(0, MAX_MEMORY_CHARS) + "\u2026";
|
|
3190
|
+
lines.push(text);
|
|
2986
3191
|
}
|
|
2987
3192
|
for (const s of combinedSessions) {
|
|
2988
3193
|
const date = s.created_at.split("T")[0] ?? s.created_at.split(" ")[0];
|
|
2989
|
-
|
|
3194
|
+
let text = `- [episodic] ${date} session (${s.message_count} msgs): ${s.summary}${s.topics ? ` Topics: ${s.topics}` : ""}`;
|
|
3195
|
+
if (text.length > MAX_SESSION_CHARS) text = text.slice(0, MAX_SESSION_CHARS) + "\u2026";
|
|
3196
|
+
lines.push(text);
|
|
2990
3197
|
}
|
|
2991
3198
|
return `[Memory context]
|
|
2992
3199
|
${lines.join("\n")}
|
|
2993
3200
|
[End memory context]`;
|
|
2994
3201
|
}
|
|
2995
|
-
var MEMORY_DECAY_RATE, SESSION_DECAY_RATE, VECTOR_TOP_K, FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS;
|
|
3202
|
+
var MEMORY_DECAY_RATE, SESSION_DECAY_RATE, VECTOR_TOP_K, FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS, MAX_MEMORY_CHARS, MAX_SESSION_CHARS, MAX_BRIDGE_CHARS, pendingContextBridges;
|
|
2996
3203
|
var init_inject = __esm({
|
|
2997
3204
|
"src/memory/inject.ts"() {
|
|
2998
3205
|
"use strict";
|
|
@@ -3004,6 +3211,10 @@ var init_inject = __esm({
|
|
|
3004
3211
|
FTS_TOP_K = 20;
|
|
3005
3212
|
FINAL_TOP_K_MEMORIES = 5;
|
|
3006
3213
|
FINAL_TOP_K_SESSIONS = 3;
|
|
3214
|
+
MAX_MEMORY_CHARS = 500;
|
|
3215
|
+
MAX_SESSION_CHARS = 800;
|
|
3216
|
+
MAX_BRIDGE_CHARS = 48e3;
|
|
3217
|
+
pendingContextBridges = /* @__PURE__ */ new Map();
|
|
3007
3218
|
}
|
|
3008
3219
|
});
|
|
3009
3220
|
|
|
@@ -3114,6 +3325,7 @@ function syncNativeCliFiles() {
|
|
|
3114
3325
|
"## System Capabilities",
|
|
3115
3326
|
"",
|
|
3116
3327
|
"- To send a file to the user: write [SEND_FILE:/absolute/path] in your response",
|
|
3328
|
+
"- To generate an image: write [GENERATE_IMAGE:detailed prompt] in your response (requires GEMINI_API_KEY)",
|
|
3117
3329
|
"- To suggest saving a user preference: write [UPDATE_USER:key=value] in your response",
|
|
3118
3330
|
"- For heartbeat checks: respond with exactly HEARTBEAT_OK if nothing needs attention",
|
|
3119
3331
|
"- Your working directory is ~/.cc-claw/workspace/",
|
|
@@ -3147,29 +3359,8 @@ var init_init = __esm({
|
|
|
3147
3359
|
});
|
|
3148
3360
|
|
|
3149
3361
|
// src/bootstrap/loader.ts
|
|
3150
|
-
import { readFileSync as readFileSync3, existsSync as existsSync5, readdirSync
|
|
3362
|
+
import { readFileSync as readFileSync3, existsSync as existsSync5, readdirSync } from "fs";
|
|
3151
3363
|
import { join as join4 } from "path";
|
|
3152
|
-
function loadFileWithCache(path, cache) {
|
|
3153
|
-
if (!existsSync5(path)) return null;
|
|
3154
|
-
try {
|
|
3155
|
-
const stat = statSync2(path);
|
|
3156
|
-
if (stat.mtimeMs !== cache.mtime) {
|
|
3157
|
-
cache.content = readFileSync3(path, "utf-8").trim();
|
|
3158
|
-
cache.mtime = stat.mtimeMs;
|
|
3159
|
-
log(`[bootstrap] Loaded ${path} (${cache.content.length} chars)`);
|
|
3160
|
-
syncNativeCliFiles();
|
|
3161
|
-
}
|
|
3162
|
-
return cache.content;
|
|
3163
|
-
} catch {
|
|
3164
|
-
return null;
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
function loadSoul() {
|
|
3168
|
-
return loadFileWithCache(SOUL_PATH2, soulCacheObj);
|
|
3169
|
-
}
|
|
3170
|
-
function loadUser() {
|
|
3171
|
-
return loadFileWithCache(USER_PATH2, userCacheObj);
|
|
3172
|
-
}
|
|
3173
3364
|
function searchContext(userMessage) {
|
|
3174
3365
|
if (!existsSync5(CONTEXT_DIR2)) return null;
|
|
3175
3366
|
const msgWords = new Set(
|
|
@@ -3201,23 +3392,18 @@ function searchContext(userMessage) {
|
|
|
3201
3392
|
} catch {
|
|
3202
3393
|
}
|
|
3203
3394
|
if (bestMatch && bestMatch.score >= 2) {
|
|
3395
|
+
if (bestMatch.content.length > MAX_CONTEXT_CHARS) {
|
|
3396
|
+
return bestMatch.content.slice(0, MAX_CONTEXT_CHARS) + "\n\u2026(truncated)";
|
|
3397
|
+
}
|
|
3204
3398
|
return bestMatch.content;
|
|
3205
3399
|
}
|
|
3206
3400
|
return null;
|
|
3207
3401
|
}
|
|
3208
|
-
async function assembleBootstrapPrompt(userMessage, tier = "full", chatId) {
|
|
3402
|
+
async function assembleBootstrapPrompt(userMessage, tier = "full", chatId, permMode) {
|
|
3209
3403
|
const sections = [];
|
|
3210
|
-
|
|
3211
|
-
if (
|
|
3212
|
-
sections.push(
|
|
3213
|
-
${soul}`);
|
|
3214
|
-
}
|
|
3215
|
-
if (tier === "full" || tier === "heartbeat") {
|
|
3216
|
-
const user = loadUser();
|
|
3217
|
-
if (user) {
|
|
3218
|
-
sections.push(`[User profile]
|
|
3219
|
-
${user}`);
|
|
3220
|
-
}
|
|
3404
|
+
syncNativeCliFiles();
|
|
3405
|
+
if (permMode && permMode !== "yolo") {
|
|
3406
|
+
sections.push(buildPermissionNotice(permMode));
|
|
3221
3407
|
}
|
|
3222
3408
|
if (tier === "full") {
|
|
3223
3409
|
const ctx = searchContext(userMessage);
|
|
@@ -3226,9 +3412,17 @@ ${user}`);
|
|
|
3226
3412
|
${ctx}`);
|
|
3227
3413
|
}
|
|
3228
3414
|
}
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3415
|
+
if (chatId && tier !== "slim") {
|
|
3416
|
+
const bridge = consumeContextBridge(chatId);
|
|
3417
|
+
if (bridge) {
|
|
3418
|
+
sections.push(bridge);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
if (tier !== "slim") {
|
|
3422
|
+
const memory2 = await injectMemoryContext(userMessage);
|
|
3423
|
+
if (memory2) {
|
|
3424
|
+
sections.push(memory2);
|
|
3425
|
+
}
|
|
3232
3426
|
}
|
|
3233
3427
|
if (chatId && (tier === "full" || tier === "heartbeat")) {
|
|
3234
3428
|
const orchestrationContext = buildOrchestrationContext(chatId);
|
|
@@ -3241,6 +3435,12 @@ ${ctx}`);
|
|
|
3241
3435
|
log(`[bootstrap] Assembled prompt: tier=${tier}, sections=${sections.length}, totalChars=${result.length}`);
|
|
3242
3436
|
return result;
|
|
3243
3437
|
}
|
|
3438
|
+
function buildPermissionNotice(mode) {
|
|
3439
|
+
if (mode === "plan") {
|
|
3440
|
+
return "[Permission notice]\nYou are in restricted mode (plan). You can read and analyze files but CANNOT edit, write, or execute commands.\nIf the user's request requires modifications or execution, include the marker [NEED_PERMISSION] at the end of your response. Briefly explain what you would need to do and why elevated access is required. Do NOT attempt workarounds or refuse the task \u2014 signal the need and the system will ask the user to approve.\n[End permission notice]";
|
|
3441
|
+
}
|
|
3442
|
+
return "[Permission notice]\nYou are in restricted mode (safe). Only a subset of tools are available to you.\nIf the user's request requires tools you don't have access to, include the marker [NEED_PERMISSION] at the end of your response. Briefly explain what you would need to do and why elevated access is required. Do NOT attempt workarounds or refuse the task \u2014 signal the need and the system will ask the user to approve.\n[End permission notice]";
|
|
3443
|
+
}
|
|
3244
3444
|
function truncateToTokenBudget(text, budget) {
|
|
3245
3445
|
const charBudget = budget * 4;
|
|
3246
3446
|
if (text.length <= charBudget) return text;
|
|
@@ -3285,7 +3485,7 @@ ${boardText}`);
|
|
|
3285
3485
|
return null;
|
|
3286
3486
|
}
|
|
3287
3487
|
}
|
|
3288
|
-
var
|
|
3488
|
+
var CONTEXT_DIR2, MAX_CONTEXT_CHARS, ACTIVITY_TOKEN_BUDGET, INBOX_TOKEN_BUDGET, WHITEBOARD_TOKEN_BUDGET;
|
|
3289
3489
|
var init_loader = __esm({
|
|
3290
3490
|
"src/bootstrap/loader.ts"() {
|
|
3291
3491
|
"use strict";
|
|
@@ -3296,11 +3496,8 @@ var init_loader = __esm({
|
|
|
3296
3496
|
init_store3();
|
|
3297
3497
|
init_store4();
|
|
3298
3498
|
init_store();
|
|
3299
|
-
SOUL_PATH2 = join4(WORKSPACE_PATH, "SOUL.md");
|
|
3300
|
-
USER_PATH2 = join4(WORKSPACE_PATH, "USER.md");
|
|
3301
3499
|
CONTEXT_DIR2 = join4(WORKSPACE_PATH, "context");
|
|
3302
|
-
|
|
3303
|
-
userCacheObj = { content: null, mtime: 0 };
|
|
3500
|
+
MAX_CONTEXT_CHARS = 4e3;
|
|
3304
3501
|
ACTIVITY_TOKEN_BUDGET = 1500;
|
|
3305
3502
|
INBOX_TOKEN_BUDGET = 2e3;
|
|
3306
3503
|
WHITEBOARD_TOKEN_BUDGET = 500;
|
|
@@ -3308,66 +3505,62 @@ var init_loader = __esm({
|
|
|
3308
3505
|
});
|
|
3309
3506
|
|
|
3310
3507
|
// src/memory/session-log.ts
|
|
3311
|
-
function appendToLog(chatId, userMessage, assistantResponse) {
|
|
3312
|
-
if (!logs.has(chatId)) logs.set(chatId, []);
|
|
3313
|
-
const entries = logs.get(chatId);
|
|
3508
|
+
function appendToLog(chatId, userMessage, assistantResponse, backend2, model2, sessionId) {
|
|
3314
3509
|
const now = Date.now();
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
const
|
|
3318
|
-
|
|
3319
|
-
|
|
3510
|
+
appendMessageLog(chatId, "user", userMessage, backend2 ?? null, model2 ?? null, sessionId ?? null);
|
|
3511
|
+
appendMessageLog(chatId, "assistant", assistantResponse, backend2 ?? null, model2 ?? null, sessionId ?? null);
|
|
3512
|
+
const existing = cache.get(chatId) ?? [];
|
|
3513
|
+
existing.push(
|
|
3514
|
+
{ role: "user", text: userMessage, timestamp: now },
|
|
3515
|
+
{ role: "assistant", text: assistantResponse, timestamp: now }
|
|
3516
|
+
);
|
|
3517
|
+
if (existing.length > CACHE_SIZE * 2) {
|
|
3518
|
+
existing.splice(0, existing.length - CACHE_SIZE * 2);
|
|
3320
3519
|
}
|
|
3520
|
+
cache.set(chatId, existing);
|
|
3321
3521
|
}
|
|
3322
3522
|
function getLog(chatId) {
|
|
3323
|
-
|
|
3523
|
+
const rows = getUnsummarizedLog(chatId);
|
|
3524
|
+
return rows.map((r) => ({
|
|
3525
|
+
role: r.role,
|
|
3526
|
+
text: r.content,
|
|
3527
|
+
timestamp: (/* @__PURE__ */ new Date(r.created_at + (r.created_at.includes("Z") ? "" : "Z"))).getTime()
|
|
3528
|
+
}));
|
|
3529
|
+
}
|
|
3530
|
+
function getMessagePairCount(chatId) {
|
|
3531
|
+
const cached = cache.get(chatId);
|
|
3532
|
+
if (cached && cached.length > 0) {
|
|
3533
|
+
return Math.floor(cached.length / 2);
|
|
3534
|
+
}
|
|
3535
|
+
return Math.floor(getUnsummarizedLog(chatId).length / 2);
|
|
3324
3536
|
}
|
|
3325
3537
|
function clearLog(chatId) {
|
|
3326
|
-
|
|
3538
|
+
markMessageLogSummarized(chatId);
|
|
3539
|
+
cache.delete(chatId);
|
|
3327
3540
|
}
|
|
3328
3541
|
function getLoggedChatIds() {
|
|
3329
|
-
return
|
|
3330
|
-
}
|
|
3331
|
-
function getMessagePairCount(chatId) {
|
|
3332
|
-
return Math.floor((logs.get(chatId)?.length ?? 0) / 2);
|
|
3542
|
+
return getUnsummarizedChatIds();
|
|
3333
3543
|
}
|
|
3334
|
-
var
|
|
3544
|
+
var CACHE_SIZE, cache;
|
|
3335
3545
|
var init_session_log = __esm({
|
|
3336
3546
|
"src/memory/session-log.ts"() {
|
|
3337
3547
|
"use strict";
|
|
3338
|
-
|
|
3339
|
-
|
|
3548
|
+
init_store4();
|
|
3549
|
+
CACHE_SIZE = 20;
|
|
3550
|
+
cache = /* @__PURE__ */ new Map();
|
|
3340
3551
|
}
|
|
3341
3552
|
});
|
|
3342
3553
|
|
|
3343
3554
|
// src/memory/summarize.ts
|
|
3344
3555
|
var summarize_exports = {};
|
|
3345
3556
|
__export(summarize_exports, {
|
|
3557
|
+
CONTEXT_BRIDGE_PROMPT: () => CONTEXT_BRIDGE_PROMPT,
|
|
3346
3558
|
summarizeAllPending: () => summarizeAllPending,
|
|
3347
|
-
summarizeSession: () => summarizeSession
|
|
3559
|
+
summarizeSession: () => summarizeSession,
|
|
3560
|
+
summarizeWithFallbackChain: () => summarizeWithFallbackChain
|
|
3348
3561
|
});
|
|
3349
3562
|
import { spawn } from "child_process";
|
|
3350
3563
|
import { createInterface } from "readline";
|
|
3351
|
-
function resolveSummarizerAdapter(chatId) {
|
|
3352
|
-
const config2 = getSummarizer(chatId);
|
|
3353
|
-
if (config2.backend === "off") return null;
|
|
3354
|
-
let adapter;
|
|
3355
|
-
if (config2.backend) {
|
|
3356
|
-
adapter = getAdapter(config2.backend);
|
|
3357
|
-
} else {
|
|
3358
|
-
try {
|
|
3359
|
-
adapter = getAdapterForChat(chatId);
|
|
3360
|
-
} catch {
|
|
3361
|
-
try {
|
|
3362
|
-
adapter = getAdapter("claude");
|
|
3363
|
-
} catch {
|
|
3364
|
-
return null;
|
|
3365
|
-
}
|
|
3366
|
-
}
|
|
3367
|
-
}
|
|
3368
|
-
const model2 = config2.model ?? adapter.summarizerModel;
|
|
3369
|
-
return { adapter, model: model2 };
|
|
3370
|
-
}
|
|
3371
3564
|
function buildTranscript(entries) {
|
|
3372
3565
|
const lines = [];
|
|
3373
3566
|
let totalLen = 0;
|
|
@@ -3385,19 +3578,7 @@ function buildTranscript(entries) {
|
|
|
3385
3578
|
}
|
|
3386
3579
|
return lines.join("\n\n");
|
|
3387
3580
|
}
|
|
3388
|
-
async function
|
|
3389
|
-
const pairCount = getMessagePairCount(chatId);
|
|
3390
|
-
if (pairCount < MIN_PAIRS) {
|
|
3391
|
-
clearLog(chatId);
|
|
3392
|
-
return false;
|
|
3393
|
-
}
|
|
3394
|
-
const resolved = resolveSummarizerAdapter(chatId);
|
|
3395
|
-
if (!resolved) {
|
|
3396
|
-
clearLog(chatId);
|
|
3397
|
-
return false;
|
|
3398
|
-
}
|
|
3399
|
-
const { adapter, model: model2 } = resolved;
|
|
3400
|
-
const entries = getLog(chatId);
|
|
3581
|
+
async function attemptSummarize(chatId, adapter, model2, entries) {
|
|
3401
3582
|
const transcript = buildTranscript(entries);
|
|
3402
3583
|
const prompt = `${SUMMARIZE_PROMPT}
|
|
3403
3584
|
|
|
@@ -3416,7 +3597,6 @@ ${transcript}`;
|
|
|
3416
3597
|
let inputTokens = 0;
|
|
3417
3598
|
let outputTokens = 0;
|
|
3418
3599
|
let cacheReadTokens = 0;
|
|
3419
|
-
const SUMMARIZE_TIMEOUT_MS = 6e4;
|
|
3420
3600
|
await new Promise((resolve) => {
|
|
3421
3601
|
const proc = spawn(config2.executable, config2.args, {
|
|
3422
3602
|
env,
|
|
@@ -3426,7 +3606,7 @@ ${transcript}`;
|
|
|
3426
3606
|
proc.stderr?.resume();
|
|
3427
3607
|
const rl2 = createInterface({ input: proc.stdout });
|
|
3428
3608
|
const timeout = setTimeout(() => {
|
|
3429
|
-
warn(`[summarize] Timeout
|
|
3609
|
+
warn(`[summarize] Timeout (${adapter.id}:${model2}) for chat ${chatId}`);
|
|
3430
3610
|
rl2.close();
|
|
3431
3611
|
proc.kill("SIGTERM");
|
|
3432
3612
|
setTimeout(() => proc.kill("SIGKILL"), 2e3);
|
|
@@ -3441,9 +3621,7 @@ ${transcript}`;
|
|
|
3441
3621
|
}
|
|
3442
3622
|
const events = adapter.parseLine(msg);
|
|
3443
3623
|
for (const ev of events) {
|
|
3444
|
-
if (ev.type === "text" && ev.text)
|
|
3445
|
-
accumulatedText += ev.text;
|
|
3446
|
-
}
|
|
3624
|
+
if (ev.type === "text" && ev.text) accumulatedText += ev.text;
|
|
3447
3625
|
if (ev.type === "usage" && ev.usage) {
|
|
3448
3626
|
inputTokens += ev.usage.input;
|
|
3449
3627
|
outputTokens += ev.usage.output;
|
|
@@ -3452,9 +3630,9 @@ ${transcript}`;
|
|
|
3452
3630
|
if (ev.type === "result") {
|
|
3453
3631
|
resultText = ev.resultText || accumulatedText;
|
|
3454
3632
|
if (ev.usage) {
|
|
3455
|
-
inputTokens
|
|
3456
|
-
outputTokens
|
|
3457
|
-
cacheReadTokens
|
|
3633
|
+
inputTokens = ev.usage.input;
|
|
3634
|
+
outputTokens = ev.usage.output;
|
|
3635
|
+
cacheReadTokens = ev.usage.cacheRead;
|
|
3458
3636
|
}
|
|
3459
3637
|
if (adapter.shouldKillOnResult()) {
|
|
3460
3638
|
rl2.close();
|
|
@@ -3475,11 +3653,9 @@ ${transcript}`;
|
|
|
3475
3653
|
if (inputTokens + outputTokens > 0) {
|
|
3476
3654
|
addUsage(chatId, inputTokens, outputTokens, cacheReadTokens, model2);
|
|
3477
3655
|
}
|
|
3656
|
+
if (!resultText) resultText = accumulatedText;
|
|
3478
3657
|
if (!resultText) {
|
|
3479
|
-
|
|
3480
|
-
}
|
|
3481
|
-
if (!resultText) {
|
|
3482
|
-
warn(`[summarize] ${adapter.displayName} returned empty result for chat ${chatId} \u2014 keeping log for retry`);
|
|
3658
|
+
warn(`[summarize] ${adapter.id}:${model2} returned empty result for chat ${chatId}`);
|
|
3483
3659
|
return false;
|
|
3484
3660
|
}
|
|
3485
3661
|
const summaryMatch = resultText.match(/SUMMARY:\s*(.+?)(?=\nKEY_DETAILS:|\nTOPICS:|$)/s);
|
|
@@ -3487,20 +3663,72 @@ ${transcript}`;
|
|
|
3487
3663
|
const topicsMatch = resultText.match(/TOPICS:\s*(.+)/);
|
|
3488
3664
|
let summary = summaryMatch?.[1]?.trim() ?? resultText.trim();
|
|
3489
3665
|
const keyDetails = keyDetailsMatch?.[1]?.trim();
|
|
3490
|
-
if (keyDetails)
|
|
3491
|
-
summary += `
|
|
3666
|
+
if (keyDetails) summary += `
|
|
3492
3667
|
Key details: ${keyDetails}`;
|
|
3493
|
-
}
|
|
3494
3668
|
const topics = topicsMatch?.[1]?.trim() ?? "";
|
|
3495
|
-
saveSessionSummaryWithEmbedding(chatId, summary, topics,
|
|
3496
|
-
log(`[summarize] Saved
|
|
3497
|
-
clearLog(chatId);
|
|
3669
|
+
saveSessionSummaryWithEmbedding(chatId, summary, topics, Math.floor(entries.length / 2));
|
|
3670
|
+
log(`[summarize] Saved summary via ${adapter.id}:${model2} for chat ${chatId}`);
|
|
3498
3671
|
return true;
|
|
3499
3672
|
} catch (err) {
|
|
3500
|
-
warn(`[summarize]
|
|
3673
|
+
warn(`[summarize] ${adapter.id}:${model2} failed for ${chatId}: ${errorMessage(err)}`);
|
|
3501
3674
|
return false;
|
|
3502
3675
|
}
|
|
3503
3676
|
}
|
|
3677
|
+
async function summarizeWithFallbackChain(chatId, targetBackendId) {
|
|
3678
|
+
const pairCount = getMessagePairCount(chatId);
|
|
3679
|
+
if (pairCount < MIN_PAIRS) {
|
|
3680
|
+
clearLog(chatId);
|
|
3681
|
+
return false;
|
|
3682
|
+
}
|
|
3683
|
+
const entries = getLog(chatId);
|
|
3684
|
+
if (entries.length === 0) return false;
|
|
3685
|
+
const tried = /* @__PURE__ */ new Set();
|
|
3686
|
+
try {
|
|
3687
|
+
const config2 = getSummarizer(chatId);
|
|
3688
|
+
if (config2.backend !== "off") {
|
|
3689
|
+
const adapter = config2.backend ? getAdapter(config2.backend) : getAdapterForChat(chatId);
|
|
3690
|
+
const model2 = config2.model ?? adapter.summarizerModel;
|
|
3691
|
+
const key = `${adapter.id}:${model2}`;
|
|
3692
|
+
tried.add(key);
|
|
3693
|
+
if (await attemptSummarize(chatId, adapter, model2, entries)) {
|
|
3694
|
+
clearLog(chatId);
|
|
3695
|
+
return true;
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
} catch {
|
|
3699
|
+
}
|
|
3700
|
+
if (targetBackendId) {
|
|
3701
|
+
try {
|
|
3702
|
+
const targetAdapter = getAdapter(targetBackendId);
|
|
3703
|
+
const model2 = targetAdapter.summarizerModel;
|
|
3704
|
+
const key = `${targetAdapter.id}:${model2}`;
|
|
3705
|
+
if (!tried.has(key)) {
|
|
3706
|
+
tried.add(key);
|
|
3707
|
+
if (await attemptSummarize(chatId, targetAdapter, model2, entries)) {
|
|
3708
|
+
clearLog(chatId);
|
|
3709
|
+
return true;
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
} catch {
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
for (const adapter of getAllAdapters()) {
|
|
3716
|
+
const model2 = adapter.summarizerModel;
|
|
3717
|
+
const key = `${adapter.id}:${model2}`;
|
|
3718
|
+
if (!tried.has(key)) {
|
|
3719
|
+
tried.add(key);
|
|
3720
|
+
if (await attemptSummarize(chatId, adapter, model2, entries)) {
|
|
3721
|
+
clearLog(chatId);
|
|
3722
|
+
return true;
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
warn(`[summarize] All fallback attempts failed for chat ${chatId} \u2014 raw log preserved`);
|
|
3727
|
+
return false;
|
|
3728
|
+
}
|
|
3729
|
+
async function summarizeSession(chatId) {
|
|
3730
|
+
return summarizeWithFallbackChain(chatId);
|
|
3731
|
+
}
|
|
3504
3732
|
async function summarizeAllPending() {
|
|
3505
3733
|
const chatIds = getLoggedChatIds();
|
|
3506
3734
|
if (chatIds.length === 0) return;
|
|
@@ -3509,7 +3737,7 @@ async function summarizeAllPending() {
|
|
|
3509
3737
|
await summarizeSession(chatId);
|
|
3510
3738
|
}
|
|
3511
3739
|
}
|
|
3512
|
-
var MIN_PAIRS, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP, SUMMARIZE_PROMPT;
|
|
3740
|
+
var MIN_PAIRS, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP, SUMMARIZE_TIMEOUT_MS, SUMMARIZE_PROMPT, CONTEXT_BRIDGE_PROMPT;
|
|
3513
3741
|
var init_summarize = __esm({
|
|
3514
3742
|
"src/memory/summarize.ts"() {
|
|
3515
3743
|
"use strict";
|
|
@@ -3521,18 +3749,20 @@ var init_summarize = __esm({
|
|
|
3521
3749
|
USER_MSG_LIMIT = 4e3;
|
|
3522
3750
|
AGENT_MSG_LIMIT = 6e3;
|
|
3523
3751
|
TRANSCRIPT_CAP = 1e5;
|
|
3752
|
+
SUMMARIZE_TIMEOUT_MS = 6e4;
|
|
3524
3753
|
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.
|
|
3525
3754
|
|
|
3526
3755
|
Instructions:
|
|
3527
3756
|
1. SUMMARY: Write a detailed summary (5-15 sentences depending on session complexity). Capture:
|
|
3528
3757
|
- What the user asked for and why
|
|
3529
3758
|
- What was done, decided, or accomplished
|
|
3530
|
-
- Specific details that would be hard to rediscover: names, dates, values, locations, references, sources, outcomes
|
|
3531
|
-
-
|
|
3759
|
+
- Specific details that would be hard to rediscover: names, dates, values, locations, references, sources, decisions, outcomes \u2014 across any domain
|
|
3760
|
+
- The state at session end: what's done, what's in progress, what's planned next
|
|
3532
3761
|
- Any preferences, corrections, or instructions the user expressed
|
|
3533
3762
|
|
|
3534
3763
|
2. KEY_DETAILS: List the 3-8 most important specific facts as bullet points. These should be concrete and retrievable \u2014 not vague.
|
|
3535
3764
|
Good: "User's quarterly report deadline is March 15"
|
|
3765
|
+
Good: "Flight booked to NYC on April 3, confirmation #AB1234"
|
|
3536
3766
|
Bad: "Discussed deadlines"
|
|
3537
3767
|
|
|
3538
3768
|
3. TOPICS: Extract 5-10 specific topic keywords that would help retrieve this session in a future search. Use proper nouns, specific terms, and distinguishing phrases \u2014 NOT generic words like "discussion" or "help".
|
|
@@ -3543,6 +3773,14 @@ KEY_DETAILS:
|
|
|
3543
3773
|
- <detail 1>
|
|
3544
3774
|
- <detail 2>
|
|
3545
3775
|
TOPICS: <specific-keyword1, specific-keyword2, ...>`;
|
|
3776
|
+
CONTEXT_BRIDGE_PROMPT = `You are handing off an active work session to a new AI assistant. Write a concise handoff brief (3-8 sentences) covering:
|
|
3777
|
+
- What the user is actively working on RIGHT NOW
|
|
3778
|
+
- Exact current state: what's done, what's in progress, what's blocked
|
|
3779
|
+
- Specific details needed to continue: names, dates, decisions, references, links, preferences, or any domain-specific context relevant to the work
|
|
3780
|
+
- The immediate next step the assistant should be ready to take
|
|
3781
|
+
- Any critical constraints or preferences expressed by the user
|
|
3782
|
+
|
|
3783
|
+
Be specific. The new assistant has no prior context. Cover any domain \u2014 personal, research, scheduling, content, technical \u2014 whatever applies to this session.`;
|
|
3546
3784
|
}
|
|
3547
3785
|
});
|
|
3548
3786
|
|
|
@@ -3579,20 +3817,20 @@ var init_registry = __esm({
|
|
|
3579
3817
|
});
|
|
3580
3818
|
|
|
3581
3819
|
// src/agents/roles.ts
|
|
3582
|
-
function buildRoleInstructions(role, task, persona) {
|
|
3820
|
+
function buildRoleInstructions(role, task, persona, includeTools = true) {
|
|
3583
3821
|
const rolePrompt = persona ?? ROLE_PRESETS[role] ?? ROLE_PRESETS.worker;
|
|
3584
3822
|
const capitalizedRole = role.charAt(0).toUpperCase() + role.slice(1);
|
|
3585
|
-
|
|
3823
|
+
const parts = [
|
|
3586
3824
|
`## Your Role: ${capitalizedRole}`,
|
|
3587
3825
|
"",
|
|
3588
3826
|
rolePrompt,
|
|
3589
|
-
""
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3827
|
+
""
|
|
3828
|
+
];
|
|
3829
|
+
if (includeTools) {
|
|
3830
|
+
parts.push(ORCHESTRATOR_TOOLS, "");
|
|
3831
|
+
}
|
|
3832
|
+
parts.push("## Task", "", task);
|
|
3833
|
+
return parts.join("\n");
|
|
3596
3834
|
}
|
|
3597
3835
|
var ROLE_PRESETS, ORCHESTRATOR_TOOLS;
|
|
3598
3836
|
var init_roles = __esm({
|
|
@@ -3660,7 +3898,8 @@ function buildAgentPrompt(opts, runnerSkillPath) {
|
|
|
3660
3898
|
}
|
|
3661
3899
|
parts.push("");
|
|
3662
3900
|
}
|
|
3663
|
-
|
|
3901
|
+
const includeTools = opts.includeOrchestratorTools !== false;
|
|
3902
|
+
parts.push(buildRoleInstructions(opts.role ?? "worker", opts.task, opts.persona, includeTools));
|
|
3664
3903
|
return parts.join("\n");
|
|
3665
3904
|
}
|
|
3666
3905
|
function buildSpawnEnv(runner, isSubAgent = false) {
|
|
@@ -4243,7 +4482,8 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
4243
4482
|
role: opts.role,
|
|
4244
4483
|
persona: opts.persona,
|
|
4245
4484
|
extraArgs: mcpExtraArgs.length ? mcpExtraArgs : void 0,
|
|
4246
|
-
isSubAgent: true
|
|
4485
|
+
isSubAgent: true,
|
|
4486
|
+
includeOrchestratorTools: process.env.DASHBOARD_ENABLED === "1"
|
|
4247
4487
|
}, {
|
|
4248
4488
|
onText: () => {
|
|
4249
4489
|
updateAgentOutput(db3, agentId);
|
|
@@ -4458,7 +4698,7 @@ function startNextQueued(chatId) {
|
|
|
4458
4698
|
description: agent.description ?? void 0,
|
|
4459
4699
|
model: agent.model ?? void 0,
|
|
4460
4700
|
skills: JSON.parse(agent.skills),
|
|
4461
|
-
permMode: agent.permMode === "inherit" ?
|
|
4701
|
+
permMode: agent.permMode === "inherit" ? getMode(chatId) : agent.permMode,
|
|
4462
4702
|
role: agent.role ?? "worker",
|
|
4463
4703
|
persona: agent.persona ?? void 0,
|
|
4464
4704
|
maxRuntimeMs: agent.maxRuntimeMs,
|
|
@@ -4964,6 +5204,10 @@ function startDashboard() {
|
|
|
4964
5204
|
if (body.cwd && !existsSync8(body.cwd)) {
|
|
4965
5205
|
return jsonResponse(res, { error: `Directory not found: ${body.cwd}` }, 400);
|
|
4966
5206
|
}
|
|
5207
|
+
if (!body.permMode || body.permMode === "inherit") {
|
|
5208
|
+
const { getMode: getMode2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
5209
|
+
body.permMode = getMode2(body.chatId);
|
|
5210
|
+
}
|
|
4967
5211
|
const result = await spawnSubAgent(body.chatId, body);
|
|
4968
5212
|
return jsonResponse(res, result);
|
|
4969
5213
|
} catch (err) {
|
|
@@ -5216,7 +5460,10 @@ function startDashboard() {
|
|
|
5216
5460
|
const { getMode: getMode2, getCwd: getCwd2, getModel: getModel2, addUsage: addUsage2, getBackend: getBackend2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
5217
5461
|
const { getAdapterForChat: getAdapterForChat2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
5218
5462
|
const chatId = body.chatId;
|
|
5219
|
-
const
|
|
5463
|
+
const PERM_LEVEL = { plan: 0, safe: 1, yolo: 2 };
|
|
5464
|
+
const storedMode = getMode2(chatId);
|
|
5465
|
+
const requestedMode = body.mode ?? storedMode;
|
|
5466
|
+
const mode = (PERM_LEVEL[requestedMode] ?? 2) <= (PERM_LEVEL[storedMode] ?? 2) ? requestedMode : storedMode;
|
|
5220
5467
|
const cwd = body.cwd ?? getCwd2(chatId);
|
|
5221
5468
|
const model2 = body.model ?? getModel2(chatId) ?? (() => {
|
|
5222
5469
|
try {
|
|
@@ -5866,13 +6113,13 @@ function askAgent(chatId, userMessage, opts) {
|
|
|
5866
6113
|
return withChatLock(chatId, () => askAgentImpl(chatId, userMessage, opts));
|
|
5867
6114
|
}
|
|
5868
6115
|
async function askAgentImpl(chatId, userMessage, opts) {
|
|
5869
|
-
const { cwd, onStream, model: model2, permMode, onToolAction, bootstrapTier, timeoutMs } = opts ?? {};
|
|
5870
|
-
const adapter = getAdapterForChat(chatId);
|
|
5871
|
-
const mode = permMode ??
|
|
6116
|
+
const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs } = opts ?? {};
|
|
6117
|
+
const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(chatId);
|
|
6118
|
+
const mode = permMode ?? getMode(chatId);
|
|
5872
6119
|
const thinkingLevel = getThinkingLevel(chatId);
|
|
5873
6120
|
const resolvedCwd = cwd ?? WORKSPACE_PATH;
|
|
5874
6121
|
const tier = bootstrapTier ?? "full";
|
|
5875
|
-
const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, chatId);
|
|
6122
|
+
const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, chatId, mode);
|
|
5876
6123
|
const existingSessionId = getSessionId(chatId);
|
|
5877
6124
|
const allowedTools = getEnabledTools(chatId);
|
|
5878
6125
|
const mcpConfigPath = getMcpConfigPath(chatId);
|
|
@@ -5933,7 +6180,17 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
5933
6180
|
clearSession(chatId);
|
|
5934
6181
|
}
|
|
5935
6182
|
if (result.resultText) {
|
|
5936
|
-
appendToLog(chatId, userMessage, result.resultText);
|
|
6183
|
+
appendToLog(chatId, userMessage, result.resultText, adapter.id, model2 ?? null, result.sessionId ?? null);
|
|
6184
|
+
const AUTO_SUMMARIZE_THRESHOLD = 30;
|
|
6185
|
+
const pairCount = getMessagePairCount(chatId);
|
|
6186
|
+
if (pairCount >= AUTO_SUMMARIZE_THRESHOLD) {
|
|
6187
|
+
log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
|
|
6188
|
+
summarizeWithFallbackChain(chatId).then((saved) => {
|
|
6189
|
+
if (saved) opts?.onCompaction?.(chatId);
|
|
6190
|
+
}).catch((err) => {
|
|
6191
|
+
warn(`[agent] Auto-summarize failed for chat ${chatId}: ${err}`);
|
|
6192
|
+
});
|
|
6193
|
+
}
|
|
5937
6194
|
}
|
|
5938
6195
|
return {
|
|
5939
6196
|
text: result.resultText || `(No response from ${adapter.displayName})`,
|
|
@@ -6125,6 +6382,9 @@ var init_delivery = __esm({
|
|
|
6125
6382
|
// src/scheduler/retry.ts
|
|
6126
6383
|
function classifyError(err) {
|
|
6127
6384
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6385
|
+
for (const pattern of EXHAUSTED_PATTERNS) {
|
|
6386
|
+
if (pattern.test(msg)) return "exhausted";
|
|
6387
|
+
}
|
|
6128
6388
|
for (const pattern of TRANSIENT_PATTERNS) {
|
|
6129
6389
|
if (pattern.test(msg)) return "transient";
|
|
6130
6390
|
}
|
|
@@ -6138,10 +6398,21 @@ function getBackoffMs(retryCount) {
|
|
|
6138
6398
|
const jitter = Math.floor(base * 0.2 * (Math.random() * 2 - 1));
|
|
6139
6399
|
return base + jitter;
|
|
6140
6400
|
}
|
|
6141
|
-
var TRANSIENT_PATTERNS, PERMANENT_PATTERNS, BACKOFF_MS, MAX_RETRIES, AUTO_PAUSE_THRESHOLD;
|
|
6401
|
+
var EXHAUSTED_PATTERNS, TRANSIENT_PATTERNS, PERMANENT_PATTERNS, BACKOFF_MS, MAX_RETRIES, AUTO_PAUSE_THRESHOLD;
|
|
6142
6402
|
var init_retry = __esm({
|
|
6143
6403
|
"src/scheduler/retry.ts"() {
|
|
6144
6404
|
"use strict";
|
|
6405
|
+
EXHAUSTED_PATTERNS = [
|
|
6406
|
+
/out of.*usage/i,
|
|
6407
|
+
/\d+-hour limit reached/i,
|
|
6408
|
+
/usage limit reached/i,
|
|
6409
|
+
/hit your.*limit/i,
|
|
6410
|
+
/usage.?limit/i,
|
|
6411
|
+
/exceeded your current quota/i,
|
|
6412
|
+
/RESOURCE.?EXHAUSTED/i,
|
|
6413
|
+
/insufficient.?quota/i,
|
|
6414
|
+
/check your plan and billing/i
|
|
6415
|
+
];
|
|
6145
6416
|
TRANSIENT_PATTERNS = [
|
|
6146
6417
|
/rate.?limit/i,
|
|
6147
6418
|
/too many requests/i,
|
|
@@ -6458,23 +6729,50 @@ async function runWithRetry(job, model2, runId, t0) {
|
|
|
6458
6729
|
if (job.thinking && job.thinking !== "auto") {
|
|
6459
6730
|
setThinkingLevel(chatId, job.thinking);
|
|
6460
6731
|
}
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6732
|
+
const timeoutMs = job.timeout ? job.timeout * 1e3 : void 0;
|
|
6733
|
+
const primaryBackend = resolveJobBackendId(job);
|
|
6734
|
+
const chain = [
|
|
6735
|
+
{ backend: primaryBackend, model: model2 },
|
|
6736
|
+
...job.fallbacks ?? []
|
|
6737
|
+
];
|
|
6738
|
+
for (let chainIdx = 0; chainIdx < chain.length; chainIdx++) {
|
|
6739
|
+
const { backend: currentBackend, model: currentModel } = chain[chainIdx];
|
|
6740
|
+
const isFallback = chainIdx > 0;
|
|
6741
|
+
if (isFallback) {
|
|
6742
|
+
log(`[scheduler] Job #${job.id} falling back to ${currentBackend}:${currentModel} (fallback ${chainIdx}/${job.fallbacks.length})`);
|
|
6743
|
+
}
|
|
6744
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
6745
|
+
try {
|
|
6746
|
+
const response = await askAgent(chatId, job.description, {
|
|
6747
|
+
model: currentModel,
|
|
6748
|
+
backend: currentBackend,
|
|
6749
|
+
bootstrapTier: "slim",
|
|
6750
|
+
timeoutMs,
|
|
6751
|
+
permMode: job.sessionType === "main" ? getMode(job.chatId) : "yolo"
|
|
6752
|
+
});
|
|
6753
|
+
if (isFallback) {
|
|
6754
|
+
response.text = `[Fallback: ran on ${currentBackend}:${currentModel}]
|
|
6755
|
+
|
|
6756
|
+
${response.text}`;
|
|
6757
|
+
}
|
|
6758
|
+
return response;
|
|
6759
|
+
} catch (err) {
|
|
6760
|
+
lastError = err;
|
|
6761
|
+
const errorClass = classifyError(err);
|
|
6762
|
+
if (errorClass === "exhausted") {
|
|
6763
|
+
log(`[scheduler] Job #${job.id} backend ${currentBackend} exhausted: ${errorMessage(err)}`);
|
|
6764
|
+
break;
|
|
6765
|
+
}
|
|
6766
|
+
if (errorClass === "permanent" || attempt >= MAX_RETRIES) {
|
|
6767
|
+
throw err;
|
|
6768
|
+
}
|
|
6769
|
+
const backoffMs = getBackoffMs(attempt);
|
|
6770
|
+
log(`[scheduler] Job #${job.id} transient error (attempt ${attempt + 1}/${MAX_RETRIES}), retrying in ${backoffMs / 1e3}s: ${errorMessage(err)}`);
|
|
6771
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
6471
6772
|
}
|
|
6472
|
-
const backoffMs = getBackoffMs(attempt);
|
|
6473
|
-
log(`[scheduler] Job #${job.id} transient error (attempt ${attempt + 1}/${MAX_RETRIES}), retrying in ${backoffMs / 1e3}s: ${errorMessage(err)}`);
|
|
6474
|
-
await new Promise((r) => setTimeout(r, backoffMs));
|
|
6475
6773
|
}
|
|
6476
6774
|
}
|
|
6477
|
-
throw lastError ?? new Error("
|
|
6775
|
+
throw lastError ?? new Error("All backends exhausted");
|
|
6478
6776
|
}
|
|
6479
6777
|
function resolveJobBackendId(job) {
|
|
6480
6778
|
return job.backend ?? (() => {
|
|
@@ -6578,7 +6876,7 @@ function wrapBackendAdapter(adapter) {
|
|
|
6578
6876
|
const config2 = adapter.buildSpawnConfig({
|
|
6579
6877
|
prompt: opts.prompt,
|
|
6580
6878
|
sessionId: opts.sessionId,
|
|
6581
|
-
permMode: opts.permMode ?? "
|
|
6879
|
+
permMode: opts.permMode ?? "plan",
|
|
6582
6880
|
allowedTools: opts.allowedTools ?? [],
|
|
6583
6881
|
cwd: opts.cwd
|
|
6584
6882
|
});
|
|
@@ -7146,6 +7444,7 @@ var init_telegram2 = __esm({
|
|
|
7146
7444
|
// Skills & profile
|
|
7147
7445
|
{ command: "skills", description: "List and invoke skills" },
|
|
7148
7446
|
{ command: "voice", description: "Toggle voice responses" },
|
|
7447
|
+
{ command: "imagine", description: "Generate an image from a prompt" },
|
|
7149
7448
|
{ command: "heartbeat", description: "Configure proactive heartbeat" },
|
|
7150
7449
|
{ command: "chats", description: "Manage multi-chat aliases" }
|
|
7151
7450
|
]);
|
|
@@ -7791,7 +8090,7 @@ async function finalizeProfile(chatId, state, channel) {
|
|
|
7791
8090
|
"<!-- Add any additional preferences below this line -->",
|
|
7792
8091
|
""
|
|
7793
8092
|
].join("\n");
|
|
7794
|
-
writeFileSync4(
|
|
8093
|
+
writeFileSync4(USER_PATH2, content, "utf-8");
|
|
7795
8094
|
activeProfiles.delete(chatId);
|
|
7796
8095
|
log(`[profile] User profile saved for chat ${chatId}`);
|
|
7797
8096
|
await channel.sendText(
|
|
@@ -7817,23 +8116,23 @@ function extractUserUpdates(text) {
|
|
|
7817
8116
|
return { cleanText, updates };
|
|
7818
8117
|
}
|
|
7819
8118
|
function appendToUserProfile(key, value) {
|
|
7820
|
-
if (!existsSync11(
|
|
7821
|
-
const content = readFileSync7(
|
|
8119
|
+
if (!existsSync11(USER_PATH2)) return;
|
|
8120
|
+
const content = readFileSync7(USER_PATH2, "utf-8");
|
|
7822
8121
|
const line = `- **${key}**: ${value}`;
|
|
7823
8122
|
if (content.includes(line)) return;
|
|
7824
8123
|
const updated = content.trimEnd() + `
|
|
7825
8124
|
${line}
|
|
7826
8125
|
`;
|
|
7827
|
-
writeFileSync4(
|
|
8126
|
+
writeFileSync4(USER_PATH2, updated, "utf-8");
|
|
7828
8127
|
log(`[profile] Appended preference: ${key}=${value}`);
|
|
7829
8128
|
}
|
|
7830
|
-
var
|
|
8129
|
+
var USER_PATH2, activeProfiles;
|
|
7831
8130
|
var init_profile = __esm({
|
|
7832
8131
|
"src/bootstrap/profile.ts"() {
|
|
7833
8132
|
"use strict";
|
|
7834
8133
|
init_paths();
|
|
7835
8134
|
init_log();
|
|
7836
|
-
|
|
8135
|
+
USER_PATH2 = join12(WORKSPACE_PATH, "USER.md");
|
|
7837
8136
|
activeProfiles = /* @__PURE__ */ new Map();
|
|
7838
8137
|
}
|
|
7839
8138
|
});
|
|
@@ -8024,14 +8323,6 @@ var init_heartbeat = __esm({
|
|
|
8024
8323
|
});
|
|
8025
8324
|
|
|
8026
8325
|
// src/format-time.ts
|
|
8027
|
-
function formatLocalDate(utcDatetime) {
|
|
8028
|
-
const d = parseUtcDatetime(utcDatetime);
|
|
8029
|
-
if (!d) return utcDatetime.split("T")[0] ?? utcDatetime.split(" ")[0];
|
|
8030
|
-
const year = d.getFullYear();
|
|
8031
|
-
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
8032
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
8033
|
-
return `${year}-${month}-${day}`;
|
|
8034
|
-
}
|
|
8035
8326
|
function formatLocalDateTime(utcDatetime) {
|
|
8036
8327
|
const d = parseUtcDatetime(utcDatetime);
|
|
8037
8328
|
if (!d) return utcDatetime;
|
|
@@ -8069,6 +8360,83 @@ var init_format_time = __esm({
|
|
|
8069
8360
|
}
|
|
8070
8361
|
});
|
|
8071
8362
|
|
|
8363
|
+
// src/media/image-gen.ts
|
|
8364
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync13 } from "fs";
|
|
8365
|
+
import { join as join14 } from "path";
|
|
8366
|
+
async function generateImage(prompt) {
|
|
8367
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
8368
|
+
if (!apiKey) {
|
|
8369
|
+
throw new Error("Image generation requires GEMINI_API_KEY. Configure it in ~/.cc-claw/.env");
|
|
8370
|
+
}
|
|
8371
|
+
log(`[image-gen] Generating image: "${prompt.slice(0, 100)}"`);
|
|
8372
|
+
const response = await fetch(
|
|
8373
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${IMAGE_MODEL}:generateContent?key=${apiKey}`,
|
|
8374
|
+
{
|
|
8375
|
+
method: "POST",
|
|
8376
|
+
headers: { "Content-Type": "application/json" },
|
|
8377
|
+
body: JSON.stringify({
|
|
8378
|
+
contents: [
|
|
8379
|
+
{
|
|
8380
|
+
parts: [{ text: prompt }]
|
|
8381
|
+
}
|
|
8382
|
+
],
|
|
8383
|
+
generationConfig: {
|
|
8384
|
+
responseModalities: ["TEXT", "IMAGE"]
|
|
8385
|
+
}
|
|
8386
|
+
})
|
|
8387
|
+
}
|
|
8388
|
+
);
|
|
8389
|
+
if (!response.ok) {
|
|
8390
|
+
const errText = await response.text();
|
|
8391
|
+
throw new Error(`Gemini image API error: ${response.status} ${errText.slice(0, 300)}`);
|
|
8392
|
+
}
|
|
8393
|
+
const data = await response.json();
|
|
8394
|
+
const parts = data.candidates?.[0]?.content?.parts;
|
|
8395
|
+
if (!parts || parts.length === 0) {
|
|
8396
|
+
throw new Error("Gemini returned no content for image generation");
|
|
8397
|
+
}
|
|
8398
|
+
let imageData = null;
|
|
8399
|
+
let mimeType = "image/png";
|
|
8400
|
+
let textResponse = null;
|
|
8401
|
+
for (const part of parts) {
|
|
8402
|
+
if (part.inlineData) {
|
|
8403
|
+
imageData = part.inlineData.data;
|
|
8404
|
+
mimeType = part.inlineData.mimeType ?? "image/png";
|
|
8405
|
+
} else if (part.text) {
|
|
8406
|
+
textResponse = part.text;
|
|
8407
|
+
}
|
|
8408
|
+
}
|
|
8409
|
+
if (!imageData) {
|
|
8410
|
+
throw new Error(textResponse ?? "Gemini did not generate an image. The prompt may have been filtered.");
|
|
8411
|
+
}
|
|
8412
|
+
if (!existsSync13(IMAGE_OUTPUT_DIR)) {
|
|
8413
|
+
mkdirSync5(IMAGE_OUTPUT_DIR, { recursive: true });
|
|
8414
|
+
}
|
|
8415
|
+
const ext = mimeType.includes("jpeg") || mimeType.includes("jpg") ? "jpg" : "png";
|
|
8416
|
+
const filename = `img_${Date.now()}.${ext}`;
|
|
8417
|
+
const filePath = join14(IMAGE_OUTPUT_DIR, filename);
|
|
8418
|
+
const buffer = Buffer.from(imageData, "base64");
|
|
8419
|
+
writeFileSync5(filePath, buffer);
|
|
8420
|
+
log(`[image-gen] Saved ${buffer.length} bytes to ${filePath}`);
|
|
8421
|
+
return { filePath, text: textResponse, mimeType };
|
|
8422
|
+
}
|
|
8423
|
+
function isImageGenAvailable() {
|
|
8424
|
+
return !!process.env.GEMINI_API_KEY;
|
|
8425
|
+
}
|
|
8426
|
+
var IMAGE_MODEL, IMAGE_OUTPUT_DIR;
|
|
8427
|
+
var init_image_gen = __esm({
|
|
8428
|
+
"src/media/image-gen.ts"() {
|
|
8429
|
+
"use strict";
|
|
8430
|
+
init_log();
|
|
8431
|
+
IMAGE_MODEL = "gemini-3.1-flash-image-preview";
|
|
8432
|
+
IMAGE_OUTPUT_DIR = join14(
|
|
8433
|
+
process.env.CC_CLAW_HOME ?? join14(process.env.HOME ?? "/tmp", ".cc-claw"),
|
|
8434
|
+
"data",
|
|
8435
|
+
"images"
|
|
8436
|
+
);
|
|
8437
|
+
}
|
|
8438
|
+
});
|
|
8439
|
+
|
|
8072
8440
|
// src/voice/stt.ts
|
|
8073
8441
|
import crypto from "crypto";
|
|
8074
8442
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -9137,7 +9505,7 @@ async function handleCommand(msg, channel) {
|
|
|
9137
9505
|
case "help":
|
|
9138
9506
|
await channel.sendText(
|
|
9139
9507
|
chatId,
|
|
9140
|
-
"Hey! I'm CC-Claw \u2014 your personal AI assistant on Telegram.\n\nI use AI coding CLIs (Claude, Gemini, Codex) as my brain. Just send me a message to get started.\n\nCommands:\n/backend [name] - Switch AI backend (or /claude /gemini /codex)\n/model - Switch model for active backend\n/summarizer - Configure session summarization model\n/status - Show session, model, backend, and usage\n/cost - Show estimated API cost (use /cost all for all-time)\n/usage - Show usage per backend with limits\n/limits - Configure usage limits per backend\n/newchat - Start a fresh conversation\n/summarize - Save session to memory (without resetting)\n/summarize all - Summarize all pending sessions (pre-restart)\n/cwd <path> - Set working directory\n/cwd - Show current working directory\n/memory - List stored memories\n/forget <keyword> - Remove a memory\n/voice - Toggle voice responses\n/cron <description> - Schedule a task (or /schedule)\n/cron - List scheduled jobs (or /jobs)\n/cron cancel <id> - Cancel a job\n/cron pause <id> - Pause a job\n/cron resume <id> - Resume a job\n/cron run <id> - Trigger a job now\n/cron runs [id] - View run history\n/cron edit <id> - Edit a job\n/cron health - Scheduler health\n/skills - List skills from all backends\n/skill-install <url> - Install a skill from GitHub\n/setup-profile - Set up your user profile\n/chats - List authorized chats and aliases\n/heartbeat - Proactive awareness (on/off/interval/hours)\n/history - List recent session summaries\n/stop - Cancel the current running task\n/tools - Configure which tools the agent can use\n/permissions - Switch permission mode (yolo/safe/
|
|
9508
|
+
"Hey! I'm CC-Claw \u2014 your personal AI assistant on Telegram.\n\nI use AI coding CLIs (Claude, Gemini, Codex) as my brain. Just send me a message to get started.\n\nCommands:\n/backend [name] - Switch AI backend (or /claude /gemini /codex)\n/model - Switch model for active backend\n/summarizer - Configure session summarization model\n/status - Show session, model, backend, and usage\n/cost - Show estimated API cost (use /cost all for all-time)\n/usage - Show usage per backend with limits\n/limits - Configure usage limits per backend\n/newchat - Start a fresh conversation\n/summarize - Save session to memory (without resetting)\n/summarize all - Summarize all pending sessions (pre-restart)\n/cwd <path> - Set working directory\n/cwd - Show current working directory\n/memory - List stored memories\n/forget <keyword> - Remove a memory\n/voice - Toggle voice responses\n/imagine <prompt> - Generate an image (or /image)\n/cron <description> - Schedule a task (or /schedule)\n/cron - List scheduled jobs (or /jobs)\n/cron cancel <id> - Cancel a job\n/cron pause <id> - Pause a job\n/cron resume <id> - Resume a job\n/cron run <id> - Trigger a job now\n/cron runs [id] - View run history\n/cron edit <id> - Edit a job\n/cron health - Scheduler health\n/skills - List skills from all backends\n/skill-install <url> - Install a skill from GitHub\n/setup-profile - Set up your user profile\n/chats - List authorized chats and aliases\n/heartbeat - Proactive awareness (on/off/interval/hours)\n/history - List recent session summaries\n/stop - Cancel the current running task\n/tools - Configure which tools the agent can use\n/permissions - Switch permission mode (yolo/safe/plan)\n/verbose - Tool visibility (off/normal/verbose)\n/agents - List active sub-agents\n/tasks - Show task board for current orchestration\n/stopagent <id> - Cancel a specific sub-agent\n/stopall - Cancel all sub-agents in this chat\n/runners - List registered CLI runners\n/mcps - List registered MCP servers\n/help - Show this message",
|
|
9141
9509
|
"plain"
|
|
9142
9510
|
);
|
|
9143
9511
|
break;
|
|
@@ -9197,7 +9565,7 @@ async function handleCommand(msg, channel) {
|
|
|
9197
9565
|
toolButtons.push(row);
|
|
9198
9566
|
}
|
|
9199
9567
|
toolButtons.push([{ label: "Reset to defaults (all on)", data: "tool:reset" }]);
|
|
9200
|
-
const modeNote = currentMode === "
|
|
9568
|
+
const modeNote = currentMode === "plan" ? "\n\nNote: In plan mode, tool list is ignored (read-only)." : currentMode === "yolo" ? "\n\nNote: In YOLO mode, tool list is ignored (all tools allowed)." : "";
|
|
9201
9569
|
await channel.sendKeyboard(
|
|
9202
9570
|
chatId,
|
|
9203
9571
|
`Configure allowed tools (mode: ${currentMode})${modeNote}
|
|
@@ -9233,7 +9601,15 @@ Tap to toggle:`,
|
|
|
9233
9601
|
} else {
|
|
9234
9602
|
const pairs = getMessagePairCount(chatId);
|
|
9235
9603
|
if (pairs < 2) {
|
|
9236
|
-
|
|
9604
|
+
if (getSessionId(chatId)) {
|
|
9605
|
+
await channel.sendText(
|
|
9606
|
+
chatId,
|
|
9607
|
+
"Session log was cleared (auto-compact or service restart). Your conversation history is preserved in episodic memory. Continue chatting normally, or use /newchat to start fresh.",
|
|
9608
|
+
"plain"
|
|
9609
|
+
);
|
|
9610
|
+
} else {
|
|
9611
|
+
await channel.sendText(chatId, "Not enough conversation to summarize (need at least 2 exchanges).", "plain");
|
|
9612
|
+
}
|
|
9237
9613
|
break;
|
|
9238
9614
|
}
|
|
9239
9615
|
await channel.sendText(chatId, `Summarizing current session (${pairs} exchanges)...`, "plain");
|
|
@@ -9620,6 +9996,30 @@ ${lines.join("\n")}`, "plain");
|
|
|
9620
9996
|
);
|
|
9621
9997
|
break;
|
|
9622
9998
|
}
|
|
9999
|
+
case "imagine":
|
|
10000
|
+
case "image": {
|
|
10001
|
+
if (!commandArgs) {
|
|
10002
|
+
await channel.sendText(chatId, "Usage: /imagine <prompt>\nExample: /imagine a cat astronaut on Mars", "plain");
|
|
10003
|
+
return;
|
|
10004
|
+
}
|
|
10005
|
+
if (!isImageGenAvailable()) {
|
|
10006
|
+
await channel.sendText(chatId, "Image generation requires GEMINI_API_KEY. Configure it in ~/.cc-claw/.env", "plain");
|
|
10007
|
+
return;
|
|
10008
|
+
}
|
|
10009
|
+
await channel.sendText(chatId, "\u{1F3A8} Generating image\u2026", "plain");
|
|
10010
|
+
try {
|
|
10011
|
+
const result = await generateImage(commandArgs);
|
|
10012
|
+
const file = await readFile5(result.filePath);
|
|
10013
|
+
const name = result.filePath.split("/").pop() ?? "image.png";
|
|
10014
|
+
await channel.sendFile(chatId, file, name);
|
|
10015
|
+
if (result.text) {
|
|
10016
|
+
await channel.sendText(chatId, result.text, "plain");
|
|
10017
|
+
}
|
|
10018
|
+
} catch (err) {
|
|
10019
|
+
await channel.sendText(chatId, `Image generation failed: ${errorMessage(err)}`, "plain");
|
|
10020
|
+
}
|
|
10021
|
+
break;
|
|
10022
|
+
}
|
|
9623
10023
|
case "schedule": {
|
|
9624
10024
|
if (!commandArgs) {
|
|
9625
10025
|
await channel.sendText(
|
|
@@ -9742,20 +10142,38 @@ ${lines.join("\n")}`, "plain");
|
|
|
9742
10142
|
break;
|
|
9743
10143
|
}
|
|
9744
10144
|
case "history": {
|
|
9745
|
-
const
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
10145
|
+
const query = commandArgs?.trim() ?? "";
|
|
10146
|
+
if (query) {
|
|
10147
|
+
const results = searchMessageLog(chatId, query, 20);
|
|
10148
|
+
if (results.length === 0) {
|
|
10149
|
+
await channel.sendText(chatId, `No matching history found. Try /memories ${query} for older sessions.`, "plain");
|
|
10150
|
+
break;
|
|
10151
|
+
}
|
|
10152
|
+
const lines = [`\u{1F4CB} History: "${query}"`, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"];
|
|
10153
|
+
for (const row of results.slice(0, 10)) {
|
|
10154
|
+
const roleLabel = row.role === "user" ? "You" : "Assistant";
|
|
10155
|
+
const date = row.created_at.slice(0, 10);
|
|
10156
|
+
const preview = row.content.slice(0, 300) + (row.content.length > 300 ? "\u2026" : "");
|
|
10157
|
+
lines.push(`[${row.backend ?? "?"} \xB7 ${date}] ${roleLabel}: ${preview}`);
|
|
10158
|
+
}
|
|
10159
|
+
lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
10160
|
+
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
10161
|
+
} else {
|
|
10162
|
+
const rows = getRecentMessageLog(chatId, 20).reverse();
|
|
10163
|
+
if (rows.length === 0) {
|
|
10164
|
+
await channel.sendText(chatId, "No conversation history found.", "plain");
|
|
10165
|
+
break;
|
|
10166
|
+
}
|
|
10167
|
+
const lines = ["\u{1F4CB} Recent conversation (last 10 exchanges):", "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"];
|
|
10168
|
+
for (const row of rows) {
|
|
10169
|
+
const roleLabel = row.role === "user" ? "You" : "Assistant";
|
|
10170
|
+
const date = row.created_at.slice(0, 10);
|
|
10171
|
+
const preview = row.content.slice(0, 300) + (row.content.length > 300 ? "\u2026" : "");
|
|
10172
|
+
lines.push(`[${row.backend ?? "?"} \xB7 ${date}] ${roleLabel}: ${preview}`);
|
|
10173
|
+
}
|
|
10174
|
+
lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
10175
|
+
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
9750
10176
|
}
|
|
9751
|
-
const lines = summaries.map((s) => {
|
|
9752
|
-
const date = formatLocalDate(s.created_at);
|
|
9753
|
-
const shortSummary = s.summary.length > 200 ? s.summary.slice(0, 200) + "\u2026" : s.summary;
|
|
9754
|
-
return `${date} (${s.message_count} msgs)
|
|
9755
|
-
${shortSummary}
|
|
9756
|
-
Topics: ${s.topics}`;
|
|
9757
|
-
});
|
|
9758
|
-
await channel.sendText(chatId, lines.join("\n\n"), "plain");
|
|
9759
10177
|
break;
|
|
9760
10178
|
}
|
|
9761
10179
|
case "skills": {
|
|
@@ -9876,8 +10294,8 @@ Use /skills to see it.`, "plain");
|
|
|
9876
10294
|
if (!lim.max_input_tokens) continue;
|
|
9877
10295
|
const u = getBackendUsageInWindow(lim.backend, lim.window);
|
|
9878
10296
|
const pct = (u.input_tokens / lim.max_input_tokens * 100).toFixed(0);
|
|
9879
|
-
const
|
|
9880
|
-
lines.push(` ${lim.backend} (${lim.window}): ${pct}% of ${(lim.max_input_tokens / 1e3).toFixed(0)}K${
|
|
10297
|
+
const warn3 = u.input_tokens / lim.max_input_tokens >= lim.warn_pct;
|
|
10298
|
+
lines.push(` ${lim.backend} (${lim.window}): ${pct}% of ${(lim.max_input_tokens / 1e3).toFixed(0)}K${warn3 ? " \u26A0\uFE0F" : ""}`);
|
|
9881
10299
|
}
|
|
9882
10300
|
}
|
|
9883
10301
|
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
@@ -10376,8 +10794,17 @@ async function handleText(msg, channel) {
|
|
|
10376
10794
|
await channel.sendText(chatId, limitMsg, "plain");
|
|
10377
10795
|
return;
|
|
10378
10796
|
}
|
|
10379
|
-
if (isChatBusy(chatId)) {
|
|
10380
|
-
|
|
10797
|
+
if (isChatBusy(chatId) && !bypassBusyCheck.delete(chatId)) {
|
|
10798
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10799
|
+
pendingInterrupts.set(chatId, { msg, channel });
|
|
10800
|
+
await channel.sendKeyboard(chatId, "\u23F3 Agent is working on a request\u2026", [
|
|
10801
|
+
[
|
|
10802
|
+
{ label: "\u{1F4E5} Queue message", data: `interrupt:queue:${chatId}` },
|
|
10803
|
+
{ label: "\u26A1 Send now", data: `interrupt:now:${chatId}` }
|
|
10804
|
+
]
|
|
10805
|
+
]);
|
|
10806
|
+
return;
|
|
10807
|
+
}
|
|
10381
10808
|
}
|
|
10382
10809
|
let typingActive = true;
|
|
10383
10810
|
const typingLoop = async () => {
|
|
@@ -10395,12 +10822,57 @@ async function handleText(msg, channel) {
|
|
|
10395
10822
|
const tMode = getMode(chatId);
|
|
10396
10823
|
const tVerbose = getVerboseLevel(chatId);
|
|
10397
10824
|
const tToolCb = tVerbose !== "off" ? makeToolActionCallback(chatId, channel, tVerbose) : void 0;
|
|
10398
|
-
const response = await askAgent(chatId, text, {
|
|
10825
|
+
const response = await askAgent(chatId, text, {
|
|
10826
|
+
cwd: getCwd(chatId),
|
|
10827
|
+
model: model2,
|
|
10828
|
+
permMode: tMode,
|
|
10829
|
+
onToolAction: tToolCb,
|
|
10830
|
+
onCompaction: (cid) => {
|
|
10831
|
+
channel.sendText(cid, "\u{1F4BE} Context saved to memory.").catch(() => {
|
|
10832
|
+
});
|
|
10833
|
+
}
|
|
10834
|
+
});
|
|
10399
10835
|
if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, model2);
|
|
10836
|
+
if (response.text.includes("[NEED_PERMISSION]") && tMode !== "yolo") {
|
|
10837
|
+
const cleanText = response.text.replace(/\[NEED_PERMISSION\]/g, "").trim();
|
|
10838
|
+
await sendResponse(chatId, channel, cleanText, msg.messageId);
|
|
10839
|
+
const targetMode = determineEscalationTarget(chatId, tMode);
|
|
10840
|
+
storePendingEscalation(chatId, text);
|
|
10841
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10842
|
+
await channel.sendKeyboard(chatId, `Switch to ${targetMode} mode?`, [
|
|
10843
|
+
[{ label: "Allow", data: `perm:escalate:${targetMode}` }, { label: "Deny", data: "perm:deny" }]
|
|
10844
|
+
]);
|
|
10845
|
+
}
|
|
10846
|
+
return;
|
|
10847
|
+
}
|
|
10400
10848
|
await sendResponse(chatId, channel, response.text, msg.messageId);
|
|
10401
10849
|
} catch (err) {
|
|
10402
10850
|
error("[router] Error:", err);
|
|
10403
10851
|
const errMsg = errorMessage(err);
|
|
10852
|
+
const errorClass = classifyError(err);
|
|
10853
|
+
if (errorClass === "exhausted" && typeof channel.sendKeyboard === "function") {
|
|
10854
|
+
const currentBackend = getBackend(chatId) ?? "claude";
|
|
10855
|
+
const { getAllAdapters: getAllAdapters2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
10856
|
+
const otherBackends = getAllAdapters2().filter((a) => a.id !== currentBackend);
|
|
10857
|
+
if (otherBackends.length > 0) {
|
|
10858
|
+
pendingFallbackMessages.set(chatId, { msg, channel });
|
|
10859
|
+
const buttons = otherBackends.map((a) => ({
|
|
10860
|
+
label: `\u{1F504} ${a.displayName}`,
|
|
10861
|
+
data: `fallback:${a.id}:${chatId}`
|
|
10862
|
+
}));
|
|
10863
|
+
buttons.push({ label: "\u274C Wait", data: `fallback:wait:${chatId}` });
|
|
10864
|
+
await channel.sendKeyboard(
|
|
10865
|
+
chatId,
|
|
10866
|
+
`\u26A0\uFE0F ${getAdapter(currentBackend).displayName} is out of usage.
|
|
10867
|
+
|
|
10868
|
+
${errMsg}
|
|
10869
|
+
|
|
10870
|
+
Switch to another backend?`,
|
|
10871
|
+
[buttons]
|
|
10872
|
+
);
|
|
10873
|
+
return;
|
|
10874
|
+
}
|
|
10875
|
+
}
|
|
10404
10876
|
const userMsg = diagnoseAgentError(errMsg, chatId);
|
|
10405
10877
|
await channel.sendText(chatId, userMsg, "plain");
|
|
10406
10878
|
} finally {
|
|
@@ -10505,16 +10977,48 @@ async function processFileSends2(chatId, channel, text) {
|
|
|
10505
10977
|
}
|
|
10506
10978
|
return text.replace(fileSendPattern, "").trim();
|
|
10507
10979
|
}
|
|
10980
|
+
async function processImageGenerations(chatId, channel, text) {
|
|
10981
|
+
const pattern = /\[GENERATE_IMAGE:(.+?)\]/g;
|
|
10982
|
+
const prompts = [];
|
|
10983
|
+
for (const match of text.matchAll(pattern)) {
|
|
10984
|
+
prompts.push(match[1].trim());
|
|
10985
|
+
}
|
|
10986
|
+
if (prompts.length === 0) return text;
|
|
10987
|
+
if (!isImageGenAvailable()) {
|
|
10988
|
+
log("[router] [GENERATE_IMAGE] marker found but GEMINI_API_KEY not set");
|
|
10989
|
+
return text.replace(pattern, "(Image generation unavailable \u2014 GEMINI_API_KEY not configured)").trim();
|
|
10990
|
+
}
|
|
10991
|
+
for (const prompt of prompts) {
|
|
10992
|
+
try {
|
|
10993
|
+
const result = await generateImage(prompt);
|
|
10994
|
+
const file = await readFile5(result.filePath);
|
|
10995
|
+
const name = result.filePath.split("/").pop() ?? "image.png";
|
|
10996
|
+
await channel.sendFile(chatId, file, name);
|
|
10997
|
+
if (result.text) {
|
|
10998
|
+
await channel.sendText(chatId, result.text, "plain");
|
|
10999
|
+
}
|
|
11000
|
+
} catch (err) {
|
|
11001
|
+
error(`[router] Image generation failed for "${prompt.slice(0, 50)}":`, err);
|
|
11002
|
+
await channel.sendText(chatId, `Image generation failed: ${errorMessage(err)}`, "plain");
|
|
11003
|
+
}
|
|
11004
|
+
}
|
|
11005
|
+
return text.replace(pattern, "").trim();
|
|
11006
|
+
}
|
|
10508
11007
|
async function processReaction(chatId, channel, text, messageId) {
|
|
10509
|
-
const
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
11008
|
+
const reactPatternGlobal = /\[REACT:(.+?)\]/g;
|
|
11009
|
+
if (!reactPatternGlobal.test(text)) return text;
|
|
11010
|
+
let reacted = false;
|
|
11011
|
+
reactPatternGlobal.lastIndex = 0;
|
|
11012
|
+
let match;
|
|
11013
|
+
while ((match = reactPatternGlobal.exec(text)) !== null) {
|
|
11014
|
+
const emoji = match[1].trim();
|
|
11015
|
+
if (!reacted && messageId && typeof channel.reactToMessage === "function" && ALLOWED_REACTION_EMOJIS.has(emoji)) {
|
|
11016
|
+
channel.reactToMessage(chatId, messageId, emoji).catch(() => {
|
|
11017
|
+
});
|
|
11018
|
+
reacted = true;
|
|
11019
|
+
}
|
|
10516
11020
|
}
|
|
10517
|
-
return text.replace(
|
|
11021
|
+
return text.replace(/\[REACT:(.+?)\]/g, "").trim();
|
|
10518
11022
|
}
|
|
10519
11023
|
async function sendResponse(chatId, channel, text, messageId) {
|
|
10520
11024
|
text = await processReaction(chatId, channel, text, messageId);
|
|
@@ -10526,7 +11030,29 @@ async function sendResponse(chatId, channel, text, messageId) {
|
|
|
10526
11030
|
]);
|
|
10527
11031
|
}
|
|
10528
11032
|
}
|
|
10529
|
-
|
|
11033
|
+
let afterHistory = afterUpdates;
|
|
11034
|
+
const historySearchMatch = afterHistory.match(/\[HISTORY_SEARCH:([^\]]+)\]/);
|
|
11035
|
+
if (historySearchMatch) {
|
|
11036
|
+
afterHistory = afterHistory.replace(/\[HISTORY_SEARCH:[^\]]+\]/g, "").trim();
|
|
11037
|
+
const hsQuery = historySearchMatch[1].trim();
|
|
11038
|
+
const hsResults = searchMessageLog(chatId, hsQuery, 10);
|
|
11039
|
+
if (hsResults.length > 0) {
|
|
11040
|
+
const hsLines = [`\u{1F4CB} History: "${hsQuery}"`, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"];
|
|
11041
|
+
for (const row of hsResults.slice(0, 8)) {
|
|
11042
|
+
const label2 = row.role === "user" ? "You" : "Assistant";
|
|
11043
|
+
const date = row.created_at.slice(0, 10);
|
|
11044
|
+
hsLines.push(`[${row.backend ?? "?"} \xB7 ${date}] ${label2}: ${row.content.slice(0, 250)}${row.content.length > 250 ? "\u2026" : ""}`);
|
|
11045
|
+
}
|
|
11046
|
+
hsLines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
11047
|
+
channel.sendText(chatId, hsLines.join("\n")).catch(() => {
|
|
11048
|
+
});
|
|
11049
|
+
} else {
|
|
11050
|
+
channel.sendText(chatId, `No history found for "${hsQuery}". Try /memories ${hsQuery} for older sessions.`).catch(() => {
|
|
11051
|
+
});
|
|
11052
|
+
}
|
|
11053
|
+
}
|
|
11054
|
+
const afterFiles = await processFileSends2(chatId, channel, afterHistory);
|
|
11055
|
+
const cleanText = await processImageGenerations(chatId, channel, afterFiles);
|
|
10530
11056
|
if (!cleanText) return;
|
|
10531
11057
|
if (isVoiceEnabled(chatId)) {
|
|
10532
11058
|
try {
|
|
@@ -10551,16 +11077,17 @@ async function sendBackendSwitchConfirmation(chatId, target, channel) {
|
|
|
10551
11077
|
await channel.sendText(chatId, `Already using ${targetAdapter.displayName}.`, "plain");
|
|
10552
11078
|
return;
|
|
10553
11079
|
}
|
|
10554
|
-
const currentLabel = current ? getAdapter(current).displayName : "current backend";
|
|
10555
11080
|
if (typeof channel.sendKeyboard === "function") {
|
|
10556
11081
|
await channel.sendKeyboard(
|
|
10557
11082
|
chatId,
|
|
10558
|
-
`\
|
|
10559
|
-
|
|
10560
|
-
What would you like to do?`,
|
|
11083
|
+
`\u{1F504} Switch to ${targetAdapter.displayName}?
|
|
11084
|
+
Your conversation history is preserved. ${targetAdapter.displayName} will receive a summary of recent context and can access your full history on request.`,
|
|
10561
11085
|
[
|
|
10562
|
-
[
|
|
10563
|
-
|
|
11086
|
+
[
|
|
11087
|
+
{ label: `Switch to ${targetAdapter.displayName}`, data: `backend_confirm:${target}` },
|
|
11088
|
+
{ label: `Switch + Clear History`, data: `backend_confirm_clear:${target}` }
|
|
11089
|
+
],
|
|
11090
|
+
[{ label: "Cancel", data: `backend_cancel:${target}` }]
|
|
10564
11091
|
]
|
|
10565
11092
|
);
|
|
10566
11093
|
} else {
|
|
@@ -10568,21 +11095,21 @@ What would you like to do?`,
|
|
|
10568
11095
|
}
|
|
10569
11096
|
}
|
|
10570
11097
|
async function doBackendSwitch(chatId, backendId, channel) {
|
|
10571
|
-
|
|
10572
|
-
|
|
11098
|
+
const targetAdapter = getAdapter(backendId);
|
|
11099
|
+
const summarized = await summarizeWithFallbackChain(chatId, backendId);
|
|
11100
|
+
if (summarized) {
|
|
11101
|
+
await channel.sendText(chatId, `\u{1F4BE} Context saved \u2014 ${targetAdapter.displayName} summarized your session.`, "plain");
|
|
11102
|
+
}
|
|
11103
|
+
const bridge = buildContextBridge(chatId);
|
|
11104
|
+
if (bridge) {
|
|
11105
|
+
setPendingContextBridge(chatId, bridge);
|
|
11106
|
+
}
|
|
10573
11107
|
clearSession(chatId);
|
|
10574
11108
|
clearModel(chatId);
|
|
10575
11109
|
clearThinkingLevel(chatId);
|
|
10576
11110
|
setBackend(chatId, backendId);
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
await channel.sendText(
|
|
10580
|
-
chatId,
|
|
10581
|
-
`Backend switched to ${adapter.displayName}.
|
|
10582
|
-
Default model: ${adapter.defaultModel}
|
|
10583
|
-
Session reset. Ready!`,
|
|
10584
|
-
"plain"
|
|
10585
|
-
);
|
|
11111
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Backend switched to ${targetAdapter.displayName}`, detail: { field: "backend", value: backendId } });
|
|
11112
|
+
await channel.sendText(chatId, `\u2705 Switched to ${targetAdapter.displayName}. Ready!`, "plain");
|
|
10586
11113
|
}
|
|
10587
11114
|
async function handleCallback(chatId, data, channel) {
|
|
10588
11115
|
if (data.startsWith("backend:")) {
|
|
@@ -10595,14 +11122,17 @@ async function handleCallback(chatId, data, channel) {
|
|
|
10595
11122
|
return;
|
|
10596
11123
|
}
|
|
10597
11124
|
await sendBackendSwitchConfirmation(chatId, chosen, channel);
|
|
11125
|
+
} else if (data.startsWith("backend_confirm_clear:")) {
|
|
11126
|
+
const target = data.slice(21);
|
|
11127
|
+
if (!getAllBackendIds().includes(target)) return;
|
|
11128
|
+
clearMessageLog(chatId);
|
|
11129
|
+
await doBackendSwitch(chatId, target, channel);
|
|
10598
11130
|
} else if (data.startsWith("backend_confirm:")) {
|
|
10599
11131
|
const chosen = data.slice(16);
|
|
10600
11132
|
if (!getAllBackendIds().includes(chosen)) return;
|
|
10601
11133
|
await doBackendSwitch(chatId, chosen, channel);
|
|
10602
|
-
} else if (data === "backend_cancel") {
|
|
10603
|
-
|
|
10604
|
-
const label2 = current ? getAdapter(current).displayName : "current backend";
|
|
10605
|
-
await channel.sendText(chatId, `No change. Staying on ${label2}.`, "plain");
|
|
11134
|
+
} else if (data.startsWith("backend_cancel:") || data === "backend_cancel") {
|
|
11135
|
+
await channel.sendText(chatId, "Switch cancelled.", "plain");
|
|
10606
11136
|
} else if (data.startsWith("model:")) {
|
|
10607
11137
|
const chosen = data.slice(6);
|
|
10608
11138
|
let adapter;
|
|
@@ -10657,7 +11187,8 @@ Select thinking/effort level:`,
|
|
|
10657
11187
|
await channel.sendText(chatId, `Summarizer pinned to ${bk}:${mdl ?? "default"}.`, "plain");
|
|
10658
11188
|
}
|
|
10659
11189
|
} else if (data.startsWith("perms:")) {
|
|
10660
|
-
|
|
11190
|
+
let chosen = data.slice(6);
|
|
11191
|
+
if (chosen === "readonly") chosen = "plan";
|
|
10661
11192
|
if (!PERM_MODES[chosen]) return;
|
|
10662
11193
|
const previous = getMode(chatId);
|
|
10663
11194
|
if (chosen === previous) {
|
|
@@ -10672,6 +11203,20 @@ Select thinking/effort level:`,
|
|
|
10672
11203
|
${PERM_MODES[chosen]}`,
|
|
10673
11204
|
"plain"
|
|
10674
11205
|
);
|
|
11206
|
+
} else if (data.startsWith("perm:escalate:")) {
|
|
11207
|
+
const targetMode = data.slice(14);
|
|
11208
|
+
if (!PERM_MODES[targetMode]) return;
|
|
11209
|
+
setMode(chatId, targetMode);
|
|
11210
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Permission escalated to ${targetMode}`, detail: { field: "permissions", value: targetMode } });
|
|
11211
|
+
await channel.sendText(chatId, `Switched to ${targetMode} mode. Re-processing your request...`, "plain");
|
|
11212
|
+
const pending = getPendingEscalation(chatId);
|
|
11213
|
+
if (pending) {
|
|
11214
|
+
removePendingEscalation(chatId);
|
|
11215
|
+
await handleMessage({ text: pending, chatId, source: "telegram" }, channel);
|
|
11216
|
+
}
|
|
11217
|
+
} else if (data === "perm:deny") {
|
|
11218
|
+
removePendingEscalation(chatId);
|
|
11219
|
+
await channel.sendText(chatId, "Keeping current mode.", "plain");
|
|
10675
11220
|
} else if (data.startsWith("verbose:")) {
|
|
10676
11221
|
const chosen = data.slice(8);
|
|
10677
11222
|
if (!VERBOSE_LEVELS[chosen]) return;
|
|
@@ -10746,6 +11291,47 @@ ${PERM_MODES[chosen]}`,
|
|
|
10746
11291
|
touchBookmark(chatId, alias);
|
|
10747
11292
|
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${bookmark.path}`, detail: { field: "cwd", value: bookmark.path } });
|
|
10748
11293
|
await sendCwdSessionChoice(chatId, bookmark.path, channel);
|
|
11294
|
+
} else if (data.startsWith("interrupt:")) {
|
|
11295
|
+
const parts = data.split(":");
|
|
11296
|
+
const action = parts[1];
|
|
11297
|
+
const targetChatId = parts.slice(2).join(":");
|
|
11298
|
+
const pending = pendingInterrupts.get(targetChatId);
|
|
11299
|
+
if (action === "now" && pending) {
|
|
11300
|
+
pendingInterrupts.delete(targetChatId);
|
|
11301
|
+
stopAgent(targetChatId);
|
|
11302
|
+
await channel.sendText(chatId, "\u26A1 Stopping current task and processing your message\u2026", "plain");
|
|
11303
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
11304
|
+
await handleMessage(pending.msg, pending.channel);
|
|
11305
|
+
} else if (action === "queue" && pending) {
|
|
11306
|
+
pendingInterrupts.delete(targetChatId);
|
|
11307
|
+
bypassBusyCheck.add(targetChatId);
|
|
11308
|
+
await channel.sendText(chatId, "\u{1F4E5} Message queued \u2014 will process after current task.", "plain");
|
|
11309
|
+
handleMessage(pending.msg, pending.channel).catch(() => {
|
|
11310
|
+
});
|
|
11311
|
+
} else {
|
|
11312
|
+
await channel.sendText(chatId, "Message already processed or expired.", "plain");
|
|
11313
|
+
}
|
|
11314
|
+
} else if (data.startsWith("fallback:")) {
|
|
11315
|
+
const parts = data.split(":");
|
|
11316
|
+
const targetBackend = parts[1];
|
|
11317
|
+
const targetChatId = parts[2];
|
|
11318
|
+
const pendingMsg = pendingFallbackMessages.get(targetChatId);
|
|
11319
|
+
if (targetBackend === "wait") {
|
|
11320
|
+
pendingFallbackMessages.delete(targetChatId);
|
|
11321
|
+
await channel.sendText(chatId, "OK \u2014 you can switch manually with /backend when ready.", "plain");
|
|
11322
|
+
} else if (pendingMsg) {
|
|
11323
|
+
pendingFallbackMessages.delete(targetChatId);
|
|
11324
|
+
await summarizeWithFallbackChain(targetChatId, targetBackend);
|
|
11325
|
+
const bridge = buildContextBridge(targetChatId);
|
|
11326
|
+
if (bridge) setPendingContextBridge(targetChatId, bridge);
|
|
11327
|
+
clearSession(targetChatId);
|
|
11328
|
+
setBackend(targetChatId, targetBackend);
|
|
11329
|
+
const adapter = getAdapter(targetBackend);
|
|
11330
|
+
await channel.sendText(chatId, `Switched to ${adapter.displayName}. Resending your message\u2026`, "plain");
|
|
11331
|
+
await handleMessage(pendingMsg.msg, pendingMsg.channel);
|
|
11332
|
+
} else {
|
|
11333
|
+
await channel.sendText(chatId, "Fallback expired. Use /backend to switch manually.", "plain");
|
|
11334
|
+
}
|
|
10749
11335
|
} else if (data.startsWith("skills:page:")) {
|
|
10750
11336
|
const page = parseInt(data.slice(12), 10);
|
|
10751
11337
|
const skills2 = await discoverAllSkills();
|
|
@@ -10957,7 +11543,7 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
|
|
|
10957
11543
|
const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
|
|
10958
11544
|
await channel.sendKeyboard(chatId, header2, buttons);
|
|
10959
11545
|
}
|
|
10960
|
-
var PERM_MODES, VERBOSE_LEVELS, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
|
|
11546
|
+
var PERM_MODES, VERBOSE_LEVELS, pendingInterrupts, bypassBusyCheck, pendingFallbackMessages, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
|
|
10961
11547
|
var init_router = __esm({
|
|
10962
11548
|
"src/router.ts"() {
|
|
10963
11549
|
"use strict";
|
|
@@ -10968,9 +11554,13 @@ var init_router = __esm({
|
|
|
10968
11554
|
init_log();
|
|
10969
11555
|
init_format_time();
|
|
10970
11556
|
init_agent();
|
|
11557
|
+
init_retry();
|
|
11558
|
+
init_image_gen();
|
|
10971
11559
|
init_stt();
|
|
10972
11560
|
init_store4();
|
|
10973
11561
|
init_summarize();
|
|
11562
|
+
init_inject();
|
|
11563
|
+
init_store4();
|
|
10974
11564
|
init_session_log();
|
|
10975
11565
|
init_backends();
|
|
10976
11566
|
init_cron();
|
|
@@ -10988,14 +11578,16 @@ var init_router = __esm({
|
|
|
10988
11578
|
PERM_MODES = {
|
|
10989
11579
|
yolo: "YOLO \u2014 all tools, full autopilot",
|
|
10990
11580
|
safe: "Safe \u2014 only my allowed tools",
|
|
10991
|
-
|
|
10992
|
-
plan: "Plan \u2014 plan only, no execution"
|
|
11581
|
+
plan: "Plan \u2014 read and analyze only"
|
|
10993
11582
|
};
|
|
10994
11583
|
VERBOSE_LEVELS = {
|
|
10995
11584
|
off: "Off \u2014 no tool visibility",
|
|
10996
11585
|
normal: "Normal \u2014 summarized actions",
|
|
10997
11586
|
verbose: "Verbose \u2014 full details"
|
|
10998
11587
|
};
|
|
11588
|
+
pendingInterrupts = /* @__PURE__ */ new Map();
|
|
11589
|
+
bypassBusyCheck = /* @__PURE__ */ new Set();
|
|
11590
|
+
pendingFallbackMessages = /* @__PURE__ */ new Map();
|
|
10999
11591
|
CLI_INSTALL_HINTS = {
|
|
11000
11592
|
claude: "Install: npm install -g @anthropic-ai/claude-code",
|
|
11001
11593
|
gemini: "Install: npm install -g @anthropic-ai/gemini-cli",
|
|
@@ -11090,19 +11682,19 @@ var init_router = __esm({
|
|
|
11090
11682
|
});
|
|
11091
11683
|
|
|
11092
11684
|
// src/skills/bootstrap.ts
|
|
11093
|
-
import { existsSync as
|
|
11685
|
+
import { existsSync as existsSync14 } from "fs";
|
|
11094
11686
|
import { readdir as readdir3, readFile as readFile6, writeFile as writeFile3, copyFile } from "fs/promises";
|
|
11095
|
-
import { join as
|
|
11687
|
+
import { join as join15, dirname as dirname3 } from "path";
|
|
11096
11688
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
11097
11689
|
async function copyAgentManifestSkills() {
|
|
11098
|
-
if (!
|
|
11690
|
+
if (!existsSync14(PKG_SKILLS)) return;
|
|
11099
11691
|
try {
|
|
11100
11692
|
const entries = await readdir3(PKG_SKILLS, { withFileTypes: true });
|
|
11101
11693
|
for (const entry of entries) {
|
|
11102
11694
|
if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
|
|
11103
|
-
const src =
|
|
11104
|
-
const dest =
|
|
11105
|
-
if (
|
|
11695
|
+
const src = join15(PKG_SKILLS, entry.name);
|
|
11696
|
+
const dest = join15(SKILLS_PATH, entry.name);
|
|
11697
|
+
if (existsSync14(dest)) continue;
|
|
11106
11698
|
await copyFile(src, dest);
|
|
11107
11699
|
log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
|
|
11108
11700
|
}
|
|
@@ -11112,8 +11704,8 @@ async function copyAgentManifestSkills() {
|
|
|
11112
11704
|
}
|
|
11113
11705
|
async function bootstrapSkills() {
|
|
11114
11706
|
await copyAgentManifestSkills();
|
|
11115
|
-
const usmDir =
|
|
11116
|
-
if (
|
|
11707
|
+
const usmDir = join15(SKILLS_PATH, USM_DIR_NAME);
|
|
11708
|
+
if (existsSync14(usmDir)) return;
|
|
11117
11709
|
try {
|
|
11118
11710
|
const entries = await readdir3(SKILLS_PATH);
|
|
11119
11711
|
const dirs = entries.filter((e) => !e.startsWith("."));
|
|
@@ -11135,8 +11727,8 @@ async function bootstrapSkills() {
|
|
|
11135
11727
|
}
|
|
11136
11728
|
}
|
|
11137
11729
|
async function patchUsmForCcClaw(usmDir) {
|
|
11138
|
-
const skillPath =
|
|
11139
|
-
if (!
|
|
11730
|
+
const skillPath = join15(usmDir, "SKILL.md");
|
|
11731
|
+
if (!existsSync14(skillPath)) return;
|
|
11140
11732
|
try {
|
|
11141
11733
|
let content = await readFile6(skillPath, "utf-8");
|
|
11142
11734
|
let patched = false;
|
|
@@ -11181,8 +11773,8 @@ var init_bootstrap = __esm({
|
|
|
11181
11773
|
USM_REPO = "jacob-bd/universal-skills-manager";
|
|
11182
11774
|
USM_DIR_NAME = "universal-skills-manager";
|
|
11183
11775
|
CC_CLAW_ECOSYSTEM_PATCH = `| **CC-Claw** | \`~/.cc-claw/workspace/skills/\` | N/A (daemon, no project scope) |`;
|
|
11184
|
-
PKG_ROOT =
|
|
11185
|
-
PKG_SKILLS =
|
|
11776
|
+
PKG_ROOT = join15(dirname3(fileURLToPath3(import.meta.url)), "..", "..");
|
|
11777
|
+
PKG_SKILLS = join15(PKG_ROOT, "skills");
|
|
11186
11778
|
}
|
|
11187
11779
|
});
|
|
11188
11780
|
|
|
@@ -11396,13 +11988,13 @@ __export(ai_skill_exports, {
|
|
|
11396
11988
|
generateAiSkill: () => generateAiSkill,
|
|
11397
11989
|
installAiSkill: () => installAiSkill
|
|
11398
11990
|
});
|
|
11399
|
-
import { existsSync as
|
|
11400
|
-
import { join as
|
|
11991
|
+
import { existsSync as existsSync15, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
11992
|
+
import { join as join16 } from "path";
|
|
11401
11993
|
import { homedir as homedir4 } from "os";
|
|
11402
11994
|
function generateAiSkill() {
|
|
11403
11995
|
const version = VERSION;
|
|
11404
11996
|
let systemState = "";
|
|
11405
|
-
if (
|
|
11997
|
+
if (existsSync15(DB_PATH)) {
|
|
11406
11998
|
try {
|
|
11407
11999
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store4(), __toCommonJS(store_exports3));
|
|
11408
12000
|
const readDb = openDatabaseReadOnly2();
|
|
@@ -11467,11 +12059,10 @@ cc-claw logs --error # Show error log
|
|
|
11467
12059
|
\`\`\`bash
|
|
11468
12060
|
cc-claw backend list --json # Available backends
|
|
11469
12061
|
cc-claw backend get --json # Active backend
|
|
11470
|
-
cc-claw backend set claude # Switch backend
|
|
12062
|
+
cc-claw backend set claude # Switch backend (claude/gemini/codex)
|
|
11471
12063
|
cc-claw model list --json # Models for active backend
|
|
12064
|
+
cc-claw model get --json # Active model
|
|
11472
12065
|
cc-claw model set claude-opus-4-6 # Switch model
|
|
11473
|
-
cc-claw thinking get --json # Current thinking level
|
|
11474
|
-
cc-claw thinking set high # Set thinking level
|
|
11475
12066
|
\`\`\`
|
|
11476
12067
|
|
|
11477
12068
|
### Chat
|
|
@@ -11491,12 +12082,23 @@ cc-claw memory add "key" "value" # Save a memory (needs daemon)
|
|
|
11491
12082
|
cc-claw memory forget "keyword" # Delete memories (needs daemon)
|
|
11492
12083
|
\`\`\`
|
|
11493
12084
|
|
|
12085
|
+
### Session
|
|
12086
|
+
\`\`\`bash
|
|
12087
|
+
cc-claw session get --json # Current session ID and age
|
|
12088
|
+
cc-claw session new # Clear session (newchat + summarize)
|
|
12089
|
+
\`\`\`
|
|
12090
|
+
|
|
11494
12091
|
### Scheduler (cron/schedule are aliases)
|
|
11495
12092
|
\`\`\`bash
|
|
11496
12093
|
cc-claw cron list --json # All scheduled jobs
|
|
11497
12094
|
cc-claw cron health --json # Scheduler health
|
|
11498
12095
|
cc-claw cron runs --json # Run history
|
|
11499
12096
|
cc-claw cron create --description "Morning briefing" --cron "0 9 * * *" --backend claude
|
|
12097
|
+
cc-claw cron edit 3 --model gemini-3-flash-preview # Change job model
|
|
12098
|
+
cc-claw cron edit 3 --backend gemini --model gemini-3-flash-preview # Change backend + model
|
|
12099
|
+
cc-claw cron edit 3 --description "New task" --timeout 300 # Edit multiple fields
|
|
12100
|
+
cc-claw cron create --description "Report" --cron "0 9 * * *" --backend claude --model claude-sonnet-4-6 --fallback codex:gpt-5.4 --fallback gemini:gemini-3-flash-preview # With fallback chain
|
|
12101
|
+
cc-claw cron edit 3 --fallback codex:gpt-5.4 --fallback gemini:gemini-3-flash-preview # Set fallbacks
|
|
11500
12102
|
cc-claw cron cancel 3 # Cancel job #3
|
|
11501
12103
|
cc-claw cron pause 3 # Pause job
|
|
11502
12104
|
cc-claw cron resume 3 # Resume job
|
|
@@ -11527,7 +12129,14 @@ cc-claw usage cost --json # Session cost estimate
|
|
|
11527
12129
|
cc-claw usage cost --all --json # All-time cost by model
|
|
11528
12130
|
cc-claw usage tokens --json # Backend usage in last 24h
|
|
11529
12131
|
cc-claw usage limits list --json # Current limits
|
|
11530
|
-
cc-claw usage limits set claude daily 500000
|
|
12132
|
+
cc-claw usage limits set claude daily 500000 # Set a limit
|
|
12133
|
+
cc-claw usage limits clear claude daily # Remove a limit
|
|
12134
|
+
\`\`\`
|
|
12135
|
+
|
|
12136
|
+
### Thinking / Reasoning
|
|
12137
|
+
\`\`\`bash
|
|
12138
|
+
cc-claw thinking get --json # Current thinking level
|
|
12139
|
+
cc-claw thinking set high # Set level (auto/off/low/medium/high/extra_high)
|
|
11531
12140
|
\`\`\`
|
|
11532
12141
|
|
|
11533
12142
|
### Permissions & Tools
|
|
@@ -11537,10 +12146,71 @@ cc-claw permissions set yolo # Set mode (yolo/safe/readonly/plan)
|
|
|
11537
12146
|
cc-claw tools list --json # Tools with enabled/disabled status
|
|
11538
12147
|
cc-claw tools enable Bash # Enable a tool
|
|
11539
12148
|
cc-claw tools disable WebFetch # Disable a tool
|
|
12149
|
+
cc-claw tools reset # Reset to defaults (all on)
|
|
11540
12150
|
cc-claw verbose get --json # Tool visibility level
|
|
11541
12151
|
cc-claw verbose set verbose # Set level (off/normal/verbose)
|
|
11542
12152
|
\`\`\`
|
|
11543
12153
|
|
|
12154
|
+
### Working Directory
|
|
12155
|
+
\`\`\`bash
|
|
12156
|
+
cc-claw cwd get --json # Show current working directory
|
|
12157
|
+
cc-claw cwd set /path/to/dir # Set working directory
|
|
12158
|
+
cc-claw cwd clear # Reset to default
|
|
12159
|
+
\`\`\`
|
|
12160
|
+
|
|
12161
|
+
### Voice
|
|
12162
|
+
\`\`\`bash
|
|
12163
|
+
cc-claw voice get --json # Show voice status
|
|
12164
|
+
cc-claw voice set on # Enable voice responses
|
|
12165
|
+
cc-claw voice set off # Disable voice responses
|
|
12166
|
+
\`\`\`
|
|
12167
|
+
|
|
12168
|
+
### Image Generation
|
|
12169
|
+
\`\`\`bash
|
|
12170
|
+
# Via Telegram command:
|
|
12171
|
+
/imagine a futuristic city at sunset # Generate image from prompt
|
|
12172
|
+
/image a cat astronaut on Mars # Alias for /imagine
|
|
12173
|
+
|
|
12174
|
+
# Via AI marker in responses:
|
|
12175
|
+
# The AI can write [GENERATE_IMAGE:prompt] in its response to trigger generation
|
|
12176
|
+
# Requires GEMINI_API_KEY in ~/.cc-claw/.env
|
|
12177
|
+
\`\`\`
|
|
12178
|
+
|
|
12179
|
+
### Heartbeat
|
|
12180
|
+
\`\`\`bash
|
|
12181
|
+
cc-claw heartbeat get --json # Show heartbeat config
|
|
12182
|
+
cc-claw heartbeat set on # Enable proactive awareness
|
|
12183
|
+
cc-claw heartbeat set off # Disable heartbeat
|
|
12184
|
+
cc-claw heartbeat set interval 30m # Set check interval
|
|
12185
|
+
cc-claw heartbeat set hours 9-22 # Set active hours
|
|
12186
|
+
\`\`\`
|
|
12187
|
+
|
|
12188
|
+
### Summarizer
|
|
12189
|
+
\`\`\`bash
|
|
12190
|
+
cc-claw summarizer get --json # Current summarizer config
|
|
12191
|
+
cc-claw summarizer set auto # Auto (use active backend's summarizer)
|
|
12192
|
+
cc-claw summarizer set off # Disable summarization
|
|
12193
|
+
cc-claw summarizer set claude:claude-haiku-4-5 # Pin specific backend:model
|
|
12194
|
+
\`\`\`
|
|
12195
|
+
|
|
12196
|
+
### Chat Aliases
|
|
12197
|
+
\`\`\`bash
|
|
12198
|
+
cc-claw chats list --json # Authorized chats and aliases
|
|
12199
|
+
cc-claw chats alias 123456789 "work" # Set alias for a chat
|
|
12200
|
+
cc-claw chats remove-alias "work" # Remove alias
|
|
12201
|
+
\`\`\`
|
|
12202
|
+
|
|
12203
|
+
### Skills
|
|
12204
|
+
\`\`\`bash
|
|
12205
|
+
cc-claw skills list --json # All skills from all backends
|
|
12206
|
+
cc-claw skills install <github-url> # Install skill from GitHub
|
|
12207
|
+
\`\`\`
|
|
12208
|
+
|
|
12209
|
+
### MCP Servers
|
|
12210
|
+
\`\`\`bash
|
|
12211
|
+
cc-claw mcps list --json # Registered MCP servers
|
|
12212
|
+
\`\`\`
|
|
12213
|
+
|
|
11544
12214
|
### Database
|
|
11545
12215
|
\`\`\`bash
|
|
11546
12216
|
cc-claw db stats --json # Row counts, file size, WAL status
|
|
@@ -11561,15 +12231,10 @@ cc-claw service uninstall # Remove service
|
|
|
11561
12231
|
|
|
11562
12232
|
### Other
|
|
11563
12233
|
\`\`\`bash
|
|
11564
|
-
cc-claw
|
|
11565
|
-
cc-claw
|
|
11566
|
-
cc-claw
|
|
11567
|
-
cc-claw
|
|
11568
|
-
cc-claw session new # Clear session
|
|
11569
|
-
cc-claw cwd get --json # Working directory
|
|
11570
|
-
cc-claw voice get --json # Voice status
|
|
11571
|
-
cc-claw heartbeat get --json # Heartbeat config
|
|
11572
|
-
cc-claw summarizer get --json # Summarizer config
|
|
12234
|
+
cc-claw setup # Interactive configuration wizard
|
|
12235
|
+
cc-claw tui # Interactive terminal chat
|
|
12236
|
+
cc-claw completion --shell zsh # Generate shell completions (bash/zsh/fish)
|
|
12237
|
+
cc-claw --ai # Generate/install SKILL.md for AI tools
|
|
11573
12238
|
\`\`\`
|
|
11574
12239
|
|
|
11575
12240
|
## JSON Output Format
|
|
@@ -11625,11 +12290,11 @@ function installAiSkill() {
|
|
|
11625
12290
|
const failed = [];
|
|
11626
12291
|
for (const [backend2, dirs] of Object.entries(BACKEND_SKILL_DIRS2)) {
|
|
11627
12292
|
for (const dir of dirs) {
|
|
11628
|
-
const skillDir =
|
|
11629
|
-
const skillPath =
|
|
12293
|
+
const skillDir = join16(dir, "cc-claw-cli");
|
|
12294
|
+
const skillPath = join16(skillDir, "SKILL.md");
|
|
11630
12295
|
try {
|
|
11631
|
-
|
|
11632
|
-
|
|
12296
|
+
mkdirSync6(skillDir, { recursive: true });
|
|
12297
|
+
writeFileSync6(skillPath, skill, "utf-8");
|
|
11633
12298
|
installed.push(skillPath);
|
|
11634
12299
|
} catch {
|
|
11635
12300
|
failed.push(skillPath);
|
|
@@ -11645,10 +12310,10 @@ var init_ai_skill = __esm({
|
|
|
11645
12310
|
init_paths();
|
|
11646
12311
|
init_version();
|
|
11647
12312
|
BACKEND_SKILL_DIRS2 = {
|
|
11648
|
-
"cc-claw": [
|
|
11649
|
-
claude: [
|
|
11650
|
-
gemini: [
|
|
11651
|
-
codex: [
|
|
12313
|
+
"cc-claw": [join16(homedir4(), ".cc-claw", "workspace", "skills")],
|
|
12314
|
+
claude: [join16(homedir4(), ".claude", "skills")],
|
|
12315
|
+
gemini: [join16(homedir4(), ".gemini", "skills")],
|
|
12316
|
+
codex: [join16(homedir4(), ".agents", "skills")]
|
|
11652
12317
|
};
|
|
11653
12318
|
}
|
|
11654
12319
|
});
|
|
@@ -11658,21 +12323,21 @@ var index_exports = {};
|
|
|
11658
12323
|
__export(index_exports, {
|
|
11659
12324
|
main: () => main
|
|
11660
12325
|
});
|
|
11661
|
-
import { mkdirSync as
|
|
11662
|
-
import { join as
|
|
12326
|
+
import { mkdirSync as mkdirSync7, existsSync as existsSync16, renameSync, statSync as statSync2, readFileSync as readFileSync10 } from "fs";
|
|
12327
|
+
import { join as join17 } from "path";
|
|
11663
12328
|
import dotenv from "dotenv";
|
|
11664
12329
|
function migrateLayout() {
|
|
11665
12330
|
const moves = [
|
|
11666
|
-
[
|
|
11667
|
-
[
|
|
11668
|
-
[
|
|
11669
|
-
[
|
|
11670
|
-
[
|
|
11671
|
-
[
|
|
11672
|
-
[
|
|
12331
|
+
[join17(CC_CLAW_HOME, "cc-claw.db"), join17(DATA_PATH, "cc-claw.db")],
|
|
12332
|
+
[join17(CC_CLAW_HOME, "cc-claw.db-shm"), join17(DATA_PATH, "cc-claw.db-shm")],
|
|
12333
|
+
[join17(CC_CLAW_HOME, "cc-claw.db-wal"), join17(DATA_PATH, "cc-claw.db-wal")],
|
|
12334
|
+
[join17(CC_CLAW_HOME, "cc-claw.log"), join17(LOGS_PATH, "cc-claw.log")],
|
|
12335
|
+
[join17(CC_CLAW_HOME, "cc-claw.log.1"), join17(LOGS_PATH, "cc-claw.log.1")],
|
|
12336
|
+
[join17(CC_CLAW_HOME, "cc-claw.error.log"), join17(LOGS_PATH, "cc-claw.error.log")],
|
|
12337
|
+
[join17(CC_CLAW_HOME, "cc-claw.error.log.1"), join17(LOGS_PATH, "cc-claw.error.log.1")]
|
|
11673
12338
|
];
|
|
11674
12339
|
for (const [from, to] of moves) {
|
|
11675
|
-
if (
|
|
12340
|
+
if (existsSync16(from) && !existsSync16(to)) {
|
|
11676
12341
|
try {
|
|
11677
12342
|
renameSync(from, to);
|
|
11678
12343
|
} catch {
|
|
@@ -11683,7 +12348,7 @@ function migrateLayout() {
|
|
|
11683
12348
|
function rotateLogs() {
|
|
11684
12349
|
for (const file of [LOG_PATH, ERROR_LOG_PATH]) {
|
|
11685
12350
|
try {
|
|
11686
|
-
const { size } =
|
|
12351
|
+
const { size } = statSync2(file);
|
|
11687
12352
|
if (size > LOG_MAX_BYTES) {
|
|
11688
12353
|
const archivePath = `${file}.1`;
|
|
11689
12354
|
try {
|
|
@@ -11698,14 +12363,21 @@ function rotateLogs() {
|
|
|
11698
12363
|
}
|
|
11699
12364
|
async function main() {
|
|
11700
12365
|
rotateLogs();
|
|
11701
|
-
|
|
12366
|
+
let version = "unknown";
|
|
12367
|
+
try {
|
|
12368
|
+
const pkgPath = new URL("../package.json", import.meta.url);
|
|
12369
|
+
version = JSON.parse(readFileSync10(pkgPath, "utf-8")).version;
|
|
12370
|
+
} catch {
|
|
12371
|
+
}
|
|
12372
|
+
log(`[cc-claw] Starting v${version}`);
|
|
11702
12373
|
initDatabase();
|
|
12374
|
+
pruneMessageLog(30, 2e3);
|
|
11703
12375
|
bootstrapBuiltinMcps(getDb());
|
|
11704
|
-
const
|
|
12376
|
+
const SUMMARIZE_TIMEOUT_MS2 = 3e4;
|
|
11705
12377
|
try {
|
|
11706
12378
|
let timer;
|
|
11707
12379
|
const timeoutPromise = new Promise((_, reject) => {
|
|
11708
|
-
timer = setTimeout(() => reject(new Error("timeout")),
|
|
12380
|
+
timer = setTimeout(() => reject(new Error("timeout")), SUMMARIZE_TIMEOUT_MS2);
|
|
11709
12381
|
});
|
|
11710
12382
|
try {
|
|
11711
12383
|
await Promise.race([
|
|
@@ -11767,11 +12439,11 @@ async function main() {
|
|
|
11767
12439
|
bootstrapSkills().catch((err) => error("[cc-claw] Skill bootstrap failed:", err));
|
|
11768
12440
|
try {
|
|
11769
12441
|
const { generateAiSkill: generateAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
|
|
11770
|
-
const { writeFileSync:
|
|
11771
|
-
const { join:
|
|
11772
|
-
const skillDir =
|
|
11773
|
-
|
|
11774
|
-
|
|
12442
|
+
const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync11 } = await import("fs");
|
|
12443
|
+
const { join: join20 } = await import("path");
|
|
12444
|
+
const skillDir = join20(SKILLS_PATH, "cc-claw-cli");
|
|
12445
|
+
mkdirSync11(skillDir, { recursive: true });
|
|
12446
|
+
writeFileSync9(join20(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
|
|
11775
12447
|
log("[cc-claw] AI skill updated");
|
|
11776
12448
|
} catch {
|
|
11777
12449
|
}
|
|
@@ -11831,10 +12503,10 @@ var init_index = __esm({
|
|
|
11831
12503
|
init_bootstrap2();
|
|
11832
12504
|
init_health3();
|
|
11833
12505
|
for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
|
|
11834
|
-
if (!
|
|
12506
|
+
if (!existsSync16(dir)) mkdirSync7(dir, { recursive: true });
|
|
11835
12507
|
}
|
|
11836
12508
|
migrateLayout();
|
|
11837
|
-
if (
|
|
12509
|
+
if (existsSync16(ENV_PATH)) {
|
|
11838
12510
|
dotenv.config({ path: ENV_PATH });
|
|
11839
12511
|
} else {
|
|
11840
12512
|
console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
|
|
@@ -11855,10 +12527,10 @@ __export(service_exports, {
|
|
|
11855
12527
|
serviceStatus: () => serviceStatus,
|
|
11856
12528
|
uninstallService: () => uninstallService
|
|
11857
12529
|
});
|
|
11858
|
-
import { existsSync as
|
|
12530
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
11859
12531
|
import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
|
|
11860
12532
|
import { homedir as homedir5, platform } from "os";
|
|
11861
|
-
import { join as
|
|
12533
|
+
import { join as join18, dirname as dirname4 } from "path";
|
|
11862
12534
|
function resolveExecutable2(name) {
|
|
11863
12535
|
try {
|
|
11864
12536
|
return execFileSync2("which", [name], { encoding: "utf-8" }).trim();
|
|
@@ -11871,14 +12543,14 @@ function getPathDirs() {
|
|
|
11871
12543
|
const home = homedir5();
|
|
11872
12544
|
const dirs = /* @__PURE__ */ new Set([
|
|
11873
12545
|
nodeBin,
|
|
11874
|
-
|
|
12546
|
+
join18(home, ".local", "bin"),
|
|
11875
12547
|
"/usr/local/bin",
|
|
11876
12548
|
"/usr/bin",
|
|
11877
12549
|
"/bin"
|
|
11878
12550
|
]);
|
|
11879
12551
|
try {
|
|
11880
12552
|
const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
|
|
11881
|
-
if (prefix) dirs.add(
|
|
12553
|
+
if (prefix) dirs.add(join18(prefix, "bin"));
|
|
11882
12554
|
} catch {
|
|
11883
12555
|
}
|
|
11884
12556
|
return [...dirs].join(":");
|
|
@@ -11933,21 +12605,21 @@ function generatePlist() {
|
|
|
11933
12605
|
}
|
|
11934
12606
|
function installMacOS() {
|
|
11935
12607
|
const agentsDir = dirname4(PLIST_PATH);
|
|
11936
|
-
if (!
|
|
11937
|
-
if (!
|
|
11938
|
-
if (
|
|
12608
|
+
if (!existsSync17(agentsDir)) mkdirSync8(agentsDir, { recursive: true });
|
|
12609
|
+
if (!existsSync17(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
|
|
12610
|
+
if (existsSync17(PLIST_PATH)) {
|
|
11939
12611
|
try {
|
|
11940
12612
|
execFileSync2("launchctl", ["unload", PLIST_PATH]);
|
|
11941
12613
|
} catch {
|
|
11942
12614
|
}
|
|
11943
12615
|
}
|
|
11944
|
-
|
|
12616
|
+
writeFileSync7(PLIST_PATH, generatePlist());
|
|
11945
12617
|
console.log(` Installed: ${PLIST_PATH}`);
|
|
11946
12618
|
execFileSync2("launchctl", ["load", PLIST_PATH]);
|
|
11947
12619
|
console.log(" Service loaded and starting.");
|
|
11948
12620
|
}
|
|
11949
12621
|
function uninstallMacOS() {
|
|
11950
|
-
if (!
|
|
12622
|
+
if (!existsSync17(PLIST_PATH)) {
|
|
11951
12623
|
console.log(" No service found to uninstall.");
|
|
11952
12624
|
return;
|
|
11953
12625
|
}
|
|
@@ -11998,9 +12670,9 @@ WantedBy=default.target
|
|
|
11998
12670
|
`;
|
|
11999
12671
|
}
|
|
12000
12672
|
function installLinux() {
|
|
12001
|
-
if (!
|
|
12002
|
-
if (!
|
|
12003
|
-
|
|
12673
|
+
if (!existsSync17(SYSTEMD_DIR)) mkdirSync8(SYSTEMD_DIR, { recursive: true });
|
|
12674
|
+
if (!existsSync17(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
|
|
12675
|
+
writeFileSync7(UNIT_PATH, generateUnit());
|
|
12004
12676
|
console.log(` Installed: ${UNIT_PATH}`);
|
|
12005
12677
|
execFileSync2("systemctl", ["--user", "daemon-reload"]);
|
|
12006
12678
|
execFileSync2("systemctl", ["--user", "enable", "cc-claw"]);
|
|
@@ -12008,7 +12680,7 @@ function installLinux() {
|
|
|
12008
12680
|
console.log(" Service enabled and started.");
|
|
12009
12681
|
}
|
|
12010
12682
|
function uninstallLinux() {
|
|
12011
|
-
if (!
|
|
12683
|
+
if (!existsSync17(UNIT_PATH)) {
|
|
12012
12684
|
console.log(" No service found to uninstall.");
|
|
12013
12685
|
return;
|
|
12014
12686
|
}
|
|
@@ -12033,7 +12705,7 @@ function statusLinux() {
|
|
|
12033
12705
|
}
|
|
12034
12706
|
}
|
|
12035
12707
|
function installService() {
|
|
12036
|
-
if (!
|
|
12708
|
+
if (!existsSync17(join18(CC_CLAW_HOME, ".env"))) {
|
|
12037
12709
|
console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
|
|
12038
12710
|
console.error(" Run 'cc-claw setup' before installing the service.");
|
|
12039
12711
|
process.exitCode = 1;
|
|
@@ -12062,9 +12734,9 @@ var init_service = __esm({
|
|
|
12062
12734
|
"use strict";
|
|
12063
12735
|
init_paths();
|
|
12064
12736
|
PLIST_LABEL = "com.cc-claw";
|
|
12065
|
-
PLIST_PATH =
|
|
12066
|
-
SYSTEMD_DIR =
|
|
12067
|
-
UNIT_PATH =
|
|
12737
|
+
PLIST_PATH = join18(homedir5(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
12738
|
+
SYSTEMD_DIR = join18(homedir5(), ".config", "systemd", "user");
|
|
12739
|
+
UNIT_PATH = join18(SYSTEMD_DIR, "cc-claw.service");
|
|
12068
12740
|
}
|
|
12069
12741
|
});
|
|
12070
12742
|
|
|
@@ -12231,13 +12903,13 @@ var init_daemon = __esm({
|
|
|
12231
12903
|
});
|
|
12232
12904
|
|
|
12233
12905
|
// src/cli/resolve-chat.ts
|
|
12234
|
-
import { readFileSync as
|
|
12906
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
12235
12907
|
function resolveChatId(globalOpts) {
|
|
12236
12908
|
const explicit = globalOpts.chat;
|
|
12237
12909
|
if (explicit) return explicit;
|
|
12238
12910
|
if (_cachedDefault) return _cachedDefault;
|
|
12239
12911
|
try {
|
|
12240
|
-
const content =
|
|
12912
|
+
const content = readFileSync12(ENV_PATH, "utf-8");
|
|
12241
12913
|
const match = content.match(/^ALLOWED_CHAT_ID=(.+)$/m);
|
|
12242
12914
|
if (match) {
|
|
12243
12915
|
_cachedDefault = match[1].split(",")[0].trim();
|
|
@@ -12263,12 +12935,12 @@ __export(api_client_exports, {
|
|
|
12263
12935
|
apiPost: () => apiPost,
|
|
12264
12936
|
isDaemonRunning: () => isDaemonRunning
|
|
12265
12937
|
});
|
|
12266
|
-
import { readFileSync as
|
|
12938
|
+
import { readFileSync as readFileSync13, existsSync as existsSync18 } from "fs";
|
|
12267
12939
|
import { request as httpRequest } from "http";
|
|
12268
12940
|
function getToken() {
|
|
12269
12941
|
if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
|
|
12270
12942
|
try {
|
|
12271
|
-
if (
|
|
12943
|
+
if (existsSync18(TOKEN_PATH)) return readFileSync13(TOKEN_PATH, "utf-8").trim();
|
|
12272
12944
|
} catch {
|
|
12273
12945
|
}
|
|
12274
12946
|
return null;
|
|
@@ -12361,7 +13033,7 @@ var status_exports = {};
|
|
|
12361
13033
|
__export(status_exports, {
|
|
12362
13034
|
statusCommand: () => statusCommand
|
|
12363
13035
|
});
|
|
12364
|
-
import { existsSync as
|
|
13036
|
+
import { existsSync as existsSync19, statSync as statSync3 } from "fs";
|
|
12365
13037
|
async function statusCommand(globalOpts, localOpts) {
|
|
12366
13038
|
try {
|
|
12367
13039
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
@@ -12401,7 +13073,7 @@ async function statusCommand(globalOpts, localOpts) {
|
|
|
12401
13073
|
const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
|
|
12402
13074
|
const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
|
|
12403
13075
|
const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
|
|
12404
|
-
const dbStat =
|
|
13076
|
+
const dbStat = existsSync19(DB_PATH) ? statSync3(DB_PATH) : null;
|
|
12405
13077
|
let daemonRunning = false;
|
|
12406
13078
|
let daemonInfo = {};
|
|
12407
13079
|
if (localOpts.deep) {
|
|
@@ -12492,12 +13164,12 @@ var doctor_exports = {};
|
|
|
12492
13164
|
__export(doctor_exports, {
|
|
12493
13165
|
doctorCommand: () => doctorCommand
|
|
12494
13166
|
});
|
|
12495
|
-
import { existsSync as
|
|
13167
|
+
import { existsSync as existsSync20, statSync as statSync4, accessSync, constants } from "fs";
|
|
12496
13168
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
12497
13169
|
async function doctorCommand(globalOpts, localOpts) {
|
|
12498
13170
|
const checks = [];
|
|
12499
|
-
if (
|
|
12500
|
-
const size =
|
|
13171
|
+
if (existsSync20(DB_PATH)) {
|
|
13172
|
+
const size = statSync4(DB_PATH).size;
|
|
12501
13173
|
checks.push({ name: "Database", status: "ok", message: `${DB_PATH} (${(size / 1024).toFixed(0)}KB)` });
|
|
12502
13174
|
try {
|
|
12503
13175
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
@@ -12526,7 +13198,7 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
12526
13198
|
} else {
|
|
12527
13199
|
checks.push({ name: "Database", status: "error", message: `Not found at ${DB_PATH}`, fix: "cc-claw setup" });
|
|
12528
13200
|
}
|
|
12529
|
-
if (
|
|
13201
|
+
if (existsSync20(ENV_PATH)) {
|
|
12530
13202
|
checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
|
|
12531
13203
|
} else {
|
|
12532
13204
|
checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
|
|
@@ -12566,7 +13238,7 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
12566
13238
|
checks.push({ name: "Daemon", status: "warning", message: "could not probe" });
|
|
12567
13239
|
}
|
|
12568
13240
|
const tokenPath = `${DATA_PATH}/api-token`;
|
|
12569
|
-
if (
|
|
13241
|
+
if (existsSync20(tokenPath)) {
|
|
12570
13242
|
try {
|
|
12571
13243
|
accessSync(tokenPath, constants.R_OK);
|
|
12572
13244
|
checks.push({ name: "API token", status: "ok", message: "token file readable" });
|
|
@@ -12591,10 +13263,10 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
12591
13263
|
}
|
|
12592
13264
|
} catch {
|
|
12593
13265
|
}
|
|
12594
|
-
if (
|
|
13266
|
+
if (existsSync20(ERROR_LOG_PATH)) {
|
|
12595
13267
|
try {
|
|
12596
|
-
const { readFileSync:
|
|
12597
|
-
const logContent =
|
|
13268
|
+
const { readFileSync: readFileSync18 } = await import("fs");
|
|
13269
|
+
const logContent = readFileSync18(ERROR_LOG_PATH, "utf-8");
|
|
12598
13270
|
const recentLines = logContent.split("\n").filter(Boolean).slice(-100);
|
|
12599
13271
|
const last24h = Date.now() - 864e5;
|
|
12600
13272
|
const recentErrors = recentLines.filter((line) => {
|
|
@@ -12710,15 +13382,15 @@ var logs_exports = {};
|
|
|
12710
13382
|
__export(logs_exports, {
|
|
12711
13383
|
logsCommand: () => logsCommand
|
|
12712
13384
|
});
|
|
12713
|
-
import { existsSync as
|
|
13385
|
+
import { existsSync as existsSync21, readFileSync as readFileSync14, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
|
|
12714
13386
|
async function logsCommand(opts) {
|
|
12715
13387
|
const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
|
|
12716
|
-
if (!
|
|
13388
|
+
if (!existsSync21(logFile)) {
|
|
12717
13389
|
outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
|
|
12718
13390
|
process.exit(1);
|
|
12719
13391
|
}
|
|
12720
13392
|
const maxLines = parseInt(opts.lines ?? "100", 10);
|
|
12721
|
-
const content =
|
|
13393
|
+
const content = readFileSync14(logFile, "utf-8");
|
|
12722
13394
|
const allLines = content.split("\n");
|
|
12723
13395
|
const tailLines = allLines.slice(-maxLines);
|
|
12724
13396
|
console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
|
|
@@ -12728,7 +13400,7 @@ async function logsCommand(opts) {
|
|
|
12728
13400
|
let lastSize = Buffer.byteLength(content, "utf-8");
|
|
12729
13401
|
watchFile2(logFile, { interval: 500 }, () => {
|
|
12730
13402
|
try {
|
|
12731
|
-
const newContent =
|
|
13403
|
+
const newContent = readFileSync14(logFile, "utf-8");
|
|
12732
13404
|
const newSize = Buffer.byteLength(newContent, "utf-8");
|
|
12733
13405
|
if (newSize > lastSize) {
|
|
12734
13406
|
const newPart = newContent.slice(content.length);
|
|
@@ -12761,12 +13433,12 @@ __export(backend_exports, {
|
|
|
12761
13433
|
backendList: () => backendList,
|
|
12762
13434
|
backendSet: () => backendSet
|
|
12763
13435
|
});
|
|
12764
|
-
import { existsSync as
|
|
13436
|
+
import { existsSync as existsSync22 } from "fs";
|
|
12765
13437
|
async function backendList(globalOpts) {
|
|
12766
13438
|
const { getAllAdapters: getAllAdapters2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
12767
13439
|
const chatId = resolveChatId(globalOpts);
|
|
12768
13440
|
let activeBackend = null;
|
|
12769
|
-
if (
|
|
13441
|
+
if (existsSync22(DB_PATH)) {
|
|
12770
13442
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
12771
13443
|
const readDb = openDatabaseReadOnly2();
|
|
12772
13444
|
try {
|
|
@@ -12797,7 +13469,7 @@ async function backendList(globalOpts) {
|
|
|
12797
13469
|
}
|
|
12798
13470
|
async function backendGet(globalOpts) {
|
|
12799
13471
|
const chatId = resolveChatId(globalOpts);
|
|
12800
|
-
if (!
|
|
13472
|
+
if (!existsSync22(DB_PATH)) {
|
|
12801
13473
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
12802
13474
|
process.exit(1);
|
|
12803
13475
|
}
|
|
@@ -12841,13 +13513,13 @@ __export(model_exports, {
|
|
|
12841
13513
|
modelList: () => modelList,
|
|
12842
13514
|
modelSet: () => modelSet
|
|
12843
13515
|
});
|
|
12844
|
-
import { existsSync as
|
|
13516
|
+
import { existsSync as existsSync23 } from "fs";
|
|
12845
13517
|
async function modelList(globalOpts) {
|
|
12846
13518
|
const chatId = resolveChatId(globalOpts);
|
|
12847
13519
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
12848
13520
|
const { getAdapter: getAdapter2, getAllAdapters: getAllAdapters2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
|
|
12849
13521
|
let backendId = "claude";
|
|
12850
|
-
if (
|
|
13522
|
+
if (existsSync23(DB_PATH)) {
|
|
12851
13523
|
const readDb = openDatabaseReadOnly2();
|
|
12852
13524
|
try {
|
|
12853
13525
|
const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
|
|
@@ -12880,7 +13552,7 @@ async function modelList(globalOpts) {
|
|
|
12880
13552
|
}
|
|
12881
13553
|
async function modelGet(globalOpts) {
|
|
12882
13554
|
const chatId = resolveChatId(globalOpts);
|
|
12883
|
-
if (!
|
|
13555
|
+
if (!existsSync23(DB_PATH)) {
|
|
12884
13556
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
12885
13557
|
process.exit(1);
|
|
12886
13558
|
}
|
|
@@ -12924,9 +13596,9 @@ __export(memory_exports, {
|
|
|
12924
13596
|
memoryList: () => memoryList,
|
|
12925
13597
|
memorySearch: () => memorySearch
|
|
12926
13598
|
});
|
|
12927
|
-
import { existsSync as
|
|
13599
|
+
import { existsSync as existsSync24 } from "fs";
|
|
12928
13600
|
async function memoryList(globalOpts) {
|
|
12929
|
-
if (!
|
|
13601
|
+
if (!existsSync24(DB_PATH)) {
|
|
12930
13602
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
12931
13603
|
process.exit(1);
|
|
12932
13604
|
}
|
|
@@ -12950,7 +13622,7 @@ async function memoryList(globalOpts) {
|
|
|
12950
13622
|
});
|
|
12951
13623
|
}
|
|
12952
13624
|
async function memorySearch(globalOpts, query) {
|
|
12953
|
-
if (!
|
|
13625
|
+
if (!existsSync24(DB_PATH)) {
|
|
12954
13626
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
12955
13627
|
process.exit(1);
|
|
12956
13628
|
}
|
|
@@ -12972,7 +13644,7 @@ async function memorySearch(globalOpts, query) {
|
|
|
12972
13644
|
});
|
|
12973
13645
|
}
|
|
12974
13646
|
async function memoryHistory(globalOpts, opts) {
|
|
12975
|
-
if (!
|
|
13647
|
+
if (!existsSync24(DB_PATH)) {
|
|
12976
13648
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
12977
13649
|
process.exit(1);
|
|
12978
13650
|
}
|
|
@@ -13020,7 +13692,17 @@ __export(cron_exports2, {
|
|
|
13020
13692
|
cronList: () => cronList,
|
|
13021
13693
|
cronRuns: () => cronRuns
|
|
13022
13694
|
});
|
|
13023
|
-
import { existsSync as
|
|
13695
|
+
import { existsSync as existsSync25 } from "fs";
|
|
13696
|
+
function parseFallbacks(raw) {
|
|
13697
|
+
return raw.slice(0, 3).map((f) => {
|
|
13698
|
+
const [backend2, ...rest] = f.split(":");
|
|
13699
|
+
const model2 = rest.join(":");
|
|
13700
|
+
if (!backend2 || !model2) {
|
|
13701
|
+
throw new Error(`Invalid fallback format "${f}" \u2014 expected backend:model (e.g. gemini:gemini-3-flash-preview)`);
|
|
13702
|
+
}
|
|
13703
|
+
return { backend: backend2, model: model2 };
|
|
13704
|
+
});
|
|
13705
|
+
}
|
|
13024
13706
|
function parseAndValidateTimeout(raw) {
|
|
13025
13707
|
if (!raw) return null;
|
|
13026
13708
|
const val = parseInt(raw, 10);
|
|
@@ -13031,7 +13713,7 @@ function parseAndValidateTimeout(raw) {
|
|
|
13031
13713
|
return val;
|
|
13032
13714
|
}
|
|
13033
13715
|
async function cronList(globalOpts) {
|
|
13034
|
-
if (!
|
|
13716
|
+
if (!existsSync25(DB_PATH)) {
|
|
13035
13717
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
13036
13718
|
process.exit(1);
|
|
13037
13719
|
}
|
|
@@ -13052,15 +13734,24 @@ async function cronList(globalOpts) {
|
|
|
13052
13734
|
lines.push(` ${statusDot(status)} #${j.id} [${status}] ${schedule2}${tz}`);
|
|
13053
13735
|
lines.push(` ${j.description}`);
|
|
13054
13736
|
if (j.backend) lines.push(` Backend: ${j.backend}${j.model ? ` / ${j.model}` : ""}`);
|
|
13737
|
+
if (j.fallbacks) {
|
|
13738
|
+
try {
|
|
13739
|
+
const fb = JSON.parse(j.fallbacks);
|
|
13740
|
+
if (Array.isArray(fb) && fb.length > 0) {
|
|
13741
|
+
lines.push(` Fallbacks: ${fb.map((f) => `${f.backend}:${f.model}`).join(" \u2192 ")}`);
|
|
13742
|
+
}
|
|
13743
|
+
} catch {
|
|
13744
|
+
}
|
|
13745
|
+
}
|
|
13055
13746
|
if (j.timeout) lines.push(` Timeout: ${j.timeout}s`);
|
|
13056
|
-
if (j.next_run_at) lines.push(` Next: ${muted(j.next_run_at)}`);
|
|
13747
|
+
if (j.next_run_at) lines.push(` Next: ${muted(formatLocalDateTime(j.next_run_at))}`);
|
|
13057
13748
|
lines.push("");
|
|
13058
13749
|
}
|
|
13059
13750
|
return lines.join("\n");
|
|
13060
13751
|
});
|
|
13061
13752
|
}
|
|
13062
13753
|
async function cronHealth(globalOpts) {
|
|
13063
|
-
if (!
|
|
13754
|
+
if (!existsSync25(DB_PATH)) {
|
|
13064
13755
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
13065
13756
|
process.exit(1);
|
|
13066
13757
|
}
|
|
@@ -13114,6 +13805,7 @@ async function cronCreate(globalOpts, opts) {
|
|
|
13114
13805
|
everyMs = unit.startsWith("h") ? num * 36e5 : unit.startsWith("m") ? num * 6e4 : num * 1e3;
|
|
13115
13806
|
}
|
|
13116
13807
|
}
|
|
13808
|
+
const fallbacks = opts.fallback?.length ? parseFallbacks(opts.fallback) : void 0;
|
|
13117
13809
|
const job = insertJob2({
|
|
13118
13810
|
scheduleType: schedType,
|
|
13119
13811
|
cron: opts.cron ?? null,
|
|
@@ -13125,6 +13817,7 @@ async function cronCreate(globalOpts, opts) {
|
|
|
13125
13817
|
model: opts.model ?? null,
|
|
13126
13818
|
thinking: opts.thinking ?? null,
|
|
13127
13819
|
timeout,
|
|
13820
|
+
fallbacks,
|
|
13128
13821
|
sessionType: opts.sessionType ?? "isolated",
|
|
13129
13822
|
deliveryMode: opts.delivery ?? "announce",
|
|
13130
13823
|
channel: opts.channel ?? null,
|
|
@@ -13209,6 +13902,11 @@ async function cronEdit(globalOpts, id, opts) {
|
|
|
13209
13902
|
updates.push("timezone = ?");
|
|
13210
13903
|
values.push(opts.timezone);
|
|
13211
13904
|
}
|
|
13905
|
+
if (opts.fallback?.length) {
|
|
13906
|
+
const fallbacks = parseFallbacks(opts.fallback);
|
|
13907
|
+
updates.push("fallbacks = ?");
|
|
13908
|
+
values.push(JSON.stringify(fallbacks));
|
|
13909
|
+
}
|
|
13212
13910
|
if (updates.length === 0) {
|
|
13213
13911
|
outputError("NO_CHANGES", "No fields to update. Specify fields with flags (e.g. --description, --cron).");
|
|
13214
13912
|
process.exit(1);
|
|
@@ -13224,7 +13922,7 @@ async function cronEdit(globalOpts, id, opts) {
|
|
|
13224
13922
|
}
|
|
13225
13923
|
}
|
|
13226
13924
|
async function cronRuns(globalOpts, jobId, opts) {
|
|
13227
|
-
if (!
|
|
13925
|
+
if (!existsSync25(DB_PATH)) {
|
|
13228
13926
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
13229
13927
|
process.exit(1);
|
|
13230
13928
|
}
|
|
@@ -13243,7 +13941,7 @@ async function cronRuns(globalOpts, jobId, opts) {
|
|
|
13243
13941
|
const lines = ["", divider("Run History"), ""];
|
|
13244
13942
|
for (const r of list) {
|
|
13245
13943
|
const duration = r.duration_ms ? ` (${(r.duration_ms / 1e3).toFixed(1)}s)` : "";
|
|
13246
|
-
lines.push(` #${r.job_id} [${r.status}] ${r.started_at}${duration}`);
|
|
13944
|
+
lines.push(` #${r.job_id} [${r.status}] ${formatLocalDateTime(r.started_at)}${duration}`);
|
|
13247
13945
|
if (r.error) lines.push(` Error: ${r.error.slice(0, 100)}`);
|
|
13248
13946
|
lines.push("");
|
|
13249
13947
|
}
|
|
@@ -13257,6 +13955,7 @@ var init_cron2 = __esm({
|
|
|
13257
13955
|
init_paths();
|
|
13258
13956
|
init_resolve_chat();
|
|
13259
13957
|
init_types2();
|
|
13958
|
+
init_format_time();
|
|
13260
13959
|
}
|
|
13261
13960
|
});
|
|
13262
13961
|
|
|
@@ -13270,9 +13969,9 @@ __export(agents_exports, {
|
|
|
13270
13969
|
runnersList: () => runnersList,
|
|
13271
13970
|
tasksList: () => tasksList
|
|
13272
13971
|
});
|
|
13273
|
-
import { existsSync as
|
|
13972
|
+
import { existsSync as existsSync26 } from "fs";
|
|
13274
13973
|
async function agentsList(globalOpts) {
|
|
13275
|
-
if (!
|
|
13974
|
+
if (!existsSync26(DB_PATH)) {
|
|
13276
13975
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
13277
13976
|
process.exit(1);
|
|
13278
13977
|
}
|
|
@@ -13303,7 +14002,7 @@ async function agentsList(globalOpts) {
|
|
|
13303
14002
|
});
|
|
13304
14003
|
}
|
|
13305
14004
|
async function tasksList(globalOpts) {
|
|
13306
|
-
if (!
|
|
14005
|
+
if (!existsSync26(DB_PATH)) {
|
|
13307
14006
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
13308
14007
|
process.exit(1);
|
|
13309
14008
|
}
|
|
@@ -13431,18 +14130,18 @@ __export(db_exports, {
|
|
|
13431
14130
|
dbPath: () => dbPath,
|
|
13432
14131
|
dbStats: () => dbStats
|
|
13433
14132
|
});
|
|
13434
|
-
import { existsSync as
|
|
14133
|
+
import { existsSync as existsSync27, statSync as statSync5, copyFileSync, mkdirSync as mkdirSync9 } from "fs";
|
|
13435
14134
|
import { dirname as dirname5 } from "path";
|
|
13436
14135
|
async function dbStats(globalOpts) {
|
|
13437
|
-
if (!
|
|
14136
|
+
if (!existsSync27(DB_PATH)) {
|
|
13438
14137
|
outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
|
|
13439
14138
|
process.exit(1);
|
|
13440
14139
|
}
|
|
13441
14140
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
13442
14141
|
const readDb = openDatabaseReadOnly2();
|
|
13443
|
-
const mainSize =
|
|
14142
|
+
const mainSize = statSync5(DB_PATH).size;
|
|
13444
14143
|
const walPath = DB_PATH + "-wal";
|
|
13445
|
-
const walSize =
|
|
14144
|
+
const walSize = existsSync27(walPath) ? statSync5(walPath).size : 0;
|
|
13446
14145
|
const tableNames = readDb.prepare(
|
|
13447
14146
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
|
|
13448
14147
|
).all();
|
|
@@ -13476,17 +14175,17 @@ async function dbPath(globalOpts) {
|
|
|
13476
14175
|
output({ path: DB_PATH }, (d) => d.path);
|
|
13477
14176
|
}
|
|
13478
14177
|
async function dbBackup(globalOpts, destPath) {
|
|
13479
|
-
if (!
|
|
14178
|
+
if (!existsSync27(DB_PATH)) {
|
|
13480
14179
|
outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
|
|
13481
14180
|
process.exit(1);
|
|
13482
14181
|
}
|
|
13483
14182
|
const dest = destPath ?? `${DB_PATH}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
13484
14183
|
try {
|
|
13485
|
-
|
|
14184
|
+
mkdirSync9(dirname5(dest), { recursive: true });
|
|
13486
14185
|
copyFileSync(DB_PATH, dest);
|
|
13487
14186
|
const walPath = DB_PATH + "-wal";
|
|
13488
|
-
if (
|
|
13489
|
-
output({ path: dest, sizeBytes:
|
|
14187
|
+
if (existsSync27(walPath)) copyFileSync(walPath, dest + "-wal");
|
|
14188
|
+
output({ path: dest, sizeBytes: statSync5(dest).size }, (d) => {
|
|
13490
14189
|
const b = d;
|
|
13491
14190
|
return `
|
|
13492
14191
|
${success("Backup created:")} ${b.path} (${(b.sizeBytes / 1024).toFixed(0)}KB)
|
|
@@ -13514,9 +14213,9 @@ __export(usage_exports, {
|
|
|
13514
14213
|
usageCost: () => usageCost,
|
|
13515
14214
|
usageTokens: () => usageTokens
|
|
13516
14215
|
});
|
|
13517
|
-
import { existsSync as
|
|
14216
|
+
import { existsSync as existsSync28 } from "fs";
|
|
13518
14217
|
function ensureDb() {
|
|
13519
|
-
if (!
|
|
14218
|
+
if (!existsSync28(DB_PATH)) {
|
|
13520
14219
|
outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
|
|
13521
14220
|
process.exit(1);
|
|
13522
14221
|
}
|
|
@@ -13706,9 +14405,9 @@ __export(config_exports, {
|
|
|
13706
14405
|
configList: () => configList,
|
|
13707
14406
|
configSet: () => configSet
|
|
13708
14407
|
});
|
|
13709
|
-
import { existsSync as
|
|
14408
|
+
import { existsSync as existsSync29, readFileSync as readFileSync15 } from "fs";
|
|
13710
14409
|
async function configList(globalOpts) {
|
|
13711
|
-
if (!
|
|
14410
|
+
if (!existsSync29(DB_PATH)) {
|
|
13712
14411
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
13713
14412
|
process.exit(1);
|
|
13714
14413
|
}
|
|
@@ -13742,7 +14441,7 @@ async function configGet(globalOpts, key) {
|
|
|
13742
14441
|
outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
|
|
13743
14442
|
process.exit(1);
|
|
13744
14443
|
}
|
|
13745
|
-
if (!
|
|
14444
|
+
if (!existsSync29(DB_PATH)) {
|
|
13746
14445
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
13747
14446
|
process.exit(1);
|
|
13748
14447
|
}
|
|
@@ -13788,11 +14487,11 @@ async function configSet(globalOpts, key, value) {
|
|
|
13788
14487
|
}
|
|
13789
14488
|
}
|
|
13790
14489
|
async function configEnv(_globalOpts) {
|
|
13791
|
-
if (!
|
|
14490
|
+
if (!existsSync29(ENV_PATH)) {
|
|
13792
14491
|
outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
|
|
13793
14492
|
process.exit(1);
|
|
13794
14493
|
}
|
|
13795
|
-
const content =
|
|
14494
|
+
const content = readFileSync15(ENV_PATH, "utf-8");
|
|
13796
14495
|
const entries = {};
|
|
13797
14496
|
const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
|
|
13798
14497
|
for (const line of content.split("\n")) {
|
|
@@ -13841,9 +14540,9 @@ __export(session_exports, {
|
|
|
13841
14540
|
sessionGet: () => sessionGet,
|
|
13842
14541
|
sessionNew: () => sessionNew
|
|
13843
14542
|
});
|
|
13844
|
-
import { existsSync as
|
|
14543
|
+
import { existsSync as existsSync30 } from "fs";
|
|
13845
14544
|
async function sessionGet(globalOpts) {
|
|
13846
|
-
if (!
|
|
14545
|
+
if (!existsSync30(DB_PATH)) {
|
|
13847
14546
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
13848
14547
|
process.exit(1);
|
|
13849
14548
|
}
|
|
@@ -13904,9 +14603,9 @@ __export(permissions_exports, {
|
|
|
13904
14603
|
verboseGet: () => verboseGet,
|
|
13905
14604
|
verboseSet: () => verboseSet
|
|
13906
14605
|
});
|
|
13907
|
-
import { existsSync as
|
|
14606
|
+
import { existsSync as existsSync31 } from "fs";
|
|
13908
14607
|
function ensureDb2() {
|
|
13909
|
-
if (!
|
|
14608
|
+
if (!existsSync31(DB_PATH)) {
|
|
13910
14609
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
13911
14610
|
process.exit(1);
|
|
13912
14611
|
}
|
|
@@ -13922,7 +14621,8 @@ async function permissionsGet(globalOpts) {
|
|
|
13922
14621
|
output({ mode }, (d) => d.mode);
|
|
13923
14622
|
}
|
|
13924
14623
|
async function permissionsSet(globalOpts, mode) {
|
|
13925
|
-
|
|
14624
|
+
if (mode === "readonly") mode = "plan";
|
|
14625
|
+
const valid = ["yolo", "safe", "plan"];
|
|
13926
14626
|
if (!valid.includes(mode)) {
|
|
13927
14627
|
outputError("INVALID_MODE", `Invalid mode "${mode}". Valid: ${valid.join(", ")}`);
|
|
13928
14628
|
process.exit(1);
|
|
@@ -14052,9 +14752,9 @@ __export(cwd_exports, {
|
|
|
14052
14752
|
cwdGet: () => cwdGet,
|
|
14053
14753
|
cwdSet: () => cwdSet
|
|
14054
14754
|
});
|
|
14055
|
-
import { existsSync as
|
|
14755
|
+
import { existsSync as existsSync32 } from "fs";
|
|
14056
14756
|
async function cwdGet(globalOpts) {
|
|
14057
|
-
if (!
|
|
14757
|
+
if (!existsSync32(DB_PATH)) {
|
|
14058
14758
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
14059
14759
|
process.exit(1);
|
|
14060
14760
|
}
|
|
@@ -14116,9 +14816,9 @@ __export(voice_exports, {
|
|
|
14116
14816
|
voiceGet: () => voiceGet,
|
|
14117
14817
|
voiceSet: () => voiceSet
|
|
14118
14818
|
});
|
|
14119
|
-
import { existsSync as
|
|
14819
|
+
import { existsSync as existsSync33 } from "fs";
|
|
14120
14820
|
async function voiceGet(globalOpts) {
|
|
14121
|
-
if (!
|
|
14821
|
+
if (!existsSync33(DB_PATH)) {
|
|
14122
14822
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
14123
14823
|
process.exit(1);
|
|
14124
14824
|
}
|
|
@@ -14167,9 +14867,9 @@ __export(heartbeat_exports, {
|
|
|
14167
14867
|
heartbeatGet: () => heartbeatGet,
|
|
14168
14868
|
heartbeatSet: () => heartbeatSet
|
|
14169
14869
|
});
|
|
14170
|
-
import { existsSync as
|
|
14870
|
+
import { existsSync as existsSync34 } from "fs";
|
|
14171
14871
|
async function heartbeatGet(globalOpts) {
|
|
14172
|
-
if (!
|
|
14872
|
+
if (!existsSync34(DB_PATH)) {
|
|
14173
14873
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
14174
14874
|
process.exit(1);
|
|
14175
14875
|
}
|
|
@@ -14279,9 +14979,9 @@ __export(chats_exports, {
|
|
|
14279
14979
|
chatsList: () => chatsList,
|
|
14280
14980
|
chatsRemoveAlias: () => chatsRemoveAlias
|
|
14281
14981
|
});
|
|
14282
|
-
import { existsSync as
|
|
14982
|
+
import { existsSync as existsSync35 } from "fs";
|
|
14283
14983
|
async function chatsList(_globalOpts) {
|
|
14284
|
-
if (!
|
|
14984
|
+
if (!existsSync35(DB_PATH)) {
|
|
14285
14985
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
14286
14986
|
process.exit(1);
|
|
14287
14987
|
}
|
|
@@ -14409,9 +15109,9 @@ var mcps_exports = {};
|
|
|
14409
15109
|
__export(mcps_exports, {
|
|
14410
15110
|
mcpsList: () => mcpsList
|
|
14411
15111
|
});
|
|
14412
|
-
import { existsSync as
|
|
15112
|
+
import { existsSync as existsSync36 } from "fs";
|
|
14413
15113
|
async function mcpsList(_globalOpts) {
|
|
14414
|
-
if (!
|
|
15114
|
+
if (!existsSync36(DB_PATH)) {
|
|
14415
15115
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
14416
15116
|
process.exit(1);
|
|
14417
15117
|
}
|
|
@@ -14448,11 +15148,11 @@ __export(chat_exports, {
|
|
|
14448
15148
|
chatSend: () => chatSend
|
|
14449
15149
|
});
|
|
14450
15150
|
import { request as httpRequest2 } from "http";
|
|
14451
|
-
import { readFileSync as
|
|
15151
|
+
import { readFileSync as readFileSync16, existsSync as existsSync37 } from "fs";
|
|
14452
15152
|
function getToken2() {
|
|
14453
15153
|
if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
|
|
14454
15154
|
try {
|
|
14455
|
-
if (
|
|
15155
|
+
if (existsSync37(TOKEN_PATH2)) return readFileSync16(TOKEN_PATH2, "utf-8").trim();
|
|
14456
15156
|
} catch {
|
|
14457
15157
|
}
|
|
14458
15158
|
return null;
|
|
@@ -14878,10 +15578,10 @@ var init_completion = __esm({
|
|
|
14878
15578
|
|
|
14879
15579
|
// src/setup.ts
|
|
14880
15580
|
var setup_exports = {};
|
|
14881
|
-
import { existsSync as
|
|
15581
|
+
import { existsSync as existsSync38, writeFileSync as writeFileSync8, readFileSync as readFileSync17, copyFileSync as copyFileSync2, mkdirSync as mkdirSync10, statSync as statSync6 } from "fs";
|
|
14882
15582
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
14883
15583
|
import { createInterface as createInterface5 } from "readline";
|
|
14884
|
-
import { join as
|
|
15584
|
+
import { join as join19 } from "path";
|
|
14885
15585
|
function divider2() {
|
|
14886
15586
|
console.log(dim("\u2500".repeat(55)));
|
|
14887
15587
|
}
|
|
@@ -14955,22 +15655,22 @@ async function setup() {
|
|
|
14955
15655
|
}
|
|
14956
15656
|
console.log("");
|
|
14957
15657
|
for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
|
|
14958
|
-
if (!
|
|
15658
|
+
if (!existsSync38(dir)) mkdirSync10(dir, { recursive: true });
|
|
14959
15659
|
}
|
|
14960
15660
|
const env = {};
|
|
14961
|
-
const envSource =
|
|
15661
|
+
const envSource = existsSync38(ENV_PATH) ? ENV_PATH : existsSync38(".env") ? ".env" : null;
|
|
14962
15662
|
if (envSource) {
|
|
14963
15663
|
console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
|
|
14964
15664
|
console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
|
|
14965
|
-
const existing =
|
|
15665
|
+
const existing = readFileSync17(envSource, "utf-8");
|
|
14966
15666
|
for (const line of existing.split("\n")) {
|
|
14967
15667
|
const match = line.match(/^([^#=]+)=(.*)$/);
|
|
14968
15668
|
if (match) env[match[1].trim()] = match[2].trim();
|
|
14969
15669
|
}
|
|
14970
15670
|
}
|
|
14971
|
-
const cwdDb =
|
|
14972
|
-
if (
|
|
14973
|
-
const { size } =
|
|
15671
|
+
const cwdDb = join19(process.cwd(), "cc-claw.db");
|
|
15672
|
+
if (existsSync38(cwdDb) && !existsSync38(DB_PATH)) {
|
|
15673
|
+
const { size } = statSync6(cwdDb);
|
|
14974
15674
|
console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
|
|
14975
15675
|
const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
|
|
14976
15676
|
if (migrate) {
|
|
@@ -15163,7 +15863,7 @@ async function setup() {
|
|
|
15163
15863
|
envLines.push("", "# Video Analysis", `GEMINI_API_KEY=${env.GEMINI_API_KEY}`);
|
|
15164
15864
|
}
|
|
15165
15865
|
const envContent = envLines.join("\n") + "\n";
|
|
15166
|
-
|
|
15866
|
+
writeFileSync8(ENV_PATH, envContent, { mode: 384 });
|
|
15167
15867
|
console.log(green(` Config saved to ${ENV_PATH} (permissions: owner-only)`));
|
|
15168
15868
|
header(6, TOTAL_STEPS, "Run on Startup (Daemon)");
|
|
15169
15869
|
console.log(" CC-Claw can run automatically in the background, starting");
|
|
@@ -15332,7 +16032,7 @@ function registerCronCommands(cmd) {
|
|
|
15332
16032
|
const { cronList: cronList2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
|
|
15333
16033
|
await cronList2(program.opts());
|
|
15334
16034
|
});
|
|
15335
|
-
cmd.command("create").description("Create a scheduled job").requiredOption("--description <text>", "Job description").option("--prompt <text>", "Agent prompt (defaults to description)").option("--cron <expr>", "Cron expression (e.g. '0 9 * * *')").option("--at <iso8601>", "One-shot time").option("--every <interval>", "Repeat interval (e.g. 30m, 1h)").option("--backend <name>", "Backend for this job").option("--model <name>", "Model for this job").option("--thinking <level>", "Thinking level").option("--timeout <seconds>", "Job timeout in seconds (30-3600)").option("--timezone <tz>", "IANA timezone", "UTC").option("--session-type <type>", "Session type (isolated/main)", "isolated").option("--delivery <mode>", "Delivery mode (announce/webhook/none)", "announce").option("--channel <name>", "Delivery channel").option("--target <id>", "Delivery target").option("--cwd <path>", "Working directory").action(async (opts) => {
|
|
16035
|
+
cmd.command("create").description("Create a scheduled job").requiredOption("--description <text>", "Job description").option("--prompt <text>", "Agent prompt (defaults to description)").option("--cron <expr>", "Cron expression (e.g. '0 9 * * *')").option("--at <iso8601>", "One-shot time").option("--every <interval>", "Repeat interval (e.g. 30m, 1h)").option("--backend <name>", "Backend for this job").option("--model <name>", "Model for this job").option("--thinking <level>", "Thinking level").option("--timeout <seconds>", "Job timeout in seconds (30-3600)").option("--fallback <backend:model>", "Fallback backend:model (repeatable, max 3)", (val, prev) => [...prev, val], []).option("--timezone <tz>", "IANA timezone", "UTC").option("--session-type <type>", "Session type (isolated/main)", "isolated").option("--delivery <mode>", "Delivery mode (announce/webhook/none)", "announce").option("--channel <name>", "Delivery channel").option("--target <id>", "Delivery target").option("--cwd <path>", "Working directory").action(async (opts) => {
|
|
15336
16036
|
const { cronCreate: cronCreate2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
|
|
15337
16037
|
await cronCreate2(program.opts(), opts);
|
|
15338
16038
|
});
|
|
@@ -15352,7 +16052,7 @@ function registerCronCommands(cmd) {
|
|
|
15352
16052
|
const { cronAction: cronAction2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
|
|
15353
16053
|
await cronAction2(program.opts(), "run", id);
|
|
15354
16054
|
});
|
|
15355
|
-
cmd.command("edit <id>").description("Edit a job (same flags as create)").option("--description <text>").option("--cron <expr>").option("--at <iso8601>").option("--every <interval>").option("--backend <name>").option("--model <name>").option("--thinking <level>").option("--timeout <seconds>", "Job timeout in seconds (30-3600)").option("--timezone <tz>").action(async (id, opts) => {
|
|
16055
|
+
cmd.command("edit <id>").description("Edit a job (same flags as create)").option("--description <text>").option("--cron <expr>").option("--at <iso8601>").option("--every <interval>").option("--backend <name>").option("--model <name>").option("--thinking <level>").option("--timeout <seconds>", "Job timeout in seconds (30-3600)").option("--fallback <backend:model>", "Fallback backend:model (repeatable, max 3)", (val, prev) => [...prev, val], []).option("--timezone <tz>").action(async (id, opts) => {
|
|
15356
16056
|
const { cronEdit: cronEdit2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
|
|
15357
16057
|
await cronEdit2(program.opts(), id, opts);
|
|
15358
16058
|
});
|