cc-claw 0.3.3 → 0.3.5
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 +1 -0
- package/dist/cli.js +274 -78
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,6 +124,7 @@ Read commands work offline (direct DB access). Write commands require the daemon
|
|
|
124
124
|
| `/help` | All commands |
|
|
125
125
|
| `/status` | Backend, model, session, usage |
|
|
126
126
|
| `/newchat` | Start fresh (summarizes current session) |
|
|
127
|
+
| `/summarize` | Save session to memory without resetting |
|
|
127
128
|
| `/stop` | Cancel running task |
|
|
128
129
|
|
|
129
130
|
### Backend & Model
|
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.3.
|
|
51
|
+
VERSION = true ? "0.3.5" : (() => {
|
|
52
52
|
try {
|
|
53
53
|
return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
|
|
54
54
|
} catch {
|
|
@@ -1143,6 +1143,7 @@ function initDatabase() {
|
|
|
1143
1143
|
backend TEXT,
|
|
1144
1144
|
model TEXT,
|
|
1145
1145
|
thinking TEXT,
|
|
1146
|
+
timeout INTEGER,
|
|
1146
1147
|
session_type TEXT NOT NULL DEFAULT 'isolated',
|
|
1147
1148
|
channel TEXT,
|
|
1148
1149
|
target TEXT,
|
|
@@ -1177,6 +1178,7 @@ function initDatabase() {
|
|
|
1177
1178
|
backend TEXT,
|
|
1178
1179
|
model TEXT,
|
|
1179
1180
|
thinking TEXT,
|
|
1181
|
+
timeout INTEGER,
|
|
1180
1182
|
session_type TEXT NOT NULL DEFAULT 'isolated',
|
|
1181
1183
|
channel TEXT,
|
|
1182
1184
|
target TEXT,
|
|
@@ -1257,6 +1259,10 @@ function initDatabase() {
|
|
|
1257
1259
|
db.exec("ALTER TABLE jobs ADD COLUMN consecutive_failures INTEGER NOT NULL DEFAULT 0");
|
|
1258
1260
|
} catch {
|
|
1259
1261
|
}
|
|
1262
|
+
try {
|
|
1263
|
+
db.exec("ALTER TABLE jobs ADD COLUMN timeout INTEGER");
|
|
1264
|
+
} catch {
|
|
1265
|
+
}
|
|
1260
1266
|
}
|
|
1261
1267
|
} catch {
|
|
1262
1268
|
}
|
|
@@ -1949,8 +1955,8 @@ function getBackendUsageInWindow(backend2, windowType) {
|
|
|
1949
1955
|
function insertJob(params) {
|
|
1950
1956
|
const result = db.prepare(`
|
|
1951
1957
|
INSERT INTO jobs (schedule_type, cron, at_time, every_ms, description, chat_id,
|
|
1952
|
-
backend, model, thinking, session_type, channel, target, delivery_mode, timezone)
|
|
1953
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1958
|
+
backend, model, thinking, timeout, session_type, channel, target, delivery_mode, timezone)
|
|
1959
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1954
1960
|
`).run(
|
|
1955
1961
|
params.scheduleType,
|
|
1956
1962
|
params.cron ?? null,
|
|
@@ -1961,6 +1967,7 @@ function insertJob(params) {
|
|
|
1961
1967
|
params.backend ?? null,
|
|
1962
1968
|
params.model ?? null,
|
|
1963
1969
|
params.thinking ?? null,
|
|
1970
|
+
params.timeout ?? null,
|
|
1964
1971
|
params.sessionType ?? "isolated",
|
|
1965
1972
|
params.channel ?? null,
|
|
1966
1973
|
params.target ?? null,
|
|
@@ -2012,6 +2019,7 @@ function updateJob(id, fields) {
|
|
|
2012
2019
|
backend: "backend",
|
|
2013
2020
|
model: "model",
|
|
2014
2021
|
thinking: "thinking",
|
|
2022
|
+
timeout: "timeout",
|
|
2015
2023
|
sessionType: "session_type",
|
|
2016
2024
|
channel: "channel",
|
|
2017
2025
|
target: "target",
|
|
@@ -2275,7 +2283,7 @@ var init_store4 = __esm({
|
|
|
2275
2283
|
ALL_TOOLS = ["Read", "Glob", "Grep", "Bash", "Write", "Edit", "WebFetch", "WebSearch", "Agent", "AskUserQuestion"];
|
|
2276
2284
|
JOB_SELECT = `
|
|
2277
2285
|
SELECT id, schedule_type as scheduleType, cron, at_time as atTime, every_ms as everyMs,
|
|
2278
|
-
description, chat_id as chatId, backend, model, thinking,
|
|
2286
|
+
description, chat_id as chatId, backend, model, thinking, timeout,
|
|
2279
2287
|
session_type as sessionType, channel, target, delivery_mode as deliveryMode,
|
|
2280
2288
|
timezone, enabled, active, created_at as createdAt, last_run_at as lastRunAt,
|
|
2281
2289
|
next_run_at as nextRunAt, consecutive_failures as consecutiveFailures
|
|
@@ -5230,9 +5238,14 @@ data: ${JSON.stringify(data)}
|
|
|
5230
5238
|
`);
|
|
5231
5239
|
};
|
|
5232
5240
|
try {
|
|
5233
|
-
const response = await askAgent2(chatId, body.message,
|
|
5234
|
-
|
|
5235
|
-
|
|
5241
|
+
const response = await askAgent2(chatId, body.message, {
|
|
5242
|
+
cwd,
|
|
5243
|
+
onStream: (partial) => {
|
|
5244
|
+
sendSSE("text", partial);
|
|
5245
|
+
},
|
|
5246
|
+
model: model2,
|
|
5247
|
+
permMode: mode
|
|
5248
|
+
});
|
|
5236
5249
|
if (response.usage) addUsage2(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, model2 ?? "unknown");
|
|
5237
5250
|
sendSSE("done", JSON.stringify({ text: response.text, usage: response.usage }));
|
|
5238
5251
|
res.end();
|
|
@@ -5241,7 +5254,7 @@ data: ${JSON.stringify(data)}
|
|
|
5241
5254
|
res.end();
|
|
5242
5255
|
}
|
|
5243
5256
|
} else {
|
|
5244
|
-
const response = await askAgent2(chatId, body.message, cwd,
|
|
5257
|
+
const response = await askAgent2(chatId, body.message, { cwd, model: model2, permMode: mode });
|
|
5245
5258
|
if (response.usage) addUsage2(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, model2 ?? "unknown");
|
|
5246
5259
|
return jsonResponse(res, { text: response.text, usage: response.usage, sessionId: response.sessionId });
|
|
5247
5260
|
}
|
|
@@ -5696,24 +5709,32 @@ function stopAgent(chatId) {
|
|
|
5696
5709
|
function isAgentActive(chatId) {
|
|
5697
5710
|
return activeChats.has(chatId);
|
|
5698
5711
|
}
|
|
5699
|
-
function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolAction, thinkingLevel) {
|
|
5712
|
+
function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs) {
|
|
5713
|
+
const effectiveTimeout = timeoutMs ?? SPAWN_TIMEOUT_MS;
|
|
5700
5714
|
return new Promise((resolve, reject) => {
|
|
5701
5715
|
const thinkingConfig = thinkingLevel && thinkingLevel !== "auto" ? adapter.applyThinkingConfig(thinkingLevel, model2) : void 0;
|
|
5702
5716
|
const env = adapter.getEnv(thinkingConfig?.envOverrides);
|
|
5703
5717
|
const finalArgs = thinkingConfig?.extraArgs ? [...config2.args, ...thinkingConfig.extraArgs] : config2.args;
|
|
5704
|
-
log(`[agent:spawn] backend=${adapter.id} exe=${config2.executable} model=${model2} cwd=${config2.cwd ?? "(inherited)"}`);
|
|
5718
|
+
log(`[agent:spawn] backend=${adapter.id} exe=${config2.executable} model=${model2} timeout=${effectiveTimeout / 1e3}s cwd=${config2.cwd ?? "(inherited)"}`);
|
|
5705
5719
|
const proc = spawn4(config2.executable, finalArgs, {
|
|
5706
5720
|
env,
|
|
5707
5721
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5708
5722
|
...config2.cwd ? { cwd: config2.cwd } : {}
|
|
5709
5723
|
});
|
|
5710
5724
|
cancelState.process = proc;
|
|
5725
|
+
let timedOut = false;
|
|
5726
|
+
let sigkillTimer;
|
|
5711
5727
|
const spawnTimeout = setTimeout(() => {
|
|
5712
|
-
|
|
5713
|
-
|
|
5728
|
+
timedOut = true;
|
|
5729
|
+
warn(`[agent] Spawn timeout after ${effectiveTimeout / 1e3}s for ${adapter.id} \u2014 killing process`);
|
|
5714
5730
|
proc.kill("SIGTERM");
|
|
5715
|
-
setTimeout(() =>
|
|
5716
|
-
|
|
5731
|
+
sigkillTimer = setTimeout(() => {
|
|
5732
|
+
try {
|
|
5733
|
+
proc.kill("SIGKILL");
|
|
5734
|
+
} catch {
|
|
5735
|
+
}
|
|
5736
|
+
}, 3e3);
|
|
5737
|
+
}, effectiveTimeout);
|
|
5717
5738
|
let resultText = "";
|
|
5718
5739
|
let accumulatedText = "";
|
|
5719
5740
|
let sessionId;
|
|
@@ -5808,12 +5829,17 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
|
|
|
5808
5829
|
});
|
|
5809
5830
|
proc.on("error", (err) => {
|
|
5810
5831
|
clearTimeout(spawnTimeout);
|
|
5832
|
+
if (sigkillTimer) clearTimeout(sigkillTimer);
|
|
5811
5833
|
rl2.close();
|
|
5812
5834
|
cancelState.process = void 0;
|
|
5813
5835
|
reject(err);
|
|
5814
5836
|
});
|
|
5815
5837
|
proc.on("close", (code, signal) => {
|
|
5816
5838
|
clearTimeout(spawnTimeout);
|
|
5839
|
+
if (sigkillTimer) {
|
|
5840
|
+
clearTimeout(sigkillTimer);
|
|
5841
|
+
sigkillTimer = void 0;
|
|
5842
|
+
}
|
|
5817
5843
|
if (cancelState.killTimer) {
|
|
5818
5844
|
clearTimeout(cancelState.killTimer);
|
|
5819
5845
|
cancelState.killTimer = void 0;
|
|
@@ -5823,6 +5849,10 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
|
|
|
5823
5849
|
const stderr = Buffer.concat(stderrChunks).toString().trim();
|
|
5824
5850
|
if (stderr) warn(`[agent] stderr: ${stderr.slice(0, 500)}`);
|
|
5825
5851
|
}
|
|
5852
|
+
if (timedOut) {
|
|
5853
|
+
reject(new Error(`Spawn timeout after ${effectiveTimeout / 1e3}s`));
|
|
5854
|
+
return;
|
|
5855
|
+
}
|
|
5826
5856
|
if (code && code !== 0 && !cancelState.cancelled && !resultText) {
|
|
5827
5857
|
const stderr = Buffer.concat(stderrChunks).toString().trim();
|
|
5828
5858
|
reject(new Error(`CLI exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}`));
|
|
@@ -5832,10 +5862,11 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
|
|
|
5832
5862
|
});
|
|
5833
5863
|
});
|
|
5834
5864
|
}
|
|
5835
|
-
function askAgent(chatId, userMessage,
|
|
5836
|
-
return withChatLock(chatId, () => askAgentImpl(chatId, userMessage,
|
|
5865
|
+
function askAgent(chatId, userMessage, opts) {
|
|
5866
|
+
return withChatLock(chatId, () => askAgentImpl(chatId, userMessage, opts));
|
|
5837
5867
|
}
|
|
5838
|
-
async function askAgentImpl(chatId, userMessage,
|
|
5868
|
+
async function askAgentImpl(chatId, userMessage, opts) {
|
|
5869
|
+
const { cwd, onStream, model: model2, permMode, onToolAction, bootstrapTier, timeoutMs } = opts ?? {};
|
|
5839
5870
|
const adapter = getAdapterForChat(chatId);
|
|
5840
5871
|
const mode = permMode ?? "yolo";
|
|
5841
5872
|
const thinkingLevel = getThinkingLevel(chatId);
|
|
@@ -5876,13 +5907,13 @@ async function askAgentImpl(chatId, userMessage, cwd, onStream, model2, permMode
|
|
|
5876
5907
|
const resolvedModel = model2 ?? adapter.defaultModel;
|
|
5877
5908
|
let result = { resultText: "", sessionId: void 0, input: 0, output: 0, cacheRead: 0, sawToolEvents: false, sawResultEvent: false };
|
|
5878
5909
|
try {
|
|
5879
|
-
result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, onStream, onToolAction, thinkingLevel);
|
|
5910
|
+
result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs);
|
|
5880
5911
|
const wasEmptyResponse = !result.resultText && !result.sawToolEvents && !result.sawResultEvent;
|
|
5881
5912
|
if (wasEmptyResponse && !cancelState.cancelled && existingSessionId) {
|
|
5882
5913
|
warn(`[agent] No result with session ${existingSessionId} for chat ${chatId} \u2014 retrying fresh`);
|
|
5883
5914
|
await summarizeSession(chatId);
|
|
5884
5915
|
clearSession(chatId);
|
|
5885
|
-
result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, onStream, onToolAction, thinkingLevel);
|
|
5916
|
+
result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs);
|
|
5886
5917
|
}
|
|
5887
5918
|
} finally {
|
|
5888
5919
|
activeChats.delete(chatId);
|
|
@@ -6429,7 +6460,8 @@ async function runWithRetry(job, model2, runId, t0) {
|
|
|
6429
6460
|
}
|
|
6430
6461
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
6431
6462
|
try {
|
|
6432
|
-
const
|
|
6463
|
+
const timeoutMs = job.timeout ? job.timeout * 1e3 : void 0;
|
|
6464
|
+
const response = await askAgent(chatId, job.description, { model: model2, bootstrapTier: "slim", timeoutMs });
|
|
6433
6465
|
return response;
|
|
6434
6466
|
} catch (err) {
|
|
6435
6467
|
lastError = err;
|
|
@@ -7069,6 +7101,7 @@ var init_telegram2 = __esm({
|
|
|
7069
7101
|
{ command: "help", description: "Show available commands" },
|
|
7070
7102
|
{ command: "status", description: "Session, backend, model, and usage" },
|
|
7071
7103
|
{ command: "newchat", description: "Start a fresh conversation" },
|
|
7104
|
+
{ command: "summarize", description: "Save session to memory (or 'all' for pre-restart)" },
|
|
7072
7105
|
{ command: "stop", description: "Cancel the current running task" },
|
|
7073
7106
|
// Backend & model
|
|
7074
7107
|
{ command: "backend", description: "Switch AI backend (Claude/Gemini/Codex)" },
|
|
@@ -7856,7 +7889,7 @@ async function runHeartbeat(chatId, config2) {
|
|
|
7856
7889
|
cleanExpiredWatches();
|
|
7857
7890
|
const prompt = assembleHeartbeatPrompt(chatId);
|
|
7858
7891
|
try {
|
|
7859
|
-
const response = await askAgent(chatId, prompt,
|
|
7892
|
+
const response = await askAgent(chatId, prompt, { bootstrapTier: "heartbeat" });
|
|
7860
7893
|
if (response.usage) {
|
|
7861
7894
|
let heartbeatModel;
|
|
7862
7895
|
try {
|
|
@@ -7988,6 +8021,52 @@ var init_heartbeat = __esm({
|
|
|
7988
8021
|
}
|
|
7989
8022
|
});
|
|
7990
8023
|
|
|
8024
|
+
// src/format-time.ts
|
|
8025
|
+
function formatLocalDate(utcDatetime) {
|
|
8026
|
+
const d = parseUtcDatetime(utcDatetime);
|
|
8027
|
+
if (!d) return utcDatetime.split("T")[0] ?? utcDatetime.split(" ")[0];
|
|
8028
|
+
const year = d.getFullYear();
|
|
8029
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
8030
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
8031
|
+
return `${year}-${month}-${day}`;
|
|
8032
|
+
}
|
|
8033
|
+
function formatLocalDateTime(utcDatetime) {
|
|
8034
|
+
const d = parseUtcDatetime(utcDatetime);
|
|
8035
|
+
if (!d) return utcDatetime;
|
|
8036
|
+
const year = d.getFullYear();
|
|
8037
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
8038
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
8039
|
+
const hours = String(d.getHours()).padStart(2, "0");
|
|
8040
|
+
const minutes = String(d.getMinutes()).padStart(2, "0");
|
|
8041
|
+
const tzAbbr = getTimezoneAbbr(d);
|
|
8042
|
+
return `${year}-${month}-${day} ${hours}:${minutes} ${tzAbbr}`;
|
|
8043
|
+
}
|
|
8044
|
+
function parseUtcDatetime(utcDatetime) {
|
|
8045
|
+
if (!utcDatetime) return null;
|
|
8046
|
+
const normalized = utcDatetime.includes("T") ? utcDatetime : utcDatetime.replace(" ", "T");
|
|
8047
|
+
const withZ = normalized.endsWith("Z") ? normalized : normalized + "Z";
|
|
8048
|
+
const d = new Date(withZ);
|
|
8049
|
+
return isNaN(d.getTime()) ? null : d;
|
|
8050
|
+
}
|
|
8051
|
+
function getTimezoneAbbr(date) {
|
|
8052
|
+
try {
|
|
8053
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
8054
|
+
timeZone: systemTimezone,
|
|
8055
|
+
timeZoneName: "short"
|
|
8056
|
+
}).formatToParts(date);
|
|
8057
|
+
return parts.find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
8058
|
+
} catch {
|
|
8059
|
+
return "";
|
|
8060
|
+
}
|
|
8061
|
+
}
|
|
8062
|
+
var systemTimezone;
|
|
8063
|
+
var init_format_time = __esm({
|
|
8064
|
+
"src/format-time.ts"() {
|
|
8065
|
+
"use strict";
|
|
8066
|
+
systemTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
8067
|
+
}
|
|
8068
|
+
});
|
|
8069
|
+
|
|
7991
8070
|
// src/voice/stt.ts
|
|
7992
8071
|
import crypto from "crypto";
|
|
7993
8072
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -8092,10 +8171,13 @@ var init_stt = __esm({
|
|
|
8092
8171
|
});
|
|
8093
8172
|
|
|
8094
8173
|
// src/scheduler/types.ts
|
|
8095
|
-
var COMMON_TIMEZONES;
|
|
8174
|
+
var TIMEOUT_MIN_SECONDS, TIMEOUT_MAX_SECONDS, TIMEOUT_DEFAULT_SECONDS, COMMON_TIMEZONES;
|
|
8096
8175
|
var init_types2 = __esm({
|
|
8097
8176
|
"src/scheduler/types.ts"() {
|
|
8098
8177
|
"use strict";
|
|
8178
|
+
TIMEOUT_MIN_SECONDS = 30;
|
|
8179
|
+
TIMEOUT_MAX_SECONDS = 3600;
|
|
8180
|
+
TIMEOUT_DEFAULT_SECONDS = 600;
|
|
8099
8181
|
COMMON_TIMEZONES = {
|
|
8100
8182
|
"US/Eastern": "America/New_York",
|
|
8101
8183
|
"US/Central": "America/Chicago",
|
|
@@ -8311,8 +8393,23 @@ async function handleWizardText(chatId, text, channel) {
|
|
|
8311
8393
|
case "thinking": {
|
|
8312
8394
|
const level = text.trim().toLowerCase();
|
|
8313
8395
|
pending.thinking = level || "auto";
|
|
8314
|
-
pending.step = "
|
|
8315
|
-
await
|
|
8396
|
+
pending.step = "timeout";
|
|
8397
|
+
await promptTimeout(chatId, channel);
|
|
8398
|
+
break;
|
|
8399
|
+
}
|
|
8400
|
+
case "timeout": {
|
|
8401
|
+
const val = parseInt(text.trim(), 10);
|
|
8402
|
+
if (text.trim().toLowerCase() === "default") {
|
|
8403
|
+
pending.timeout = void 0;
|
|
8404
|
+
pending.step = "session";
|
|
8405
|
+
await promptSession(chatId, channel);
|
|
8406
|
+
} else if (isNaN(val) || val < TIMEOUT_MIN_SECONDS || val > TIMEOUT_MAX_SECONDS) {
|
|
8407
|
+
await channel.sendText(chatId, `Invalid timeout. Enter a value between ${TIMEOUT_MIN_SECONDS} and ${TIMEOUT_MAX_SECONDS} seconds, or "default" for ${TIMEOUT_DEFAULT_SECONDS}s.`, "plain");
|
|
8408
|
+
} else {
|
|
8409
|
+
pending.timeout = val;
|
|
8410
|
+
pending.step = "session";
|
|
8411
|
+
await promptSession(chatId, channel);
|
|
8412
|
+
}
|
|
8316
8413
|
break;
|
|
8317
8414
|
}
|
|
8318
8415
|
case "session": {
|
|
@@ -8390,7 +8487,18 @@ async function handleWizardCallback(chatId, data, channel) {
|
|
|
8390
8487
|
await promptThinking(chatId, channel);
|
|
8391
8488
|
} else if (data.startsWith("sched:thinking:")) {
|
|
8392
8489
|
pending.thinking = data.slice(15);
|
|
8490
|
+
pending.step = "timeout";
|
|
8491
|
+
await promptTimeout(chatId, channel);
|
|
8492
|
+
} else if (data.startsWith("sched:timeout:")) {
|
|
8493
|
+
const val = data.slice(14);
|
|
8494
|
+
if (val === "default") {
|
|
8495
|
+
pending.timeout = void 0;
|
|
8496
|
+
} else {
|
|
8497
|
+
pending.timeout = parseInt(val, 10);
|
|
8498
|
+
}
|
|
8393
8499
|
pending.step = "session";
|
|
8500
|
+
const label2 = pending.timeout ? `${pending.timeout}s (${Math.round(pending.timeout / 60)} min)` : `Default (${TIMEOUT_DEFAULT_SECONDS}s)`;
|
|
8501
|
+
await channel.sendText(chatId, `Timeout: ${label2}`, "plain");
|
|
8394
8502
|
await promptSession(chatId, channel);
|
|
8395
8503
|
} else if (data.startsWith("sched:session:")) {
|
|
8396
8504
|
pending.sessionType = data.slice(14);
|
|
@@ -8484,10 +8592,27 @@ async function promptThinking(chatId, channel) {
|
|
|
8484
8592
|
await channel.sendKeyboard(chatId, "Thinking/effort level for this job?", buttons);
|
|
8485
8593
|
} else {
|
|
8486
8594
|
pending.thinking = "auto";
|
|
8487
|
-
pending.step = "
|
|
8488
|
-
await
|
|
8595
|
+
pending.step = "timeout";
|
|
8596
|
+
await promptTimeout(chatId, channel);
|
|
8489
8597
|
}
|
|
8490
8598
|
}
|
|
8599
|
+
async function promptTimeout(chatId, channel) {
|
|
8600
|
+
if (typeof channel.sendKeyboard !== "function") {
|
|
8601
|
+
await channel.sendText(chatId, `Job timeout in seconds (${TIMEOUT_MIN_SECONDS}-${TIMEOUT_MAX_SECONDS}), or "default" for ${TIMEOUT_DEFAULT_SECONDS}s:`, "plain");
|
|
8602
|
+
return;
|
|
8603
|
+
}
|
|
8604
|
+
await channel.sendKeyboard(chatId, "Maximum runtime for this job?", [
|
|
8605
|
+
[
|
|
8606
|
+
{ label: `5 min (300s)`, data: "sched:timeout:300" },
|
|
8607
|
+
{ label: `10 min (600s)`, data: "sched:timeout:600" }
|
|
8608
|
+
],
|
|
8609
|
+
[
|
|
8610
|
+
{ label: `15 min (900s)`, data: "sched:timeout:900" },
|
|
8611
|
+
{ label: `30 min (1800s)`, data: "sched:timeout:1800" }
|
|
8612
|
+
],
|
|
8613
|
+
[{ label: `Default (${TIMEOUT_DEFAULT_SECONDS}s)`, data: "sched:timeout:default" }]
|
|
8614
|
+
]);
|
|
8615
|
+
}
|
|
8491
8616
|
async function promptSession(chatId, channel) {
|
|
8492
8617
|
if (typeof channel.sendKeyboard !== "function") {
|
|
8493
8618
|
await channel.sendText(chatId, "Session type: isolated (fresh, no history) or main (uses conversation context)?", "plain");
|
|
@@ -8523,6 +8648,7 @@ async function promptConfirm(chatId, channel) {
|
|
|
8523
8648
|
const pending = pendingJobs.get(chatId);
|
|
8524
8649
|
if (!pending) return;
|
|
8525
8650
|
const backendName = pending.backend ? getAdapter(pending.backend).displayName : "default";
|
|
8651
|
+
const timeoutLabel = pending.timeout ? `${pending.timeout}s (${Math.round(pending.timeout / 60)} min)` : `Default (${TIMEOUT_DEFAULT_SECONDS}s)`;
|
|
8526
8652
|
const lines = [
|
|
8527
8653
|
"Job configuration:",
|
|
8528
8654
|
"",
|
|
@@ -8532,6 +8658,7 @@ async function promptConfirm(chatId, channel) {
|
|
|
8532
8658
|
` Backend: ${backendName}`,
|
|
8533
8659
|
` Model: ${pending.model ?? "default"}`,
|
|
8534
8660
|
` Thinking: ${pending.thinking ?? "auto"}`,
|
|
8661
|
+
` Timeout: ${timeoutLabel}`,
|
|
8535
8662
|
` Session: ${pending.sessionType ?? "isolated"}`,
|
|
8536
8663
|
` Delivery: ${pending.deliveryMode ?? "announce"}`
|
|
8537
8664
|
];
|
|
@@ -8562,6 +8689,7 @@ async function finalizeJob(chatId, channel) {
|
|
|
8562
8689
|
backend: pending.backend ?? null,
|
|
8563
8690
|
model: pending.model ?? null,
|
|
8564
8691
|
thinking: pending.thinking ?? null,
|
|
8692
|
+
timeout: pending.timeout ?? null,
|
|
8565
8693
|
sessionType: pending.sessionType ?? "isolated",
|
|
8566
8694
|
channel: pending.channel ?? null,
|
|
8567
8695
|
target: pending.target ?? null,
|
|
@@ -8622,6 +8750,7 @@ async function startEditWizard(chatId, jobId, channel) {
|
|
|
8622
8750
|
backend: job.backend,
|
|
8623
8751
|
model: job.model ?? void 0,
|
|
8624
8752
|
thinking: job.thinking ?? void 0,
|
|
8753
|
+
timeout: job.timeout ?? void 0,
|
|
8625
8754
|
sessionType: job.sessionType,
|
|
8626
8755
|
deliveryMode: job.deliveryMode,
|
|
8627
8756
|
channel: job.channel ?? void 0,
|
|
@@ -9006,7 +9135,7 @@ async function handleCommand(msg, channel) {
|
|
|
9006
9135
|
case "help":
|
|
9007
9136
|
await channel.sendText(
|
|
9008
9137
|
chatId,
|
|
9009
|
-
"Hey! I'm CC-Claw \u2014 your personal AI assistant on Telegram.\n\nI use AI coding CLIs (Claude, Gemini, Codex) as my brain. Just send me a message to get started.\n\nCommands:\n/backend [name] - Switch AI backend (or /claude /gemini /codex)\n/model - Switch model for active backend\n/summarizer - Configure session summarization model\n/status - Show session, model, backend, and usage\n/cost - Show estimated API cost (use /cost all for all-time)\n/usage - Show usage per backend with limits\n/limits - Configure usage limits per backend\n/newchat - Start a fresh conversation\n/cwd <path> - Set working directory\n/cwd - Show current working directory\n/memory - List stored memories\n/forget <keyword> - Remove a memory\n/voice - Toggle voice responses\n/cron <description> - Schedule a task (or /schedule)\n/cron - List scheduled jobs (or /jobs)\n/cron cancel <id> - Cancel a job\n/cron pause <id> - Pause a job\n/cron resume <id> - Resume a job\n/cron run <id> - Trigger a job now\n/cron runs [id] - View run history\n/cron edit <id> - Edit a job\n/cron health - Scheduler health\n/skills - List skills from all backends\n/skill-install <url> - Install a skill from GitHub\n/setup-profile - Set up your user profile\n/chats - List authorized chats and aliases\n/heartbeat - Proactive awareness (on/off/interval/hours)\n/history - List recent session summaries\n/stop - Cancel the current running task\n/tools - Configure which tools the agent can use\n/permissions - Switch permission mode (yolo/safe/readonly/plan)\n/verbose - Tool visibility (off/normal/verbose)\n/agents - List active sub-agents\n/tasks - Show task board for current orchestration\n/stopagent <id> - Cancel a specific sub-agent\n/stopall - Cancel all sub-agents in this chat\n/runners - List registered CLI runners\n/mcps - List registered MCP servers\n/help - Show this message",
|
|
9138
|
+
"Hey! I'm CC-Claw \u2014 your personal AI assistant on Telegram.\n\nI use AI coding CLIs (Claude, Gemini, Codex) as my brain. Just send me a message to get started.\n\nCommands:\n/backend [name] - Switch AI backend (or /claude /gemini /codex)\n/model - Switch model for active backend\n/summarizer - Configure session summarization model\n/status - Show session, model, backend, and usage\n/cost - Show estimated API cost (use /cost all for all-time)\n/usage - Show usage per backend with limits\n/limits - Configure usage limits per backend\n/newchat - Start a fresh conversation\n/summarize - Save session to memory (without resetting)\n/summarize all - Summarize all pending sessions (pre-restart)\n/cwd <path> - Set working directory\n/cwd - Show current working directory\n/memory - List stored memories\n/forget <keyword> - Remove a memory\n/voice - Toggle voice responses\n/cron <description> - Schedule a task (or /schedule)\n/cron - List scheduled jobs (or /jobs)\n/cron cancel <id> - Cancel a job\n/cron pause <id> - Pause a job\n/cron resume <id> - Resume a job\n/cron run <id> - Trigger a job now\n/cron runs [id] - View run history\n/cron edit <id> - Edit a job\n/cron health - Scheduler health\n/skills - List skills from all backends\n/skill-install <url> - Install a skill from GitHub\n/setup-profile - Set up your user profile\n/chats - List authorized chats and aliases\n/heartbeat - Proactive awareness (on/off/interval/hours)\n/history - List recent session summaries\n/stop - Cancel the current running task\n/tools - Configure which tools the agent can use\n/permissions - Switch permission mode (yolo/safe/readonly/plan)\n/verbose - Tool visibility (off/normal/verbose)\n/agents - List active sub-agents\n/tasks - Show task board for current orchestration\n/stopagent <id> - Cancel a specific sub-agent\n/stopall - Cancel all sub-agents in this chat\n/runners - List registered CLI runners\n/mcps - List registered MCP servers\n/help - Show this message",
|
|
9010
9139
|
"plain"
|
|
9011
9140
|
);
|
|
9012
9141
|
break;
|
|
@@ -9088,6 +9217,32 @@ Tap to toggle:`,
|
|
|
9088
9217
|
await channel.sendText(chatId, msg2, "plain");
|
|
9089
9218
|
break;
|
|
9090
9219
|
}
|
|
9220
|
+
case "summarize": {
|
|
9221
|
+
if (commandArgs?.toLowerCase() === "all") {
|
|
9222
|
+
const pendingIds = getLoggedChatIds();
|
|
9223
|
+
if (pendingIds.length === 0) {
|
|
9224
|
+
await channel.sendText(chatId, "No pending sessions to summarize.", "plain");
|
|
9225
|
+
break;
|
|
9226
|
+
}
|
|
9227
|
+
await channel.sendText(chatId, `Summarizing ${pendingIds.length} pending session(s)...`, "plain");
|
|
9228
|
+
await summarizeAllPending();
|
|
9229
|
+
await channel.sendText(chatId, `Done. ${pendingIds.length} session(s) summarized and saved to memory.`, "plain");
|
|
9230
|
+
} else {
|
|
9231
|
+
const pairs = getMessagePairCount(chatId);
|
|
9232
|
+
if (pairs < 2) {
|
|
9233
|
+
await channel.sendText(chatId, "Not enough conversation to summarize (need at least 2 exchanges). Session log is preserved.", "plain");
|
|
9234
|
+
break;
|
|
9235
|
+
}
|
|
9236
|
+
await channel.sendText(chatId, `Summarizing current session (${pairs} exchanges)...`, "plain");
|
|
9237
|
+
const success2 = await summarizeSession(chatId);
|
|
9238
|
+
if (success2) {
|
|
9239
|
+
await channel.sendText(chatId, "Session summarized and saved to memory. Session continues (use /newchat to also reset).", "plain");
|
|
9240
|
+
} else {
|
|
9241
|
+
await channel.sendText(chatId, "Summarization failed. Session log preserved for retry.", "plain");
|
|
9242
|
+
}
|
|
9243
|
+
}
|
|
9244
|
+
break;
|
|
9245
|
+
}
|
|
9091
9246
|
case "status": {
|
|
9092
9247
|
const sessionId = getSessionId(chatId);
|
|
9093
9248
|
const cwd = getCwd(chatId);
|
|
@@ -9564,7 +9719,7 @@ ${lines.join("\n")}`, "plain");
|
|
|
9564
9719
|
Error: ${r.error.slice(0, 100)}` : "";
|
|
9565
9720
|
const usage2 = r.usageInput ? `
|
|
9566
9721
|
Tokens: ${r.usageInput}in / ${r.usageOutput}out` : "";
|
|
9567
|
-
return `#${r.jobId} [${r.status}] ${r.startedAt}${duration}${error3}${usage2}`;
|
|
9722
|
+
return `#${r.jobId} [${r.status}] ${formatLocalDateTime(r.startedAt)}${duration}${error3}${usage2}`;
|
|
9568
9723
|
});
|
|
9569
9724
|
await channel.sendText(chatId, lines.join("\n\n"), "plain");
|
|
9570
9725
|
break;
|
|
@@ -9591,7 +9746,7 @@ ${lines.join("\n")}`, "plain");
|
|
|
9591
9746
|
return;
|
|
9592
9747
|
}
|
|
9593
9748
|
const lines = summaries.map((s) => {
|
|
9594
|
-
const date =
|
|
9749
|
+
const date = formatLocalDate(s.created_at);
|
|
9595
9750
|
const shortSummary = s.summary.length > 200 ? s.summary.slice(0, 200) + "\u2026" : s.summary;
|
|
9596
9751
|
return `${date} (${s.message_count} msgs)
|
|
9597
9752
|
${shortSummary}
|
|
@@ -9606,20 +9761,8 @@ Topics: ${s.topics}`;
|
|
|
9606
9761
|
await channel.sendText(chatId, "No skills found. Install skills with /skill-install <github-url> or place them in ~/.cc-claw/workspace/skills/", "plain");
|
|
9607
9762
|
return;
|
|
9608
9763
|
}
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
const tags = s.sources.join(", ");
|
|
9612
|
-
return [{ label: `${s.name} [${tags}]`, data: `skill:${s.source}:${s.name}` }];
|
|
9613
|
-
});
|
|
9614
|
-
await channel.sendKeyboard(chatId, `${skills2.length} skills available. Select one to invoke:`, buttons);
|
|
9615
|
-
} else {
|
|
9616
|
-
const lines = skills2.map((s) => {
|
|
9617
|
-
const tags = s.sources.join(", ");
|
|
9618
|
-
const desc = s.description ? ` \u2014 ${s.description.slice(0, 50)}` : "";
|
|
9619
|
-
return `\u2022 ${s.name} [${tags}]${desc}`;
|
|
9620
|
-
});
|
|
9621
|
-
await channel.sendText(chatId, ["Available skills:", "", ...lines].join("\n"), "plain");
|
|
9622
|
-
}
|
|
9764
|
+
const page = commandArgs ? parseInt(commandArgs, 10) || 1 : 1;
|
|
9765
|
+
await sendSkillsPage(chatId, channel, skills2, page);
|
|
9623
9766
|
break;
|
|
9624
9767
|
}
|
|
9625
9768
|
case "skill-install": {
|
|
@@ -10047,7 +10190,7 @@ Use /skills to see it.`, "plain");
|
|
|
10047
10190
|
}
|
|
10048
10191
|
const runLines = cronRuns2.map((r) => {
|
|
10049
10192
|
const dur = r.durationMs ? ` (${(r.durationMs / 1e3).toFixed(1)}s)` : "";
|
|
10050
|
-
return `#${r.jobId} [${r.status}] ${r.startedAt}${dur}`;
|
|
10193
|
+
return `#${r.jobId} [${r.status}] ${formatLocalDateTime(r.startedAt)}${dur}`;
|
|
10051
10194
|
});
|
|
10052
10195
|
await channel.sendText(chatId, runLines.join("\n\n"), "plain");
|
|
10053
10196
|
break;
|
|
@@ -10096,7 +10239,7 @@ async function handleVoice(msg, channel) {
|
|
|
10096
10239
|
const vModel = resolveModel(chatId);
|
|
10097
10240
|
const vVerbose = getVerboseLevel(chatId);
|
|
10098
10241
|
const vToolCb = vVerbose !== "off" ? makeToolActionCallback(chatId, channel, vVerbose) : void 0;
|
|
10099
|
-
const response = await askAgent(chatId, transcript, getCwd(chatId),
|
|
10242
|
+
const response = await askAgent(chatId, transcript, { cwd: getCwd(chatId), model: vModel, permMode: mode, onToolAction: vToolCb });
|
|
10100
10243
|
if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, vModel);
|
|
10101
10244
|
await sendResponse(chatId, channel, response.text);
|
|
10102
10245
|
} catch (err) {
|
|
@@ -10143,7 +10286,7 @@ Summarize this for the user.`;
|
|
|
10143
10286
|
const vMode = getMode(chatId);
|
|
10144
10287
|
const vidVerbose = getVerboseLevel(chatId);
|
|
10145
10288
|
const vidToolCb = vidVerbose !== "off" ? makeToolActionCallback(chatId, channel, vidVerbose) : void 0;
|
|
10146
|
-
const response2 = await askAgent(chatId, prompt2, getCwd(chatId),
|
|
10289
|
+
const response2 = await askAgent(chatId, prompt2, { cwd: getCwd(chatId), model: vidModel, permMode: vMode, onToolAction: vidToolCb });
|
|
10147
10290
|
if (response2.usage) addUsage(chatId, response2.usage.input, response2.usage.output, response2.usage.cacheRead, vidModel);
|
|
10148
10291
|
await sendResponse(chatId, channel, response2.text);
|
|
10149
10292
|
return;
|
|
@@ -10184,7 +10327,7 @@ ${content}
|
|
|
10184
10327
|
const mMode = getMode(chatId);
|
|
10185
10328
|
const mVerbose = getVerboseLevel(chatId);
|
|
10186
10329
|
const mToolCb = mVerbose !== "off" ? makeToolActionCallback(chatId, channel, mVerbose) : void 0;
|
|
10187
|
-
const response = await askAgent(chatId, prompt, getCwd(chatId),
|
|
10330
|
+
const response = await askAgent(chatId, prompt, { cwd: getCwd(chatId), model: mediaModel, permMode: mMode, onToolAction: mToolCb });
|
|
10188
10331
|
if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, mediaModel);
|
|
10189
10332
|
await sendResponse(chatId, channel, response.text);
|
|
10190
10333
|
if (tempFilePath) {
|
|
@@ -10249,7 +10392,7 @@ async function handleText(msg, channel) {
|
|
|
10249
10392
|
const tMode = getMode(chatId);
|
|
10250
10393
|
const tVerbose = getVerboseLevel(chatId);
|
|
10251
10394
|
const tToolCb = tVerbose !== "off" ? makeToolActionCallback(chatId, channel, tVerbose) : void 0;
|
|
10252
|
-
const response = await askAgent(chatId, text, getCwd(chatId),
|
|
10395
|
+
const response = await askAgent(chatId, text, { cwd: getCwd(chatId), model: model2, permMode: tMode, onToolAction: tToolCb });
|
|
10253
10396
|
if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, model2);
|
|
10254
10397
|
await sendResponse(chatId, channel, response.text);
|
|
10255
10398
|
} catch (err) {
|
|
@@ -10588,6 +10731,10 @@ ${PERM_MODES[chosen]}`,
|
|
|
10588
10731
|
touchBookmark(chatId, alias);
|
|
10589
10732
|
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${bookmark.path}`, detail: { field: "cwd", value: bookmark.path } });
|
|
10590
10733
|
await sendCwdSessionChoice(chatId, bookmark.path, channel);
|
|
10734
|
+
} else if (data.startsWith("skills:page:")) {
|
|
10735
|
+
const page = parseInt(data.slice(12), 10);
|
|
10736
|
+
const skills2 = await discoverAllSkills();
|
|
10737
|
+
await sendSkillsPage(chatId, channel, skills2, page);
|
|
10591
10738
|
} else if (data.startsWith("skill:")) {
|
|
10592
10739
|
const parts = data.slice(6).split(":");
|
|
10593
10740
|
let skillName;
|
|
@@ -10620,7 +10767,7 @@ ${PERM_MODES[chosen]}`,
|
|
|
10620
10767
|
const sMode = getMode(chatId);
|
|
10621
10768
|
const sVerbose = getVerboseLevel(chatId);
|
|
10622
10769
|
const sToolCb = sVerbose !== "off" ? makeToolActionCallback(chatId, channel, sVerbose) : void 0;
|
|
10623
|
-
const response = await askAgent(chatId, skillContent, getCwd(chatId),
|
|
10770
|
+
const response = await askAgent(chatId, skillContent, { cwd: getCwd(chatId), model: skillModel, permMode: sMode, onToolAction: sToolCb });
|
|
10624
10771
|
if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, skillModel);
|
|
10625
10772
|
await sendResponse(chatId, channel, response.text);
|
|
10626
10773
|
}
|
|
@@ -10764,7 +10911,38 @@ function isTextExt(ext) {
|
|
|
10764
10911
|
"log"
|
|
10765
10912
|
].includes(ext);
|
|
10766
10913
|
}
|
|
10767
|
-
|
|
10914
|
+
async function sendSkillsPage(chatId, channel, skills2, page) {
|
|
10915
|
+
const totalPages = Math.ceil(skills2.length / SKILLS_PER_PAGE);
|
|
10916
|
+
const safePage = Math.max(1, Math.min(page, totalPages));
|
|
10917
|
+
const start = (safePage - 1) * SKILLS_PER_PAGE;
|
|
10918
|
+
const pageSkills = skills2.slice(start, start + SKILLS_PER_PAGE);
|
|
10919
|
+
if (typeof channel.sendKeyboard !== "function") {
|
|
10920
|
+
const lines = pageSkills.map((s) => {
|
|
10921
|
+
const tags = s.sources.join(", ");
|
|
10922
|
+
const desc = s.description ? ` \u2014 ${s.description.slice(0, 50)}` : "";
|
|
10923
|
+
return `\u2022 ${s.name} [${tags}]${desc}`;
|
|
10924
|
+
});
|
|
10925
|
+
const header3 = totalPages > 1 ? `Skills (page ${safePage}/${totalPages}, ${skills2.length} total):` : "Available skills:";
|
|
10926
|
+
const footer = totalPages > 1 ? `
|
|
10927
|
+
Use /skills <page> to navigate (e.g. /skills 2)` : "";
|
|
10928
|
+
await channel.sendText(chatId, [header3, "", ...lines, footer].join("\n"), "plain");
|
|
10929
|
+
return;
|
|
10930
|
+
}
|
|
10931
|
+
const buttons = pageSkills.map((s) => {
|
|
10932
|
+
const tags = s.sources.join(", ");
|
|
10933
|
+
return [{ label: `${s.name} [${tags}]`, data: `skill:${s.source}:${s.name}` }];
|
|
10934
|
+
});
|
|
10935
|
+
if (totalPages > 1) {
|
|
10936
|
+
const navRow = [];
|
|
10937
|
+
if (safePage > 1) navRow.push({ label: `\u2190 Page ${safePage - 1}`, data: `skills:page:${safePage - 1}` });
|
|
10938
|
+
navRow.push({ label: `${safePage}/${totalPages}`, data: "skills:page:noop" });
|
|
10939
|
+
if (safePage < totalPages) navRow.push({ label: `Page ${safePage + 1} \u2192`, data: `skills:page:${safePage + 1}` });
|
|
10940
|
+
buttons.push(navRow);
|
|
10941
|
+
}
|
|
10942
|
+
const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
|
|
10943
|
+
await channel.sendKeyboard(chatId, header2, buttons);
|
|
10944
|
+
}
|
|
10945
|
+
var PERM_MODES, VERBOSE_LEVELS, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, SKILLS_PER_PAGE;
|
|
10768
10946
|
var init_router = __esm({
|
|
10769
10947
|
"src/router.ts"() {
|
|
10770
10948
|
"use strict";
|
|
@@ -10773,10 +10951,12 @@ var init_router = __esm({
|
|
|
10773
10951
|
init_profile();
|
|
10774
10952
|
init_heartbeat();
|
|
10775
10953
|
init_log();
|
|
10954
|
+
init_format_time();
|
|
10776
10955
|
init_agent();
|
|
10777
10956
|
init_stt();
|
|
10778
10957
|
init_store4();
|
|
10779
10958
|
init_summarize();
|
|
10959
|
+
init_session_log();
|
|
10780
10960
|
init_backends();
|
|
10781
10961
|
init_cron();
|
|
10782
10962
|
init_wizard();
|
|
@@ -10819,6 +10999,7 @@ var init_router = __esm({
|
|
|
10819
10999
|
/\/etc\/shadow$/,
|
|
10820
11000
|
/\/etc\/passwd$/
|
|
10821
11001
|
];
|
|
11002
|
+
SKILLS_PER_PAGE = 25;
|
|
10822
11003
|
}
|
|
10823
11004
|
});
|
|
10824
11005
|
|
|
@@ -12420,7 +12601,9 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
12420
12601
|
lines.push(` ${parts.join(", ")}`);
|
|
12421
12602
|
const fixable = r.checks.filter((c) => c.fix);
|
|
12422
12603
|
if (fixable.length > 0 && !localOpts.fix) {
|
|
12423
|
-
|
|
12604
|
+
for (const f of fixable) {
|
|
12605
|
+
lines.push(muted(` Fix: ${f.fix}`));
|
|
12606
|
+
}
|
|
12424
12607
|
}
|
|
12425
12608
|
}
|
|
12426
12609
|
lines.push("");
|
|
@@ -12723,7 +12906,7 @@ async function memoryHistory(globalOpts, opts) {
|
|
|
12723
12906
|
`;
|
|
12724
12907
|
const lines = ["", divider(`Session History (${sums.length})`), ""];
|
|
12725
12908
|
for (const s of sums) {
|
|
12726
|
-
const date =
|
|
12909
|
+
const date = formatLocalDateTime(s.created_at);
|
|
12727
12910
|
lines.push(` ${date} (${s.message_count} msgs)`);
|
|
12728
12911
|
lines.push(` ${s.summary}`);
|
|
12729
12912
|
lines.push(` Topics: ${muted(s.topics)}`);
|
|
@@ -12738,6 +12921,7 @@ var init_memory = __esm({
|
|
|
12738
12921
|
init_format();
|
|
12739
12922
|
init_paths();
|
|
12740
12923
|
init_resolve_chat();
|
|
12924
|
+
init_format_time();
|
|
12741
12925
|
}
|
|
12742
12926
|
});
|
|
12743
12927
|
|
|
@@ -12752,6 +12936,15 @@ __export(cron_exports2, {
|
|
|
12752
12936
|
cronRuns: () => cronRuns
|
|
12753
12937
|
});
|
|
12754
12938
|
import { existsSync as existsSync24 } from "fs";
|
|
12939
|
+
function parseAndValidateTimeout(raw) {
|
|
12940
|
+
if (!raw) return null;
|
|
12941
|
+
const val = parseInt(raw, 10);
|
|
12942
|
+
if (isNaN(val) || val < TIMEOUT_MIN_SECONDS || val > TIMEOUT_MAX_SECONDS) {
|
|
12943
|
+
outputError("INVALID_TIMEOUT", `Timeout must be between ${TIMEOUT_MIN_SECONDS} and ${TIMEOUT_MAX_SECONDS} seconds (got: ${raw})`);
|
|
12944
|
+
process.exit(1);
|
|
12945
|
+
}
|
|
12946
|
+
return val;
|
|
12947
|
+
}
|
|
12755
12948
|
async function cronList(globalOpts) {
|
|
12756
12949
|
if (!existsSync24(DB_PATH)) {
|
|
12757
12950
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
@@ -12774,6 +12967,7 @@ async function cronList(globalOpts) {
|
|
|
12774
12967
|
lines.push(` ${statusDot(status)} #${j.id} [${status}] ${schedule2}${tz}`);
|
|
12775
12968
|
lines.push(` ${j.description}`);
|
|
12776
12969
|
if (j.backend) lines.push(` Backend: ${j.backend}${j.model ? ` / ${j.model}` : ""}`);
|
|
12970
|
+
if (j.timeout) lines.push(` Timeout: ${j.timeout}s`);
|
|
12777
12971
|
if (j.next_run_at) lines.push(` Next: ${muted(j.next_run_at)}`);
|
|
12778
12972
|
lines.push("");
|
|
12779
12973
|
}
|
|
@@ -12810,7 +13004,7 @@ async function cronHealth(globalOpts) {
|
|
|
12810
13004
|
});
|
|
12811
13005
|
}
|
|
12812
13006
|
async function cronCreate(globalOpts, opts) {
|
|
12813
|
-
const { isDaemonRunning: isDaemonRunning2
|
|
13007
|
+
const { isDaemonRunning: isDaemonRunning2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
12814
13008
|
if (!await isDaemonRunning2()) {
|
|
12815
13009
|
outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
|
|
12816
13010
|
process.exit(1);
|
|
@@ -12821,10 +13015,9 @@ async function cronCreate(globalOpts, opts) {
|
|
|
12821
13015
|
}
|
|
12822
13016
|
const chatId = resolveChatId(globalOpts);
|
|
12823
13017
|
const { success: successFmt } = await Promise.resolve().then(() => (init_format(), format_exports));
|
|
13018
|
+
const timeout = parseAndValidateTimeout(opts.timeout);
|
|
12824
13019
|
try {
|
|
12825
|
-
const {
|
|
12826
|
-
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
12827
|
-
const db3 = getDb2();
|
|
13020
|
+
const { insertJob: insertJob2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
12828
13021
|
const schedType = opts.cron ? "cron" : opts.at ? "at" : "every";
|
|
12829
13022
|
let everyMs = null;
|
|
12830
13023
|
if (opts.every) {
|
|
@@ -12835,28 +13028,25 @@ async function cronCreate(globalOpts, opts) {
|
|
|
12835
13028
|
everyMs = unit.startsWith("h") ? num * 36e5 : unit.startsWith("m") ? num * 6e4 : num * 1e3;
|
|
12836
13029
|
}
|
|
12837
13030
|
}
|
|
12838
|
-
const
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
schedType,
|
|
12843
|
-
opts.cron ?? null,
|
|
12844
|
-
opts.at ?? null,
|
|
13031
|
+
const job = insertJob2({
|
|
13032
|
+
scheduleType: schedType,
|
|
13033
|
+
cron: opts.cron ?? null,
|
|
13034
|
+
atTime: opts.at ?? null,
|
|
12845
13035
|
everyMs,
|
|
12846
|
-
opts.description,
|
|
13036
|
+
description: opts.description,
|
|
12847
13037
|
chatId,
|
|
12848
|
-
opts.backend ?? null,
|
|
12849
|
-
opts.model ?? null,
|
|
12850
|
-
opts.thinking ?? null,
|
|
12851
|
-
|
|
12852
|
-
opts.
|
|
12853
|
-
opts.
|
|
12854
|
-
opts.
|
|
12855
|
-
opts.
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
output({ id:
|
|
12859
|
-
${successFmt(`Job #${
|
|
13038
|
+
backend: opts.backend ?? null,
|
|
13039
|
+
model: opts.model ?? null,
|
|
13040
|
+
thinking: opts.thinking ?? null,
|
|
13041
|
+
timeout,
|
|
13042
|
+
sessionType: opts.sessionType ?? "isolated",
|
|
13043
|
+
deliveryMode: opts.delivery ?? "announce",
|
|
13044
|
+
channel: opts.channel ?? null,
|
|
13045
|
+
target: opts.target ?? null,
|
|
13046
|
+
timezone: opts.timezone ?? "UTC"
|
|
13047
|
+
});
|
|
13048
|
+
output({ id: job.id, success: true }, () => `
|
|
13049
|
+
${successFmt(`Job #${job.id} created.`)}
|
|
12860
13050
|
`);
|
|
12861
13051
|
} catch (err) {
|
|
12862
13052
|
outputError("CREATE_FAILED", err.message);
|
|
@@ -12923,6 +13113,11 @@ async function cronEdit(globalOpts, id, opts) {
|
|
|
12923
13113
|
updates.push("thinking = ?");
|
|
12924
13114
|
values.push(opts.thinking);
|
|
12925
13115
|
}
|
|
13116
|
+
if (opts.timeout) {
|
|
13117
|
+
const timeout = parseAndValidateTimeout(opts.timeout);
|
|
13118
|
+
updates.push("timeout = ?");
|
|
13119
|
+
values.push(timeout);
|
|
13120
|
+
}
|
|
12926
13121
|
if (opts.timezone) {
|
|
12927
13122
|
updates.push("timezone = ?");
|
|
12928
13123
|
values.push(opts.timezone);
|
|
@@ -12974,6 +13169,7 @@ var init_cron2 = __esm({
|
|
|
12974
13169
|
init_format();
|
|
12975
13170
|
init_paths();
|
|
12976
13171
|
init_resolve_chat();
|
|
13172
|
+
init_types2();
|
|
12977
13173
|
}
|
|
12978
13174
|
});
|
|
12979
13175
|
|
|
@@ -15049,7 +15245,7 @@ function registerCronCommands(cmd) {
|
|
|
15049
15245
|
const { cronList: cronList2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
|
|
15050
15246
|
await cronList2(program.opts());
|
|
15051
15247
|
});
|
|
15052
|
-
cmd.command("create").description("Create a scheduled job").requiredOption("--description <text>", "Job description").option("--prompt <text>", "Agent prompt (defaults to description)").option("--cron <expr>", "Cron expression (e.g. '0 9 * * *')").option("--at <iso8601>", "One-shot time").option("--every <interval>", "Repeat interval (e.g. 30m, 1h)").option("--backend <name>", "Backend for this job").option("--model <name>", "Model for this job").option("--thinking <level>", "Thinking level").option("--timezone <tz>", "IANA timezone", "UTC").option("--session-type <type>", "Session type (isolated/main)", "isolated").option("--delivery <mode>", "Delivery mode (announce/webhook/none)", "announce").option("--channel <name>", "Delivery channel").option("--target <id>", "Delivery target").option("--cwd <path>", "Working directory").action(async (opts) => {
|
|
15248
|
+
cmd.command("create").description("Create a scheduled job").requiredOption("--description <text>", "Job description").option("--prompt <text>", "Agent prompt (defaults to description)").option("--cron <expr>", "Cron expression (e.g. '0 9 * * *')").option("--at <iso8601>", "One-shot time").option("--every <interval>", "Repeat interval (e.g. 30m, 1h)").option("--backend <name>", "Backend for this job").option("--model <name>", "Model for this job").option("--thinking <level>", "Thinking level").option("--timeout <seconds>", "Job timeout in seconds (30-3600)").option("--timezone <tz>", "IANA timezone", "UTC").option("--session-type <type>", "Session type (isolated/main)", "isolated").option("--delivery <mode>", "Delivery mode (announce/webhook/none)", "announce").option("--channel <name>", "Delivery channel").option("--target <id>", "Delivery target").option("--cwd <path>", "Working directory").action(async (opts) => {
|
|
15053
15249
|
const { cronCreate: cronCreate2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
|
|
15054
15250
|
await cronCreate2(program.opts(), opts);
|
|
15055
15251
|
});
|
|
@@ -15069,7 +15265,7 @@ function registerCronCommands(cmd) {
|
|
|
15069
15265
|
const { cronAction: cronAction2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
|
|
15070
15266
|
await cronAction2(program.opts(), "run", id);
|
|
15071
15267
|
});
|
|
15072
|
-
cmd.command("edit <id>").description("Edit a job (same flags as create)").option("--description <text>").option("--cron <expr>").option("--at <iso8601>").option("--every <interval>").option("--backend <name>").option("--model <name>").option("--thinking <level>").option("--timezone <tz>").action(async (id, opts) => {
|
|
15268
|
+
cmd.command("edit <id>").description("Edit a job (same flags as create)").option("--description <text>").option("--cron <expr>").option("--at <iso8601>").option("--every <interval>").option("--backend <name>").option("--model <name>").option("--thinking <level>").option("--timeout <seconds>", "Job timeout in seconds (30-3600)").option("--timezone <tz>").action(async (id, opts) => {
|
|
15073
15269
|
const { cronEdit: cronEdit2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
|
|
15074
15270
|
await cronEdit2(program.opts(), id, opts);
|
|
15075
15271
|
});
|
package/package.json
CHANGED