polygram 0.3.5 → 0.3.6
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/config.example.json +8 -0
- package/package.json +1 -1
- package/polygram.js +100 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "polygram",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.6",
|
|
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/config.example.json
CHANGED
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
"streamReplies": true,
|
|
12
12
|
"streamMinChars": 30,
|
|
13
13
|
"streamThrottleMs": 500,
|
|
14
|
+
"_comment_pairedChatDefaults": "When a user sends /pair <CODE> from a private chat that isn't in config.chats yet, polygram auto-creates a chat entry using these defaults (merged over top-level `defaults`). Leave out `cwd` at your peril — without it, auto-onboarded DMs have no working directory and pairing will fail.",
|
|
15
|
+
"pairedChatDefaults": {
|
|
16
|
+
"agent": "admin",
|
|
17
|
+
"cwd": "/Users/you/admin-agent",
|
|
18
|
+
"model": "sonnet",
|
|
19
|
+
"effort": "medium",
|
|
20
|
+
"timeout": 600
|
|
21
|
+
},
|
|
14
22
|
"voice": {
|
|
15
23
|
"enabled": true,
|
|
16
24
|
"provider": "openai",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
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
|
@@ -1271,6 +1271,87 @@ function createBot(token) {
|
|
|
1271
1271
|
// not another bot's problem.
|
|
1272
1272
|
const knownChat = (chatId) => !!config.chats[chatId];
|
|
1273
1273
|
|
|
1274
|
+
// Claim a pair code from an unconfigured private chat and persist a new
|
|
1275
|
+
// chat entry so subsequent messages go through the normal flow. Replies
|
|
1276
|
+
// to the user on both success and failure. Returns the new chatConfig on
|
|
1277
|
+
// success, null on any failure.
|
|
1278
|
+
//
|
|
1279
|
+
// The new chat inherits cwd/agent from bot-level pairedChatDefaults if
|
|
1280
|
+
// present, otherwise from the first existing chat the bot owns — on the
|
|
1281
|
+
// reasonable assumption that paired DMs should behave like other DMs for
|
|
1282
|
+
// this bot. Operator can override by setting config.bots.<bot>.pairedChatDefaults.
|
|
1283
|
+
async function onboardPairedChat(ctx, code) {
|
|
1284
|
+
const chatId = ctx.chat.id.toString();
|
|
1285
|
+
const userId = ctx.message.from?.id;
|
|
1286
|
+
const send = (text) => bot.api.sendMessage(chatId, text).catch(() => {});
|
|
1287
|
+
|
|
1288
|
+
if (!userId) {
|
|
1289
|
+
await send('No user id on request.');
|
|
1290
|
+
return null;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
const res = pairings.claimCode({
|
|
1294
|
+
code, claimer_user_id: userId,
|
|
1295
|
+
chat_id: chatId, bot_name: BOT_NAME,
|
|
1296
|
+
});
|
|
1297
|
+
dbWrite(() => db.logEvent('pair-claim-attempt', {
|
|
1298
|
+
bot: BOT_NAME, user_id: userId, chat_id: chatId,
|
|
1299
|
+
ok: res.ok, reason: res.reason, via: 'auto-onboard',
|
|
1300
|
+
}), 'log pair-claim-attempt');
|
|
1301
|
+
|
|
1302
|
+
if (!res.ok) {
|
|
1303
|
+
const reply = res.reason === 'rate-limited'
|
|
1304
|
+
? 'Too many attempts. Try again later.'
|
|
1305
|
+
: 'Invalid or expired code.';
|
|
1306
|
+
await send(reply);
|
|
1307
|
+
return null;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const paired = config.bot?.pairedChatDefaults || {};
|
|
1311
|
+
const globals = config.defaults || {};
|
|
1312
|
+
const firstChat = Object.values(config.chats)[0] || {};
|
|
1313
|
+
const chatName = paired.name
|
|
1314
|
+
|| (ctx.chat.username && `@${ctx.chat.username}`)
|
|
1315
|
+
|| ctx.chat.first_name
|
|
1316
|
+
|| `User ${userId}`;
|
|
1317
|
+
|
|
1318
|
+
const cwd = paired.cwd || firstChat.cwd;
|
|
1319
|
+
if (!cwd) {
|
|
1320
|
+
dbWrite(() => db.logEvent('auto-onboard-failed', {
|
|
1321
|
+
bot: BOT_NAME, chat_id: chatId, user_id: userId,
|
|
1322
|
+
reason: 'no-cwd',
|
|
1323
|
+
}), 'log auto-onboard-failed');
|
|
1324
|
+
await send('Paired, but no working directory is configured. Ask the operator to set pairedChatDefaults.cwd.');
|
|
1325
|
+
return null;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
const newChat = {
|
|
1329
|
+
name: chatName,
|
|
1330
|
+
bot: BOT_NAME,
|
|
1331
|
+
agent: paired.agent || firstChat.agent,
|
|
1332
|
+
model: paired.model || globals.model || 'sonnet',
|
|
1333
|
+
effort: paired.effort || globals.effort || 'medium',
|
|
1334
|
+
cwd,
|
|
1335
|
+
timeout: paired.timeout || globals.timeout || 600,
|
|
1336
|
+
};
|
|
1337
|
+
if (paired.requireMention != null) newChat.requireMention = paired.requireMention;
|
|
1338
|
+
|
|
1339
|
+
config.chats[chatId] = newChat;
|
|
1340
|
+
try { saveConfig(); }
|
|
1341
|
+
catch (err) {
|
|
1342
|
+
console.error(`[${BOT_NAME}] saveConfig on auto-onboard failed: ${err.message}`);
|
|
1343
|
+
}
|
|
1344
|
+
dbWrite(() => db.logEvent('chat-auto-created', {
|
|
1345
|
+
bot: BOT_NAME, chat_id: chatId, user_id: userId,
|
|
1346
|
+
source: 'pair-claim', model: newChat.model, effort: newChat.effort,
|
|
1347
|
+
}), 'log chat-auto-created');
|
|
1348
|
+
|
|
1349
|
+
const chatLabel = res.chat_id ? `chat ${res.chat_id}` : `every chat ${BOT_NAME} is in`;
|
|
1350
|
+
const suffix = res.note ? `\n(${res.note})` : '';
|
|
1351
|
+
await send(`Paired. You can use me in ${chatLabel}.${suffix}`);
|
|
1352
|
+
return newChat;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1274
1355
|
bot.on('message', async (ctx) => {
|
|
1275
1356
|
if (!isWellFormedMessage(ctx.message)) {
|
|
1276
1357
|
dbWrite(() => db.logEvent('malformed-update', {
|
|
@@ -1281,7 +1362,25 @@ function createBot(token) {
|
|
|
1281
1362
|
return;
|
|
1282
1363
|
}
|
|
1283
1364
|
const chatId = ctx.chat.id.toString();
|
|
1284
|
-
|
|
1365
|
+
let chatConfig = config.chats[chatId];
|
|
1366
|
+
|
|
1367
|
+
// Auto-onboarding: /pair <CODE> from an unconfigured private chat.
|
|
1368
|
+
// Without this, the !chatConfig drop below would silently eat pair
|
|
1369
|
+
// claims from DMs the operator hasn't pre-listed — defeating the
|
|
1370
|
+
// whole point of pair codes (which exist to grant access without
|
|
1371
|
+
// pre-configuration). Group chats are not auto-onboarded: they must
|
|
1372
|
+
// still be added to config.json by the operator, because adding a
|
|
1373
|
+
// group can affect multiple users.
|
|
1374
|
+
if (!chatConfig && ctx.chat.type === 'private') {
|
|
1375
|
+
const probe = (ctx.message.text || '').trim();
|
|
1376
|
+
const pairMatch = /^\/pair(?:@\S+)?\s+(\S+)\s*$/.exec(probe);
|
|
1377
|
+
if (pairMatch) {
|
|
1378
|
+
chatConfig = await onboardPairedChat(ctx, pairMatch[1]);
|
|
1379
|
+
if (!chatConfig) return;
|
|
1380
|
+
recordInbound(ctx.message);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1285
1384
|
if (!chatConfig) return;
|
|
1286
1385
|
|
|
1287
1386
|
// Record every inbound msg, even unaddressed ones — needed for reply-to
|