openclaw-telegram-manager 2.5.6 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +195 -44
- 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/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 +4 -5
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +3 -3
- package/dist/lib/types.js.map +1 -1
- package/dist/plugin.js +234 -82
- package/dist/setup.js +29 -3
- 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
|
@@ -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",
|
|
@@ -9392,7 +9392,7 @@ function buildInitTypeButtons(groupId, threadId, secret, userId) {
|
|
|
9392
9392
|
],
|
|
9393
9393
|
[
|
|
9394
9394
|
{ text: "Marketing", callback_data: cb("im") },
|
|
9395
|
-
{ text: "
|
|
9395
|
+
{ text: "General", callback_data: cb("ig") }
|
|
9396
9396
|
]
|
|
9397
9397
|
]);
|
|
9398
9398
|
}
|
|
@@ -9401,7 +9401,7 @@ function buildInitConfirmButton(groupId, threadId, secret, userId, type) {
|
|
|
9401
9401
|
coding: "yc",
|
|
9402
9402
|
research: "yr",
|
|
9403
9403
|
marketing: "ym",
|
|
9404
|
-
|
|
9404
|
+
general: "yg"
|
|
9405
9405
|
};
|
|
9406
9406
|
const cb = buildCallbackData(actionMap[type], groupId, threadId, secret, userId);
|
|
9407
9407
|
return buildInlineKeyboard([[{ text: "Use this name", callback_data: cb }]]);
|
|
@@ -9428,7 +9428,7 @@ function buildInitWelcomeHtml() {
|
|
|
9428
9428
|
"\u2022 <b>Coding</b> \u2014 tracks architecture decisions and deployment steps",
|
|
9429
9429
|
"\u2022 <b>Research</b> \u2014 tracks sources and key findings",
|
|
9430
9430
|
"\u2022 <b>Marketing</b> \u2014 tracks campaigns and metrics",
|
|
9431
|
-
"\u2022 <b>
|
|
9431
|
+
"\u2022 <b>General</b> \u2014 general-purpose tracking",
|
|
9432
9432
|
"",
|
|
9433
9433
|
"<i>The AI may take a few seconds to respond \u2014 no need to tap twice.</i>"
|
|
9434
9434
|
].join("\n");
|
|
@@ -9526,9 +9526,7 @@ function buildHelpCard() {
|
|
|
9526
9526
|
"/tm rename new-name \u2014 rename this topic",
|
|
9527
9527
|
"/tm snooze 7d \u2014 pause health checks (e.g. 7d, 30d)",
|
|
9528
9528
|
"/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"
|
|
9529
|
+
"/tm unarchive \u2014 bring back an archived topic"
|
|
9532
9530
|
].join("\n");
|
|
9533
9531
|
}
|
|
9534
9532
|
function buildListMessage(topics) {
|
|
@@ -9544,8 +9542,8 @@ function buildListMessage(topics) {
|
|
|
9544
9542
|
let rendered = 0;
|
|
9545
9543
|
for (const t of sorted) {
|
|
9546
9544
|
const activity = t.lastMessageAt ? `active ${relativeTime(t.lastMessageAt)}` : "no activity yet";
|
|
9547
|
-
const
|
|
9548
|
-
const entry =
|
|
9545
|
+
const icon = t.status === "snoozed" ? "\u{1F4A4} " : t.status === "archived" ? "\u{1F4E6} " : "";
|
|
9546
|
+
const entry = `${icon}**${t.name}** \xB7 ${t.type}
|
|
9549
9547
|
${activity}`;
|
|
9550
9548
|
const tentative = [...lines, entry, ""].join("\n");
|
|
9551
9549
|
if (tentative.length > TELEGRAM_MSG_LIMIT - 40) {
|
|
@@ -9628,12 +9626,22 @@ var FILE_MODE3 = 384;
|
|
|
9628
9626
|
function getSystemPromptTemplate(name, slug, absoluteWorkspacePath) {
|
|
9629
9627
|
return `You are the assistant for the Telegram topic: ${name}.
|
|
9630
9628
|
|
|
9629
|
+
Context hierarchy (strictly ordered):
|
|
9630
|
+
1. TOPIC FILES (primary) \u2014 your project folder at: ${absoluteWorkspacePath}/projects/${slug}/
|
|
9631
|
+
These define WHAT this topic is working on: current tasks, status, goals, history.
|
|
9632
|
+
When asked "what are we working on?", answer ONLY from these files.
|
|
9633
|
+
2. WORKSPACE MEMORY (secondary) \u2014 files like memory/, MEMORY.md at the workspace root.
|
|
9634
|
+
These contain general learnings, user preferences, and cross-topic knowledge.
|
|
9635
|
+
Use them for HOW to work (patterns, mistakes to avoid, coding style) \u2014 but
|
|
9636
|
+
never to determine what this topic is about or what its current tasks are.
|
|
9637
|
+
Workspace memory may reference projects belonging to OTHER topics \u2014 ignore those.
|
|
9638
|
+
|
|
9631
9639
|
Determinism rules:
|
|
9632
|
-
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9640
|
+
- After /reset, /new, or context compaction: ALWAYS re-read your topic files
|
|
9641
|
+
first \u2014 STATUS.md, then TODO.md, then LEARNINGS.md (last 20 entries), then
|
|
9642
|
+
COMMANDS.md \u2014 before doing anything else. These are your ground truth.
|
|
9643
|
+
Do not rely on summarized memory or workspace-level files for this topic's
|
|
9644
|
+
tasks, status, or goals.
|
|
9637
9645
|
- Before context compaction or when the conversation is long: proactively
|
|
9638
9646
|
flush current progress to STATUS.md (update "Last done (UTC)" and
|
|
9639
9647
|
"Next actions (now)") so compaction cannot erase critical state.
|
|
@@ -9656,8 +9664,11 @@ Learning capture:
|
|
|
9656
9664
|
- If LEARNINGS.md exceeds ~200 lines, archive older entries to LEARNINGS-archive.md.
|
|
9657
9665
|
|
|
9658
9666
|
Separation:
|
|
9659
|
-
- Your
|
|
9660
|
-
|
|
9667
|
+
- Your project folder is strictly projects/${slug}/. This is your identity.
|
|
9668
|
+
- Do not read, write, or reference files in any other topic's project directory.
|
|
9669
|
+
- Workspace-level files (memory/, MEMORY.md, projects-index, etc.) are shared
|
|
9670
|
+
context \u2014 you may read them for general knowledge and learnings, but they
|
|
9671
|
+
do not define this topic's current work, goals, or identity.
|
|
9661
9672
|
- If the user mentions another topic by name or slug, ask for explicit
|
|
9662
9673
|
confirmation before mixing work: "This references topic X \u2014 switch context?"
|
|
9663
9674
|
- Never copy data between topic folders without explicit user instruction.
|
|
@@ -9973,7 +9984,7 @@ async function handleStatus(ctx) {
|
|
|
9973
9984
|
}
|
|
9974
9985
|
|
|
9975
9986
|
// src/commands/init.ts
|
|
9976
|
-
var VALID_TYPES = /* @__PURE__ */ new Set(["coding", "research", "marketing", "
|
|
9987
|
+
var VALID_TYPES = /* @__PURE__ */ new Set(["coding", "research", "marketing", "general"]);
|
|
9977
9988
|
function deriveTopicName(nameArg, messageContext, threadId) {
|
|
9978
9989
|
const topicTitle = messageContext?.["topicTitle"] ?? "";
|
|
9979
9990
|
let name;
|
|
@@ -10120,13 +10131,12 @@ Warning: config sync failed: ${msg}`;
|
|
|
10120
10131
|
try {
|
|
10121
10132
|
const htmlCard = buildTopicCardHtml(name, topicType);
|
|
10122
10133
|
await ctx.postFn(groupId, threadId, htmlCard);
|
|
10123
|
-
return { text: ""
|
|
10134
|
+
return { text: "" };
|
|
10124
10135
|
} catch {
|
|
10125
10136
|
}
|
|
10126
10137
|
}
|
|
10127
10138
|
return {
|
|
10128
|
-
text: `${topicCard}${restartMsg}
|
|
10129
|
-
pin: true
|
|
10139
|
+
text: `${topicCard}${restartMsg}`
|
|
10130
10140
|
};
|
|
10131
10141
|
}
|
|
10132
10142
|
async function handleInitInteractive(ctx, args) {
|
|
@@ -10740,14 +10750,14 @@ function readFileOrNull(filePath) {
|
|
|
10740
10750
|
}
|
|
10741
10751
|
}
|
|
10742
10752
|
function extractDoneSection(statusContent) {
|
|
10743
|
-
if (!statusContent) return "
|
|
10753
|
+
if (!statusContent) return "No status available yet.";
|
|
10744
10754
|
const match = statusContent.match(/^##\s*Last done\s*\(UTC\)\s*\n([\s\S]*?)(?=\n##\s|\n*$)/im);
|
|
10745
|
-
if (!match) return
|
|
10755
|
+
if (!match) return "No recent activity found.";
|
|
10746
10756
|
const text = match[1]?.trim();
|
|
10747
|
-
return text || "
|
|
10757
|
+
return text || "Empty.";
|
|
10748
10758
|
}
|
|
10749
10759
|
function extractTodayLearnings(learningsContent) {
|
|
10750
|
-
if (!learningsContent) return "
|
|
10760
|
+
if (!learningsContent) return "No learnings recorded yet.";
|
|
10751
10761
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10752
10762
|
const lines = learningsContent.split("\n");
|
|
10753
10763
|
const todayLines = [];
|
|
@@ -10764,32 +10774,32 @@ function extractTodayLearnings(learningsContent) {
|
|
|
10764
10774
|
todayLines.push(line);
|
|
10765
10775
|
}
|
|
10766
10776
|
}
|
|
10767
|
-
return todayLines.length > 0 ? todayLines.join("\n") : "
|
|
10777
|
+
return todayLines.length > 0 ? todayLines.join("\n") : "None today.";
|
|
10768
10778
|
}
|
|
10769
10779
|
function extractBlockers(todoContent) {
|
|
10770
|
-
if (!todoContent) return "
|
|
10780
|
+
if (!todoContent) return "No tasks recorded yet.";
|
|
10771
10781
|
const lines = todoContent.split("\n");
|
|
10772
10782
|
const blockerLines = lines.filter(
|
|
10773
10783
|
(l) => /\[BLOCKED\]/i.test(l) || /\bblocked\b/i.test(l)
|
|
10774
10784
|
);
|
|
10775
|
-
return blockerLines.length > 0 ? blockerLines.join("\n") : "
|
|
10785
|
+
return blockerLines.length > 0 ? blockerLines.join("\n") : "None.";
|
|
10776
10786
|
}
|
|
10777
10787
|
function extractNextActions(statusContent) {
|
|
10778
|
-
if (!statusContent) return "
|
|
10788
|
+
if (!statusContent) return "No status available yet.";
|
|
10779
10789
|
const match = statusContent.match(/^##\s*Next (?:3 )?actions(?: \(now\))?\s*\n([\s\S]*?)(?=\n##\s|\n*$)/im);
|
|
10780
|
-
if (!match) return
|
|
10790
|
+
if (!match) return "None yet.";
|
|
10781
10791
|
const text = match[1]?.trim();
|
|
10782
|
-
return text || "
|
|
10792
|
+
return text || "None yet.";
|
|
10783
10793
|
}
|
|
10784
10794
|
function extractUpcoming(statusContent) {
|
|
10785
|
-
if (!statusContent) return "
|
|
10795
|
+
if (!statusContent) return "No status available yet.";
|
|
10786
10796
|
const match = statusContent.match(/^##\s*Upcoming actions\s*\n([\s\S]*?)(?=\n##\s|\n*$)/im);
|
|
10787
|
-
if (!match) return
|
|
10797
|
+
if (!match) return "None yet.";
|
|
10788
10798
|
const text = match[1]?.trim();
|
|
10789
|
-
return text || "
|
|
10799
|
+
return text || "None yet.";
|
|
10790
10800
|
}
|
|
10791
10801
|
function computeHealth(lastMessageAt, statusContent, blockers) {
|
|
10792
|
-
if (blockers && blockers !== "
|
|
10802
|
+
if (blockers && blockers !== "None." && blockers !== "No tasks recorded yet.") {
|
|
10793
10803
|
return "blocked";
|
|
10794
10804
|
}
|
|
10795
10805
|
if (!lastMessageAt) return "stale";
|
|
@@ -10799,12 +10809,139 @@ function computeHealth(lastMessageAt, statusContent, blockers) {
|
|
|
10799
10809
|
}
|
|
10800
10810
|
|
|
10801
10811
|
// src/commands/doctor-all.ts
|
|
10812
|
+
function getEligibility(entry, now, statusTimestamp) {
|
|
10813
|
+
if (entry.status === "archived") return { eligible: false, skipReason: "archived" };
|
|
10814
|
+
if (entry.snoozeUntil && new Date(entry.snoozeUntil).getTime() > now.getTime()) {
|
|
10815
|
+
return { eligible: false, skipReason: "snoozed" };
|
|
10816
|
+
}
|
|
10817
|
+
const lastActive = mostRecent(entry.lastMessageAt, statusTimestamp);
|
|
10818
|
+
if (lastActive) {
|
|
10819
|
+
const lastActiveMs = new Date(lastActive).getTime();
|
|
10820
|
+
const inactiveMs = INACTIVE_AFTER_DAYS * 24 * 60 * 60 * 1e3;
|
|
10821
|
+
if (now.getTime() - lastActiveMs > inactiveMs) {
|
|
10822
|
+
return { eligible: false, skipReason: "inactive" };
|
|
10823
|
+
}
|
|
10824
|
+
}
|
|
10825
|
+
if (entry.lastDoctorReportAt) {
|
|
10826
|
+
const lastReport = new Date(entry.lastDoctorReportAt).getTime();
|
|
10827
|
+
if (now.getTime() - lastReport < DOCTOR_PER_TOPIC_CAP_MS) {
|
|
10828
|
+
return { eligible: false, skipReason: "recently-checked" };
|
|
10829
|
+
}
|
|
10830
|
+
}
|
|
10831
|
+
return { eligible: true };
|
|
10832
|
+
}
|
|
10833
|
+
var SKIP_ICONS = {
|
|
10834
|
+
archived: "\u{1F4E6}",
|
|
10835
|
+
// 📦
|
|
10836
|
+
snoozed: "\u{1F4A4}",
|
|
10837
|
+
// 💤
|
|
10838
|
+
inactive: "\u{1F507}",
|
|
10839
|
+
// 🔇
|
|
10840
|
+
"recently-checked": "\u23F0"
|
|
10841
|
+
// ⏰
|
|
10842
|
+
};
|
|
10843
|
+
var SKIP_LABELS = {
|
|
10844
|
+
archived: "archived",
|
|
10845
|
+
snoozed: "snoozed",
|
|
10846
|
+
inactive: "inactive",
|
|
10847
|
+
"recently-checked": "recently checked"
|
|
10848
|
+
};
|
|
10849
|
+
var SUMMARY_SOFT_LIMIT = 3800;
|
|
10850
|
+
function buildDoctorAllSummary(data) {
|
|
10851
|
+
const {
|
|
10852
|
+
checkedTopics,
|
|
10853
|
+
skippedTopics,
|
|
10854
|
+
postFailures,
|
|
10855
|
+
dailyReportsSent,
|
|
10856
|
+
dailyReportsSkipped,
|
|
10857
|
+
hasPostFn,
|
|
10858
|
+
migrationGroups,
|
|
10859
|
+
errors
|
|
10860
|
+
} = data;
|
|
10861
|
+
if (checkedTopics.length === 0 && skippedTopics.length === 0) {
|
|
10862
|
+
return "**Health Check Summary**\n\nNo topics registered yet.";
|
|
10863
|
+
}
|
|
10864
|
+
const lines = ["**Health Check Summary**", ""];
|
|
10865
|
+
let checkedRendered = 0;
|
|
10866
|
+
for (const t of checkedTopics) {
|
|
10867
|
+
let icon;
|
|
10868
|
+
let label;
|
|
10869
|
+
switch (t.status) {
|
|
10870
|
+
case "checked":
|
|
10871
|
+
icon = "\u2705";
|
|
10872
|
+
label = "checked";
|
|
10873
|
+
break;
|
|
10874
|
+
case "check-failed":
|
|
10875
|
+
icon = "\u274C";
|
|
10876
|
+
label = "check failed";
|
|
10877
|
+
break;
|
|
10878
|
+
case "post-failed":
|
|
10879
|
+
icon = "\u26A0\uFE0F";
|
|
10880
|
+
label = "failed to post";
|
|
10881
|
+
break;
|
|
10882
|
+
}
|
|
10883
|
+
const line = `${icon} ${t.name} \u2014 ${label}`;
|
|
10884
|
+
if (lines.join("\n").length + line.length > SUMMARY_SOFT_LIMIT) {
|
|
10885
|
+
const remaining = checkedTopics.length - checkedRendered;
|
|
10886
|
+
lines.push(`... and ${remaining} more`);
|
|
10887
|
+
break;
|
|
10888
|
+
}
|
|
10889
|
+
lines.push(line);
|
|
10890
|
+
checkedRendered++;
|
|
10891
|
+
}
|
|
10892
|
+
if (skippedTopics.length > 0) {
|
|
10893
|
+
lines.push("");
|
|
10894
|
+
lines.push("\u23ED\uFE0F Skipped:");
|
|
10895
|
+
let skippedRendered = 0;
|
|
10896
|
+
for (const t of skippedTopics) {
|
|
10897
|
+
const icon = SKIP_ICONS[t.reason];
|
|
10898
|
+
const label = SKIP_LABELS[t.reason];
|
|
10899
|
+
const line = `${icon} ${t.name} \u2014 ${label}`;
|
|
10900
|
+
if (lines.join("\n").length + line.length > SUMMARY_SOFT_LIMIT) {
|
|
10901
|
+
const remaining = skippedTopics.length - skippedRendered;
|
|
10902
|
+
lines.push(`... and ${remaining} more`);
|
|
10903
|
+
break;
|
|
10904
|
+
}
|
|
10905
|
+
lines.push(line);
|
|
10906
|
+
skippedRendered++;
|
|
10907
|
+
}
|
|
10908
|
+
}
|
|
10909
|
+
if (postFailures > 0) {
|
|
10910
|
+
lines.push("");
|
|
10911
|
+
lines.push(`\u26A0\uFE0F ${postFailures} topic(s) failed to post`);
|
|
10912
|
+
}
|
|
10913
|
+
if (hasPostFn) {
|
|
10914
|
+
const parts = [];
|
|
10915
|
+
if (dailyReportsSent > 0) parts.push(`${dailyReportsSent} sent`);
|
|
10916
|
+
if (dailyReportsSkipped > 0) parts.push(`${dailyReportsSkipped} skipped`);
|
|
10917
|
+
if (parts.length > 0) {
|
|
10918
|
+
lines.push("");
|
|
10919
|
+
lines.push(`Daily reports: ${parts.join(", ")}`);
|
|
10920
|
+
}
|
|
10921
|
+
}
|
|
10922
|
+
if (migrationGroups > 0) {
|
|
10923
|
+
lines.push("");
|
|
10924
|
+
lines.push(`**Warning:** ${migrationGroups} group(s) had all topics fail. The group may have been migrated or deleted.`);
|
|
10925
|
+
}
|
|
10926
|
+
if (errors.length > 0) {
|
|
10927
|
+
lines.push("");
|
|
10928
|
+
lines.push(`**Errors (${errors.length}):**`);
|
|
10929
|
+
for (const e of errors.slice(0, 10)) {
|
|
10930
|
+
lines.push(`- ${e}`);
|
|
10931
|
+
}
|
|
10932
|
+
if (errors.length > 10) {
|
|
10933
|
+
lines.push(`... and ${errors.length - 10} more`);
|
|
10934
|
+
}
|
|
10935
|
+
}
|
|
10936
|
+
return truncateMessage(lines.join("\n"));
|
|
10937
|
+
}
|
|
10802
10938
|
async function handleDoctorAll(ctx) {
|
|
10803
|
-
const { workspaceDir, configDir,
|
|
10939
|
+
const { workspaceDir, configDir, logger } = ctx;
|
|
10940
|
+
const registry = readRegistry(workspaceDir);
|
|
10941
|
+
const userId = ctx.userId ?? registry.topicManagerAdmins[0];
|
|
10804
10942
|
if (!userId) {
|
|
10805
10943
|
return { text: "Something went wrong \u2014 could not identify your user account." };
|
|
10806
10944
|
}
|
|
10807
|
-
const registry = readRegistry(workspaceDir);
|
|
10808
10945
|
const auth = checkAuthorization(userId, "doctor-all", registry);
|
|
10809
10946
|
if (!auth.authorized) {
|
|
10810
10947
|
return { text: auth.message ?? "Not authorized." };
|
|
@@ -10833,12 +10970,16 @@ async function handleDoctorAll(ctx) {
|
|
|
10833
10970
|
const allEntries = Object.entries(registry.topics);
|
|
10834
10971
|
const reports = [];
|
|
10835
10972
|
const errors = [];
|
|
10836
|
-
|
|
10837
|
-
|
|
10973
|
+
const checkedTopics = [];
|
|
10974
|
+
const skippedTopics = [];
|
|
10838
10975
|
const groupPostResults = /* @__PURE__ */ new Map();
|
|
10839
10976
|
for (const [_key, entry] of allEntries) {
|
|
10840
|
-
|
|
10841
|
-
|
|
10977
|
+
const capsuleDir = path11.join(projectsBase, entry.slug);
|
|
10978
|
+
const statusForEligibility = readFileOrNull(path11.join(capsuleDir, "STATUS.md"));
|
|
10979
|
+
const statusTs = statusForEligibility ? extractStatusTimestamp(statusForEligibility) : null;
|
|
10980
|
+
const eligibility = getEligibility(entry, now, statusTs);
|
|
10981
|
+
if (!eligibility.eligible) {
|
|
10982
|
+
skippedTopics.push({ name: entry.name, reason: eligibility.skipReason });
|
|
10842
10983
|
continue;
|
|
10843
10984
|
}
|
|
10844
10985
|
try {
|
|
@@ -10874,11 +11015,12 @@ async function handleDoctorAll(ctx) {
|
|
|
10874
11015
|
}
|
|
10875
11016
|
const group = groupPostResults.get(gk);
|
|
10876
11017
|
group.total++;
|
|
10877
|
-
|
|
11018
|
+
checkedTopics.push({ name: entry.name, slug: entry.slug, status: "checked" });
|
|
10878
11019
|
} catch (err) {
|
|
10879
11020
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10880
11021
|
errors.push(`${entry.slug}: ${msg}`);
|
|
10881
11022
|
logger.error(`[doctor-all] Error processing ${entry.slug}: ${msg}`);
|
|
11023
|
+
checkedTopics.push({ name: entry.name, slug: entry.slug, status: "check-failed" });
|
|
10882
11024
|
const gk = entry.groupId;
|
|
10883
11025
|
if (!groupPostResults.has(gk)) {
|
|
10884
11026
|
groupPostResults.set(gk, { total: 0, failed: 0 });
|
|
@@ -10894,14 +11036,13 @@ async function handleDoctorAll(ctx) {
|
|
|
10894
11036
|
migrationGroups.push(gid);
|
|
10895
11037
|
}
|
|
10896
11038
|
}
|
|
10897
|
-
let
|
|
10898
|
-
|
|
11039
|
+
let postFailures = 0;
|
|
11040
|
+
const postFailedSlugs = /* @__PURE__ */ new Set();
|
|
10899
11041
|
if (ctx.postFn && reports.length > 0) {
|
|
10900
11042
|
const rateLimitedPost = createRateLimitedPoster(ctx.postFn);
|
|
10901
11043
|
for (const report of reports) {
|
|
10902
11044
|
try {
|
|
10903
11045
|
await rateLimitedPost(report.groupId, report.threadId, report.text, report.keyboard);
|
|
10904
|
-
postSuccesses++;
|
|
10905
11046
|
await withRegistry(workspaceDir, (data) => {
|
|
10906
11047
|
const key = `${report.groupId}:${report.threadId}`;
|
|
10907
11048
|
const entry = data.topics[key];
|
|
@@ -10911,7 +11052,8 @@ async function handleDoctorAll(ctx) {
|
|
|
10911
11052
|
}
|
|
10912
11053
|
});
|
|
10913
11054
|
} catch (err) {
|
|
10914
|
-
|
|
11055
|
+
postFailures++;
|
|
11056
|
+
postFailedSlugs.add(report.slug);
|
|
10915
11057
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10916
11058
|
logger.error(`[doctor-all] Post failed for ${report.slug}: ${msg}`);
|
|
10917
11059
|
await withRegistry(workspaceDir, (data) => {
|
|
@@ -10923,6 +11065,11 @@ async function handleDoctorAll(ctx) {
|
|
|
10923
11065
|
});
|
|
10924
11066
|
}
|
|
10925
11067
|
}
|
|
11068
|
+
for (const outcome of checkedTopics) {
|
|
11069
|
+
if (postFailedSlugs.has(outcome.slug) && outcome.status === "checked") {
|
|
11070
|
+
outcome.status = "post-failed";
|
|
11071
|
+
}
|
|
11072
|
+
}
|
|
10926
11073
|
}
|
|
10927
11074
|
let dailyReportSuccesses = 0;
|
|
10928
11075
|
let dailyReportSkipped = 0;
|
|
@@ -10975,7 +11122,10 @@ async function handleDoctorAll(ctx) {
|
|
|
10975
11122
|
await withRegistry(workspaceDir, (data) => {
|
|
10976
11123
|
data.lastDoctorAllRunAt = now.toISOString();
|
|
10977
11124
|
for (const [_key, entry] of Object.entries(data.topics)) {
|
|
10978
|
-
|
|
11125
|
+
const dir = path11.join(projectsBase, entry.slug);
|
|
11126
|
+
const statusFile = readFileOrNull(path11.join(dir, "STATUS.md"));
|
|
11127
|
+
const ts = statusFile ? extractStatusTimestamp(statusFile) : null;
|
|
11128
|
+
if (!isEligible(entry, now, ts)) continue;
|
|
10979
11129
|
if (entry.lastMessageAt) {
|
|
10980
11130
|
const lastMsg = new Date(entry.lastMessageAt).getTime();
|
|
10981
11131
|
const lastDoctor = entry.lastDoctorRunAt ? new Date(entry.lastDoctorRunAt).getTime() : 0;
|
|
@@ -11001,42 +11151,44 @@ async function handleDoctorAll(ctx) {
|
|
|
11001
11151
|
}
|
|
11002
11152
|
}
|
|
11003
11153
|
});
|
|
11004
|
-
const lines = [
|
|
11005
|
-
`**Health Check Summary**`,
|
|
11006
|
-
"",
|
|
11007
|
-
`Checked: ${processed}`,
|
|
11008
|
-
`Skipped: ${skipped}`,
|
|
11009
|
-
`Total topics: ${allEntries.length}`
|
|
11010
|
-
];
|
|
11011
|
-
if (ctx.postFn) {
|
|
11012
|
-
lines.push(`Posted: ${postSuccesses}, Post failures: ${postErrors}`);
|
|
11013
|
-
lines.push(`Daily reports: ${dailyReportSuccesses} sent, ${dailyReportSkipped} skipped`);
|
|
11014
|
-
}
|
|
11015
|
-
if (errors.length > 0) {
|
|
11016
|
-
lines.push("");
|
|
11017
|
-
lines.push(`**Errors (${errors.length}):**`);
|
|
11018
|
-
for (const e of errors.slice(0, 10)) {
|
|
11019
|
-
lines.push(`- ${e}`);
|
|
11020
|
-
}
|
|
11021
|
-
if (errors.length > 10) {
|
|
11022
|
-
lines.push(`... and ${errors.length - 10} more`);
|
|
11023
|
-
}
|
|
11024
|
-
}
|
|
11025
|
-
if (migrationGroups.length > 0) {
|
|
11026
|
-
lines.push("");
|
|
11027
|
-
lines.push(`**Warning:** ${migrationGroups.length} group(s) had all topics fail. The group may have been migrated or deleted.`);
|
|
11028
|
-
}
|
|
11029
11154
|
return {
|
|
11030
|
-
text:
|
|
11155
|
+
text: buildDoctorAllSummary({
|
|
11156
|
+
checkedTopics,
|
|
11157
|
+
skippedTopics,
|
|
11158
|
+
postFailures,
|
|
11159
|
+
dailyReportsSent: dailyReportSuccesses,
|
|
11160
|
+
dailyReportsSkipped: dailyReportSkipped,
|
|
11161
|
+
hasPostFn: !!ctx.postFn,
|
|
11162
|
+
migrationGroups: migrationGroups.length,
|
|
11163
|
+
errors
|
|
11164
|
+
})
|
|
11031
11165
|
};
|
|
11032
11166
|
}
|
|
11033
|
-
|
|
11167
|
+
var STATUS_TIMESTAMP_RE = /^##\s*Last done\s*\(UTC\)/im;
|
|
11168
|
+
var ISO_TIMESTAMP_RE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
11169
|
+
function extractStatusTimestamp(content) {
|
|
11170
|
+
if (!STATUS_TIMESTAMP_RE.test(content)) return null;
|
|
11171
|
+
const idx = content.search(STATUS_TIMESTAMP_RE);
|
|
11172
|
+
const sectionAfter = content.slice(idx);
|
|
11173
|
+
const nextSection = sectionAfter.indexOf("\n## ", 1);
|
|
11174
|
+
const section = nextSection > 0 ? sectionAfter.slice(0, nextSection) : sectionAfter;
|
|
11175
|
+
const match = section.match(ISO_TIMESTAMP_RE);
|
|
11176
|
+
return match ? match[0] : null;
|
|
11177
|
+
}
|
|
11178
|
+
function mostRecent(a, b) {
|
|
11179
|
+
if (!a && !b) return null;
|
|
11180
|
+
if (!a) return b;
|
|
11181
|
+
if (!b) return a;
|
|
11182
|
+
return a > b ? a : b;
|
|
11183
|
+
}
|
|
11184
|
+
function isEligible(entry, now, statusTimestamp) {
|
|
11034
11185
|
if (entry.status === "archived") return false;
|
|
11035
11186
|
if (entry.snoozeUntil && new Date(entry.snoozeUntil).getTime() > now.getTime()) return false;
|
|
11036
|
-
|
|
11037
|
-
|
|
11187
|
+
const lastActive = mostRecent(entry.lastMessageAt, statusTimestamp);
|
|
11188
|
+
if (lastActive) {
|
|
11189
|
+
const lastActiveMs = new Date(lastActive).getTime();
|
|
11038
11190
|
const inactiveMs = INACTIVE_AFTER_DAYS * 24 * 60 * 60 * 1e3;
|
|
11039
|
-
if (now.getTime() -
|
|
11191
|
+
if (now.getTime() - lastActiveMs > inactiveMs) return false;
|
|
11040
11192
|
}
|
|
11041
11193
|
if (entry.lastDoctorReportAt) {
|
|
11042
11194
|
const lastReport = new Date(entry.lastDoctorReportAt).getTime();
|
|
@@ -11105,18 +11257,18 @@ function formatStatus(name, content) {
|
|
|
11105
11257
|
""
|
|
11106
11258
|
];
|
|
11107
11259
|
if (timestamp) {
|
|
11108
|
-
lines.push(
|
|
11260
|
+
lines.push(`\u{1F552} **Last activity:** ${relativeTime(timestamp)}`);
|
|
11109
11261
|
}
|
|
11110
11262
|
if (lastDoneBody && !isPlaceholder(lastDoneBody)) {
|
|
11111
11263
|
lines.push(lastDoneBody);
|
|
11112
11264
|
}
|
|
11113
11265
|
lines.push("");
|
|
11114
|
-
lines.push("**Next actions**");
|
|
11266
|
+
lines.push("\u{1F3AF} **Next actions**");
|
|
11115
11267
|
lines.push(formatSection(nextRaw));
|
|
11116
11268
|
const upcomingFormatted = formatSection(upcomingRaw);
|
|
11117
11269
|
if (upcomingFormatted !== "_None yet._") {
|
|
11118
11270
|
lines.push("");
|
|
11119
|
-
lines.push("**Upcoming**");
|
|
11271
|
+
lines.push("\u{1F4C5} **Upcoming**");
|
|
11120
11272
|
lines.push(upcomingFormatted);
|
|
11121
11273
|
}
|
|
11122
11274
|
return lines.join("\n");
|
|
@@ -11560,13 +11712,13 @@ async function handleCallback(data, ctx) {
|
|
|
11560
11712
|
ic: "coding",
|
|
11561
11713
|
ir: "research",
|
|
11562
11714
|
im: "marketing",
|
|
11563
|
-
|
|
11715
|
+
ig: "general"
|
|
11564
11716
|
};
|
|
11565
11717
|
const initConfirmMap = {
|
|
11566
11718
|
yc: "coding",
|
|
11567
11719
|
yr: "research",
|
|
11568
11720
|
ym: "marketing",
|
|
11569
|
-
|
|
11721
|
+
yg: "general"
|
|
11570
11722
|
};
|
|
11571
11723
|
const cbCtx = { ...ctx, groupId: cbGroupId, threadId: cbThreadId, userId: cbUserId };
|
|
11572
11724
|
if (action in initTypeMap) {
|
package/dist/setup.js
CHANGED
|
@@ -1145,6 +1145,7 @@ import * as crypto from "node:crypto";
|
|
|
1145
1145
|
import { execSync } from "node:child_process";
|
|
1146
1146
|
import * as readline from "node:readline";
|
|
1147
1147
|
var PLUGIN_NAME = "openclaw-telegram-manager";
|
|
1148
|
+
var PLUGIN_DISPLAY_NAME = "OpenClaw Telegram Manager";
|
|
1148
1149
|
var PLUGIN_VERSION = JSON.parse(
|
|
1149
1150
|
fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
|
|
1150
1151
|
).version;
|
|
@@ -1254,7 +1255,7 @@ if (command === "setup") {
|
|
|
1254
1255
|
process.exit(1);
|
|
1255
1256
|
}
|
|
1256
1257
|
async function runSetup() {
|
|
1257
|
-
banner(
|
|
1258
|
+
banner(PLUGIN_DISPLAY_NAME, `v${PLUGIN_VERSION}`);
|
|
1258
1259
|
startSpinner("Checking OpenClaw version\u2026");
|
|
1259
1260
|
const version = checkOpenClawVersion();
|
|
1260
1261
|
ok(`OpenClaw ${c.dim}${version}${c.reset}`);
|
|
@@ -1269,8 +1270,10 @@ async function runSetup() {
|
|
|
1269
1270
|
startSpinner("Patching memory flush\u2026");
|
|
1270
1271
|
patchMemoryFlush(configDir);
|
|
1271
1272
|
startSpinner("Preparing workspace\u2026");
|
|
1272
|
-
const
|
|
1273
|
+
const workspaceDir = path.join(configDir, "workspace");
|
|
1274
|
+
const projectsDir = path.join(workspaceDir, "projects");
|
|
1273
1275
|
ensureDir(projectsDir);
|
|
1276
|
+
ensureGitignore(workspaceDir);
|
|
1274
1277
|
initRegistry(projectsDir);
|
|
1275
1278
|
createEmptyInclude(configDir, existingGroups);
|
|
1276
1279
|
ok("Workspace ready");
|
|
@@ -1290,7 +1293,7 @@ async function runSetup() {
|
|
|
1290
1293
|
console.log("");
|
|
1291
1294
|
}
|
|
1292
1295
|
async function runUninstall() {
|
|
1293
|
-
banner(
|
|
1296
|
+
banner(PLUGIN_DISPLAY_NAME, "uninstall");
|
|
1294
1297
|
startSpinner("Locating config\u2026");
|
|
1295
1298
|
const configDir = locateConfigDir();
|
|
1296
1299
|
ok(`Config ${c.dim}${configDir}${c.reset}`);
|
|
@@ -1497,6 +1500,29 @@ function createEmptyInclude(configDir, seedGroups) {
|
|
|
1497
1500
|
].join("\n");
|
|
1498
1501
|
fs.writeFileSync(includePath, content, { mode: 384 });
|
|
1499
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
|
+
}
|
|
1500
1526
|
function writeHeartbeat(configDir) {
|
|
1501
1527
|
const heartbeatPath = path.join(configDir, "workspace", "HEARTBEAT.md");
|
|
1502
1528
|
let content = "";
|