polygram 0.12.0-rc.39 → 0.12.0-rc.40

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.
@@ -17,7 +17,7 @@
17
17
  'use strict';
18
18
 
19
19
  const { toTelegramHtml } = require('../telegram/format');
20
- const { getTopicConfig } = require('../session-key');
20
+ const { getTopicConfig, getConfigWriteScope } = require('../session-key');
21
21
 
22
22
  const MODEL_OPTIONS = ['opus', 'sonnet', 'haiku'];
23
23
  const EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
@@ -30,6 +30,7 @@ function createHandleConfigCallback({
30
30
  getSessionKey,
31
31
  formatConfigInfoText,
32
32
  buildConfigKeyboard,
33
+ saveConfig = () => {},
33
34
  botName,
34
35
  logger = console,
35
36
  } = {}) {
@@ -58,17 +59,29 @@ function createHandleConfigCallback({
58
59
  return;
59
60
  }
60
61
 
61
- const oldValue = chatConfig[setting];
62
+ // Write to the scope the card belongs to: a topic card targets THAT topic
63
+ // (so Music ≠ General), a chat-level card the chat root. Resolving the
64
+ // thread BEFORE the already-set check so "Already X" compares against the
65
+ // topic's effective value, not the chat root (2026-06-12 bug).
66
+ const callbackThreadIdEarly = ctx.callbackQuery.message?.message_thread_id?.toString() || null;
67
+ const { scope: writeScope, threadId: writeThreadId } =
68
+ getConfigWriteScope(chatConfig, callbackThreadIdEarly);
69
+ const oldValue = writeScope[setting] != null ? writeScope[setting] : chatConfig[setting];
62
70
  if (oldValue === value) {
63
71
  await ctx.answerCallbackQuery({ text: `Already ${value}` }).catch(() => {});
64
72
  return;
65
73
  }
66
74
 
67
- chatConfig[setting] = value;
75
+ writeScope[setting] = value;
76
+ // Persist to config.json so the change survives a restart — without this,
77
+ // every /model tap was lost on the next deploy (2026-06-12). Best-effort:
78
+ // never let a disk hiccup swallow the in-memory change + live application.
79
+ try { saveConfig(); }
80
+ catch (err) { logger.error?.(`[${botName}] config-callback saveConfig failed: ${err.message}`); }
68
81
  const cmdUserId = ctx.callbackQuery.from?.id || null;
69
82
  const cmdUser = ctx.callbackQuery.from?.first_name || ctx.callbackQuery.from?.username || null;
70
83
  dbWrite(() => db.logConfigChange({
71
- chat_id: chatId, thread_id: null, field: setting,
84
+ chat_id: chatId, thread_id: writeThreadId, field: setting,
72
85
  old_value: oldValue, new_value: value,
73
86
  user: cmdUser, user_id: cmdUserId, source: 'inline-button',
74
87
  }), `log ${setting} change`);
@@ -77,7 +90,7 @@ function createHandleConfigCallback({
77
90
  // via setModel / applyFlagSettings; chatConfig is already updated
78
91
  // on disk above so a missing live session still picks up the new
79
92
  // value on its next cold spawn.
80
- const callbackThreadId = ctx.callbackQuery.message?.message_thread_id?.toString() || null;
93
+ const callbackThreadId = callbackThreadIdEarly;
81
94
  const callbackSessionKey = getSessionKey(chatId, callbackThreadId, chatConfig);
82
95
  let applied = false;
83
96
  if (setting === 'effort') {
@@ -27,6 +27,8 @@
27
27
 
28
28
  'use strict';
29
29
 
30
+ const { getConfigWriteScope } = require('../session-key');
31
+
30
32
  function createSlashCommands({
31
33
  config,
32
34
  db,
@@ -40,6 +42,7 @@ function createSlashCommands({
40
42
  getOrSpawnForChat,
41
43
  parsePairCodeArgs,
42
44
  modelVersionsDesc,
45
+ saveConfig = () => {},
43
46
  botName,
44
47
  logEvent,
45
48
  logger = console,
@@ -214,10 +217,15 @@ function createSlashCommands({
214
217
  if (botAllowsCommands && text.startsWith('/model ')) {
215
218
  const newModel = text.slice(7).trim();
216
219
  if (['opus', 'sonnet', 'haiku'].includes(newModel)) {
217
- const oldModel = chatConfig.model;
218
- chatConfig.model = newModel;
220
+ // Write to the topic when in one (so Music ≠ General) and persist to
221
+ // config.json so it survives restarts — both were missing (2026-06-12).
222
+ const { scope: wScope, threadId: wThread } = getConfigWriteScope(chatConfig, threadIdStr);
223
+ const oldModel = wScope.model != null ? wScope.model : chatConfig.model;
224
+ wScope.model = newModel;
225
+ try { saveConfig(); }
226
+ catch (err) { logger.error?.(`[${botName}] /model saveConfig failed: ${err.message}`); }
219
227
  dbWrite(() => db.logConfigChange({
220
- chat_id: chatId, thread_id: threadIdStr, field: 'model',
228
+ chat_id: chatId, thread_id: wThread, field: 'model',
221
229
  old_value: oldModel, new_value: newModel,
222
230
  user: cmdUser, user_id: cmdUserId, source: 'command',
223
231
  }), 'log model change');
@@ -234,10 +242,13 @@ function createSlashCommands({
234
242
  if (botAllowsCommands && text.startsWith('/effort ')) {
235
243
  const newEffort = text.slice(8).trim();
236
244
  if (['low', 'medium', 'high', 'xhigh', 'max'].includes(newEffort)) {
237
- const oldEffort = chatConfig.effort;
238
- chatConfig.effort = newEffort;
245
+ const { scope: wScope, threadId: wThread } = getConfigWriteScope(chatConfig, threadIdStr);
246
+ const oldEffort = wScope.effort != null ? wScope.effort : chatConfig.effort;
247
+ wScope.effort = newEffort;
248
+ try { saveConfig(); }
249
+ catch (err) { logger.error?.(`[${botName}] /effort saveConfig failed: ${err.message}`); }
239
250
  dbWrite(() => db.logConfigChange({
240
- chat_id: chatId, thread_id: threadIdStr, field: 'effort',
251
+ chat_id: chatId, thread_id: wThread, field: 'effort',
241
252
  old_value: oldEffort, new_value: newEffort,
242
253
  user: cmdUser, user_id: cmdUserId, source: 'command',
243
254
  }), 'log effort change');
@@ -106,10 +106,30 @@ function getTopicConfig(chatConfig, threadId) {
106
106
  return overrides;
107
107
  }
108
108
 
109
+ /**
110
+ * Resolve the config object a per-chat/topic setting (model/effort) should be
111
+ * WRITTEN to, given where the command/card was used. When in a topic, return
112
+ * (creating if needed) that topic's override entry — so a /model in the Music
113
+ * topic targets Music alone and matches what the per-topic card displays,
114
+ * instead of leaking to the chat root and every other topic that inherits it
115
+ * (the 2026-06-12 "/model in Music does nothing" bug). At the chat level
116
+ * (no thread), the writable scope is the chat config itself.
117
+ *
118
+ * @returns {{ scope: object, threadId: (string|null) }}
119
+ */
120
+ function getConfigWriteScope(chatConfig, threadId) {
121
+ const tid = (threadId == null || threadId === '') ? null : String(threadId);
122
+ if (!tid) return { scope: chatConfig, threadId: null };
123
+ chatConfig.topics = chatConfig.topics || {};
124
+ chatConfig.topics[tid] = chatConfig.topics[tid] || {};
125
+ return { scope: chatConfig.topics[tid], threadId: tid };
126
+ }
127
+
109
128
  module.exports = {
110
129
  getSessionKey,
111
130
  getChatIdFromKey,
112
131
  getThreadIdFromKey,
113
132
  getTopicName,
114
133
  getTopicConfig,
134
+ getConfigWriteScope,
115
135
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.12.0-rc.39",
3
+ "version": "0.12.0-rc.40",
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
@@ -2459,7 +2459,7 @@ async function main() {
2459
2459
  });
2460
2460
  handleConfigCallback = createHandleConfigCallback({
2461
2461
  config, db, dbWrite, pm, getSessionKey,
2462
- formatConfigInfoText, buildConfigKeyboard,
2462
+ formatConfigInfoText, buildConfigKeyboard, saveConfig,
2463
2463
  botName: BOT_NAME, logger: console,
2464
2464
  });
2465
2465
  handleAbortIfRequested = createHandleAbort({
@@ -2532,7 +2532,7 @@ async function main() {
2532
2532
  config, db, dbWrite, pm, pairings, parsePairingTtl,
2533
2533
  contextHintShown, formatContextReply, getClaudeSessionId,
2534
2534
  getOrSpawnForChat, parsePairCodeArgs,
2535
- modelVersionsDesc: MODEL_VERSIONS_DESC,
2535
+ modelVersionsDesc: MODEL_VERSIONS_DESC, saveConfig,
2536
2536
  botName: BOT_NAME, logEvent, logger: console,
2537
2537
  });
2538
2538
  console.log('[polygram] using SDK ProcessManager');