cc-claw 0.11.2 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +358 -252
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -72,7 +72,7 @@ var VERSION;
|
|
|
72
72
|
var init_version = __esm({
|
|
73
73
|
"src/version.ts"() {
|
|
74
74
|
"use strict";
|
|
75
|
-
VERSION = true ? "0.
|
|
75
|
+
VERSION = true ? "0.12.1" : (() => {
|
|
76
76
|
try {
|
|
77
77
|
return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
|
|
78
78
|
} catch {
|
|
@@ -682,18 +682,15 @@ __export(store_exports4, {
|
|
|
682
682
|
cleanupReflectionData: () => cleanupReflectionData,
|
|
683
683
|
getAppliedInsightCountToday: () => getAppliedInsightCountToday,
|
|
684
684
|
getAppliedInsights: () => getAppliedInsights,
|
|
685
|
-
getConflictingInsights: () => getConflictingInsights,
|
|
686
685
|
getGrowthMetrics: () => getGrowthMetrics,
|
|
687
686
|
getInsightById: () => getInsightById,
|
|
688
687
|
getLastAnalysisTime: () => getLastAnalysisTime,
|
|
689
688
|
getPendingInsightCount: () => getPendingInsightCount,
|
|
690
689
|
getPendingInsights: () => getPendingInsights,
|
|
691
|
-
getRecommendations: () => getRecommendations,
|
|
692
690
|
getReflectionModelConfig: () => getReflectionModelConfig,
|
|
693
691
|
getReflectionStatus: () => getReflectionStatus,
|
|
694
692
|
getRejectedInsights: () => getRejectedInsights,
|
|
695
693
|
getSignalCountForChat: () => getSignalCountForChat,
|
|
696
|
-
getSignalsForPeriod: () => getSignalsForPeriod,
|
|
697
694
|
getUnprocessedSignalCount: () => getUnprocessedSignalCount,
|
|
698
695
|
getUnprocessedSignals: () => getUnprocessedSignals,
|
|
699
696
|
initReflectionTables: () => initReflectionTables,
|
|
@@ -839,13 +836,6 @@ function getSignalCountForChat(db3, chatId, minutesBack) {
|
|
|
839
836
|
`).get(chatId, minutesBack);
|
|
840
837
|
return row.count;
|
|
841
838
|
}
|
|
842
|
-
function getSignalsForPeriod(db3, chatId, period) {
|
|
843
|
-
return db3.prepare(`
|
|
844
|
-
SELECT * FROM feedback_signals
|
|
845
|
-
WHERE chatId = ? AND date(created_at) = ?
|
|
846
|
-
ORDER BY created_at ASC
|
|
847
|
-
`).all(chatId, period);
|
|
848
|
-
}
|
|
849
839
|
function getUnprocessedSignalCount(db3, chatId) {
|
|
850
840
|
const row = db3.prepare(`
|
|
851
841
|
SELECT COUNT(*) as count FROM feedback_signals
|
|
@@ -916,14 +906,6 @@ function getRejectedInsights(db3, chatId, limit = 20) {
|
|
|
916
906
|
LIMIT ?
|
|
917
907
|
`).all(chatId, limit);
|
|
918
908
|
}
|
|
919
|
-
function getRecommendations(db3, chatId, limit = 20) {
|
|
920
|
-
return db3.prepare(`
|
|
921
|
-
SELECT * FROM insights
|
|
922
|
-
WHERE chatId = ? AND status = 'recommendation'
|
|
923
|
-
ORDER BY created_at DESC
|
|
924
|
-
LIMIT ?
|
|
925
|
-
`).all(chatId, limit);
|
|
926
|
-
}
|
|
927
909
|
function updateInsightStatus(db3, id, status) {
|
|
928
910
|
if (status === "applied") {
|
|
929
911
|
db3.prepare(`
|
|
@@ -944,13 +926,6 @@ function updateInsightRollback(db3, id, rollbackData) {
|
|
|
944
926
|
function updateInsightEffectiveness(db3, id, score) {
|
|
945
927
|
db3.prepare("UPDATE insights SET effectiveness = ? WHERE id = ?").run(score, id);
|
|
946
928
|
}
|
|
947
|
-
function getConflictingInsights(db3, chatId, targetFile) {
|
|
948
|
-
return db3.prepare(`
|
|
949
|
-
SELECT * FROM insights
|
|
950
|
-
WHERE chatId = ? AND targetFile = ? AND status = 'applied'
|
|
951
|
-
ORDER BY appliedAt DESC
|
|
952
|
-
`).all(chatId, targetFile);
|
|
953
|
-
}
|
|
954
929
|
function getPendingInsightCount(db3, chatId) {
|
|
955
930
|
const row = db3.prepare(`
|
|
956
931
|
SELECT COUNT(*) as count FROM insights WHERE chatId = ? AND status = 'pending'
|
|
@@ -960,7 +935,7 @@ function getPendingInsightCount(db3, chatId) {
|
|
|
960
935
|
function getAppliedInsightCountToday(db3, chatId) {
|
|
961
936
|
const row = db3.prepare(`
|
|
962
937
|
SELECT COUNT(*) as count FROM insights
|
|
963
|
-
WHERE chatId = ? AND status = 'applied' AND date(
|
|
938
|
+
WHERE chatId = ? AND status = 'applied' AND appliedAt >= date('now') AND appliedAt < date('now', '+1 day')
|
|
964
939
|
`).get(chatId);
|
|
965
940
|
return row.count;
|
|
966
941
|
}
|
|
@@ -1558,6 +1533,7 @@ function initDatabase() {
|
|
|
1558
1533
|
target TEXT,
|
|
1559
1534
|
delivery_mode TEXT NOT NULL DEFAULT 'announce',
|
|
1560
1535
|
timezone TEXT NOT NULL DEFAULT 'UTC',
|
|
1536
|
+
job_type TEXT DEFAULT 'normal',
|
|
1561
1537
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
1562
1538
|
active INTEGER NOT NULL DEFAULT 1,
|
|
1563
1539
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
@@ -1594,6 +1570,7 @@ function initDatabase() {
|
|
|
1594
1570
|
target TEXT,
|
|
1595
1571
|
delivery_mode TEXT NOT NULL DEFAULT 'announce',
|
|
1596
1572
|
timezone TEXT NOT NULL DEFAULT 'UTC',
|
|
1573
|
+
job_type TEXT DEFAULT 'normal',
|
|
1597
1574
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
1598
1575
|
active INTEGER NOT NULL DEFAULT 1,
|
|
1599
1576
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
@@ -1681,6 +1658,14 @@ function initDatabase() {
|
|
|
1681
1658
|
db.exec("ALTER TABLE jobs ADD COLUMN title TEXT");
|
|
1682
1659
|
} catch {
|
|
1683
1660
|
}
|
|
1661
|
+
try {
|
|
1662
|
+
db.exec("ALTER TABLE jobs ADD COLUMN job_type TEXT DEFAULT 'normal'");
|
|
1663
|
+
} catch {
|
|
1664
|
+
}
|
|
1665
|
+
try {
|
|
1666
|
+
db.exec("UPDATE jobs SET job_type = 'reflection' WHERE description LIKE '%reflection analysis%' AND job_type = 'normal'");
|
|
1667
|
+
} catch {
|
|
1668
|
+
}
|
|
1684
1669
|
backfillJobTitles(db);
|
|
1685
1670
|
}
|
|
1686
1671
|
} catch {
|
|
@@ -2529,8 +2514,8 @@ function getBackendUsageInWindow(backend2, windowType) {
|
|
|
2529
2514
|
function insertJob(params) {
|
|
2530
2515
|
const result = db.prepare(`
|
|
2531
2516
|
INSERT INTO jobs (schedule_type, cron, at_time, every_ms, title, description, chat_id,
|
|
2532
|
-
backend, model, thinking, timeout, fallbacks, session_type, channel, target, delivery_mode, timezone)
|
|
2533
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2517
|
+
backend, model, thinking, timeout, fallbacks, session_type, channel, target, delivery_mode, timezone, job_type)
|
|
2518
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2534
2519
|
`).run(
|
|
2535
2520
|
params.scheduleType,
|
|
2536
2521
|
params.cron ?? null,
|
|
@@ -2548,27 +2533,31 @@ function insertJob(params) {
|
|
|
2548
2533
|
params.channel ?? null,
|
|
2549
2534
|
params.target ?? null,
|
|
2550
2535
|
params.deliveryMode ?? "announce",
|
|
2551
|
-
params.timezone ?? "UTC"
|
|
2536
|
+
params.timezone ?? "UTC",
|
|
2537
|
+
params.jobType ?? "normal"
|
|
2552
2538
|
);
|
|
2553
2539
|
return getJobById(Number(result.lastInsertRowid));
|
|
2554
2540
|
}
|
|
2555
2541
|
function backfillJobTitles(database) {
|
|
2556
|
-
const rows = database.prepare(
|
|
2542
|
+
const rows = database.prepare(
|
|
2543
|
+
"SELECT id, description FROM jobs WHERE active = 1 AND (title IS NULL OR title LIKE 'No thinking%' OR title LIKE 'You must%' OR title LIKE 'You MUST%')"
|
|
2544
|
+
).all();
|
|
2557
2545
|
if (rows.length === 0) return;
|
|
2558
|
-
const
|
|
2559
|
-
/^you\s+must\s+call\s+the\s+\w+\s+tool\b[^.]*\.\s*/i,
|
|
2560
|
-
/^important:\s*[^.]*\.\s*/i,
|
|
2561
|
-
/^always\s+use\s+[^.]*\.\s*/i,
|
|
2562
|
-
/^note:\s*[^.]*\.\s*/i
|
|
2563
|
-
];
|
|
2546
|
+
const instructionLinePattern = /^(?:you\s+must|important|always|note|never|no\s+thinking|do\s+not|don't|remember|make\s+sure|ensure|critical|warning|before\s+you|first,?\s+you|use\s+the|call\s+the)\b[^.\n]*[.!]?\s*/i;
|
|
2564
2547
|
for (const row of rows) {
|
|
2565
2548
|
let text = row.description;
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2549
|
+
let prevLen = 0;
|
|
2550
|
+
while (text.length !== prevLen && text.length > 0) {
|
|
2551
|
+
prevLen = text.length;
|
|
2552
|
+
text = text.replace(instructionLinePattern, "").trim();
|
|
2553
|
+
text = text.replace(/^\n+/, "").trim();
|
|
2554
|
+
}
|
|
2555
|
+
if (text.length < 10) text = row.description;
|
|
2556
|
+
text = text.replace(/https?:\/\/\S+/g, "").trim();
|
|
2557
|
+
text = text.replace(/\([^)]*\)\s*$/, "").trim();
|
|
2558
|
+
const firstLine = text.split(/\n/)[0].trim();
|
|
2559
|
+
const firstSentence = firstLine.match(/^.{5,55}(?:\b|[.!?])/)?.[0] ?? firstLine.slice(0, 55);
|
|
2560
|
+
const title = firstSentence.length > 55 ? firstSentence.slice(0, 52) + "\u2026" : firstSentence;
|
|
2572
2561
|
database.prepare("UPDATE jobs SET title = ? WHERE id = ?").run(title, row.id);
|
|
2573
2562
|
}
|
|
2574
2563
|
}
|
|
@@ -2626,7 +2615,8 @@ function updateJob(id, fields) {
|
|
|
2626
2615
|
channel: "channel",
|
|
2627
2616
|
target: "target",
|
|
2628
2617
|
deliveryMode: "delivery_mode",
|
|
2629
|
-
timezone: "timezone"
|
|
2618
|
+
timezone: "timezone",
|
|
2619
|
+
jobType: "job_type"
|
|
2630
2620
|
};
|
|
2631
2621
|
for (const [key, val] of Object.entries(fields)) {
|
|
2632
2622
|
const col = fieldMap[key];
|
|
@@ -3063,7 +3053,7 @@ var init_store5 = __esm({
|
|
|
3063
3053
|
SELECT id, schedule_type as scheduleType, cron, at_time as atTime, every_ms as everyMs,
|
|
3064
3054
|
title, description, chat_id as chatId, backend, model, thinking, timeout, fallbacks,
|
|
3065
3055
|
session_type as sessionType, channel, target, delivery_mode as deliveryMode,
|
|
3066
|
-
timezone, enabled, active, created_at as createdAt, last_run_at as lastRunAt,
|
|
3056
|
+
timezone, job_type as jobType, enabled, active, created_at as createdAt, last_run_at as lastRunAt,
|
|
3067
3057
|
next_run_at as nextRunAt, consecutive_failures as consecutiveFailures
|
|
3068
3058
|
FROM jobs
|
|
3069
3059
|
`;
|
|
@@ -4955,7 +4945,7 @@ ${transcript}`;
|
|
|
4955
4945
|
if (!resultText) resultText = accumulatedText;
|
|
4956
4946
|
if (!resultText) {
|
|
4957
4947
|
warn(`[summarize] ${adapter.id}:${model2} returned empty result for chat ${chatId}`);
|
|
4958
|
-
return false;
|
|
4948
|
+
return { success: false, rawText: "" };
|
|
4959
4949
|
}
|
|
4960
4950
|
const summaryMatch = resultText.match(/SUMMARY:\s*(.+?)(?=\nKEY_DETAILS:|\nTOPICS:|$)/s);
|
|
4961
4951
|
const keyDetailsMatch = resultText.match(/KEY_DETAILS:\s*(.+?)(?=\nTOPICS:|$)/s);
|
|
@@ -4967,16 +4957,44 @@ Key details: ${keyDetails}`;
|
|
|
4967
4957
|
const topics = topicsMatch?.[1]?.trim() ?? "";
|
|
4968
4958
|
saveSessionSummaryWithEmbedding(chatId, summary, topics, Math.floor(entries.length / 2));
|
|
4969
4959
|
log(`[summarize] Saved summary via ${adapter.id}:${model2} for chat ${chatId}`);
|
|
4970
|
-
return true;
|
|
4960
|
+
return { success: true, rawText: resultText };
|
|
4971
4961
|
} catch (err) {
|
|
4972
4962
|
warn(`[summarize] ${adapter.id}:${model2} failed for ${chatId}: ${errorMessage(err)}`);
|
|
4973
|
-
return false;
|
|
4963
|
+
return { success: false, rawText: "" };
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
async function extractAndLogSignals(rawText, chatId, adapterId, model2) {
|
|
4967
|
+
try {
|
|
4968
|
+
const { getReflectionStatus: getReflectionStatus2, logSignal: logSignal2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
4969
|
+
const { isSyntheticChatId: isSyntheticChatId3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
|
|
4970
|
+
const db3 = getDb();
|
|
4971
|
+
if (isSyntheticChatId3(chatId) || getReflectionStatus2(db3, chatId) === "frozen") return;
|
|
4972
|
+
const signalMatches = rawText.matchAll(/^-\s*\[(\w+)\]\s*(.+)/gm);
|
|
4973
|
+
for (const match of signalMatches) {
|
|
4974
|
+
const signalType = match[1].toLowerCase();
|
|
4975
|
+
const trigger = match[2].trim();
|
|
4976
|
+
if (VALID_SIGNAL_TYPES.includes(signalType)) {
|
|
4977
|
+
logSignal2(db3, {
|
|
4978
|
+
chatId,
|
|
4979
|
+
signalType,
|
|
4980
|
+
trigger,
|
|
4981
|
+
context: null,
|
|
4982
|
+
source: "llm_summary",
|
|
4983
|
+
confidence: 0.7,
|
|
4984
|
+
backend: adapterId,
|
|
4985
|
+
model: model2,
|
|
4986
|
+
thinkingLevel: null
|
|
4987
|
+
});
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
} catch (e) {
|
|
4991
|
+
warn(`[summarize] Signal extraction failed for ${chatId}: ${e}`);
|
|
4974
4992
|
}
|
|
4975
4993
|
}
|
|
4976
|
-
async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend) {
|
|
4994
|
+
async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend, clearLogAfter = true) {
|
|
4977
4995
|
const pairCount = getMessagePairCount(chatId);
|
|
4978
4996
|
if (pairCount < MIN_PAIRS) {
|
|
4979
|
-
clearLog(chatId);
|
|
4997
|
+
if (clearLogAfter) clearLog(chatId);
|
|
4980
4998
|
return false;
|
|
4981
4999
|
}
|
|
4982
5000
|
const entries = getLog(chatId);
|
|
@@ -4993,8 +5011,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
|
|
|
4993
5011
|
const model2 = config2.model ?? adapter.summarizerModel;
|
|
4994
5012
|
const key = `${adapter.id}:${model2}`;
|
|
4995
5013
|
tried.add(key);
|
|
4996
|
-
|
|
4997
|
-
|
|
5014
|
+
const result = await attemptSummarize(chatId, adapter, model2, entries);
|
|
5015
|
+
if (result.success) {
|
|
5016
|
+
await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
|
|
5017
|
+
if (clearLogAfter) clearLog(chatId);
|
|
4998
5018
|
return true;
|
|
4999
5019
|
}
|
|
5000
5020
|
}
|
|
@@ -5007,8 +5027,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
|
|
|
5007
5027
|
const key = `${targetAdapter.id}:${model2}`;
|
|
5008
5028
|
if (!tried.has(key)) {
|
|
5009
5029
|
tried.add(key);
|
|
5010
|
-
|
|
5011
|
-
|
|
5030
|
+
const result = await attemptSummarize(chatId, targetAdapter, model2, entries);
|
|
5031
|
+
if (result.success) {
|
|
5032
|
+
await extractAndLogSignals(result.rawText, chatId, targetAdapter.id, model2);
|
|
5033
|
+
if (clearLogAfter) clearLog(chatId);
|
|
5012
5034
|
return true;
|
|
5013
5035
|
}
|
|
5014
5036
|
}
|
|
@@ -5020,8 +5042,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
|
|
|
5020
5042
|
const key = `${adapter.id}:${model2}`;
|
|
5021
5043
|
if (!tried.has(key)) {
|
|
5022
5044
|
tried.add(key);
|
|
5023
|
-
|
|
5024
|
-
|
|
5045
|
+
const result = await attemptSummarize(chatId, adapter, model2, entries);
|
|
5046
|
+
if (result.success) {
|
|
5047
|
+
await extractAndLogSignals(result.rawText, chatId, adapter.id, model2);
|
|
5048
|
+
if (clearLogAfter) clearLog(chatId);
|
|
5025
5049
|
return true;
|
|
5026
5050
|
}
|
|
5027
5051
|
}
|
|
@@ -5029,8 +5053,8 @@ async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBacken
|
|
|
5029
5053
|
warn(`[summarize] All fallback attempts failed for chat ${chatId} \u2014 raw log preserved`);
|
|
5030
5054
|
return false;
|
|
5031
5055
|
}
|
|
5032
|
-
async function summarizeSession(chatId) {
|
|
5033
|
-
return summarizeWithFallbackChain(chatId);
|
|
5056
|
+
async function summarizeSession(chatId, clearLogAfter = true) {
|
|
5057
|
+
return summarizeWithFallbackChain(chatId, void 0, void 0, clearLogAfter);
|
|
5034
5058
|
}
|
|
5035
5059
|
async function summarizeAllPending() {
|
|
5036
5060
|
const allIds = getLoggedChatIds();
|
|
@@ -5048,7 +5072,7 @@ async function summarizeAllPending() {
|
|
|
5048
5072
|
await summarizeSession(chatId);
|
|
5049
5073
|
}
|
|
5050
5074
|
}
|
|
5051
|
-
var MIN_PAIRS, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP, SUMMARIZE_TIMEOUT_MS, SUMMARIZE_PROMPT, CONTEXT_BRIDGE_PROMPT;
|
|
5075
|
+
var MIN_PAIRS, USER_MSG_LIMIT, AGENT_MSG_LIMIT, TRANSCRIPT_CAP, SUMMARIZE_TIMEOUT_MS, SUMMARIZE_PROMPT, CONTEXT_BRIDGE_PROMPT, VALID_SIGNAL_TYPES;
|
|
5052
5076
|
var init_summarize = __esm({
|
|
5053
5077
|
"src/memory/summarize.ts"() {
|
|
5054
5078
|
"use strict";
|
|
@@ -5083,7 +5107,15 @@ SUMMARY: <detailed summary>
|
|
|
5083
5107
|
KEY_DETAILS:
|
|
5084
5108
|
- <detail 1>
|
|
5085
5109
|
- <detail 2>
|
|
5086
|
-
TOPICS: <specific-keyword1, specific-keyword2,
|
|
5110
|
+
TOPICS: <specific-keyword1, specific-keyword2, ...>
|
|
5111
|
+
|
|
5112
|
+
SIGNALS (0-5 items, only if the conversation contains user feedback about the assistant's performance):
|
|
5113
|
+
- [correction] brief description of what the user corrected
|
|
5114
|
+
- [preference] brief description of a preference the user expressed
|
|
5115
|
+
- [frustration] brief description of user frustration or dissatisfaction
|
|
5116
|
+
- [praise] brief description of user satisfaction or approval
|
|
5117
|
+
- [error_pattern] brief description of a recurring error the user pointed out
|
|
5118
|
+
If no feedback signals are present, omit this section entirely.`;
|
|
5087
5119
|
CONTEXT_BRIDGE_PROMPT = `You are handing off an active work session to a new AI assistant. Write a concise handoff brief (3-8 sentences) covering:
|
|
5088
5120
|
- What the user is actively working on RIGHT NOW
|
|
5089
5121
|
- Exact current state: what's done, what's in progress, what's blocked
|
|
@@ -5092,6 +5124,7 @@ TOPICS: <specific-keyword1, specific-keyword2, ...>`;
|
|
|
5092
5124
|
- Any critical constraints or preferences expressed by the user
|
|
5093
5125
|
|
|
5094
5126
|
Be specific. The new assistant has no prior context. Cover any domain \u2014 personal, research, scheduling, content, technical \u2014 whatever applies to this session.`;
|
|
5127
|
+
VALID_SIGNAL_TYPES = ["correction", "preference", "praise", "frustration", "error_pattern"];
|
|
5095
5128
|
}
|
|
5096
5129
|
});
|
|
5097
5130
|
|
|
@@ -6443,9 +6476,9 @@ function aggregateDailyMetrics(db3, chatId, date) {
|
|
|
6443
6476
|
const signalCounts = db3.prepare(`
|
|
6444
6477
|
SELECT signalType, COUNT(*) as count
|
|
6445
6478
|
FROM feedback_signals
|
|
6446
|
-
WHERE chatId = ? AND date(
|
|
6479
|
+
WHERE chatId = ? AND created_at >= ? AND created_at < date(?, '+1 day')
|
|
6447
6480
|
GROUP BY signalType
|
|
6448
|
-
`).all(chatId, period);
|
|
6481
|
+
`).all(chatId, period, period);
|
|
6449
6482
|
let corrections = 0;
|
|
6450
6483
|
let praises = 0;
|
|
6451
6484
|
let errors = 0;
|
|
@@ -6464,8 +6497,8 @@ function aggregateDailyMetrics(db3, chatId, date) {
|
|
|
6464
6497
|
}
|
|
6465
6498
|
const insightsRow = db3.prepare(`
|
|
6466
6499
|
SELECT COUNT(*) as count FROM insights
|
|
6467
|
-
WHERE chatId = ? AND date(
|
|
6468
|
-
`).get(chatId, period);
|
|
6500
|
+
WHERE chatId = ? AND appliedAt >= ? AND appliedAt < date(?, '+1 day')
|
|
6501
|
+
`).get(chatId, period, period);
|
|
6469
6502
|
upsertGrowthMetric(db3, chatId, period, {
|
|
6470
6503
|
corrections,
|
|
6471
6504
|
praises,
|
|
@@ -6771,7 +6804,7 @@ function resolveReflectionAdapter(chatId) {
|
|
|
6771
6804
|
if (config2.mode === "pinned" && config2.backend) {
|
|
6772
6805
|
try {
|
|
6773
6806
|
const adapter = getAdapter(config2.backend);
|
|
6774
|
-
const model2 = config2.model ?? adapter.
|
|
6807
|
+
const model2 = config2.model ?? adapter.defaultModel;
|
|
6775
6808
|
return { adapter, model: model2 };
|
|
6776
6809
|
} catch {
|
|
6777
6810
|
return null;
|
|
@@ -6792,11 +6825,11 @@ function resolveReflectionAdapter(chatId) {
|
|
|
6792
6825
|
}
|
|
6793
6826
|
try {
|
|
6794
6827
|
const adapter = getAdapterForChat(chatId);
|
|
6795
|
-
return { adapter, model: adapter.
|
|
6828
|
+
return { adapter, model: adapter.defaultModel };
|
|
6796
6829
|
} catch {
|
|
6797
6830
|
try {
|
|
6798
6831
|
const adapter = getAdapter("claude");
|
|
6799
|
-
return { adapter, model: adapter.
|
|
6832
|
+
return { adapter, model: adapter.defaultModel };
|
|
6800
6833
|
} catch {
|
|
6801
6834
|
return null;
|
|
6802
6835
|
}
|
|
@@ -6813,7 +6846,7 @@ function getTodayConversations(chatId) {
|
|
|
6813
6846
|
const db3 = getDb();
|
|
6814
6847
|
const rows = db3.prepare(`
|
|
6815
6848
|
SELECT role, content FROM message_log
|
|
6816
|
-
WHERE chat_id = ? AND date(
|
|
6849
|
+
WHERE chat_id = ? AND created_at >= date('now') AND created_at < date('now', '+1 day')
|
|
6817
6850
|
ORDER BY created_at ASC
|
|
6818
6851
|
LIMIT 200
|
|
6819
6852
|
`).all(chatId);
|
|
@@ -6832,7 +6865,7 @@ function getTodayConversations(chatId) {
|
|
|
6832
6865
|
}
|
|
6833
6866
|
return lines.join("\n\n");
|
|
6834
6867
|
}
|
|
6835
|
-
async function spawnAnalysis(adapter, model2, prompt) {
|
|
6868
|
+
async function spawnAnalysis(adapter, model2, prompt, timeoutMs = ANALYSIS_TIMEOUT_MS) {
|
|
6836
6869
|
const config2 = adapter.buildSpawnConfig({
|
|
6837
6870
|
prompt,
|
|
6838
6871
|
model: model2,
|
|
@@ -6855,7 +6888,7 @@ async function spawnAnalysis(adapter, model2, prompt) {
|
|
|
6855
6888
|
rl2.close();
|
|
6856
6889
|
proc.kill("SIGTERM");
|
|
6857
6890
|
setTimeout(() => proc.kill("SIGKILL"), 2e3);
|
|
6858
|
-
},
|
|
6891
|
+
}, timeoutMs);
|
|
6859
6892
|
rl2.on("line", (line) => {
|
|
6860
6893
|
if (!line.trim()) return;
|
|
6861
6894
|
let msg;
|
|
@@ -6947,7 +6980,7 @@ async function runAnalysis(chatId, opts = {}) {
|
|
|
6947
6980
|
log(`[reflection] Running analysis via ${adapter.id}:${model2} for chat ${chatId} (${signals.length} signals, force=${force})`);
|
|
6948
6981
|
let rawOutput;
|
|
6949
6982
|
try {
|
|
6950
|
-
rawOutput = await spawnAnalysis(adapter, model2, prompt);
|
|
6983
|
+
rawOutput = await spawnAnalysis(adapter, model2, prompt, opts.timeoutMs);
|
|
6951
6984
|
} catch (err) {
|
|
6952
6985
|
warn(`[reflection] Analysis spawn failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6953
6986
|
return [];
|
|
@@ -7648,8 +7681,7 @@ function startDashboard() {
|
|
|
7648
7681
|
const body = JSON.parse(await readBody(req));
|
|
7649
7682
|
const { setBackend: setBackend2, clearSession: clearSession2, clearModel: clearModel2, clearThinkingLevel: clearThinkingLevel2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
|
|
7650
7683
|
const { summarizeSession: summarizeSession2 } = await Promise.resolve().then(() => (init_summarize(), summarize_exports));
|
|
7651
|
-
summarizeSession2(body.chatId)
|
|
7652
|
-
});
|
|
7684
|
+
await summarizeSession2(body.chatId);
|
|
7653
7685
|
clearSession2(body.chatId);
|
|
7654
7686
|
clearModel2(body.chatId);
|
|
7655
7687
|
clearThinkingLevel2(body.chatId);
|
|
@@ -8574,7 +8606,8 @@ __export(agent_exports, {
|
|
|
8574
8606
|
getInFlightMessage: () => getInFlightMessage,
|
|
8575
8607
|
isChatBusy: () => isChatBusy,
|
|
8576
8608
|
isSyntheticChatId: () => isSyntheticChatId,
|
|
8577
|
-
stopAgent: () => stopAgent
|
|
8609
|
+
stopAgent: () => stopAgent,
|
|
8610
|
+
stopAllActiveAgents: () => stopAllActiveAgents
|
|
8578
8611
|
});
|
|
8579
8612
|
import { spawn as spawn5 } from "child_process";
|
|
8580
8613
|
import { createInterface as createInterface4 } from "readline";
|
|
@@ -8602,13 +8635,22 @@ function withChatLock(chatId, fn) {
|
|
|
8602
8635
|
}
|
|
8603
8636
|
const next = prev.then(fn, fn);
|
|
8604
8637
|
chatLocks.set(chatId, next.then(() => {
|
|
8638
|
+
chatLocks.delete(chatId);
|
|
8605
8639
|
}, () => {
|
|
8640
|
+
chatLocks.delete(chatId);
|
|
8606
8641
|
}));
|
|
8607
8642
|
return next;
|
|
8608
8643
|
}
|
|
8609
8644
|
function isChatBusy(chatId) {
|
|
8610
8645
|
return activeChats.has(chatId);
|
|
8611
8646
|
}
|
|
8647
|
+
function stopAllActiveAgents() {
|
|
8648
|
+
for (const [, state] of activeChats) {
|
|
8649
|
+
if (state.process) {
|
|
8650
|
+
killProcessGroup(state.process);
|
|
8651
|
+
}
|
|
8652
|
+
}
|
|
8653
|
+
}
|
|
8612
8654
|
function stopAgent(chatId) {
|
|
8613
8655
|
const state = activeChats.get(chatId);
|
|
8614
8656
|
if (!state) return false;
|
|
@@ -8864,7 +8906,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
8864
8906
|
const effectiveAgentMode = optsAgentMode ?? getAgentMode(settingsChat);
|
|
8865
8907
|
const sideQuestCtx = settingsSourceChatId ? { parentChatId: settingsSourceChatId, actualChatId: chatId } : void 0;
|
|
8866
8908
|
const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, settingsChat, mode, responseStyle, effectiveAgentMode, sideQuestCtx);
|
|
8867
|
-
const existingSessionId = getSessionId(settingsChat);
|
|
8909
|
+
const existingSessionId = settingsSourceChatId ? null : getSessionId(settingsChat);
|
|
8868
8910
|
const allowedTools = getEnabledTools(settingsChat);
|
|
8869
8911
|
const mcpConfigPath = tier !== "slim" && effectiveAgentMode !== "native" && MCP_CONFIG_FLAG[adapter.id] ? getMcpConfigPath(chatId) : null;
|
|
8870
8912
|
const baseConfig = adapter.buildSpawnConfig({
|
|
@@ -8922,17 +8964,19 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
8922
8964
|
}
|
|
8923
8965
|
}
|
|
8924
8966
|
} catch (spawnErr) {
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8967
|
+
if (!isSyntheticChatId(chatId)) {
|
|
8968
|
+
try {
|
|
8969
|
+
const errMsg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
8970
|
+
const { logErrorSignal: logErrorSignal2 } = await Promise.resolve().then(() => (init_detect(), detect_exports));
|
|
8971
|
+
if (/timeout/i.test(errMsg)) {
|
|
8972
|
+
logErrorSignal2(chatId, "timeout", `Backend timed out`, { backendId: adapter.id, model: resolvedModel });
|
|
8973
|
+
} else if (/repetition.?loop/i.test(errMsg)) {
|
|
8974
|
+
logErrorSignal2(chatId, "repetition_loop", `Repetition loop detected`, { backendId: adapter.id, model: resolvedModel });
|
|
8975
|
+
} else {
|
|
8976
|
+
logErrorSignal2(chatId, "spawn_error", errMsg.slice(0, 300), { backendId: adapter.id, model: resolvedModel });
|
|
8977
|
+
}
|
|
8978
|
+
} catch {
|
|
8934
8979
|
}
|
|
8935
|
-
} catch {
|
|
8936
8980
|
}
|
|
8937
8981
|
throw spawnErr;
|
|
8938
8982
|
} finally {
|
|
@@ -9067,9 +9111,10 @@ ${responseText.slice(0, 500)}`);
|
|
|
9067
9111
|
return true;
|
|
9068
9112
|
}
|
|
9069
9113
|
try {
|
|
9114
|
+
const text = responseText.replace(/\s*\[REACT:[^\]]*\]\s*/g, " ").trim();
|
|
9070
9115
|
if (job.deliveryMode === "webhook") {
|
|
9071
|
-
await deliverWebhook(job,
|
|
9072
|
-
appendToLog(job.chatId, `[cron:#${job.id}] ${job.description}`,
|
|
9116
|
+
await deliverWebhook(job, text);
|
|
9117
|
+
appendToLog(job.chatId, `[cron:#${job.id}] ${job.description}`, text, job.backend ?? "claude", job.model, null);
|
|
9073
9118
|
return true;
|
|
9074
9119
|
}
|
|
9075
9120
|
const channelName = job.channel ?? "telegram";
|
|
@@ -9079,7 +9124,7 @@ ${responseText.slice(0, 500)}`);
|
|
|
9079
9124
|
return false;
|
|
9080
9125
|
}
|
|
9081
9126
|
const targetChatId = job.target ?? job.chatId;
|
|
9082
|
-
const cleanText = await processFileSends(targetChatId, channel,
|
|
9127
|
+
const cleanText = await processFileSends(targetChatId, channel, text);
|
|
9083
9128
|
if (!cleanText) return true;
|
|
9084
9129
|
if (channelName === "telegram") {
|
|
9085
9130
|
const parsed = parseTelegramTarget(targetChatId);
|
|
@@ -9310,6 +9355,143 @@ var init_health2 = __esm({
|
|
|
9310
9355
|
}
|
|
9311
9356
|
});
|
|
9312
9357
|
|
|
9358
|
+
// src/reflection/propose.ts
|
|
9359
|
+
var propose_exports = {};
|
|
9360
|
+
__export(propose_exports, {
|
|
9361
|
+
buildEvolveMenuKeyboard: () => buildEvolveMenuKeyboard,
|
|
9362
|
+
buildEvolveOnboardingKeyboard: () => buildEvolveOnboardingKeyboard,
|
|
9363
|
+
buildModelKeyboard: () => buildModelKeyboard,
|
|
9364
|
+
buildProposalKeyboard: () => buildProposalKeyboard,
|
|
9365
|
+
buildUndoKeyboard: () => buildUndoKeyboard,
|
|
9366
|
+
formatDiffCodeBlock: () => formatDiffCodeBlock,
|
|
9367
|
+
formatGrowthReport: () => formatGrowthReport,
|
|
9368
|
+
formatNightlySummary: () => formatNightlySummary,
|
|
9369
|
+
formatProposalCard: () => formatProposalCard
|
|
9370
|
+
});
|
|
9371
|
+
function pct(ratio) {
|
|
9372
|
+
return `${Math.round(ratio * 100)}%`;
|
|
9373
|
+
}
|
|
9374
|
+
function formatProposalCard(params) {
|
|
9375
|
+
const { category, insight, why, targetFile, confidence, proposedAction, proposedDiff } = params;
|
|
9376
|
+
const header2 = `[${category}] ${insight}`;
|
|
9377
|
+
const whyLine = why ? `Why: ${why}` : null;
|
|
9378
|
+
const confidencePct = pct(confidence);
|
|
9379
|
+
const showTarget = category !== "codebase" && targetFile && targetFile !== "codebase";
|
|
9380
|
+
const targetLine = showTarget ? `Target: ${targetFile} | Confidence: ${confidencePct}` : `Confidence: ${confidencePct}`;
|
|
9381
|
+
const lines = [header2, ""];
|
|
9382
|
+
if (whyLine) lines.push(whyLine);
|
|
9383
|
+
lines.push(targetLine);
|
|
9384
|
+
if (proposedDiff) {
|
|
9385
|
+
const actionLabel = proposedAction ? ` (${proposedAction})` : "";
|
|
9386
|
+
lines.push("", `Proposed change${actionLabel}:`, "```diff", proposedDiff, "```");
|
|
9387
|
+
}
|
|
9388
|
+
return lines.join("\n");
|
|
9389
|
+
}
|
|
9390
|
+
function formatNightlySummary(insights) {
|
|
9391
|
+
const count = insights.length;
|
|
9392
|
+
const header2 = `Nightly Reflection \u2014 ${count} proposal${count === 1 ? "" : "s"} ready`;
|
|
9393
|
+
const list = insights.map((ins, i) => `${i + 1}. [${ins.category}] ${ins.insight}`).join("\n");
|
|
9394
|
+
return `${header2}
|
|
9395
|
+
|
|
9396
|
+
${list}
|
|
9397
|
+
|
|
9398
|
+
Review with /evolve`;
|
|
9399
|
+
}
|
|
9400
|
+
function formatGrowthReport(metrics, modelPerformance) {
|
|
9401
|
+
const { correctionsBefore, correctionsAfter, praiseRatio, insightsApplied, pendingCount, topInsight } = metrics;
|
|
9402
|
+
const reduction = correctionsBefore > 0 ? Math.round((correctionsBefore - correctionsAfter) / correctionsBefore * 100) : 0;
|
|
9403
|
+
const lines = [];
|
|
9404
|
+
lines.push("Growth Report");
|
|
9405
|
+
lines.push("");
|
|
9406
|
+
lines.push(`Corrections: ${correctionsBefore.toFixed(1)} -> ${correctionsAfter.toFixed(1)} (${reduction}% reduction)`);
|
|
9407
|
+
lines.push(`Praise ratio: ${praiseRatio.toFixed(1)}x`);
|
|
9408
|
+
lines.push(`Insights applied: ${insightsApplied} | Pending: ${pendingCount}`);
|
|
9409
|
+
if (topInsight) {
|
|
9410
|
+
lines.push(`Top insight: "${topInsight.insight}" (${pct(topInsight.effectiveness)} effective)`);
|
|
9411
|
+
}
|
|
9412
|
+
if (modelPerformance.length > 0) {
|
|
9413
|
+
lines.push("");
|
|
9414
|
+
lines.push("Model Performance:");
|
|
9415
|
+
for (const row of modelPerformance) {
|
|
9416
|
+
const total = row.praises + row.corrections + row.errors;
|
|
9417
|
+
const scoreLabel = total > 0 ? ` (${row.praises}P / ${row.corrections}C / ${row.errors}E)` : "";
|
|
9418
|
+
lines.push(` ${row.backend} ${row.model} [${row.thinking}]${scoreLabel}`);
|
|
9419
|
+
}
|
|
9420
|
+
}
|
|
9421
|
+
return lines.join("\n");
|
|
9422
|
+
}
|
|
9423
|
+
function formatDiffCodeBlock(diff) {
|
|
9424
|
+
return `\`\`\`diff
|
|
9425
|
+
${diff}
|
|
9426
|
+
\`\`\``;
|
|
9427
|
+
}
|
|
9428
|
+
function buildProposalKeyboard(insightId, category) {
|
|
9429
|
+
if (category === "codebase") {
|
|
9430
|
+
return [
|
|
9431
|
+
[
|
|
9432
|
+
{ label: "Show Diff", data: `evolve:diff:${insightId}`, style: "primary" },
|
|
9433
|
+
{ label: "Discuss", data: `evolve:discuss:${insightId}` },
|
|
9434
|
+
{ label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
|
|
9435
|
+
]
|
|
9436
|
+
];
|
|
9437
|
+
}
|
|
9438
|
+
return [
|
|
9439
|
+
[
|
|
9440
|
+
{ label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
|
|
9441
|
+
{ label: "Discuss", data: `evolve:discuss:${insightId}` },
|
|
9442
|
+
{ label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
|
|
9443
|
+
]
|
|
9444
|
+
];
|
|
9445
|
+
}
|
|
9446
|
+
function buildEvolveOnboardingKeyboard() {
|
|
9447
|
+
return [
|
|
9448
|
+
[{ label: "\u2705 Enable Self-Learning", data: "evolve:toggle", style: "success" }],
|
|
9449
|
+
[{ label: "One-Time Analysis", data: "evolve:analyze" }]
|
|
9450
|
+
];
|
|
9451
|
+
}
|
|
9452
|
+
function buildEvolveMenuKeyboard(ctx) {
|
|
9453
|
+
const reviewLabel = ctx.pendingProposals > 0 ? `Review (${ctx.pendingProposals})` : "Review";
|
|
9454
|
+
return [
|
|
9455
|
+
[
|
|
9456
|
+
{ label: "\u{1F50D} Analyze Now", data: "evolve:analyze", style: "success" },
|
|
9457
|
+
ctx.pendingProposals > 0 ? { label: reviewLabel, data: "evolve:review", style: "primary" } : { label: reviewLabel, data: "evolve:review" }
|
|
9458
|
+
],
|
|
9459
|
+
[
|
|
9460
|
+
{ label: "Stats", data: "evolve:stats" },
|
|
9461
|
+
{ label: "History", data: "evolve:history" }
|
|
9462
|
+
],
|
|
9463
|
+
[
|
|
9464
|
+
{ label: "Model", data: "evolve:model" },
|
|
9465
|
+
{ label: "Undo", data: "evolve:undo", style: "danger" },
|
|
9466
|
+
{ label: "\u23F8 Disable", data: "evolve:toggle", style: "danger" }
|
|
9467
|
+
]
|
|
9468
|
+
];
|
|
9469
|
+
}
|
|
9470
|
+
function buildUndoKeyboard(insights) {
|
|
9471
|
+
return insights.map((ins) => [
|
|
9472
|
+
{ label: `Undo #${ins.id}: ${ins.insight}`, data: `evolve:undo:${ins.id}`, style: "danger" }
|
|
9473
|
+
]);
|
|
9474
|
+
}
|
|
9475
|
+
function buildModelKeyboard(currentMode) {
|
|
9476
|
+
const modes = [
|
|
9477
|
+
{ label: "Auto", mode: "auto" },
|
|
9478
|
+
{ label: "Pinned", mode: "pinned" },
|
|
9479
|
+
{ label: "Cheap", mode: "cheap" }
|
|
9480
|
+
];
|
|
9481
|
+
return [
|
|
9482
|
+
modes.map(({ label: label2, mode }) => ({
|
|
9483
|
+
label: mode === currentMode ? `[${label2}]` : label2,
|
|
9484
|
+
data: `evolve:model:${mode}`,
|
|
9485
|
+
...mode === currentMode ? { style: "primary" } : {}
|
|
9486
|
+
}))
|
|
9487
|
+
];
|
|
9488
|
+
}
|
|
9489
|
+
var init_propose = __esm({
|
|
9490
|
+
"src/reflection/propose.ts"() {
|
|
9491
|
+
"use strict";
|
|
9492
|
+
}
|
|
9493
|
+
});
|
|
9494
|
+
|
|
9313
9495
|
// src/scheduler/cron.ts
|
|
9314
9496
|
var cron_exports = {};
|
|
9315
9497
|
__export(cron_exports, {
|
|
@@ -9530,6 +9712,17 @@ async function executeJob(job) {
|
|
|
9530
9712
|
async function runWithRetry(job, model2, runId, t0) {
|
|
9531
9713
|
let lastError;
|
|
9532
9714
|
const chatId = job.sessionType === "isolated" ? `cron:${job.id}:${runId}` : job.chatId;
|
|
9715
|
+
if (job.jobType === "reflection") {
|
|
9716
|
+
const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
|
|
9717
|
+
const { formatNightlySummary: formatNightlySummary2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
9718
|
+
const timeoutMs2 = job.timeout ? job.timeout * 1e3 : void 0;
|
|
9719
|
+
const insights = await runAnalysis2(job.chatId, { force: true, timeoutMs: timeoutMs2 });
|
|
9720
|
+
if (insights.length === 0) {
|
|
9721
|
+
return { text: "Nightly reflection: no new insights from recent interactions." };
|
|
9722
|
+
}
|
|
9723
|
+
const items = insights.map((ins, i) => ({ id: i + 1, ...ins }));
|
|
9724
|
+
return { text: formatNightlySummary2(items) };
|
|
9725
|
+
}
|
|
9533
9726
|
if (job.thinking && job.thinking !== "auto") {
|
|
9534
9727
|
setThinkingLevel(chatId, job.thinking);
|
|
9535
9728
|
}
|
|
@@ -12783,143 +12976,6 @@ var init_pagination = __esm({
|
|
|
12783
12976
|
}
|
|
12784
12977
|
});
|
|
12785
12978
|
|
|
12786
|
-
// src/reflection/propose.ts
|
|
12787
|
-
var propose_exports = {};
|
|
12788
|
-
__export(propose_exports, {
|
|
12789
|
-
buildEvolveMenuKeyboard: () => buildEvolveMenuKeyboard,
|
|
12790
|
-
buildEvolveOnboardingKeyboard: () => buildEvolveOnboardingKeyboard,
|
|
12791
|
-
buildModelKeyboard: () => buildModelKeyboard,
|
|
12792
|
-
buildProposalKeyboard: () => buildProposalKeyboard,
|
|
12793
|
-
buildUndoKeyboard: () => buildUndoKeyboard,
|
|
12794
|
-
formatDiffCodeBlock: () => formatDiffCodeBlock,
|
|
12795
|
-
formatGrowthReport: () => formatGrowthReport,
|
|
12796
|
-
formatNightlySummary: () => formatNightlySummary,
|
|
12797
|
-
formatProposalCard: () => formatProposalCard
|
|
12798
|
-
});
|
|
12799
|
-
function pct(ratio) {
|
|
12800
|
-
return `${Math.round(ratio * 100)}%`;
|
|
12801
|
-
}
|
|
12802
|
-
function formatProposalCard(params) {
|
|
12803
|
-
const { category, insight, why, targetFile, confidence, proposedAction, proposedDiff } = params;
|
|
12804
|
-
const header2 = `[${category}] ${insight}`;
|
|
12805
|
-
const whyLine = why ? `Why: ${why}` : null;
|
|
12806
|
-
const confidencePct = pct(confidence);
|
|
12807
|
-
const showTarget = category !== "codebase" && targetFile && targetFile !== "codebase";
|
|
12808
|
-
const targetLine = showTarget ? `Target: ${targetFile} | Confidence: ${confidencePct}` : `Confidence: ${confidencePct}`;
|
|
12809
|
-
const lines = [header2, ""];
|
|
12810
|
-
if (whyLine) lines.push(whyLine);
|
|
12811
|
-
lines.push(targetLine);
|
|
12812
|
-
if (proposedDiff) {
|
|
12813
|
-
const actionLabel = proposedAction ? ` (${proposedAction})` : "";
|
|
12814
|
-
lines.push("", `Proposed change${actionLabel}:`, "```diff", proposedDiff, "```");
|
|
12815
|
-
}
|
|
12816
|
-
return lines.join("\n");
|
|
12817
|
-
}
|
|
12818
|
-
function formatNightlySummary(insights) {
|
|
12819
|
-
const count = insights.length;
|
|
12820
|
-
const header2 = `Nightly Reflection \u2014 ${count} proposal${count === 1 ? "" : "s"} ready`;
|
|
12821
|
-
const list = insights.map((ins, i) => `${i + 1}. [${ins.category}] ${ins.insight}`).join("\n");
|
|
12822
|
-
return `${header2}
|
|
12823
|
-
|
|
12824
|
-
${list}
|
|
12825
|
-
|
|
12826
|
-
Review with /evolve`;
|
|
12827
|
-
}
|
|
12828
|
-
function formatGrowthReport(metrics, modelPerformance) {
|
|
12829
|
-
const { correctionsBefore, correctionsAfter, praiseRatio, insightsApplied, pendingCount, topInsight } = metrics;
|
|
12830
|
-
const reduction = correctionsBefore > 0 ? Math.round((correctionsBefore - correctionsAfter) / correctionsBefore * 100) : 0;
|
|
12831
|
-
const lines = [];
|
|
12832
|
-
lines.push("Growth Report");
|
|
12833
|
-
lines.push("");
|
|
12834
|
-
lines.push(`Corrections: ${correctionsBefore.toFixed(1)} -> ${correctionsAfter.toFixed(1)} (${reduction}% reduction)`);
|
|
12835
|
-
lines.push(`Praise ratio: ${praiseRatio.toFixed(1)}x`);
|
|
12836
|
-
lines.push(`Insights applied: ${insightsApplied} | Pending: ${pendingCount}`);
|
|
12837
|
-
if (topInsight) {
|
|
12838
|
-
lines.push(`Top insight: "${topInsight.insight}" (${pct(topInsight.effectiveness)} effective)`);
|
|
12839
|
-
}
|
|
12840
|
-
if (modelPerformance.length > 0) {
|
|
12841
|
-
lines.push("");
|
|
12842
|
-
lines.push("Model Performance:");
|
|
12843
|
-
for (const row of modelPerformance) {
|
|
12844
|
-
const total = row.praises + row.corrections + row.errors;
|
|
12845
|
-
const scoreLabel = total > 0 ? ` (${row.praises}P / ${row.corrections}C / ${row.errors}E)` : "";
|
|
12846
|
-
lines.push(` ${row.backend} ${row.model} [${row.thinking}]${scoreLabel}`);
|
|
12847
|
-
}
|
|
12848
|
-
}
|
|
12849
|
-
return lines.join("\n");
|
|
12850
|
-
}
|
|
12851
|
-
function formatDiffCodeBlock(diff) {
|
|
12852
|
-
return `\`\`\`diff
|
|
12853
|
-
${diff}
|
|
12854
|
-
\`\`\``;
|
|
12855
|
-
}
|
|
12856
|
-
function buildProposalKeyboard(insightId, category) {
|
|
12857
|
-
if (category === "codebase") {
|
|
12858
|
-
return [
|
|
12859
|
-
[
|
|
12860
|
-
{ label: "Show Diff", data: `evolve:diff:${insightId}`, style: "primary" },
|
|
12861
|
-
{ label: "Discuss", data: `evolve:discuss:${insightId}` },
|
|
12862
|
-
{ label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
|
|
12863
|
-
]
|
|
12864
|
-
];
|
|
12865
|
-
}
|
|
12866
|
-
return [
|
|
12867
|
-
[
|
|
12868
|
-
{ label: "Apply", data: `evolve:apply:${insightId}`, style: "success" },
|
|
12869
|
-
{ label: "Discuss", data: `evolve:discuss:${insightId}` },
|
|
12870
|
-
{ label: "Reject", data: `evolve:reject:${insightId}`, style: "danger" }
|
|
12871
|
-
]
|
|
12872
|
-
];
|
|
12873
|
-
}
|
|
12874
|
-
function buildEvolveOnboardingKeyboard() {
|
|
12875
|
-
return [
|
|
12876
|
-
[{ label: "\u2705 Enable Self-Learning", data: "evolve:toggle", style: "success" }],
|
|
12877
|
-
[{ label: "One-Time Analysis", data: "evolve:analyze" }]
|
|
12878
|
-
];
|
|
12879
|
-
}
|
|
12880
|
-
function buildEvolveMenuKeyboard(ctx) {
|
|
12881
|
-
const reviewLabel = ctx.pendingProposals > 0 ? `Review (${ctx.pendingProposals})` : "Review";
|
|
12882
|
-
return [
|
|
12883
|
-
[
|
|
12884
|
-
{ label: "\u{1F50D} Analyze Now", data: "evolve:analyze", style: "success" },
|
|
12885
|
-
ctx.pendingProposals > 0 ? { label: reviewLabel, data: "evolve:review", style: "primary" } : { label: reviewLabel, data: "evolve:review" }
|
|
12886
|
-
],
|
|
12887
|
-
[
|
|
12888
|
-
{ label: "Stats", data: "evolve:stats" },
|
|
12889
|
-
{ label: "History", data: "evolve:history" }
|
|
12890
|
-
],
|
|
12891
|
-
[
|
|
12892
|
-
{ label: "Model", data: "evolve:model" },
|
|
12893
|
-
{ label: "Undo", data: "evolve:undo", style: "danger" },
|
|
12894
|
-
{ label: "\u23F8 Disable", data: "evolve:toggle", style: "danger" }
|
|
12895
|
-
]
|
|
12896
|
-
];
|
|
12897
|
-
}
|
|
12898
|
-
function buildUndoKeyboard(insights) {
|
|
12899
|
-
return insights.map((ins) => [
|
|
12900
|
-
{ label: `Undo #${ins.id}: ${ins.insight}`, data: `evolve:undo:${ins.id}`, style: "danger" }
|
|
12901
|
-
]);
|
|
12902
|
-
}
|
|
12903
|
-
function buildModelKeyboard(currentMode) {
|
|
12904
|
-
const modes = [
|
|
12905
|
-
{ label: "Auto", mode: "auto" },
|
|
12906
|
-
{ label: "Pinned", mode: "pinned" },
|
|
12907
|
-
{ label: "Cheap", mode: "cheap" }
|
|
12908
|
-
];
|
|
12909
|
-
return [
|
|
12910
|
-
modes.map(({ label: label2, mode }) => ({
|
|
12911
|
-
label: mode === currentMode ? `[${label2}]` : label2,
|
|
12912
|
-
data: `evolve:model:${mode}`,
|
|
12913
|
-
...mode === currentMode ? { style: "primary" } : {}
|
|
12914
|
-
}))
|
|
12915
|
-
];
|
|
12916
|
-
}
|
|
12917
|
-
var init_propose = __esm({
|
|
12918
|
-
"src/reflection/propose.ts"() {
|
|
12919
|
-
"use strict";
|
|
12920
|
-
}
|
|
12921
|
-
});
|
|
12922
|
-
|
|
12923
12979
|
// src/router.ts
|
|
12924
12980
|
import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2, mkdir as mkdir2, readdir as readdir3, stat } from "fs/promises";
|
|
12925
12981
|
import { existsSync as existsSync19 } from "fs";
|
|
@@ -14835,32 +14891,49 @@ Message: "${testMsg}"`, { parseMode: "plain" });
|
|
|
14835
14891
|
break;
|
|
14836
14892
|
}
|
|
14837
14893
|
case "reflect": {
|
|
14838
|
-
const
|
|
14839
|
-
|
|
14840
|
-
|
|
14894
|
+
const lastReflect = lastReflectTime.get(chatId);
|
|
14895
|
+
if (lastReflect && Date.now() - lastReflect < REFLECT_COOLDOWN_MS) {
|
|
14896
|
+
const remaining = Math.ceil((REFLECT_COOLDOWN_MS - (Date.now() - lastReflect)) / 1e3);
|
|
14897
|
+
await channel.sendText(chatId, `Please wait ${remaining}s before reflecting again.`, { parseMode: "plain" });
|
|
14898
|
+
break;
|
|
14899
|
+
}
|
|
14900
|
+
const { getMessagePairCount: getMessagePairCount2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
|
|
14901
|
+
const pairCount = getMessagePairCount2(chatId);
|
|
14902
|
+
if (pairCount < 2) {
|
|
14903
|
+
await channel.sendText(chatId, "Need more conversation to analyze \u2014 at least 2 message exchanges.", { parseMode: "plain" });
|
|
14904
|
+
break;
|
|
14905
|
+
}
|
|
14906
|
+
lastReflectTime.set(chatId, Date.now());
|
|
14907
|
+
await channel.sendText(chatId, "Step 1/3: Summarizing conversation...", { parseMode: "plain" });
|
|
14841
14908
|
try {
|
|
14909
|
+
const { summarizeSession: summarizeSession2 } = await Promise.resolve().then(() => (init_summarize(), summarize_exports));
|
|
14910
|
+
await summarizeSession2(chatId, false);
|
|
14911
|
+
} catch (e) {
|
|
14912
|
+
await channel.sendText(chatId, `Summarization failed: ${e}`, { parseMode: "plain" });
|
|
14913
|
+
break;
|
|
14914
|
+
}
|
|
14915
|
+
await channel.sendText(chatId, "Step 2/3: Analyzing for insights...", { parseMode: "plain" });
|
|
14916
|
+
try {
|
|
14917
|
+
const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_analyze(), analyze_exports));
|
|
14918
|
+
const { formatProposalCard: formatProposalCard2, buildProposalKeyboard: buildProposalKeyboard2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
|
|
14842
14919
|
const insights = await runAnalysis2(chatId, { force: true });
|
|
14843
14920
|
if (insights.length === 0) {
|
|
14844
|
-
|
|
14845
|
-
const isFrozen = getRefStatus(getDb(), chatId) === "frozen";
|
|
14846
|
-
if (isFrozen) {
|
|
14847
|
-
const msg2 = "No insights found. Self-learning is disabled \u2014\nenable it with /evolve so feedback signals are\ncaptured automatically between analyses.";
|
|
14848
|
-
if (typeof channel.sendKeyboard === "function") {
|
|
14849
|
-
await channel.sendKeyboard(chatId, msg2, [[
|
|
14850
|
-
{ label: "Open /evolve", data: "evolve:menu", style: "primary" }
|
|
14851
|
-
]]);
|
|
14852
|
-
} else {
|
|
14853
|
-
await channel.sendText(chatId, msg2, { parseMode: "plain" });
|
|
14854
|
-
}
|
|
14855
|
-
} else {
|
|
14856
|
-
await channel.sendText(chatId, "No new insights from recent interactions.", { parseMode: "plain" });
|
|
14857
|
-
}
|
|
14921
|
+
await channel.sendText(chatId, "Step 3/3: No actionable improvements found in this session.", { parseMode: "plain" });
|
|
14858
14922
|
} else {
|
|
14859
|
-
|
|
14860
|
-
|
|
14923
|
+
await channel.sendText(chatId, `Step 3/3: Found ${insights.length} proposal(s).`, { parseMode: "plain" });
|
|
14924
|
+
const { getPendingInsights: getPendingInsights2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
|
|
14925
|
+
const pending = getPendingInsights2(getDb(), chatId);
|
|
14926
|
+
for (const insight of pending.slice(0, 5)) {
|
|
14927
|
+
const card = formatProposalCard2(insight);
|
|
14928
|
+
const kb = buildProposalKeyboard2(insight.id, insight.category);
|
|
14929
|
+
await channel.sendKeyboard(chatId, card, kb);
|
|
14930
|
+
}
|
|
14931
|
+
if (insights.length > 5) {
|
|
14932
|
+
await channel.sendText(chatId, `${insights.length - 5} more proposals. Use /evolve to review all.`, { parseMode: "plain" });
|
|
14933
|
+
}
|
|
14861
14934
|
}
|
|
14862
14935
|
} catch (e) {
|
|
14863
|
-
await channel.sendText(chatId, `Analysis failed: ${e}
|
|
14936
|
+
await channel.sendText(chatId, `Analysis failed: ${e}. You can review any pending proposals with /evolve.`, { parseMode: "plain" });
|
|
14864
14937
|
}
|
|
14865
14938
|
break;
|
|
14866
14939
|
}
|
|
@@ -15314,7 +15387,8 @@ async function handleSideQuest(parentChatId, msg, channel) {
|
|
|
15314
15387
|
backendId: adapter.id,
|
|
15315
15388
|
model: model2 ?? adapter.defaultModel
|
|
15316
15389
|
});
|
|
15317
|
-
} catch {
|
|
15390
|
+
} catch (e) {
|
|
15391
|
+
log(`[reflection] Side quest signal detection error: ${e}`);
|
|
15318
15392
|
}
|
|
15319
15393
|
} catch (err) {
|
|
15320
15394
|
typingActive = false;
|
|
@@ -15875,7 +15949,7 @@ async function sendJobDetail(chatId, jobId, channel) {
|
|
|
15875
15949
|
const lines = [
|
|
15876
15950
|
`Job #${job.id}: ${job.title ?? job.description}`,
|
|
15877
15951
|
buildSectionHeader("", 22),
|
|
15878
|
-
...job.title ? [`Task: ${job.description
|
|
15952
|
+
...job.title ? [`Task: ${job.description}`] : [],
|
|
15879
15953
|
`Runs: ${schedule2}${tz}`,
|
|
15880
15954
|
`Backend: ${backend2} | Model: ${model2}`,
|
|
15881
15955
|
`Last run: ${lastRun}${lastRunStatus ? ` (${lastRunStatus})` : ""}`,
|
|
@@ -16242,9 +16316,12 @@ ${PERM_MODES[chosen]}`,
|
|
|
16242
16316
|
const pending2 = pendingInterrupts.get(targetChatId);
|
|
16243
16317
|
if (pending2) {
|
|
16244
16318
|
pendingInterrupts.delete(targetChatId);
|
|
16245
|
-
|
|
16246
|
-
|
|
16247
|
-
|
|
16319
|
+
await channel.sendText(chatId, "\u{1F5FA} Launching side quest\u2026", { parseMode: "plain" });
|
|
16320
|
+
handleSideQuest(targetChatId, pending2.msg, pending2.channel).catch((err) => {
|
|
16321
|
+
error(`[router] Side quest error for ${targetChatId}:`, err);
|
|
16322
|
+
channel.sendText(targetChatId, `\u{1F5FA} Side quest failed: ${err.message}`, { parseMode: "plain" }).catch(() => {
|
|
16323
|
+
});
|
|
16324
|
+
});
|
|
16248
16325
|
} else {
|
|
16249
16326
|
await channel.sendText(chatId, "Main task finished \u2014 your message was already processed.", { parseMode: "plain" });
|
|
16250
16327
|
}
|
|
@@ -17454,7 +17531,7 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
|
|
|
17454
17531
|
const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
|
|
17455
17532
|
await channel.sendKeyboard(chatId, header2, buttons);
|
|
17456
17533
|
}
|
|
17457
|
-
var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, activeSideQuests, MAX_SIDE_QUESTS, pendingFallbackMessages, dashboardClawWarnings, pendingSummaryUndo, pendingNewchatUndo, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
|
|
17534
|
+
var PERM_MODES, VERBOSE_LEVELS, HELP_CATEGORIES, USAGE_WINDOW_MAP, MEDIA_INCOMING_PATH, TONE_PATTERNS, pendingInterrupts, bypassBusyCheck, activeSideQuests, MAX_SIDE_QUESTS, pendingFallbackMessages, dashboardClawWarnings, lastReflectTime, REFLECT_COOLDOWN_MS, pendingSummaryUndo, pendingNewchatUndo, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
|
|
17458
17535
|
var init_router = __esm({
|
|
17459
17536
|
"src/router.ts"() {
|
|
17460
17537
|
"use strict";
|
|
@@ -17597,6 +17674,8 @@ var init_router = __esm({
|
|
|
17597
17674
|
MAX_SIDE_QUESTS = 2;
|
|
17598
17675
|
pendingFallbackMessages = /* @__PURE__ */ new Map();
|
|
17599
17676
|
dashboardClawWarnings = /* @__PURE__ */ new Map();
|
|
17677
|
+
lastReflectTime = /* @__PURE__ */ new Map();
|
|
17678
|
+
REFLECT_COOLDOWN_MS = 12e4;
|
|
17600
17679
|
pendingSummaryUndo = /* @__PURE__ */ new Map();
|
|
17601
17680
|
pendingNewchatUndo = /* @__PURE__ */ new Map();
|
|
17602
17681
|
CLI_INSTALL_HINTS = {
|
|
@@ -18611,6 +18690,8 @@ async function main() {
|
|
|
18611
18690
|
const shutdown = async (signal) => {
|
|
18612
18691
|
log(`[cc-claw] Received ${signal}, shutting down...`);
|
|
18613
18692
|
try {
|
|
18693
|
+
const { stopAllActiveAgents: stopAllActiveAgents2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
|
|
18694
|
+
stopAllActiveAgents2();
|
|
18614
18695
|
stopAllHeartbeats();
|
|
18615
18696
|
stopHealthMonitor3();
|
|
18616
18697
|
stopMonitor();
|
|
@@ -19378,14 +19459,18 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
19378
19459
|
checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
|
|
19379
19460
|
}
|
|
19380
19461
|
const CLI_BINARIES = { claude: "claude", gemini: "gemini", codex: "codex", cursor: "agent" };
|
|
19462
|
+
let installedBackends = 0;
|
|
19381
19463
|
for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
|
|
19382
19464
|
try {
|
|
19383
19465
|
const path = execFileSync3("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
19384
19466
|
checks.push({ name: `${label2} CLI`, status: "ok", message: path });
|
|
19467
|
+
installedBackends++;
|
|
19385
19468
|
} catch {
|
|
19386
|
-
checks.push({ name: `${label2} CLI`, status: "warning", message: "not found in PATH" });
|
|
19387
19469
|
}
|
|
19388
19470
|
}
|
|
19471
|
+
if (installedBackends === 0) {
|
|
19472
|
+
checks.push({ name: "Backend CLIs", status: "error", message: "No backend CLIs found. Install at least one (claude, gemini, codex, or cursor agent)." });
|
|
19473
|
+
}
|
|
19389
19474
|
try {
|
|
19390
19475
|
const { isDaemonRunning: isDaemonRunning2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
19391
19476
|
const running = await isDaemonRunning2();
|
|
@@ -19415,6 +19500,13 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
19415
19500
|
} catch {
|
|
19416
19501
|
checks.push({ name: "Daemon", status: "warning", message: "could not probe" });
|
|
19417
19502
|
}
|
|
19503
|
+
try {
|
|
19504
|
+
const latest = execFileSync3("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
19505
|
+
if (latest && latest !== VERSION) {
|
|
19506
|
+
checks.push({ name: "Update available", status: "warning", message: `v${latest} available (current: v${VERSION})`, fix: "npm install -g cc-claw@latest" });
|
|
19507
|
+
}
|
|
19508
|
+
} catch {
|
|
19509
|
+
}
|
|
19418
19510
|
const tokenPath = `${DATA_PATH}/api-token`;
|
|
19419
19511
|
if (existsSync26(tokenPath)) {
|
|
19420
19512
|
try {
|
|
@@ -23330,6 +23422,20 @@ program.command("setup").description("Interactive configuration wizard").action(
|
|
|
23330
23422
|
await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
23331
23423
|
});
|
|
23332
23424
|
async function run(argv = process.argv) {
|
|
23425
|
+
if (argv.includes("--version") || argv.includes("-V")) {
|
|
23426
|
+
console.log(VERSION);
|
|
23427
|
+
try {
|
|
23428
|
+
const { execFileSync: execFileSync5 } = await import("child_process");
|
|
23429
|
+
const latest = execFileSync5("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
23430
|
+
if (latest && latest !== VERSION) {
|
|
23431
|
+
console.log(`
|
|
23432
|
+
Update available: v${latest} (current: v${VERSION})`);
|
|
23433
|
+
console.log(`Run: npm install -g cc-claw@latest`);
|
|
23434
|
+
}
|
|
23435
|
+
} catch {
|
|
23436
|
+
}
|
|
23437
|
+
return;
|
|
23438
|
+
}
|
|
23333
23439
|
if (argv.includes("--ai")) {
|
|
23334
23440
|
const { generateAiSkill: generateAiSkill2, installAiSkill: installAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
|
|
23335
23441
|
if (argv.includes("--install")) {
|
package/package.json
CHANGED