polygram 0.8.0-rc.2 → 0.8.0-rc.4

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "polygram",
4
- "version": "0.8.0-rc.2",
4
+ "version": "0.8.0-rc.4",
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.8.0-rc.2",
3
+ "version": "0.8.0-rc.4",
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
@@ -1709,16 +1709,34 @@ async function handleConfigCallback(ctx) {
1709
1709
  user: cmdUser, user_id: cmdUserId, source: 'inline-button',
1710
1710
  }), `log ${setting} change`);
1711
1711
 
1712
- // Graceful respawn of the topic's session that the card is in. With
1712
+ // Graceful application of the change to the topic's session. With
1713
1713
  // isolateTopics=false sessionKey is the chat (one shared session). With
1714
1714
  // isolateTopics=true sessionKey carries the topic, so other topics'
1715
1715
  // in-flight turns are not disturbed and the card update + button toast
1716
- // only affect the user's own context. Mirrors the text-command flow in
1717
- // handleMessage's requestRespawnForSession.
1716
+ // only affect the user's own context.
1717
+ //
1718
+ // CLI pm: requestRespawn drains pending turns then kills the process;
1719
+ // the next user message spawns fresh with the updated chatConfig.
1720
+ // SDK pm: applies live to the running Query via setModel /
1721
+ // applyFlagSettings — no respawn needed, change takes effect for the
1722
+ // rest of the in-flight turn AND all future ones. Falls back to
1723
+ // {killed: false} if neither method is available, leaving the new
1724
+ // chatConfig value to be picked up by the next cold spawn.
1718
1725
  const callbackThreadId = ctx.callbackQuery.message?.message_thread_id?.toString() || null;
1719
1726
  const callbackSessionKey = getSessionKey(chatId, callbackThreadId, chatConfig);
1720
1727
  const reason = setting === 'model' ? 'model-change' : 'effort-change';
1721
- const respawn = pm.requestRespawn(callbackSessionKey, reason);
1728
+ let respawn;
1729
+ if (typeof pm.requestRespawn === 'function') {
1730
+ respawn = pm.requestRespawn(callbackSessionKey, reason);
1731
+ } else if (setting === 'effort' && typeof pm.applyFlagSettings === 'function') {
1732
+ const ok = await pm.applyFlagSettings(callbackSessionKey, { effortLevel: value });
1733
+ respawn = { killed: ok };
1734
+ } else if (setting === 'model' && typeof pm.setModel === 'function') {
1735
+ const ok = await pm.setModel(callbackSessionKey, value);
1736
+ respawn = { killed: ok };
1737
+ } else {
1738
+ respawn = { killed: false };
1739
+ }
1722
1740
  const anyActive = !respawn.killed;
1723
1741
 
1724
1742
  // Re-render the card with updated ✓ + the same help text shown initially.
@@ -1885,13 +1903,18 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
1885
1903
  }
1886
1904
  try {
1887
1905
  const u = await q.getContextUsage();
1888
- const pct = ((u?.percentage ?? 0) * 100).toFixed(0);
1906
+ // SDK returns percentage in 0-100 scale (verified rc.3 prod
1907
+ // — saw "77" for a 77%-used context). Display directly.
1908
+ const pct = (u?.percentage ?? 0).toFixed(0);
1889
1909
  const total = (u?.totalTokens ?? 0).toLocaleString();
1890
1910
  const max = (u?.maxTokens ?? 0).toLocaleString();
1891
1911
  const lines = [`📚 Context: ${total} / ${max} tokens (${pct}%)`];
1892
1912
  if (u?.model) lines.push(`Model: ${u.model}`);
1893
1913
  if (u?.isAutoCompactEnabled && u?.autoCompactThreshold) {
1894
- const thrPct = (u.autoCompactThreshold * 100).toFixed(0);
1914
+ // autoCompactThreshold scale is currently unverified; assume
1915
+ // matches percentage (0-100). If it turns out to be 0-1 we'll
1916
+ // see something like "Auto-compact at 0%" and can flip back.
1917
+ const thrPct = u.autoCompactThreshold.toFixed(0);
1895
1918
  lines.push(`Auto-compact at ${thrPct}%.`);
1896
1919
  }
1897
1920
  // Top-3 categories by token cost so the user knows where the
@@ -1969,17 +1992,33 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
1969
1992
  }
1970
1993
  return;
1971
1994
  }
