@inetafrica/open-claudia 2.6.42 → 2.6.44
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/CHANGELOG.md +3 -0
- package/channels/telegram/adapter.js +47 -15
- package/core/runner.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.6.43
|
|
4
|
+
- **Fix the `🧠 Recall this turn` banner rendering raw HTML.** The recall debug banner (from `/recall on`) is built with `<b>` tags and HTML-escaped names, but its two `send()` calls in `runner.js` omitted the Telegram `parseMode: "HTML"` opts that every normal reply already passes via `telegramHtmlOpts()` — so the tags and `&` entities showed up literally in chat instead of rendering. Both recall sends now pass `telegramHtmlOpts()`, matching the rest of the reply path. Display-only; no change to recall behaviour itself.
|
|
5
|
+
|
|
3
6
|
## v2.6.42
|
|
4
7
|
- **Read-time `📖 Recalled my notes on …` now fires for direct file reads, not just the CLI.** The read-side recall banner — and the Hebbian co-use signal that reinforces the recall graph — was wired only to shell commands, watching for `open-claudia pack show <dir>` / `entity show <slug>`. When the agent opened one of its own notes by reading the raw `…/packs/<dir>/PACK.md` or `…/entities/<slug>.md` straight through the Read tool, the detector never saw it: no banner, and the open never counted as co-use. A new `core/recall/read-signal.js` resolves a file path to its memory node (mirroring the existing Write/Edit path-detection), and `runner.js` calls it on Read tool-uses across the Claude and Cursor backends — deduped against the CLI path via the shared notify key, so reading the same node both ways still announces once. Adds `test-read-signal.js`.
|
|
5
8
|
|
|
@@ -19,11 +19,16 @@ class TelegramAdapter {
|
|
|
19
19
|
this.token = token;
|
|
20
20
|
this.ownerChatId = ownerChatId;
|
|
21
21
|
this.chatIds = chatIds || [];
|
|
22
|
+
// Own the HTTP(S) agent so we can drop dead keep-alive sockets on reconnect.
|
|
23
|
+
this._agent = new https.Agent({ keepAlive: true });
|
|
22
24
|
this.bot = new TelegramBot(token, {
|
|
23
25
|
polling: { autoStart: false, params: { timeout: 30 } },
|
|
26
|
+
request: { agent: this._agent },
|
|
24
27
|
});
|
|
25
28
|
this._listeners = { message: new Set(), action: new Set() };
|
|
26
29
|
this._reconnectTimer = null;
|
|
30
|
+
this._recoveryTimer = null;
|
|
31
|
+
this._reconnectAttempts = 0;
|
|
27
32
|
this._wireInbound();
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -45,9 +50,49 @@ class TelegramAdapter {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
async stop() {
|
|
53
|
+
if (this._reconnectTimer) { clearTimeout(this._reconnectTimer); this._reconnectTimer = null; }
|
|
54
|
+
if (this._recoveryTimer) { clearTimeout(this._recoveryTimer); this._recoveryTimer = null; }
|
|
48
55
|
try { await this.bot.stopPolling(); } catch (e) {}
|
|
49
56
|
}
|
|
50
57
|
|
|
58
|
+
// Recover from a wedged poll loop. The library reuses one keep-alive socket;
|
|
59
|
+
// after a sleep/wake or network blip that socket dies and every poll then
|
|
60
|
+
// ETIMEDOUTs forever — restarting polling alone reuses the same dead socket.
|
|
61
|
+
// So we destroy the agent (forcing a fresh connection), back off, and if we
|
|
62
|
+
// still can't recover after several tries we exit so launchd (KeepAlive)
|
|
63
|
+
// relaunches a fully clean process.
|
|
64
|
+
_scheduleReconnect() {
|
|
65
|
+
if (this._reconnectTimer) return; // one cycle in flight; swallow the flood
|
|
66
|
+
if (this._recoveryTimer) { clearTimeout(this._recoveryTimer); this._recoveryTimer = null; }
|
|
67
|
+
this._reconnectAttempts += 1;
|
|
68
|
+
const MAX = 6;
|
|
69
|
+
if (this._reconnectAttempts > MAX) {
|
|
70
|
+
console.error(`Polling unrecoverable after ${MAX} attempts — exiting for supervisor restart.`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const delay = Math.min(30000, 1000 * 2 ** this._reconnectAttempts); // 2s,4s,8s,16s,30s,30s
|
|
74
|
+
console.log(`Network lost. Reconnecting in ${Math.round(delay / 1000)}s (attempt ${this._reconnectAttempts}/${MAX})...`);
|
|
75
|
+
this._reconnectTimer = setTimeout(async () => {
|
|
76
|
+
this._reconnectTimer = null;
|
|
77
|
+
try {
|
|
78
|
+
try { await this.bot.stopPolling(); } catch (e) {}
|
|
79
|
+
try { this._agent.destroy(); } catch (e) {} // drop the dead pooled socket
|
|
80
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
81
|
+
await this.bot.startPolling();
|
|
82
|
+
console.log("Polling restarted.");
|
|
83
|
+
// No fresh error within 30s ⇒ genuinely recovered; reset the counter.
|
|
84
|
+
this._recoveryTimer = setTimeout(() => {
|
|
85
|
+
this._recoveryTimer = null;
|
|
86
|
+
if (this._reconnectAttempts) console.log("Connection healthy again.");
|
|
87
|
+
this._reconnectAttempts = 0;
|
|
88
|
+
}, 30000);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error("Reconnect attempt failed:", e.message);
|
|
91
|
+
this._scheduleReconnect(); // back off and retry; don't die on first miss
|
|
92
|
+
}
|
|
93
|
+
}, delay);
|
|
94
|
+
}
|
|
95
|
+
|
|
51
96
|
_wireInbound() {
|
|
52
97
|
this.bot.on("polling_error", (err) => {
|
|
53
98
|
const msg = err.message || "";
|
|
@@ -56,21 +101,8 @@ class TelegramAdapter {
|
|
|
56
101
|
console.error("Another instance is polling. Exiting.");
|
|
57
102
|
process.exit(1);
|
|
58
103
|
}
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
console.log("Network lost. Reconnecting in 10s...");
|
|
62
|
-
this._reconnectTimer = setTimeout(async () => {
|
|
63
|
-
this._reconnectTimer = null;
|
|
64
|
-
try {
|
|
65
|
-
await this.bot.stopPolling();
|
|
66
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
67
|
-
await this.bot.startPolling();
|
|
68
|
-
console.log("Reconnected.");
|
|
69
|
-
} catch (e) {
|
|
70
|
-
console.error("Reconnect failed:", e.message);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
}, 10000);
|
|
104
|
+
if (/ETIMEDOUT|ECONNRESET|ENOTFOUND|ENETUNREACH|EAI_AGAIN|EFATAL|socket hang up/i.test(msg)) {
|
|
105
|
+
this._scheduleReconnect();
|
|
74
106
|
}
|
|
75
107
|
});
|
|
76
108
|
|
package/core/runner.js
CHANGED
|
@@ -934,8 +934,8 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
934
934
|
const esc = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
935
935
|
const fmt = (arr, icon) => arr.map((x) => (x.why ? `${icon} <b>${esc(x.name)}</b> — ${esc(x.why)}` : `${icon} <b>${esc(x.name)}</b>`));
|
|
936
936
|
const lines = [...fmt(r.packs || [], "📦"), ...fmt(r.entities || [], "👤")];
|
|
937
|
-
if (lines.length) send(`🧠 <b>Recall this turn</b> (${esc(r.engine)})\n${lines.join("\n")}
|
|
938
|
-
else if (r.gated) send(`🧠 <b>Recall</b> (${esc(r.engine)}): skipped by pre-gate — trivial turn
|
|
937
|
+
if (lines.length) send(`🧠 <b>Recall this turn</b> (${esc(r.engine)})\n${lines.join("\n")}`, telegramHtmlOpts()).catch(() => {});
|
|
938
|
+
else if (r.gated) send(`🧠 <b>Recall</b> (${esc(r.engine)}): skipped by pre-gate — trivial turn.`, telegramHtmlOpts()).catch(() => {});
|
|
939
939
|
}
|
|
940
940
|
} catch (e) { /* best-effort */ }
|
|
941
941
|
// /stop landed during the pre-spawn window (recall/compaction): bail before
|
package/package.json
CHANGED