openclaw-telegram-manager 2.5.7 → 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 +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/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 +231 -80
- package/dist/setup.js +26 -1
- 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,6 +10809,132 @@ 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
10939
|
const { workspaceDir, configDir, logger } = ctx;
|
|
10804
10940
|
const registry = readRegistry(workspaceDir);
|
|
@@ -10834,12 +10970,16 @@ async function handleDoctorAll(ctx) {
|
|
|
10834
10970
|
const allEntries = Object.entries(registry.topics);
|
|
10835
10971
|
const reports = [];
|
|
10836
10972
|
const errors = [];
|
|
10837
|
-
|
|
10838
|
-
|
|
10973
|
+
const checkedTopics = [];
|
|
10974
|
+
const skippedTopics = [];
|
|
10839
10975
|
const groupPostResults = /* @__PURE__ */ new Map();
|
|
10840
10976
|
for (const [_key, entry] of allEntries) {
|
|
10841
|
-
|
|
10842
|
-
|
|
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 });
|
|
10843
10983
|
continue;
|
|
10844
10984
|
}
|
|
10845
10985
|
try {
|
|
@@ -10875,11 +11015,12 @@ async function handleDoctorAll(ctx) {
|
|
|
10875
11015
|
}
|
|
10876
11016
|
const group = groupPostResults.get(gk);
|
|
10877
11017
|
group.total++;
|
|
10878
|
-
|
|
11018
|
+
checkedTopics.push({ name: entry.name, slug: entry.slug, status: "checked" });
|
|
10879
11019
|
} catch (err) {
|
|
10880
11020
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10881
11021
|
errors.push(`${entry.slug}: ${msg}`);
|
|
10882
11022
|
logger.error(`[doctor-all] Error processing ${entry.slug}: ${msg}`);
|
|
11023
|
+
checkedTopics.push({ name: entry.name, slug: entry.slug, status: "check-failed" });
|
|
10883
11024
|
const gk = entry.groupId;
|
|
10884
11025
|
if (!groupPostResults.has(gk)) {
|
|
10885
11026
|
groupPostResults.set(gk, { total: 0, failed: 0 });
|
|
@@ -10895,14 +11036,13 @@ async function handleDoctorAll(ctx) {
|
|
|
10895
11036
|
migrationGroups.push(gid);
|
|
10896
11037
|
}
|
|
10897
11038
|
}
|
|
10898
|
-
let
|
|
10899
|
-
|
|
11039
|
+
let postFailures = 0;
|
|
11040
|
+
const postFailedSlugs = /* @__PURE__ */ new Set();
|
|
10900
11041
|
if (ctx.postFn && reports.length > 0) {
|
|
10901
11042
|
const rateLimitedPost = createRateLimitedPoster(ctx.postFn);
|
|
10902
11043
|
for (const report of reports) {
|
|
10903
11044
|
try {
|
|
10904
11045
|
await rateLimitedPost(report.groupId, report.threadId, report.text, report.keyboard);
|
|
10905
|
-
postSuccesses++;
|
|
10906
11046
|
await withRegistry(workspaceDir, (data) => {
|
|
10907
11047
|
const key = `${report.groupId}:${report.threadId}`;
|
|
10908
11048
|
const entry = data.topics[key];
|
|
@@ -10912,7 +11052,8 @@ async function handleDoctorAll(ctx) {
|
|
|
10912
11052
|
}
|
|
10913
11053
|
});
|
|
10914
11054
|
} catch (err) {
|
|
10915
|
-
|
|
11055
|
+
postFailures++;
|
|
11056
|
+
postFailedSlugs.add(report.slug);
|
|
10916
11057
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10917
11058
|
logger.error(`[doctor-all] Post failed for ${report.slug}: ${msg}`);
|
|
10918
11059
|
await withRegistry(workspaceDir, (data) => {
|
|
@@ -10924,6 +11065,11 @@ async function handleDoctorAll(ctx) {
|
|
|
10924
11065
|
});
|
|
10925
11066
|
}
|
|
10926
11067
|
}
|
|
11068
|
+
for (const outcome of checkedTopics) {
|
|
11069
|
+
if (postFailedSlugs.has(outcome.slug) && outcome.status === "checked") {
|
|
11070
|
+
outcome.status = "post-failed";
|
|
11071
|
+
}
|
|
11072
|
+
}
|
|
10927
11073
|
}
|
|
10928
11074
|
let dailyReportSuccesses = 0;
|
|
10929
11075
|
let dailyReportSkipped = 0;
|
|
@@ -10976,7 +11122,10 @@ async function handleDoctorAll(ctx) {
|
|
|
10976
11122
|
await withRegistry(workspaceDir, (data) => {
|
|
10977
11123
|
data.lastDoctorAllRunAt = now.toISOString();
|
|
10978
11124
|
for (const [_key, entry] of Object.entries(data.topics)) {
|
|
10979
|
-
|
|
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;
|
|
10980
11129
|
if (entry.lastMessageAt) {
|
|
10981
11130
|
const lastMsg = new Date(entry.lastMessageAt).getTime();
|
|
10982
11131
|
const lastDoctor = entry.lastDoctorRunAt ? new Date(entry.lastDoctorRunAt).getTime() : 0;
|
|
@@ -11002,42 +11151,44 @@ async function handleDoctorAll(ctx) {
|
|
|
11002
11151
|
}
|
|
11003
11152
|
}
|
|
11004
11153
|
});
|
|
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
11154
|
return {
|
|
11031
|
-
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
|
+
})
|
|
11032
11165
|
};
|
|
11033
11166
|
}
|
|
11034
|
-
|
|
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) {
|
|
11035
11185
|
if (entry.status === "archived") return false;
|
|
11036
11186
|
if (entry.snoozeUntil && new Date(entry.snoozeUntil).getTime() > now.getTime()) return false;
|
|
11037
|
-
|
|
11038
|
-
|
|
11187
|
+
const lastActive = mostRecent(entry.lastMessageAt, statusTimestamp);
|
|
11188
|
+
if (lastActive) {
|
|
11189
|
+
const lastActiveMs = new Date(lastActive).getTime();
|
|
11039
11190
|
const inactiveMs = INACTIVE_AFTER_DAYS * 24 * 60 * 60 * 1e3;
|
|
11040
|
-
if (now.getTime() -
|
|
11191
|
+
if (now.getTime() - lastActiveMs > inactiveMs) return false;
|
|
11041
11192
|
}
|
|
11042
11193
|
if (entry.lastDoctorReportAt) {
|
|
11043
11194
|
const lastReport = new Date(entry.lastDoctorReportAt).getTime();
|
|
@@ -11106,18 +11257,18 @@ function formatStatus(name, content) {
|
|
|
11106
11257
|
""
|
|
11107
11258
|
];
|
|
11108
11259
|
if (timestamp) {
|
|
11109
|
-
lines.push(
|
|
11260
|
+
lines.push(`\u{1F552} **Last activity:** ${relativeTime(timestamp)}`);
|
|
11110
11261
|
}
|
|
11111
11262
|
if (lastDoneBody && !isPlaceholder(lastDoneBody)) {
|
|
11112
11263
|
lines.push(lastDoneBody);
|
|
11113
11264
|
}
|
|
11114
11265
|
lines.push("");
|
|
11115
|
-
lines.push("**Next actions**");
|
|
11266
|
+
lines.push("\u{1F3AF} **Next actions**");
|
|
11116
11267
|
lines.push(formatSection(nextRaw));
|
|
11117
11268
|
const upcomingFormatted = formatSection(upcomingRaw);
|
|
11118
11269
|
if (upcomingFormatted !== "_None yet._") {
|
|
11119
11270
|
lines.push("");
|
|
11120
|
-
lines.push("**Upcoming**");
|
|
11271
|
+
lines.push("\u{1F4C5} **Upcoming**");
|
|
11121
11272
|
lines.push(upcomingFormatted);
|
|
11122
11273
|
}
|
|
11123
11274
|
return lines.join("\n");
|
|
@@ -11561,13 +11712,13 @@ async function handleCallback(data, ctx) {
|
|
|
11561
11712
|
ic: "coding",
|
|
11562
11713
|
ir: "research",
|
|
11563
11714
|
im: "marketing",
|
|
11564
|
-
|
|
11715
|
+
ig: "general"
|
|
11565
11716
|
};
|
|
11566
11717
|
const initConfirmMap = {
|
|
11567
11718
|
yc: "coding",
|
|
11568
11719
|
yr: "research",
|
|
11569
11720
|
ym: "marketing",
|
|
11570
|
-
|
|
11721
|
+
yg: "general"
|
|
11571
11722
|
};
|
|
11572
11723
|
const cbCtx = { ...ctx, groupId: cbGroupId, threadId: cbThreadId, userId: cbUserId };
|
|
11573
11724
|
if (action in initTypeMap) {
|
package/dist/setup.js
CHANGED
|
@@ -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 = "";
|