cc-claw 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/cli.js +2009 -505
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -38,13 +38,14 @@ __export(paths_exports, {
|
|
|
38
38
|
IDENTITY_PATH: () => IDENTITY_PATH,
|
|
39
39
|
LOGS_PATH: () => LOGS_PATH,
|
|
40
40
|
LOG_PATH: () => LOG_PATH,
|
|
41
|
+
MEDIA_PATH: () => MEDIA_PATH,
|
|
41
42
|
RUNNERS_PATH: () => RUNNERS_PATH,
|
|
42
43
|
SKILLS_PATH: () => SKILLS_PATH,
|
|
43
44
|
WORKSPACE_PATH: () => WORKSPACE_PATH
|
|
44
45
|
});
|
|
45
46
|
import { homedir } from "os";
|
|
46
47
|
import { join } from "path";
|
|
47
|
-
var CC_CLAW_HOME, ENV_PATH, DATA_PATH, DB_PATH, LOGS_PATH, LOG_PATH, ERROR_LOG_PATH, IDENTITY_PATH, WORKSPACE_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH;
|
|
48
|
+
var CC_CLAW_HOME, ENV_PATH, DATA_PATH, DB_PATH, LOGS_PATH, LOG_PATH, ERROR_LOG_PATH, IDENTITY_PATH, WORKSPACE_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH, MEDIA_PATH;
|
|
48
49
|
var init_paths = __esm({
|
|
49
50
|
"src/paths.ts"() {
|
|
50
51
|
"use strict";
|
|
@@ -60,6 +61,7 @@ var init_paths = __esm({
|
|
|
60
61
|
SKILLS_PATH = join(WORKSPACE_PATH, "skills");
|
|
61
62
|
RUNNERS_PATH = join(CC_CLAW_HOME, "runners");
|
|
62
63
|
AGENTS_PATH = join(CC_CLAW_HOME, "agents");
|
|
64
|
+
MEDIA_PATH = join(CC_CLAW_HOME, "media");
|
|
63
65
|
}
|
|
64
66
|
});
|
|
65
67
|
|
|
@@ -70,7 +72,7 @@ var VERSION;
|
|
|
70
72
|
var init_version = __esm({
|
|
71
73
|
"src/version.ts"() {
|
|
72
74
|
"use strict";
|
|
73
|
-
VERSION = true ? "0.
|
|
75
|
+
VERSION = true ? "0.9.1" : (() => {
|
|
74
76
|
try {
|
|
75
77
|
return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
|
|
76
78
|
} catch {
|
|
@@ -683,6 +685,7 @@ __export(store_exports4, {
|
|
|
683
685
|
getConflictingInsights: () => getConflictingInsights,
|
|
684
686
|
getGrowthMetrics: () => getGrowthMetrics,
|
|
685
687
|
getInsightById: () => getInsightById,
|
|
688
|
+
getLastAnalysisTime: () => getLastAnalysisTime,
|
|
686
689
|
getPendingInsightCount: () => getPendingInsightCount,
|
|
687
690
|
getPendingInsights: () => getPendingInsights,
|
|
688
691
|
getRecommendations: () => getRecommendations,
|
|
@@ -691,6 +694,7 @@ __export(store_exports4, {
|
|
|
691
694
|
getRejectedInsights: () => getRejectedInsights,
|
|
692
695
|
getSignalCountForChat: () => getSignalCountForChat,
|
|
693
696
|
getSignalsForPeriod: () => getSignalsForPeriod,
|
|
697
|
+
getUnprocessedSignalCount: () => getUnprocessedSignalCount,
|
|
694
698
|
getUnprocessedSignals: () => getUnprocessedSignals,
|
|
695
699
|
initReflectionTables: () => initReflectionTables,
|
|
696
700
|
logInsight: () => logInsight,
|
|
@@ -713,8 +717,8 @@ function cleanupBackupFiles(ccClawHome) {
|
|
|
713
717
|
if (!file.includes(".bak.")) continue;
|
|
714
718
|
const fullPath = join3(identityDir, file);
|
|
715
719
|
try {
|
|
716
|
-
const
|
|
717
|
-
if (
|
|
720
|
+
const stat2 = statSync(fullPath);
|
|
721
|
+
if (stat2.mtimeMs < thirtyDaysAgo) unlinkSync(fullPath);
|
|
718
722
|
} catch {
|
|
719
723
|
}
|
|
720
724
|
}
|
|
@@ -841,6 +845,20 @@ function getSignalsForPeriod(db3, chatId, period) {
|
|
|
841
845
|
ORDER BY created_at ASC
|
|
842
846
|
`).all(chatId, period);
|
|
843
847
|
}
|
|
848
|
+
function getUnprocessedSignalCount(db3, chatId) {
|
|
849
|
+
const row = db3.prepare(`
|
|
850
|
+
SELECT COUNT(*) as count FROM feedback_signals
|
|
851
|
+
WHERE chatId = ? AND processedAt IS NULL
|
|
852
|
+
`).get(chatId);
|
|
853
|
+
return row.count;
|
|
854
|
+
}
|
|
855
|
+
function getLastAnalysisTime(db3, chatId) {
|
|
856
|
+
const row = db3.prepare(`
|
|
857
|
+
SELECT MAX(processedAt) as lastTime FROM feedback_signals
|
|
858
|
+
WHERE chatId = ? AND processedAt IS NOT NULL
|
|
859
|
+
`).get(chatId);
|
|
860
|
+
return row.lastTime;
|
|
861
|
+
}
|
|
844
862
|
function getReflectionStatus(db3, chatId) {
|
|
845
863
|
const row = db3.prepare("SELECT status FROM chat_reflection WHERE chatId = ?").get(chatId);
|
|
846
864
|
return row?.status ?? "frozen";
|
|
@@ -1280,6 +1298,8 @@ __export(store_exports5, {
|
|
|
1280
1298
|
clearUsage: () => clearUsage,
|
|
1281
1299
|
completeJobRun: () => completeJobRun,
|
|
1282
1300
|
deleteBookmark: () => deleteBookmark,
|
|
1301
|
+
deleteMemoryById: () => deleteMemoryById,
|
|
1302
|
+
deleteSessionSummary: () => deleteSessionSummary,
|
|
1283
1303
|
determineEscalationTarget: () => determineEscalationTarget,
|
|
1284
1304
|
findBookmarksByPrefix: () => findBookmarksByPrefix,
|
|
1285
1305
|
forgetMemory: () => forgetMemory,
|
|
@@ -1310,6 +1330,7 @@ __export(store_exports5, {
|
|
|
1310
1330
|
getJobById: () => getJobById,
|
|
1311
1331
|
getJobRuns: () => getJobRuns,
|
|
1312
1332
|
getMemoriesWithoutEmbeddings: () => getMemoriesWithoutEmbeddings,
|
|
1333
|
+
getMemoryById: () => getMemoryById,
|
|
1313
1334
|
getMode: () => getMode,
|
|
1314
1335
|
getModel: () => getModel,
|
|
1315
1336
|
getModelSignature: () => getModelSignature,
|
|
@@ -1977,6 +1998,13 @@ function forgetMemory(keyword) {
|
|
|
1977
1998
|
).run(`%${escaped}%`, `%${escaped}%`);
|
|
1978
1999
|
return result.changes;
|
|
1979
2000
|
}
|
|
2001
|
+
function getMemoryById(id) {
|
|
2002
|
+
return db.prepare("SELECT * FROM memories WHERE id = ?").get(id);
|
|
2003
|
+
}
|
|
2004
|
+
function deleteMemoryById(id) {
|
|
2005
|
+
const result = db.prepare("DELETE FROM memories WHERE id = ?").run(id);
|
|
2006
|
+
return result.changes > 0;
|
|
2007
|
+
}
|
|
1980
2008
|
function applySalienceDecay() {
|
|
1981
2009
|
db.exec("CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
1982
2010
|
const lastDecayRow = db.prepare("SELECT value FROM meta WHERE key = 'last_salience_decay'").get();
|
|
@@ -2276,6 +2304,10 @@ function saveSessionSummary(chatId, summary, topics, messageCount, embedding) {
|
|
|
2276
2304
|
function updateSessionSummaryEmbedding(id, embedding) {
|
|
2277
2305
|
db.prepare("UPDATE session_summaries SET embedding = ? WHERE id = ?").run(embedding, id);
|
|
2278
2306
|
}
|
|
2307
|
+
function deleteSessionSummary(id) {
|
|
2308
|
+
const result = db.prepare("DELETE FROM session_summaries WHERE id = ?").run(id);
|
|
2309
|
+
return result.changes > 0;
|
|
2310
|
+
}
|
|
2279
2311
|
function getSessionSummariesWithoutEmbeddings(limit = 100) {
|
|
2280
2312
|
return db.prepare(
|
|
2281
2313
|
"SELECT * FROM session_summaries WHERE embedding IS NULL ORDER BY id LIMIT ?"
|
|
@@ -3258,6 +3290,12 @@ var init_gemini = __esm({
|
|
|
3258
3290
|
thinkingLevels: ["low", "high"],
|
|
3259
3291
|
defaultThinkingLevel: "high"
|
|
3260
3292
|
},
|
|
3293
|
+
"gemini-2.5-pro": {
|
|
3294
|
+
label: "Gemini 2.5 Pro \u2014 stable, 1M context",
|
|
3295
|
+
thinking: "adjustable",
|
|
3296
|
+
thinkingLevels: ["low", "high"],
|
|
3297
|
+
defaultThinkingLevel: "high"
|
|
3298
|
+
},
|
|
3261
3299
|
"gemini-3-flash-preview": {
|
|
3262
3300
|
label: "Gemini 3 Flash \u2014 fast",
|
|
3263
3301
|
thinking: "adjustable",
|
|
@@ -3275,11 +3313,13 @@ var init_gemini = __esm({
|
|
|
3275
3313
|
summarizerModel = "gemini-3.1-flash-lite-preview";
|
|
3276
3314
|
pricing = {
|
|
3277
3315
|
"gemini-3.1-pro-preview": { in: 1.25, out: 10, cache: 0.32 },
|
|
3316
|
+
"gemini-2.5-pro": { in: 1.25, out: 10, cache: 0.32 },
|
|
3278
3317
|
"gemini-3-flash-preview": { in: 0.15, out: 0.6, cache: 0.04 },
|
|
3279
3318
|
"gemini-3.1-flash-lite-preview": { in: 0.04, out: 0.15, cache: 0.01 }
|
|
3280
3319
|
};
|
|
3281
3320
|
contextWindow = {
|
|
3282
3321
|
"gemini-3.1-pro-preview": 1e6,
|
|
3322
|
+
"gemini-2.5-pro": 1e6,
|
|
3283
3323
|
"gemini-3-flash-preview": 1e6,
|
|
3284
3324
|
"gemini-3.1-flash-lite-preview": 1e6
|
|
3285
3325
|
};
|
|
@@ -3828,6 +3868,7 @@ You are a proactive personal AI assistant running inside a Telegram bot powered
|
|
|
3828
3868
|
- **Files**: To send a file to the user, write \`[SEND_FILE:/absolute/path/to/file]\` in your response \u2014 the system uploads it automatically.
|
|
3829
3869
|
- **Heartbeat**: You periodically wake up to check on things and alert the user proactively.
|
|
3830
3870
|
- **Sub-Agents**: You can spawn sub-agents using different coding CLIs to work on tasks in parallel. Use the orchestration MCP tools (spawn_agent, send_message, read_inbox, etc.).
|
|
3871
|
+
- **Interactive UI**: Most commands show interactive keyboards in Telegram. Use /menu for quick access to common actions.
|
|
3831
3872
|
- **MCP Tools**: You have access to MCP servers for external tools and data sources. Use /mcp to see what's available.
|
|
3832
3873
|
- **Self-Learning**: The system tracks your corrections, preferences, and feedback.
|
|
3833
3874
|
Use /evolve to manage everything: analyze, review proposals, see stats, and more.
|
|
@@ -3883,6 +3924,7 @@ You are an expert user and operator of the CC-Claw architecture. Because you are
|
|
|
3883
3924
|
## 5. System Controls & Modes
|
|
3884
3925
|
- **Permission Modes**: Command execution is gated. \`safe\` mode requires user approval for mutations, \`yolo\` auto-runs everything without asking, and \`plan\` is strictly read-only for drafting proposals.
|
|
3885
3926
|
- **Global Settings**: The user controls your behavior system-wide using commands like \`/response_style\` (concise, normal, detailed) to enforce verbosity constraints, and \`/voice_config\` to select Text-to-Speech voices from premium providers like ElevenLabs, Grok (xAI), or local macOS.
|
|
3927
|
+
- **Telegram UX**: Most slash commands use interactive inline keyboards with colored buttons. \`/menu\` (or \`/m\`) is the home screen. \`/usage\` unifies cost, limits, and usage tracking. Inline mode (\`@botname query\`) searches memories from any chat.
|
|
3886
3928
|
|
|
3887
3929
|
## 6. Integrations & MCP
|
|
3888
3930
|
- **Skills**: You can load specific workflows from the \`~/.cc-claw/workspace/skills/\` directory.
|
|
@@ -3956,6 +3998,10 @@ function bootstrapWorkspaceFiles() {
|
|
|
3956
3998
|
mkdirSync(CONTEXT_DIR, { recursive: true });
|
|
3957
3999
|
log("[bootstrap] Created context/ directory");
|
|
3958
4000
|
}
|
|
4001
|
+
if (!existsSync5(MEDIA_PATH)) {
|
|
4002
|
+
mkdirSync(MEDIA_PATH, { recursive: true });
|
|
4003
|
+
log("[bootstrap] Created media/ directory");
|
|
4004
|
+
}
|
|
3959
4005
|
const expertisePath = join6(CONTEXT_DIR, "cc-claw-expertise.md");
|
|
3960
4006
|
if (!existsSync5(expertisePath)) {
|
|
3961
4007
|
writeFileSync(expertisePath, DEFAULT_EXPERTISE, "utf-8");
|
|
@@ -4165,6 +4211,11 @@ ${ctx}`);
|
|
|
4165
4211
|
} catch {
|
|
4166
4212
|
}
|
|
4167
4213
|
}
|
|
4214
|
+
if (tier !== "slim" && tier !== "heartbeat") {
|
|
4215
|
+
sections.push(
|
|
4216
|
+
"[React] Start your response with [REACT:emoji] \u2014 one emoji from the Telegram allowed set matching the message tone. Examples: \u{1F914} question, \u{1FAE1} task/request, \u{1F525} exciting, \u{1F923} funny, \u{1F622} sad, \u{1F4AF} agree, \u{1F389} celebrate, \u{1F468}\u200D\u{1F4BB} coding, \u{1F92F} mind-blown, \u{1F440} interesting, \u{1F91D} thanks, \u{1F60E} chill."
|
|
4217
|
+
);
|
|
4218
|
+
}
|
|
4168
4219
|
sections.push(userMessage);
|
|
4169
4220
|
const result = sections.join("\n\n");
|
|
4170
4221
|
log(`[bootstrap] Assembled prompt: tier=${tier}, sections=${sections.length}, totalChars=${result.length}`);
|
|
@@ -7485,11 +7536,11 @@ data: ${JSON.stringify(data)}
|
|
|
7485
7536
|
const body = JSON.parse(await readBody(req));
|
|
7486
7537
|
const { setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
7487
7538
|
const { existsSync: fileExists, readFileSync: fileRead } = await import("fs");
|
|
7488
|
-
const { join:
|
|
7539
|
+
const { join: join26 } = await import("path");
|
|
7489
7540
|
const { CC_CLAW_HOME: home } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
7490
7541
|
const chatId = body.chatId || (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim() || "default";
|
|
7491
|
-
const soulPath =
|
|
7492
|
-
const userPath =
|
|
7542
|
+
const soulPath = join26(home, "identity/SOUL.md");
|
|
7543
|
+
const userPath = join26(home, "identity/USER.md");
|
|
7493
7544
|
const soul = fileExists(soulPath) ? fileRead(soulPath, "utf-8") : "";
|
|
7494
7545
|
const user = fileExists(userPath) ? fileRead(userPath, "utf-8") : "";
|
|
7495
7546
|
setReflectionStatus2(getDb(), chatId, "active", soul, user);
|
|
@@ -9526,6 +9577,7 @@ var init_telegram2 = __esm({
|
|
|
9526
9577
|
"use strict";
|
|
9527
9578
|
init_telegram();
|
|
9528
9579
|
init_log();
|
|
9580
|
+
init_store5();
|
|
9529
9581
|
TelegramChannel = class {
|
|
9530
9582
|
name = "telegram";
|
|
9531
9583
|
bot;
|
|
@@ -9566,12 +9618,15 @@ var init_telegram2 = __esm({
|
|
|
9566
9618
|
voice: true,
|
|
9567
9619
|
files: true,
|
|
9568
9620
|
typing: true,
|
|
9569
|
-
richFormatting: true
|
|
9621
|
+
richFormatting: true,
|
|
9622
|
+
buttonStyles: true
|
|
9570
9623
|
};
|
|
9571
9624
|
}
|
|
9572
9625
|
async start(handler) {
|
|
9573
9626
|
await this.bot.api.setMyCommands([
|
|
9574
9627
|
// Core
|
|
9628
|
+
{ command: "menu", description: "Home screen \u2014 quick-access keyboard" },
|
|
9629
|
+
{ command: "m", description: "Home screen (alias for /menu)" },
|
|
9575
9630
|
{ command: "help", description: "Show available commands" },
|
|
9576
9631
|
{ command: "status", description: "Session, backend, model, and usage" },
|
|
9577
9632
|
{ command: "new", description: "Start a fresh conversation" },
|
|
@@ -9684,6 +9739,12 @@ var init_telegram2 = __esm({
|
|
|
9684
9739
|
}
|
|
9685
9740
|
}
|
|
9686
9741
|
});
|
|
9742
|
+
this.bot.on("inline_query", (ctx) => {
|
|
9743
|
+
if (!this.isAuthorized(ctx.from.id.toString())) return;
|
|
9744
|
+
this.handleInlineQuery(ctx).catch((err) => {
|
|
9745
|
+
error("[telegram] Inline query error:", err);
|
|
9746
|
+
});
|
|
9747
|
+
});
|
|
9687
9748
|
this.bot.catch((err) => {
|
|
9688
9749
|
error("[telegram] Unhandled error:", err);
|
|
9689
9750
|
});
|
|
@@ -9792,19 +9853,28 @@ var init_telegram2 = __esm({
|
|
|
9792
9853
|
for (const row of buttons) {
|
|
9793
9854
|
for (const btn of row) {
|
|
9794
9855
|
keyboard.text(btn.label, btn.data);
|
|
9856
|
+
if (btn.style === "success") keyboard.success();
|
|
9857
|
+
else if (btn.style === "danger") keyboard.danger();
|
|
9858
|
+
else if (btn.style === "primary") keyboard.primary();
|
|
9795
9859
|
}
|
|
9796
9860
|
keyboard.row();
|
|
9797
9861
|
}
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9862
|
+
try {
|
|
9863
|
+
const msg = await this.bot.api.sendMessage(numericChatId(chatId), text, {
|
|
9864
|
+
reply_markup: keyboard
|
|
9865
|
+
});
|
|
9866
|
+
return msg.message_id.toString();
|
|
9867
|
+
} catch {
|
|
9868
|
+
return void 0;
|
|
9869
|
+
}
|
|
9801
9870
|
}
|
|
9802
9871
|
async reactToMessage(chatId, messageId, emoji) {
|
|
9803
9872
|
try {
|
|
9804
9873
|
await this.bot.api.setMessageReaction(numericChatId(chatId), parseInt(messageId), [
|
|
9805
9874
|
{ type: "emoji", emoji }
|
|
9806
9875
|
]);
|
|
9807
|
-
} catch {
|
|
9876
|
+
} catch (err) {
|
|
9877
|
+
log(`[telegram] reactToMessage failed (chat=${chatId} msg=${messageId}): ${err}`);
|
|
9808
9878
|
}
|
|
9809
9879
|
}
|
|
9810
9880
|
/** Get the underlying Grammy Bot instance (for scheduler, etc.) */
|
|
@@ -9812,6 +9882,57 @@ var init_telegram2 = __esm({
|
|
|
9812
9882
|
return this.bot;
|
|
9813
9883
|
}
|
|
9814
9884
|
// ── Private helpers ──────────────────────────────────────
|
|
9885
|
+
async handleInlineQuery(ctx) {
|
|
9886
|
+
const query = ctx.inlineQuery?.query?.trim() ?? "";
|
|
9887
|
+
const results = [];
|
|
9888
|
+
if (query.length < 2) {
|
|
9889
|
+
const recent = getRecentMemories(10);
|
|
9890
|
+
for (const mem of recent) {
|
|
9891
|
+
results.push({
|
|
9892
|
+
type: "article",
|
|
9893
|
+
id: `mem-${mem.id}`,
|
|
9894
|
+
title: mem.trigger,
|
|
9895
|
+
description: mem.content.slice(0, 100),
|
|
9896
|
+
input_message_content: {
|
|
9897
|
+
message_text: mem.content.slice(0, 4096)
|
|
9898
|
+
}
|
|
9899
|
+
});
|
|
9900
|
+
}
|
|
9901
|
+
} else {
|
|
9902
|
+
const memories = searchMemories(query, 5);
|
|
9903
|
+
for (const mem of memories) {
|
|
9904
|
+
results.push({
|
|
9905
|
+
type: "article",
|
|
9906
|
+
id: `mem-${mem.id}`,
|
|
9907
|
+
title: mem.trigger,
|
|
9908
|
+
description: mem.content.slice(0, 100),
|
|
9909
|
+
input_message_content: {
|
|
9910
|
+
message_text: mem.content.slice(0, 4096)
|
|
9911
|
+
}
|
|
9912
|
+
});
|
|
9913
|
+
}
|
|
9914
|
+
const historyLimit = 10 - results.length;
|
|
9915
|
+
if (historyLimit > 0) {
|
|
9916
|
+
const messages = searchMessageLog(this.primaryChatId, query, historyLimit);
|
|
9917
|
+
for (const msg of messages) {
|
|
9918
|
+
const date = msg.created_at?.slice(0, 10) ?? "";
|
|
9919
|
+
const backend2 = msg.backend ?? "?";
|
|
9920
|
+
const role = msg.role === "user" ? "You" : "Assistant";
|
|
9921
|
+
const preview = msg.content.slice(0, 60).replace(/\n/g, " ");
|
|
9922
|
+
results.push({
|
|
9923
|
+
type: "article",
|
|
9924
|
+
id: `msg-${msg.id}`,
|
|
9925
|
+
title: `[${backend2} \xB7 ${date}] ${role}: ${preview}`,
|
|
9926
|
+
description: msg.content.slice(0, 100).replace(/\n/g, " "),
|
|
9927
|
+
input_message_content: {
|
|
9928
|
+
message_text: msg.content.slice(0, 4096)
|
|
9929
|
+
}
|
|
9930
|
+
});
|
|
9931
|
+
}
|
|
9932
|
+
}
|
|
9933
|
+
}
|
|
9934
|
+
await ctx.answerInlineQuery(results.slice(0, 10), { cache_time: 30 });
|
|
9935
|
+
}
|
|
9815
9936
|
trackAgentMessage(messageId, chatId) {
|
|
9816
9937
|
if (this.agentMessageIds.size >= 1e4) {
|
|
9817
9938
|
this.agentMessageIds.clear();
|
|
@@ -10576,6 +10697,17 @@ var init_heartbeat = __esm({
|
|
|
10576
10697
|
}
|
|
10577
10698
|
});
|
|
10578
10699
|
|
|
10700
|
+
// src/ui/format.ts
|
|
10701
|
+
function buildSectionHeader(label2, width = 26) {
|
|
10702
|
+
const inner = `\u2501\u2501 ${label2} `;
|
|
10703
|
+
return inner + "\u2501".repeat(Math.max(0, width - inner.length));
|
|
10704
|
+
}
|
|
10705
|
+
var init_format = __esm({
|
|
10706
|
+
"src/ui/format.ts"() {
|
|
10707
|
+
"use strict";
|
|
10708
|
+
}
|
|
10709
|
+
});
|
|
10710
|
+
|
|
10579
10711
|
// src/format-time.ts
|
|
10580
10712
|
function formatLocalDateTime(utcDatetime) {
|
|
10581
10713
|
const d = parseUtcDatetime(utcDatetime);
|
|
@@ -11627,6 +11759,85 @@ var init_wizard = __esm({
|
|
|
11627
11759
|
}
|
|
11628
11760
|
});
|
|
11629
11761
|
|
|
11762
|
+
// src/scheduler/humanize.ts
|
|
11763
|
+
function formatTime(hour, minute) {
|
|
11764
|
+
const h = hour % 12 || 12;
|
|
11765
|
+
const ampm = hour < 12 ? "AM" : "PM";
|
|
11766
|
+
return minute === 0 ? `${h}:00 ${ampm}` : `${h}:${String(minute).padStart(2, "0")} ${ampm}`;
|
|
11767
|
+
}
|
|
11768
|
+
function humanizeCron(expr, _tz) {
|
|
11769
|
+
if (expr.startsWith("every:")) {
|
|
11770
|
+
const ms = parseInt(expr.slice(6), 10);
|
|
11771
|
+
if (isNaN(ms)) return expr;
|
|
11772
|
+
const minutes = Math.round(ms / 6e4);
|
|
11773
|
+
if (minutes < 60) return `Every ${minutes} minute${minutes !== 1 ? "s" : ""}`;
|
|
11774
|
+
const hours = Math.round(minutes / 60);
|
|
11775
|
+
if (hours < 24) return `Every ${hours} hour${hours !== 1 ? "s" : ""}`;
|
|
11776
|
+
const days = Math.round(hours / 24);
|
|
11777
|
+
return `Every ${days} day${days !== 1 ? "s" : ""}`;
|
|
11778
|
+
}
|
|
11779
|
+
if (expr.startsWith("at:")) {
|
|
11780
|
+
const dateStr = expr.slice(3);
|
|
11781
|
+
try {
|
|
11782
|
+
const date = new Date(dateStr);
|
|
11783
|
+
const month2 = date.toLocaleString("en-US", { month: "short" });
|
|
11784
|
+
const day = date.getDate();
|
|
11785
|
+
const year = date.getFullYear();
|
|
11786
|
+
return `Once on ${month2} ${day}, ${year}`;
|
|
11787
|
+
} catch {
|
|
11788
|
+
return expr;
|
|
11789
|
+
}
|
|
11790
|
+
}
|
|
11791
|
+
const parts = expr.trim().split(/\s+/);
|
|
11792
|
+
if (parts.length !== 5) return expr;
|
|
11793
|
+
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
|
|
11794
|
+
if (minute === "*" && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
|
|
11795
|
+
return "Every minute";
|
|
11796
|
+
}
|
|
11797
|
+
if (minute.startsWith("*/") && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
|
|
11798
|
+
const n = parseInt(minute.slice(2), 10);
|
|
11799
|
+
return `Every ${n} minute${n !== 1 ? "s" : ""}`;
|
|
11800
|
+
}
|
|
11801
|
+
if (!minute.includes("*") && !minute.includes("/") && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
|
|
11802
|
+
const m = parseInt(minute, 10);
|
|
11803
|
+
return m === 0 ? "Every hour" : `Every hour at :${String(m).padStart(2, "0")}`;
|
|
11804
|
+
}
|
|
11805
|
+
if (minute === "0" && hour.startsWith("*/") && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
|
|
11806
|
+
const n = parseInt(hour.slice(2), 10);
|
|
11807
|
+
return `Every ${n} hour${n !== 1 ? "s" : ""}`;
|
|
11808
|
+
}
|
|
11809
|
+
const hasSpecificTime = !minute.includes("*") && !minute.includes("/") && !hour.includes("*") && !hour.includes("/");
|
|
11810
|
+
if (hasSpecificTime) {
|
|
11811
|
+
const h = parseInt(hour, 10);
|
|
11812
|
+
const m = parseInt(minute, 10);
|
|
11813
|
+
const timeStr = formatTime(h, m);
|
|
11814
|
+
if (dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
|
|
11815
|
+
return `Every day at ${timeStr}`;
|
|
11816
|
+
}
|
|
11817
|
+
if (dayOfMonth === "*" && month === "*" && dayOfWeek !== "*") {
|
|
11818
|
+
const dayNames = dayOfWeek.split(",").map((d) => {
|
|
11819
|
+
const idx = parseInt(d, 10);
|
|
11820
|
+
return DAYS[idx] ?? d;
|
|
11821
|
+
});
|
|
11822
|
+
if (dayNames.length === 1) return `Every ${dayNames[0]} at ${timeStr}`;
|
|
11823
|
+
return `Every ${dayNames.join(", ")} at ${timeStr}`;
|
|
11824
|
+
}
|
|
11825
|
+
if (dayOfMonth !== "*" && month === "*" && dayOfWeek === "*") {
|
|
11826
|
+
const d = parseInt(dayOfMonth, 10);
|
|
11827
|
+
const suffix = d === 1 || d === 21 || d === 31 ? "st" : d === 2 || d === 22 ? "nd" : d === 3 || d === 23 ? "rd" : "th";
|
|
11828
|
+
return `${d}${suffix} of every month at ${timeStr}`;
|
|
11829
|
+
}
|
|
11830
|
+
}
|
|
11831
|
+
return expr;
|
|
11832
|
+
}
|
|
11833
|
+
var DAYS;
|
|
11834
|
+
var init_humanize = __esm({
|
|
11835
|
+
"src/scheduler/humanize.ts"() {
|
|
11836
|
+
"use strict";
|
|
11837
|
+
DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
11838
|
+
}
|
|
11839
|
+
});
|
|
11840
|
+
|
|
11630
11841
|
// src/media/video.ts
|
|
11631
11842
|
async function analyzeVideo(videoBuffer, prompt, mimeType = "video/mp4") {
|
|
11632
11843
|
const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
|
|
@@ -11903,6 +12114,48 @@ var init_backend_cmd = __esm({
|
|
|
11903
12114
|
}
|
|
11904
12115
|
});
|
|
11905
12116
|
|
|
12117
|
+
// src/ui/pagination.ts
|
|
12118
|
+
function buildPaginatedKeyboard(opts) {
|
|
12119
|
+
const { items, page, callbackPrefix, renderItem, headerText, footerButtons } = opts;
|
|
12120
|
+
const pageSize = opts.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
12121
|
+
const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
|
|
12122
|
+
const safePage = Math.max(1, Math.min(page, totalPages));
|
|
12123
|
+
const start = (safePage - 1) * pageSize;
|
|
12124
|
+
const pageItems = items.slice(start, start + pageSize);
|
|
12125
|
+
const text = headerText(safePage, totalPages, items.length);
|
|
12126
|
+
const buttons = [];
|
|
12127
|
+
for (const [i, item] of pageItems.entries()) {
|
|
12128
|
+
const rendered = renderItem(item, start + i);
|
|
12129
|
+
buttons.push([rendered]);
|
|
12130
|
+
}
|
|
12131
|
+
if (totalPages > 1) {
|
|
12132
|
+
const navRow = [];
|
|
12133
|
+
if (safePage > 1) navRow.push({ label: `\u2190 Page ${safePage - 1}`, data: `${callbackPrefix}page:${safePage - 1}` });
|
|
12134
|
+
navRow.push({ label: `${safePage}/${totalPages}`, data: `${callbackPrefix}page:noop` });
|
|
12135
|
+
if (safePage < totalPages) navRow.push({ label: `Page ${safePage + 1} \u2192`, data: `${callbackPrefix}page:${safePage + 1}` });
|
|
12136
|
+
buttons.push(navRow);
|
|
12137
|
+
}
|
|
12138
|
+
if (footerButtons && footerButtons.length > 0) {
|
|
12139
|
+
buttons.push(footerButtons);
|
|
12140
|
+
}
|
|
12141
|
+
return { text, buttons };
|
|
12142
|
+
}
|
|
12143
|
+
function isPaginationCallback(data, prefix) {
|
|
12144
|
+
const pagePrefix = `${prefix}page:`;
|
|
12145
|
+
if (!data.startsWith(pagePrefix)) return null;
|
|
12146
|
+
const val = data.slice(pagePrefix.length);
|
|
12147
|
+
if (val === "noop") return null;
|
|
12148
|
+
const page = parseInt(val, 10);
|
|
12149
|
+
return isNaN(page) ? null : page;
|
|
12150
|
+
}
|
|
12151
|
+
var DEFAULT_PAGE_SIZE;
|
|
12152
|
+
var init_pagination = __esm({
|
|
12153
|
+
"src/ui/pagination.ts"() {
|
|
12154
|
+
"use strict";
|
|
12155
|
+
DEFAULT_PAGE_SIZE = 10;
|
|
12156
|
+
}
|
|
12157
|
+
});
|
|
12158
|
+
|
|
11906
12159
|
// src/reflection/propose.ts
|
|
11907
12160
|
var propose_exports = {};
|
|
11908
12161
|
__export(propose_exports, {
|
|
@@ -11972,40 +12225,40 @@ function buildProposalKeyboard(insightId, category) {
|
|
|
11972
12225
|
if (category === "codebase") {
|
|
11973
12226
|
return [
|
|
11974
12227
|
[
|
|
11975
|
-
{
|
|
11976
|
-
{
|
|
11977
|
-
{
|
|
12228
|
+
{ label: "Show Diff", data: `evolve:diff:${insightId}`, style: "primary" },
|
|
12229
|
+
{ label: "Skip", data: `evolve:skip:${insightId}` },
|
|
12230
|
+
{ label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
|
|
11978
12231
|
]
|
|
11979
12232
|
];
|
|
11980
12233
|
}
|
|
11981
12234
|
return [
|
|
11982
12235
|
[
|
|
11983
|
-
{
|
|
11984
|
-
{
|
|
11985
|
-
{
|
|
12236
|
+
{ label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
|
|
12237
|
+
{ label: "Skip", data: `evolve:skip:${insightId}` },
|
|
12238
|
+
{ label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
|
|
11986
12239
|
]
|
|
11987
12240
|
];
|
|
11988
12241
|
}
|
|
11989
12242
|
function buildEvolveMenuKeyboard() {
|
|
11990
12243
|
return [
|
|
11991
12244
|
[
|
|
11992
|
-
{
|
|
11993
|
-
{
|
|
12245
|
+
{ label: "Analyze Now", data: "evolve:analyze", style: "success" },
|
|
12246
|
+
{ label: "Review Proposals", data: "evolve:review" }
|
|
11994
12247
|
],
|
|
11995
12248
|
[
|
|
11996
|
-
{
|
|
11997
|
-
{
|
|
12249
|
+
{ label: "Stats", data: "evolve:stats" },
|
|
12250
|
+
{ label: "History", data: "evolve:history" }
|
|
11998
12251
|
],
|
|
11999
12252
|
[
|
|
12000
|
-
{
|
|
12001
|
-
{
|
|
12002
|
-
{
|
|
12253
|
+
{ label: "On/Off", data: "evolve:toggle" },
|
|
12254
|
+
{ label: "Undo", data: "evolve:undo", style: "danger" },
|
|
12255
|
+
{ label: "Model", data: "evolve:model" }
|
|
12003
12256
|
]
|
|
12004
12257
|
];
|
|
12005
12258
|
}
|
|
12006
12259
|
function buildUndoKeyboard(insights) {
|
|
12007
12260
|
return insights.map((ins) => [
|
|
12008
|
-
{
|
|
12261
|
+
{ label: `Undo #${ins.id}: ${ins.insight}`, data: `evolve:undo:${ins.id}`, style: "danger" }
|
|
12009
12262
|
]);
|
|
12010
12263
|
}
|
|
12011
12264
|
function buildModelKeyboard(currentMode) {
|
|
@@ -12016,8 +12269,9 @@ function buildModelKeyboard(currentMode) {
|
|
|
12016
12269
|
];
|
|
12017
12270
|
return [
|
|
12018
12271
|
modes.map(({ label: label2, mode }) => ({
|
|
12019
|
-
|
|
12020
|
-
|
|
12272
|
+
label: mode === currentMode ? `[${label2}]` : label2,
|
|
12273
|
+
data: `evolve:model:${mode}`,
|
|
12274
|
+
...mode === currentMode ? { style: "primary" } : {}
|
|
12021
12275
|
}))
|
|
12022
12276
|
];
|
|
12023
12277
|
}
|
|
@@ -12028,8 +12282,8 @@ var init_propose = __esm({
|
|
|
12028
12282
|
});
|
|
12029
12283
|
|
|
12030
12284
|
// src/router.ts
|
|
12031
|
-
import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2 } from "fs/promises";
|
|
12032
|
-
import { resolve as resolvePath } from "path";
|
|
12285
|
+
import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2, mkdir as mkdir2, readdir as readdir3, stat } from "fs/promises";
|
|
12286
|
+
import { resolve as resolvePath, join as join19 } from "path";
|
|
12033
12287
|
function parseMcpListOutput(output2) {
|
|
12034
12288
|
const results = [];
|
|
12035
12289
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -12082,6 +12336,7 @@ function formatModelShort(modelId) {
|
|
|
12082
12336
|
"claude-sonnet-4-6": "Sonnet 4.6",
|
|
12083
12337
|
"claude-haiku-4-5": "Haiku 4.5",
|
|
12084
12338
|
"gemini-3.1-pro-preview": "Gemini Pro 3.1",
|
|
12339
|
+
"gemini-2.5-pro": "Gemini 2.5 Pro",
|
|
12085
12340
|
"gemini-3-flash-preview": "Gemini 3 Flash",
|
|
12086
12341
|
"gpt-5.4": "GPT-5.4",
|
|
12087
12342
|
"gpt-5.3-codex": "GPT-5.3 Codex",
|
|
@@ -12108,22 +12363,208 @@ function resolveModel(chatId) {
|
|
|
12108
12363
|
return "unknown";
|
|
12109
12364
|
}
|
|
12110
12365
|
}
|
|
12366
|
+
async function sendHelpCategories(chatId, channel) {
|
|
12367
|
+
if (typeof channel.sendKeyboard !== "function") return;
|
|
12368
|
+
const header2 = `${buildSectionHeader("CC-Claw Commands")}`;
|
|
12369
|
+
const cats = Object.keys(HELP_CATEGORIES);
|
|
12370
|
+
const rows = [];
|
|
12371
|
+
for (let i = 0; i < cats.length; i += 2) {
|
|
12372
|
+
const row = [
|
|
12373
|
+
{ label: cats[i], data: `help:${cats[i].toLowerCase()}`, style: "primary" }
|
|
12374
|
+
];
|
|
12375
|
+
if (cats[i + 1]) {
|
|
12376
|
+
row.push({ label: cats[i + 1], data: `help:${cats[i + 1].toLowerCase()}`, style: "primary" });
|
|
12377
|
+
}
|
|
12378
|
+
rows.push(row);
|
|
12379
|
+
}
|
|
12380
|
+
await channel.sendKeyboard(chatId, header2, rows);
|
|
12381
|
+
}
|
|
12382
|
+
async function sendHelpCategory(chatId, category, channel) {
|
|
12383
|
+
if (typeof channel.sendKeyboard !== "function") return;
|
|
12384
|
+
const cat = Object.entries(HELP_CATEGORIES).find(([k]) => k.toLowerCase() === category.toLowerCase());
|
|
12385
|
+
if (!cat) return;
|
|
12386
|
+
const [name, cmds] = cat;
|
|
12387
|
+
const header2 = `${buildSectionHeader(`${name} (${cmds.length} commands)`)}`;
|
|
12388
|
+
const body = cmds.map((c) => `${c.cmd} \u2014 ${c.desc}`).join("\n");
|
|
12389
|
+
await channel.sendKeyboard(chatId, `${header2}
|
|
12390
|
+
|
|
12391
|
+
${body}`, [
|
|
12392
|
+
[{ label: "\u2190 Back to Categories", data: "help:back" }]
|
|
12393
|
+
]);
|
|
12394
|
+
}
|
|
12395
|
+
function buildUsagePricing() {
|
|
12396
|
+
const pricing = {};
|
|
12397
|
+
for (const a of getAllAdapters()) Object.assign(pricing, a.pricing);
|
|
12398
|
+
return { pricing, defaultPricing: { in: 3, out: 15, cache: 0.3 } };
|
|
12399
|
+
}
|
|
12400
|
+
function computeCost(tokens, model2, pricing, defaultPricing) {
|
|
12401
|
+
const p = pricing[model2] ?? defaultPricing;
|
|
12402
|
+
return tokens.input_tokens / 1e6 * p.in + tokens.output_tokens / 1e6 * p.out + tokens.cache_read_tokens / 1e6 * p.cache;
|
|
12403
|
+
}
|
|
12404
|
+
async function sendUnifiedUsage(chatId, channel, view) {
|
|
12405
|
+
const { pricing, defaultPricing } = buildUsagePricing();
|
|
12406
|
+
const fmt = (n) => `$${n.toFixed(4)}`;
|
|
12407
|
+
const fmtK = (n) => `${(n / 1e3).toFixed(0)}K`;
|
|
12408
|
+
const lines = [buildSectionHeader("Usage & Costs"), ""];
|
|
12409
|
+
if (view === "session") {
|
|
12410
|
+
const sessionStart = getSessionStartedAt(chatId);
|
|
12411
|
+
const chatModelUsage = getChatUsageByModel(chatId, sessionStart);
|
|
12412
|
+
let sessionTokens = 0;
|
|
12413
|
+
let sessionCost = 0;
|
|
12414
|
+
for (const u of chatModelUsage) {
|
|
12415
|
+
sessionTokens += u.input_tokens + u.output_tokens;
|
|
12416
|
+
sessionCost += computeCost(u, u.model, pricing, defaultPricing);
|
|
12417
|
+
}
|
|
12418
|
+
lines.push(`Session: ${fmtK(sessionTokens)} tokens \xB7 ~${fmt(sessionCost)}`);
|
|
12419
|
+
lines.push("");
|
|
12420
|
+
for (const bid of getAllBackendIds()) {
|
|
12421
|
+
const u = getBackendUsageInWindow(bid, "daily");
|
|
12422
|
+
const a = getAdapter(bid);
|
|
12423
|
+
const limit = getAllBackendLimits().find((l) => l.backend === bid && l.window === "daily");
|
|
12424
|
+
if (limit?.max_input_tokens) {
|
|
12425
|
+
lines.push(`${a.displayName}: ${fmtK(u.input_tokens)} / ${fmtK(limit.max_input_tokens)} limit (24h)`);
|
|
12426
|
+
} else {
|
|
12427
|
+
lines.push(`${a.displayName}: ${fmtK(u.input_tokens + u.output_tokens)} / no limit`);
|
|
12428
|
+
}
|
|
12429
|
+
}
|
|
12430
|
+
} else if (view === "model") {
|
|
12431
|
+
const allUsage = getAllTimeUsage();
|
|
12432
|
+
if (allUsage.length === 0) {
|
|
12433
|
+
lines.push("No usage history recorded yet.");
|
|
12434
|
+
} else {
|
|
12435
|
+
let grandTotal = 0;
|
|
12436
|
+
for (const u of allUsage) {
|
|
12437
|
+
const cost = computeCost(u, u.model, pricing, defaultPricing);
|
|
12438
|
+
grandTotal += cost;
|
|
12439
|
+
lines.push(`${formatModelShort(u.model)}: ${fmtK(u.input_tokens + u.output_tokens)} \xB7 ${fmt(cost)}`);
|
|
12440
|
+
}
|
|
12441
|
+
lines.push("", `Total: ${fmt(grandTotal)}`);
|
|
12442
|
+
}
|
|
12443
|
+
} else if (view === "limits") {
|
|
12444
|
+
await sendUsageLimits(chatId, channel);
|
|
12445
|
+
return;
|
|
12446
|
+
} else {
|
|
12447
|
+
const windowType = USAGE_WINDOW_MAP[view] ?? "daily";
|
|
12448
|
+
const windowLabel = view;
|
|
12449
|
+
for (const bid of getAllBackendIds()) {
|
|
12450
|
+
const u = view === "30d" ? getBackendUsageInWindow(bid, "weekly") : getBackendUsageInWindow(bid, windowType);
|
|
12451
|
+
const a = getAdapter(bid);
|
|
12452
|
+
const limit = getAllBackendLimits().find((l) => l.backend === bid && l.window === windowType);
|
|
12453
|
+
if (limit?.max_input_tokens) {
|
|
12454
|
+
lines.push(`${a.displayName}: ${fmtK(u.input_tokens)} / ${fmtK(limit.max_input_tokens)} limit (${windowLabel})`);
|
|
12455
|
+
} else {
|
|
12456
|
+
lines.push(`${a.displayName}: ${fmtK(u.input_tokens + u.output_tokens)} (${windowLabel}) \xB7 no limit`);
|
|
12457
|
+
}
|
|
12458
|
+
}
|
|
12459
|
+
}
|
|
12460
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12461
|
+
const buttons = [
|
|
12462
|
+
[
|
|
12463
|
+
{ label: view === "session" ? "Session \u2713" : "Session", data: "usage:session", ...view === "session" ? { style: "primary" } : {} },
|
|
12464
|
+
{ label: view === "24h" ? "24h \u2713" : "24h", data: "usage:24h", ...view === "24h" ? { style: "primary" } : {} },
|
|
12465
|
+
{ label: view === "7d" ? "7d \u2713" : "7d", data: "usage:7d", ...view === "7d" ? { style: "primary" } : {} },
|
|
12466
|
+
{ label: view === "30d" ? "30d \u2713" : "30d", data: "usage:30d", ...view === "30d" ? { style: "primary" } : {} }
|
|
12467
|
+
],
|
|
12468
|
+
[
|
|
12469
|
+
{ label: view === "model" ? "By Model \u2713" : "By Model", data: "usage:model", ...view === "model" ? { style: "primary" } : {} },
|
|
12470
|
+
{ label: "Set Limits", data: "usage:limits" }
|
|
12471
|
+
]
|
|
12472
|
+
];
|
|
12473
|
+
await channel.sendKeyboard(chatId, lines.join("\n"), buttons);
|
|
12474
|
+
} else {
|
|
12475
|
+
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
12476
|
+
}
|
|
12477
|
+
}
|
|
12478
|
+
async function sendUsageLimits(chatId, channel) {
|
|
12479
|
+
const limits2 = getAllBackendLimits();
|
|
12480
|
+
const fmtK = (n) => `${(n / 1e3).toFixed(0)}K`;
|
|
12481
|
+
const lines = [buildSectionHeader("Set Usage Limits"), ""];
|
|
12482
|
+
for (const bid of getAllBackendIds()) {
|
|
12483
|
+
const a = getAdapter(bid);
|
|
12484
|
+
const lim = limits2.find((l) => l.backend === bid);
|
|
12485
|
+
if (lim?.max_input_tokens) {
|
|
12486
|
+
lines.push(`${a.displayName}: ${fmtK(lim.max_input_tokens)} tokens / ${lim.window}`);
|
|
12487
|
+
} else {
|
|
12488
|
+
lines.push(`${a.displayName}: none`);
|
|
12489
|
+
}
|
|
12490
|
+
}
|
|
12491
|
+
lines.push("", "Use /limits <backend> <window> <tokens> to set.\nExample: /limits claude daily 500000");
|
|
12492
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12493
|
+
const setButtons = getAllBackendIds().map((bid) => ({
|
|
12494
|
+
label: `Set ${getAdapter(bid).displayName} Limit`,
|
|
12495
|
+
data: `usage:limits:set:${bid}`
|
|
12496
|
+
}));
|
|
12497
|
+
const rows = [];
|
|
12498
|
+
for (let i = 0; i < setButtons.length; i += 2) {
|
|
12499
|
+
rows.push(setButtons.slice(i, i + 2));
|
|
12500
|
+
}
|
|
12501
|
+
rows.push([
|
|
12502
|
+
{ label: "Clear All", data: "usage:limits:clear", style: "danger" }
|
|
12503
|
+
]);
|
|
12504
|
+
rows.push([
|
|
12505
|
+
{ label: "\u2190 Back to Usage", data: "usage:back" }
|
|
12506
|
+
]);
|
|
12507
|
+
await channel.sendKeyboard(chatId, lines.join("\n"), rows);
|
|
12508
|
+
} else {
|
|
12509
|
+
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
12510
|
+
}
|
|
12511
|
+
}
|
|
12512
|
+
function getMediaRetentionMs() {
|
|
12513
|
+
const hours = parseInt(process.env.MEDIA_RETENTION_HOURS ?? "24", 10);
|
|
12514
|
+
return (isNaN(hours) || hours < 1 ? 24 : hours) * 60 * 60 * 1e3;
|
|
12515
|
+
}
|
|
12516
|
+
async function saveMedia(buffer, prefix, ext) {
|
|
12517
|
+
await mkdir2(MEDIA_INCOMING_PATH, { recursive: true });
|
|
12518
|
+
const filename = `${prefix}-${Date.now()}.${ext}`;
|
|
12519
|
+
const fullPath = join19(MEDIA_INCOMING_PATH, filename);
|
|
12520
|
+
await writeFile3(fullPath, buffer);
|
|
12521
|
+
return fullPath;
|
|
12522
|
+
}
|
|
12523
|
+
async function cleanupOldMedia() {
|
|
12524
|
+
try {
|
|
12525
|
+
await mkdir2(MEDIA_INCOMING_PATH, { recursive: true });
|
|
12526
|
+
const retentionMs = getMediaRetentionMs();
|
|
12527
|
+
const retentionHours = Math.round(retentionMs / (60 * 60 * 1e3));
|
|
12528
|
+
const files = await readdir3(MEDIA_INCOMING_PATH);
|
|
12529
|
+
const now = Date.now();
|
|
12530
|
+
let removed = 0;
|
|
12531
|
+
for (const file of files) {
|
|
12532
|
+
try {
|
|
12533
|
+
const filePath = join19(MEDIA_INCOMING_PATH, file);
|
|
12534
|
+
const s = await stat(filePath);
|
|
12535
|
+
if (now - s.mtimeMs > retentionMs) {
|
|
12536
|
+
await unlink2(filePath);
|
|
12537
|
+
removed++;
|
|
12538
|
+
}
|
|
12539
|
+
} catch {
|
|
12540
|
+
}
|
|
12541
|
+
}
|
|
12542
|
+
if (removed > 0) log(`[media] Cleaned up ${removed} file(s) older than ${retentionHours}h from incoming/`);
|
|
12543
|
+
} catch {
|
|
12544
|
+
}
|
|
12545
|
+
}
|
|
12111
12546
|
function pickReactionEmoji(msg) {
|
|
12112
12547
|
switch (msg.type) {
|
|
12113
12548
|
case "voice":
|
|
12114
|
-
return "\u{
|
|
12549
|
+
return "\u{1F917}";
|
|
12115
12550
|
case "photo":
|
|
12116
12551
|
return "\u{1F440}";
|
|
12117
12552
|
case "video":
|
|
12118
|
-
return "\u{
|
|
12553
|
+
return "\u{1F440}";
|
|
12119
12554
|
case "document":
|
|
12120
|
-
return "\u{
|
|
12555
|
+
return "\u{1F913}";
|
|
12121
12556
|
case "command":
|
|
12122
12557
|
return "\u{1FAE1}";
|
|
12123
12558
|
default:
|
|
12124
12559
|
return "\u{1F468}\u200D\u{1F4BB}";
|
|
12125
12560
|
}
|
|
12126
12561
|
}
|
|
12562
|
+
function pickFallbackEmoji(userMessage) {
|
|
12563
|
+
for (const { pattern, emoji } of TONE_PATTERNS) {
|
|
12564
|
+
if (pattern.test(userMessage)) return emoji;
|
|
12565
|
+
}
|
|
12566
|
+
return "\u{1F44D}";
|
|
12567
|
+
}
|
|
12127
12568
|
async function handleResponseExhaustion(responseText, chatId, msg, channel) {
|
|
12128
12569
|
const raw = responseText.replace(/\n\n🧠 \[.+$/, "").trim();
|
|
12129
12570
|
if (raw.length > 300 || !isExhaustedMessage(raw)) return false;
|
|
@@ -12134,7 +12575,8 @@ async function handleResponseExhaustion(responseText, chatId, msg, channel) {
|
|
|
12134
12575
|
pendingFallbackMessages.set(chatId, { msg, channel, agentMode: msg.agentMode });
|
|
12135
12576
|
const buttons = otherBackends.map((a) => ({
|
|
12136
12577
|
label: `\u{1F504} ${a.displayName}`,
|
|
12137
|
-
data: `fallback:${a.id}:${chatId}
|
|
12578
|
+
data: `fallback:${a.id}:${chatId}`,
|
|
12579
|
+
style: "primary"
|
|
12138
12580
|
}));
|
|
12139
12581
|
buttons.push({ label: "\u274C Wait", data: `fallback:wait:${chatId}` });
|
|
12140
12582
|
await channel.sendKeyboard(
|
|
@@ -12201,13 +12643,40 @@ async function handleCommand(msg, channel) {
|
|
|
12201
12643
|
}
|
|
12202
12644
|
switch (command) {
|
|
12203
12645
|
case "start":
|
|
12204
|
-
case "help":
|
|
12205
|
-
|
|
12206
|
-
chatId,
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12646
|
+
case "help": {
|
|
12647
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12648
|
+
await sendHelpCategories(chatId, channel);
|
|
12649
|
+
} else {
|
|
12650
|
+
await channel.sendText(
|
|
12651
|
+
chatId,
|
|
12652
|
+
"CC-Claw Commands\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n" + Object.entries(HELP_CATEGORIES).map(
|
|
12653
|
+
([cat, cmds]) => `${cat}:
|
|
12654
|
+
${cmds.map((c) => ` ${c.cmd} \u2014 ${c.desc}`).join("\n")}`
|
|
12655
|
+
).join("\n\n"),
|
|
12656
|
+
"plain"
|
|
12657
|
+
);
|
|
12658
|
+
}
|
|
12659
|
+
break;
|
|
12660
|
+
}
|
|
12661
|
+
case "menu":
|
|
12662
|
+
case "m": {
|
|
12663
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12664
|
+
const header2 = `\u{1F43E} ${buildSectionHeader("CC-Claw")}`;
|
|
12665
|
+
await channel.sendKeyboard(chatId, header2, [
|
|
12666
|
+
[{ label: "New Chat", data: "menu:newchat" }, { label: "Status", data: "menu:status" }],
|
|
12667
|
+
[{ label: "Backend", data: "menu:backend" }, { label: "Model", data: "menu:model" }],
|
|
12668
|
+
[{ label: "Jobs", data: "menu:jobs" }, { label: "Memory", data: "menu:memory" }],
|
|
12669
|
+
[{ label: "History", data: "menu:history" }, { label: "Help", data: "menu:help" }]
|
|
12670
|
+
]);
|
|
12671
|
+
} else {
|
|
12672
|
+
await channel.sendText(
|
|
12673
|
+
chatId,
|
|
12674
|
+
"CC-Claw Menu:\n/newchat \xB7 /status \xB7 /backend \xB7 /model\n/jobs \xB7 /memory \xB7 /history \xB7 /help",
|
|
12675
|
+
"plain"
|
|
12676
|
+
);
|
|
12677
|
+
}
|
|
12210
12678
|
break;
|
|
12679
|
+
}
|
|
12211
12680
|
case "stop": {
|
|
12212
12681
|
const stopped = stopAgent(chatId);
|
|
12213
12682
|
await channel.sendText(
|
|
@@ -12215,6 +12684,11 @@ async function handleCommand(msg, channel) {
|
|
|
12215
12684
|
stopped ? "Stopping current task..." : "Nothing is running.",
|
|
12216
12685
|
"plain"
|
|
12217
12686
|
);
|
|
12687
|
+
if (stopped && typeof channel.sendKeyboard === "function") {
|
|
12688
|
+
await channel.sendKeyboard(chatId, "", [
|
|
12689
|
+
[{ label: "\u{1F195} New Chat", data: "menu:newchat" }]
|
|
12690
|
+
]);
|
|
12691
|
+
}
|
|
12218
12692
|
break;
|
|
12219
12693
|
}
|
|
12220
12694
|
case "permissions": {
|
|
@@ -12222,7 +12696,8 @@ async function handleCommand(msg, channel) {
|
|
|
12222
12696
|
if (typeof channel.sendKeyboard === "function") {
|
|
12223
12697
|
const buttons = Object.entries(PERM_MODES).map(([id, label2]) => [{
|
|
12224
12698
|
label: `${id === currentMode ? "\u2713 " : ""}${label2}`,
|
|
12225
|
-
data: `perms:${id}
|
|
12699
|
+
data: `perms:${id}`,
|
|
12700
|
+
...id === currentMode ? { style: "primary" } : {}
|
|
12226
12701
|
}]);
|
|
12227
12702
|
await channel.sendKeyboard(chatId, "Select permission mode:", buttons);
|
|
12228
12703
|
} else {
|
|
@@ -12238,7 +12713,8 @@ async function handleCommand(msg, channel) {
|
|
|
12238
12713
|
const currentVerbose = getVerboseLevel(chatId);
|
|
12239
12714
|
const buttons = Object.entries(VERBOSE_LEVELS).map(([id, label2]) => [{
|
|
12240
12715
|
label: `${id === currentVerbose ? "\u2713 " : ""}${label2}`,
|
|
12241
|
-
data: `verbose:${id}
|
|
12716
|
+
data: `verbose:${id}`,
|
|
12717
|
+
...id === currentVerbose ? { style: "primary" } : {}
|
|
12242
12718
|
}]);
|
|
12243
12719
|
if (typeof channel.sendKeyboard === "function") {
|
|
12244
12720
|
await channel.sendKeyboard(chatId, "Tool visibility level:", buttons);
|
|
@@ -12263,7 +12739,7 @@ async function handleCommand(msg, channel) {
|
|
|
12263
12739
|
}));
|
|
12264
12740
|
toolButtons.push(row);
|
|
12265
12741
|
}
|
|
12266
|
-
toolButtons.push([{ label: "Reset to defaults (all on)", data: "tool:reset" }]);
|
|
12742
|
+
toolButtons.push([{ label: "Reset to defaults (all on)", data: "tool:reset", style: "danger" }]);
|
|
12267
12743
|
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)." : "";
|
|
12268
12744
|
await channel.sendKeyboard(
|
|
12269
12745
|
chatId,
|
|
@@ -12279,13 +12755,31 @@ Tap to toggle:`,
|
|
|
12279
12755
|
}
|
|
12280
12756
|
case "new":
|
|
12281
12757
|
case "newchat": {
|
|
12758
|
+
const oldSessionId = getSessionId(chatId);
|
|
12759
|
+
const exchangeCount = getMessagePairCount(chatId);
|
|
12282
12760
|
const summarized = await summarizeSession(chatId);
|
|
12283
12761
|
clearSession(chatId);
|
|
12284
12762
|
clearChatGeminiSlot(chatId);
|
|
12285
12763
|
setSessionStartedAt(chatId);
|
|
12286
12764
|
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: "New session started", detail: { field: "session", action: "reset", summarized } });
|
|
12287
|
-
|
|
12288
|
-
|
|
12765
|
+
if (typeof channel.sendKeyboard === "function" && oldSessionId) {
|
|
12766
|
+
const msg2 = `\u2705 New session started. Previous session archived${exchangeCount > 0 ? ` (${exchangeCount} exchanges)` : ""}.`;
|
|
12767
|
+
const kbMsgId = await channel.sendKeyboard(chatId, msg2, [
|
|
12768
|
+
[{ label: "Undo", data: "newchat:undo" }]
|
|
12769
|
+
]);
|
|
12770
|
+
if (kbMsgId) {
|
|
12771
|
+
const prev = pendingNewchatUndo.get(chatId);
|
|
12772
|
+
if (prev) clearTimeout(prev.timer);
|
|
12773
|
+
const timer = setTimeout(async () => {
|
|
12774
|
+
pendingNewchatUndo.delete(chatId);
|
|
12775
|
+
await channel.editText?.(chatId, kbMsgId, msg2, "plain");
|
|
12776
|
+
}, 3e4);
|
|
12777
|
+
pendingNewchatUndo.set(chatId, { oldSessionId, timer, msgId: kbMsgId });
|
|
12778
|
+
}
|
|
12779
|
+
} else {
|
|
12780
|
+
const msg2 = summarized ? "Session summarized and saved. Fresh conversation started!" : "Fresh conversation started. What's on your mind?";
|
|
12781
|
+
await channel.sendText(chatId, msg2, "plain");
|
|
12782
|
+
}
|
|
12289
12783
|
break;
|
|
12290
12784
|
}
|
|
12291
12785
|
case "summarize": {
|
|
@@ -12312,12 +12806,51 @@ Tap to toggle:`,
|
|
|
12312
12806
|
}
|
|
12313
12807
|
break;
|
|
12314
12808
|
}
|
|
12315
|
-
await channel.
|
|
12809
|
+
const progressMsgId = await channel.sendTextReturningId?.(chatId, `Summarizing ${pairs} exchanges...`, "plain");
|
|
12810
|
+
if (!progressMsgId) {
|
|
12811
|
+
await channel.sendText(chatId, `Summarizing ${pairs} exchanges...`, "plain");
|
|
12812
|
+
}
|
|
12316
12813
|
const success2 = await summarizeSession(chatId);
|
|
12317
12814
|
if (success2) {
|
|
12318
|
-
|
|
12815
|
+
const recentSummary = listSessionSummaries(1, chatId);
|
|
12816
|
+
const summaryId = recentSummary[0]?.id;
|
|
12817
|
+
const pendingIds = getLoggedChatIds().filter((id) => id !== chatId);
|
|
12818
|
+
const doneText = `\u2705 Session saved to memory (${pairs} exchanges).`;
|
|
12819
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12820
|
+
if (progressMsgId) {
|
|
12821
|
+
await channel.editText?.(chatId, progressMsgId, doneText, "plain");
|
|
12822
|
+
}
|
|
12823
|
+
const buttons = [];
|
|
12824
|
+
const row = [];
|
|
12825
|
+
if (pendingIds.length > 0) {
|
|
12826
|
+
row.push({ label: `Summarize All (${pendingIds.length} pending)`, data: "summ:all", style: "primary" });
|
|
12827
|
+
}
|
|
12828
|
+
if (summaryId) {
|
|
12829
|
+
row.push({ label: "Undo", data: "summ:undo" });
|
|
12830
|
+
}
|
|
12831
|
+
if (row.length > 0) {
|
|
12832
|
+
buttons.push(row);
|
|
12833
|
+
const kbMsgId = await channel.sendKeyboard(chatId, progressMsgId ? "" : doneText, buttons);
|
|
12834
|
+
if (summaryId && kbMsgId) {
|
|
12835
|
+
const prev = pendingSummaryUndo.get(chatId);
|
|
12836
|
+
if (prev) clearTimeout(prev.timer);
|
|
12837
|
+
const timer = setTimeout(async () => {
|
|
12838
|
+
pendingSummaryUndo.delete(chatId);
|
|
12839
|
+
await channel.editText?.(chatId, kbMsgId, `\u2705 Session saved to memory (${pairs} exchanges).`, "plain");
|
|
12840
|
+
}, 3e4);
|
|
12841
|
+
pendingSummaryUndo.set(chatId, { summaryId, timer, msgId: kbMsgId });
|
|
12842
|
+
}
|
|
12843
|
+
}
|
|
12844
|
+
} else {
|
|
12845
|
+
await channel.sendText(chatId, doneText, "plain");
|
|
12846
|
+
}
|
|
12319
12847
|
} else {
|
|
12320
|
-
|
|
12848
|
+
const failText = "Summarization failed. Session log preserved for retry.";
|
|
12849
|
+
if (progressMsgId) {
|
|
12850
|
+
await channel.editText?.(chatId, progressMsgId, failText, "plain");
|
|
12851
|
+
} else {
|
|
12852
|
+
await channel.sendText(chatId, failText, "plain");
|
|
12853
|
+
}
|
|
12321
12854
|
}
|
|
12322
12855
|
}
|
|
12323
12856
|
break;
|
|
@@ -12366,7 +12899,6 @@ Tap to toggle:`,
|
|
|
12366
12899
|
}
|
|
12367
12900
|
const iStats = getIntentStats();
|
|
12368
12901
|
const iTotal = iStats.chat + iStats.agentic;
|
|
12369
|
-
const intentLine = iTotal > 0 ? `\u26A1 ${iStats.chat} chat \xB7 ${iStats.agentic} agentic (${(iStats.chat / iTotal * 100).toFixed(0)}% fast-path)` : `\u26A1 No messages classified yet`;
|
|
12370
12902
|
let geminiSlotInfo = "";
|
|
12371
12903
|
if ((backendId ?? "claude") === "gemini") {
|
|
12372
12904
|
try {
|
|
@@ -12379,30 +12911,46 @@ Tap to toggle:`,
|
|
|
12379
12911
|
} catch {
|
|
12380
12912
|
}
|
|
12381
12913
|
}
|
|
12914
|
+
const iQuick = iStats.chat;
|
|
12915
|
+
const iDeep = iStats.agentic;
|
|
12916
|
+
const intentLine = iTotal > 0 ? `\u26A1 ${iTotal} messages: ${iQuick} quick, ${iDeep} deep` : `\u26A1 No messages classified yet`;
|
|
12382
12917
|
const lines = [
|
|
12383
12918
|
`\u{1F43E} CC-Claw v${VERSION}`,
|
|
12384
12919
|
`\u23F1 Uptime: ${uptimeStr}`,
|
|
12385
12920
|
``,
|
|
12386
|
-
|
|
12921
|
+
buildSectionHeader("Engine"),
|
|
12387
12922
|
`\u{1F9E0} ${adapter?.displayName ?? backendId ?? "not set"} \xB7 ${formatModelShort(model2)}${geminiSlotInfo}`,
|
|
12388
12923
|
`\u{1F4AD} Think: ${thinking2} \xB7 Mode: ${mode}`,
|
|
12389
12924
|
`\u{1F916} Agents: ${getAgentMode(chatId)}`,
|
|
12390
12925
|
`\u{1F507} Voice: ${voice2 ? "on" : "off"} \xB7 Sig: ${modelSig}`,
|
|
12391
12926
|
``,
|
|
12392
|
-
|
|
12927
|
+
buildSectionHeader("Session"),
|
|
12393
12928
|
`\u{1F4CB} ${sessionId ? sessionId.slice(0, 12) + "..." : "no active session"}`,
|
|
12394
12929
|
`\u{1F4C1} ${cwd ?? "default workspace"}`,
|
|
12395
12930
|
`\u{1F4D0} Context: ${ctxBar} ${usedK}K/${maxK}K (${contextPct.toFixed(1)}%)`,
|
|
12396
12931
|
``,
|
|
12397
|
-
|
|
12932
|
+
buildSectionHeader("Usage"),
|
|
12398
12933
|
`\u{1F4E8} ${usage2.request_count} requests \xB7 ${(usage2.input_tokens / 1e3).toFixed(0)}K in \xB7 ${(usage2.output_tokens / 1e3).toFixed(0)}K out`,
|
|
12399
12934
|
`\u{1F4BE} ${(usage2.cache_read_tokens / 1e3).toFixed(0)}K cached`,
|
|
12400
12935
|
...limitsLine ? [limitsLine] : [],
|
|
12401
12936
|
``,
|
|
12402
|
-
|
|
12937
|
+
buildSectionHeader("Activity"),
|
|
12403
12938
|
intentLine
|
|
12404
12939
|
];
|
|
12405
|
-
|
|
12940
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12941
|
+
await channel.sendKeyboard(chatId, lines.join("\n"), [
|
|
12942
|
+
[
|
|
12943
|
+
{ label: "Switch Backend", data: "menu:backend", style: "primary" },
|
|
12944
|
+
{ label: "Switch Model", data: "menu:model", style: "primary" }
|
|
12945
|
+
],
|
|
12946
|
+
[
|
|
12947
|
+
{ label: "Change Mode", data: "menu:permissions" },
|
|
12948
|
+
{ label: "Change Style", data: "menu:style" }
|
|
12949
|
+
]
|
|
12950
|
+
]);
|
|
12951
|
+
} else {
|
|
12952
|
+
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
12953
|
+
}
|
|
12406
12954
|
break;
|
|
12407
12955
|
}
|
|
12408
12956
|
case "backend": {
|
|
@@ -12416,7 +12964,8 @@ Tap to toggle:`,
|
|
|
12416
12964
|
if (typeof channel.sendKeyboard === "function") {
|
|
12417
12965
|
const buttons = adapters2.map((a) => [{
|
|
12418
12966
|
label: `${a.id === currentBackend ? "\u2713 " : ""}${a.displayName}`,
|
|
12419
|
-
data: `backend:${a.id}
|
|
12967
|
+
data: `backend:${a.id}`,
|
|
12968
|
+
...a.id === currentBackend ? { style: "primary" } : {}
|
|
12420
12969
|
}]);
|
|
12421
12970
|
await channel.sendKeyboard(chatId, "Select AI backend:", buttons);
|
|
12422
12971
|
} else {
|
|
@@ -12502,7 +13051,8 @@ Use: /gemini_accounts <name> to pin`, "plain");
|
|
|
12502
13051
|
const tag = info.thinking === "adjustable" ? " \u26A1" : "";
|
|
12503
13052
|
return [{
|
|
12504
13053
|
label: `${id === current ? "\u2713 " : ""}${info.label}${tag}`,
|
|
12505
|
-
data: `model:${id}
|
|
13054
|
+
data: `model:${id}`,
|
|
13055
|
+
...id === current ? { style: "primary" } : {}
|
|
12506
13056
|
}];
|
|
12507
13057
|
});
|
|
12508
13058
|
await channel.sendKeyboard(chatId, `Models for ${adapter.displayName}:`, buttons);
|
|
@@ -12524,15 +13074,18 @@ Use: /gemini_accounts <name> to pin`, "plain");
|
|
|
12524
13074
|
const current = getSummarizer(chatId);
|
|
12525
13075
|
const currentLabel = current.backend === "off" ? "Off" : current.backend ? `${current.backend}:${current.model ?? "default"}` : `Auto (${adapter?.summarizerModel ?? "default"})`;
|
|
12526
13076
|
if (typeof channel.sendKeyboard === "function") {
|
|
13077
|
+
const isAuto = !current.backend && current.backend !== "off";
|
|
13078
|
+
const isOff = current.backend === "off";
|
|
12527
13079
|
const buttons = [
|
|
12528
|
-
[{ label: `Auto (use active backend)${
|
|
12529
|
-
[{ label: `Off (disable)${
|
|
13080
|
+
[{ label: `Auto (use active backend)${isAuto ? " \u2713" : ""}`, data: "summarizer:auto", ...isAuto ? { style: "primary" } : {} }],
|
|
13081
|
+
[{ label: `Off (disable)${isOff ? " \u2713" : ""}`, data: "summarizer:off", ...isOff ? { style: "primary" } : {} }]
|
|
12530
13082
|
];
|
|
12531
13083
|
for (const a of getAllAdapters()) {
|
|
12532
13084
|
const pinned = current.backend === a.id;
|
|
12533
13085
|
buttons.push([{
|
|
12534
13086
|
label: `${pinned ? "\u2713 " : ""}${a.displayName}: ${a.summarizerModel}`,
|
|
12535
|
-
data: `summarizer:${a.id}:${a.summarizerModel}
|
|
13087
|
+
data: `summarizer:${a.id}:${a.summarizerModel}`,
|
|
13088
|
+
...pinned ? { style: "primary" } : {}
|
|
12536
13089
|
}]);
|
|
12537
13090
|
}
|
|
12538
13091
|
await channel.sendKeyboard(chatId, `Session summarizer (current: ${currentLabel}):`, buttons);
|
|
@@ -12543,112 +13096,37 @@ Use /summarizer auto, /summarizer off, or /summarizer <backend>:<model>`, "plain
|
|
|
12543
13096
|
}
|
|
12544
13097
|
break;
|
|
12545
13098
|
}
|
|
12546
|
-
case "cost":
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
}
|
|
12557
|
-
const lines = ["All-time cost breakdown by model:", ""];
|
|
12558
|
-
let grandTotal = 0;
|
|
12559
|
-
let grandRequests = 0;
|
|
12560
|
-
for (const u of allUsage) {
|
|
12561
|
-
const p = pricing[u.model] ?? defaultPricing;
|
|
12562
|
-
const costIn = u.input_tokens / 1e6 * p.in;
|
|
12563
|
-
const costOut = u.output_tokens / 1e6 * p.out;
|
|
12564
|
-
const costCache = u.cache_read_tokens / 1e6 * p.cache;
|
|
12565
|
-
const modelTotal = costIn + costOut + costCache;
|
|
12566
|
-
grandTotal += modelTotal;
|
|
12567
|
-
grandRequests += u.request_count;
|
|
12568
|
-
lines.push(
|
|
12569
|
-
`${u.model}:`,
|
|
12570
|
-
` Requests: ${u.request_count.toLocaleString()}`,
|
|
12571
|
-
` Input: ${u.input_tokens.toLocaleString()} \u2192 ${fmt(costIn)}`,
|
|
12572
|
-
` Output: ${u.output_tokens.toLocaleString()} \u2192 ${fmt(costOut)}`,
|
|
12573
|
-
` Cache read: ${u.cache_read_tokens.toLocaleString()} \u2192 ${fmt(costCache)}`,
|
|
12574
|
-
` Subtotal: ${fmt(modelTotal)}`,
|
|
12575
|
-
""
|
|
12576
|
-
);
|
|
12577
|
-
}
|
|
12578
|
-
lines.push(
|
|
12579
|
-
`Grand total: ${fmt(grandTotal)} (${grandRequests.toLocaleString()} requests)`,
|
|
12580
|
-
"",
|
|
12581
|
-
"Estimates based on API pricing. Actual costs may differ if you're on a subscription plan."
|
|
12582
|
-
);
|
|
12583
|
-
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
12584
|
-
} else {
|
|
12585
|
-
const sessionStart = getSessionStartedAt(chatId);
|
|
12586
|
-
const chatModelUsage = getChatUsageByModel(chatId, sessionStart);
|
|
12587
|
-
if (chatModelUsage.length > 0) {
|
|
12588
|
-
const sessionLabel = sessionStart ? "this session" : "this chat";
|
|
12589
|
-
const lines = [`Cost estimate for ${sessionLabel}:`, ""];
|
|
12590
|
-
let grandTotal = 0;
|
|
12591
|
-
let grandRequests = 0;
|
|
12592
|
-
for (const u of chatModelUsage) {
|
|
12593
|
-
const p = pricing[u.model] ?? defaultPricing;
|
|
12594
|
-
const costIn = u.input_tokens / 1e6 * p.in;
|
|
12595
|
-
const costOut = u.output_tokens / 1e6 * p.out;
|
|
12596
|
-
const costCache = u.cache_read_tokens / 1e6 * p.cache;
|
|
12597
|
-
const modelTotal = costIn + costOut + costCache;
|
|
12598
|
-
grandTotal += modelTotal;
|
|
12599
|
-
grandRequests += u.request_count;
|
|
12600
|
-
lines.push(
|
|
12601
|
-
`${u.model}:`,
|
|
12602
|
-
` Requests: ${u.request_count.toLocaleString()}`,
|
|
12603
|
-
` Input: ${u.input_tokens.toLocaleString()} \u2192 ${fmt(costIn)}`,
|
|
12604
|
-
` Output: ${u.output_tokens.toLocaleString()} \u2192 ${fmt(costOut)}`,
|
|
12605
|
-
` Cache read: ${u.cache_read_tokens.toLocaleString()} \u2192 ${fmt(costCache)}`,
|
|
12606
|
-
` Subtotal: ${fmt(modelTotal)}`,
|
|
12607
|
-
""
|
|
12608
|
-
);
|
|
13099
|
+
case "cost":
|
|
13100
|
+
case "limits":
|
|
13101
|
+
case "usage": {
|
|
13102
|
+
if (commandArgs && command === "limits") {
|
|
13103
|
+
const parts = commandArgs.split(/\s+/);
|
|
13104
|
+
if (parts.length >= 3) {
|
|
13105
|
+
const [bk, win, val] = parts;
|
|
13106
|
+
if (!getAllBackendIds().includes(bk)) {
|
|
13107
|
+
await channel.sendText(chatId, `Unknown backend: ${bk}. Available: ${getAllBackendIds().join(", ")}`, "plain");
|
|
13108
|
+
break;
|
|
12609
13109
|
}
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
} else if (sessionStart) {
|
|
12618
|
-
await channel.sendText(chatId, "No usage recorded this session yet.\nUse /cost all for all-time totals.", "plain");
|
|
12619
|
-
} else {
|
|
12620
|
-
const usage2 = getUsage(chatId);
|
|
12621
|
-
if (usage2.request_count === 0) {
|
|
12622
|
-
await channel.sendText(chatId, "No usage recorded for this chat yet.", "plain");
|
|
13110
|
+
if (!["hourly", "daily", "weekly"].includes(win)) {
|
|
13111
|
+
await channel.sendText(chatId, "Window must be: hourly, daily, or weekly", "plain");
|
|
13112
|
+
break;
|
|
13113
|
+
}
|
|
13114
|
+
if (val === "off") {
|
|
13115
|
+
clearBackendLimit(bk, win);
|
|
13116
|
+
await channel.sendText(chatId, `Limit removed for ${bk} (${win}).`, "plain");
|
|
12623
13117
|
} else {
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
costModel = getModel(chatId) ?? "unknown";
|
|
13118
|
+
const tokens = parseInt(val, 10);
|
|
13119
|
+
if (isNaN(tokens) || tokens <= 0) {
|
|
13120
|
+
await channel.sendText(chatId, "Token limit must be a positive number.", "plain");
|
|
13121
|
+
break;
|
|
12629
13122
|
}
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
const costOut = usage2.output_tokens / 1e6 * p.out;
|
|
12633
|
-
const costCache = usage2.cache_read_tokens / 1e6 * p.cache;
|
|
12634
|
-
const totalCost = costIn + costOut + costCache;
|
|
12635
|
-
const lines = [
|
|
12636
|
-
`Cost estimate for this chat (${costModel}):`,
|
|
12637
|
-
"",
|
|
12638
|
-
`Requests: ${usage2.request_count}`,
|
|
12639
|
-
`Input: ${usage2.input_tokens.toLocaleString()} tokens \u2192 ${fmt(costIn)}`,
|
|
12640
|
-
`Output: ${usage2.output_tokens.toLocaleString()} tokens \u2192 ${fmt(costOut)}`,
|
|
12641
|
-
`Cache read: ${usage2.cache_read_tokens.toLocaleString()} tokens \u2192 ${fmt(costCache)}`,
|
|
12642
|
-
"",
|
|
12643
|
-
`Total est.: ${fmt(totalCost)}`,
|
|
12644
|
-
"",
|
|
12645
|
-
"Per-model breakdown unavailable (legacy data).",
|
|
12646
|
-
"Estimates based on API pricing. Actual costs may differ if you're on a subscription plan."
|
|
12647
|
-
];
|
|
12648
|
-
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
13123
|
+
setBackendLimit(bk, win, tokens);
|
|
13124
|
+
await channel.sendText(chatId, `Limit set: ${bk} (${win}) = ${(tokens / 1e3).toFixed(0)}K input tokens`, "plain");
|
|
12649
13125
|
}
|
|
13126
|
+
break;
|
|
12650
13127
|
}
|
|
12651
13128
|
}
|
|
13129
|
+
await sendUnifiedUsage(chatId, channel, "session");
|
|
12652
13130
|
break;
|
|
12653
13131
|
}
|
|
12654
13132
|
case "cwd": {
|
|
@@ -12751,15 +13229,37 @@ ${lines.join("\n")}`, "plain");
|
|
|
12751
13229
|
break;
|
|
12752
13230
|
}
|
|
12753
13231
|
case "memory": {
|
|
13232
|
+
if (commandArgs?.startsWith("edit ")) {
|
|
13233
|
+
const editMatch = commandArgs.match(/^edit\s+(\d+)\s+(.+)/);
|
|
13234
|
+
if (!editMatch) {
|
|
13235
|
+
await channel.sendText(chatId, "Usage: /memory edit <id> <new content>", "plain");
|
|
13236
|
+
break;
|
|
13237
|
+
}
|
|
13238
|
+
const editId = parseInt(editMatch[1], 10);
|
|
13239
|
+
const newContent = editMatch[2];
|
|
13240
|
+
const mem = getMemoryById(editId);
|
|
13241
|
+
if (!mem) {
|
|
13242
|
+
await channel.sendText(chatId, "Memory not found.", "plain");
|
|
13243
|
+
break;
|
|
13244
|
+
}
|
|
13245
|
+
deleteMemoryById(editId);
|
|
13246
|
+
saveMemoryWithEmbedding(mem.trigger, newContent, mem.type);
|
|
13247
|
+
await channel.sendText(chatId, `Memory #${editId} updated.`, "plain");
|
|
13248
|
+
break;
|
|
13249
|
+
}
|
|
12754
13250
|
const memories = listMemories();
|
|
12755
13251
|
if (memories.length === 0) {
|
|
12756
13252
|
await channel.sendText(chatId, "No memories stored yet.", "plain");
|
|
12757
13253
|
return;
|
|
12758
13254
|
}
|
|
12759
|
-
|
|
12760
|
-
(
|
|
12761
|
-
|
|
12762
|
-
|
|
13255
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13256
|
+
await sendMemoryPage(chatId, channel, 1);
|
|
13257
|
+
} else {
|
|
13258
|
+
const lines = memories.map(
|
|
13259
|
+
(m) => `- ${m.trigger}: ${m.content} (salience: ${m.salience.toFixed(2)})`
|
|
13260
|
+
);
|
|
13261
|
+
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
13262
|
+
}
|
|
12763
13263
|
break;
|
|
12764
13264
|
}
|
|
12765
13265
|
case "remember": {
|
|
@@ -12771,6 +13271,11 @@ ${lines.join("\n")}`, "plain");
|
|
|
12771
13271
|
const trigger = content.split(/\s+/).slice(0, 3).join(" ");
|
|
12772
13272
|
saveMemoryWithEmbedding(trigger, content, "semantic");
|
|
12773
13273
|
await channel.sendText(chatId, "Got it, I'll remember that.", "plain");
|
|
13274
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13275
|
+
await channel.sendKeyboard(chatId, "", [
|
|
13276
|
+
[{ label: "\u{1F4DA} View Memories", data: "menu:memory" }]
|
|
13277
|
+
]);
|
|
13278
|
+
}
|
|
12774
13279
|
break;
|
|
12775
13280
|
}
|
|
12776
13281
|
case "forget": {
|
|
@@ -12791,8 +13296,8 @@ ${lines.join("\n")}`, "plain");
|
|
|
12791
13296
|
if (typeof channel.sendKeyboard === "function") {
|
|
12792
13297
|
await channel.sendKeyboard(chatId, `\u{1F3A7} Voice responses: ${vcEnabled ? "ON" : "OFF"}`, [
|
|
12793
13298
|
[
|
|
12794
|
-
{ label: `${vcEnabled ? "" : "\u2713 "}\u{1F507} Off`, data: "voice:off" },
|
|
12795
|
-
{ label: `${vcEnabled ? "\u2713 " : ""}\u{1F50A} On`, data: "voice:on" }
|
|
13299
|
+
{ label: `${vcEnabled ? "" : "\u2713 "}\u{1F507} Off`, data: "voice:off", ...!vcEnabled ? { style: "danger" } : {} },
|
|
13300
|
+
{ label: `${vcEnabled ? "\u2713 " : ""}\u{1F50A} On`, data: "voice:on", ...vcEnabled ? { style: "success" } : {} }
|
|
12796
13301
|
]
|
|
12797
13302
|
]);
|
|
12798
13303
|
} else {
|
|
@@ -12810,9 +13315,9 @@ ${lines.join("\n")}`, "plain");
|
|
|
12810
13315
|
if (typeof channel.sendKeyboard === "function") {
|
|
12811
13316
|
await channel.sendKeyboard(chatId, "\u{1F5E3}\uFE0F AI Response Style:", [
|
|
12812
13317
|
[
|
|
12813
|
-
{ label: `${currentStyle === "concise" ? "\u2713 " : ""}Concise`, data: "style:concise" },
|
|
12814
|
-
{ label: `${currentStyle === "normal" ? "\u2713 " : ""}Normal`, data: "style:normal" },
|
|
12815
|
-
{ label: `${currentStyle === "detailed" ? "\u2713 " : ""}Detailed`, data: "style:detailed" }
|
|
13318
|
+
{ label: `${currentStyle === "concise" ? "\u2713 " : ""}Concise`, data: "style:concise", ...currentStyle === "concise" ? { style: "primary" } : {} },
|
|
13319
|
+
{ label: `${currentStyle === "normal" ? "\u2713 " : ""}Normal`, data: "style:normal", ...currentStyle === "normal" ? { style: "primary" } : {} },
|
|
13320
|
+
{ label: `${currentStyle === "detailed" ? "\u2713 " : ""}Detailed`, data: "style:detailed", ...currentStyle === "detailed" ? { style: "primary" } : {} }
|
|
12816
13321
|
]
|
|
12817
13322
|
]);
|
|
12818
13323
|
} else {
|
|
@@ -12826,8 +13331,8 @@ ${lines.join("\n")}`, "plain");
|
|
|
12826
13331
|
await channel.sendKeyboard(chatId, `Model Signature: ${currentSig === "on" ? "ON" : "OFF"}
|
|
12827
13332
|
Appends model + thinking level to each response.`, [
|
|
12828
13333
|
[
|
|
12829
|
-
{ label: currentSig === "on" ? "\u2713 On" : "On", data: "model_sig:on" },
|
|
12830
|
-
{ label: currentSig !== "on" ? "\u2713 Off" : "Off", data: "model_sig:off" }
|
|
13334
|
+
{ label: currentSig === "on" ? "\u2713 On" : "On", data: "model_sig:on", ...currentSig === "on" ? { style: "success" } : {} },
|
|
13335
|
+
{ label: currentSig !== "on" ? "\u2713 Off" : "Off", data: "model_sig:off", ...currentSig !== "on" ? { style: "danger" } : {} }
|
|
12831
13336
|
]
|
|
12832
13337
|
]);
|
|
12833
13338
|
} else {
|
|
@@ -12874,32 +13379,12 @@ Appends model + thinking level to each response.`, [
|
|
|
12874
13379
|
break;
|
|
12875
13380
|
}
|
|
12876
13381
|
case "jobs": {
|
|
12877
|
-
|
|
12878
|
-
if (jobs.length === 0) {
|
|
12879
|
-
await channel.sendText(chatId, "No scheduled jobs.", "plain");
|
|
12880
|
-
return;
|
|
12881
|
-
}
|
|
12882
|
-
const lines = jobs.map((j) => {
|
|
12883
|
-
const status = !j.active ? "cancelled" : !j.enabled ? "paused" : "active";
|
|
12884
|
-
const schedule2 = j.cron ?? (j.atTime ? `at ${j.atTime}` : j.everyMs ? `every ${j.everyMs / 1e3}s` : "?");
|
|
12885
|
-
const tz = j.timezone !== "UTC" ? ` (${j.timezone})` : "";
|
|
12886
|
-
const backend2 = j.backend ?? "default";
|
|
12887
|
-
const model2 = j.model ?? "default";
|
|
12888
|
-
const failures = j.consecutiveFailures > 0 ? ` [${j.consecutiveFailures} failures]` : "";
|
|
12889
|
-
const lastRun = j.lastRunAt ? `
|
|
12890
|
-
Last: ${j.lastRunAt}` : "";
|
|
12891
|
-
const nextRun = j.nextRunAt ? `
|
|
12892
|
-
Next: ${j.nextRunAt}` : "";
|
|
12893
|
-
return `#${j.id} [${status}] ${schedule2}${tz}
|
|
12894
|
-
${j.description}
|
|
12895
|
-
Backend: ${backend2} / ${model2}${failures}${lastRun}${nextRun}`;
|
|
12896
|
-
});
|
|
12897
|
-
await channel.sendText(chatId, lines.join("\n\n"), "plain");
|
|
13382
|
+
await sendJobsBoard(chatId, channel, 1);
|
|
12898
13383
|
break;
|
|
12899
13384
|
}
|
|
12900
13385
|
case "cancel": {
|
|
12901
13386
|
if (!commandArgs) {
|
|
12902
|
-
await
|
|
13387
|
+
await sendJobPicker(chatId, channel, "cancel");
|
|
12903
13388
|
return;
|
|
12904
13389
|
}
|
|
12905
13390
|
const id = parseInt(commandArgs, 10);
|
|
@@ -12913,7 +13398,7 @@ Appends model + thinking level to each response.`, [
|
|
|
12913
13398
|
}
|
|
12914
13399
|
case "pause": {
|
|
12915
13400
|
if (!commandArgs) {
|
|
12916
|
-
await
|
|
13401
|
+
await sendJobPicker(chatId, channel, "pause");
|
|
12917
13402
|
return;
|
|
12918
13403
|
}
|
|
12919
13404
|
const pauseId = parseInt(commandArgs, 10);
|
|
@@ -12927,7 +13412,7 @@ Appends model + thinking level to each response.`, [
|
|
|
12927
13412
|
}
|
|
12928
13413
|
case "resume": {
|
|
12929
13414
|
if (!commandArgs) {
|
|
12930
|
-
await
|
|
13415
|
+
await sendJobPicker(chatId, channel, "resume");
|
|
12931
13416
|
return;
|
|
12932
13417
|
}
|
|
12933
13418
|
const resumeId = parseInt(commandArgs, 10);
|
|
@@ -12941,7 +13426,7 @@ Appends model + thinking level to each response.`, [
|
|
|
12941
13426
|
}
|
|
12942
13427
|
case "run": {
|
|
12943
13428
|
if (!commandArgs) {
|
|
12944
|
-
await
|
|
13429
|
+
await sendJobPicker(chatId, channel, "run");
|
|
12945
13430
|
return;
|
|
12946
13431
|
}
|
|
12947
13432
|
const runJobId = parseInt(commandArgs, 10);
|
|
@@ -12952,20 +13437,24 @@ Appends model + thinking level to each response.`, [
|
|
|
12952
13437
|
}
|
|
12953
13438
|
case "runs": {
|
|
12954
13439
|
const runsJobId = commandArgs ? parseInt(commandArgs, 10) : void 0;
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
13440
|
+
if (runsJobId) {
|
|
13441
|
+
await sendJobRunsView(chatId, runsJobId, channel, 1);
|
|
13442
|
+
} else {
|
|
13443
|
+
const runs = getJobRuns(void 0, 10);
|
|
13444
|
+
if (runs.length === 0) {
|
|
13445
|
+
await channel.sendText(chatId, "No run history yet.", "plain");
|
|
13446
|
+
return;
|
|
13447
|
+
}
|
|
13448
|
+
const lines = runs.map((r) => {
|
|
13449
|
+
const duration = r.durationMs ? ` (${(r.durationMs / 1e3).toFixed(1)}s)` : "";
|
|
13450
|
+
const error3 = r.error ? `
|
|
12963
13451
|
Error: ${r.error.slice(0, 100)}` : "";
|
|
12964
|
-
|
|
13452
|
+
const usage2 = r.usageInput ? `
|
|
12965
13453
|
Tokens: ${r.usageInput}in / ${r.usageOutput}out` : "";
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
13454
|
+
return `#${r.jobId} [${r.status}] ${formatLocalDateTime(r.startedAt)}${duration}${error3}${usage2}`;
|
|
13455
|
+
});
|
|
13456
|
+
await channel.sendText(chatId, lines.join("\n\n"), "plain");
|
|
13457
|
+
}
|
|
12969
13458
|
break;
|
|
12970
13459
|
}
|
|
12971
13460
|
case "editjob": {
|
|
@@ -12980,6 +13469,14 @@ Appends model + thinking level to each response.`, [
|
|
|
12980
13469
|
case "health": {
|
|
12981
13470
|
const report = getHealthReport();
|
|
12982
13471
|
await channel.sendText(chatId, formatHealthReport(report), "plain");
|
|
13472
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13473
|
+
await channel.sendKeyboard(chatId, "", [
|
|
13474
|
+
[
|
|
13475
|
+
{ label: "\u{1F504} Refresh", data: "menu:health" },
|
|
13476
|
+
{ label: "\u{1F4CB} View Jobs", data: "menu:jobs" }
|
|
13477
|
+
]
|
|
13478
|
+
]);
|
|
13479
|
+
}
|
|
12983
13480
|
break;
|
|
12984
13481
|
}
|
|
12985
13482
|
case "history": {
|
|
@@ -13000,20 +13497,24 @@ Appends model + thinking level to each response.`, [
|
|
|
13000
13497
|
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");
|
|
13001
13498
|
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
13002
13499
|
} else {
|
|
13003
|
-
|
|
13004
|
-
|
|
13005
|
-
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
const
|
|
13012
|
-
|
|
13013
|
-
|
|
13500
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13501
|
+
await sendHistoryView(chatId, channel, {});
|
|
13502
|
+
} else {
|
|
13503
|
+
const rows = getRecentMessageLog(chatId, 20).reverse();
|
|
13504
|
+
if (rows.length === 0) {
|
|
13505
|
+
await channel.sendText(chatId, "No conversation history found.", "plain");
|
|
13506
|
+
break;
|
|
13507
|
+
}
|
|
13508
|
+
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"];
|
|
13509
|
+
for (const row of rows) {
|
|
13510
|
+
const roleLabel = row.role === "user" ? "You" : "Assistant";
|
|
13511
|
+
const date = row.created_at.slice(0, 10);
|
|
13512
|
+
const preview = row.content.slice(0, 300) + (row.content.length > 300 ? "\u2026" : "");
|
|
13513
|
+
lines.push(`[${row.backend ?? "?"} \xB7 ${date}] ${roleLabel}: ${preview}`);
|
|
13514
|
+
}
|
|
13515
|
+
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");
|
|
13516
|
+
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
13014
13517
|
}
|
|
13015
|
-
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");
|
|
13016
|
-
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
13017
13518
|
}
|
|
13018
13519
|
break;
|
|
13019
13520
|
}
|
|
@@ -13049,11 +13550,34 @@ Use /skills to see it.`, "plain");
|
|
|
13049
13550
|
case "chats": {
|
|
13050
13551
|
if (!commandArgs) {
|
|
13051
13552
|
const aliases = getAllChatAliases();
|
|
13052
|
-
if (
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13553
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13554
|
+
if (aliases.length === 0) {
|
|
13555
|
+
await channel.sendKeyboard(
|
|
13556
|
+
chatId,
|
|
13557
|
+
"Authorized Chats\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\nNo chat aliases configured yet.\nGroup chats are auto-discovered when the bot receives a message.",
|
|
13558
|
+
[[{ label: "Refresh", data: "chats:refresh", style: "primary" }]]
|
|
13559
|
+
);
|
|
13560
|
+
} else {
|
|
13561
|
+
const lines = aliases.map(
|
|
13562
|
+
(a) => `${a.alias.padEnd(12)} | ${a.chatId}${a.chatId === chatId ? " [Active]" : ""}`
|
|
13563
|
+
);
|
|
13564
|
+
const text = ["Authorized Chats", "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501", "", ...lines].join("\n");
|
|
13565
|
+
const buttons = [];
|
|
13566
|
+
const actionRow = [];
|
|
13567
|
+
if (aliases.length > 0) {
|
|
13568
|
+
actionRow.push({ label: "Remove", data: "chats:remove", style: "danger" });
|
|
13569
|
+
}
|
|
13570
|
+
actionRow.push({ label: "Refresh", data: "chats:refresh", style: "primary" });
|
|
13571
|
+
buttons.push(actionRow);
|
|
13572
|
+
await channel.sendKeyboard(chatId, text, buttons);
|
|
13573
|
+
}
|
|
13574
|
+
} else {
|
|
13575
|
+
if (aliases.length === 0) {
|
|
13576
|
+
await channel.sendText(chatId, "No chat aliases configured yet.\nUsage: /chats name <chat_id> <alias>\n\nGroup chats are auto-discovered when the bot receives a message from them.", "plain");
|
|
13577
|
+
} else {
|
|
13578
|
+
const lines = ["Authorized chats:", "", ...aliases.map((a) => ` ${a.alias} -> ${a.chatId}`)];
|
|
13579
|
+
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
13580
|
+
}
|
|
13057
13581
|
}
|
|
13058
13582
|
break;
|
|
13059
13583
|
}
|
|
@@ -13072,117 +13596,79 @@ Use /skills to see it.`, "plain");
|
|
|
13072
13596
|
break;
|
|
13073
13597
|
}
|
|
13074
13598
|
case "heartbeat": {
|
|
13075
|
-
if (
|
|
13076
|
-
|
|
13077
|
-
|
|
13078
|
-
|
|
13079
|
-
|
|
13080
|
-
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
|
|
13086
|
-
}
|
|
13087
|
-
case "off": {
|
|
13088
|
-
setHeartbeatConfig(chatId, { enabled: false });
|
|
13089
|
-
stopHeartbeatForChat(chatId);
|
|
13090
|
-
await channel.sendText(chatId, "Heartbeat disabled.", "plain");
|
|
13091
|
-
break;
|
|
13092
|
-
}
|
|
13093
|
-
case "interval": {
|
|
13094
|
-
const ms = parseIntervalToMs(value ?? "30m");
|
|
13095
|
-
if (!ms || ms < 6e4) {
|
|
13096
|
-
await channel.sendText(chatId, "Invalid interval. Use e.g. 15m, 30m, 1h, 2h", "plain");
|
|
13599
|
+
if (commandArgs) {
|
|
13600
|
+
const { action, value } = parseHeartbeatCommand(chatId, commandArgs);
|
|
13601
|
+
switch (action) {
|
|
13602
|
+
case "on": {
|
|
13603
|
+
setHeartbeatConfig(chatId, { enabled: true });
|
|
13604
|
+
startHeartbeatForChat(chatId);
|
|
13605
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13606
|
+
await sendHeartbeatKeyboard(chatId, channel);
|
|
13607
|
+
} else {
|
|
13608
|
+
await channel.sendText(chatId, "Heartbeat enabled. I'll check in periodically.", "plain");
|
|
13609
|
+
}
|
|
13097
13610
|
break;
|
|
13098
13611
|
}
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
const hourMatch = (value ?? "").match(/^(\d{1,2})-(\d{1,2})$/);
|
|
13108
|
-
if (!hourMatch) {
|
|
13109
|
-
await channel.sendText(chatId, "Usage: /heartbeat hours 8-22", "plain");
|
|
13612
|
+
case "off": {
|
|
13613
|
+
setHeartbeatConfig(chatId, { enabled: false });
|
|
13614
|
+
stopHeartbeatForChat(chatId);
|
|
13615
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13616
|
+
await sendHeartbeatKeyboard(chatId, channel);
|
|
13617
|
+
} else {
|
|
13618
|
+
await channel.sendText(chatId, "Heartbeat disabled.", "plain");
|
|
13619
|
+
}
|
|
13110
13620
|
break;
|
|
13111
13621
|
}
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
|
|
13147
|
-
|
|
13148
|
-
|
|
13149
|
-
|
|
13150
|
-
|
|
13151
|
-
const lines = limits2.map(
|
|
13152
|
-
(l) => `${l.backend} (${l.window}): ${l.max_input_tokens ? `${(l.max_input_tokens / 1e3).toFixed(0)}K input tokens` : "no limit"}`
|
|
13153
|
-
);
|
|
13154
|
-
await channel.sendText(chatId, ["Current limits:", "", ...lines].join("\n"), "plain");
|
|
13622
|
+
case "interval": {
|
|
13623
|
+
const ms = parseIntervalToMs(value ?? "30m");
|
|
13624
|
+
if (!ms || ms < 6e4) {
|
|
13625
|
+
await channel.sendText(chatId, "Invalid interval. Use e.g. 15m, 30m, 1h, 2h", "plain");
|
|
13626
|
+
break;
|
|
13627
|
+
}
|
|
13628
|
+
setHeartbeatConfig(chatId, { intervalMs: ms });
|
|
13629
|
+
stopHeartbeatForChat(chatId);
|
|
13630
|
+
const hbConf = getHeartbeatConfig(chatId);
|
|
13631
|
+
if (hbConf?.enabled) startHeartbeatForChat(chatId);
|
|
13632
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13633
|
+
await sendHeartbeatKeyboard(chatId, channel);
|
|
13634
|
+
} else {
|
|
13635
|
+
await channel.sendText(chatId, `Heartbeat interval set to ${ms / 6e4} minutes.`, "plain");
|
|
13636
|
+
}
|
|
13637
|
+
break;
|
|
13638
|
+
}
|
|
13639
|
+
case "hours": {
|
|
13640
|
+
const hourMatch = (value ?? "").match(/^(\d{1,2})-(\d{1,2})$/);
|
|
13641
|
+
if (!hourMatch) {
|
|
13642
|
+
await channel.sendText(chatId, "Usage: /heartbeat hours 8-22", "plain");
|
|
13643
|
+
break;
|
|
13644
|
+
}
|
|
13645
|
+
const hbStart = `${hourMatch[1].padStart(2, "0")}:00`;
|
|
13646
|
+
const hbEnd = `${hourMatch[2].padStart(2, "0")}:00`;
|
|
13647
|
+
setHeartbeatConfig(chatId, { activeStart: hbStart, activeEnd: hbEnd });
|
|
13648
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13649
|
+
await sendHeartbeatKeyboard(chatId, channel);
|
|
13650
|
+
} else {
|
|
13651
|
+
await channel.sendText(chatId, `Active hours: ${hbStart} - ${hbEnd}`, "plain");
|
|
13652
|
+
}
|
|
13653
|
+
break;
|
|
13654
|
+
}
|
|
13655
|
+
default:
|
|
13656
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13657
|
+
await sendHeartbeatKeyboard(chatId, channel);
|
|
13658
|
+
} else {
|
|
13659
|
+
await channel.sendText(chatId, formatHeartbeatStatus(chatId), "plain");
|
|
13660
|
+
}
|
|
13155
13661
|
}
|
|
13156
13662
|
break;
|
|
13157
13663
|
}
|
|
13158
|
-
|
|
13159
|
-
|
|
13160
|
-
await channel.sendText(chatId, "Usage: /limits <backend> <window> <tokens|off>\nExample: /limits claude daily 500000", "plain");
|
|
13161
|
-
break;
|
|
13162
|
-
}
|
|
13163
|
-
const [bk, win, val] = parts;
|
|
13164
|
-
if (!getAllBackendIds().includes(bk)) {
|
|
13165
|
-
await channel.sendText(chatId, `Unknown backend: ${bk}. Available: ${getAllBackendIds().join(", ")}`, "plain");
|
|
13166
|
-
break;
|
|
13167
|
-
}
|
|
13168
|
-
if (!["hourly", "daily", "weekly"].includes(win)) {
|
|
13169
|
-
await channel.sendText(chatId, "Window must be: hourly, daily, or weekly", "plain");
|
|
13170
|
-
break;
|
|
13171
|
-
}
|
|
13172
|
-
if (val === "off") {
|
|
13173
|
-
clearBackendLimit(bk, win);
|
|
13174
|
-
await channel.sendText(chatId, `Limit removed for ${bk} (${win}).`, "plain");
|
|
13664
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13665
|
+
await sendHeartbeatKeyboard(chatId, channel);
|
|
13175
13666
|
} else {
|
|
13176
|
-
|
|
13177
|
-
if (isNaN(tokens) || tokens <= 0) {
|
|
13178
|
-
await channel.sendText(chatId, "Token limit must be a positive number.", "plain");
|
|
13179
|
-
break;
|
|
13180
|
-
}
|
|
13181
|
-
setBackendLimit(bk, win, tokens);
|
|
13182
|
-
await channel.sendText(chatId, `Limit set: ${bk} (${win}) = ${(tokens / 1e3).toFixed(0)}K input tokens`, "plain");
|
|
13667
|
+
await channel.sendText(chatId, formatHeartbeatStatus(chatId), "plain");
|
|
13183
13668
|
}
|
|
13184
13669
|
break;
|
|
13185
13670
|
}
|
|
13671
|
+
/* usage + limits handled above in unified "cost"/"limits"/"usage" case */
|
|
13186
13672
|
case "agents": {
|
|
13187
13673
|
if (commandArgs?.startsWith("mode")) {
|
|
13188
13674
|
const modeArg = commandArgs.slice(5).trim().toLowerCase();
|
|
@@ -13200,9 +13686,9 @@ Use /skills to see it.`, "plain");
|
|
|
13200
13686
|
|
|
13201
13687
|
Choose a mode:`, [
|
|
13202
13688
|
[
|
|
13203
|
-
{ label: "Auto (recommended)", data: "agentmode:auto" },
|
|
13204
|
-
{ label: "Native (fast)", data: "agentmode:native" },
|
|
13205
|
-
{ label: "Orchestrated", data: "agentmode:claw" }
|
|
13689
|
+
{ label: "Auto (recommended)", data: "agentmode:auto", ...current === "auto" ? { style: "primary" } : {} },
|
|
13690
|
+
{ label: "Native (fast)", data: "agentmode:native", ...current === "native" ? { style: "primary" } : {} },
|
|
13691
|
+
{ label: "Orchestrated", data: "agentmode:claw", ...current === "claw" ? { style: "primary" } : {} }
|
|
13206
13692
|
]
|
|
13207
13693
|
]);
|
|
13208
13694
|
} else {
|
|
@@ -13280,6 +13766,18 @@ ${lines2.join("\n")}`, "html");
|
|
|
13280
13766
|
}
|
|
13281
13767
|
lines.push(`Total: ${agents2.length} agent(s) (${runningCount} running, ${queuedCount} queued)`);
|
|
13282
13768
|
await channel.sendText(chatId, lines.join("\n"), "html");
|
|
13769
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13770
|
+
const agentButtons = [];
|
|
13771
|
+
for (const a of agents2) {
|
|
13772
|
+
const shortId = a.id.slice(0, 8);
|
|
13773
|
+
const taskLabel = a.task ? a.task.slice(0, 20) : a.status;
|
|
13774
|
+
agentButtons.push([
|
|
13775
|
+
{ label: `\u{1F6D1} Stop ${shortId}: ${taskLabel}`, data: `agents:stop:${shortId}`, style: "danger" }
|
|
13776
|
+
]);
|
|
13777
|
+
}
|
|
13778
|
+
agentButtons.push([{ label: "\u{1F4CB} View Task Board", data: "agents:tasks" }]);
|
|
13779
|
+
await channel.sendKeyboard(chatId, "Agent actions:", agentButtons);
|
|
13780
|
+
}
|
|
13283
13781
|
break;
|
|
13284
13782
|
}
|
|
13285
13783
|
case "tasks": {
|
|
@@ -13322,11 +13820,56 @@ ${lines2.join("\n")}`, "html");
|
|
|
13322
13820
|
lines.push("No tasks yet.");
|
|
13323
13821
|
}
|
|
13324
13822
|
await channel.sendText(chatId, lines.join("\n").trimEnd(), "html");
|
|
13823
|
+
if (typeof channel.sendKeyboard === "function" && tasks.length > 0) {
|
|
13824
|
+
const taskButtons = [];
|
|
13825
|
+
const viewable = tasks.filter((t) => t.status === "pending" || t.status === "in_progress");
|
|
13826
|
+
for (const t of viewable.slice(0, 10)) {
|
|
13827
|
+
const statusIcon = t.status === "in_progress" ? "\u{1F504}" : "\u{1F4CB}";
|
|
13828
|
+
taskButtons.push([
|
|
13829
|
+
{ label: `${statusIcon} #${t.id}: ${t.subject.slice(0, 30)}`, data: `tasks:view:${t.id}`, style: "primary" }
|
|
13830
|
+
]);
|
|
13831
|
+
}
|
|
13832
|
+
if (taskButtons.length > 0) {
|
|
13833
|
+
await channel.sendKeyboard(chatId, "View task details:", taskButtons);
|
|
13834
|
+
}
|
|
13835
|
+
}
|
|
13325
13836
|
break;
|
|
13326
13837
|
}
|
|
13327
13838
|
case "stopagent": {
|
|
13328
13839
|
if (!commandArgs) {
|
|
13329
|
-
|
|
13840
|
+
const db4 = getDb();
|
|
13841
|
+
const agents3 = listActiveAgents(db4);
|
|
13842
|
+
if (agents3.length === 0) {
|
|
13843
|
+
await channel.sendText(chatId, "No active agents to stop.", "plain");
|
|
13844
|
+
break;
|
|
13845
|
+
}
|
|
13846
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
13847
|
+
const STATUS_EMOJI_STOP = { running: "\u{1F7E2}", queued: "\u{1F7E1}", starting: "\u{1F535}", idle: "\u26AA" };
|
|
13848
|
+
const agentLines = agents3.map((a) => {
|
|
13849
|
+
const emoji = STATUS_EMOJI_STOP[a.status] ?? "\u26AA";
|
|
13850
|
+
const shortId = a.id.slice(0, 8);
|
|
13851
|
+
const taskLabel = a.task ? a.task.slice(0, 40) : "no task";
|
|
13852
|
+
const runtimeMs = Date.now() - (/* @__PURE__ */ new Date(a.createdAt + "Z")).getTime();
|
|
13853
|
+
const mins = Math.floor(runtimeMs / 6e4);
|
|
13854
|
+
const runtime = mins < 1 ? "<1m" : `${mins}m`;
|
|
13855
|
+
return `${emoji} ${shortId} \u2014 "${taskLabel}" (${a.status}, ${runtime})`;
|
|
13856
|
+
});
|
|
13857
|
+
const buttons = agents3.map((a) => {
|
|
13858
|
+
const shortId = a.id.slice(0, 8);
|
|
13859
|
+
const taskLabel = a.task ? a.task.slice(0, 20) : a.status;
|
|
13860
|
+
return [{ label: `${shortId}: ${taskLabel}`, data: `stopagent:pick:${shortId}`, style: "danger" }];
|
|
13861
|
+
});
|
|
13862
|
+
buttons.push([
|
|
13863
|
+
{ label: "\u{1F6D1} Stop All", data: "stopagent:all", style: "danger" },
|
|
13864
|
+
{ label: "Cancel", data: "stopagent:cancel" }
|
|
13865
|
+
]);
|
|
13866
|
+
await channel.sendKeyboard(chatId, `Stop which agent?
|
|
13867
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
13868
|
+
|
|
13869
|
+
${agentLines.join("\n")}`, buttons);
|
|
13870
|
+
} else {
|
|
13871
|
+
await channel.sendText(chatId, "Usage: /stopagent <id>\nUse first 8 chars of agent ID or full ID.", "plain");
|
|
13872
|
+
}
|
|
13330
13873
|
break;
|
|
13331
13874
|
}
|
|
13332
13875
|
const db3 = getDb();
|
|
@@ -13344,6 +13887,14 @@ ${lines2.join("\n")}`, "html");
|
|
|
13344
13887
|
case "stopall": {
|
|
13345
13888
|
const count = cancelAllAgents(chatId);
|
|
13346
13889
|
await channel.sendText(chatId, count > 0 ? `Cancelled ${count} agent(s).` : "No active agents to cancel.", "plain");
|
|
13890
|
+
if (count > 0 && typeof channel.sendKeyboard === "function") {
|
|
13891
|
+
await channel.sendKeyboard(chatId, "", [
|
|
13892
|
+
[
|
|
13893
|
+
{ label: "\u{1F4CB} View Task Board", data: "agents:tasks" },
|
|
13894
|
+
{ label: "\u{1F195} New Chat", data: "menu:newchat" }
|
|
13895
|
+
]
|
|
13896
|
+
]);
|
|
13897
|
+
}
|
|
13347
13898
|
break;
|
|
13348
13899
|
}
|
|
13349
13900
|
case "runners": {
|
|
@@ -13431,19 +13982,7 @@ ${lines2.join("\n")}`, "html");
|
|
|
13431
13982
|
}
|
|
13432
13983
|
case "cron": {
|
|
13433
13984
|
if (!commandArgs) {
|
|
13434
|
-
|
|
13435
|
-
if (cronJobs.length === 0) {
|
|
13436
|
-
await channel.sendText(chatId, "No scheduled jobs.\n\nCreate one: /cron <description> or /schedule <description>", "plain");
|
|
13437
|
-
return;
|
|
13438
|
-
}
|
|
13439
|
-
const cronLines = cronJobs.map((j) => {
|
|
13440
|
-
const status = !j.active ? "cancelled" : !j.enabled ? "paused" : "active";
|
|
13441
|
-
const sched = j.cron ?? (j.atTime ? `at ${j.atTime}` : j.everyMs ? `every ${j.everyMs / 1e3}s` : "?");
|
|
13442
|
-
const tz = j.timezone !== "UTC" ? ` (${j.timezone})` : "";
|
|
13443
|
-
return `#${j.id} [${status}] ${sched}${tz}
|
|
13444
|
-
${j.description}`;
|
|
13445
|
-
});
|
|
13446
|
-
await channel.sendText(chatId, cronLines.join("\n\n"), "plain");
|
|
13985
|
+
await sendJobsBoard(chatId, channel, 1);
|
|
13447
13986
|
return;
|
|
13448
13987
|
}
|
|
13449
13988
|
const cronParts = commandArgs.split(/\s+/);
|
|
@@ -13452,7 +13991,7 @@ ${lines2.join("\n")}`, "html");
|
|
|
13452
13991
|
switch (cronSub) {
|
|
13453
13992
|
case "cancel": {
|
|
13454
13993
|
if (!cronSubArgs) {
|
|
13455
|
-
await
|
|
13994
|
+
await sendJobPicker(chatId, channel, "cancel");
|
|
13456
13995
|
return;
|
|
13457
13996
|
}
|
|
13458
13997
|
const id = parseInt(cronSubArgs, 10);
|
|
@@ -13462,7 +14001,7 @@ ${lines2.join("\n")}`, "html");
|
|
|
13462
14001
|
}
|
|
13463
14002
|
case "pause": {
|
|
13464
14003
|
if (!cronSubArgs) {
|
|
13465
|
-
await
|
|
14004
|
+
await sendJobPicker(chatId, channel, "pause");
|
|
13466
14005
|
return;
|
|
13467
14006
|
}
|
|
13468
14007
|
const pauseId = parseInt(cronSubArgs, 10);
|
|
@@ -13472,7 +14011,7 @@ ${lines2.join("\n")}`, "html");
|
|
|
13472
14011
|
}
|
|
13473
14012
|
case "resume": {
|
|
13474
14013
|
if (!cronSubArgs) {
|
|
13475
|
-
await
|
|
14014
|
+
await sendJobPicker(chatId, channel, "resume");
|
|
13476
14015
|
return;
|
|
13477
14016
|
}
|
|
13478
14017
|
const resumeId = parseInt(cronSubArgs, 10);
|
|
@@ -13482,7 +14021,7 @@ ${lines2.join("\n")}`, "html");
|
|
|
13482
14021
|
}
|
|
13483
14022
|
case "run": {
|
|
13484
14023
|
if (!cronSubArgs) {
|
|
13485
|
-
await
|
|
14024
|
+
await sendJobPicker(chatId, channel, "run");
|
|
13486
14025
|
return;
|
|
13487
14026
|
}
|
|
13488
14027
|
const runId = parseInt(cronSubArgs, 10);
|
|
@@ -13493,16 +14032,20 @@ ${lines2.join("\n")}`, "html");
|
|
|
13493
14032
|
}
|
|
13494
14033
|
case "runs": {
|
|
13495
14034
|
const runsId = cronSubArgs ? parseInt(cronSubArgs, 10) : void 0;
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
14035
|
+
if (runsId) {
|
|
14036
|
+
await sendJobRunsView(chatId, runsId, channel, 1);
|
|
14037
|
+
} else {
|
|
14038
|
+
const cronRuns2 = getJobRuns(void 0, 10);
|
|
14039
|
+
if (cronRuns2.length === 0) {
|
|
14040
|
+
await channel.sendText(chatId, "No run history.", "plain");
|
|
14041
|
+
return;
|
|
14042
|
+
}
|
|
14043
|
+
const runLines = cronRuns2.map((r) => {
|
|
14044
|
+
const dur = r.durationMs ? ` (${(r.durationMs / 1e3).toFixed(1)}s)` : "";
|
|
14045
|
+
return `#${r.jobId} [${r.status}] ${formatLocalDateTime(r.startedAt)}${dur}`;
|
|
14046
|
+
});
|
|
14047
|
+
await channel.sendText(chatId, runLines.join("\n\n"), "plain");
|
|
13500
14048
|
}
|
|
13501
|
-
const runLines = cronRuns2.map((r) => {
|
|
13502
|
-
const dur = r.durationMs ? ` (${(r.durationMs / 1e3).toFixed(1)}s)` : "";
|
|
13503
|
-
return `#${r.jobId} [${r.status}] ${formatLocalDateTime(r.startedAt)}${dur}`;
|
|
13504
|
-
});
|
|
13505
|
-
await channel.sendText(chatId, runLines.join("\n\n"), "plain");
|
|
13506
14049
|
break;
|
|
13507
14050
|
}
|
|
13508
14051
|
case "edit": {
|
|
@@ -13538,19 +14081,50 @@ Message: "${testMsg}"`, "plain");
|
|
|
13538
14081
|
break;
|
|
13539
14082
|
}
|
|
13540
14083
|
case "reflect": {
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
if (
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
14084
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
14085
|
+
const { getUnprocessedSignalCount: getUnprocessedSignalCount2, getLastAnalysisTime: getLastAnalysisTime2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
14086
|
+
const signalCount = getUnprocessedSignalCount2(getDb(), chatId);
|
|
14087
|
+
const lastTime = getLastAnalysisTime2(getDb(), chatId);
|
|
14088
|
+
let lastAnalysisText = "never";
|
|
14089
|
+
if (lastTime) {
|
|
14090
|
+
const diffMs = Date.now() - (/* @__PURE__ */ new Date(lastTime + "Z")).getTime();
|
|
14091
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
14092
|
+
if (diffHours < 1) lastAnalysisText = "less than an hour ago";
|
|
14093
|
+
else if (diffHours < 24) lastAnalysisText = `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`;
|
|
14094
|
+
else {
|
|
14095
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
14096
|
+
lastAnalysisText = `${diffDays} day${diffDays === 1 ? "" : "s"} ago`;
|
|
14097
|
+
}
|
|
14098
|
+
}
|
|
14099
|
+
const text = [
|
|
14100
|
+
"Reflection Analysis",
|
|
14101
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
14102
|
+
"",
|
|
14103
|
+
"This will analyze recent interactions for",
|
|
14104
|
+
"improvement insights. Uses the summarizer model.",
|
|
14105
|
+
"",
|
|
14106
|
+
`Unprocessed signals: ${signalCount}`,
|
|
14107
|
+
`Last analysis: ${lastAnalysisText}`
|
|
14108
|
+
].join("\n");
|
|
14109
|
+
await channel.sendKeyboard(chatId, text, [[
|
|
14110
|
+
{ label: "Analyze Now", data: "reflect:go", style: "success" },
|
|
14111
|
+
{ label: "Cancel", data: "reflect:cancel" }
|
|
14112
|
+
]]);
|
|
14113
|
+
} else {
|
|
14114
|
+
const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
|
|
14115
|
+
const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
14116
|
+
await channel.sendText(chatId, "Analyzing recent interactions...", "plain");
|
|
14117
|
+
try {
|
|
14118
|
+
const insights = await runAnalysis2(chatId);
|
|
14119
|
+
if (insights.length === 0) {
|
|
14120
|
+
await channel.sendText(chatId, "No new insights from recent interactions.", "plain");
|
|
14121
|
+
} else {
|
|
14122
|
+
const items = insights.map((ins, i) => ({ id: i + 1, category: ins.category, insight: ins.insight }));
|
|
14123
|
+
await channel.sendText(chatId, formatNightlySummary2(items), "plain");
|
|
14124
|
+
}
|
|
14125
|
+
} catch (e) {
|
|
14126
|
+
await channel.sendText(chatId, `Analysis failed: ${e}`, "plain");
|
|
13551
14127
|
}
|
|
13552
|
-
} catch (e) {
|
|
13553
|
-
await channel.sendText(chatId, `Analysis failed: ${e}`, "plain");
|
|
13554
14128
|
}
|
|
13555
14129
|
break;
|
|
13556
14130
|
}
|
|
@@ -13582,7 +14156,8 @@ async function handleVoice(msg, channel) {
|
|
|
13582
14156
|
const response = await askAgent(chatId, transcript, { cwd: getCwd(chatId), model: vModel, permMode: mode, onToolAction: vToolCb });
|
|
13583
14157
|
if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, vModel);
|
|
13584
14158
|
if (await handleResponseExhaustion(response.text, chatId, msg, channel)) return;
|
|
13585
|
-
|
|
14159
|
+
const voiceResponse = ensureReaction(response.text, transcript);
|
|
14160
|
+
await sendResponse(chatId, channel, voiceResponse, msg.messageId);
|
|
13586
14161
|
} catch (err) {
|
|
13587
14162
|
error("[router] Voice error:", err);
|
|
13588
14163
|
await channel.sendText(chatId, `Voice processing error: ${errorMessage(err)}`, "plain");
|
|
@@ -13607,11 +14182,8 @@ async function handleMedia(msg, channel) {
|
|
|
13607
14182
|
const videoBuffer = await channel.downloadFile(fileId);
|
|
13608
14183
|
const videoMime = msg.metadata?.mimeType ?? msg.mimeType ?? "video/mp4";
|
|
13609
14184
|
const videoExt = videoMime.split("/")[1]?.replace("quicktime", "mov") || "mp4";
|
|
13610
|
-
const
|
|
13611
|
-
await writeFile3(tempVideoPath, videoBuffer);
|
|
14185
|
+
const videoPath = await saveMedia(videoBuffer, "video", videoExt);
|
|
13612
14186
|
let prompt2;
|
|
13613
|
-
const cleanupVideo = () => unlink2(tempVideoPath).catch(() => {
|
|
13614
|
-
});
|
|
13615
14187
|
if (wantsVideoAnalysis(caption)) {
|
|
13616
14188
|
const geminiPrompt = caption || "Analyze this video and describe what you see in detail.";
|
|
13617
14189
|
let analysis;
|
|
@@ -13625,21 +14197,21 @@ async function handleMedia(msg, channel) {
|
|
|
13625
14197
|
Gemini video analysis:
|
|
13626
14198
|
${analysis}
|
|
13627
14199
|
|
|
13628
|
-
The video is also saved at: ${
|
|
14200
|
+
The video is also saved at: ${videoPath}
|
|
13629
14201
|
|
|
13630
14202
|
Respond to the user based on this analysis.` : `The user sent a video. Gemini analyzed it:
|
|
13631
14203
|
|
|
13632
14204
|
${analysis}
|
|
13633
14205
|
|
|
13634
|
-
The video is also saved at: ${
|
|
14206
|
+
The video is also saved at: ${videoPath}
|
|
13635
14207
|
|
|
13636
14208
|
Summarize this for the user.`;
|
|
13637
14209
|
} else {
|
|
13638
14210
|
prompt2 = caption ? `The user sent a video with caption: "${caption}"
|
|
13639
14211
|
|
|
13640
|
-
The video has been saved to: ${
|
|
14212
|
+
The video has been saved to: ${videoPath}
|
|
13641
14213
|
|
|
13642
|
-
Respond to their message. Do NOT analyze the video unless they ask you to.` : `The user sent a video. It has been saved to: ${
|
|
14214
|
+
Respond to their message. Do NOT analyze the video unless they ask you to.` : `The user sent a video. It has been saved to: ${videoPath}
|
|
13643
14215
|
|
|
13644
14216
|
Acknowledge receipt. Do NOT analyze the video unless they ask you to.`;
|
|
13645
14217
|
}
|
|
@@ -13649,12 +14221,9 @@ Acknowledge receipt. Do NOT analyze the video unless they ask you to.`;
|
|
|
13649
14221
|
const vidToolCb = vidVerbose !== "off" ? makeToolActionCallback(chatId, channel, vidVerbose) : void 0;
|
|
13650
14222
|
const response2 = await askAgent(chatId, prompt2, { cwd: getCwd(chatId), model: vidModel, permMode: vMode, onToolAction: vidToolCb });
|
|
13651
14223
|
if (response2.usage) addUsage(chatId, response2.usage.input, response2.usage.output, response2.usage.cacheRead, vidModel);
|
|
13652
|
-
if (await handleResponseExhaustion(response2.text, chatId, msg, channel))
|
|
13653
|
-
|
|
13654
|
-
|
|
13655
|
-
}
|
|
13656
|
-
await sendResponse(chatId, channel, response2.text, msg.messageId);
|
|
13657
|
-
cleanupVideo();
|
|
14224
|
+
if (await handleResponseExhaustion(response2.text, chatId, msg, channel)) return;
|
|
14225
|
+
const vidResponse = ensureReaction(response2.text, caption || "video");
|
|
14226
|
+
await sendResponse(chatId, channel, vidResponse, msg.messageId);
|
|
13658
14227
|
return;
|
|
13659
14228
|
}
|
|
13660
14229
|
const fileBuffer = await channel.downloadFile(fileName);
|
|
@@ -13664,8 +14233,7 @@ Acknowledge receipt. Do NOT analyze the video unless they ask you to.`;
|
|
|
13664
14233
|
let tempFilePath;
|
|
13665
14234
|
if (msg.type === "photo" || isImageExt(ext)) {
|
|
13666
14235
|
const imgExt = msg.type === "photo" ? "jpg" : ext || "jpg";
|
|
13667
|
-
tempFilePath =
|
|
13668
|
-
await writeFile3(tempFilePath, fileBuffer);
|
|
14236
|
+
tempFilePath = await saveMedia(fileBuffer, "img", imgExt);
|
|
13669
14237
|
prompt = caption ? `The user sent an image with caption: "${caption}"
|
|
13670
14238
|
|
|
13671
14239
|
The image has been saved to: ${tempFilePath}
|
|
@@ -13695,16 +14263,9 @@ ${content}
|
|
|
13695
14263
|
const mToolCb = mVerbose !== "off" ? makeToolActionCallback(chatId, channel, mVerbose) : void 0;
|
|
13696
14264
|
const response = await askAgent(chatId, prompt, { cwd: getCwd(chatId), model: mediaModel, permMode: mMode, onToolAction: mToolCb });
|
|
13697
14265
|
if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, mediaModel);
|
|
13698
|
-
if (await handleResponseExhaustion(response.text, chatId, msg, channel))
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
return;
|
|
13702
|
-
}
|
|
13703
|
-
await sendResponse(chatId, channel, response.text, msg.messageId);
|
|
13704
|
-
if (tempFilePath) {
|
|
13705
|
-
unlink2(tempFilePath).catch(() => {
|
|
13706
|
-
});
|
|
13707
|
-
}
|
|
14266
|
+
if (await handleResponseExhaustion(response.text, chatId, msg, channel)) return;
|
|
14267
|
+
const mediaResponse = ensureReaction(response.text, caption || "file");
|
|
14268
|
+
await sendResponse(chatId, channel, mediaResponse, msg.messageId);
|
|
13708
14269
|
} catch (err) {
|
|
13709
14270
|
error("[router] Media error:", err);
|
|
13710
14271
|
await channel.sendText(chatId, `Error processing file: ${errorMessage(err)}`, "plain");
|
|
@@ -13860,12 +14421,13 @@ async function handleText(msg, channel) {
|
|
|
13860
14421
|
storePendingEscalation(chatId, text);
|
|
13861
14422
|
if (typeof channel.sendKeyboard === "function") {
|
|
13862
14423
|
await channel.sendKeyboard(chatId, `Switch to ${targetMode} mode?`, [
|
|
13863
|
-
[{ label: "Allow", data: `perm:escalate:${targetMode}
|
|
14424
|
+
[{ label: "Allow", data: `perm:escalate:${targetMode}`, style: "success" }, { label: "Deny", data: "perm:deny", style: "danger" }]
|
|
13864
14425
|
]);
|
|
13865
14426
|
}
|
|
13866
14427
|
return;
|
|
13867
14428
|
}
|
|
13868
14429
|
if (await handleResponseExhaustion(responseText, chatId, msg, channel)) return;
|
|
14430
|
+
responseText = ensureReaction(responseText, cleanText || text);
|
|
13869
14431
|
await sendResponse(chatId, channel, responseText, msg.messageId);
|
|
13870
14432
|
try {
|
|
13871
14433
|
const { detectAndLogSignals: detectAndLogSignals2 } = await Promise.resolve().then(() => (init_detect(), detect_exports));
|
|
@@ -13886,7 +14448,7 @@ async function handleText(msg, channel) {
|
|
|
13886
14448
|
const modeLabel = mode === "accounts" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Only" : mode === "keys" ? "\u{1F511} Only" : mode;
|
|
13887
14449
|
if (typeof channel.sendKeyboard === "function") {
|
|
13888
14450
|
const rows = [
|
|
13889
|
-
[{ label: "Claude", data: "backend:claude" }, { label: "Codex", data: "backend:codex" }],
|
|
14451
|
+
[{ label: "Claude", data: "backend:claude", style: "primary" }, { label: "Codex", data: "backend:codex", style: "primary" }],
|
|
13890
14452
|
[{ label: "\u2699\uFE0F Change rotation mode", data: "gopen:accounts" }]
|
|
13891
14453
|
];
|
|
13892
14454
|
await channel.sendKeyboard(chatId, `\u26A0\uFE0F No eligible slots for rotation mode (${modeLabel}). Switch backend?`, rows);
|
|
@@ -13898,7 +14460,7 @@ async function handleText(msg, channel) {
|
|
|
13898
14460
|
const timeLeft = errMsg.split("|")[1] || "?";
|
|
13899
14461
|
if (typeof channel.sendKeyboard === "function") {
|
|
13900
14462
|
const rows = [
|
|
13901
|
-
[{ label: "Claude", data: "backend:claude" }, { label: "Codex", data: "backend:codex" }],
|
|
14463
|
+
[{ label: "Claude", data: "backend:claude", style: "primary" }, { label: "Codex", data: "backend:codex", style: "primary" }],
|
|
13902
14464
|
[{ label: "\u2699\uFE0F Change rotation mode", data: "gopen:accounts" }]
|
|
13903
14465
|
];
|
|
13904
14466
|
await channel.sendKeyboard(chatId, `\u26A0\uFE0F All eligible slots in cooldown. Next available in ~${timeLeft}. Switch backend?`, rows);
|
|
@@ -14074,6 +14636,11 @@ async function processImageGenerations(chatId, channel, text) {
|
|
|
14074
14636
|
}
|
|
14075
14637
|
return text.replace(pattern, "").trim();
|
|
14076
14638
|
}
|
|
14639
|
+
function ensureReaction(responseText, userMessage) {
|
|
14640
|
+
if (/\[REACT:.+?\]/.test(responseText)) return responseText;
|
|
14641
|
+
const emoji = pickFallbackEmoji(userMessage);
|
|
14642
|
+
return `[REACT:${emoji}]${responseText}`;
|
|
14643
|
+
}
|
|
14077
14644
|
async function processReaction(chatId, channel, text, messageId) {
|
|
14078
14645
|
const reactPatternGlobal = /\[REACT:(.+?)\]/g;
|
|
14079
14646
|
if (!reactPatternGlobal.test(text)) return text;
|
|
@@ -14083,7 +14650,8 @@ async function processReaction(chatId, channel, text, messageId) {
|
|
|
14083
14650
|
while ((match = reactPatternGlobal.exec(text)) !== null) {
|
|
14084
14651
|
const emoji = match[1].trim();
|
|
14085
14652
|
if (!reacted && messageId && typeof channel.reactToMessage === "function" && ALLOWED_REACTION_EMOJIS.has(emoji)) {
|
|
14086
|
-
channel.reactToMessage(chatId, messageId, emoji).catch(() => {
|
|
14653
|
+
channel.reactToMessage(chatId, messageId, emoji).catch((err) => {
|
|
14654
|
+
log(`[reaction] Failed for chat=${chatId}: ${err}`);
|
|
14087
14655
|
});
|
|
14088
14656
|
reacted = true;
|
|
14089
14657
|
}
|
|
@@ -14096,7 +14664,7 @@ async function sendResponse(chatId, channel, text, messageId) {
|
|
|
14096
14664
|
for (const { key, value } of updates) {
|
|
14097
14665
|
if (typeof channel.sendKeyboard === "function") {
|
|
14098
14666
|
await channel.sendKeyboard(chatId, `Save preference? ${key}: ${value}`, [
|
|
14099
|
-
[{ label: "Yes", data: `userupdate:yes:${key}:${value}
|
|
14667
|
+
[{ label: "Yes", data: `userupdate:yes:${key}:${value}`, style: "success" }, { label: "No", data: "userupdate:no" }]
|
|
14100
14668
|
]);
|
|
14101
14669
|
}
|
|
14102
14670
|
}
|
|
@@ -14218,8 +14786,8 @@ async function sendBackendSwitchConfirmation(chatId, target, channel) {
|
|
|
14218
14786
|
Your conversation history is preserved. ${targetAdapter.displayName} will receive a summary of recent context and can access your full history on request.`,
|
|
14219
14787
|
[
|
|
14220
14788
|
[
|
|
14221
|
-
{ label: `Switch to ${targetAdapter.displayName}`, data: `backend_confirm:${target}
|
|
14222
|
-
{ label: `Switch + Clear History`, data: `backend_confirm_clear:${target}
|
|
14789
|
+
{ label: `Switch to ${targetAdapter.displayName}`, data: `backend_confirm:${target}`, style: "primary" },
|
|
14790
|
+
{ label: `Switch + Clear History`, data: `backend_confirm_clear:${target}`, style: "primary" }
|
|
14223
14791
|
],
|
|
14224
14792
|
[{ label: "Cancel", data: `backend_cancel:${target}` }]
|
|
14225
14793
|
]
|
|
@@ -14252,7 +14820,397 @@ async function doBackendSwitch(chatId, backendId, channel) {
|
|
|
14252
14820
|
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Backend switched to ${targetAdapter.displayName}`, detail: { field: "backend", value: backendId } });
|
|
14253
14821
|
await channel.sendText(chatId, `\u2705 Switched to ${targetAdapter.displayName}. Ready!`, "plain");
|
|
14254
14822
|
}
|
|
14823
|
+
async function sendHistoryView(chatId, channel, filter) {
|
|
14824
|
+
const limit = filter.limit ?? 10;
|
|
14825
|
+
let rows = getRecentMessageLog(chatId, 100).reverse();
|
|
14826
|
+
if (filter.backend) {
|
|
14827
|
+
rows = rows.filter((r) => r.backend === filter.backend);
|
|
14828
|
+
}
|
|
14829
|
+
if (filter.period === "today") {
|
|
14830
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
14831
|
+
rows = rows.filter((r) => r.created_at.slice(0, 10) === today);
|
|
14832
|
+
} else if (filter.period === "week") {
|
|
14833
|
+
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString();
|
|
14834
|
+
rows = rows.filter((r) => r.created_at >= weekAgo);
|
|
14835
|
+
}
|
|
14836
|
+
const userMsgs = rows.filter((r) => r.role === "user").slice(0, limit);
|
|
14837
|
+
if (userMsgs.length === 0) {
|
|
14838
|
+
await channel.sendText(chatId, "No conversation history found.", "plain");
|
|
14839
|
+
return;
|
|
14840
|
+
}
|
|
14841
|
+
const lines = [`\u{1F4CB} Recent Conversation (last ${userMsgs.length})`, "\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"];
|
|
14842
|
+
for (const row of userMsgs) {
|
|
14843
|
+
const date = row.created_at.slice(0, 10);
|
|
14844
|
+
const preview = row.content.slice(0, 80) + (row.content.length > 80 ? "\u2026" : "");
|
|
14845
|
+
lines.push(`[${row.backend ?? "?"} \xB7 ${date}] ${preview}`);
|
|
14846
|
+
}
|
|
14847
|
+
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
14848
|
+
const filterRow = [
|
|
14849
|
+
{ label: "Show More (25)", data: "hist:recent:25" },
|
|
14850
|
+
{ label: "Today", data: "hist:today" },
|
|
14851
|
+
{ label: "This Week", data: "hist:week" }
|
|
14852
|
+
];
|
|
14853
|
+
const backendRow = getAllAdapters().map((a) => ({
|
|
14854
|
+
label: a.displayName,
|
|
14855
|
+
data: `hist:backend:${a.id}`,
|
|
14856
|
+
style: "primary"
|
|
14857
|
+
}));
|
|
14858
|
+
await channel.sendKeyboard(chatId, "Tip: /history <query> for full-text search", [filterRow, backendRow]);
|
|
14859
|
+
}
|
|
14860
|
+
async function sendMemoryPage(chatId, channel, page) {
|
|
14861
|
+
const memories = listMemories();
|
|
14862
|
+
if (memories.length === 0) {
|
|
14863
|
+
await channel.sendText(chatId, "No memories stored yet.", "plain");
|
|
14864
|
+
return;
|
|
14865
|
+
}
|
|
14866
|
+
const pageSize = 10;
|
|
14867
|
+
const totalPages = Math.max(1, Math.ceil(memories.length / pageSize));
|
|
14868
|
+
const safePage = Math.max(1, Math.min(page, totalPages));
|
|
14869
|
+
const start = (safePage - 1) * pageSize;
|
|
14870
|
+
const pageItems = memories.slice(start, start + pageSize);
|
|
14871
|
+
const lines = [
|
|
14872
|
+
`Memories (${memories.length} total, ${safePage === 1 ? "top " : ""}${pageItems.length} by relevance${totalPages > 1 ? ` \xB7 page ${safePage}/${totalPages}` : ""})`,
|
|
14873
|
+
"\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",
|
|
14874
|
+
""
|
|
14875
|
+
];
|
|
14876
|
+
for (const [i, m] of pageItems.entries()) {
|
|
14877
|
+
lines.push(`${start + i + 1}. ${m.trigger}: ${m.content.slice(0, 60)}${m.content.length > 60 ? "\u2026" : ""}`);
|
|
14878
|
+
}
|
|
14879
|
+
const buttons = [];
|
|
14880
|
+
const itemRow = [];
|
|
14881
|
+
for (const [i, m] of pageItems.entries()) {
|
|
14882
|
+
itemRow.push({ label: `${start + i + 1}`, data: `mem:view:${m.id}` });
|
|
14883
|
+
if (itemRow.length === 5) {
|
|
14884
|
+
buttons.push([...itemRow]);
|
|
14885
|
+
itemRow.length = 0;
|
|
14886
|
+
}
|
|
14887
|
+
}
|
|
14888
|
+
if (itemRow.length > 0) buttons.push([...itemRow]);
|
|
14889
|
+
if (totalPages > 1) {
|
|
14890
|
+
const navRow = [];
|
|
14891
|
+
if (safePage > 1) navRow.push({ label: `\u2190 Page ${safePage - 1}`, data: `mem:page:${safePage - 1}` });
|
|
14892
|
+
navRow.push({ label: `${safePage}/${totalPages}`, data: "mem:page:noop" });
|
|
14893
|
+
if (safePage < totalPages) navRow.push({ label: `Page ${safePage + 1} \u2192`, data: `mem:page:${safePage + 1}` });
|
|
14894
|
+
buttons.push(navRow);
|
|
14895
|
+
}
|
|
14896
|
+
const footerRow = [];
|
|
14897
|
+
if (memories.length > 50) {
|
|
14898
|
+
footerRow.push({ label: "Show All (file)", data: "mem:showall", style: "primary" });
|
|
14899
|
+
}
|
|
14900
|
+
footerRow.push({ label: "Search: /memory <query>", data: "mem:page:noop" });
|
|
14901
|
+
buttons.push(footerRow);
|
|
14902
|
+
await channel.sendKeyboard(chatId, lines.join("\n"), buttons);
|
|
14903
|
+
}
|
|
14904
|
+
async function sendHeartbeatKeyboard(chatId, channel) {
|
|
14905
|
+
const config2 = getHeartbeatConfig(chatId);
|
|
14906
|
+
const enabled = config2?.enabled === 1;
|
|
14907
|
+
const intervalMs = config2?.intervalMs ?? 18e5;
|
|
14908
|
+
const intervalMin = intervalMs / 6e4;
|
|
14909
|
+
const activeStart = config2?.activeStart ?? "08:00";
|
|
14910
|
+
const activeEnd = config2?.activeEnd ?? "22:00";
|
|
14911
|
+
const lines = [
|
|
14912
|
+
buildSectionHeader("Heartbeat"),
|
|
14913
|
+
"CC-Claw periodically wakes up to check on",
|
|
14914
|
+
"things and alert you proactively \u2014 even when",
|
|
14915
|
+
"you haven't sent a message.",
|
|
14916
|
+
"",
|
|
14917
|
+
`Status: ${enabled ? "ON" : "OFF"}`,
|
|
14918
|
+
`Interval: every ${intervalMin} min`,
|
|
14919
|
+
`Active hours: ${activeStart}-${activeEnd}`
|
|
14920
|
+
];
|
|
14921
|
+
const presets = [15, 30, 60, 120];
|
|
14922
|
+
const intervalRow = presets.map((m) => ({
|
|
14923
|
+
label: `${m === intervalMin ? "\u2713 " : ""}${m} min`,
|
|
14924
|
+
data: `hb:interval:${m}`,
|
|
14925
|
+
...m === intervalMin ? { style: "primary" } : {}
|
|
14926
|
+
}));
|
|
14927
|
+
await channel.sendKeyboard(chatId, lines.join("\n"), [
|
|
14928
|
+
[
|
|
14929
|
+
{ label: `${enabled ? "\u2713 " : ""}On`, data: "hb:on", ...enabled ? { style: "success" } : {} },
|
|
14930
|
+
{ label: `${!enabled ? "\u2713 " : ""}Off`, data: "hb:off", ...!enabled ? { style: "danger" } : {} }
|
|
14931
|
+
],
|
|
14932
|
+
intervalRow,
|
|
14933
|
+
[{ label: "Active Hours: /heartbeat hours <start>-<end>", data: "hb:noop" }]
|
|
14934
|
+
]);
|
|
14935
|
+
}
|
|
14936
|
+
async function sendForgetPicker(chatId, channel, page) {
|
|
14937
|
+
const memories = listMemories();
|
|
14938
|
+
if (memories.length === 0) {
|
|
14939
|
+
await channel.sendText(chatId, "No memories to forget.", "plain");
|
|
14940
|
+
return;
|
|
14941
|
+
}
|
|
14942
|
+
const { text, buttons } = buildPaginatedKeyboard({
|
|
14943
|
+
items: memories,
|
|
14944
|
+
page,
|
|
14945
|
+
pageSize: 10,
|
|
14946
|
+
callbackPrefix: "forget:",
|
|
14947
|
+
renderItem: (m) => {
|
|
14948
|
+
const preview = `${m.trigger}: ${m.content}`.slice(0, 30);
|
|
14949
|
+
return { label: preview, data: `forget:pick:${m.id}` };
|
|
14950
|
+
},
|
|
14951
|
+
headerText: (p, tp, total) => `Which memory to forget? (${total} total${tp > 1 ? `, page ${p}/${tp}` : ""})`,
|
|
14952
|
+
footerButtons: [{ label: "Cancel", data: "forget:cancel" }]
|
|
14953
|
+
});
|
|
14954
|
+
await channel.sendKeyboard(chatId, text, buttons);
|
|
14955
|
+
}
|
|
14956
|
+
function getJobScheduleText(job) {
|
|
14957
|
+
if (job.cron) return humanizeCron(job.cron);
|
|
14958
|
+
if (job.everyMs) return humanizeCron(`every:${job.everyMs}`);
|
|
14959
|
+
if (job.atTime) return humanizeCron(`at:${job.atTime}`);
|
|
14960
|
+
return "?";
|
|
14961
|
+
}
|
|
14962
|
+
function getJobStatusEmoji(job) {
|
|
14963
|
+
if (!job.active) return "\u{1F6AB}";
|
|
14964
|
+
if (!job.enabled) return "\u23F8";
|
|
14965
|
+
return "\u25B6\uFE0F";
|
|
14966
|
+
}
|
|
14967
|
+
function getJobStatusLabel(job) {
|
|
14968
|
+
if (!job.active) return "cancelled";
|
|
14969
|
+
if (!job.enabled) return "paused";
|
|
14970
|
+
return "active";
|
|
14971
|
+
}
|
|
14972
|
+
async function sendJobsBoard(chatId, channel, page) {
|
|
14973
|
+
const jobs = listJobs();
|
|
14974
|
+
if (jobs.length === 0) {
|
|
14975
|
+
await channel.sendText(chatId, "No scheduled jobs.\n\nCreate one: /schedule <description>", "plain");
|
|
14976
|
+
return;
|
|
14977
|
+
}
|
|
14978
|
+
if (typeof channel.sendKeyboard !== "function") {
|
|
14979
|
+
const lines = jobs.map((j) => {
|
|
14980
|
+
const status = getJobStatusLabel(j);
|
|
14981
|
+
const schedule2 = getJobScheduleText(j);
|
|
14982
|
+
const tz = j.timezone !== "UTC" ? ` (${j.timezone})` : "";
|
|
14983
|
+
return `#${j.id} [${status}] ${schedule2}${tz}
|
|
14984
|
+
${j.description}`;
|
|
14985
|
+
});
|
|
14986
|
+
await channel.sendText(chatId, lines.join("\n\n"), "plain");
|
|
14987
|
+
return;
|
|
14988
|
+
}
|
|
14989
|
+
const headerLines = [];
|
|
14990
|
+
for (const [i, j] of jobs.entries()) {
|
|
14991
|
+
const emoji = getJobStatusEmoji(j);
|
|
14992
|
+
const schedule2 = getJobScheduleText(j);
|
|
14993
|
+
const desc = j.description.length > 20 ? j.description.slice(0, 20) + "\u2026" : j.description;
|
|
14994
|
+
headerLines.push(`${i + 1}. ${desc} | ${schedule2} | ${emoji} ${getJobStatusLabel(j)}`);
|
|
14995
|
+
}
|
|
14996
|
+
const { text, buttons } = buildPaginatedKeyboard({
|
|
14997
|
+
items: jobs,
|
|
14998
|
+
page,
|
|
14999
|
+
pageSize: 10,
|
|
15000
|
+
callbackPrefix: "job:",
|
|
15001
|
+
renderItem: (j) => ({
|
|
15002
|
+
label: `#${j.id}: ${j.description.slice(0, 30)}`,
|
|
15003
|
+
data: `job:view:${j.id}`,
|
|
15004
|
+
style: "primary"
|
|
15005
|
+
}),
|
|
15006
|
+
headerText: (p, tp, total) => {
|
|
15007
|
+
const pageJobs = jobs.slice((p - 1) * 10, p * 10);
|
|
15008
|
+
const lines = [`Scheduled Jobs (${total})`, buildSectionHeader("", 20)];
|
|
15009
|
+
for (const j of pageJobs) {
|
|
15010
|
+
const emoji = getJobStatusEmoji(j);
|
|
15011
|
+
const schedule2 = getJobScheduleText(j);
|
|
15012
|
+
const desc = j.description.length > 24 ? j.description.slice(0, 24) + "\u2026" : j.description;
|
|
15013
|
+
lines.push(`${emoji} ${desc} \xB7 ${schedule2}`);
|
|
15014
|
+
}
|
|
15015
|
+
lines.push("");
|
|
15016
|
+
lines.push("Tap a job to view details:");
|
|
15017
|
+
return lines.join("\n");
|
|
15018
|
+
},
|
|
15019
|
+
footerButtons: []
|
|
15020
|
+
});
|
|
15021
|
+
await channel.sendKeyboard(chatId, text, buttons);
|
|
15022
|
+
}
|
|
15023
|
+
async function sendJobDetail(chatId, jobId, channel) {
|
|
15024
|
+
const job = getJobById(jobId);
|
|
15025
|
+
if (!job) {
|
|
15026
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
15027
|
+
await channel.sendKeyboard(chatId, "This job no longer exists.", [
|
|
15028
|
+
[{ label: "Refresh List", data: "job:back" }]
|
|
15029
|
+
]);
|
|
15030
|
+
} else {
|
|
15031
|
+
await channel.sendText(chatId, "Job not found.", "plain");
|
|
15032
|
+
}
|
|
15033
|
+
return;
|
|
15034
|
+
}
|
|
15035
|
+
const schedule2 = getJobScheduleText(job);
|
|
15036
|
+
const tz = job.timezone !== "UTC" ? ` (${job.timezone})` : "";
|
|
15037
|
+
const backend2 = job.backend ?? "default";
|
|
15038
|
+
const model2 = job.model ?? "default";
|
|
15039
|
+
const lastRun = job.lastRunAt ? formatLocalDateTime(job.lastRunAt) : "never";
|
|
15040
|
+
const nextRun = job.nextRunAt ? formatLocalDateTime(job.nextRunAt) : "\u2014";
|
|
15041
|
+
const status = getJobStatusLabel(job);
|
|
15042
|
+
const failures = job.consecutiveFailures > 0 ? `
|
|
15043
|
+
\u26A0\uFE0F ${job.consecutiveFailures} consecutive failures` : "";
|
|
15044
|
+
const runs = getJobRuns(jobId, 1);
|
|
15045
|
+
const lastRunStatus = runs.length > 0 ? runs[0].status : null;
|
|
15046
|
+
const lines = [
|
|
15047
|
+
`Job #${job.id}: ${job.description}`,
|
|
15048
|
+
buildSectionHeader("", 22),
|
|
15049
|
+
`Runs: ${schedule2}${tz}`,
|
|
15050
|
+
`Backend: ${backend2} | Model: ${model2}`,
|
|
15051
|
+
`Last run: ${lastRun}${lastRunStatus ? ` (${lastRunStatus})` : ""}`,
|
|
15052
|
+
`Next run: ${nextRun}`,
|
|
15053
|
+
`Status: ${status}${failures}`
|
|
15054
|
+
];
|
|
15055
|
+
const text = lines.join("\n");
|
|
15056
|
+
if (typeof channel.sendKeyboard !== "function") {
|
|
15057
|
+
await channel.sendText(chatId, text, "plain");
|
|
15058
|
+
return;
|
|
15059
|
+
}
|
|
15060
|
+
const actionRow1 = [
|
|
15061
|
+
{ label: "Run Now", data: `job:run:${job.id}`, style: "success" }
|
|
15062
|
+
];
|
|
15063
|
+
if (job.active && job.enabled) {
|
|
15064
|
+
actionRow1.push({ label: "Pause", data: `job:pause:${job.id}` });
|
|
15065
|
+
} else if (job.active && !job.enabled) {
|
|
15066
|
+
actionRow1.push({ label: "Resume", data: `job:resume:${job.id}` });
|
|
15067
|
+
}
|
|
15068
|
+
actionRow1.push({ label: "Edit", data: `job:edit:${job.id}` });
|
|
15069
|
+
const actionRow2 = [
|
|
15070
|
+
{ label: "View Runs", data: `job:runs:${job.id}`, style: "primary" }
|
|
15071
|
+
];
|
|
15072
|
+
if (job.active) {
|
|
15073
|
+
actionRow2.push({ label: "Cancel Job", data: `job:cancel:${job.id}`, style: "danger" });
|
|
15074
|
+
}
|
|
15075
|
+
const buttons = [actionRow1, actionRow2, [{ label: "\u2190 Back to List", data: "job:back" }]];
|
|
15076
|
+
await channel.sendKeyboard(chatId, text, buttons);
|
|
15077
|
+
}
|
|
15078
|
+
async function sendJobRunsView(chatId, jobId, channel, page) {
|
|
15079
|
+
const runs = getJobRuns(jobId, 50);
|
|
15080
|
+
if (runs.length === 0) {
|
|
15081
|
+
const msg = `No runs for job #${jobId}.`;
|
|
15082
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
15083
|
+
await channel.sendKeyboard(chatId, msg, [
|
|
15084
|
+
[{ label: "\u2190 Back to Job", data: `job:view:${jobId}` }]
|
|
15085
|
+
]);
|
|
15086
|
+
} else {
|
|
15087
|
+
await channel.sendText(chatId, msg, "plain");
|
|
15088
|
+
}
|
|
15089
|
+
return;
|
|
15090
|
+
}
|
|
15091
|
+
if (typeof channel.sendKeyboard !== "function") {
|
|
15092
|
+
const lines = runs.slice(0, 10).map((r) => {
|
|
15093
|
+
const duration = r.durationMs ? ` (${(r.durationMs / 1e3).toFixed(1)}s)` : "";
|
|
15094
|
+
const error3 = r.error ? `
|
|
15095
|
+
Error: ${r.error.slice(0, 100)}` : "";
|
|
15096
|
+
return `#${r.jobId} [${r.status}] ${formatLocalDateTime(r.startedAt)}${duration}${error3}`;
|
|
15097
|
+
});
|
|
15098
|
+
await channel.sendText(chatId, lines.join("\n\n"), "plain");
|
|
15099
|
+
return;
|
|
15100
|
+
}
|
|
15101
|
+
const statusEmoji = {
|
|
15102
|
+
success: "\u2705",
|
|
15103
|
+
failed: "\u274C",
|
|
15104
|
+
running: "\u{1F504}",
|
|
15105
|
+
skipped: "\u23ED",
|
|
15106
|
+
no_content: "\u{1F4ED}",
|
|
15107
|
+
delivery_failed: "\u{1F4EA}"
|
|
15108
|
+
};
|
|
15109
|
+
const { text, buttons } = buildPaginatedKeyboard({
|
|
15110
|
+
items: runs,
|
|
15111
|
+
page,
|
|
15112
|
+
pageSize: 10,
|
|
15113
|
+
callbackPrefix: `job:runs:${jobId}:`,
|
|
15114
|
+
renderItem: (r) => {
|
|
15115
|
+
const emoji = statusEmoji[r.status] ?? "\u2753";
|
|
15116
|
+
const duration = r.durationMs ? ` ${(r.durationMs / 1e3).toFixed(1)}s` : "";
|
|
15117
|
+
return {
|
|
15118
|
+
label: `${emoji} ${formatLocalDateTime(r.startedAt)}${duration}`,
|
|
15119
|
+
data: `job:runs:${jobId}:noop`
|
|
15120
|
+
};
|
|
15121
|
+
},
|
|
15122
|
+
headerText: (p, tp, total) => {
|
|
15123
|
+
const pageRuns = runs.slice((p - 1) * 10, p * 10);
|
|
15124
|
+
const lines = [`Runs for Job #${jobId} (${total} total)`, buildSectionHeader("", 22)];
|
|
15125
|
+
for (const r of pageRuns) {
|
|
15126
|
+
const emoji = statusEmoji[r.status] ?? "\u2753";
|
|
15127
|
+
const duration = r.durationMs ? ` (${(r.durationMs / 1e3).toFixed(1)}s)` : "";
|
|
15128
|
+
const error3 = r.error ? `
|
|
15129
|
+
\u2514 ${r.error.slice(0, 80)}` : "";
|
|
15130
|
+
const usage2 = r.usageInput ? `
|
|
15131
|
+
\u2514 Tokens: ${r.usageInput}in / ${r.usageOutput}out` : "";
|
|
15132
|
+
lines.push(`${emoji} ${formatLocalDateTime(r.startedAt)}${duration}${error3}${usage2}`);
|
|
15133
|
+
}
|
|
15134
|
+
return lines.join("\n");
|
|
15135
|
+
},
|
|
15136
|
+
footerButtons: [{ label: "\u2190 Back to Job", data: `job:view:${jobId}` }]
|
|
15137
|
+
});
|
|
15138
|
+
await channel.sendKeyboard(chatId, text, buttons);
|
|
15139
|
+
}
|
|
15140
|
+
async function sendJobPicker(chatId, channel, action) {
|
|
15141
|
+
const allJobs = listJobs();
|
|
15142
|
+
const jobs = action === "resume" ? allJobs.filter((j) => j.active && !j.enabled) : action === "pause" ? allJobs.filter((j) => j.active && j.enabled) : allJobs.filter((j) => j.active);
|
|
15143
|
+
if (jobs.length === 0) {
|
|
15144
|
+
const msg = action === "resume" ? "No paused jobs." : action === "pause" ? "No active jobs to pause." : "No active jobs.";
|
|
15145
|
+
await channel.sendText(chatId, msg, "plain");
|
|
15146
|
+
return;
|
|
15147
|
+
}
|
|
15148
|
+
if (typeof channel.sendKeyboard !== "function") {
|
|
15149
|
+
await channel.sendText(chatId, `Usage: /${action} <job-id>`, "plain");
|
|
15150
|
+
return;
|
|
15151
|
+
}
|
|
15152
|
+
const actionLabel = action.charAt(0).toUpperCase() + action.slice(1);
|
|
15153
|
+
const style = action === "run" ? "success" : action === "cancel" ? "danger" : void 0;
|
|
15154
|
+
const buttons = jobs.map((j) => [{
|
|
15155
|
+
label: `#${j.id}: ${j.description.slice(0, 30)}`,
|
|
15156
|
+
data: action === "cancel" ? `job:cancel:${j.id}` : `job:${action}:${j.id}`,
|
|
15157
|
+
...style ? { style } : {}
|
|
15158
|
+
}]);
|
|
15159
|
+
buttons.push([{ label: "Cancel", data: "job:back" }]);
|
|
15160
|
+
await channel.sendKeyboard(chatId, `${actionLabel} which job?`, buttons);
|
|
15161
|
+
}
|
|
14255
15162
|
async function handleCallback(chatId, data, channel) {
|
|
15163
|
+
if (data.startsWith("menu:")) {
|
|
15164
|
+
const action = data.slice(5);
|
|
15165
|
+
const synth = { chatId, messageId: "", text: "", senderName: "User", type: "command", source: "telegram", command: "", commandArgs: "" };
|
|
15166
|
+
switch (action) {
|
|
15167
|
+
case "newchat":
|
|
15168
|
+
synth.command = "newchat";
|
|
15169
|
+
await handleCommand(synth, channel);
|
|
15170
|
+
break;
|
|
15171
|
+
case "status":
|
|
15172
|
+
synth.command = "status";
|
|
15173
|
+
await handleCommand(synth, channel);
|
|
15174
|
+
break;
|
|
15175
|
+
case "backend":
|
|
15176
|
+
synth.command = "backend";
|
|
15177
|
+
await handleCommand(synth, channel);
|
|
15178
|
+
break;
|
|
15179
|
+
case "model":
|
|
15180
|
+
synth.command = "model";
|
|
15181
|
+
await handleCommand(synth, channel);
|
|
15182
|
+
break;
|
|
15183
|
+
case "jobs":
|
|
15184
|
+
synth.command = "jobs";
|
|
15185
|
+
await handleCommand(synth, channel);
|
|
15186
|
+
break;
|
|
15187
|
+
case "memory":
|
|
15188
|
+
synth.command = "memory";
|
|
15189
|
+
await handleCommand(synth, channel);
|
|
15190
|
+
break;
|
|
15191
|
+
case "history":
|
|
15192
|
+
synth.command = "history";
|
|
15193
|
+
await handleCommand(synth, channel);
|
|
15194
|
+
break;
|
|
15195
|
+
case "help":
|
|
15196
|
+
synth.command = "help";
|
|
15197
|
+
await handleCommand(synth, channel);
|
|
15198
|
+
break;
|
|
15199
|
+
case "permissions":
|
|
15200
|
+
synth.command = "permissions";
|
|
15201
|
+
await handleCommand(synth, channel);
|
|
15202
|
+
break;
|
|
15203
|
+
case "style":
|
|
15204
|
+
synth.command = "response_style";
|
|
15205
|
+
await handleCommand(synth, channel);
|
|
15206
|
+
break;
|
|
15207
|
+
case "health":
|
|
15208
|
+
synth.command = "health";
|
|
15209
|
+
await handleCommand(synth, channel);
|
|
15210
|
+
break;
|
|
15211
|
+
}
|
|
15212
|
+
return;
|
|
15213
|
+
}
|
|
14256
15214
|
if (data.startsWith("backend:")) {
|
|
14257
15215
|
const chosen = data.slice(8);
|
|
14258
15216
|
if (!getAllBackendIds().includes(chosen)) return;
|
|
@@ -14493,9 +15451,9 @@ ${PERM_MODES[chosen]}`,
|
|
|
14493
15451
|
if (typeof channel.sendKeyboard === "function") {
|
|
14494
15452
|
await channel.sendKeyboard(chatId, "\u{1F5E3}\uFE0F AI Response Style:", [
|
|
14495
15453
|
[
|
|
14496
|
-
{ label: `${selectedStyle === "concise" ? "\u2713 " : ""}Concise`, data: "style:concise" },
|
|
14497
|
-
{ label: `${selectedStyle === "normal" ? "\u2713 " : ""}Normal`, data: "style:normal" },
|
|
14498
|
-
{ label: `${selectedStyle === "detailed" ? "\u2713 " : ""}Detailed`, data: "style:detailed" }
|
|
15454
|
+
{ label: `${selectedStyle === "concise" ? "\u2713 " : ""}Concise`, data: "style:concise", ...selectedStyle === "concise" ? { style: "primary" } : {} },
|
|
15455
|
+
{ label: `${selectedStyle === "normal" ? "\u2713 " : ""}Normal`, data: "style:normal", ...selectedStyle === "normal" ? { style: "primary" } : {} },
|
|
15456
|
+
{ label: `${selectedStyle === "detailed" ? "\u2713 " : ""}Detailed`, data: "style:detailed", ...selectedStyle === "detailed" ? { style: "primary" } : {} }
|
|
14499
15457
|
]
|
|
14500
15458
|
]);
|
|
14501
15459
|
}
|
|
@@ -14513,6 +15471,78 @@ ${PERM_MODES[chosen]}`,
|
|
|
14513
15471
|
await channel.sendText(chatId, `Agent mode set to <b>${mode}</b>. Session cleared.`, "html");
|
|
14514
15472
|
}
|
|
14515
15473
|
return;
|
|
15474
|
+
} else if (data.startsWith("agents:")) {
|
|
15475
|
+
const rest = data.slice(7);
|
|
15476
|
+
if (rest.startsWith("stop:")) {
|
|
15477
|
+
const shortId = rest.slice(5);
|
|
15478
|
+
const db3 = getDb();
|
|
15479
|
+
const agents2 = listActiveAgents(db3);
|
|
15480
|
+
const match = agents2.find((a) => a.id.startsWith(shortId));
|
|
15481
|
+
if (!match) {
|
|
15482
|
+
await channel.sendText(chatId, `Agent ${shortId} not found or already stopped. Use /agents to refresh.`, "plain");
|
|
15483
|
+
return;
|
|
15484
|
+
}
|
|
15485
|
+
const ok = cancelAgent(match.id);
|
|
15486
|
+
await channel.sendText(chatId, ok ? `Agent ${match.id.slice(0, 8)} cancelled.` : "Could not cancel agent.", "plain");
|
|
15487
|
+
} else if (rest === "tasks") {
|
|
15488
|
+
const synth = { chatId, messageId: "", text: "", senderName: "User", type: "command", source: "telegram", command: "tasks", commandArgs: "" };
|
|
15489
|
+
await handleCommand(synth, channel);
|
|
15490
|
+
}
|
|
15491
|
+
return;
|
|
15492
|
+
} else if (data.startsWith("stopagent:")) {
|
|
15493
|
+
const rest = data.slice(10);
|
|
15494
|
+
if (rest.startsWith("pick:")) {
|
|
15495
|
+
const shortId = rest.slice(5);
|
|
15496
|
+
const db3 = getDb();
|
|
15497
|
+
const agents2 = listActiveAgents(db3);
|
|
15498
|
+
const match = agents2.find((a) => a.id.startsWith(shortId));
|
|
15499
|
+
if (!match) {
|
|
15500
|
+
await channel.sendText(chatId, `Agent ${shortId} not found or already stopped.`, "plain");
|
|
15501
|
+
return;
|
|
15502
|
+
}
|
|
15503
|
+
const ok = cancelAgent(match.id);
|
|
15504
|
+
await channel.sendText(chatId, ok ? `Agent ${match.id.slice(0, 8)} cancelled.` : "Could not cancel agent.", "plain");
|
|
15505
|
+
} else if (rest === "all") {
|
|
15506
|
+
const count = cancelAllAgents(chatId);
|
|
15507
|
+
await channel.sendText(chatId, count > 0 ? `Cancelled ${count} agent(s).` : "No active agents to cancel.", "plain");
|
|
15508
|
+
} else if (rest === "cancel") {
|
|
15509
|
+
await channel.sendText(chatId, "Cancelled.", "plain");
|
|
15510
|
+
}
|
|
15511
|
+
return;
|
|
15512
|
+
} else if (data.startsWith("tasks:")) {
|
|
15513
|
+
const rest = data.slice(6);
|
|
15514
|
+
if (rest.startsWith("view:")) {
|
|
15515
|
+
const taskId = parseInt(rest.slice(5), 10);
|
|
15516
|
+
const db3 = getDb();
|
|
15517
|
+
const task = getTask(db3, taskId);
|
|
15518
|
+
if (!task) {
|
|
15519
|
+
await channel.sendText(chatId, "Task not found or outdated. Use /tasks to refresh.", "plain");
|
|
15520
|
+
return;
|
|
15521
|
+
}
|
|
15522
|
+
const STATUS_EMOJI_TASK = {
|
|
15523
|
+
pending: "\u{1F4CB}",
|
|
15524
|
+
in_progress: "\u{1F504}",
|
|
15525
|
+
completed: "\u2705",
|
|
15526
|
+
failed: "\u274C",
|
|
15527
|
+
abandoned: "\u{1F6AB}"
|
|
15528
|
+
};
|
|
15529
|
+
const emoji = STATUS_EMOJI_TASK[task.status] ?? "\u{1F4CB}";
|
|
15530
|
+
const assignee = task.assignee ? `Assignee: ${task.assignee.slice(0, 8)}` : "Unassigned";
|
|
15531
|
+
const lines = [
|
|
15532
|
+
`${emoji} Task #${task.id}: ${task.subject}`,
|
|
15533
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
15534
|
+
`Status: ${task.status}`,
|
|
15535
|
+
assignee,
|
|
15536
|
+
`Created: ${task.createdAt}`,
|
|
15537
|
+
task.completedAt ? `Completed: ${task.completedAt}` : "",
|
|
15538
|
+
"",
|
|
15539
|
+
task.description,
|
|
15540
|
+
task.result ? `
|
|
15541
|
+
Result: ${task.result.slice(0, 500)}` : ""
|
|
15542
|
+
].filter(Boolean);
|
|
15543
|
+
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
15544
|
+
}
|
|
15545
|
+
return;
|
|
14516
15546
|
} else if (data.startsWith("grotation:")) {
|
|
14517
15547
|
const mode = data.split(":")[1];
|
|
14518
15548
|
const validModes = ["off", "all", "accounts", "keys"];
|
|
@@ -14766,10 +15796,10 @@ ${PERM_MODES[chosen]}`,
|
|
|
14766
15796
|
const current = getReflectionStatus2(getDb2(), chatId);
|
|
14767
15797
|
if (current === "frozen") {
|
|
14768
15798
|
const { readFileSync: readFileSync21, existsSync: existsSync43 } = await import("fs");
|
|
14769
|
-
const { join:
|
|
15799
|
+
const { join: join26 } = await import("path");
|
|
14770
15800
|
const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
14771
|
-
const soulPath =
|
|
14772
|
-
const userPath =
|
|
15801
|
+
const soulPath = join26(CC_CLAW_HOME3, "identity/SOUL.md");
|
|
15802
|
+
const userPath = join26(CC_CLAW_HOME3, "identity/USER.md");
|
|
14773
15803
|
const soul = existsSync43(soulPath) ? readFileSync21(soulPath, "utf-8") : "";
|
|
14774
15804
|
const user = existsSync43(userPath) ? readFileSync21(userPath, "utf-8") : "";
|
|
14775
15805
|
setReflectionStatus2(getDb2(), chatId, "active", soul, user);
|
|
@@ -14821,6 +15851,378 @@ ${PERM_MODES[chosen]}`,
|
|
|
14821
15851
|
}
|
|
14822
15852
|
}
|
|
14823
15853
|
return;
|
|
15854
|
+
} else if (data.startsWith("summ:")) {
|
|
15855
|
+
const action = data.slice(5);
|
|
15856
|
+
if (action === "all") {
|
|
15857
|
+
const pendingIds = getLoggedChatIds();
|
|
15858
|
+
if (pendingIds.length === 0) {
|
|
15859
|
+
await channel.sendText(chatId, "No pending sessions to summarize.", "plain");
|
|
15860
|
+
return;
|
|
15861
|
+
}
|
|
15862
|
+
await channel.sendText(chatId, `Summarizing ${pendingIds.length} pending session(s)...`, "plain");
|
|
15863
|
+
await summarizeAllPending();
|
|
15864
|
+
await channel.sendText(chatId, `Done. ${pendingIds.length} session(s) summarized and saved to memory.`, "plain");
|
|
15865
|
+
} else if (action === "undo") {
|
|
15866
|
+
const pending = pendingSummaryUndo.get(chatId);
|
|
15867
|
+
if (!pending) {
|
|
15868
|
+
await channel.sendText(chatId, "Undo window expired.", "plain");
|
|
15869
|
+
return;
|
|
15870
|
+
}
|
|
15871
|
+
clearTimeout(pending.timer);
|
|
15872
|
+
pendingSummaryUndo.delete(chatId);
|
|
15873
|
+
const deleted = deleteSessionSummary(pending.summaryId);
|
|
15874
|
+
if (deleted) {
|
|
15875
|
+
await channel.editText?.(chatId, pending.msgId, "\u21A9\uFE0F Summary undone. Session log restored.", "plain");
|
|
15876
|
+
} else {
|
|
15877
|
+
await channel.editText?.(chatId, pending.msgId, "Could not undo \u2014 summary not found.", "plain");
|
|
15878
|
+
}
|
|
15879
|
+
}
|
|
15880
|
+
return;
|
|
15881
|
+
} else if (data.startsWith("reflect:")) {
|
|
15882
|
+
const action = data.slice(8);
|
|
15883
|
+
if (action === "go") {
|
|
15884
|
+
const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
|
|
15885
|
+
const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
15886
|
+
await channel.sendText(chatId, "Analyzing recent interactions...", "plain");
|
|
15887
|
+
try {
|
|
15888
|
+
const insights = await runAnalysis2(chatId);
|
|
15889
|
+
if (insights.length === 0) {
|
|
15890
|
+
await channel.sendText(chatId, "No new insights from recent interactions.", "plain");
|
|
15891
|
+
} else {
|
|
15892
|
+
const items = insights.map((ins, i) => ({ id: i + 1, category: ins.category, insight: ins.insight }));
|
|
15893
|
+
await channel.sendText(chatId, formatNightlySummary2(items) + "\n\nUse /evolve to review and apply proposals.", "plain");
|
|
15894
|
+
}
|
|
15895
|
+
} catch (e) {
|
|
15896
|
+
await channel.sendText(chatId, `Analysis failed: ${e}`, "plain");
|
|
15897
|
+
}
|
|
15898
|
+
} else if (action === "cancel") {
|
|
15899
|
+
await channel.sendText(chatId, "Reflection cancelled.", "plain");
|
|
15900
|
+
}
|
|
15901
|
+
return;
|
|
15902
|
+
} else if (data.startsWith("chats:")) {
|
|
15903
|
+
const action = data.slice(6);
|
|
15904
|
+
if (action === "refresh") {
|
|
15905
|
+
const aliases = getAllChatAliases();
|
|
15906
|
+
if (aliases.length === 0) {
|
|
15907
|
+
await channel.sendKeyboard?.(
|
|
15908
|
+
chatId,
|
|
15909
|
+
"Authorized Chats\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\nNo chat aliases configured yet.",
|
|
15910
|
+
[[{ label: "Refresh", data: "chats:refresh", style: "primary" }]]
|
|
15911
|
+
);
|
|
15912
|
+
} else {
|
|
15913
|
+
const lines = aliases.map(
|
|
15914
|
+
(a) => `${a.alias.padEnd(12)} | ${a.chatId}${a.chatId === chatId ? " [Active]" : ""}`
|
|
15915
|
+
);
|
|
15916
|
+
const text = ["Authorized Chats", "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501", "", ...lines].join("\n");
|
|
15917
|
+
const buttons = [];
|
|
15918
|
+
const actionRow = [];
|
|
15919
|
+
if (aliases.length > 0) {
|
|
15920
|
+
actionRow.push({ label: "Remove", data: "chats:remove", style: "danger" });
|
|
15921
|
+
}
|
|
15922
|
+
actionRow.push({ label: "Refresh", data: "chats:refresh", style: "primary" });
|
|
15923
|
+
buttons.push(actionRow);
|
|
15924
|
+
await channel.sendKeyboard?.(chatId, text, buttons);
|
|
15925
|
+
}
|
|
15926
|
+
} else if (action === "remove") {
|
|
15927
|
+
const aliases = getAllChatAliases();
|
|
15928
|
+
if (aliases.length === 0) {
|
|
15929
|
+
await channel.sendText(chatId, "No aliases to remove.", "plain");
|
|
15930
|
+
return;
|
|
15931
|
+
}
|
|
15932
|
+
const aliasButtons = aliases.map((a, i) => [{ label: a.alias, data: `chats:remove:${i}` }]);
|
|
15933
|
+
aliasButtons.push([{ label: "Cancel", data: "chats:refresh" }]);
|
|
15934
|
+
await channel.sendKeyboard?.(chatId, "Remove which alias?", aliasButtons);
|
|
15935
|
+
} else if (action.startsWith("remove:")) {
|
|
15936
|
+
const idx = parseInt(action.slice(7), 10);
|
|
15937
|
+
const aliases = getAllChatAliases();
|
|
15938
|
+
if (isNaN(idx) || idx < 0 || idx >= aliases.length) {
|
|
15939
|
+
await channel.sendText(chatId, "This data is outdated. Use /chats to refresh.", "plain");
|
|
15940
|
+
return;
|
|
15941
|
+
}
|
|
15942
|
+
const alias = aliases[idx].alias;
|
|
15943
|
+
const removed = removeChatAlias(alias);
|
|
15944
|
+
await channel.sendText(chatId, removed ? `Alias "${alias}" removed.` : `Alias "${alias}" not found.`, "plain");
|
|
15945
|
+
}
|
|
15946
|
+
return;
|
|
15947
|
+
} else if (data.startsWith("hist:")) {
|
|
15948
|
+
const rest = data.slice(5);
|
|
15949
|
+
if (rest.startsWith("recent:")) {
|
|
15950
|
+
const limit = parseInt(rest.slice(7), 10) || 25;
|
|
15951
|
+
await sendHistoryView(chatId, channel, { limit });
|
|
15952
|
+
} else if (rest === "today") {
|
|
15953
|
+
await sendHistoryView(chatId, channel, { period: "today" });
|
|
15954
|
+
} else if (rest === "week") {
|
|
15955
|
+
await sendHistoryView(chatId, channel, { period: "week" });
|
|
15956
|
+
} else if (rest.startsWith("backend:")) {
|
|
15957
|
+
const backend2 = rest.slice(8);
|
|
15958
|
+
await sendHistoryView(chatId, channel, { backend: backend2 });
|
|
15959
|
+
}
|
|
15960
|
+
return;
|
|
15961
|
+
} else if (data.startsWith("forget:")) {
|
|
15962
|
+
const rest = data.slice(7);
|
|
15963
|
+
const page = isPaginationCallback(data, "forget:");
|
|
15964
|
+
if (page !== null) {
|
|
15965
|
+
await sendForgetPicker(chatId, channel, page);
|
|
15966
|
+
} else if (rest.startsWith("pick:")) {
|
|
15967
|
+
const id = parseInt(rest.slice(5), 10);
|
|
15968
|
+
const memory2 = getMemoryById(id);
|
|
15969
|
+
if (!memory2) {
|
|
15970
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
15971
|
+
await channel.sendKeyboard(chatId, "This memory no longer exists.", [
|
|
15972
|
+
[{ label: "Refresh List", data: "forget:page:1" }]
|
|
15973
|
+
]);
|
|
15974
|
+
} else {
|
|
15975
|
+
await channel.sendText(chatId, "This memory no longer exists.", "plain");
|
|
15976
|
+
}
|
|
15977
|
+
return;
|
|
15978
|
+
}
|
|
15979
|
+
const detail = `Delete this memory?
|
|
15980
|
+
"${memory2.trigger}: ${memory2.content}"
|
|
15981
|
+
Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0, 10)}`;
|
|
15982
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
15983
|
+
await channel.sendKeyboard(chatId, detail, [
|
|
15984
|
+
[
|
|
15985
|
+
{ label: "Confirm Delete", data: `forget:confirm:${id}`, style: "danger" },
|
|
15986
|
+
{ label: "Cancel", data: "forget:cancel" }
|
|
15987
|
+
]
|
|
15988
|
+
]);
|
|
15989
|
+
}
|
|
15990
|
+
} else if (rest.startsWith("confirm:")) {
|
|
15991
|
+
const id = parseInt(rest.slice(8), 10);
|
|
15992
|
+
const deleted = deleteMemoryById(id);
|
|
15993
|
+
await channel.sendText(
|
|
15994
|
+
chatId,
|
|
15995
|
+
deleted ? "Memory deleted." : "Memory not found \u2014 already deleted.",
|
|
15996
|
+
"plain"
|
|
15997
|
+
);
|
|
15998
|
+
} else if (rest === "cancel") {
|
|
15999
|
+
await channel.sendText(chatId, "Cancelled.", "plain");
|
|
16000
|
+
}
|
|
16001
|
+
return;
|
|
16002
|
+
} else if (data.startsWith("mem:")) {
|
|
16003
|
+
const rest = data.slice(4);
|
|
16004
|
+
const page = isPaginationCallback(data, "mem:");
|
|
16005
|
+
if (page !== null) {
|
|
16006
|
+
await sendMemoryPage(chatId, channel, page);
|
|
16007
|
+
} else if (rest.startsWith("view:")) {
|
|
16008
|
+
const id = parseInt(rest.slice(5), 10);
|
|
16009
|
+
const memory2 = getMemoryById(id);
|
|
16010
|
+
if (!memory2) {
|
|
16011
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
16012
|
+
await channel.sendKeyboard(chatId, "This memory no longer exists.", [
|
|
16013
|
+
[{ label: "Refresh List", data: "mem:page:1" }]
|
|
16014
|
+
]);
|
|
16015
|
+
} else {
|
|
16016
|
+
await channel.sendText(chatId, "This memory no longer exists.", "plain");
|
|
16017
|
+
}
|
|
16018
|
+
return;
|
|
16019
|
+
}
|
|
16020
|
+
const detail = [
|
|
16021
|
+
buildSectionHeader(`Memory #${memory2.id}`),
|
|
16022
|
+
`Trigger: ${memory2.trigger}`,
|
|
16023
|
+
`Content: ${memory2.content}`,
|
|
16024
|
+
`Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0, 10)}`
|
|
16025
|
+
].join("\n");
|
|
16026
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
16027
|
+
await channel.sendKeyboard(chatId, detail, [
|
|
16028
|
+
[
|
|
16029
|
+
{ label: "Edit", data: `mem:edit:${id}`, style: "primary" },
|
|
16030
|
+
{ label: "Forget", data: `mem:forget:${id}`, style: "danger" }
|
|
16031
|
+
],
|
|
16032
|
+
[{ label: "Back to List", data: "mem:back" }]
|
|
16033
|
+
]);
|
|
16034
|
+
}
|
|
16035
|
+
} else if (rest.startsWith("forget:confirm:")) {
|
|
16036
|
+
const id = parseInt(rest.slice(15), 10);
|
|
16037
|
+
const deleted = deleteMemoryById(id);
|
|
16038
|
+
await channel.sendText(
|
|
16039
|
+
chatId,
|
|
16040
|
+
deleted ? "Memory deleted." : "Memory not found \u2014 already deleted.",
|
|
16041
|
+
"plain"
|
|
16042
|
+
);
|
|
16043
|
+
} else if (rest.startsWith("forget:")) {
|
|
16044
|
+
const id = parseInt(rest.slice(7), 10);
|
|
16045
|
+
const memory2 = getMemoryById(id);
|
|
16046
|
+
if (!memory2) {
|
|
16047
|
+
await channel.sendText(chatId, "Memory not found.", "plain");
|
|
16048
|
+
return;
|
|
16049
|
+
}
|
|
16050
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
16051
|
+
await channel.sendKeyboard(
|
|
16052
|
+
chatId,
|
|
16053
|
+
`Delete this memory?
|
|
16054
|
+
"${memory2.trigger}: ${memory2.content}"`,
|
|
16055
|
+
[
|
|
16056
|
+
[
|
|
16057
|
+
{ label: "Confirm Delete", data: `mem:forget:confirm:${id}`, style: "danger" },
|
|
16058
|
+
{ label: "Cancel", data: `mem:view:${id}` }
|
|
16059
|
+
]
|
|
16060
|
+
]
|
|
16061
|
+
);
|
|
16062
|
+
}
|
|
16063
|
+
} else if (rest.startsWith("edit:")) {
|
|
16064
|
+
const id = parseInt(rest.slice(5), 10);
|
|
16065
|
+
await channel.sendText(chatId, `Type: /memory edit ${id} <new content>`, "plain");
|
|
16066
|
+
} else if (rest === "back") {
|
|
16067
|
+
await sendMemoryPage(chatId, channel, 1);
|
|
16068
|
+
} else if (rest === "showall") {
|
|
16069
|
+
const memories = listMemories();
|
|
16070
|
+
const lines = memories.map(
|
|
16071
|
+
(m, i) => `${i + 1}. [${m.salience.toFixed(2)}] ${m.trigger}: ${m.content}`
|
|
16072
|
+
);
|
|
16073
|
+
const buffer = Buffer.from(lines.join("\n"), "utf-8");
|
|
16074
|
+
await channel.sendFile(chatId, buffer, "memories.txt", "text/plain");
|
|
16075
|
+
}
|
|
16076
|
+
return;
|
|
16077
|
+
} else if (data.startsWith("hb:")) {
|
|
16078
|
+
const rest = data.slice(3);
|
|
16079
|
+
if (rest === "on") {
|
|
16080
|
+
setHeartbeatConfig(chatId, { enabled: true });
|
|
16081
|
+
startHeartbeatForChat(chatId);
|
|
16082
|
+
await sendHeartbeatKeyboard(chatId, channel);
|
|
16083
|
+
} else if (rest === "off") {
|
|
16084
|
+
setHeartbeatConfig(chatId, { enabled: false });
|
|
16085
|
+
stopHeartbeatForChat(chatId);
|
|
16086
|
+
await sendHeartbeatKeyboard(chatId, channel);
|
|
16087
|
+
} else if (rest.startsWith("interval:")) {
|
|
16088
|
+
const min = parseInt(rest.slice(9), 10);
|
|
16089
|
+
if (isNaN(min) || min < 1) return;
|
|
16090
|
+
const ms = min * 6e4;
|
|
16091
|
+
setHeartbeatConfig(chatId, { intervalMs: ms });
|
|
16092
|
+
stopHeartbeatForChat(chatId);
|
|
16093
|
+
const hbConf = getHeartbeatConfig(chatId);
|
|
16094
|
+
if (hbConf?.enabled) startHeartbeatForChat(chatId);
|
|
16095
|
+
await sendHeartbeatKeyboard(chatId, channel);
|
|
16096
|
+
}
|
|
16097
|
+
return;
|
|
16098
|
+
} else if (data.startsWith("newchat:")) {
|
|
16099
|
+
const rest = data.slice(8);
|
|
16100
|
+
if (rest === "undo") {
|
|
16101
|
+
const pending = pendingNewchatUndo.get(chatId);
|
|
16102
|
+
if (!pending) {
|
|
16103
|
+
await channel.sendText(chatId, "Undo window expired.", "plain");
|
|
16104
|
+
return;
|
|
16105
|
+
}
|
|
16106
|
+
clearTimeout(pending.timer);
|
|
16107
|
+
pendingNewchatUndo.delete(chatId);
|
|
16108
|
+
setSessionId(chatId, pending.oldSessionId);
|
|
16109
|
+
await channel.editText?.(chatId, pending.msgId, "\u21A9\uFE0F Previous session restored.", "plain");
|
|
16110
|
+
}
|
|
16111
|
+
return;
|
|
16112
|
+
} else if (data.startsWith("help:")) {
|
|
16113
|
+
const rest = data.slice(5);
|
|
16114
|
+
if (rest === "back") {
|
|
16115
|
+
await sendHelpCategories(chatId, channel);
|
|
16116
|
+
} else {
|
|
16117
|
+
await sendHelpCategory(chatId, rest, channel);
|
|
16118
|
+
}
|
|
16119
|
+
return;
|
|
16120
|
+
} else if (data.startsWith("usage:")) {
|
|
16121
|
+
const rest = data.slice(6);
|
|
16122
|
+
if (rest === "session" || rest === "24h" || rest === "7d" || rest === "30d" || rest === "model") {
|
|
16123
|
+
await sendUnifiedUsage(chatId, channel, rest);
|
|
16124
|
+
} else if (rest === "limits") {
|
|
16125
|
+
await sendUsageLimits(chatId, channel);
|
|
16126
|
+
} else if (rest === "back") {
|
|
16127
|
+
await sendUnifiedUsage(chatId, channel, "session");
|
|
16128
|
+
} else if (rest === "limits:clear") {
|
|
16129
|
+
for (const bid of getAllBackendIds()) {
|
|
16130
|
+
for (const win of ["hourly", "daily", "weekly"]) {
|
|
16131
|
+
clearBackendLimit(bid, win);
|
|
16132
|
+
}
|
|
16133
|
+
}
|
|
16134
|
+
await channel.sendText(chatId, "All usage limits cleared.", "plain");
|
|
16135
|
+
await sendUnifiedUsage(chatId, channel, "session");
|
|
16136
|
+
} else if (rest.startsWith("limits:set:")) {
|
|
16137
|
+
const bid = rest.slice(11);
|
|
16138
|
+
await channel.sendText(chatId, `Set limit for ${bid}:
|
|
16139
|
+
/limits ${bid} daily <tokens>
|
|
16140
|
+
Example: /limits ${bid} daily 500000`, "plain");
|
|
16141
|
+
}
|
|
16142
|
+
return;
|
|
16143
|
+
} else if (data.startsWith("job:")) {
|
|
16144
|
+
const rest = data.slice(4);
|
|
16145
|
+
if (rest === "back") {
|
|
16146
|
+
await sendJobsBoard(chatId, channel, 1);
|
|
16147
|
+
} else if (rest.startsWith("view:")) {
|
|
16148
|
+
const id = parseInt(rest.slice(5), 10);
|
|
16149
|
+
await sendJobDetail(chatId, id, channel);
|
|
16150
|
+
} else if (rest.startsWith("run:")) {
|
|
16151
|
+
const id = parseInt(rest.slice(4), 10);
|
|
16152
|
+
await channel.sendText(chatId, `Triggering job #${id}...`, "plain");
|
|
16153
|
+
const result = await triggerJob(id);
|
|
16154
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
16155
|
+
await channel.sendKeyboard(chatId, result, [
|
|
16156
|
+
[{ label: "\u2190 Back to Job", data: `job:view:${id}` }]
|
|
16157
|
+
]);
|
|
16158
|
+
} else {
|
|
16159
|
+
await channel.sendText(chatId, result, "plain");
|
|
16160
|
+
}
|
|
16161
|
+
} else if (rest.startsWith("pause:")) {
|
|
16162
|
+
const id = parseInt(rest.slice(6), 10);
|
|
16163
|
+
const paused = pauseJob(id);
|
|
16164
|
+
if (paused) {
|
|
16165
|
+
await sendJobDetail(chatId, id, channel);
|
|
16166
|
+
} else {
|
|
16167
|
+
await channel.sendText(chatId, `Job #${id} not found.`, "plain");
|
|
16168
|
+
}
|
|
16169
|
+
} else if (rest.startsWith("resume:")) {
|
|
16170
|
+
const id = parseInt(rest.slice(7), 10);
|
|
16171
|
+
const resumed = resumeJob(id);
|
|
16172
|
+
if (resumed) {
|
|
16173
|
+
await sendJobDetail(chatId, id, channel);
|
|
16174
|
+
} else {
|
|
16175
|
+
await channel.sendText(chatId, `Job #${id} not found.`, "plain");
|
|
16176
|
+
}
|
|
16177
|
+
} else if (rest.startsWith("cancel:confirm:")) {
|
|
16178
|
+
const id = parseInt(rest.slice(15), 10);
|
|
16179
|
+
const cancelled = cancelJob(id);
|
|
16180
|
+
if (cancelled) {
|
|
16181
|
+
await channel.sendText(chatId, `Job #${id} cancelled.`, "plain");
|
|
16182
|
+
await sendJobsBoard(chatId, channel, 1);
|
|
16183
|
+
} else {
|
|
16184
|
+
await channel.sendText(chatId, `Job #${id} not found.`, "plain");
|
|
16185
|
+
}
|
|
16186
|
+
} else if (rest.startsWith("cancel:")) {
|
|
16187
|
+
const id = parseInt(rest.slice(7), 10);
|
|
16188
|
+
const job = getJobById(id);
|
|
16189
|
+
if (!job) {
|
|
16190
|
+
await channel.sendText(chatId, `Job #${id} not found.`, "plain");
|
|
16191
|
+
return;
|
|
16192
|
+
}
|
|
16193
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
16194
|
+
await channel.sendKeyboard(
|
|
16195
|
+
chatId,
|
|
16196
|
+
`Cancel job #${id}: ${job.description}?
|
|
16197
|
+
|
|
16198
|
+
This cannot be undone.`,
|
|
16199
|
+
[
|
|
16200
|
+
[
|
|
16201
|
+
{ label: "Confirm Cancel", data: `job:cancel:confirm:${id}`, style: "danger" },
|
|
16202
|
+
{ label: "\u2190 Back", data: `job:view:${id}` }
|
|
16203
|
+
]
|
|
16204
|
+
]
|
|
16205
|
+
);
|
|
16206
|
+
}
|
|
16207
|
+
} else if (rest.startsWith("runs:")) {
|
|
16208
|
+
const parts = rest.slice(5).split(":");
|
|
16209
|
+
const jobId = parseInt(parts[0], 10);
|
|
16210
|
+
const page = isPaginationCallback(data, `job:runs:${jobId}:`);
|
|
16211
|
+
if (page !== null) {
|
|
16212
|
+
await sendJobRunsView(chatId, jobId, channel, page);
|
|
16213
|
+
} else {
|
|
16214
|
+
await sendJobRunsView(chatId, jobId, channel, 1);
|
|
16215
|
+
}
|
|
16216
|
+
} else if (rest.startsWith("edit:")) {
|
|
16217
|
+
const id = parseInt(rest.slice(5), 10);
|
|
16218
|
+
await startEditWizard(chatId, id, channel);
|
|
16219
|
+
} else {
|
|
16220
|
+
const page = isPaginationCallback(data, "job:");
|
|
16221
|
+
if (page !== null) {
|
|
16222
|
+
await sendJobsBoard(chatId, channel, page);
|
|
16223
|
+
}
|
|
16224
|
+
}
|
|
16225
|
+
return;
|
|
14824
16226
|
}
|
|
14825
16227
|
}
|
|
14826
16228
|
async function handleShell(command, chatId, channel, skipGuard = false) {
|
|
@@ -14833,7 +16235,7 @@ async function handleShell(command, chatId, channel, skipGuard = false) {
|
|
|
14833
16235
|
|
|
14834
16236
|
Command: ${command}`,
|
|
14835
16237
|
[[
|
|
14836
|
-
{ label: "Run anyway", data: `shell:confirm:${id}
|
|
16238
|
+
{ label: "Run anyway", data: `shell:confirm:${id}`, style: "danger" },
|
|
14837
16239
|
{ label: "Cancel", data: `shell:cancel:${id}` }
|
|
14838
16240
|
]]
|
|
14839
16241
|
);
|
|
@@ -14871,7 +16273,7 @@ async function handleRawShell(command, chatId, channel, skipGuard = false) {
|
|
|
14871
16273
|
|
|
14872
16274
|
Command: ${command}`,
|
|
14873
16275
|
[[
|
|
14874
|
-
{ label: "Run anyway", data: `shell:confirm:${id}
|
|
16276
|
+
{ label: "Run anyway", data: `shell:confirm:${id}`, style: "danger" },
|
|
14875
16277
|
{ label: "Cancel", data: `shell:cancel:${id}` }
|
|
14876
16278
|
]]
|
|
14877
16279
|
);
|
|
@@ -14993,14 +16395,16 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
|
|
|
14993
16395
|
const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
|
|
14994
16396
|
await channel.sendKeyboard(chatId, header2, buttons);
|
|
14995
16397
|
}
|
|
14996
|
-
var PERM_MODES, VERBOSE_LEVELS, pendingInterrupts, bypassBusyCheck, pendingFallbackMessages, dashboardClawWarnings, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
|
|
16398
|
+
var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, pendingFallbackMessages, dashboardClawWarnings, pendingSummaryUndo, pendingNewchatUndo, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
|
|
14997
16399
|
var init_router = __esm({
|
|
14998
16400
|
"src/router.ts"() {
|
|
14999
16401
|
"use strict";
|
|
16402
|
+
init_paths();
|
|
15000
16403
|
init_discover();
|
|
15001
16404
|
init_install();
|
|
15002
16405
|
init_profile();
|
|
15003
16406
|
init_heartbeat();
|
|
16407
|
+
init_format();
|
|
15004
16408
|
init_log();
|
|
15005
16409
|
init_format_time();
|
|
15006
16410
|
init_agent();
|
|
@@ -15019,6 +16423,7 @@ var init_router = __esm({
|
|
|
15019
16423
|
init_cron();
|
|
15020
16424
|
init_wizard();
|
|
15021
16425
|
init_health2();
|
|
16426
|
+
init_humanize();
|
|
15022
16427
|
init_video();
|
|
15023
16428
|
init_store();
|
|
15024
16429
|
init_orchestrator();
|
|
@@ -15029,6 +16434,7 @@ var init_router = __esm({
|
|
|
15029
16434
|
init_guard();
|
|
15030
16435
|
init_exec();
|
|
15031
16436
|
init_backend_cmd();
|
|
16437
|
+
init_pagination();
|
|
15032
16438
|
PERM_MODES = {
|
|
15033
16439
|
yolo: "YOLO \u2014 all tools, full autopilot",
|
|
15034
16440
|
safe: "Safe \u2014 only my allowed tools",
|
|
@@ -15039,10 +16445,98 @@ var init_router = __esm({
|
|
|
15039
16445
|
normal: "Normal \u2014 summarized actions",
|
|
15040
16446
|
verbose: "Verbose \u2014 full details"
|
|
15041
16447
|
};
|
|
16448
|
+
HELP_CATEGORIES = {
|
|
16449
|
+
Talk: [
|
|
16450
|
+
{ cmd: "/newchat", desc: "Start a fresh conversation" },
|
|
16451
|
+
{ cmd: "/stop", desc: "Cancel the current running task" },
|
|
16452
|
+
{ cmd: "/summarize", desc: "Save session to memory" },
|
|
16453
|
+
{ cmd: "/history", desc: "Browse conversation history" }
|
|
16454
|
+
],
|
|
16455
|
+
Brain: [
|
|
16456
|
+
{ cmd: "/backend", desc: "Switch AI backend" },
|
|
16457
|
+
{ cmd: "/model", desc: "Switch model" },
|
|
16458
|
+
{ cmd: "/permissions", desc: "Set permission mode" },
|
|
16459
|
+
{ cmd: "/response_style", desc: "Set response verbosity" },
|
|
16460
|
+
{ cmd: "/model_signature", desc: "Toggle model signature" }
|
|
16461
|
+
],
|
|
16462
|
+
Memory: [
|
|
16463
|
+
{ cmd: "/memory", desc: "Browse stored memories" },
|
|
16464
|
+
{ cmd: "/remember", desc: "Save a new memory" },
|
|
16465
|
+
{ cmd: "/forget", desc: "Remove a memory" },
|
|
16466
|
+
{ cmd: "/summarizer", desc: "Configure summarizer" }
|
|
16467
|
+
],
|
|
16468
|
+
Schedule: [
|
|
16469
|
+
{ cmd: "/cron", desc: "Schedule a task (aka /schedule)" },
|
|
16470
|
+
{ cmd: "/jobs", desc: "List scheduled jobs" },
|
|
16471
|
+
{ cmd: "/editjob", desc: "Edit a job" },
|
|
16472
|
+
{ cmd: "/health", desc: "Scheduler health" }
|
|
16473
|
+
],
|
|
16474
|
+
Agents: [
|
|
16475
|
+
{ cmd: "/agents", desc: "List active sub-agents" },
|
|
16476
|
+
{ cmd: "/tasks", desc: "Show task board" },
|
|
16477
|
+
{ cmd: "/stopagent", desc: "Cancel a sub-agent" },
|
|
16478
|
+
{ cmd: "/runners", desc: "List CLI runners" }
|
|
16479
|
+
],
|
|
16480
|
+
Settings: [
|
|
16481
|
+
{ cmd: "/voice", desc: "Toggle voice responses" },
|
|
16482
|
+
{ cmd: "/voice_config", desc: "Configure voice provider" },
|
|
16483
|
+
{ cmd: "/cwd", desc: "Set working directory" },
|
|
16484
|
+
{ cmd: "/tools", desc: "Configure allowed tools" },
|
|
16485
|
+
{ cmd: "/verbose", desc: "Tool visibility level" },
|
|
16486
|
+
{ cmd: "/heartbeat", desc: "Proactive awareness" },
|
|
16487
|
+
{ cmd: "/chats", desc: "Manage authorized chats" },
|
|
16488
|
+
{ cmd: "/setup-profile", desc: "Set up user profile" }
|
|
16489
|
+
],
|
|
16490
|
+
Usage: [
|
|
16491
|
+
{ cmd: "/usage", desc: "Costs, limits & token usage" }
|
|
16492
|
+
],
|
|
16493
|
+
System: [
|
|
16494
|
+
{ cmd: "/status", desc: "System status overview" },
|
|
16495
|
+
{ cmd: "/skills", desc: "List skills" },
|
|
16496
|
+
{ cmd: "/skill-install", desc: "Install skill from GitHub" },
|
|
16497
|
+
{ cmd: "/mcps", desc: "List MCP servers" },
|
|
16498
|
+
{ cmd: "/mcp", desc: "Manage MCP servers" },
|
|
16499
|
+
{ cmd: "/evolve", desc: "Self-learning controls" },
|
|
16500
|
+
{ cmd: "/intent", desc: "Test intent classifier" }
|
|
16501
|
+
]
|
|
16502
|
+
};
|
|
16503
|
+
USAGE_WINDOW_MAP = { "24h": "daily", "7d": "weekly" };
|
|
16504
|
+
MEDIA_INCOMING_PATH = join19(MEDIA_PATH, "incoming");
|
|
16505
|
+
TONE_PATTERNS = [
|
|
16506
|
+
// Humor / laughter
|
|
16507
|
+
{ pattern: /\b(lol|lmao|rofl|haha|hehe|😂|🤣|funny|hilarious|joke)\b/i, emoji: "\u{1F923}" },
|
|
16508
|
+
// Sadness / empathy
|
|
16509
|
+
{ pattern: /\b(sad|upset|depressed|heartbroken|miss|grief|mourning|sorry for|RIP)\b/i, emoji: "\u{1F622}" },
|
|
16510
|
+
// Frustration / anger
|
|
16511
|
+
{ pattern: /\b(angry|furious|pissed|annoyed|hate|wtf|ugh|damn|broken again|still broken)\b/i, emoji: "\u{1F621}" },
|
|
16512
|
+
// Excitement / celebration
|
|
16513
|
+
{ pattern: /\b(amazing|awesome|incredible|congrats|celebrate|woohoo|yay|🎉|shipped|launched)\b/i, emoji: "\u{1F389}" },
|
|
16514
|
+
// Mind-blown / surprise
|
|
16515
|
+
{ pattern: /\b(wow|insane|mind.?blown|unbelievable|no way|holy|crazy|wild)\b/i, emoji: "\u{1F92F}" },
|
|
16516
|
+
// Gratitude / thanks
|
|
16517
|
+
{ pattern: /\b(thanks|thank you|appreciate|grateful|cheers|thx)\b/i, emoji: "\u{1F91D}" },
|
|
16518
|
+
// Agreement / strong positive
|
|
16519
|
+
{ pattern: /\b(exactly|perfect|nailed it|spot on|100%|absolutely|yes!)\b/i, emoji: "\u{1F4AF}" },
|
|
16520
|
+
// Questions / curiosity
|
|
16521
|
+
{ pattern: /\?\s*$/, emoji: "\u{1F914}" },
|
|
16522
|
+
{ pattern: /\b(what|how|why|when|where|can you|could you|do you know)\b.*\?/i, emoji: "\u{1F914}" },
|
|
16523
|
+
// Task / request / command
|
|
16524
|
+
{ pattern: /\b(please|can you|could you|go ahead|do it|make it|fix|build|create|deploy|run|send|update|add|remove|change|set up|configure)\b/i, emoji: "\u{1FAE1}" },
|
|
16525
|
+
// Coding / technical
|
|
16526
|
+
{ pattern: /\b(code|bug|error|function|api|database|git|commit|merge|refactor|test|debug)\b/i, emoji: "\u{1F468}\u200D\u{1F4BB}" },
|
|
16527
|
+
// Interesting / looking at something
|
|
16528
|
+
{ pattern: /\b(look at|check out|see this|interesting|curious|what do you think)\b/i, emoji: "\u{1F440}" },
|
|
16529
|
+
// Love / strong like
|
|
16530
|
+
{ pattern: /\b(love|❤|heart|beautiful|gorgeous)\b/i, emoji: "\u{1F60D}" },
|
|
16531
|
+
// Chill / casual greeting
|
|
16532
|
+
{ pattern: /^(hey|hi|hello|sup|yo|what'?s up|howdy)\b/i, emoji: "\u{1F60E}" }
|
|
16533
|
+
];
|
|
15042
16534
|
pendingInterrupts = /* @__PURE__ */ new Map();
|
|
15043
16535
|
bypassBusyCheck = /* @__PURE__ */ new Set();
|
|
15044
16536
|
pendingFallbackMessages = /* @__PURE__ */ new Map();
|
|
15045
16537
|
dashboardClawWarnings = /* @__PURE__ */ new Map();
|
|
16538
|
+
pendingSummaryUndo = /* @__PURE__ */ new Map();
|
|
16539
|
+
pendingNewchatUndo = /* @__PURE__ */ new Map();
|
|
15046
16540
|
CLI_INSTALL_HINTS = {
|
|
15047
16541
|
claude: "Install: npm install -g @anthropic-ai/claude-code",
|
|
15048
16542
|
gemini: "Install: npm install -g @google/gemini-cli",
|
|
@@ -15138,17 +16632,17 @@ var init_router = __esm({
|
|
|
15138
16632
|
|
|
15139
16633
|
// src/skills/bootstrap.ts
|
|
15140
16634
|
import { existsSync as existsSync16 } from "fs";
|
|
15141
|
-
import { readdir as
|
|
15142
|
-
import { join as
|
|
16635
|
+
import { readdir as readdir4, readFile as readFile6, writeFile as writeFile4, copyFile } from "fs/promises";
|
|
16636
|
+
import { join as join20, dirname as dirname3 } from "path";
|
|
15143
16637
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15144
16638
|
async function copyAgentManifestSkills() {
|
|
15145
16639
|
if (!existsSync16(PKG_SKILLS)) return;
|
|
15146
16640
|
try {
|
|
15147
|
-
const entries = await
|
|
16641
|
+
const entries = await readdir4(PKG_SKILLS, { withFileTypes: true });
|
|
15148
16642
|
for (const entry of entries) {
|
|
15149
16643
|
if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
|
|
15150
|
-
const src =
|
|
15151
|
-
const dest =
|
|
16644
|
+
const src = join20(PKG_SKILLS, entry.name);
|
|
16645
|
+
const dest = join20(SKILLS_PATH, entry.name);
|
|
15152
16646
|
if (existsSync16(dest)) continue;
|
|
15153
16647
|
await copyFile(src, dest);
|
|
15154
16648
|
log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
|
|
@@ -15159,10 +16653,10 @@ async function copyAgentManifestSkills() {
|
|
|
15159
16653
|
}
|
|
15160
16654
|
async function bootstrapSkills() {
|
|
15161
16655
|
await copyAgentManifestSkills();
|
|
15162
|
-
const usmDir =
|
|
16656
|
+
const usmDir = join20(SKILLS_PATH, USM_DIR_NAME);
|
|
15163
16657
|
if (existsSync16(usmDir)) return;
|
|
15164
16658
|
try {
|
|
15165
|
-
const entries = await
|
|
16659
|
+
const entries = await readdir4(SKILLS_PATH);
|
|
15166
16660
|
const dirs = entries.filter((e) => !e.startsWith("."));
|
|
15167
16661
|
if (dirs.length > 0) return;
|
|
15168
16662
|
} catch {
|
|
@@ -15182,7 +16676,7 @@ async function bootstrapSkills() {
|
|
|
15182
16676
|
}
|
|
15183
16677
|
}
|
|
15184
16678
|
async function patchUsmForCcClaw(usmDir) {
|
|
15185
|
-
const skillPath =
|
|
16679
|
+
const skillPath = join20(usmDir, "SKILL.md");
|
|
15186
16680
|
if (!existsSync16(skillPath)) return;
|
|
15187
16681
|
try {
|
|
15188
16682
|
let content = await readFile6(skillPath, "utf-8");
|
|
@@ -15228,8 +16722,8 @@ var init_bootstrap = __esm({
|
|
|
15228
16722
|
USM_REPO = "jacob-bd/universal-skills-manager";
|
|
15229
16723
|
USM_DIR_NAME = "universal-skills-manager";
|
|
15230
16724
|
CC_CLAW_ECOSYSTEM_PATCH = `| **CC-Claw** | \`~/.cc-claw/workspace/skills/\` | N/A (daemon, no project scope) |`;
|
|
15231
|
-
PKG_ROOT =
|
|
15232
|
-
PKG_SKILLS =
|
|
16725
|
+
PKG_ROOT = join20(dirname3(fileURLToPath2(import.meta.url)), "..", "..");
|
|
16726
|
+
PKG_SKILLS = join20(PKG_ROOT, "skills");
|
|
15233
16727
|
}
|
|
15234
16728
|
});
|
|
15235
16729
|
|
|
@@ -15444,7 +16938,7 @@ __export(ai_skill_exports, {
|
|
|
15444
16938
|
installAiSkill: () => installAiSkill
|
|
15445
16939
|
});
|
|
15446
16940
|
import { existsSync as existsSync17, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
15447
|
-
import { join as
|
|
16941
|
+
import { join as join21 } from "path";
|
|
15448
16942
|
import { homedir as homedir5 } from "os";
|
|
15449
16943
|
function generateAiSkill() {
|
|
15450
16944
|
const version = VERSION;
|
|
@@ -15498,6 +16992,12 @@ Use the CC-Claw CLI when you need to:
|
|
|
15498
16992
|
- Every command supports \`--json\` for machine-parseable output
|
|
15499
16993
|
- Use \`--chat <id>\` to target a specific chat context
|
|
15500
16994
|
|
|
16995
|
+
## Telegram Quick Reference
|
|
16996
|
+
|
|
16997
|
+
- \`/menu\` (\`/m\`) \u2014 Home screen keyboard with quick access to all major features
|
|
16998
|
+
- \`/usage\` \u2014 Unified cost, limits, and usage view (\`/cost\` and \`/limits\` are aliases)
|
|
16999
|
+
- \`@botname <query>\` \u2014 Inline mode: memory/history search from any Telegram chat (requires \`/setinline\` in BotFather)
|
|
17000
|
+
|
|
15501
17001
|
## Command Reference
|
|
15502
17002
|
|
|
15503
17003
|
### Diagnostics
|
|
@@ -15745,8 +17245,8 @@ function installAiSkill() {
|
|
|
15745
17245
|
const failed = [];
|
|
15746
17246
|
for (const [backend2, dirs] of Object.entries(BACKEND_SKILL_DIRS2)) {
|
|
15747
17247
|
for (const dir of dirs) {
|
|
15748
|
-
const skillDir =
|
|
15749
|
-
const skillPath =
|
|
17248
|
+
const skillDir = join21(dir, "cc-claw-cli");
|
|
17249
|
+
const skillPath = join21(skillDir, "SKILL.md");
|
|
15750
17250
|
try {
|
|
15751
17251
|
mkdirSync7(skillDir, { recursive: true });
|
|
15752
17252
|
writeFileSync6(skillPath, skill, "utf-8");
|
|
@@ -15765,10 +17265,10 @@ var init_ai_skill = __esm({
|
|
|
15765
17265
|
init_paths();
|
|
15766
17266
|
init_version();
|
|
15767
17267
|
BACKEND_SKILL_DIRS2 = {
|
|
15768
|
-
"cc-claw": [
|
|
15769
|
-
claude: [
|
|
15770
|
-
gemini: [
|
|
15771
|
-
codex: [
|
|
17268
|
+
"cc-claw": [join21(homedir5(), ".cc-claw", "workspace", "skills")],
|
|
17269
|
+
claude: [join21(homedir5(), ".claude", "skills")],
|
|
17270
|
+
gemini: [join21(homedir5(), ".gemini", "skills")],
|
|
17271
|
+
codex: [join21(homedir5(), ".agents", "skills")]
|
|
15772
17272
|
};
|
|
15773
17273
|
}
|
|
15774
17274
|
});
|
|
@@ -15779,17 +17279,17 @@ __export(index_exports, {
|
|
|
15779
17279
|
main: () => main
|
|
15780
17280
|
});
|
|
15781
17281
|
import { mkdirSync as mkdirSync8, existsSync as existsSync18, renameSync, statSync as statSync3, readFileSync as readFileSync12 } from "fs";
|
|
15782
|
-
import { join as
|
|
17282
|
+
import { join as join22 } from "path";
|
|
15783
17283
|
import dotenv from "dotenv";
|
|
15784
17284
|
function migrateLayout() {
|
|
15785
17285
|
const moves = [
|
|
15786
|
-
[
|
|
15787
|
-
[
|
|
15788
|
-
[
|
|
15789
|
-
[
|
|
15790
|
-
[
|
|
15791
|
-
[
|
|
15792
|
-
[
|
|
17286
|
+
[join22(CC_CLAW_HOME, "cc-claw.db"), join22(DATA_PATH, "cc-claw.db")],
|
|
17287
|
+
[join22(CC_CLAW_HOME, "cc-claw.db-shm"), join22(DATA_PATH, "cc-claw.db-shm")],
|
|
17288
|
+
[join22(CC_CLAW_HOME, "cc-claw.db-wal"), join22(DATA_PATH, "cc-claw.db-wal")],
|
|
17289
|
+
[join22(CC_CLAW_HOME, "cc-claw.log"), join22(LOGS_PATH, "cc-claw.log")],
|
|
17290
|
+
[join22(CC_CLAW_HOME, "cc-claw.log.1"), join22(LOGS_PATH, "cc-claw.log.1")],
|
|
17291
|
+
[join22(CC_CLAW_HOME, "cc-claw.error.log"), join22(LOGS_PATH, "cc-claw.error.log")],
|
|
17292
|
+
[join22(CC_CLAW_HOME, "cc-claw.error.log.1"), join22(LOGS_PATH, "cc-claw.error.log.1")]
|
|
15793
17293
|
];
|
|
15794
17294
|
for (const [from, to] of moves) {
|
|
15795
17295
|
if (existsSync18(from) && !existsSync18(to)) {
|
|
@@ -15880,11 +17380,9 @@ async function main() {
|
|
|
15880
17380
|
initOrchestrator();
|
|
15881
17381
|
let notifyQueue = Promise.resolve();
|
|
15882
17382
|
setNotifyCallback((chatId, message) => {
|
|
15883
|
-
const primaryChatId = (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim();
|
|
15884
|
-
const targetChatId = primaryChatId || chatId;
|
|
15885
17383
|
notifyQueue = notifyQueue.then(async () => {
|
|
15886
17384
|
for (const ch of channelRegistry.list()) {
|
|
15887
|
-
await ch.sendText(
|
|
17385
|
+
await ch.sendText(chatId, `\u{1F916} ${message}`, "plain").catch(() => {
|
|
15888
17386
|
});
|
|
15889
17387
|
}
|
|
15890
17388
|
await new Promise((r) => setTimeout(r, 300));
|
|
@@ -15920,10 +17418,10 @@ async function main() {
|
|
|
15920
17418
|
try {
|
|
15921
17419
|
const { generateAiSkill: generateAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
|
|
15922
17420
|
const { writeFileSync: writeFileSync10, mkdirSync: mkdirSync13 } = await import("fs");
|
|
15923
|
-
const { join:
|
|
15924
|
-
const skillDir =
|
|
17421
|
+
const { join: join26 } = await import("path");
|
|
17422
|
+
const skillDir = join26(SKILLS_PATH, "cc-claw-cli");
|
|
15925
17423
|
mkdirSync13(skillDir, { recursive: true });
|
|
15926
|
-
writeFileSync10(
|
|
17424
|
+
writeFileSync10(join26(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
|
|
15927
17425
|
log("[cc-claw] AI skill updated");
|
|
15928
17426
|
} catch {
|
|
15929
17427
|
}
|
|
@@ -15940,6 +17438,8 @@ async function main() {
|
|
|
15940
17438
|
log(`[orchestrator] Agent ${agent.id.slice(0, 8)} idle \u2014 nudging`);
|
|
15941
17439
|
}
|
|
15942
17440
|
});
|
|
17441
|
+
cleanupOldMedia().catch(() => {
|
|
17442
|
+
});
|
|
15943
17443
|
log("[cc-claw] Ready!");
|
|
15944
17444
|
const shutdown = async (signal) => {
|
|
15945
17445
|
log(`[cc-claw] Received ${signal}, shutting down...`);
|
|
@@ -16117,7 +17617,7 @@ __export(service_exports, {
|
|
|
16117
17617
|
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4 } from "fs";
|
|
16118
17618
|
import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
|
|
16119
17619
|
import { homedir as homedir6, platform } from "os";
|
|
16120
|
-
import { join as
|
|
17620
|
+
import { join as join23, dirname as dirname4 } from "path";
|
|
16121
17621
|
function resolveExecutable2(name) {
|
|
16122
17622
|
try {
|
|
16123
17623
|
return execFileSync2("which", [name], { encoding: "utf-8" }).trim();
|
|
@@ -16130,14 +17630,14 @@ function getPathDirs() {
|
|
|
16130
17630
|
const home = homedir6();
|
|
16131
17631
|
const dirs = /* @__PURE__ */ new Set([
|
|
16132
17632
|
nodeBin,
|
|
16133
|
-
|
|
17633
|
+
join23(home, ".local", "bin"),
|
|
16134
17634
|
"/usr/local/bin",
|
|
16135
17635
|
"/usr/bin",
|
|
16136
17636
|
"/bin"
|
|
16137
17637
|
]);
|
|
16138
17638
|
try {
|
|
16139
17639
|
const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
|
|
16140
|
-
if (prefix) dirs.add(
|
|
17640
|
+
if (prefix) dirs.add(join23(prefix, "bin"));
|
|
16141
17641
|
} catch {
|
|
16142
17642
|
}
|
|
16143
17643
|
return [...dirs].join(":");
|
|
@@ -16312,7 +17812,7 @@ function statusLinux() {
|
|
|
16312
17812
|
}
|
|
16313
17813
|
}
|
|
16314
17814
|
function installService() {
|
|
16315
|
-
if (!existsSync20(
|
|
17815
|
+
if (!existsSync20(join23(CC_CLAW_HOME, ".env"))) {
|
|
16316
17816
|
console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
|
|
16317
17817
|
console.error(" Run 'cc-claw setup' before installing the service.");
|
|
16318
17818
|
process.exitCode = 1;
|
|
@@ -16341,9 +17841,9 @@ var init_service = __esm({
|
|
|
16341
17841
|
"use strict";
|
|
16342
17842
|
init_paths();
|
|
16343
17843
|
PLIST_LABEL = "com.cc-claw";
|
|
16344
|
-
PLIST_PATH =
|
|
16345
|
-
SYSTEMD_DIR =
|
|
16346
|
-
UNIT_PATH =
|
|
17844
|
+
PLIST_PATH = join23(homedir6(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
17845
|
+
SYSTEMD_DIR = join23(homedir6(), ".config", "systemd", "user");
|
|
17846
|
+
UNIT_PATH = join23(SYSTEMD_DIR, "cc-claw.service");
|
|
16347
17847
|
}
|
|
16348
17848
|
});
|
|
16349
17849
|
|
|
@@ -16440,7 +17940,7 @@ function outputError(code, message) {
|
|
|
16440
17940
|
}
|
|
16441
17941
|
}
|
|
16442
17942
|
var isJsonMode;
|
|
16443
|
-
var
|
|
17943
|
+
var init_format2 = __esm({
|
|
16444
17944
|
"src/cli/format.ts"() {
|
|
16445
17945
|
"use strict";
|
|
16446
17946
|
isJsonMode = () => process.argv.includes("--json") || !process.stdout.isTTY && !process.argv.includes("--human");
|
|
@@ -16505,7 +18005,7 @@ async function restartService() {
|
|
|
16505
18005
|
var init_daemon = __esm({
|
|
16506
18006
|
"src/cli/commands/daemon.ts"() {
|
|
16507
18007
|
"use strict";
|
|
16508
|
-
|
|
18008
|
+
init_format2();
|
|
16509
18009
|
}
|
|
16510
18010
|
});
|
|
16511
18011
|
|
|
@@ -16659,7 +18159,7 @@ function formatUptime2(seconds) {
|
|
|
16659
18159
|
var init_status = __esm({
|
|
16660
18160
|
"src/cli/commands/status.ts"() {
|
|
16661
18161
|
"use strict";
|
|
16662
|
-
|
|
18162
|
+
init_format2();
|
|
16663
18163
|
init_paths();
|
|
16664
18164
|
init_version();
|
|
16665
18165
|
init_resolve_chat();
|
|
@@ -16888,7 +18388,7 @@ function formatUptime3(seconds) {
|
|
|
16888
18388
|
var init_doctor = __esm({
|
|
16889
18389
|
"src/cli/commands/doctor.ts"() {
|
|
16890
18390
|
"use strict";
|
|
16891
|
-
|
|
18391
|
+
init_format2();
|
|
16892
18392
|
init_paths();
|
|
16893
18393
|
init_version();
|
|
16894
18394
|
}
|
|
@@ -16938,7 +18438,7 @@ var init_logs = __esm({
|
|
|
16938
18438
|
"src/cli/commands/logs.ts"() {
|
|
16939
18439
|
"use strict";
|
|
16940
18440
|
init_paths();
|
|
16941
|
-
|
|
18441
|
+
init_format2();
|
|
16942
18442
|
}
|
|
16943
18443
|
});
|
|
16944
18444
|
|
|
@@ -16955,7 +18455,7 @@ __export(gemini_exports, {
|
|
|
16955
18455
|
geminiRotation: () => geminiRotation
|
|
16956
18456
|
});
|
|
16957
18457
|
import { existsSync as existsSync24, mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, readFileSync as readFileSync17, chmodSync } from "fs";
|
|
16958
|
-
import { join as
|
|
18458
|
+
import { join as join24 } from "path";
|
|
16959
18459
|
import { createInterface as createInterface5 } from "readline";
|
|
16960
18460
|
function requireDb() {
|
|
16961
18461
|
if (!existsSync24(DB_PATH)) {
|
|
@@ -16983,7 +18483,7 @@ async function resolveSlotId(idOrLabel) {
|
|
|
16983
18483
|
function resolveOAuthEmail(configHome) {
|
|
16984
18484
|
if (!configHome) return null;
|
|
16985
18485
|
try {
|
|
16986
|
-
const accountsPath =
|
|
18486
|
+
const accountsPath = join24(configHome, ".gemini", "google_accounts.json");
|
|
16987
18487
|
if (!existsSync24(accountsPath)) return null;
|
|
16988
18488
|
const accounts = JSON.parse(readFileSync17(accountsPath, "utf-8"));
|
|
16989
18489
|
return accounts.active || null;
|
|
@@ -17067,14 +18567,14 @@ async function geminiAddKey(globalOpts, opts) {
|
|
|
17067
18567
|
}
|
|
17068
18568
|
async function geminiAddAccount(globalOpts, opts) {
|
|
17069
18569
|
await requireWriteDb();
|
|
17070
|
-
const slotsDir =
|
|
18570
|
+
const slotsDir = join24(CC_CLAW_HOME, "gemini-slots");
|
|
17071
18571
|
if (!existsSync24(slotsDir)) mkdirSync10(slotsDir, { recursive: true });
|
|
17072
18572
|
const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
17073
18573
|
const tempId = Date.now();
|
|
17074
|
-
const slotDir =
|
|
18574
|
+
const slotDir = join24(slotsDir, `slot-${tempId}`);
|
|
17075
18575
|
mkdirSync10(slotDir, { recursive: true, mode: 448 });
|
|
17076
|
-
mkdirSync10(
|
|
17077
|
-
writeFileSync8(
|
|
18576
|
+
mkdirSync10(join24(slotDir, ".gemini"), { recursive: true });
|
|
18577
|
+
writeFileSync8(join24(slotDir, ".gemini", "settings.json"), JSON.stringify({
|
|
17078
18578
|
security: { auth: { selectedType: "oauth-personal" } }
|
|
17079
18579
|
}, null, 2));
|
|
17080
18580
|
console.log("");
|
|
@@ -17091,7 +18591,7 @@ async function geminiAddAccount(globalOpts, opts) {
|
|
|
17091
18591
|
});
|
|
17092
18592
|
} catch {
|
|
17093
18593
|
}
|
|
17094
|
-
const oauthPath =
|
|
18594
|
+
const oauthPath = join24(slotDir, ".gemini", "oauth_creds.json");
|
|
17095
18595
|
if (!existsSync24(oauthPath)) {
|
|
17096
18596
|
console.log(error2("\n No OAuth credentials found. Sign-in may have failed."));
|
|
17097
18597
|
console.log(" The slot directory is preserved at: " + slotDir);
|
|
@@ -17100,7 +18600,7 @@ async function geminiAddAccount(globalOpts, opts) {
|
|
|
17100
18600
|
}
|
|
17101
18601
|
let accountEmail = "unknown";
|
|
17102
18602
|
try {
|
|
17103
|
-
const accounts = JSON.parse(__require("fs").readFileSync(
|
|
18603
|
+
const accounts = JSON.parse(__require("fs").readFileSync(join24(slotDir, ".gemini", "google_accounts.json"), "utf-8"));
|
|
17104
18604
|
accountEmail = accounts.active || accountEmail;
|
|
17105
18605
|
} catch {
|
|
17106
18606
|
}
|
|
@@ -17193,7 +18693,7 @@ var dbInitialized;
|
|
|
17193
18693
|
var init_gemini2 = __esm({
|
|
17194
18694
|
"src/cli/commands/gemini.ts"() {
|
|
17195
18695
|
"use strict";
|
|
17196
|
-
|
|
18696
|
+
init_format2();
|
|
17197
18697
|
init_paths();
|
|
17198
18698
|
dbInitialized = false;
|
|
17199
18699
|
}
|
|
@@ -17273,7 +18773,7 @@ async function backendSet(globalOpts, name) {
|
|
|
17273
18773
|
var init_backend = __esm({
|
|
17274
18774
|
"src/cli/commands/backend.ts"() {
|
|
17275
18775
|
"use strict";
|
|
17276
|
-
|
|
18776
|
+
init_format2();
|
|
17277
18777
|
init_paths();
|
|
17278
18778
|
init_resolve_chat();
|
|
17279
18779
|
}
|
|
@@ -17356,7 +18856,7 @@ async function modelSet(globalOpts, name) {
|
|
|
17356
18856
|
var init_model = __esm({
|
|
17357
18857
|
"src/cli/commands/model.ts"() {
|
|
17358
18858
|
"use strict";
|
|
17359
|
-
|
|
18859
|
+
init_format2();
|
|
17360
18860
|
init_paths();
|
|
17361
18861
|
init_resolve_chat();
|
|
17362
18862
|
}
|
|
@@ -17448,7 +18948,7 @@ async function memoryHistory(globalOpts, opts) {
|
|
|
17448
18948
|
var init_memory = __esm({
|
|
17449
18949
|
"src/cli/commands/memory.ts"() {
|
|
17450
18950
|
"use strict";
|
|
17451
|
-
|
|
18951
|
+
init_format2();
|
|
17452
18952
|
init_paths();
|
|
17453
18953
|
init_resolve_chat();
|
|
17454
18954
|
init_format_time();
|
|
@@ -17563,7 +19063,7 @@ async function cronCreate(globalOpts, opts) {
|
|
|
17563
19063
|
process.exit(1);
|
|
17564
19064
|
}
|
|
17565
19065
|
const chatId = resolveChatId(globalOpts);
|
|
17566
|
-
const { success: successFmt } = await Promise.resolve().then(() => (
|
|
19066
|
+
const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
17567
19067
|
const timeout = parseAndValidateTimeout(opts.timeout);
|
|
17568
19068
|
try {
|
|
17569
19069
|
const { initDatabase: initDatabase2, insertJob: insertJob2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
@@ -17612,7 +19112,7 @@ async function cronAction(globalOpts, action, id) {
|
|
|
17612
19112
|
process.exit(1);
|
|
17613
19113
|
}
|
|
17614
19114
|
const jobId = parseInt(id, 10);
|
|
17615
|
-
const { success: successFmt } = await Promise.resolve().then(() => (
|
|
19115
|
+
const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
17616
19116
|
const res = await apiPost2(`/api/cron/${action}`, { id: jobId });
|
|
17617
19117
|
if (res.ok) {
|
|
17618
19118
|
const labels = { cancel: "cancelled", pause: "paused", resume: "resumed", run: "triggered" };
|
|
@@ -17631,7 +19131,7 @@ async function cronEdit(globalOpts, id, opts) {
|
|
|
17631
19131
|
process.exit(1);
|
|
17632
19132
|
}
|
|
17633
19133
|
const jobId = parseInt(id, 10);
|
|
17634
|
-
const { success: successFmt } = await Promise.resolve().then(() => (
|
|
19134
|
+
const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
17635
19135
|
try {
|
|
17636
19136
|
const { initDatabase: initDatabase2, getDb: getDb2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
17637
19137
|
initDatabase2();
|
|
@@ -17733,7 +19233,7 @@ async function cronRuns(globalOpts, jobId, opts) {
|
|
|
17733
19233
|
var init_cron2 = __esm({
|
|
17734
19234
|
"src/cli/commands/cron.ts"() {
|
|
17735
19235
|
"use strict";
|
|
17736
|
-
|
|
19236
|
+
init_format2();
|
|
17737
19237
|
init_paths();
|
|
17738
19238
|
init_resolve_chat();
|
|
17739
19239
|
init_types2();
|
|
@@ -17823,7 +19323,7 @@ async function agentsSpawn(globalOpts, opts) {
|
|
|
17823
19323
|
role: opts.role
|
|
17824
19324
|
});
|
|
17825
19325
|
if (res.ok) {
|
|
17826
|
-
const { success: s } = await Promise.resolve().then(() => (
|
|
19326
|
+
const { success: s } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
17827
19327
|
output(res.data, (d) => {
|
|
17828
19328
|
return `
|
|
17829
19329
|
${s(`Agent spawned: ${d.agentId?.slice(0, 8) ?? "?"} (${opts.runner})`)}
|
|
@@ -17842,7 +19342,7 @@ async function agentsCancel(globalOpts, id) {
|
|
|
17842
19342
|
}
|
|
17843
19343
|
const res = await apiPost2("/api/orchestrator/cancel", { agentId: id });
|
|
17844
19344
|
if (res.ok) {
|
|
17845
|
-
const { success: s } = await Promise.resolve().then(() => (
|
|
19345
|
+
const { success: s } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
17846
19346
|
output({ success: true }, () => `
|
|
17847
19347
|
${s(`Agent ${id.slice(0, 8)} cancelled.`)}
|
|
17848
19348
|
`);
|
|
@@ -17860,7 +19360,7 @@ async function agentsCancelAll(globalOpts) {
|
|
|
17860
19360
|
const chatId = resolveChatId(globalOpts);
|
|
17861
19361
|
const res = await apiPost2("/api/orchestrator/cancel-all", { chatId });
|
|
17862
19362
|
if (res.ok) {
|
|
17863
|
-
const { success: s } = await Promise.resolve().then(() => (
|
|
19363
|
+
const { success: s } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
17864
19364
|
const count = res.data?.cancelled ?? 0;
|
|
17865
19365
|
output({ cancelled: count }, () => `
|
|
17866
19366
|
${s(count > 0 ? `Cancelled ${count} agent(s).` : "No active agents.")}
|
|
@@ -17899,7 +19399,7 @@ async function runnersList(globalOpts) {
|
|
|
17899
19399
|
var init_agents = __esm({
|
|
17900
19400
|
"src/cli/commands/agents.ts"() {
|
|
17901
19401
|
"use strict";
|
|
17902
|
-
|
|
19402
|
+
init_format2();
|
|
17903
19403
|
init_paths();
|
|
17904
19404
|
init_resolve_chat();
|
|
17905
19405
|
}
|
|
@@ -17981,7 +19481,7 @@ async function dbBackup(globalOpts, destPath) {
|
|
|
17981
19481
|
var init_db = __esm({
|
|
17982
19482
|
"src/cli/commands/db.ts"() {
|
|
17983
19483
|
"use strict";
|
|
17984
|
-
|
|
19484
|
+
init_format2();
|
|
17985
19485
|
init_paths();
|
|
17986
19486
|
}
|
|
17987
19487
|
});
|
|
@@ -18144,7 +19644,7 @@ async function limitsSet(globalOpts, backend2, window, tokens) {
|
|
|
18144
19644
|
}
|
|
18145
19645
|
const res = await apiPost2("/api/limits/set", { backend: backend2, window, maxInputTokens: parseInt(tokens, 10) });
|
|
18146
19646
|
if (res.ok) {
|
|
18147
|
-
const { success: success2 } = await Promise.resolve().then(() => (
|
|
19647
|
+
const { success: success2 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
18148
19648
|
output({ success: true }, () => `
|
|
18149
19649
|
${success2(`Limit set: ${backend2} (${window}) = ${(parseInt(tokens, 10) / 1e3).toFixed(0)}K input tokens`)}
|
|
18150
19650
|
`);
|
|
@@ -18161,7 +19661,7 @@ async function limitsClear(globalOpts, backend2, window) {
|
|
|
18161
19661
|
}
|
|
18162
19662
|
const res = await apiPost2("/api/limits/clear", { backend: backend2, window });
|
|
18163
19663
|
if (res.ok) {
|
|
18164
|
-
const { success: success2 } = await Promise.resolve().then(() => (
|
|
19664
|
+
const { success: success2 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
18165
19665
|
output({ success: true }, () => `
|
|
18166
19666
|
${success2(`Limit removed for ${backend2} (${window}).`)}
|
|
18167
19667
|
`);
|
|
@@ -18173,7 +19673,7 @@ async function limitsClear(globalOpts, backend2, window) {
|
|
|
18173
19673
|
var init_usage = __esm({
|
|
18174
19674
|
"src/cli/commands/usage.ts"() {
|
|
18175
19675
|
"use strict";
|
|
18176
|
-
|
|
19676
|
+
init_format2();
|
|
18177
19677
|
init_paths();
|
|
18178
19678
|
init_resolve_chat();
|
|
18179
19679
|
}
|
|
@@ -18297,7 +19797,7 @@ var RUNTIME_KEYS, KEY_TABLE_MAP, ALLOWED_TABLES, ALLOWED_COLS;
|
|
|
18297
19797
|
var init_config = __esm({
|
|
18298
19798
|
"src/cli/commands/config.ts"() {
|
|
18299
19799
|
"use strict";
|
|
18300
|
-
|
|
19800
|
+
init_format2();
|
|
18301
19801
|
init_paths();
|
|
18302
19802
|
init_resolve_chat();
|
|
18303
19803
|
RUNTIME_KEYS = ["backend", "model", "thinking", "summarizer", "mode", "verbose", "cwd", "voice", "response-style"];
|
|
@@ -18368,7 +19868,7 @@ async function sessionNew(globalOpts) {
|
|
|
18368
19868
|
var init_session = __esm({
|
|
18369
19869
|
"src/cli/commands/session.ts"() {
|
|
18370
19870
|
"use strict";
|
|
18371
|
-
|
|
19871
|
+
init_format2();
|
|
18372
19872
|
init_paths();
|
|
18373
19873
|
init_resolve_chat();
|
|
18374
19874
|
}
|
|
@@ -18522,7 +20022,7 @@ async function verboseSet(globalOpts, level) {
|
|
|
18522
20022
|
var init_permissions = __esm({
|
|
18523
20023
|
"src/cli/commands/permissions.ts"() {
|
|
18524
20024
|
"use strict";
|
|
18525
|
-
|
|
20025
|
+
init_format2();
|
|
18526
20026
|
init_paths();
|
|
18527
20027
|
init_resolve_chat();
|
|
18528
20028
|
}
|
|
@@ -18587,7 +20087,7 @@ async function cwdClear(globalOpts) {
|
|
|
18587
20087
|
var init_cwd = __esm({
|
|
18588
20088
|
"src/cli/commands/cwd.ts"() {
|
|
18589
20089
|
"use strict";
|
|
18590
|
-
|
|
20090
|
+
init_format2();
|
|
18591
20091
|
init_paths();
|
|
18592
20092
|
init_resolve_chat();
|
|
18593
20093
|
}
|
|
@@ -18638,7 +20138,7 @@ async function voiceSet(globalOpts, value) {
|
|
|
18638
20138
|
var init_voice = __esm({
|
|
18639
20139
|
"src/cli/commands/voice.ts"() {
|
|
18640
20140
|
"use strict";
|
|
18641
|
-
|
|
20141
|
+
init_format2();
|
|
18642
20142
|
init_paths();
|
|
18643
20143
|
init_resolve_chat();
|
|
18644
20144
|
}
|
|
@@ -18749,7 +20249,7 @@ async function heartbeatSet(globalOpts, subcommand, value) {
|
|
|
18749
20249
|
var init_heartbeat2 = __esm({
|
|
18750
20250
|
"src/cli/commands/heartbeat.ts"() {
|
|
18751
20251
|
"use strict";
|
|
18752
|
-
|
|
20252
|
+
init_format2();
|
|
18753
20253
|
init_paths();
|
|
18754
20254
|
init_resolve_chat();
|
|
18755
20255
|
}
|
|
@@ -18821,7 +20321,7 @@ async function chatsRemoveAlias(_globalOpts, name) {
|
|
|
18821
20321
|
var init_chats = __esm({
|
|
18822
20322
|
"src/cli/commands/chats.ts"() {
|
|
18823
20323
|
"use strict";
|
|
18824
|
-
|
|
20324
|
+
init_format2();
|
|
18825
20325
|
init_paths();
|
|
18826
20326
|
}
|
|
18827
20327
|
});
|
|
@@ -18863,7 +20363,7 @@ async function skillsList(_globalOpts) {
|
|
|
18863
20363
|
}
|
|
18864
20364
|
async function skillsInstall(_globalOpts, url) {
|
|
18865
20365
|
const { isDaemonRunning: isDaemonRunning2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
18866
|
-
const { success: successFmt } = await Promise.resolve().then(() => (
|
|
20366
|
+
const { success: successFmt } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
18867
20367
|
try {
|
|
18868
20368
|
const { installSkillFromGitHub: installSkillFromGitHub2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
18869
20369
|
const result = await installSkillFromGitHub2(url);
|
|
@@ -18883,7 +20383,7 @@ async function skillsInstall(_globalOpts, url) {
|
|
|
18883
20383
|
var init_skills = __esm({
|
|
18884
20384
|
"src/cli/commands/skills.ts"() {
|
|
18885
20385
|
"use strict";
|
|
18886
|
-
|
|
20386
|
+
init_format2();
|
|
18887
20387
|
}
|
|
18888
20388
|
});
|
|
18889
20389
|
|
|
@@ -18920,7 +20420,7 @@ async function mcpsList(_globalOpts) {
|
|
|
18920
20420
|
var init_mcps = __esm({
|
|
18921
20421
|
"src/cli/commands/mcps.ts"() {
|
|
18922
20422
|
"use strict";
|
|
18923
|
-
|
|
20423
|
+
init_format2();
|
|
18924
20424
|
init_paths();
|
|
18925
20425
|
}
|
|
18926
20426
|
});
|
|
@@ -19059,7 +20559,7 @@ var TOKEN_PATH2, DEFAULT_PORT2;
|
|
|
19059
20559
|
var init_chat = __esm({
|
|
19060
20560
|
"src/cli/commands/chat.ts"() {
|
|
19061
20561
|
"use strict";
|
|
19062
|
-
|
|
20562
|
+
init_format2();
|
|
19063
20563
|
init_paths();
|
|
19064
20564
|
init_resolve_chat();
|
|
19065
20565
|
TOKEN_PATH2 = `${DATA_PATH}/api-token`;
|
|
@@ -19204,7 +20704,7 @@ async function tuiCommand(globalOpts, cmdOpts) {
|
|
|
19204
20704
|
var init_tui = __esm({
|
|
19205
20705
|
"src/cli/commands/tui.ts"() {
|
|
19206
20706
|
"use strict";
|
|
19207
|
-
|
|
20707
|
+
init_format2();
|
|
19208
20708
|
init_resolve_chat();
|
|
19209
20709
|
}
|
|
19210
20710
|
});
|
|
@@ -19730,7 +21230,7 @@ async function evolveHistory(globalOpts, opts) {
|
|
|
19730
21230
|
var init_evolve = __esm({
|
|
19731
21231
|
"src/cli/commands/evolve.ts"() {
|
|
19732
21232
|
"use strict";
|
|
19733
|
-
|
|
21233
|
+
init_format2();
|
|
19734
21234
|
init_paths();
|
|
19735
21235
|
init_resolve_chat();
|
|
19736
21236
|
init_format_time();
|
|
@@ -19742,7 +21242,7 @@ var setup_exports = {};
|
|
|
19742
21242
|
import { existsSync as existsSync42, writeFileSync as writeFileSync9, readFileSync as readFileSync20, copyFileSync as copyFileSync3, mkdirSync as mkdirSync12, statSync as statSync7 } from "fs";
|
|
19743
21243
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
19744
21244
|
import { createInterface as createInterface7 } from "readline";
|
|
19745
|
-
import { join as
|
|
21245
|
+
import { join as join25 } from "path";
|
|
19746
21246
|
function divider2() {
|
|
19747
21247
|
console.log(dim("\u2500".repeat(55)));
|
|
19748
21248
|
}
|
|
@@ -19829,7 +21329,7 @@ async function setup() {
|
|
|
19829
21329
|
if (match) env[match[1].trim()] = match[2].trim();
|
|
19830
21330
|
}
|
|
19831
21331
|
}
|
|
19832
|
-
const cwdDb =
|
|
21332
|
+
const cwdDb = join25(process.cwd(), "cc-claw.db");
|
|
19833
21333
|
if (existsSync42(cwdDb) && !existsSync42(DB_PATH)) {
|
|
19834
21334
|
const { size } = statSync7(cwdDb);
|
|
19835
21335
|
console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
|
|
@@ -19855,6 +21355,10 @@ async function setup() {
|
|
|
19855
21355
|
console.log(cyan(" 3. Choose a display name (e.g., 'CC-Claw')"));
|
|
19856
21356
|
console.log(cyan(" 4. Choose a username (must end in 'bot', e.g., 'cc_claw_bot')"));
|
|
19857
21357
|
console.log(cyan(" 5. BotFather will give you a token \u2014 copy it\n"));
|
|
21358
|
+
console.log(cyan(" 6. (Optional) Enable inline search \u2014 lets you search memories"));
|
|
21359
|
+
console.log(cyan(" from any chat by typing @your_bot username"));
|
|
21360
|
+
console.log(cyan(" Send /setinline to @BotFather, select your bot, then send:"));
|
|
21361
|
+
console.log(cyan(" Search memories, history, and jobs\n"));
|
|
19858
21362
|
console.log(dim(" The token looks like: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz\n"));
|
|
19859
21363
|
await ask(dim(" Press Enter when you're ready to paste the token..."));
|
|
19860
21364
|
console.log("");
|
|
@@ -20454,7 +21958,7 @@ heartbeat.command("set <subcommand> [value]").description("Configure heartbeat (
|
|
|
20454
21958
|
var summarizer = program.command("summarizer").description("Session summarizer config");
|
|
20455
21959
|
summarizer.command("get").description("Current summarizer config").action(async () => {
|
|
20456
21960
|
if (!(await import("fs")).existsSync(DB_PATH)) {
|
|
20457
|
-
const { outputError: outputError3 } = await Promise.resolve().then(() => (
|
|
21961
|
+
const { outputError: outputError3 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20458
21962
|
outputError3("DB_NOT_FOUND", "Database not found.");
|
|
20459
21963
|
process.exit(1);
|
|
20460
21964
|
}
|
|
@@ -20464,25 +21968,25 @@ summarizer.command("get").description("Current summarizer config").action(async
|
|
|
20464
21968
|
const row = readDb.prepare("SELECT backend, model FROM chat_summarizer WHERE chat_id = ?").get(chatId);
|
|
20465
21969
|
readDb.close();
|
|
20466
21970
|
const value = row?.backend === "off" ? "off" : row?.backend ? `${row.backend}:${row.model ?? "default"}` : "auto";
|
|
20467
|
-
const { output: out } = await Promise.resolve().then(() => (
|
|
21971
|
+
const { output: out } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20468
21972
|
out({ value }, (d) => d.value);
|
|
20469
21973
|
});
|
|
20470
21974
|
summarizer.command("set <value>").description("Set summarizer (auto/off/<backend>:<model>)").action(async (value) => {
|
|
20471
21975
|
const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
20472
21976
|
if (!await isDaemonRunning2()) {
|
|
20473
|
-
const { outputError: outputError3 } = await Promise.resolve().then(() => (
|
|
21977
|
+
const { outputError: outputError3 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20474
21978
|
outputError3("DAEMON_OFFLINE", "Daemon not running.");
|
|
20475
21979
|
process.exit(1);
|
|
20476
21980
|
}
|
|
20477
21981
|
const chatId = program.opts().chat ?? "default";
|
|
20478
21982
|
const res = await apiPost2("/api/summarizer/set", { chatId, value });
|
|
20479
21983
|
if (res.ok) {
|
|
20480
|
-
const { output: out, success: s } = await Promise.resolve().then(() => (
|
|
21984
|
+
const { output: out, success: s } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20481
21985
|
out({ success: true }, () => `
|
|
20482
21986
|
${s(`Summarizer: ${value}`)}
|
|
20483
21987
|
`);
|
|
20484
21988
|
} else {
|
|
20485
|
-
const { outputError: outputError3 } = await Promise.resolve().then(() => (
|
|
21989
|
+
const { outputError: outputError3 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20486
21990
|
outputError3("SET_FAILED", `Failed: ${JSON.stringify(res.data)}`);
|
|
20487
21991
|
process.exit(1);
|
|
20488
21992
|
}
|
|
@@ -20490,7 +21994,7 @@ summarizer.command("set <value>").description("Set summarizer (auto/off/<backend
|
|
|
20490
21994
|
var thinking = program.command("thinking").description("Thinking/reasoning level");
|
|
20491
21995
|
thinking.command("get").description("Current thinking level").action(async () => {
|
|
20492
21996
|
if (!(await import("fs")).existsSync(DB_PATH)) {
|
|
20493
|
-
const { outputError: outputError3 } = await Promise.resolve().then(() => (
|
|
21997
|
+
const { outputError: outputError3 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20494
21998
|
outputError3("DB_NOT_FOUND", "Database not found.");
|
|
20495
21999
|
process.exit(1);
|
|
20496
22000
|
}
|
|
@@ -20499,25 +22003,25 @@ thinking.command("get").description("Current thinking level").action(async () =>
|
|
|
20499
22003
|
const chatId = program.opts().chat ?? "default";
|
|
20500
22004
|
const row = readDb.prepare("SELECT level FROM chat_thinking WHERE chat_id = ?").get(chatId);
|
|
20501
22005
|
readDb.close();
|
|
20502
|
-
const { output: out } = await Promise.resolve().then(() => (
|
|
22006
|
+
const { output: out } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20503
22007
|
out({ level: row?.level ?? "auto" }, (d) => d.level);
|
|
20504
22008
|
});
|
|
20505
22009
|
thinking.command("set <level>").description("Set thinking level").action(async (level) => {
|
|
20506
22010
|
const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
20507
22011
|
if (!await isDaemonRunning2()) {
|
|
20508
|
-
const { outputError: outputError3 } = await Promise.resolve().then(() => (
|
|
22012
|
+
const { outputError: outputError3 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20509
22013
|
outputError3("DAEMON_OFFLINE", "Daemon not running.");
|
|
20510
22014
|
process.exit(1);
|
|
20511
22015
|
}
|
|
20512
22016
|
const chatId = program.opts().chat ?? "default";
|
|
20513
22017
|
const res = await apiPost2("/api/thinking/set", { chatId, level });
|
|
20514
22018
|
if (res.ok) {
|
|
20515
|
-
const { output: out, success: s } = await Promise.resolve().then(() => (
|
|
22019
|
+
const { output: out, success: s } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20516
22020
|
out({ success: true }, () => `
|
|
20517
22021
|
${s(`Thinking: ${level}`)}
|
|
20518
22022
|
`);
|
|
20519
22023
|
} else {
|
|
20520
|
-
const { outputError: outputError3 } = await Promise.resolve().then(() => (
|
|
22024
|
+
const { outputError: outputError3 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20521
22025
|
outputError3("SET_FAILED", `Failed: ${JSON.stringify(res.data)}`);
|
|
20522
22026
|
process.exit(1);
|
|
20523
22027
|
}
|
|
@@ -20557,19 +22061,19 @@ chat.command("send <message>").description("Send a message and get a response").
|
|
|
20557
22061
|
chat.command("stop").description("Cancel the current running task").action(async () => {
|
|
20558
22062
|
const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
20559
22063
|
if (!await isDaemonRunning2()) {
|
|
20560
|
-
const { outputError: outputError3 } = await Promise.resolve().then(() => (
|
|
22064
|
+
const { outputError: outputError3 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20561
22065
|
outputError3("DAEMON_OFFLINE", "Daemon not running.");
|
|
20562
22066
|
process.exit(1);
|
|
20563
22067
|
}
|
|
20564
22068
|
const chatId = program.opts().chat ?? "default";
|
|
20565
22069
|
const res = await apiPost2("/api/chat/stop", { chatId });
|
|
20566
22070
|
if (res.ok) {
|
|
20567
|
-
const { output: out, success: s } = await Promise.resolve().then(() => (
|
|
22071
|
+
const { output: out, success: s } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20568
22072
|
out({ success: true }, () => `
|
|
20569
22073
|
${s("Stopping current task...")}
|
|
20570
22074
|
`);
|
|
20571
22075
|
} else {
|
|
20572
|
-
const { outputError: outputError3 } = await Promise.resolve().then(() => (
|
|
22076
|
+
const { outputError: outputError3 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
20573
22077
|
outputError3("STOP_FAILED", "Nothing is running.");
|
|
20574
22078
|
process.exit(1);
|
|
20575
22079
|
}
|