openclaw-telegram-manager 2.5.7 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/commands/daily-report.js +14 -14
- package/dist/commands/daily-report.js.map +1 -1
- package/dist/commands/doctor-all.d.ts +34 -0
- package/dist/commands/doctor-all.d.ts.map +1 -1
- package/dist/commands/doctor-all.js +189 -42
- package/dist/commands/doctor-all.js.map +1 -1
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +4 -5
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/status.js +3 -3
- package/dist/commands/status.js.map +1 -1
- package/dist/lib/include-generator.d.ts.map +1 -1
- package/dist/lib/include-generator.js +20 -7
- package/dist/lib/include-generator.js.map +1 -1
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/lib/registry.js +11 -0
- package/dist/lib/registry.js.map +1 -1
- package/dist/lib/telegram.d.ts +1 -1
- package/dist/lib/telegram.d.ts.map +1 -1
- package/dist/lib/telegram.js +6 -8
- package/dist/lib/telegram.js.map +1 -1
- package/dist/lib/types.d.ts +5 -6
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +4 -4
- package/dist/lib/types.js.map +1 -1
- package/dist/plugin.js +243 -81
- package/dist/setup.js +27 -2
- package/dist/setup.js.map +1 -1
- package/dist/tool.js +2 -2
- package/dist/tool.js.map +1 -1
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -8815,7 +8815,7 @@ __export(value_exports2, {
|
|
|
8815
8815
|
});
|
|
8816
8816
|
|
|
8817
8817
|
// src/lib/types.ts
|
|
8818
|
-
var CURRENT_REGISTRY_VERSION =
|
|
8818
|
+
var CURRENT_REGISTRY_VERSION = 5;
|
|
8819
8819
|
var CAPSULE_VERSION = 3;
|
|
8820
8820
|
var MAX_POST_ERROR_LENGTH = 500;
|
|
8821
8821
|
var MAX_NAME_LENGTH = 100;
|
|
@@ -8832,7 +8832,7 @@ var TopicTypeSchema = Type.Union([
|
|
|
8832
8832
|
Type.Literal("coding"),
|
|
8833
8833
|
Type.Literal("research"),
|
|
8834
8834
|
Type.Literal("marketing"),
|
|
8835
|
-
Type.Literal("
|
|
8835
|
+
Type.Literal("general")
|
|
8836
8836
|
]);
|
|
8837
8837
|
var TopicStatusSchema = Type.Union([
|
|
8838
8838
|
Type.Literal("active"),
|
|
@@ -8870,7 +8870,7 @@ var OVERLAY_FILES = {
|
|
|
8870
8870
|
coding: ["ARCHITECTURE.md", "DEPLOY.md"],
|
|
8871
8871
|
research: ["SOURCES.md", "FINDINGS.md"],
|
|
8872
8872
|
marketing: ["CAMPAIGNS.md", "METRICS.md"],
|
|
8873
|
-
|
|
8873
|
+
general: []
|
|
8874
8874
|
};
|
|
8875
8875
|
var BASE_FILES = [
|
|
8876
8876
|
"README.md",
|
|
@@ -8931,6 +8931,17 @@ var migrations = {
|
|
|
8931
8931
|
}
|
|
8932
8932
|
}
|
|
8933
8933
|
return data;
|
|
8934
|
+
},
|
|
8935
|
+
"4_to_5": (data) => {
|
|
8936
|
+
const topics = data["topics"];
|
|
8937
|
+
if (topics && typeof topics === "object" && !Array.isArray(topics)) {
|
|
8938
|
+
for (const entry of Object.values(topics)) {
|
|
8939
|
+
if (entry["type"] === "custom") {
|
|
8940
|
+
entry["type"] = "general";
|
|
8941
|
+
}
|
|
8942
|
+
}
|
|
8943
|
+
}
|
|
8944
|
+
return data;
|
|
8934
8945
|
}
|
|
8935
8946
|
};
|
|
8936
8947
|
function migrateRegistry(data) {
|
|
@@ -9392,7 +9403,7 @@ function buildInitTypeButtons(groupId, threadId, secret, userId) {
|
|
|
9392
9403
|
],
|
|
9393
9404
|
[
|
|
9394
9405
|
{ text: "Marketing", callback_data: cb("im") },
|
|
9395
|
-
{ text: "
|
|
9406
|
+
{ text: "General", callback_data: cb("ig") }
|
|
9396
9407
|
]
|
|
9397
9408
|
]);
|
|
9398
9409
|
}
|
|
@@ -9401,7 +9412,7 @@ function buildInitConfirmButton(groupId, threadId, secret, userId, type) {
|
|
|
9401
9412
|
coding: "yc",
|
|
9402
9413
|
research: "yr",
|
|
9403
9414
|
marketing: "ym",
|
|
9404
|
-
|
|
9415
|
+
general: "yg"
|
|
9405
9416
|
};
|
|
9406
9417
|
const cb = buildCallbackData(actionMap[type], groupId, threadId, secret, userId);
|
|
9407
9418
|
return buildInlineKeyboard([[{ text: "Use this name", callback_data: cb }]]);
|
|
@@ -9428,7 +9439,7 @@ function buildInitWelcomeHtml() {
|
|
|
9428
9439
|
"\u2022 <b>Coding</b> \u2014 tracks architecture decisions and deployment steps",
|
|
9429
9440
|
"\u2022 <b>Research</b> \u2014 tracks sources and key findings",
|
|
9430
9441
|
"\u2022 <b>Marketing</b> \u2014 tracks campaigns and metrics",
|
|
9431
|
-
"\u2022 <b>
|
|
9442
|
+
"\u2022 <b>General</b> \u2014 general-purpose tracking",
|
|
9432
9443
|
"",
|
|
9433
9444
|
"<i>The AI may take a few seconds to respond \u2014 no need to tap twice.</i>"
|
|
9434
9445
|
].join("\n");
|
|
@@ -9526,9 +9537,7 @@ function buildHelpCard() {
|
|
|
9526
9537
|
"/tm rename new-name \u2014 rename this topic",
|
|
9527
9538
|
"/tm snooze 7d \u2014 pause health checks (e.g. 7d, 30d)",
|
|
9528
9539
|
"/tm archive \u2014 archive this topic",
|
|
9529
|
-
"/tm unarchive \u2014 bring back an archived topic"
|
|
9530
|
-
"/tm sync \u2014 fix config if out of sync",
|
|
9531
|
-
"/tm upgrade \u2014 update topic files to latest version"
|
|
9540
|
+
"/tm unarchive \u2014 bring back an archived topic"
|
|
9532
9541
|
].join("\n");
|
|
9533
9542
|
}
|
|
9534
9543
|
function buildListMessage(topics) {
|
|
@@ -9544,8 +9553,8 @@ function buildListMessage(topics) {
|
|
|
9544
9553
|
let rendered = 0;
|
|
9545
9554
|
for (const t of sorted) {
|
|
9546
9555
|
const activity = t.lastMessageAt ? `active ${relativeTime(t.lastMessageAt)}` : "no activity yet";
|
|
9547
|
-
const
|
|
9548
|
-
const entry =
|
|
9556
|
+
const icon = t.status === "snoozed" ? "\u{1F4A4} " : t.status === "archived" ? "\u{1F4E6} " : "";
|
|
9557
|
+
const entry = `${icon}**${t.name}** \xB7 ${t.type}
|
|
9549
9558
|
${activity}`;
|
|
9550
9559
|
const tentative = [...lines, entry, ""].join("\n");
|
|
9551
9560
|
if (tentative.length > TELEGRAM_MSG_LIMIT - 40) {
|
|
@@ -9628,12 +9637,22 @@ var FILE_MODE3 = 384;
|
|
|
9628
9637
|
function getSystemPromptTemplate(name, slug, absoluteWorkspacePath) {
|
|
9629
9638
|
return `You are the assistant for the Telegram topic: ${name}.
|
|
9630
9639
|
|
|
9640
|
+
Context hierarchy (strictly ordered):
|
|
9641
|
+
1. TOPIC FILES (primary) \u2014 your project folder at: ${absoluteWorkspacePath}/projects/${slug}/
|
|
9642
|
+
These define WHAT this topic is working on: current tasks, status, goals, history.
|
|
9643
|
+
When asked "what are we working on?", answer ONLY from these files.
|
|
9644
|
+
2. WORKSPACE MEMORY (secondary) \u2014 files like memory/, MEMORY.md at the workspace root.
|
|
9645
|
+
These contain general learnings, user preferences, and cross-topic knowledge.
|
|
9646
|
+
Use them for HOW to work (patterns, mistakes to avoid, coding style) \u2014 but
|
|
9647
|
+
never to determine what this topic is about or what its current tasks are.
|
|
9648
|
+
Workspace memory may reference projects belonging to OTHER topics \u2014 ignore those.
|
|
9649
|
+
|
|
9631
9650
|
Determinism rules:
|
|
9632
|
-
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9651
|
+
- After /reset, /new, or context compaction: ALWAYS re-read your topic files
|
|
9652
|
+
first \u2014 STATUS.md, then TODO.md, then LEARNINGS.md (last 20 entries), then
|
|
9653
|
+
COMMANDS.md \u2014 before doing anything else. These are your ground truth.
|
|
9654
|
+
Do not rely on summarized memory or workspace-level files for this topic's
|
|
9655
|
+
tasks, status, or goals.
|
|
9637
9656
|
- Before context compaction or when the conversation is long: proactively
|
|
9638
9657
|
flush current progress to STATUS.md (update "Last done (UTC)" and
|
|
9639
9658
|
"Next actions (now)") so compaction cannot erase critical state.
|
|
@@ -9656,8 +9675,11 @@ Learning capture:
|
|
|
9656
9675
|
- If LEARNINGS.md exceeds ~200 lines, archive older entries to LEARNINGS-archive.md.
|
|
9657
9676
|
|
|
9658
9677
|
Separation:
|
|
9659
|
-
- Your
|
|
9660
|
-
|
|
9678
|
+
- Your project folder is strictly projects/${slug}/. This is your identity.
|
|
9679
|
+
- Do not read, write, or reference files in any other topic's project directory.
|
|
9680
|
+
- Workspace-level files (memory/, MEMORY.md, projects-index, etc.) are shared
|
|
9681
|
+
context \u2014 you may read them for general knowledge and learnings, but they
|
|
9682
|
+
do not define this topic's current work, goals, or identity.
|
|
9661
9683
|
- If the user mentions another topic by name or slug, ask for explicit
|
|
9662
9684
|
confirmation before mixing work: "This references topic X \u2014 switch context?"
|
|
9663
9685
|
- Never copy data between topic folders without explicit user instruction.
|
|
@@ -9973,7 +9995,7 @@ async function handleStatus(ctx) {
|
|
|
9973
9995
|
}
|
|
9974
9996
|
|
|
9975
9997
|
// src/commands/init.ts
|
|
9976
|
-
var VALID_TYPES = /* @__PURE__ */ new Set(["coding", "research", "marketing", "
|
|
9998
|
+
var VALID_TYPES = /* @__PURE__ */ new Set(["coding", "research", "marketing", "general"]);
|
|
9977
9999
|
function deriveTopicName(nameArg, messageContext, threadId) {
|
|
9978
10000
|
const topicTitle = messageContext?.["topicTitle"] ?? "";
|
|
9979
10001
|
let name;
|
|
@@ -10120,13 +10142,12 @@ Warning: config sync failed: ${msg}`;
|
|
|
10120
10142
|
try {
|
|
10121
10143
|
const htmlCard = buildTopicCardHtml(name, topicType);
|
|
10122
10144
|
await ctx.postFn(groupId, threadId, htmlCard);
|
|
10123
|
-
return { text: ""
|
|
10145
|
+
return { text: "" };
|
|
10124
10146
|
} catch {
|
|
10125
10147
|
}
|
|
10126
10148
|
}
|
|
10127
10149
|
return {
|
|
10128
|
-
text: `${topicCard}${restartMsg}
|
|
10129
|
-
pin: true
|
|
10150
|
+
text: `${topicCard}${restartMsg}`
|
|
10130
10151
|
};
|
|
10131
10152
|
}
|
|
10132
10153
|
async function handleInitInteractive(ctx, args) {
|
|
@@ -10740,14 +10761,14 @@ function readFileOrNull(filePath) {
|
|
|
10740
10761
|
}
|
|
10741
10762
|
}
|
|
10742
10763
|
function extractDoneSection(statusContent) {
|
|
10743
|
-
if (!statusContent) return "
|
|
10764
|
+
if (!statusContent) return "No status available yet.";
|
|
10744
10765
|
const match = statusContent.match(/^##\s*Last done\s*\(UTC\)\s*\n([\s\S]*?)(?=\n##\s|\n*$)/im);
|
|
10745
|
-
if (!match) return
|
|
10766
|
+
if (!match) return "No recent activity found.";
|
|
10746
10767
|
const text = match[1]?.trim();
|
|
10747
|
-
return text || "
|
|
10768
|
+
return text || "Empty.";
|
|
10748
10769
|
}
|
|
10749
10770
|
function extractTodayLearnings(learningsContent) {
|
|
10750
|
-
if (!learningsContent) return "
|
|
10771
|
+
if (!learningsContent) return "No learnings recorded yet.";
|
|
10751
10772
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10752
10773
|
const lines = learningsContent.split("\n");
|
|
10753
10774
|
const todayLines = [];
|
|
@@ -10764,32 +10785,32 @@ function extractTodayLearnings(learningsContent) {
|
|
|
10764
10785
|
todayLines.push(line);
|
|
10765
10786
|
}
|
|
10766
10787
|
}
|
|
10767
|
-
return todayLines.length > 0 ? todayLines.join("\n") : "
|
|
10788
|
+
return todayLines.length > 0 ? todayLines.join("\n") : "None today.";
|
|
10768
10789
|
}
|
|
10769
10790
|
function extractBlockers(todoContent) {
|
|
10770
|
-
if (!todoContent) return "
|
|
10791
|
+
if (!todoContent) return "No tasks recorded yet.";
|
|
10771
10792
|
const lines = todoContent.split("\n");
|
|
10772
10793
|
const blockerLines = lines.filter(
|
|
10773
10794
|
(l) => /\[BLOCKED\]/i.test(l) || /\bblocked\b/i.test(l)
|
|
10774
10795
|
);
|
|
10775
|
-
return blockerLines.length > 0 ? blockerLines.join("\n") : "
|
|
10796
|
+
return blockerLines.length > 0 ? blockerLines.join("\n") : "None.";
|
|
10776
10797
|
}
|
|
10777
10798
|
function extractNextActions(statusContent) {
|
|
10778
|
-
if (!statusContent) return "
|
|
10799
|
+
if (!statusContent) return "No status available yet.";
|
|
10779
10800
|
const match = statusContent.match(/^##\s*Next (?:3 )?actions(?: \(now\))?\s*\n([\s\S]*?)(?=\n##\s|\n*$)/im);
|
|
10780
|
-
if (!match) return
|
|
10801
|
+
if (!match) return "None yet.";
|
|
10781
10802
|
const text = match[1]?.trim();
|
|
10782
|
-
return text || "
|
|
10803
|
+
return text || "None yet.";
|
|
10783
10804
|
}
|
|
10784
10805
|
function extractUpcoming(statusContent) {
|
|
10785
|
-
if (!statusContent) return "
|
|
10806
|
+
if (!statusContent) return "No status available yet.";
|
|
10786
10807
|
const match = statusContent.match(/^##\s*Upcoming actions\s*\n([\s\S]*?)(?=\n##\s|\n*$)/im);
|
|
10787
|
-
if (!match) return
|
|
10808
|
+
if (!match) return "None yet.";
|
|
10788
10809
|
const text = match[1]?.trim();
|
|
10789
|
-
return text || "
|
|
10810
|
+
return text || "None yet.";
|
|
10790
10811
|
}
|
|
10791
10812
|
function computeHealth(lastMessageAt, statusContent, blockers) {
|
|
10792
|
-
if (blockers && blockers !== "
|
|
10813
|
+
if (blockers && blockers !== "None." && blockers !== "No tasks recorded yet.") {
|
|
10793
10814
|
return "blocked";
|
|
10794
10815
|
}
|
|
10795
10816
|
if (!lastMessageAt) return "stale";
|
|
@@ -10799,6 +10820,132 @@ function computeHealth(lastMessageAt, statusContent, blockers) {
|
|
|
10799
10820
|
}
|
|
10800
10821
|
|
|
10801
10822
|
// src/commands/doctor-all.ts
|
|
10823
|
+
function getEligibility(entry, now, statusTimestamp) {
|
|
10824
|
+
if (entry.status === "archived") return { eligible: false, skipReason: "archived" };
|
|
10825
|
+
if (entry.snoozeUntil && new Date(entry.snoozeUntil).getTime() > now.getTime()) {
|
|
10826
|
+
return { eligible: false, skipReason: "snoozed" };
|
|
10827
|
+
}
|
|
10828
|
+
const lastActive = mostRecent(entry.lastMessageAt, statusTimestamp);
|
|
10829
|
+
if (lastActive) {
|
|
10830
|
+
const lastActiveMs = new Date(lastActive).getTime();
|
|
10831
|
+
const inactiveMs = INACTIVE_AFTER_DAYS * 24 * 60 * 60 * 1e3;
|
|
10832
|
+
if (now.getTime() - lastActiveMs > inactiveMs) {
|
|
10833
|
+
return { eligible: false, skipReason: "inactive" };
|
|
10834
|
+
}
|
|
10835
|
+
}
|
|
10836
|
+
if (entry.lastDoctorReportAt) {
|
|
10837
|
+
const lastReport = new Date(entry.lastDoctorReportAt).getTime();
|
|
10838
|
+
if (now.getTime() - lastReport < DOCTOR_PER_TOPIC_CAP_MS) {
|
|
10839
|
+
return { eligible: false, skipReason: "recently-checked" };
|
|
10840
|
+
}
|
|
10841
|
+
}
|
|
10842
|
+
return { eligible: true };
|
|
10843
|
+
}
|
|
10844
|
+
var SKIP_ICONS = {
|
|
10845
|
+
archived: "\u{1F4E6}",
|
|
10846
|
+
// 📦
|
|
10847
|
+
snoozed: "\u{1F4A4}",
|
|
10848
|
+
// 💤
|
|
10849
|
+
inactive: "\u{1F507}",
|
|
10850
|
+
// 🔇
|
|
10851
|
+
"recently-checked": "\u23F0"
|
|
10852
|
+
// ⏰
|
|
10853
|
+
};
|
|
10854
|
+
var SKIP_LABELS = {
|
|
10855
|
+
archived: "archived",
|
|
10856
|
+
snoozed: "snoozed",
|
|
10857
|
+
inactive: "inactive",
|
|
10858
|
+
"recently-checked": "recently checked"
|
|
10859
|
+
};
|
|
10860
|
+
var SUMMARY_SOFT_LIMIT = 3800;
|
|
10861
|
+
function buildDoctorAllSummary(data) {
|
|
10862
|
+
const {
|
|
10863
|
+
checkedTopics,
|
|
10864
|
+
skippedTopics,
|
|
10865
|
+
postFailures,
|
|
10866
|
+
dailyReportsSent,
|
|
10867
|
+
dailyReportsSkipped,
|
|
10868
|
+
hasPostFn,
|
|
10869
|
+
migrationGroups,
|
|
10870
|
+
errors
|
|
10871
|
+
} = data;
|
|
10872
|
+
if (checkedTopics.length === 0 && skippedTopics.length === 0) {
|
|
10873
|
+
return "**Health Check Summary**\n\nNo topics registered yet.";
|
|
10874
|
+
}
|
|
10875
|
+
const lines = ["**Health Check Summary**", ""];
|
|
10876
|
+
let checkedRendered = 0;
|
|
10877
|
+
for (const t of checkedTopics) {
|
|
10878
|
+
let icon;
|
|
10879
|
+
let label;
|
|
10880
|
+
switch (t.status) {
|
|
10881
|
+
case "checked":
|
|
10882
|
+
icon = "\u2705";
|
|
10883
|
+
label = "checked";
|
|
10884
|
+
break;
|
|
10885
|
+
case "check-failed":
|
|
10886
|
+
icon = "\u274C";
|
|
10887
|
+
label = "check failed";
|
|
10888
|
+
break;
|
|
10889
|
+
case "post-failed":
|
|
10890
|
+
icon = "\u26A0\uFE0F";
|
|
10891
|
+
label = "failed to post";
|
|
10892
|
+
break;
|
|
10893
|
+
}
|
|
10894
|
+
const line = `${icon} ${t.name} \u2014 ${label}`;
|
|
10895
|
+
if (lines.join("\n").length + line.length > SUMMARY_SOFT_LIMIT) {
|
|
10896
|
+
const remaining = checkedTopics.length - checkedRendered;
|
|
10897
|
+
lines.push(`... and ${remaining} more`);
|
|
10898
|
+
break;
|
|
10899
|
+
}
|
|
10900
|
+
lines.push(line);
|
|
10901
|
+
checkedRendered++;
|
|
10902
|
+
}
|
|
10903
|
+
if (skippedTopics.length > 0) {
|
|
10904
|
+
lines.push("");
|
|
10905
|
+
lines.push("\u23ED\uFE0F Skipped:");
|
|
10906
|
+
let skippedRendered = 0;
|
|
10907
|
+
for (const t of skippedTopics) {
|
|
10908
|
+
const icon = SKIP_ICONS[t.reason];
|
|
10909
|
+
const label = SKIP_LABELS[t.reason];
|
|
10910
|
+
const line = `${icon} ${t.name} \u2014 ${label}`;
|
|
10911
|
+
if (lines.join("\n").length + line.length > SUMMARY_SOFT_LIMIT) {
|
|
10912
|
+
const remaining = skippedTopics.length - skippedRendered;
|
|
10913
|
+
lines.push(`... and ${remaining} more`);
|
|
10914
|
+
break;
|
|
10915
|
+
}
|
|
10916
|
+
lines.push(line);
|
|
10917
|
+
skippedRendered++;
|
|
10918
|
+
}
|
|
10919
|
+
}
|
|
10920
|
+
if (postFailures > 0) {
|
|
10921
|
+
lines.push("");
|
|
10922
|
+
lines.push(`\u26A0\uFE0F ${postFailures} topic(s) failed to post`);
|
|
10923
|
+
}
|
|
10924
|
+
if (hasPostFn) {
|
|
10925
|
+
const parts = [];
|
|
10926
|
+
if (dailyReportsSent > 0) parts.push(`${dailyReportsSent} sent`);
|
|
10927
|
+
if (dailyReportsSkipped > 0) parts.push(`${dailyReportsSkipped} skipped`);
|
|
10928
|
+
if (parts.length > 0) {
|
|
10929
|
+
lines.push("");
|
|
10930
|
+
lines.push(`Daily reports: ${parts.join(", ")}`);
|
|
10931
|
+
}
|
|
10932
|
+
}
|
|
10933
|
+
if (migrationGroups > 0) {
|
|
10934
|
+
lines.push("");
|
|
10935
|
+
lines.push(`**Warning:** ${migrationGroups} group(s) had all topics fail. The group may have been migrated or deleted.`);
|
|
10936
|
+
}
|
|
10937
|
+
if (errors.length > 0) {
|
|
10938
|
+
lines.push("");
|
|
10939
|
+
lines.push(`**Errors (${errors.length}):**`);
|
|
10940
|
+
for (const e of errors.slice(0, 10)) {
|
|
10941
|
+
lines.push(`- ${e}`);
|
|
10942
|
+
}
|
|
10943
|
+
if (errors.length > 10) {
|
|
10944
|
+
lines.push(`... and ${errors.length - 10} more`);
|
|
10945
|
+
}
|
|
10946
|
+
}
|
|
10947
|
+
return truncateMessage(lines.join("\n"));
|
|
10948
|
+
}
|
|
10802
10949
|
async function handleDoctorAll(ctx) {
|
|
10803
10950
|
const { workspaceDir, configDir, logger } = ctx;
|
|
10804
10951
|
const registry = readRegistry(workspaceDir);
|
|
@@ -10834,12 +10981,16 @@ async function handleDoctorAll(ctx) {
|
|
|
10834
10981
|
const allEntries = Object.entries(registry.topics);
|
|
10835
10982
|
const reports = [];
|
|
10836
10983
|
const errors = [];
|
|
10837
|
-
|
|
10838
|
-
|
|
10984
|
+
const checkedTopics = [];
|
|
10985
|
+
const skippedTopics = [];
|
|
10839
10986
|
const groupPostResults = /* @__PURE__ */ new Map();
|
|
10840
10987
|
for (const [_key, entry] of allEntries) {
|
|
10841
|
-
|
|
10842
|
-
|
|
10988
|
+
const capsuleDir = path11.join(projectsBase, entry.slug);
|
|
10989
|
+
const statusForEligibility = readFileOrNull(path11.join(capsuleDir, "STATUS.md"));
|
|
10990
|
+
const statusTs = statusForEligibility ? extractStatusTimestamp(statusForEligibility) : null;
|
|
10991
|
+
const eligibility = getEligibility(entry, now, statusTs);
|
|
10992
|
+
if (!eligibility.eligible) {
|
|
10993
|
+
skippedTopics.push({ name: entry.name, reason: eligibility.skipReason });
|
|
10843
10994
|
continue;
|
|
10844
10995
|
}
|
|
10845
10996
|
try {
|
|
@@ -10875,11 +11026,12 @@ async function handleDoctorAll(ctx) {
|
|
|
10875
11026
|
}
|
|
10876
11027
|
const group = groupPostResults.get(gk);
|
|
10877
11028
|
group.total++;
|
|
10878
|
-
|
|
11029
|
+
checkedTopics.push({ name: entry.name, slug: entry.slug, status: "checked" });
|
|
10879
11030
|
} catch (err) {
|
|
10880
11031
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10881
11032
|
errors.push(`${entry.slug}: ${msg}`);
|
|
10882
11033
|
logger.error(`[doctor-all] Error processing ${entry.slug}: ${msg}`);
|
|
11034
|
+
checkedTopics.push({ name: entry.name, slug: entry.slug, status: "check-failed" });
|
|
10883
11035
|
const gk = entry.groupId;
|
|
10884
11036
|
if (!groupPostResults.has(gk)) {
|
|
10885
11037
|
groupPostResults.set(gk, { total: 0, failed: 0 });
|
|
@@ -10895,14 +11047,13 @@ async function handleDoctorAll(ctx) {
|
|
|
10895
11047
|
migrationGroups.push(gid);
|
|
10896
11048
|
}
|
|
10897
11049
|
}
|
|
10898
|
-
let
|
|
10899
|
-
|
|
11050
|
+
let postFailures = 0;
|
|
11051
|
+
const postFailedSlugs = /* @__PURE__ */ new Set();
|
|
10900
11052
|
if (ctx.postFn && reports.length > 0) {
|
|
10901
11053
|
const rateLimitedPost = createRateLimitedPoster(ctx.postFn);
|
|
10902
11054
|
for (const report of reports) {
|
|
10903
11055
|
try {
|
|
10904
11056
|
await rateLimitedPost(report.groupId, report.threadId, report.text, report.keyboard);
|
|
10905
|
-
postSuccesses++;
|
|
10906
11057
|
await withRegistry(workspaceDir, (data) => {
|
|
10907
11058
|
const key = `${report.groupId}:${report.threadId}`;
|
|
10908
11059
|
const entry = data.topics[key];
|
|
@@ -10912,7 +11063,8 @@ async function handleDoctorAll(ctx) {
|
|
|
10912
11063
|
}
|
|
10913
11064
|
});
|
|
10914
11065
|
} catch (err) {
|
|
10915
|
-
|
|
11066
|
+
postFailures++;
|
|
11067
|
+
postFailedSlugs.add(report.slug);
|
|
10916
11068
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10917
11069
|
logger.error(`[doctor-all] Post failed for ${report.slug}: ${msg}`);
|
|
10918
11070
|
await withRegistry(workspaceDir, (data) => {
|
|
@@ -10924,6 +11076,11 @@ async function handleDoctorAll(ctx) {
|
|
|
10924
11076
|
});
|
|
10925
11077
|
}
|
|
10926
11078
|
}
|
|
11079
|
+
for (const outcome of checkedTopics) {
|
|
11080
|
+
if (postFailedSlugs.has(outcome.slug) && outcome.status === "checked") {
|
|
11081
|
+
outcome.status = "post-failed";
|
|
11082
|
+
}
|
|
11083
|
+
}
|
|
10927
11084
|
}
|
|
10928
11085
|
let dailyReportSuccesses = 0;
|
|
10929
11086
|
let dailyReportSkipped = 0;
|
|
@@ -10976,7 +11133,10 @@ async function handleDoctorAll(ctx) {
|
|
|
10976
11133
|
await withRegistry(workspaceDir, (data) => {
|
|
10977
11134
|
data.lastDoctorAllRunAt = now.toISOString();
|
|
10978
11135
|
for (const [_key, entry] of Object.entries(data.topics)) {
|
|
10979
|
-
|
|
11136
|
+
const dir = path11.join(projectsBase, entry.slug);
|
|
11137
|
+
const statusFile = readFileOrNull(path11.join(dir, "STATUS.md"));
|
|
11138
|
+
const ts = statusFile ? extractStatusTimestamp(statusFile) : null;
|
|
11139
|
+
if (!isEligible(entry, now, ts)) continue;
|
|
10980
11140
|
if (entry.lastMessageAt) {
|
|
10981
11141
|
const lastMsg = new Date(entry.lastMessageAt).getTime();
|
|
10982
11142
|
const lastDoctor = entry.lastDoctorRunAt ? new Date(entry.lastDoctorRunAt).getTime() : 0;
|
|
@@ -11002,42 +11162,44 @@ async function handleDoctorAll(ctx) {
|
|
|
11002
11162
|
}
|
|
11003
11163
|
}
|
|
11004
11164
|
});
|
|
11005
|
-
const lines = [
|
|
11006
|
-
`**Health Check Summary**`,
|
|
11007
|
-
"",
|
|
11008
|
-
`Checked: ${processed}`,
|
|
11009
|
-
`Skipped: ${skipped}`,
|
|
11010
|
-
`Total topics: ${allEntries.length}`
|
|
11011
|
-
];
|
|
11012
|
-
if (ctx.postFn) {
|
|
11013
|
-
lines.push(`Posted: ${postSuccesses}, Post failures: ${postErrors}`);
|
|
11014
|
-
lines.push(`Daily reports: ${dailyReportSuccesses} sent, ${dailyReportSkipped} skipped`);
|
|
11015
|
-
}
|
|
11016
|
-
if (errors.length > 0) {
|
|
11017
|
-
lines.push("");
|
|
11018
|
-
lines.push(`**Errors (${errors.length}):**`);
|
|
11019
|
-
for (const e of errors.slice(0, 10)) {
|
|
11020
|
-
lines.push(`- ${e}`);
|
|
11021
|
-
}
|
|
11022
|
-
if (errors.length > 10) {
|
|
11023
|
-
lines.push(`... and ${errors.length - 10} more`);
|
|
11024
|
-
}
|
|
11025
|
-
}
|
|
11026
|
-
if (migrationGroups.length > 0) {
|
|
11027
|
-
lines.push("");
|
|
11028
|
-
lines.push(`**Warning:** ${migrationGroups.length} group(s) had all topics fail. The group may have been migrated or deleted.`);
|
|
11029
|
-
}
|
|
11030
11165
|
return {
|
|
11031
|
-
text:
|
|
11166
|
+
text: buildDoctorAllSummary({
|
|
11167
|
+
checkedTopics,
|
|
11168
|
+
skippedTopics,
|
|
11169
|
+
postFailures,
|
|
11170
|
+
dailyReportsSent: dailyReportSuccesses,
|
|
11171
|
+
dailyReportsSkipped: dailyReportSkipped,
|
|
11172
|
+
hasPostFn: !!ctx.postFn,
|
|
11173
|
+
migrationGroups: migrationGroups.length,
|
|
11174
|
+
errors
|
|
11175
|
+
})
|
|
11032
11176
|
};
|
|
11033
11177
|
}
|
|
11034
|
-
|
|
11178
|
+
var STATUS_TIMESTAMP_RE = /^##\s*Last done\s*\(UTC\)/im;
|
|
11179
|
+
var ISO_TIMESTAMP_RE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
11180
|
+
function extractStatusTimestamp(content) {
|
|
11181
|
+
if (!STATUS_TIMESTAMP_RE.test(content)) return null;
|
|
11182
|
+
const idx = content.search(STATUS_TIMESTAMP_RE);
|
|
11183
|
+
const sectionAfter = content.slice(idx);
|
|
11184
|
+
const nextSection = sectionAfter.indexOf("\n## ", 1);
|
|
11185
|
+
const section = nextSection > 0 ? sectionAfter.slice(0, nextSection) : sectionAfter;
|
|
11186
|
+
const match = section.match(ISO_TIMESTAMP_RE);
|
|
11187
|
+
return match ? match[0] : null;
|
|
11188
|
+
}
|
|
11189
|
+
function mostRecent(a, b) {
|
|
11190
|
+
if (!a && !b) return null;
|
|
11191
|
+
if (!a) return b;
|
|
11192
|
+
if (!b) return a;
|
|
11193
|
+
return a > b ? a : b;
|
|
11194
|
+
}
|
|
11195
|
+
function isEligible(entry, now, statusTimestamp) {
|
|
11035
11196
|
if (entry.status === "archived") return false;
|
|
11036
11197
|
if (entry.snoozeUntil && new Date(entry.snoozeUntil).getTime() > now.getTime()) return false;
|
|
11037
|
-
|
|
11038
|
-
|
|
11198
|
+
const lastActive = mostRecent(entry.lastMessageAt, statusTimestamp);
|
|
11199
|
+
if (lastActive) {
|
|
11200
|
+
const lastActiveMs = new Date(lastActive).getTime();
|
|
11039
11201
|
const inactiveMs = INACTIVE_AFTER_DAYS * 24 * 60 * 60 * 1e3;
|
|
11040
|
-
if (now.getTime() -
|
|
11202
|
+
if (now.getTime() - lastActiveMs > inactiveMs) return false;
|
|
11041
11203
|
}
|
|
11042
11204
|
if (entry.lastDoctorReportAt) {
|
|
11043
11205
|
const lastReport = new Date(entry.lastDoctorReportAt).getTime();
|
|
@@ -11106,18 +11268,18 @@ function formatStatus(name, content) {
|
|
|
11106
11268
|
""
|
|
11107
11269
|
];
|
|
11108
11270
|
if (timestamp) {
|
|
11109
|
-
lines.push(
|
|
11271
|
+
lines.push(`\u{1F552} **Last activity:** ${relativeTime(timestamp)}`);
|
|
11110
11272
|
}
|
|
11111
11273
|
if (lastDoneBody && !isPlaceholder(lastDoneBody)) {
|
|
11112
11274
|
lines.push(lastDoneBody);
|
|
11113
11275
|
}
|
|
11114
11276
|
lines.push("");
|
|
11115
|
-
lines.push("**Next actions**");
|
|
11277
|
+
lines.push("\u{1F3AF} **Next actions**");
|
|
11116
11278
|
lines.push(formatSection(nextRaw));
|
|
11117
11279
|
const upcomingFormatted = formatSection(upcomingRaw);
|
|
11118
11280
|
if (upcomingFormatted !== "_None yet._") {
|
|
11119
11281
|
lines.push("");
|
|
11120
|
-
lines.push("**Upcoming**");
|
|
11282
|
+
lines.push("\u{1F4C5} **Upcoming**");
|
|
11121
11283
|
lines.push(upcomingFormatted);
|
|
11122
11284
|
}
|
|
11123
11285
|
return lines.join("\n");
|
|
@@ -11561,13 +11723,13 @@ async function handleCallback(data, ctx) {
|
|
|
11561
11723
|
ic: "coding",
|
|
11562
11724
|
ir: "research",
|
|
11563
11725
|
im: "marketing",
|
|
11564
|
-
|
|
11726
|
+
ig: "general"
|
|
11565
11727
|
};
|
|
11566
11728
|
const initConfirmMap = {
|
|
11567
11729
|
yc: "coding",
|
|
11568
11730
|
yr: "research",
|
|
11569
11731
|
ym: "marketing",
|
|
11570
|
-
|
|
11732
|
+
yg: "general"
|
|
11571
11733
|
};
|
|
11572
11734
|
const cbCtx = { ...ctx, groupId: cbGroupId, threadId: cbThreadId, userId: cbUserId };
|
|
11573
11735
|
if (action in initTypeMap) {
|
package/dist/setup.js
CHANGED
|
@@ -1156,7 +1156,7 @@ var PLUGIN_FILES = ["openclaw.plugin.json", "dist/plugin.js", "skills", "package
|
|
|
1156
1156
|
var REQUIRED_PLUGIN_FILES = ["openclaw.plugin.json", "dist/plugin.js"];
|
|
1157
1157
|
var FLUSH_TAG = "[tm]";
|
|
1158
1158
|
var FLUSH_FINGERPRINTS = [FLUSH_TAG, "STATUS.md"];
|
|
1159
|
-
var SETUP_REGISTRY_VERSION =
|
|
1159
|
+
var SETUP_REGISTRY_VERSION = 5;
|
|
1160
1160
|
var MEMORY_FLUSH_INSTRUCTION = `If you are working on a Telegram topic folder (projects/<slug>/), update its STATUS.md with current "Last done (UTC)" and "Next actions (now)" before this context is compacted. ${FLUSH_TAG}`;
|
|
1161
1161
|
var SETUP_MARKER_START = "<!-- TM_AUTOPILOT_START -->";
|
|
1162
1162
|
var SETUP_MARKER_END = "<!-- TM_AUTOPILOT_END -->";
|
|
@@ -1270,8 +1270,10 @@ async function runSetup() {
|
|
|
1270
1270
|
startSpinner("Patching memory flush\u2026");
|
|
1271
1271
|
patchMemoryFlush(configDir);
|
|
1272
1272
|
startSpinner("Preparing workspace\u2026");
|
|
1273
|
-
const
|
|
1273
|
+
const workspaceDir = path.join(configDir, "workspace");
|
|
1274
|
+
const projectsDir = path.join(workspaceDir, "projects");
|
|
1274
1275
|
ensureDir(projectsDir);
|
|
1276
|
+
ensureGitignore(workspaceDir);
|
|
1275
1277
|
initRegistry(projectsDir);
|
|
1276
1278
|
createEmptyInclude(configDir, existingGroups);
|
|
1277
1279
|
ok("Workspace ready");
|
|
@@ -1498,6 +1500,29 @@ function createEmptyInclude(configDir, seedGroups) {
|
|
|
1498
1500
|
].join("\n");
|
|
1499
1501
|
fs.writeFileSync(includePath, content, { mode: 384 });
|
|
1500
1502
|
}
|
|
1503
|
+
var GITIGNORE_ENTRIES = [
|
|
1504
|
+
"projects/topics.json",
|
|
1505
|
+
"projects/audit.jsonl",
|
|
1506
|
+
"projects/*/.tm-backup/"
|
|
1507
|
+
];
|
|
1508
|
+
function ensureGitignore(workspaceDir) {
|
|
1509
|
+
const gitignorePath = path.join(workspaceDir, ".gitignore");
|
|
1510
|
+
let content = "";
|
|
1511
|
+
try {
|
|
1512
|
+
if (fs.existsSync(gitignorePath)) {
|
|
1513
|
+
content = fs.readFileSync(gitignorePath, "utf-8");
|
|
1514
|
+
}
|
|
1515
|
+
} catch {
|
|
1516
|
+
}
|
|
1517
|
+
const lines = content.split("\n");
|
|
1518
|
+
const missing = GITIGNORE_ENTRIES.filter(
|
|
1519
|
+
(entry) => !lines.some((line) => line.trim() === entry)
|
|
1520
|
+
);
|
|
1521
|
+
if (missing.length === 0) return;
|
|
1522
|
+
const block = "\n# telegram-manager (operational files)\n" + missing.join("\n") + "\n";
|
|
1523
|
+
const newContent = content ? content.trimEnd() + "\n" + block : block.trimStart();
|
|
1524
|
+
fs.writeFileSync(gitignorePath, newContent);
|
|
1525
|
+
}
|
|
1501
1526
|
function writeHeartbeat(configDir) {
|
|
1502
1527
|
const heartbeatPath = path.join(configDir, "workspace", "HEARTBEAT.md");
|
|
1503
1528
|
let content = "";
|