polygram 0.35.0 → 0.36.0
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 -1
- package/lib/error/classify.js +11 -0
- package/lib/handlers/config-callback.js +32 -2
- package/lib/handlers/config-ui.js +16 -5
- package/lib/handlers/gate-inbound.js +4 -0
- package/lib/handlers/replay-disposition.js +31 -5
- package/lib/prompt.js +10 -3
- package/lib/runtime-config.js +41 -0
- package/package.json +3 -3
- package/polygram.js +56 -19
|
@@ -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
|
+
"version": "0.36.0",
|
|
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
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
|
|
68
68
|
"codex": {
|
|
69
|
-
"_comment": "Optional native macOS arm64 or Linux x64 Codex app-server beta. Codex
|
|
69
|
+
"_comment": "Optional native macOS arm64 or Linux x64 Codex app-server beta. Codex is default-off: a chat/topic must explicitly set codexEnabled:true before pm:'codex' can be selected or dispatched, and exact target-specific startup preflight must succeed; other host targets, including Windows, are rejected. `home` must be a dedicated non-temporary 0700 CODEX_HOME already authenticated with ChatGPT; polygram never copies or reads credential contents. `binary` must be the canonical immutable path to the reviewed target's pinned Codex CLI, not a PATH symlink. On Linux set POLYGRAM_CODEX_TMPDIR to an owner-only 0700 runtime directory; otherwise Codex inherits the daemon TMPDIR after the same safety checks. Every directory containing daemon credentials or control data must be listed in daemonSecretRoots and must not overlap a Codex workspace or CODEX_HOME. Command network and model-native web search are disabled; product MCP tools and interactive approvals are unavailable. Keep long-running commands in the foreground. Detached/background servers are unsupported and may survive hard runtime loss.",
|
|
70
70
|
"home": "/Users/you/.codex-polygram",
|
|
71
71
|
"binary": "/Users/you/.local/share/codex/versions/0.145.0/codex",
|
|
72
72
|
"daemonSecretRoots": [
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"name": "Codex beta DM",
|
|
95
95
|
"bot": "admin-bot",
|
|
96
96
|
"_comment_pm": "Codex uses the pinned app-server SDK surface directly (no tmux). Native macOS arm64 and Linux x64 beta: command network and model-native web search are disabled; product MCP tools and interactive approvals are unavailable; long-running commands must stay in the foreground; detached/background servers are unsupported and may survive hard runtime loss. The model and effort are accepted only when returned by this deployment's authenticated ChatGPT-backed model catalog.",
|
|
97
|
+
"codexEnabled": true,
|
|
97
98
|
"pm": "codex",
|
|
98
99
|
"codexModel": "gpt-5.6-sol",
|
|
99
100
|
"codexEffort": "xhigh",
|
package/lib/error/classify.js
CHANGED
|
@@ -145,6 +145,14 @@ function mapCodes(codes, shape) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
const CODEX_ERROR_SHAPES = Object.freeze({
|
|
148
|
+
scopeDisabled: stableCodeShape(
|
|
149
|
+
'codexScopeDisabled',
|
|
150
|
+
'Codex is not enabled for this chat',
|
|
151
|
+
),
|
|
152
|
+
runtimeSelectionIncomplete: stableCodeShape(
|
|
153
|
+
'codexRuntimeSelectionIncomplete',
|
|
154
|
+
'Codex is enabled, but its model, effort, or workspace is not configured for this chat',
|
|
155
|
+
),
|
|
148
156
|
auth: stableCodeShape(
|
|
149
157
|
'codexAuthUnavailable',
|
|
150
158
|
'🔑 Codex authentication is unavailable. The operator needs to re-authenticate the pinned deployment.',
|
|
@@ -192,6 +200,9 @@ const CODEX_ERROR_SHAPES = Object.freeze({
|
|
|
192
200
|
});
|
|
193
201
|
|
|
194
202
|
const CODEX_ERROR_CODES = {
|
|
203
|
+
CODEX_SCOPE_DISABLED: CODEX_ERROR_SHAPES.scopeDisabled,
|
|
204
|
+
CODEX_RUNTIME_SELECTION_INCOMPLETE:
|
|
205
|
+
CODEX_ERROR_SHAPES.runtimeSelectionIncomplete,
|
|
195
206
|
...mapCodes([
|
|
196
207
|
'CODEX_AUTH_UNAVAILABLE',
|
|
197
208
|
], CODEX_ERROR_SHAPES.auth),
|
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
const { toTelegramHtml } = require('../telegram/format');
|
|
19
19
|
const { resolveRichTextEnabled } = require('../telegram/rich');
|
|
20
20
|
const { getTopicConfig, getConfigWriteScope } = require('../session-key');
|
|
21
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
resolveCodexEnabled,
|
|
23
|
+
resolveRuntimeDescriptor,
|
|
24
|
+
} = require('../runtime-config');
|
|
25
|
+
const { classify } = require('../error/classify');
|
|
22
26
|
const {
|
|
23
27
|
configuredRuntimeValue,
|
|
24
28
|
formatCodexSettingsOutcome,
|
|
@@ -54,6 +58,16 @@ function runtimeLabel(runtime) {
|
|
|
54
58
|
return RUNTIME_OPTION_BY_BACKEND.get(runtime).label;
|
|
55
59
|
}
|
|
56
60
|
|
|
61
|
+
function codexSelectionErrorMessage(error) {
|
|
62
|
+
if (
|
|
63
|
+
error?.code === 'CODEX_SCOPE_DISABLED'
|
|
64
|
+
|| error?.code === 'CODEX_RUNTIME_SELECTION_INCOMPLETE'
|
|
65
|
+
) {
|
|
66
|
+
return classify(error).userMessage;
|
|
67
|
+
}
|
|
68
|
+
return 'Codex is unavailable; check login and runtime diagnostics';
|
|
69
|
+
}
|
|
70
|
+
|
|
57
71
|
function runtimeSwitchBlocked(proc) {
|
|
58
72
|
if (!proc || proc.closed) return false;
|
|
59
73
|
try {
|
|
@@ -135,6 +149,17 @@ function createHandleConfigCallback({
|
|
|
135
149
|
.catch(() => {});
|
|
136
150
|
return;
|
|
137
151
|
}
|
|
152
|
+
if (
|
|
153
|
+
value === 'codex'
|
|
154
|
+
&& !resolveCodexEnabled(config, chatId, callbackThreadIdEarly)
|
|
155
|
+
) {
|
|
156
|
+
releaseConfigIntent();
|
|
157
|
+
await ctx.answerCallbackQuery({
|
|
158
|
+
text: codexSelectionErrorMessage({ code: 'CODEX_SCOPE_DISABLED' }),
|
|
159
|
+
show_alert: true,
|
|
160
|
+
}).catch(() => {});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
138
163
|
const currentSelection = resolveRuntimeDescriptor({
|
|
139
164
|
config,
|
|
140
165
|
chatId,
|
|
@@ -190,7 +215,7 @@ function createHandleConfigCallback({
|
|
|
190
215
|
);
|
|
191
216
|
releaseConfigIntent();
|
|
192
217
|
await ctx.answerCallbackQuery({
|
|
193
|
-
text:
|
|
218
|
+
text: codexSelectionErrorMessage(error),
|
|
194
219
|
show_alert: true,
|
|
195
220
|
}).catch(() => {});
|
|
196
221
|
return;
|
|
@@ -389,6 +414,11 @@ function createHandleConfigCallback({
|
|
|
389
414
|
backend: value,
|
|
390
415
|
configuredPm: value,
|
|
391
416
|
selectionSource: writeThreadId ? 'topic' : 'chat',
|
|
417
|
+
codexEnabled: resolveCodexEnabled(
|
|
418
|
+
config,
|
|
419
|
+
chatId,
|
|
420
|
+
callbackThreadIdEarly,
|
|
421
|
+
),
|
|
392
422
|
};
|
|
393
423
|
}
|
|
394
424
|
const topicConfig = getTopicConfig(
|
|
@@ -18,9 +18,15 @@
|
|
|
18
18
|
|
|
19
19
|
const MODEL_OPTIONS = ['opus', 'sonnet', 'haiku'];
|
|
20
20
|
const EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
21
|
+
const CODEX_MODEL_OPTIONS = new Set([
|
|
22
|
+
'gpt-5.6-sol',
|
|
23
|
+
'gpt-5.6-terra',
|
|
24
|
+
'gpt-5.6-luna',
|
|
25
|
+
]);
|
|
21
26
|
// Which runtimes the card offers. The sdk backend stays fully supported in
|
|
22
27
|
// code and config files — it is per-token and nobody picks it from a chat, so
|
|
23
|
-
// it has no button.
|
|
28
|
+
// it has no button. Codex is filtered below unless enabled for the current
|
|
29
|
+
// scope. A chat configured on sdk simply shows no ✓ on this row.
|
|
24
30
|
const RUNTIME_OPTIONS = Object.freeze([
|
|
25
31
|
Object.freeze({ backend: 'cli', label: 'Claude' }),
|
|
26
32
|
Object.freeze({ backend: 'codex', label: 'Codex' }),
|
|
@@ -63,6 +69,7 @@ function getCodexCatalogModels(runtimeView) {
|
|
|
63
69
|
!entry
|
|
64
70
|
|| typeof entry.model !== 'string'
|
|
65
71
|
|| entry.model.length === 0
|
|
72
|
+
|| !CODEX_MODEL_OPTIONS.has(entry.model)
|
|
66
73
|
|| seen.has(entry.model)
|
|
67
74
|
) {
|
|
68
75
|
return false;
|
|
@@ -244,10 +251,14 @@ function buildConfigKeyboard(
|
|
|
244
251
|
const rows = [];
|
|
245
252
|
if (show === 'all') {
|
|
246
253
|
const currentBackend = canonicalRuntimeBackend(runtimeView);
|
|
247
|
-
rows.push(RUNTIME_OPTIONS
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
254
|
+
rows.push(RUNTIME_OPTIONS
|
|
255
|
+
.filter(({ backend }) => (
|
|
256
|
+
backend !== 'codex' || runtimeView?.codexEnabled === true
|
|
257
|
+
))
|
|
258
|
+
.map(({ backend, label }) => ({
|
|
259
|
+
text: backend === currentBackend ? `✓ ${label}` : label,
|
|
260
|
+
callback_data: `cfg:runtime:${backend}`,
|
|
261
|
+
})));
|
|
251
262
|
}
|
|
252
263
|
if ((show === 'model' || show === 'all') && modelOptions.length > 0) {
|
|
253
264
|
rows.push(modelOptions.map((entry) => {
|
|
@@ -170,6 +170,10 @@ function createGateInbound({
|
|
|
170
170
|
});
|
|
171
171
|
if (r.consumed) return { action: 'handled', stage: 'rewind' };
|
|
172
172
|
} catch (err) {
|
|
173
|
+
if (err?.code === 'CODEX_SCOPE_DISABLED') {
|
|
174
|
+
dispatchHandleMessage(sessionKey, chatId, msg, bot);
|
|
175
|
+
return { action: 'dispatched', stage: 'rewind' };
|
|
176
|
+
}
|
|
173
177
|
// The text IS a recognized /rewind — on an internal error, consume it
|
|
174
178
|
// anyway; falling through would send "/rewind" to claude as a prompt.
|
|
175
179
|
logger.error?.(`[${botName}] rewind tryConsume failed: ${err?.message || err}`);
|
|
@@ -184,10 +184,23 @@ async function executeReplayPlan({ plan, deps }) {
|
|
|
184
184
|
let recovered = 0; let skipped = 0; let noticed = 0; let noticeFailed = 0;
|
|
185
185
|
let deferred = defer.length;
|
|
186
186
|
|
|
187
|
-
//
|
|
187
|
+
// Legacy provider crash recovery.
|
|
188
188
|
for (const c of recover) {
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
let r;
|
|
190
|
+
try {
|
|
191
|
+
// eslint-disable-next-line no-await-in-loop
|
|
192
|
+
r = await deps.recover(c);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (error?.code !== 'CODEX_SCOPE_DISABLED') throw error;
|
|
195
|
+
deferred += 1;
|
|
196
|
+
log('codex-replay-deferred', {
|
|
197
|
+
chat_id: c.chat_id,
|
|
198
|
+
thread_id: c.thread_id == null ? null : c.thread_id,
|
|
199
|
+
msg_id: c.msg_id,
|
|
200
|
+
reason: 'scope-disabled',
|
|
201
|
+
});
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
191
204
|
if (r && r.ok) recovered += 1; else skipped += 1;
|
|
192
205
|
}
|
|
193
206
|
|
|
@@ -204,8 +217,21 @@ async function executeReplayPlan({ plan, deps }) {
|
|
|
204
217
|
});
|
|
205
218
|
continue;
|
|
206
219
|
}
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
let r;
|
|
221
|
+
try {
|
|
222
|
+
// eslint-disable-next-line no-await-in-loop
|
|
223
|
+
r = await deps.recoverCodex(c);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (error?.code !== 'CODEX_SCOPE_DISABLED') throw error;
|
|
226
|
+
deferred += 1;
|
|
227
|
+
log('codex-replay-deferred', {
|
|
228
|
+
chat_id: c.chat_id,
|
|
229
|
+
thread_id: c.thread_id == null ? null : c.thread_id,
|
|
230
|
+
msg_id: c.msg_id,
|
|
231
|
+
reason: 'scope-disabled',
|
|
232
|
+
});
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
209
235
|
if (r && r.ok) recovered += 1; else deferred += 1;
|
|
210
236
|
}
|
|
211
237
|
|
package/lib/prompt.js
CHANGED
|
@@ -5,19 +5,26 @@
|
|
|
5
5
|
* Telegram payload → polygram DB → unresolvable marker.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
requireCodexEnabled,
|
|
10
|
+
resolveRuntimeDescriptor,
|
|
11
|
+
} = require('./runtime-config');
|
|
9
12
|
|
|
10
13
|
function resolvePromptBackend({ config, chatId, threadId = null }) {
|
|
11
14
|
const normalizedChatId = String(chatId);
|
|
12
15
|
const normalizedThreadId = threadId == null || threadId === ''
|
|
13
16
|
? null
|
|
14
17
|
: String(threadId);
|
|
15
|
-
|
|
18
|
+
const descriptor = resolveRuntimeDescriptor({
|
|
16
19
|
config,
|
|
17
20
|
chatId: normalizedChatId,
|
|
18
21
|
threadId: normalizedThreadId,
|
|
19
22
|
defaultPm: 'sdk',
|
|
20
|
-
})
|
|
23
|
+
});
|
|
24
|
+
if (descriptor.backend === 'codex') {
|
|
25
|
+
requireCodexEnabled(config, normalizedChatId, normalizedThreadId);
|
|
26
|
+
}
|
|
27
|
+
return descriptor.backend;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
/**
|
package/lib/runtime-config.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
const { createHash } = require('crypto');
|
|
14
14
|
const { pickBackend } = require('@shumkov/orchestra');
|
|
15
|
+
const { resolveBoolOverride } = require('./config-override');
|
|
15
16
|
|
|
16
17
|
const PM_DESCRIPTORS = Object.freeze({
|
|
17
18
|
sdk: Object.freeze({
|
|
@@ -93,6 +94,39 @@ class RuntimeConfigError extends Error {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
function resolveCodexEnabled(config, chatId, threadId = null) {
|
|
98
|
+
return resolveBoolOverride(
|
|
99
|
+
config,
|
|
100
|
+
'codexEnabled',
|
|
101
|
+
chatId,
|
|
102
|
+
threadId,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function requireCodexEnabled(config, chatId, threadId = null) {
|
|
107
|
+
if (resolveCodexEnabled(config, chatId, threadId)) return true;
|
|
108
|
+
throw new RuntimeConfigError(
|
|
109
|
+
'Codex is not enabled for this chat.',
|
|
110
|
+
{
|
|
111
|
+
code: 'CODEX_SCOPE_DISABLED',
|
|
112
|
+
runtime: 'codex',
|
|
113
|
+
reason: 'effective codexEnabled policy is false',
|
|
114
|
+
action: 'Enable Codex for this chat before selecting or dispatching it.',
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function requireCodexDispatchEnabled({
|
|
120
|
+
config,
|
|
121
|
+
chatId,
|
|
122
|
+
threadId = null,
|
|
123
|
+
selectedProvider,
|
|
124
|
+
liveCodexGeneration = false,
|
|
125
|
+
}) {
|
|
126
|
+
if (selectedProvider !== 'codex' && !liveCodexGeneration) return false;
|
|
127
|
+
return requireCodexEnabled(config, chatId, threadId);
|
|
128
|
+
}
|
|
129
|
+
|
|
96
130
|
/**
|
|
97
131
|
* Convert an Orchestra-branded preflight receipt into the only availability
|
|
98
132
|
* object that can enable Codex. The assertion is required lazily so a
|
|
@@ -192,6 +226,7 @@ function resolveRuntimeConfig({
|
|
|
192
226
|
let spawnProfileId = null;
|
|
193
227
|
|
|
194
228
|
if (isCodex) {
|
|
229
|
+
requireCodexEnabled(config, chatId, threadId);
|
|
195
230
|
const receipt = validateCodexAvailability(codexAvailability);
|
|
196
231
|
modelChoice = resolveScopedValue(
|
|
197
232
|
scopes.providerOptions,
|
|
@@ -365,6 +400,7 @@ function resolveCodexRuntimeCandidate({
|
|
|
365
400
|
threadId = null,
|
|
366
401
|
} = {}) {
|
|
367
402
|
if (!chatId) return null;
|
|
403
|
+
requireCodexEnabled(config, chatId, threadId);
|
|
368
404
|
|
|
369
405
|
const scopes = configScopes(config, chatId, threadId);
|
|
370
406
|
const modelChoice = resolveScopedValue(
|
|
@@ -491,9 +527,11 @@ function resolveRuntimeDescriptor({
|
|
|
491
527
|
: pickedBackend === 'cli'
|
|
492
528
|
? 'cli'
|
|
493
529
|
: 'sdk';
|
|
530
|
+
const codexEnabled = resolveCodexEnabled(config, chatId, threadId);
|
|
494
531
|
return Object.freeze({
|
|
495
532
|
configuredPm: pmChoice.value,
|
|
496
533
|
source: pmChoice.source,
|
|
534
|
+
codexEnabled,
|
|
497
535
|
runtime: PM_DESCRIPTORS[canonicalBackend].runtime,
|
|
498
536
|
backend: canonicalBackend,
|
|
499
537
|
promptMode: PM_DESCRIPTORS[canonicalBackend].promptMode,
|
|
@@ -636,7 +674,10 @@ function stableRuntimeConfigIdentity(fields) {
|
|
|
636
674
|
module.exports = {
|
|
637
675
|
createCodexRuntimeAvailability,
|
|
638
676
|
processMatchesRuntime,
|
|
677
|
+
requireCodexDispatchEnabled,
|
|
678
|
+
requireCodexEnabled,
|
|
639
679
|
RuntimeConfigError,
|
|
680
|
+
resolveCodexEnabled,
|
|
640
681
|
resolveCodexRuntimeCandidate,
|
|
641
682
|
resolveCodexRuntimeRequest,
|
|
642
683
|
resolveRuntimeConfig,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
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": {
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"config.example.json"
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
|
-
"test": "node --test --test-
|
|
28
|
-
"coverage": "node --test --test-
|
|
27
|
+
"test": "node --test --test-concurrency=4 tests/*.test.js",
|
|
28
|
+
"coverage": "node --test --test-concurrency=4 --experimental-test-coverage tests/*.test.js",
|
|
29
29
|
"coverage:lcov": "mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage/lcov.info tests/*.test.js",
|
|
30
30
|
"start": "node polygram.js",
|
|
31
31
|
"split-db": "node scripts/split-db.js",
|
package/polygram.js
CHANGED
|
@@ -79,6 +79,7 @@ const {
|
|
|
79
79
|
} = require('./lib/codex/spawn-context');
|
|
80
80
|
const {
|
|
81
81
|
processMatchesRuntime,
|
|
82
|
+
requireCodexDispatchEnabled,
|
|
82
83
|
resolveCodexRuntimeCandidate,
|
|
83
84
|
resolveRuntimeDescriptor,
|
|
84
85
|
} = require('./lib/runtime-config');
|
|
@@ -631,15 +632,17 @@ function buildUnavailableCodexRuntimeView({
|
|
|
631
632
|
runtimeView = null,
|
|
632
633
|
}) {
|
|
633
634
|
let candidate = null;
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
635
|
+
if (runtimeSelection.codexEnabled) {
|
|
636
|
+
try {
|
|
637
|
+
candidate = resolveCodexRuntimeCandidate({
|
|
638
|
+
config,
|
|
639
|
+
chatId,
|
|
640
|
+
threadId,
|
|
641
|
+
});
|
|
642
|
+
} catch {
|
|
643
|
+
// The runtime button must remain available even when the saved Codex
|
|
644
|
+
// model, effort, or cwd is incomplete.
|
|
645
|
+
}
|
|
643
646
|
}
|
|
644
647
|
const model = runtimeView?.model ?? candidate?.model ?? null;
|
|
645
648
|
const effort = runtimeView?.effort ?? candidate?.effort ?? null;
|
|
@@ -653,6 +656,7 @@ function buildUnavailableCodexRuntimeView({
|
|
|
653
656
|
backend: runtimeSelection.backend,
|
|
654
657
|
configuredPm: runtimeSelection.configuredPm,
|
|
655
658
|
selectionSource: runtimeSelection.source,
|
|
659
|
+
codexEnabled: runtimeSelection.codexEnabled,
|
|
656
660
|
model,
|
|
657
661
|
effort,
|
|
658
662
|
models: runtimeView?.models ?? Object.freeze([]),
|
|
@@ -684,10 +688,19 @@ async function resolveSessionRuntimeView({
|
|
|
684
688
|
backend: runtimeSelection.backend,
|
|
685
689
|
configuredPm: runtimeSelection.configuredPm,
|
|
686
690
|
selectionSource: runtimeSelection.source,
|
|
691
|
+
codexEnabled: runtimeSelection.codexEnabled,
|
|
687
692
|
};
|
|
688
693
|
if (runtimeSelection.runtime !== 'codex') {
|
|
689
694
|
return Object.freeze(runtimeMetadata);
|
|
690
695
|
}
|
|
696
|
+
if (!runtimeSelection.codexEnabled) {
|
|
697
|
+
return buildUnavailableCodexRuntimeView({
|
|
698
|
+
runtimeSelection,
|
|
699
|
+
chatId,
|
|
700
|
+
threadId,
|
|
701
|
+
error: { code: 'CODEX_SCOPE_DISABLED' },
|
|
702
|
+
});
|
|
703
|
+
}
|
|
691
704
|
if (!codexRuntimeController) {
|
|
692
705
|
return buildUnavailableCodexRuntimeView({
|
|
693
706
|
runtimeSelection,
|
|
@@ -1191,11 +1204,32 @@ function parsePairCodeArgs(text) {
|
|
|
1191
1204
|
async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
1192
1205
|
const chatConfig = config.chats[chatId];
|
|
1193
1206
|
if (!chatConfig) return;
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1207
|
+
const text = msg.text || msg.caption || '';
|
|
1208
|
+
const threadId = msg.message_thread_id;
|
|
1209
|
+
const threadIdStr = threadId?.toString() || null;
|
|
1210
|
+
const botAllowsCommands = !!config.bot?.allowConfigCommands;
|
|
1211
|
+
const runtimeInspectionCommand = (
|
|
1212
|
+
botAllowsCommands
|
|
1213
|
+
&& (
|
|
1214
|
+
text === '/config'
|
|
1215
|
+
|| text === '/model'
|
|
1216
|
+
|| text === '/effort'
|
|
1217
|
+
)
|
|
1218
|
+
);
|
|
1219
|
+
const selectedBackend = runtimeInspectionCommand
|
|
1220
|
+
? resolveRuntimeDescriptor({
|
|
1221
|
+
config,
|
|
1222
|
+
chatId,
|
|
1223
|
+
threadId: threadIdStr,
|
|
1224
|
+
defaultPm: 'sdk',
|
|
1225
|
+
logger: console,
|
|
1226
|
+
}).backend
|
|
1227
|
+
: resolvePromptBackend({
|
|
1228
|
+
config,
|
|
1229
|
+
chatId,
|
|
1230
|
+
threadId: threadIdStr,
|
|
1231
|
+
});
|
|
1232
|
+
const selectedInboundProvider = selectedBackend === 'codex'
|
|
1199
1233
|
? 'codex'
|
|
1200
1234
|
: 'claude';
|
|
1201
1235
|
if (
|
|
@@ -1231,9 +1265,6 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1231
1265
|
});
|
|
1232
1266
|
}
|
|
1233
1267
|
|
|
1234
|
-
const text = msg.text || msg.caption || '';
|
|
1235
|
-
const threadId = msg.message_thread_id;
|
|
1236
|
-
const threadIdStr = threadId?.toString() || null;
|
|
1237
1268
|
const label = getSessionLabel(chatConfig, threadIdStr);
|
|
1238
1269
|
|
|
1239
1270
|
const replyOpts = (tid) => ({
|
|
@@ -1245,7 +1276,6 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1245
1276
|
// comment there for the bump procedure.
|
|
1246
1277
|
const MODEL_VERSIONS = MODEL_VERSIONS_DESC;
|
|
1247
1278
|
|
|
1248
|
-
const botAllowsCommands = !!config.bot?.allowConfigCommands;
|
|
1249
1279
|
const cmdUser = msg.from?.first_name || msg.from?.username || null;
|
|
1250
1280
|
const cmdUserId = msg.from?.id || null;
|
|
1251
1281
|
|
|
@@ -1291,7 +1321,7 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1291
1321
|
}, { source: 'command-reply', botName: BOT_NAME, model: chatConfig.model, effort: chatConfig.effort, ...metaTags });
|
|
1292
1322
|
};
|
|
1293
1323
|
|
|
1294
|
-
if (
|
|
1324
|
+
if (runtimeInspectionCommand) {
|
|
1295
1325
|
const show = text === '/effort' ? 'effort' : text === '/model' ? 'model' : 'all';
|
|
1296
1326
|
// Resolve per-topic overrides so a topic's card shows its REAL
|
|
1297
1327
|
// agent/model/effort, not the chat-level default — Music topic (thread 3)
|
|
@@ -2010,6 +2040,13 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
2010
2040
|
|| current.backend === 'codex'
|
|
2011
2041
|
),
|
|
2012
2042
|
);
|
|
2043
|
+
requireCodexDispatchEnabled({
|
|
2044
|
+
config,
|
|
2045
|
+
chatId,
|
|
2046
|
+
threadId: threadIdStr || null,
|
|
2047
|
+
selectedProvider: selectedInboundProvider,
|
|
2048
|
+
liveCodexGeneration,
|
|
2049
|
+
});
|
|
2013
2050
|
if (liveCodexGeneration) {
|
|
2014
2051
|
if (!codexRuntimeController) {
|
|
2015
2052
|
codexDispatchDecision = 'unavailable';
|