polygram 0.4.8 → 0.4.10
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/.claude-plugin/plugin.json +1 -1
- package/lib/process-manager.js +21 -9
- package/package.json +1 -1
- package/polygram.js +23 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "polygram",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.10",
|
|
5
5
|
"description": "Telegram integration for Claude Code that preserves the OpenClaw per-chat session model. Migration target for OpenClaw users. Multi-bot, multi-chat, per-topic isolation; SQLite transcripts; inline-keyboard approvals. Bundles /polygram:status|logs|pair-code|approvals admin commands and a history skill.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"telegram",
|
package/lib/process-manager.js
CHANGED
|
@@ -54,11 +54,12 @@ class ProcessManager {
|
|
|
54
54
|
db = null,
|
|
55
55
|
logger = console,
|
|
56
56
|
killTimeoutMs = DEFAULT_KILL_TIMEOUT_MS,
|
|
57
|
-
onInit = null,
|
|
58
|
-
onResult = null,
|
|
59
|
-
onClose = null,
|
|
60
|
-
onStreamChunk = null
|
|
61
|
-
onToolUse = null,
|
|
57
|
+
onInit = null, // (sessionKey, event, entry) → void
|
|
58
|
+
onResult = null, // (sessionKey, event, entry, pending) → void
|
|
59
|
+
onClose = null, // (sessionKey, code, entry) → void
|
|
60
|
+
onStreamChunk = null, // (sessionKey, partialText, entry) → void — routes to pendingQueue[0]
|
|
61
|
+
onToolUse = null, // (sessionKey, toolName, entry) → void — routes to pendingQueue[0]
|
|
62
|
+
onRespawn = null, // (sessionKey, reason, entry) → void — fires after graceful drain-and-kill
|
|
62
63
|
} = {}) {
|
|
63
64
|
if (!spawnFn) throw new Error('spawnFn required');
|
|
64
65
|
this.cap = cap;
|
|
@@ -71,6 +72,7 @@ class ProcessManager {
|
|
|
71
72
|
this.onClose = onClose;
|
|
72
73
|
this.onStreamChunk = onStreamChunk;
|
|
73
74
|
this.onToolUse = onToolUse;
|
|
75
|
+
this.onRespawn = onRespawn;
|
|
74
76
|
this.procs = new Map();
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -137,13 +139,22 @@ class ProcessManager {
|
|
|
137
139
|
queued: entry.pendingQueue.length,
|
|
138
140
|
});
|
|
139
141
|
if (entry.pendingQueue.length === 0) {
|
|
140
|
-
//
|
|
141
|
-
this.
|
|
142
|
+
// Queue empty — kill immediately, fire onRespawn after close.
|
|
143
|
+
this._killAndNotifyRespawn(sessionKey, reason).catch(() => {});
|
|
142
144
|
return { killed: true, queued: 0 };
|
|
143
145
|
}
|
|
144
146
|
return { killed: false, queued: entry.pendingQueue.length };
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
async _killAndNotifyRespawn(sessionKey, reason) {
|
|
150
|
+
const entry = this.procs.get(sessionKey);
|
|
151
|
+
await this.kill(sessionKey);
|
|
152
|
+
if (this.onRespawn && entry) {
|
|
153
|
+
try { this.onRespawn(sessionKey, reason, entry); }
|
|
154
|
+
catch (err) { this.logger.error(`[pm] onRespawn: ${err.message}`); }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
147
158
|
async kill(sessionKey) {
|
|
148
159
|
const entry = this.procs.get(sessionKey);
|
|
149
160
|
if (!entry) return;
|
|
@@ -257,7 +268,8 @@ class ProcessManager {
|
|
|
257
268
|
} else {
|
|
258
269
|
entry.inFlight = false;
|
|
259
270
|
// Graceful drain-and-respawn: if caller asked for a respawn
|
|
260
|
-
// (e.g. /model change) and we just emptied the queue, kill now
|
|
271
|
+
// (e.g. /model change) and we just emptied the queue, kill now
|
|
272
|
+
// and fire onRespawn so the caller can post confirmation.
|
|
261
273
|
if (entry.needsRespawn) {
|
|
262
274
|
const reason = entry.needsRespawn;
|
|
263
275
|
entry.needsRespawn = null;
|
|
@@ -266,7 +278,7 @@ class ProcessManager {
|
|
|
266
278
|
chat_id: entry.chatId,
|
|
267
279
|
reason,
|
|
268
280
|
});
|
|
269
|
-
this.
|
|
281
|
+
this._killAndNotifyRespawn(sessionKey, reason).catch(() => {});
|
|
270
282
|
}
|
|
271
283
|
}
|
|
272
284
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"description": "Telegram daemon for Claude Code that preserves the OpenClaw per-chat session model. Migration path for OpenClaw users moving to Claude Code.",
|
|
5
5
|
"main": "lib/ipc-client.js",
|
|
6
6
|
"bin": {
|
package/polygram.js
CHANGED
|
@@ -1021,11 +1021,9 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1021
1021
|
old_value: oldModel, new_value: newModel,
|
|
1022
1022
|
user: cmdUser, user_id: cmdUserId, source: 'command',
|
|
1023
1023
|
}), 'log model change');
|
|
1024
|
-
const {
|
|
1024
|
+
const { anyActive } = requestRespawnForChat('model-change');
|
|
1025
1025
|
const ver = MODEL_VERSIONS[newModel] || newModel;
|
|
1026
|
-
const suffix = anyActive
|
|
1027
|
-
? ` (applies after ${queued} in-flight turn${queued === 1 ? '' : 's'} complete${queued === 1 ? 's' : ''})`
|
|
1028
|
-
: '';
|
|
1026
|
+
const suffix = anyActive ? ` — I'll switch when I finish` : '';
|
|
1029
1027
|
await sendReply(`Model → ${newModel} (${ver})${suffix}`);
|
|
1030
1028
|
} else {
|
|
1031
1029
|
await sendReply(`Unknown model. Use: opus, sonnet, haiku`);
|
|
@@ -1043,10 +1041,8 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1043
1041
|
old_value: oldEffort, new_value: newEffort,
|
|
1044
1042
|
user: cmdUser, user_id: cmdUserId, source: 'command',
|
|
1045
1043
|
}), 'log effort change');
|
|
1046
|
-
const {
|
|
1047
|
-
const suffix = anyActive
|
|
1048
|
-
? ` (applies after ${queued} in-flight turn${queued === 1 ? '' : 's'} complete${queued === 1 ? 's' : ''})`
|
|
1049
|
-
: '';
|
|
1044
|
+
const { anyActive } = requestRespawnForChat('effort-change');
|
|
1045
|
+
const suffix = anyActive ? ` — I'll switch when I finish` : '';
|
|
1050
1046
|
await sendReply(`Effort → ${newEffort}${suffix}`);
|
|
1051
1047
|
} else {
|
|
1052
1048
|
await sendReply(`Unknown effort. Use: low, medium, high, xhigh, max`);
|
|
@@ -1908,6 +1904,25 @@ async function main() {
|
|
|
1908
1904
|
const r = head?.context?.reactor;
|
|
1909
1905
|
if (r) r.setState(classifyToolName(toolName));
|
|
1910
1906
|
},
|
|
1907
|
+
// Fires after a graceful /model or /effort drain has actually
|
|
1908
|
+
// swapped to the new settings. Post a confirmation back to the
|
|
1909
|
+
// chat so the user knows the switch happened.
|
|
1910
|
+
onRespawn: (sessionKey, reason, entry) => {
|
|
1911
|
+
const chatId = entry.chatId;
|
|
1912
|
+
if (!chatId) return;
|
|
1913
|
+
const chatConfig = config.chats[chatId];
|
|
1914
|
+
if (!chatConfig) return;
|
|
1915
|
+
const text = reason === 'model-change'
|
|
1916
|
+
? `✓ Using ${chatConfig.model} now.`
|
|
1917
|
+
: reason === 'effort-change'
|
|
1918
|
+
? `✓ Effort is ${chatConfig.effort} now.`
|
|
1919
|
+
: `✓ Ready.`;
|
|
1920
|
+
const threadId = entry.threadId || undefined;
|
|
1921
|
+
tg(bot, 'sendMessage', {
|
|
1922
|
+
chat_id: chatId, text,
|
|
1923
|
+
...(threadId && { message_thread_id: threadId }),
|
|
1924
|
+
}, { source: 'respawn-confirm', botName: BOT_NAME }).catch(() => {});
|
|
1925
|
+
},
|
|
1911
1926
|
});
|
|
1912
1927
|
|
|
1913
1928
|
console.log(`polygram (LRU cap=${cap}, SQLite source of truth)`);
|