1972
- // Graceful respawn of the user's CURRENT session only. With
1973
- // isolateTopics=false the sessionKey is just the chat (one shared
1974
- // session for the whole chat — every topic respawns implicitly).
1975
- // With isolateTopics=true each topic is a separate session, and a
1976
- // /model in topic A should NOT disturb topic B's in-flight turn or
1977
- // post a phantom "✓ Using sonnet now" in a topic that didn't ask.
1978
- // Pre-0.6.5 this iterated pm.keys() by chat prefix and incorrectly
1979
- // fanned out across all topics under isolateTopics=true.
1980
- const requestRespawnForSession = (reason) => {
1981
- const res = pm.requestRespawn(sessionKey, reason);
1982
- return { queued: res.queued, anyActive: !res.killed };
1995
+ // Graceful application of a model/effort change to the user's CURRENT
1996
+ // session only. With isolateTopics=false the sessionKey is just the
1997
+ // chat (one shared session for the whole chat — every topic
1998
+ // respawns implicitly). With isolateTopics=true each topic is a
1999
+ // separate session, and a /model in topic A should NOT disturb
2000
+ // topic B's in-flight turn or post a phantom "✓ Using sonnet now"
2001
+ // in a topic that didn't ask.
2002
+ //
2003
+ // CLI pm: requestRespawn drains pending turns then kills the process;
2004
+ // the next user message spawns fresh with the updated chatConfig.
2005
+ // SDK pm: applies live to the running Query via setModel /
2006
+ // applyFlagSettings — no respawn needed, change takes effect for
2007
+ // the rest of the in-flight turn AND all future ones.
2008
+ const applyConfigChange = async (reason, setting, value) => {
2009
+ if (typeof pm.requestRespawn === 'function') {
2010
+ const res = pm.requestRespawn(sessionKey, reason);
2011
+ return { queued: res.queued, anyActive: !res.killed };
2012
+ }
2013
+ if (setting === 'effort' && typeof pm.applyFlagSettings === 'function') {
2014
+ const ok = await pm.applyFlagSettings(sessionKey, { effortLevel: value });
2015
+ return { queued: 0, anyActive: !ok };
2016
+ }
2017
+ if (setting === 'model' && typeof pm.setModel === 'function') {
2018
+ const ok = await pm.setModel(sessionKey, value);
2019
+ return { queued: 0, anyActive: !ok };
2020
+ }
2021
+ return { queued: 0, anyActive: false };
1983
2022
  };
1984
2023
 
1985
2024
  if (botAllowsCommands && text.startsWith('/model ')) {
@@ -1993,7 +2032,7 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
1993
2032
  old_value: oldModel, new_value: newModel,
1994
2033
  user: cmdUser, user_id: cmdUserId, source: 'command',
1995
2034
  }), 'log model change');
1996
- const { anyActive } = requestRespawnForSession('model-change');
2035
+ const { anyActive } = await applyConfigChange('model-change', 'model', newModel);
1997
2036
  const ver = MODEL_VERSIONS[newModel] || newModel;
1998
2037
  const suffix = anyActive ? ` — I'll switch when I finish` : '';
1999
2038
  await sendReply(`Model → ${newModel} (${ver})${suffix}`);
@@ -2013,7 +2052,7 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
2013
2052
  old_value: oldEffort, new_value: newEffort,
2014
2053
  user: cmdUser, user_id: cmdUserId, source: 'command',
2015
2054
  }), 'log effort change');
2016
- const { anyActive } = requestRespawnForSession('effort-change');
2055
+ const { anyActive } = await applyConfigChange('effort-change', 'effort', newEffort);
2017
2056
  const suffix = anyActive ? ` — I'll switch when I finish` : '';
2018
2057
  await sendReply(`Effort → ${newEffort}${suffix}`);
2019
2058
  } else {
@@ -2489,11 +2528,15 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
2489
2528
  const q = entry?.query;
2490
2529
  if (q && typeof q.getContextUsage === 'function') {
2491
2530
  q.getContextUsage().then((usage) => {
2531
+ // SDK returns percentage in 0-100 scale, not 0-1.
2532
+ // Pre-rc.4 we treated it as a 0-1 ratio and multiplied
2533
+ // by 100, which displayed "7700% full" for a 77%-used
2534
+ // context (and fired below the intended 85% threshold).
2492
2535
  const pct = usage?.percentage ?? 0;
2493
- if (pct < 0.85) return;
2536
+ if (pct < 85) return;
2494
2537
  return tg(bot, 'sendMessage', {
2495
2538
  chat_id: chatId,
2496
- text: `📚 Context window ${(pct * 100).toFixed(0)}% full. Send /new to start fresh — older messages will start dropping soon.`,
2539
+ text: `📚 Context window ${pct.toFixed(0)}% full. Send /new to start fresh — older messages will start dropping soon.`,
2497
2540
  ...(threadId ? { message_thread_id: threadId } : {}),
2498
2541
  }, { source: 'context-full-hint', botName: BOT_NAME });
2499
2542
  }).catch((err) => {