polygram 0.39.0 → 0.39.1
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 +2 -0
- package/lib/ops/claude-auth-health.js +52 -0
- package/package.json +1 -1
- package/polygram.js +16 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "polygram",
|
|
4
|
-
"version": "0.39.
|
|
4
|
+
"version": "0.39.1",
|
|
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 plus history (transcript queries) and polygram-send (out-of-turn IPC sends with file-upload validation) skills.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"telegram",
|
package/config.example.json
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"_comment_adminChatId": "Required when allowConfigCommands is true for pairing commands (/pair-code, /pairings, /unpair) to work. These grant cross-chat trust and are gated to the admin chat only.",
|
|
10
10
|
"adminChatId": "123456789",
|
|
11
11
|
"needsToken": false,
|
|
12
|
+
"_comment_claudeCliAuthSource": "native (default) checks the host Claude login before CLI turns. Set external only when the configured session launcher supplies proxy credentials. SDK retains native checks; Codex is unaffected. Requires a verified pinned CLI and an absolute session launcher. Restart after changes; restore native when disabling proxy routing.",
|
|
13
|
+
"claudeCliAuthSource": "native",
|
|
12
14
|
"_comment_stream": "Streaming is always on. `streamMinChars` sets the initial-send debounce (default 30) — short responses below that stay idle until the final result. `streamThrottleMs` sets the edit cadence (default 1000ms, min 250).",
|
|
13
15
|
"streamMinChars": 30,
|
|
14
16
|
"streamThrottleMs": 1000,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { resolveRuntimeDescriptor } = require('../runtime-config');
|
|
5
|
+
|
|
6
|
+
function validateClaudeCliAuthSource(botConfig = {}) {
|
|
7
|
+
const source = botConfig.claudeCliAuthSource;
|
|
8
|
+
if (source !== undefined && source !== 'native' && source !== 'external') {
|
|
9
|
+
throw new TypeError('claudeCliAuthSource must be native or external');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function needsNativeClaudeAuth(botConfig, backend) {
|
|
14
|
+
validateClaudeCliAuthSource(botConfig);
|
|
15
|
+
if (backend === 'codex') return false;
|
|
16
|
+
if (backend === 'sdk') return true;
|
|
17
|
+
if (backend !== 'cli') throw new TypeError('Unknown Claude auth backend');
|
|
18
|
+
return botConfig?.claudeCliAuthSource !== 'external';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function hasNativeClaudeRuntime(config) {
|
|
22
|
+
validateClaudeCliAuthSource(config.bot);
|
|
23
|
+
for (const [chatId, chat] of Object.entries(config.chats || {})) {
|
|
24
|
+
for (const threadId of [null, ...Object.keys(chat.topics || {})]) {
|
|
25
|
+
const { backend } = resolveRuntimeDescriptor({
|
|
26
|
+
config, chatId, threadId, defaultPm: 'sdk',
|
|
27
|
+
});
|
|
28
|
+
if (needsNativeClaudeAuth(config.bot, backend)) return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function assertExternalClaudeCliReady(botConfig, { sessionLauncher, pinnedClaudeBin }) {
|
|
35
|
+
validateClaudeCliAuthSource(botConfig);
|
|
36
|
+
if (botConfig?.claudeCliAuthSource !== 'external') return;
|
|
37
|
+
if (typeof sessionLauncher !== 'string' || !path.isAbsolute(sessionLauncher)) {
|
|
38
|
+
throw new Error('External Claude CLI auth requires an absolute session launcher');
|
|
39
|
+
}
|
|
40
|
+
// Without a pinned binary the process factory falls back to SDK, which does
|
|
41
|
+
// not use the CLI launcher's external credentials.
|
|
42
|
+
if (!pinnedClaudeBin) {
|
|
43
|
+
throw new Error('External Claude CLI auth requires a verified pinned Claude binary');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
validateClaudeCliAuthSource,
|
|
49
|
+
needsNativeClaudeAuth,
|
|
50
|
+
hasNativeClaudeRuntime,
|
|
51
|
+
assertExternalClaudeCliReady,
|
|
52
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.1",
|
|
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
|
@@ -61,6 +61,12 @@ const { filterAttachments, resolveFileCaps, resolveMaxFileOverride, MAX_TOTAL_BY
|
|
|
61
61
|
const { ProcessManager } = require('@shumkov/orchestra');
|
|
62
62
|
const { createProcessFactory, pickBackend } = require('@shumkov/orchestra');
|
|
63
63
|
const { checkClaudeAuthHealth } = require('@shumkov/orchestra').claudeBin;
|
|
64
|
+
const {
|
|
65
|
+
validateClaudeCliAuthSource,
|
|
66
|
+
needsNativeClaudeAuth,
|
|
67
|
+
hasNativeClaudeRuntime,
|
|
68
|
+
assertExternalClaudeCliReady,
|
|
69
|
+
} = require('./lib/ops/claude-auth-health');
|
|
64
70
|
const { extractAssistantText } = require('@shumkov/orchestra');
|
|
65
71
|
// 0.11.0: channels backend tool dispatcher — adapts CliProcess's reply
|
|
66
72
|
// tool callback into polygram's existing chunkText + deliverReplies primitives.
|
|
@@ -1411,7 +1417,7 @@ async function handleMessage(sessionKey, chatId, msg, bot, dispatchContext = nul
|
|
|
1411
1417
|
cmdUser, cmdUserId, label, sendReply,
|
|
1412
1418
|
})) return;
|
|
1413
1419
|
|
|
1414
|
-
// Claude-auth gate: if the CLI login has expired,
|
|
1420
|
+
// Claude-auth gate: if the native CLI login has expired, its turns wedge
|
|
1415
1421
|
// INVISIBLY — the 401 fires inside the subprocess, never reaches the
|
|
1416
1422
|
// classifier, and the turn degrades to a silent "⏱ went quiet" across every
|
|
1417
1423
|
// topic. Refuse up-front with a clear message + a loud log instead of
|
|
@@ -1424,7 +1430,9 @@ async function handleMessage(sessionKey, chatId, msg, bot, dispatchContext = nul
|
|
|
1424
1430
|
// read, and a token can expire between an original dispatch and its later
|
|
1425
1431
|
// redelivery, so skipping it there would silently re-wedge exactly the
|
|
1426
1432
|
// messages most likely to hit an expired-auth window (post-restart backlog).
|
|
1427
|
-
|
|
1433
|
+
// An externally authenticated CLI uses its launcher's credentials instead;
|
|
1434
|
+
// the host login says nothing about that route. SDK retains its native check.
|
|
1435
|
+
if (needsNativeClaudeAuth(config.bot, selectedBackend)) {
|
|
1428
1436
|
let auth;
|
|
1429
1437
|
try {
|
|
1430
1438
|
auth = checkClaudeAuthHealth();
|
|
@@ -3634,6 +3642,7 @@ async function main() {
|
|
|
3634
3642
|
// `= config.bots[BOT_NAME]` silently dropped top-level shared fields and
|
|
3635
3643
|
// orphaned apiRoot (both bots ran on cloud, not the 2GB local server).
|
|
3636
3644
|
config.bot = activeBotConfig(config, BOT_NAME);
|
|
3645
|
+
validateClaudeCliAuthSource(config.bot);
|
|
3637
3646
|
} catch (err) {
|
|
3638
3647
|
console.error(`[fatal] ${err.message}`);
|
|
3639
3648
|
process.exit(2);
|
|
@@ -3929,18 +3938,19 @@ async function main() {
|
|
|
3929
3938
|
|
|
3930
3939
|
// Claude-auth health monitor — a FREE credentials-file check (no API call, no
|
|
3931
3940
|
// model spawn). An expired refresh token makes the CLI 401 silently and wedges
|
|
3932
|
-
//
|
|
3941
|
+
// native CLI turns; the handleMessage gate refuses turns + tells the user,
|
|
3933
3942
|
// and this surfaces the same state loudly in the operator logs at boot + every
|
|
3934
3943
|
// 30 min, warning a few days ahead so re-login happens before it breaks.
|
|
3935
3944
|
const runAuthCheck = (trigger) => {
|
|
3936
3945
|
try {
|
|
3946
|
+
if (!hasNativeClaudeRuntime(config)) return;
|
|
3937
3947
|
const auth = checkClaudeAuthHealth();
|
|
3938
3948
|
const expIso = auth.refreshTokenExpiresAt ? new Date(auth.refreshTokenExpiresAt).toISOString() : 'n/a';
|
|
3939
3949
|
if (auth.state === 'expired') {
|
|
3940
|
-
console.error(`[auth] (${trigger}) Claude login EXPIRED (refresh token ${expIso}) — turns are being refused; re-login on the host.`);
|
|
3950
|
+
console.error(`[auth] (${trigger}) Native Claude login EXPIRED (refresh token ${expIso}) — native-auth turns are being refused; re-login on the host.`);
|
|
3941
3951
|
logEvent('auth-expired', { source: 'monitor', trigger, refresh_token_expires_at: auth.refreshTokenExpiresAt });
|
|
3942
3952
|
} else if (auth.state === 'expiring') {
|
|
3943
|
-
console.warn(`[auth] (${trigger}) Claude login expires in ${auth.daysLeft}d (${expIso}) — re-login soon.`);
|
|
3953
|
+
console.warn(`[auth] (${trigger}) Native Claude login expires in ${auth.daysLeft}d (${expIso}) — re-login soon.`);
|
|
3944
3954
|
logEvent('auth-expiring', { source: 'monitor', trigger, days_left: auth.daysLeft, refresh_token_expires_at: auth.refreshTokenExpiresAt });
|
|
3945
3955
|
} else if (auth.state === 'unknown') {
|
|
3946
3956
|
console.warn(`[auth] (${trigger}) Claude credentials check inconclusive: ${auth.reason}`);
|
|
@@ -4320,6 +4330,7 @@ async function main() {
|
|
|
4320
4330
|
logger: console,
|
|
4321
4331
|
});
|
|
4322
4332
|
const channelsClaudeBin = pinnedClaudeBin;
|
|
4333
|
+
assertExternalClaudeCliReady(config.bot, { sessionLauncher, pinnedClaudeBin });
|
|
4323
4334
|
|
|
4324
4335
|
const orchestraProcessFactory = createProcessFactory({
|
|
4325
4336
|
config,
|