cc-claw 0.2.7 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +565 -58
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -48,7 +48,7 @@ var VERSION;
|
|
|
48
48
|
var init_version = __esm({
|
|
49
49
|
"src/version.ts"() {
|
|
50
50
|
"use strict";
|
|
51
|
-
VERSION = true ? "0.
|
|
51
|
+
VERSION = true ? "0.3.0" : (() => {
|
|
52
52
|
try {
|
|
53
53
|
return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
|
|
54
54
|
} catch {
|
|
@@ -90,6 +90,7 @@ var init_log = __esm({
|
|
|
90
90
|
var store_exports = {};
|
|
91
91
|
__export(store_exports, {
|
|
92
92
|
claimTask: () => claimTask,
|
|
93
|
+
cleanupStaleAgents: () => cleanupStaleAgents,
|
|
93
94
|
clearState: () => clearState,
|
|
94
95
|
createAgent: () => createAgent,
|
|
95
96
|
createOrchestration: () => createOrchestration,
|
|
@@ -273,6 +274,14 @@ function listActiveAgents(db3) {
|
|
|
273
274
|
function listQueuedAgents(db3) {
|
|
274
275
|
return db3.prepare("SELECT * FROM agents WHERE status = 'queued' ORDER BY createdAt").all();
|
|
275
276
|
}
|
|
277
|
+
function cleanupStaleAgents(db3) {
|
|
278
|
+
const result = db3.prepare(`
|
|
279
|
+
UPDATE agents SET status = 'failed', completedAt = datetime('now'),
|
|
280
|
+
resultSummary = 'Marked stale after service restart'
|
|
281
|
+
WHERE status IN ('queued', 'starting', 'running', 'idle')
|
|
282
|
+
`).run();
|
|
283
|
+
return result.changes;
|
|
284
|
+
}
|
|
276
285
|
function getNextQueuedAgent(db3, orchestrationId) {
|
|
277
286
|
return db3.prepare(
|
|
278
287
|
"SELECT * FROM agents WHERE orchestrationId = ? AND status = 'queued' ORDER BY createdAt LIMIT 1"
|
|
@@ -855,10 +864,13 @@ __export(store_exports2, {
|
|
|
855
864
|
clearThinkingLevel: () => clearThinkingLevel,
|
|
856
865
|
clearUsage: () => clearUsage,
|
|
857
866
|
completeJobRun: () => completeJobRun,
|
|
867
|
+
deleteBookmark: () => deleteBookmark,
|
|
868
|
+
findBookmarksByPrefix: () => findBookmarksByPrefix,
|
|
858
869
|
forgetMemory: () => forgetMemory,
|
|
859
870
|
getActiveJobs: () => getActiveJobs,
|
|
860
871
|
getActiveWatches: () => getActiveWatches,
|
|
861
872
|
getAllBackendLimits: () => getAllBackendLimits,
|
|
873
|
+
getAllBookmarks: () => getAllBookmarks,
|
|
862
874
|
getAllChatAliases: () => getAllChatAliases,
|
|
863
875
|
getAllJobs: () => getAllJobs,
|
|
864
876
|
getAllMemoriesWithEmbeddings: () => getAllMemoriesWithEmbeddings,
|
|
@@ -867,6 +879,7 @@ __export(store_exports2, {
|
|
|
867
879
|
getBackend: () => getBackend,
|
|
868
880
|
getBackendLimit: () => getBackendLimit,
|
|
869
881
|
getBackendUsageInWindow: () => getBackendUsageInWindow,
|
|
882
|
+
getBookmark: () => getBookmark,
|
|
870
883
|
getChatIdByAlias: () => getChatIdByAlias,
|
|
871
884
|
getChatUsageByModel: () => getChatUsageByModel,
|
|
872
885
|
getCwd: () => getCwd,
|
|
@@ -878,6 +891,7 @@ __export(store_exports2, {
|
|
|
878
891
|
getMemoriesWithoutEmbeddings: () => getMemoriesWithoutEmbeddings,
|
|
879
892
|
getMode: () => getMode,
|
|
880
893
|
getModel: () => getModel,
|
|
894
|
+
getRecentBookmarks: () => getRecentBookmarks,
|
|
881
895
|
getRecentMemories: () => getRecentMemories,
|
|
882
896
|
getSessionId: () => getSessionId,
|
|
883
897
|
getSessionStartedAt: () => getSessionStartedAt,
|
|
@@ -920,13 +934,15 @@ __export(store_exports2, {
|
|
|
920
934
|
setThinkingLevel: () => setThinkingLevel,
|
|
921
935
|
setVerboseLevel: () => setVerboseLevel,
|
|
922
936
|
toggleTool: () => toggleTool,
|
|
937
|
+
touchBookmark: () => touchBookmark,
|
|
923
938
|
updateHeartbeatTimestamps: () => updateHeartbeatTimestamps,
|
|
924
939
|
updateJob: () => updateJob,
|
|
925
940
|
updateJobEnabled: () => updateJobEnabled,
|
|
926
941
|
updateJobLastRun: () => updateJobLastRun,
|
|
927
942
|
updateJobNextRun: () => updateJobNextRun,
|
|
928
943
|
updateMemoryEmbedding: () => updateMemoryEmbedding,
|
|
929
|
-
updateSessionSummaryEmbedding: () => updateSessionSummaryEmbedding
|
|
944
|
+
updateSessionSummaryEmbedding: () => updateSessionSummaryEmbedding,
|
|
945
|
+
upsertBookmark: () => upsertBookmark
|
|
930
946
|
});
|
|
931
947
|
import Database from "better-sqlite3";
|
|
932
948
|
function openDatabaseReadOnly() {
|
|
@@ -1288,6 +1304,16 @@ function initDatabase() {
|
|
|
1288
1304
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1289
1305
|
);
|
|
1290
1306
|
`);
|
|
1307
|
+
db.exec(`
|
|
1308
|
+
CREATE TABLE IF NOT EXISTS cwd_bookmarks (
|
|
1309
|
+
chatId TEXT NOT NULL,
|
|
1310
|
+
alias TEXT NOT NULL,
|
|
1311
|
+
path TEXT NOT NULL,
|
|
1312
|
+
manual INTEGER NOT NULL DEFAULT 0,
|
|
1313
|
+
lastUsed INTEGER NOT NULL DEFAULT 0,
|
|
1314
|
+
PRIMARY KEY (chatId, alias)
|
|
1315
|
+
)
|
|
1316
|
+
`);
|
|
1291
1317
|
try {
|
|
1292
1318
|
db.exec("ALTER TABLE memories ADD COLUMN embedding TEXT");
|
|
1293
1319
|
} catch {
|
|
@@ -1482,6 +1508,58 @@ function setCwd(chatId, cwd) {
|
|
|
1482
1508
|
function clearCwd(chatId) {
|
|
1483
1509
|
db.prepare("DELETE FROM chat_cwd WHERE chat_id = ?").run(chatId);
|
|
1484
1510
|
}
|
|
1511
|
+
function upsertBookmark(chatId, alias, path, manual) {
|
|
1512
|
+
const now = Date.now();
|
|
1513
|
+
const existing = db.prepare(
|
|
1514
|
+
"SELECT manual FROM cwd_bookmarks WHERE chatId = ? AND alias = ?"
|
|
1515
|
+
).get(chatId, alias);
|
|
1516
|
+
if (existing) {
|
|
1517
|
+
if (existing.manual === 1 && !manual) {
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
db.prepare(
|
|
1521
|
+
"UPDATE cwd_bookmarks SET path = ?, manual = ?, lastUsed = ? WHERE chatId = ? AND alias = ?"
|
|
1522
|
+
).run(path, manual ? 1 : 0, now, chatId, alias);
|
|
1523
|
+
} else {
|
|
1524
|
+
db.prepare(
|
|
1525
|
+
"INSERT INTO cwd_bookmarks (chatId, alias, path, manual, lastUsed) VALUES (?, ?, ?, ?, ?)"
|
|
1526
|
+
).run(chatId, alias, path, manual ? 1 : 0, now);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
function touchBookmark(chatId, alias) {
|
|
1530
|
+
db.prepare(
|
|
1531
|
+
"UPDATE cwd_bookmarks SET lastUsed = ? WHERE chatId = ? AND alias = ?"
|
|
1532
|
+
).run(Date.now(), chatId, alias);
|
|
1533
|
+
}
|
|
1534
|
+
function getBookmark(chatId, alias) {
|
|
1535
|
+
const row = db.prepare(
|
|
1536
|
+
"SELECT alias, path, manual FROM cwd_bookmarks WHERE chatId = ? AND alias = ?"
|
|
1537
|
+
).get(chatId, alias);
|
|
1538
|
+
return row ? { alias: row.alias, path: row.path, manual: row.manual === 1 } : void 0;
|
|
1539
|
+
}
|
|
1540
|
+
function getRecentBookmarks(chatId, limit = 10) {
|
|
1541
|
+
return db.prepare(
|
|
1542
|
+
"SELECT alias, path FROM cwd_bookmarks WHERE chatId = ? ORDER BY lastUsed DESC LIMIT ?"
|
|
1543
|
+
).all(chatId, limit);
|
|
1544
|
+
}
|
|
1545
|
+
function findBookmarksByPrefix(chatId, prefix) {
|
|
1546
|
+
return db.prepare(
|
|
1547
|
+
"SELECT alias, path FROM cwd_bookmarks WHERE chatId = ? AND alias LIKE ? || '%'"
|
|
1548
|
+
).all(chatId, prefix);
|
|
1549
|
+
}
|
|
1550
|
+
function deleteBookmark(chatId, alias) {
|
|
1551
|
+
const result = db.prepare(
|
|
1552
|
+
"DELETE FROM cwd_bookmarks WHERE chatId = ? AND alias = ?"
|
|
1553
|
+
).run(chatId, alias);
|
|
1554
|
+
return result.changes > 0;
|
|
1555
|
+
}
|
|
1556
|
+
function getAllBookmarks(chatId) {
|
|
1557
|
+
return db.prepare(
|
|
1558
|
+
"SELECT alias, path, manual FROM cwd_bookmarks WHERE chatId = ? ORDER BY alias"
|
|
1559
|
+
).all(chatId).map(
|
|
1560
|
+
(r) => ({ alias: r.alias, path: r.path, manual: r.manual === 1 })
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1485
1563
|
function getModel(chatId) {
|
|
1486
1564
|
const row = db.prepare(
|
|
1487
1565
|
"SELECT model FROM chat_model WHERE chat_id = ?"
|
|
@@ -2190,13 +2268,13 @@ var init_claude = __esm({
|
|
|
2190
2268
|
displayName = "Claude";
|
|
2191
2269
|
availableModels = {
|
|
2192
2270
|
"claude-opus-4-6": {
|
|
2193
|
-
label: "Opus 4.6 \u2014 most capable",
|
|
2271
|
+
label: "Opus 4.6 \u2014 most capable, 1M context",
|
|
2194
2272
|
thinking: "adjustable",
|
|
2195
2273
|
thinkingLevels: ["auto", "off", "low", "medium", "high"],
|
|
2196
2274
|
defaultThinkingLevel: "medium"
|
|
2197
2275
|
},
|
|
2198
2276
|
"claude-sonnet-4-6": {
|
|
2199
|
-
label: "Sonnet 4.6 \u2014 balanced (default)",
|
|
2277
|
+
label: "Sonnet 4.6 \u2014 balanced (default), 1M context",
|
|
2200
2278
|
thinking: "adjustable",
|
|
2201
2279
|
thinkingLevels: ["auto", "off", "low", "medium", "high"],
|
|
2202
2280
|
defaultThinkingLevel: "medium"
|
|
@@ -2216,8 +2294,8 @@ var init_claude = __esm({
|
|
|
2216
2294
|
"claude-haiku-4-5": { in: 0.8, out: 4, cache: 0.08 }
|
|
2217
2295
|
};
|
|
2218
2296
|
contextWindow = {
|
|
2219
|
-
"claude-opus-4-6":
|
|
2220
|
-
"claude-sonnet-4-6":
|
|
2297
|
+
"claude-opus-4-6": 1e6,
|
|
2298
|
+
"claude-sonnet-4-6": 1e6,
|
|
2221
2299
|
"claude-haiku-4-5": 2e5
|
|
2222
2300
|
};
|
|
2223
2301
|
_resolvedPath = "";
|
|
@@ -4351,6 +4429,11 @@ function shutdownOrchestrator() {
|
|
|
4351
4429
|
}
|
|
4352
4430
|
function initOrchestrator() {
|
|
4353
4431
|
cleanupOrphanedMcpConfigs();
|
|
4432
|
+
const db3 = getDb();
|
|
4433
|
+
const staleCount = cleanupStaleAgents(db3);
|
|
4434
|
+
if (staleCount > 0) {
|
|
4435
|
+
log(`[orchestrator] Cleared ${staleCount} stale agent(s) from previous run`);
|
|
4436
|
+
}
|
|
4354
4437
|
log("[orchestrator] Initialized");
|
|
4355
4438
|
}
|
|
4356
4439
|
var activeProcesses, timeoutTimers, runnerLocks, notifyCallback;
|
|
@@ -8264,6 +8347,180 @@ var init_video = __esm({
|
|
|
8264
8347
|
}
|
|
8265
8348
|
});
|
|
8266
8349
|
|
|
8350
|
+
// src/shell/guard.ts
|
|
8351
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
8352
|
+
function isDestructive(command) {
|
|
8353
|
+
return DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(command));
|
|
8354
|
+
}
|
|
8355
|
+
function storePendingCommand(command, chatId, raw) {
|
|
8356
|
+
const id = randomUUID2().slice(0, 8);
|
|
8357
|
+
pendingCommands.set(id, { command, chatId, raw, createdAt: Date.now() });
|
|
8358
|
+
setTimeout(() => pendingCommands.delete(id), PENDING_TTL_MS);
|
|
8359
|
+
return id;
|
|
8360
|
+
}
|
|
8361
|
+
function getPendingCommand(id) {
|
|
8362
|
+
return pendingCommands.get(id);
|
|
8363
|
+
}
|
|
8364
|
+
function removePendingCommand(id) {
|
|
8365
|
+
return pendingCommands.delete(id);
|
|
8366
|
+
}
|
|
8367
|
+
var DESTRUCTIVE_PATTERNS, pendingCommands, PENDING_TTL_MS;
|
|
8368
|
+
var init_guard = __esm({
|
|
8369
|
+
"src/shell/guard.ts"() {
|
|
8370
|
+
"use strict";
|
|
8371
|
+
DESTRUCTIVE_PATTERNS = [
|
|
8372
|
+
/\brm\s+(-[a-zA-Z]*[fr][a-zA-Z]*|-[a-zA-Z]*[rf][a-zA-Z]*|--force|--recursive)\b/,
|
|
8373
|
+
/\brm\s+-[a-zA-Z]*\s+\/\s*$/,
|
|
8374
|
+
/\bmkfs\b/,
|
|
8375
|
+
/\bdd\b.*\bof=/,
|
|
8376
|
+
/\b(shutdown|reboot|halt|poweroff)\b/,
|
|
8377
|
+
/>\s*\/dev\/sd/,
|
|
8378
|
+
/\bchmod\s+-R\s+777\b/,
|
|
8379
|
+
/\bchown\s+-R\b/,
|
|
8380
|
+
/\bkillall\b/,
|
|
8381
|
+
/\blaunchctl\s+(unload|remove)\b/,
|
|
8382
|
+
/:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*:/
|
|
8383
|
+
];
|
|
8384
|
+
pendingCommands = /* @__PURE__ */ new Map();
|
|
8385
|
+
PENDING_TTL_MS = 5 * 60 * 1e3;
|
|
8386
|
+
}
|
|
8387
|
+
});
|
|
8388
|
+
|
|
8389
|
+
// src/shell/exec.ts
|
|
8390
|
+
import { execFile as execFile3 } from "child_process";
|
|
8391
|
+
function executeShell(command, cwd, timeoutMs = SHELL_TIMEOUT_MS) {
|
|
8392
|
+
return new Promise((resolve) => {
|
|
8393
|
+
execFile3(
|
|
8394
|
+
"/bin/zsh",
|
|
8395
|
+
["-c", command],
|
|
8396
|
+
{ cwd, timeout: timeoutMs, maxBuffer: 5 * 1024 * 1024 },
|
|
8397
|
+
(error3, stdout, stderr) => {
|
|
8398
|
+
const output2 = (stdout || "") + (stderr || "");
|
|
8399
|
+
if (error3 && "killed" in error3 && error3.killed) {
|
|
8400
|
+
resolve({
|
|
8401
|
+
output: `Command timed out after ${Math.round(timeoutMs / 1e3)} seconds`,
|
|
8402
|
+
exitCode: 124,
|
|
8403
|
+
timedOut: true
|
|
8404
|
+
});
|
|
8405
|
+
return;
|
|
8406
|
+
}
|
|
8407
|
+
const exitCode = error3 ? typeof error3.code === "number" ? error3.code : 1 : 0;
|
|
8408
|
+
resolve({
|
|
8409
|
+
output: output2,
|
|
8410
|
+
exitCode,
|
|
8411
|
+
timedOut: false
|
|
8412
|
+
});
|
|
8413
|
+
}
|
|
8414
|
+
);
|
|
8415
|
+
});
|
|
8416
|
+
}
|
|
8417
|
+
function formatCodeBlock(command, output2, exitCode) {
|
|
8418
|
+
return `<pre>$ ${command}
|
|
8419
|
+
${output2}
|
|
8420
|
+
[exit ${exitCode}]</pre>`;
|
|
8421
|
+
}
|
|
8422
|
+
function formatRaw(output2) {
|
|
8423
|
+
return output2.trim();
|
|
8424
|
+
}
|
|
8425
|
+
function shouldSendAsFile(output2) {
|
|
8426
|
+
return output2.length > OUTPUT_MAX_LENGTH;
|
|
8427
|
+
}
|
|
8428
|
+
var SHELL_TIMEOUT_MS, OUTPUT_MAX_LENGTH;
|
|
8429
|
+
var init_exec = __esm({
|
|
8430
|
+
"src/shell/exec.ts"() {
|
|
8431
|
+
"use strict";
|
|
8432
|
+
SHELL_TIMEOUT_MS = 6e4;
|
|
8433
|
+
OUTPUT_MAX_LENGTH = 4e3;
|
|
8434
|
+
}
|
|
8435
|
+
});
|
|
8436
|
+
|
|
8437
|
+
// src/shell/backend-cmd.ts
|
|
8438
|
+
import { execFile as execFile4 } from "child_process";
|
|
8439
|
+
function buildNativeResponse(backendId, command, output2) {
|
|
8440
|
+
const body = output2 || "(no output)";
|
|
8441
|
+
return `<pre>[${backendId}] ${command}
|
|
8442
|
+
${body}</pre>`;
|
|
8443
|
+
}
|
|
8444
|
+
function extractTextFromNdjson(raw) {
|
|
8445
|
+
const texts = [];
|
|
8446
|
+
for (const line of raw.split("\n")) {
|
|
8447
|
+
if (!line.trim()) continue;
|
|
8448
|
+
try {
|
|
8449
|
+
const obj = JSON.parse(line);
|
|
8450
|
+
if (obj.type === "result" && obj.result) {
|
|
8451
|
+
texts.push(typeof obj.result === "string" ? obj.result : JSON.stringify(obj.result));
|
|
8452
|
+
}
|
|
8453
|
+
if (obj.type === "assistant" && obj.message?.content) {
|
|
8454
|
+
for (const block of obj.message.content) {
|
|
8455
|
+
if (block.type === "text" && block.text) texts.push(block.text);
|
|
8456
|
+
}
|
|
8457
|
+
}
|
|
8458
|
+
if (obj.type === "text" && obj.text) {
|
|
8459
|
+
texts.push(obj.text);
|
|
8460
|
+
}
|
|
8461
|
+
} catch {
|
|
8462
|
+
}
|
|
8463
|
+
}
|
|
8464
|
+
return texts.join("\n").trim();
|
|
8465
|
+
}
|
|
8466
|
+
async function handleBackendCommand(command, chatId, channel) {
|
|
8467
|
+
let adapter;
|
|
8468
|
+
try {
|
|
8469
|
+
adapter = getAdapterForChat(chatId);
|
|
8470
|
+
} catch {
|
|
8471
|
+
await channel.sendText(chatId, "No backend set. Use /backend first.", "plain");
|
|
8472
|
+
return;
|
|
8473
|
+
}
|
|
8474
|
+
const sessionId = getSessionId(chatId);
|
|
8475
|
+
if (!sessionId) {
|
|
8476
|
+
await channel.sendText(chatId, "No active session. Start a conversation first.", "plain");
|
|
8477
|
+
return;
|
|
8478
|
+
}
|
|
8479
|
+
const cwd = getCwd(chatId) ?? `${process.env.HOME ?? "/tmp"}/.cc-claw/workspace`;
|
|
8480
|
+
try {
|
|
8481
|
+
const output2 = await spawnBackendCommand(adapter, command, sessionId, cwd);
|
|
8482
|
+
const formatted = buildNativeResponse(adapter.id, command, output2);
|
|
8483
|
+
await channel.sendText(chatId, formatted, "html");
|
|
8484
|
+
} catch (err) {
|
|
8485
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8486
|
+
await channel.sendText(chatId, `Backend command failed: ${msg}`, "plain");
|
|
8487
|
+
}
|
|
8488
|
+
}
|
|
8489
|
+
function spawnBackendCommand(adapter, command, sessionId, cwd) {
|
|
8490
|
+
return new Promise((resolve, reject) => {
|
|
8491
|
+
const config2 = adapter.buildSpawnConfig({
|
|
8492
|
+
prompt: command,
|
|
8493
|
+
sessionId,
|
|
8494
|
+
cwd,
|
|
8495
|
+
permMode: "plan",
|
|
8496
|
+
allowedTools: []
|
|
8497
|
+
});
|
|
8498
|
+
const env = { ...process.env, ...adapter.getEnv() };
|
|
8499
|
+
execFile4(config2.executable, config2.args, {
|
|
8500
|
+
cwd,
|
|
8501
|
+
timeout: BACKEND_CMD_TIMEOUT_MS,
|
|
8502
|
+
env,
|
|
8503
|
+
maxBuffer: 5 * 1024 * 1024
|
|
8504
|
+
}, (error3, stdout, stderr) => {
|
|
8505
|
+
if (error3 && "killed" in error3 && error3.killed) {
|
|
8506
|
+
reject(new Error(`Backend command timed out after ${BACKEND_CMD_TIMEOUT_MS / 1e3} seconds.`));
|
|
8507
|
+
return;
|
|
8508
|
+
}
|
|
8509
|
+
const output2 = extractTextFromNdjson(stdout) || stdout || stderr || "";
|
|
8510
|
+
resolve(output2.trim());
|
|
8511
|
+
});
|
|
8512
|
+
});
|
|
8513
|
+
}
|
|
8514
|
+
var BACKEND_CMD_TIMEOUT_MS;
|
|
8515
|
+
var init_backend_cmd = __esm({
|
|
8516
|
+
"src/shell/backend-cmd.ts"() {
|
|
8517
|
+
"use strict";
|
|
8518
|
+
init_backends();
|
|
8519
|
+
init_store4();
|
|
8520
|
+
BACKEND_CMD_TIMEOUT_MS = 15e3;
|
|
8521
|
+
}
|
|
8522
|
+
});
|
|
8523
|
+
|
|
8267
8524
|
// src/router.ts
|
|
8268
8525
|
import { readFile as readFile5, writeFile as writeFile2, unlink as unlink2 } from "fs/promises";
|
|
8269
8526
|
import { resolve as resolvePath } from "path";
|
|
@@ -8353,6 +8610,17 @@ async function handleMessage(msg, channel) {
|
|
|
8353
8610
|
}
|
|
8354
8611
|
}
|
|
8355
8612
|
}
|
|
8613
|
+
if (msg.type === "text" && msg.text) {
|
|
8614
|
+
const text = msg.text.trim();
|
|
8615
|
+
if (text.startsWith("!!")) {
|
|
8616
|
+
const cmd = text.slice(2).trim();
|
|
8617
|
+
if (cmd) return handleRawShell(cmd, chatId, channel);
|
|
8618
|
+
}
|
|
8619
|
+
if (text.startsWith("!") && text.length > 1) {
|
|
8620
|
+
const cmd = text.slice(1).trim();
|
|
8621
|
+
if (cmd) return handleShell(cmd, chatId, channel);
|
|
8622
|
+
}
|
|
8623
|
+
}
|
|
8356
8624
|
switch (msg.type) {
|
|
8357
8625
|
case "command":
|
|
8358
8626
|
await handleCommand(msg, channel);
|
|
@@ -8372,6 +8640,9 @@ async function handleMessage(msg, channel) {
|
|
|
8372
8640
|
}
|
|
8373
8641
|
async function handleCommand(msg, channel) {
|
|
8374
8642
|
const { chatId, command, commandArgs } = msg;
|
|
8643
|
+
if (command?.startsWith("/")) {
|
|
8644
|
+
return handleBackendCommand(command, chatId, channel);
|
|
8645
|
+
}
|
|
8375
8646
|
switch (command) {
|
|
8376
8647
|
case "start":
|
|
8377
8648
|
case "help":
|
|
@@ -8506,15 +8777,7 @@ Tap to toggle:`,
|
|
|
8506
8777
|
case "backend": {
|
|
8507
8778
|
const requestedBackend = (commandArgs ?? "").trim().toLowerCase();
|
|
8508
8779
|
if (requestedBackend && getAllBackendIds().includes(requestedBackend)) {
|
|
8509
|
-
|
|
8510
|
-
});
|
|
8511
|
-
clearSession(chatId);
|
|
8512
|
-
clearModel(chatId);
|
|
8513
|
-
clearThinkingLevel(chatId);
|
|
8514
|
-
setBackend(chatId, requestedBackend);
|
|
8515
|
-
const adapter = getAdapter(requestedBackend);
|
|
8516
|
-
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Backend switched to ${adapter.displayName}`, detail: { field: "backend", value: requestedBackend } });
|
|
8517
|
-
await channel.sendText(chatId, `Switched to <b>${adapter.displayName}</b>.`, "html");
|
|
8780
|
+
await sendBackendSwitchConfirmation(chatId, requestedBackend, channel);
|
|
8518
8781
|
break;
|
|
8519
8782
|
}
|
|
8520
8783
|
const currentBackend = getBackend(chatId);
|
|
@@ -8538,15 +8801,7 @@ Tap to toggle:`,
|
|
|
8538
8801
|
case "codex": {
|
|
8539
8802
|
const backendId = command;
|
|
8540
8803
|
if (getAllBackendIds().includes(backendId)) {
|
|
8541
|
-
|
|
8542
|
-
});
|
|
8543
|
-
clearSession(chatId);
|
|
8544
|
-
clearModel(chatId);
|
|
8545
|
-
clearThinkingLevel(chatId);
|
|
8546
|
-
setBackend(chatId, backendId);
|
|
8547
|
-
const adapter = getAdapter(backendId);
|
|
8548
|
-
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Backend switched to ${adapter.displayName}`, detail: { field: "backend", value: backendId } });
|
|
8549
|
-
await channel.sendText(chatId, `Switched to <b>${adapter.displayName}</b>.`, "html");
|
|
8804
|
+
await sendBackendSwitchConfirmation(chatId, backendId, channel);
|
|
8550
8805
|
} else {
|
|
8551
8806
|
await channel.sendText(chatId, `Backend "${command}" is not available.`, "plain");
|
|
8552
8807
|
}
|
|
@@ -8719,11 +8974,28 @@ Use /summarizer auto, /summarizer off, or /summarizer <backend>:<model>`, "plain
|
|
|
8719
8974
|
case "cwd": {
|
|
8720
8975
|
if (!commandArgs) {
|
|
8721
8976
|
const current = getCwd(chatId);
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8977
|
+
const recents = getRecentBookmarks(chatId, 10);
|
|
8978
|
+
if (recents.length === 0) {
|
|
8979
|
+
await channel.sendText(
|
|
8980
|
+
chatId,
|
|
8981
|
+
current ? `Working directory: ${current}
|
|
8982
|
+
|
|
8983
|
+
No saved bookmarks yet. Set a directory with /cwd <path> to auto-save.` : "No working directory set. Usage: /cwd ~/projects/my-app",
|
|
8984
|
+
"plain"
|
|
8985
|
+
);
|
|
8986
|
+
return;
|
|
8987
|
+
}
|
|
8988
|
+
const text = current ? `Current: ${current}
|
|
8989
|
+
|
|
8990
|
+
Recent directories:` : "Recent directories:";
|
|
8991
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
8992
|
+
const buttons = recents.map((r) => [{ label: r.alias, data: `cwdpick:${r.alias}` }]);
|
|
8993
|
+
await channel.sendKeyboard(chatId, text, buttons);
|
|
8994
|
+
} else {
|
|
8995
|
+
const list = recents.map((r) => ` ${r.alias} \u2192 ${r.path}`).join("\n");
|
|
8996
|
+
await channel.sendText(chatId, `${text}
|
|
8997
|
+
${list}`, "plain");
|
|
8998
|
+
}
|
|
8727
8999
|
return;
|
|
8728
9000
|
}
|
|
8729
9001
|
if (commandArgs === "reset" || commandArgs === "clear") {
|
|
@@ -8731,12 +9003,71 @@ Use /summarizer auto, /summarizer off, or /summarizer <backend>:<model>`, "plain
|
|
|
8731
9003
|
await channel.sendText(chatId, "Working directory cleared. Using default.", "plain");
|
|
8732
9004
|
return;
|
|
8733
9005
|
}
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
9006
|
+
if (commandArgs === "aliases") {
|
|
9007
|
+
const all = getAllBookmarks(chatId);
|
|
9008
|
+
if (all.length === 0) {
|
|
9009
|
+
await channel.sendText(chatId, "No bookmarks saved yet.", "plain");
|
|
9010
|
+
return;
|
|
9011
|
+
}
|
|
9012
|
+
const lines = all.map((b) => ` ${b.manual ? "[manual]" : "[auto]"} ${b.alias} \u2192 ${b.path}`);
|
|
9013
|
+
await channel.sendText(chatId, `Directory bookmarks:
|
|
9014
|
+
${lines.join("\n")}`, "plain");
|
|
9015
|
+
return;
|
|
9016
|
+
}
|
|
9017
|
+
if (commandArgs.startsWith("unalias ")) {
|
|
9018
|
+
const aliasName = commandArgs.slice(8).trim();
|
|
9019
|
+
if (!aliasName) {
|
|
9020
|
+
await channel.sendText(chatId, "Usage: /cwd unalias <name>", "plain");
|
|
9021
|
+
return;
|
|
9022
|
+
}
|
|
9023
|
+
const deleted = deleteBookmark(chatId, aliasName);
|
|
9024
|
+
await channel.sendText(chatId, deleted ? `Bookmark '${aliasName}' removed.` : `Bookmark '${aliasName}' not found.`, "plain");
|
|
9025
|
+
return;
|
|
9026
|
+
}
|
|
9027
|
+
if (commandArgs.startsWith("alias ")) {
|
|
9028
|
+
const parts = commandArgs.slice(6).trim().split(/\s+/);
|
|
9029
|
+
if (parts.length < 2) {
|
|
9030
|
+
await channel.sendText(chatId, "Usage: /cwd alias <name> <path>", "plain");
|
|
9031
|
+
return;
|
|
9032
|
+
}
|
|
9033
|
+
const [aliasName, ...pathParts] = parts;
|
|
9034
|
+
const aliasPath = pathParts.join(" ").replace(/^~/, process.env.HOME ?? "");
|
|
9035
|
+
upsertBookmark(chatId, aliasName, aliasPath, true);
|
|
9036
|
+
await channel.sendText(chatId, `Bookmark saved: ${aliasName} \u2192 ${aliasPath}`, "plain");
|
|
9037
|
+
return;
|
|
9038
|
+
}
|
|
9039
|
+
const arg = commandArgs;
|
|
9040
|
+
if (arg.startsWith("/") || arg.startsWith("~")) {
|
|
9041
|
+
const resolvedPath = arg.startsWith("~") ? arg.replace("~", process.env.HOME ?? "") : arg;
|
|
9042
|
+
setCwd(chatId, resolvedPath);
|
|
9043
|
+
const basename2 = resolvedPath.split("/").filter(Boolean).pop();
|
|
9044
|
+
if (basename2) upsertBookmark(chatId, basename2, resolvedPath, false);
|
|
9045
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${resolvedPath}`, detail: { field: "cwd", value: resolvedPath } });
|
|
9046
|
+
await sendCwdSessionChoice(chatId, resolvedPath, channel);
|
|
9047
|
+
return;
|
|
9048
|
+
}
|
|
9049
|
+
const exact = getBookmark(chatId, arg);
|
|
9050
|
+
if (exact) {
|
|
9051
|
+
setCwd(chatId, exact.path);
|
|
9052
|
+
touchBookmark(chatId, arg);
|
|
9053
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${exact.path}`, detail: { field: "cwd", value: exact.path } });
|
|
9054
|
+
await sendCwdSessionChoice(chatId, exact.path, channel);
|
|
9055
|
+
return;
|
|
9056
|
+
}
|
|
9057
|
+
const matches = findBookmarksByPrefix(chatId, arg);
|
|
9058
|
+
if (matches.length === 1) {
|
|
9059
|
+
setCwd(chatId, matches[0].path);
|
|
9060
|
+
touchBookmark(chatId, matches[0].alias);
|
|
9061
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${matches[0].path}`, detail: { field: "cwd", value: matches[0].path } });
|
|
9062
|
+
await sendCwdSessionChoice(chatId, matches[0].path, channel);
|
|
9063
|
+
return;
|
|
9064
|
+
}
|
|
9065
|
+
if (matches.length > 1 && typeof channel.sendKeyboard === "function") {
|
|
9066
|
+
const buttons = matches.map((m) => [{ label: `${m.alias} \u2192 ${m.path}`, data: `cwdpick:${m.alias}` }]);
|
|
9067
|
+
await channel.sendKeyboard(chatId, `Multiple matches for "${arg}":`, buttons);
|
|
9068
|
+
return;
|
|
9069
|
+
}
|
|
9070
|
+
await channel.sendText(chatId, `Directory alias '${arg}' not found. Use /cwd aliases to see saved bookmarks.`, "plain");
|
|
8740
9071
|
break;
|
|
8741
9072
|
}
|
|
8742
9073
|
case "memory": {
|
|
@@ -9241,7 +9572,7 @@ Use /skills to see it.`, "plain");
|
|
|
9241
9572
|
lines.push("", "\u2501\u2501 <b>Built-in</b> \u2501\u2501");
|
|
9242
9573
|
lines.push(` \u2705 <b>cc-claw</b> <i>Agent orchestrator (spawn, tasks, inbox)</i>`);
|
|
9243
9574
|
}
|
|
9244
|
-
const { execFile:
|
|
9575
|
+
const { execFile: execFile5 } = await import("child_process");
|
|
9245
9576
|
const { homedir: homedir5 } = await import("os");
|
|
9246
9577
|
const discoveryCwd = homedir5();
|
|
9247
9578
|
const runnerResults = await Promise.allSettled(
|
|
@@ -9250,7 +9581,7 @@ Use /skills to see it.`, "plain");
|
|
|
9250
9581
|
if (!listCmd.length) return Promise.resolve({ runner, output: "" });
|
|
9251
9582
|
const exe = runner.getExecutablePath();
|
|
9252
9583
|
return new Promise((resolve) => {
|
|
9253
|
-
|
|
9584
|
+
execFile5(exe, listCmd.slice(1), {
|
|
9254
9585
|
encoding: "utf-8",
|
|
9255
9586
|
timeout: 3e4,
|
|
9256
9587
|
cwd: discoveryCwd,
|
|
@@ -9697,31 +10028,65 @@ async function sendResponse(chatId, channel, text) {
|
|
|
9697
10028
|
function isImageExt(ext) {
|
|
9698
10029
|
return ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].includes(ext);
|
|
9699
10030
|
}
|
|
10031
|
+
async function sendBackendSwitchConfirmation(chatId, target, channel) {
|
|
10032
|
+
const current = getBackend(chatId);
|
|
10033
|
+
const targetAdapter = getAdapter(target);
|
|
10034
|
+
if (current === target) {
|
|
10035
|
+
await channel.sendText(chatId, `Already using ${targetAdapter.displayName}.`, "plain");
|
|
10036
|
+
return;
|
|
10037
|
+
}
|
|
10038
|
+
const currentLabel = current ? getAdapter(current).displayName : "current backend";
|
|
10039
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10040
|
+
await channel.sendKeyboard(
|
|
10041
|
+
chatId,
|
|
10042
|
+
`\u26A0\uFE0F Switching to ${targetAdapter.displayName} will summarize and reset your current session.
|
|
10043
|
+
|
|
10044
|
+
What would you like to do?`,
|
|
10045
|
+
[
|
|
10046
|
+
[{ label: `Stay on ${currentLabel}`, data: "backend_cancel" }],
|
|
10047
|
+
[{ label: `Switch to ${targetAdapter.displayName} + summarize`, data: `backend_confirm:${target}` }]
|
|
10048
|
+
]
|
|
10049
|
+
);
|
|
10050
|
+
} else {
|
|
10051
|
+
await doBackendSwitch(chatId, target, channel);
|
|
10052
|
+
}
|
|
10053
|
+
}
|
|
10054
|
+
async function doBackendSwitch(chatId, backendId, channel) {
|
|
10055
|
+
summarizeSession(chatId).catch(() => {
|
|
10056
|
+
});
|
|
10057
|
+
clearSession(chatId);
|
|
10058
|
+
clearModel(chatId);
|
|
10059
|
+
clearThinkingLevel(chatId);
|
|
10060
|
+
setBackend(chatId, backendId);
|
|
10061
|
+
const adapter = getAdapter(backendId);
|
|
10062
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Backend switched to ${adapter.displayName}`, detail: { field: "backend", value: backendId } });
|
|
10063
|
+
await channel.sendText(
|
|
10064
|
+
chatId,
|
|
10065
|
+
`Backend switched to ${adapter.displayName}.
|
|
10066
|
+
Default model: ${adapter.defaultModel}
|
|
10067
|
+
Session reset. Ready!`,
|
|
10068
|
+
"plain"
|
|
10069
|
+
);
|
|
10070
|
+
}
|
|
9700
10071
|
async function handleCallback(chatId, data, channel) {
|
|
9701
10072
|
if (data.startsWith("backend:")) {
|
|
9702
10073
|
const chosen = data.slice(8);
|
|
9703
10074
|
if (!getAllBackendIds().includes(chosen)) return;
|
|
9704
10075
|
const previous = getBackend(chatId);
|
|
9705
10076
|
if (chosen === previous) {
|
|
9706
|
-
const
|
|
9707
|
-
await channel.sendText(chatId, `Already using ${
|
|
10077
|
+
const adapter = getAdapter(chosen);
|
|
10078
|
+
await channel.sendText(chatId, `Already using ${adapter.displayName}.`, "plain");
|
|
9708
10079
|
return;
|
|
9709
10080
|
}
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
const
|
|
9717
|
-
|
|
9718
|
-
await channel.sendText(
|
|
9719
|
-
chatId,
|
|
9720
|
-
`Backend switched to ${adapter.displayName}.
|
|
9721
|
-
Default model: ${adapter.defaultModel}
|
|
9722
|
-
Session reset. Ready!`,
|
|
9723
|
-
"plain"
|
|
9724
|
-
);
|
|
10081
|
+
await sendBackendSwitchConfirmation(chatId, chosen, channel);
|
|
10082
|
+
} else if (data.startsWith("backend_confirm:")) {
|
|
10083
|
+
const chosen = data.slice(16);
|
|
10084
|
+
if (!getAllBackendIds().includes(chosen)) return;
|
|
10085
|
+
await doBackendSwitch(chatId, chosen, channel);
|
|
10086
|
+
} else if (data === "backend_cancel") {
|
|
10087
|
+
const current = getBackend(chatId);
|
|
10088
|
+
const label2 = current ? getAdapter(current).displayName : "current backend";
|
|
10089
|
+
await channel.sendText(chatId, `No change. Staying on ${label2}.`, "plain");
|
|
9725
10090
|
} else if (data.startsWith("model:")) {
|
|
9726
10091
|
const chosen = data.slice(6);
|
|
9727
10092
|
let adapter;
|
|
@@ -9735,7 +10100,6 @@ Session reset. Ready!`,
|
|
|
9735
10100
|
if (!modelInfo) return;
|
|
9736
10101
|
setModel(chatId, chosen);
|
|
9737
10102
|
clearThinkingLevel(chatId);
|
|
9738
|
-
clearSession(chatId);
|
|
9739
10103
|
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Model switched to ${modelInfo.label}`, detail: { field: "model", value: chosen } });
|
|
9740
10104
|
if (modelInfo.thinking === "adjustable" && modelInfo.thinkingLevels) {
|
|
9741
10105
|
const thinkingButtons = modelInfo.thinkingLevels.map((level) => [{
|
|
@@ -9745,16 +10109,16 @@ Session reset. Ready!`,
|
|
|
9745
10109
|
if (typeof channel.sendKeyboard === "function") {
|
|
9746
10110
|
await channel.sendKeyboard(
|
|
9747
10111
|
chatId,
|
|
9748
|
-
`Model set to ${modelInfo.label}.
|
|
10112
|
+
`Model set to ${modelInfo.label}. Session continues.
|
|
9749
10113
|
|
|
9750
10114
|
Select thinking/effort level:`,
|
|
9751
10115
|
thinkingButtons
|
|
9752
10116
|
);
|
|
9753
10117
|
} else {
|
|
9754
|
-
await channel.sendText(chatId, `Model set to ${modelInfo.label}. Session
|
|
10118
|
+
await channel.sendText(chatId, `Model set to ${modelInfo.label}. Session continues.`, "plain");
|
|
9755
10119
|
}
|
|
9756
10120
|
} else {
|
|
9757
|
-
await channel.sendText(chatId, `Model switched to ${modelInfo.label}. Session
|
|
10121
|
+
await channel.sendText(chatId, `Model switched to ${modelInfo.label}. Session continues.`, "plain");
|
|
9758
10122
|
}
|
|
9759
10123
|
} else if (data.startsWith("thinking:")) {
|
|
9760
10124
|
const level = data.slice(9);
|
|
@@ -9821,6 +10185,51 @@ ${PERM_MODES[chosen]}`,
|
|
|
9821
10185
|
} else {
|
|
9822
10186
|
await channel.sendText(chatId, "Preference not saved.", "plain");
|
|
9823
10187
|
}
|
|
10188
|
+
} else if (data.startsWith("shell:")) {
|
|
10189
|
+
const parts = data.split(":");
|
|
10190
|
+
const action = parts[1];
|
|
10191
|
+
const id = parts[2];
|
|
10192
|
+
if (action === "confirm") {
|
|
10193
|
+
const pending = getPendingCommand(id);
|
|
10194
|
+
if (!pending) {
|
|
10195
|
+
await channel.sendText(chatId, "Confirmation expired. Please re-send the command.", "plain");
|
|
10196
|
+
return;
|
|
10197
|
+
}
|
|
10198
|
+
removePendingCommand(id);
|
|
10199
|
+
if (pending.raw) {
|
|
10200
|
+
await handleRawShell(pending.command, pending.chatId, channel, true);
|
|
10201
|
+
} else {
|
|
10202
|
+
await handleShell(pending.command, pending.chatId, channel, true);
|
|
10203
|
+
}
|
|
10204
|
+
} else if (action === "cancel") {
|
|
10205
|
+
removePendingCommand(id);
|
|
10206
|
+
await channel.sendText(chatId, "Command cancelled.", "plain");
|
|
10207
|
+
}
|
|
10208
|
+
} else if (data.startsWith("cwd:")) {
|
|
10209
|
+
const parts = data.split(":");
|
|
10210
|
+
const action = parts[1];
|
|
10211
|
+
const targetChatId = parts.slice(2).join(":");
|
|
10212
|
+
if (action === "keep") {
|
|
10213
|
+
await channel.sendText(chatId, "Session kept. The agent will continue with existing context.", "plain");
|
|
10214
|
+
} else if (action === "summarize") {
|
|
10215
|
+
await summarizeSession(targetChatId);
|
|
10216
|
+
clearSession(targetChatId);
|
|
10217
|
+
await channel.sendText(chatId, "Session summarized and reset. Context preserved in memory.", "plain");
|
|
10218
|
+
} else if (action === "reset") {
|
|
10219
|
+
clearSession(targetChatId);
|
|
10220
|
+
await channel.sendText(chatId, "Session reset. Clean slate.", "plain");
|
|
10221
|
+
}
|
|
10222
|
+
} else if (data.startsWith("cwdpick:")) {
|
|
10223
|
+
const alias = data.slice(8);
|
|
10224
|
+
const bookmark = getBookmark(chatId, alias);
|
|
10225
|
+
if (!bookmark) {
|
|
10226
|
+
await channel.sendText(chatId, `Bookmark '${alias}' no longer exists.`, "plain");
|
|
10227
|
+
return;
|
|
10228
|
+
}
|
|
10229
|
+
setCwd(chatId, bookmark.path);
|
|
10230
|
+
touchBookmark(chatId, alias);
|
|
10231
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${bookmark.path}`, detail: { field: "cwd", value: bookmark.path } });
|
|
10232
|
+
await sendCwdSessionChoice(chatId, bookmark.path, channel);
|
|
9824
10233
|
} else if (data.startsWith("skill:")) {
|
|
9825
10234
|
const parts = data.slice(6).split(":");
|
|
9826
10235
|
let skillName;
|
|
@@ -9858,6 +10267,101 @@ ${PERM_MODES[chosen]}`,
|
|
|
9858
10267
|
await sendResponse(chatId, channel, response.text);
|
|
9859
10268
|
}
|
|
9860
10269
|
}
|
|
10270
|
+
async function handleShell(command, chatId, channel, skipGuard = false) {
|
|
10271
|
+
if (!skipGuard && isDestructive(command)) {
|
|
10272
|
+
const id = storePendingCommand(command, chatId, false);
|
|
10273
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10274
|
+
await channel.sendKeyboard(
|
|
10275
|
+
chatId,
|
|
10276
|
+
`\u26A0\uFE0F This command looks potentially destructive.
|
|
10277
|
+
|
|
10278
|
+
Command: ${command}`,
|
|
10279
|
+
[[
|
|
10280
|
+
{ label: "Run anyway", data: `shell:confirm:${id}` },
|
|
10281
|
+
{ label: "Cancel", data: `shell:cancel:${id}` }
|
|
10282
|
+
]]
|
|
10283
|
+
);
|
|
10284
|
+
} else {
|
|
10285
|
+
await channel.sendText(chatId, `\u26A0\uFE0F Destructive command blocked: ${command}
|
|
10286
|
+
No keyboard available to confirm.`, "plain");
|
|
10287
|
+
}
|
|
10288
|
+
return;
|
|
10289
|
+
}
|
|
10290
|
+
const cwd = getCwd(chatId) ?? `${process.env.HOME ?? "/tmp"}/.cc-claw/workspace`;
|
|
10291
|
+
const result = await executeShell(command, cwd);
|
|
10292
|
+
logActivity(getDb(), {
|
|
10293
|
+
chatId,
|
|
10294
|
+
source: "telegram",
|
|
10295
|
+
eventType: "shell_command",
|
|
10296
|
+
summary: `Shell: ${command.slice(0, 100)}`,
|
|
10297
|
+
detail: { command, exitCode: result.exitCode, outputLength: result.output.length, timedOut: result.timedOut }
|
|
10298
|
+
});
|
|
10299
|
+
const formatted = formatCodeBlock(command, result.output, result.exitCode);
|
|
10300
|
+
if (shouldSendAsFile(formatted)) {
|
|
10301
|
+
const buffer = Buffer.from(result.output, "utf-8");
|
|
10302
|
+
await channel.sendFile(chatId, buffer, "output.txt", "text/plain");
|
|
10303
|
+
await channel.sendText(chatId, `Output too long (${result.output.length} chars), sent as file.`, "plain");
|
|
10304
|
+
} else {
|
|
10305
|
+
await channel.sendText(chatId, formatted, "html");
|
|
10306
|
+
}
|
|
10307
|
+
}
|
|
10308
|
+
async function handleRawShell(command, chatId, channel, skipGuard = false) {
|
|
10309
|
+
if (!skipGuard && isDestructive(command)) {
|
|
10310
|
+
const id = storePendingCommand(command, chatId, true);
|
|
10311
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10312
|
+
await channel.sendKeyboard(
|
|
10313
|
+
chatId,
|
|
10314
|
+
`\u26A0\uFE0F This command looks potentially destructive.
|
|
10315
|
+
|
|
10316
|
+
Command: ${command}`,
|
|
10317
|
+
[[
|
|
10318
|
+
{ label: "Run anyway", data: `shell:confirm:${id}` },
|
|
10319
|
+
{ label: "Cancel", data: `shell:cancel:${id}` }
|
|
10320
|
+
]]
|
|
10321
|
+
);
|
|
10322
|
+
} else {
|
|
10323
|
+
await channel.sendText(chatId, `\u26A0\uFE0F Destructive command blocked: ${command}
|
|
10324
|
+
No keyboard available to confirm.`, "plain");
|
|
10325
|
+
}
|
|
10326
|
+
return;
|
|
10327
|
+
}
|
|
10328
|
+
const cwd = getCwd(chatId) ?? `${process.env.HOME ?? "/tmp"}/.cc-claw/workspace`;
|
|
10329
|
+
const result = await executeShell(command, cwd);
|
|
10330
|
+
logActivity(getDb(), {
|
|
10331
|
+
chatId,
|
|
10332
|
+
source: "telegram",
|
|
10333
|
+
eventType: "shell_command",
|
|
10334
|
+
summary: `Shell: ${command.slice(0, 100)}`,
|
|
10335
|
+
detail: { command, exitCode: result.exitCode, outputLength: result.output.length, timedOut: result.timedOut }
|
|
10336
|
+
});
|
|
10337
|
+
const formatted = formatRaw(result.output);
|
|
10338
|
+
if (shouldSendAsFile(formatted)) {
|
|
10339
|
+
const buffer = Buffer.from(result.output, "utf-8");
|
|
10340
|
+
await channel.sendFile(chatId, buffer, "output.txt", "text/plain");
|
|
10341
|
+
await channel.sendText(chatId, `Output too long (${result.output.length} chars), sent as file.`, "plain");
|
|
10342
|
+
} else {
|
|
10343
|
+
await channel.sendText(chatId, formatted, "plain");
|
|
10344
|
+
}
|
|
10345
|
+
}
|
|
10346
|
+
async function sendCwdSessionChoice(chatId, path, channel) {
|
|
10347
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10348
|
+
await channel.sendKeyboard(
|
|
10349
|
+
chatId,
|
|
10350
|
+
`Working directory set to: ${path}
|
|
10351
|
+
|
|
10352
|
+
Changing directories mid-session may confuse the agent.
|
|
10353
|
+
What would you like to do?`,
|
|
10354
|
+
[[
|
|
10355
|
+
{ label: "Keep session", data: `cwd:keep:${chatId}` },
|
|
10356
|
+
{ label: "Summarize & reset", data: `cwd:summarize:${chatId}` },
|
|
10357
|
+
{ label: "Reset session", data: `cwd:reset:${chatId}` }
|
|
10358
|
+
]]
|
|
10359
|
+
);
|
|
10360
|
+
} else {
|
|
10361
|
+
await channel.sendText(chatId, `Working directory set to: ${path}
|
|
10362
|
+
Session kept.`, "plain");
|
|
10363
|
+
}
|
|
10364
|
+
}
|
|
9861
10365
|
function parseIntervalToMs(input) {
|
|
9862
10366
|
const match = input.trim().match(/^(\d+)\s*(m|min|h|hr|hour|s|sec)$/i);
|
|
9863
10367
|
if (!match) return null;
|
|
@@ -9925,6 +10429,9 @@ var init_router = __esm({
|
|
|
9925
10429
|
init_registry();
|
|
9926
10430
|
init_registry2();
|
|
9927
10431
|
init_store3();
|
|
10432
|
+
init_guard();
|
|
10433
|
+
init_exec();
|
|
10434
|
+
init_backend_cmd();
|
|
9928
10435
|
PERM_MODES = {
|
|
9929
10436
|
yolo: "YOLO \u2014 all tools, full autopilot",
|
|
9930
10437
|
safe: "Safe \u2014 only my allowed tools",
|
package/package.json
CHANGED