@visorcraft/idlehands 1.3.4 → 1.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/dist/agent/formatting.js +1 -5
- package/dist/agent/formatting.js.map +1 -1
- package/dist/agent.js +114 -26
- package/dist/agent.js.map +1 -1
- package/dist/anton/reporter.js +2 -20
- package/dist/anton/reporter.js.map +1 -1
- package/dist/bot/command-format.js +56 -0
- package/dist/bot/command-format.js.map +1 -0
- package/dist/bot/command-logic.js +651 -0
- package/dist/bot/command-logic.js.map +1 -0
- package/dist/bot/commands.js +100 -552
- package/dist/bot/commands.js.map +1 -1
- package/dist/bot/discord-commands.js +431 -0
- package/dist/bot/discord-commands.js.map +1 -0
- package/dist/bot/discord-routing.js +1 -8
- package/dist/bot/discord-routing.js.map +1 -1
- package/dist/bot/discord.js +16 -870
- package/dist/bot/discord.js.map +1 -1
- package/dist/bot/session-manager.js +60 -44
- package/dist/bot/session-manager.js.map +1 -1
- package/dist/bot/telegram-commands.js +201 -0
- package/dist/bot/telegram-commands.js.map +1 -0
- package/dist/bot/telegram.js +10 -309
- package/dist/bot/telegram.js.map +1 -1
- package/dist/bot/turn-lifecycle.js +66 -0
- package/dist/bot/turn-lifecycle.js.map +1 -0
- package/dist/cli/commands/project.js +52 -0
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/context.js +1 -3
- package/dist/context.js.map +1 -1
- package/dist/progress/ir.js +0 -3
- package/dist/progress/ir.js.map +1 -1
- package/dist/progress/tool-summary.js +1 -4
- package/dist/progress/tool-summary.js.map +1 -1
- package/dist/progress/turn-progress.js +1 -5
- package/dist/progress/turn-progress.js.map +1 -1
- package/dist/runtime/executor.js +1 -3
- package/dist/runtime/executor.js.map +1 -1
- package/dist/runtime/health.js +2 -1
- package/dist/runtime/health.js.map +1 -1
- package/dist/shared/async.js +5 -0
- package/dist/shared/async.js.map +1 -0
- package/dist/shared/config-utils.js +8 -0
- package/dist/shared/config-utils.js.map +1 -0
- package/dist/shared/format.js +19 -0
- package/dist/shared/format.js.map +1 -0
- package/dist/shared/math.js +5 -0
- package/dist/shared/math.js.map +1 -0
- package/dist/shared/strings.js +8 -0
- package/dist/shared/strings.js.map +1 -0
- package/dist/tools/patch.js +82 -0
- package/dist/tools/patch.js.map +1 -0
- package/dist/tools/path-safety.js +89 -0
- package/dist/tools/path-safety.js.map +1 -0
- package/dist/tools/undo.js +141 -0
- package/dist/tools/undo.js.map +1 -0
- package/dist/tools.js +11 -289
- package/dist/tools.js.map +1 -1
- package/dist/tui/event-bridge.js +1 -3
- package/dist/tui/event-bridge.js.map +1 -1
- package/dist/tui/render.js +1 -5
- package/dist/tui/render.js.map +1 -1
- package/dist/vault.js +1 -5
- package/dist/vault.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram runtime command handlers extracted from telegram.ts.
|
|
3
|
+
* Handles /hosts, /backends, /models, /rtstatus, /switch and the
|
|
4
|
+
* model-selection callback query.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Register runtime-related command handlers on the bot instance.
|
|
8
|
+
*/
|
|
9
|
+
export function registerRuntimeCommands(bot) {
|
|
10
|
+
bot.command('hosts', async (ctx) => {
|
|
11
|
+
try {
|
|
12
|
+
const { loadRuntimes, redactConfig } = await import('../runtime/store.js');
|
|
13
|
+
const config = await loadRuntimes();
|
|
14
|
+
const redacted = redactConfig(config);
|
|
15
|
+
if (!redacted.hosts.length) {
|
|
16
|
+
await ctx.reply('No hosts configured. Use `idlehands hosts add` in CLI.');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const lines = redacted.hosts.map((h) => `${h.enabled ? '🟢' : '🔴'} *${h.display_name}* (\`${h.id}\`)\n Transport: ${h.transport}`);
|
|
20
|
+
await ctx.reply(lines.join('\n\n'), { parse_mode: 'Markdown' });
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
await ctx.reply(`❌ Failed to load hosts: ${e?.message ?? String(e)}`);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
bot.command('backends', async (ctx) => {
|
|
27
|
+
try {
|
|
28
|
+
const { loadRuntimes, redactConfig } = await import('../runtime/store.js');
|
|
29
|
+
const config = await loadRuntimes();
|
|
30
|
+
const redacted = redactConfig(config);
|
|
31
|
+
if (!redacted.backends.length) {
|
|
32
|
+
await ctx.reply('No backends configured. Use `idlehands backends add` in CLI.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const lines = redacted.backends.map((b) => `${b.enabled ? '🟢' : '🔴'} *${b.display_name}* (\`${b.id}\`)\n Type: ${b.type}`);
|
|
36
|
+
await ctx.reply(lines.join('\n\n'), { parse_mode: 'Markdown' });
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
await ctx.reply(`❌ Failed to load backends: ${e?.message ?? String(e)}`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const handleRuntimeModels = async (ctx) => {
|
|
43
|
+
try {
|
|
44
|
+
const { loadRuntimes } = await import('../runtime/store.js');
|
|
45
|
+
const config = await loadRuntimes();
|
|
46
|
+
if (!config.models.length) {
|
|
47
|
+
await ctx.reply('No runtime models configured.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const enabledModels = config.models.filter((m) => m.enabled);
|
|
51
|
+
if (!enabledModels.length) {
|
|
52
|
+
await ctx.reply('No enabled runtime models. Use `idlehands models enable <id>` in CLI.');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const buttons = enabledModels.map((m) => [{
|
|
56
|
+
text: `🟢 ${m.display_name}`,
|
|
57
|
+
callback_data: `model_select:${m.id}`,
|
|
58
|
+
}]);
|
|
59
|
+
const keyboard = [];
|
|
60
|
+
for (let i = 0; i < buttons.length; i += 2) {
|
|
61
|
+
const row = buttons.slice(i, i + 2).flat();
|
|
62
|
+
keyboard.push(row);
|
|
63
|
+
}
|
|
64
|
+
await ctx.reply('📋 *Select a model to switch to:*', {
|
|
65
|
+
parse_mode: 'Markdown',
|
|
66
|
+
reply_markup: { inline_keyboard: keyboard },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
await ctx.reply(`❌ Failed to load runtime models: ${e?.message ?? String(e)}`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
bot.command('models', handleRuntimeModels);
|
|
74
|
+
bot.command('rtmodels', handleRuntimeModels);
|
|
75
|
+
bot.command('rtstatus', async (ctx) => {
|
|
76
|
+
try {
|
|
77
|
+
const { loadActiveRuntime } = await import('../runtime/executor.js');
|
|
78
|
+
const active = await loadActiveRuntime();
|
|
79
|
+
if (!active) {
|
|
80
|
+
await ctx.reply('No active runtime.');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const lines = [
|
|
84
|
+
'*Active Runtime*',
|
|
85
|
+
`Model: \`${active.modelId}\``,
|
|
86
|
+
`Backend: \`${active.backendId ?? 'none'}\``,
|
|
87
|
+
`Hosts: ${active.hostIds.map((id) => `\`${id}\``).join(', ') || 'none'}`,
|
|
88
|
+
`Healthy: ${active.healthy ? '✅ yes' : '❌ no'}`,
|
|
89
|
+
`Endpoint: \`${active.endpoint ?? 'unknown'}\``,
|
|
90
|
+
`Started: \`${active.startedAt}\``,
|
|
91
|
+
];
|
|
92
|
+
await ctx.reply(lines.join('\n'), { parse_mode: 'Markdown' });
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
await ctx.reply(`❌ Failed to read runtime status: ${e?.message ?? String(e)}`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
bot.command('switch', async (ctx) => {
|
|
99
|
+
try {
|
|
100
|
+
const modelId = ctx.match?.trim();
|
|
101
|
+
if (!modelId) {
|
|
102
|
+
await ctx.reply('Usage: /switch <model-id>');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const { plan } = await import('../runtime/planner.js');
|
|
106
|
+
const { execute, loadActiveRuntime } = await import('../runtime/executor.js');
|
|
107
|
+
const { loadRuntimes } = await import('../runtime/store.js');
|
|
108
|
+
const rtConfig = await loadRuntimes();
|
|
109
|
+
const active = await loadActiveRuntime();
|
|
110
|
+
const result = plan({ modelId, mode: 'live' }, rtConfig, active);
|
|
111
|
+
if (!result.ok) {
|
|
112
|
+
await ctx.reply(`❌ Plan failed: ${result.reason}`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (result.reuse) {
|
|
116
|
+
await ctx.reply('✅ Runtime already active and healthy.');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const statusMsg = await ctx.reply(`⏳ Switching to *${result.model.display_name}*...`, {
|
|
120
|
+
parse_mode: 'Markdown',
|
|
121
|
+
});
|
|
122
|
+
const execResult = await execute(result, {
|
|
123
|
+
onStep: async (step, status) => {
|
|
124
|
+
if (status === 'done') {
|
|
125
|
+
await ctx.api
|
|
126
|
+
.editMessageText(ctx.chat.id, statusMsg.message_id, `⏳ ${step.description}... ✓`)
|
|
127
|
+
.catch(() => { });
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
confirm: async (prompt) => {
|
|
131
|
+
await ctx.reply(`⚠️ ${prompt}\nAuto-approving for bot context.`);
|
|
132
|
+
return true;
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
if (execResult.ok) {
|
|
136
|
+
await ctx.reply(`✅ Switched to *${result.model.display_name}*`, {
|
|
137
|
+
parse_mode: 'Markdown',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
await ctx.reply(`❌ Switch failed: ${execResult.error || 'unknown error'}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
await ctx.reply(`❌ Switch failed: ${e?.message ?? String(e)}`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Handle the model_select callback query from inline keyboard buttons.
|
|
151
|
+
* Returns true if handled, false if the callback is not a model selection.
|
|
152
|
+
*/
|
|
153
|
+
export async function handleModelSelectCallback(ctx) {
|
|
154
|
+
const data = ctx.callbackQuery.data;
|
|
155
|
+
if (!data.startsWith('model_select:'))
|
|
156
|
+
return false;
|
|
157
|
+
const modelId = data.slice('model_select:'.length);
|
|
158
|
+
await ctx.answerCallbackQuery({ text: `Switching to ${modelId}...` }).catch(() => { });
|
|
159
|
+
try {
|
|
160
|
+
const { plan } = await import('../runtime/planner.js');
|
|
161
|
+
const { execute, loadActiveRuntime } = await import('../runtime/executor.js');
|
|
162
|
+
const { loadRuntimes } = await import('../runtime/store.js');
|
|
163
|
+
const rtConfig = await loadRuntimes();
|
|
164
|
+
const active = await loadActiveRuntime();
|
|
165
|
+
const result = plan({ modelId, mode: 'live' }, rtConfig, active);
|
|
166
|
+
if (!result.ok) {
|
|
167
|
+
await ctx.editMessageText(`❌ Plan failed: ${result.reason}`).catch(() => { });
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
if (result.reuse) {
|
|
171
|
+
await ctx.editMessageText(`✅ Already using *${result.model.display_name}*`, {
|
|
172
|
+
parse_mode: 'Markdown',
|
|
173
|
+
}).catch(() => { });
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
const execResult = await execute(result, {
|
|
177
|
+
onStep: async (step, status) => {
|
|
178
|
+
if (status === 'done') {
|
|
179
|
+
await ctx.editMessageText(`⏳ ${step.description}... ✓`).catch(() => { });
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
confirm: async (prompt) => {
|
|
183
|
+
await ctx.reply(`⚠️ ${prompt}\nAuto-approving for bot context.`);
|
|
184
|
+
return true;
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
if (execResult.ok) {
|
|
188
|
+
await ctx.editMessageText(`✅ Switched to *${result.model.display_name}*`, {
|
|
189
|
+
parse_mode: 'Markdown',
|
|
190
|
+
}).catch(() => { });
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
await ctx.editMessageText(`❌ Switch failed: ${execResult.error || 'unknown error'}`).catch(() => { });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
await ctx.editMessageText(`❌ Switch failed: ${e?.message ?? String(e)}`).catch(() => { });
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=telegram-commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram-commands.js","sourceRoot":"","sources":["../../src/bot/telegram-commands.ts"],"names":[],"mappings":"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"}
|
package/dist/bot/telegram.js
CHANGED
|
@@ -7,130 +7,14 @@ import { MessageEditScheduler, classifyTelegramEditError, } from '../progress/me
|
|
|
7
7
|
import { ProgressPresenter } from '../progress/progress-presenter.js';
|
|
8
8
|
import { PKG_VERSION } from '../utils.js';
|
|
9
9
|
import { formatWatchdogCancelMessage, resolveWatchdogSettings } from '../watchdog.js';
|
|
10
|
-
import { handleStart, handleHelp, handleVersion, handleNew, handleCancel, handleStatus, handleWatchdog, handleDir, handlePin, handleModel, handleCompact, handleApproval, handleMode, handleSubAgents, handleChanges, handleUndo, handleVault, handleAnton, handleAgent, handleAgents, handleEscalate, handleDeescalate, handleRestartBot, handleGitStatus, } from './commands.js';
|
|
10
|
+
import { handleStart, handleHelp, handleVersion, handleNew, handleCancel, handleStatus, handleWatchdog, handleDir, handlePin, handleUnpin, handleModel, handleCompact, handleApproval, handleMode, handleSubAgents, handleChanges, handleUndo, handleVault, handleAnton, handleAgent, handleAgents, handleEscalate, handleDeescalate, handleRestartBot, handleGitStatus, } from './commands.js';
|
|
11
11
|
import { TelegramConfirmProvider } from './confirm-telegram.js';
|
|
12
12
|
import { markdownToTelegramHtml, splitMessage, escapeHtml, formatToolCallSummary, } from './format.js';
|
|
13
13
|
import { SessionManager } from './session-manager.js';
|
|
14
14
|
import { isToolLoopBreak, formatAutoContinueNotice, AUTO_CONTINUE_PROMPT } from './auto-continue.js';
|
|
15
|
-
|
|
16
|
-
// Escalation helpers
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Check if the model response contains an escalation request.
|
|
20
|
-
* Returns { escalate: true, reason: string } if escalation marker found at start of response.
|
|
21
|
-
*/
|
|
22
|
-
function detectEscalation(text) {
|
|
23
|
-
const trimmed = text.trim();
|
|
24
|
-
const match = trimmed.match(/^\[ESCALATE:\s*([^\]]+)\]/i);
|
|
25
|
-
if (match) {
|
|
26
|
-
return { escalate: true, reason: match[1].trim() };
|
|
27
|
-
}
|
|
28
|
-
return { escalate: false };
|
|
29
|
-
}
|
|
30
|
-
/** Keyword presets for common escalation triggers */
|
|
31
|
-
const KEYWORD_PRESETS = {
|
|
32
|
-
coding: [
|
|
33
|
-
'build',
|
|
34
|
-
'implement',
|
|
35
|
-
'create',
|
|
36
|
-
'develop',
|
|
37
|
-
'architect',
|
|
38
|
-
'refactor',
|
|
39
|
-
'debug',
|
|
40
|
-
'fix',
|
|
41
|
-
'code',
|
|
42
|
-
'program',
|
|
43
|
-
'write',
|
|
44
|
-
],
|
|
45
|
-
planning: ['plan', 'design', 'roadmap', 'strategy', 'analyze', 'research', 'evaluate', 'compare'],
|
|
46
|
-
complex: [
|
|
47
|
-
'full',
|
|
48
|
-
'complete',
|
|
49
|
-
'comprehensive',
|
|
50
|
-
'multi-step',
|
|
51
|
-
'integrate',
|
|
52
|
-
'migration',
|
|
53
|
-
'overhaul',
|
|
54
|
-
'entire',
|
|
55
|
-
'whole',
|
|
56
|
-
],
|
|
57
|
-
};
|
|
58
|
-
/**
|
|
59
|
-
* Check if text matches a set of keywords.
|
|
60
|
-
* Returns matched keywords or empty array if none match.
|
|
61
|
-
*/
|
|
62
|
-
function matchKeywords(text, keywords, presets) {
|
|
63
|
-
const allKeywords = [...keywords];
|
|
64
|
-
// Add preset keywords
|
|
65
|
-
if (presets) {
|
|
66
|
-
for (const preset of presets) {
|
|
67
|
-
const presetWords = KEYWORD_PRESETS[preset];
|
|
68
|
-
if (presetWords)
|
|
69
|
-
allKeywords.push(...presetWords);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (allKeywords.length === 0)
|
|
73
|
-
return [];
|
|
74
|
-
const lowerText = text.toLowerCase();
|
|
75
|
-
const matched = [];
|
|
76
|
-
for (const kw of allKeywords) {
|
|
77
|
-
if (kw.startsWith('re:')) {
|
|
78
|
-
// Regex pattern
|
|
79
|
-
try {
|
|
80
|
-
const regex = new RegExp(kw.slice(3), 'i');
|
|
81
|
-
if (regex.test(text))
|
|
82
|
-
matched.push(kw);
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// Invalid regex, skip
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
// Word boundary match (case-insensitive)
|
|
90
|
-
const wordRegex = new RegExp(`\\b${kw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i');
|
|
91
|
-
if (wordRegex.test(lowerText))
|
|
92
|
-
matched.push(kw);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return matched;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Check if user message matches keyword escalation triggers.
|
|
99
|
-
* Returns { escalate: true, tier: number, reason: string } if keywords match.
|
|
100
|
-
* Tier indicates which model index to escalate to (highest matching tier wins).
|
|
101
|
-
*/
|
|
102
|
-
function checkKeywordEscalation(text, escalation) {
|
|
103
|
-
if (!escalation)
|
|
104
|
-
return { escalate: false };
|
|
105
|
-
// Tiered keyword escalation
|
|
106
|
-
if (escalation.tiers && escalation.tiers.length > 0) {
|
|
107
|
-
let highestTier = -1;
|
|
108
|
-
let highestReason = '';
|
|
109
|
-
// Check each tier, highest matching tier wins
|
|
110
|
-
for (let i = 0; i < escalation.tiers.length; i++) {
|
|
111
|
-
const tier = escalation.tiers[i];
|
|
112
|
-
const matched = matchKeywords(text, tier.keywords || [], tier.keyword_presets);
|
|
113
|
-
if (matched.length > 0 && i > highestTier) {
|
|
114
|
-
highestTier = i;
|
|
115
|
-
highestReason = `tier ${i} keyword match: ${matched.slice(0, 3).join(', ')}${matched.length > 3 ? '...' : ''}`;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (highestTier >= 0) {
|
|
119
|
-
return { escalate: true, tier: highestTier, reason: highestReason };
|
|
120
|
-
}
|
|
121
|
-
return { escalate: false };
|
|
122
|
-
}
|
|
123
|
-
// Legacy flat keywords (treated as tier 0)
|
|
124
|
-
const matched = matchKeywords(text, escalation.keywords || [], escalation.keyword_presets);
|
|
125
|
-
if (matched.length > 0) {
|
|
126
|
-
return {
|
|
127
|
-
escalate: true,
|
|
128
|
-
tier: 0,
|
|
129
|
-
reason: `keyword match: ${matched.slice(0, 3).join(', ')}${matched.length > 3 ? '...' : ''}`,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
return { escalate: false };
|
|
133
|
-
}
|
|
15
|
+
import { registerRuntimeCommands, handleModelSelectCallback } from './telegram-commands.js';
|
|
16
|
+
// Escalation helpers shared with Discord bot
|
|
17
|
+
import { detectEscalation, checkKeywordEscalation } from './discord-routing.js';
|
|
134
18
|
// ---------------------------------------------------------------------------
|
|
135
19
|
// Streaming message helper
|
|
136
20
|
// ---------------------------------------------------------------------------
|
|
@@ -430,6 +314,7 @@ export async function startTelegramBot(config, botConfig) {
|
|
|
430
314
|
bot.command('watchdog', (ctx) => handleWatchdog(cmdCtx(ctx)));
|
|
431
315
|
bot.command('dir', (ctx) => handleDir(cmdCtx(ctx)));
|
|
432
316
|
bot.command('pin', (ctx) => handlePin(cmdCtx(ctx)));
|
|
317
|
+
bot.command('unpin', (ctx) => handleUnpin(cmdCtx(ctx)));
|
|
433
318
|
bot.command('model', (ctx) => handleModel(cmdCtx(ctx)));
|
|
434
319
|
bot.command('compact', (ctx) => handleCompact(cmdCtx(ctx)));
|
|
435
320
|
bot.command('approval', (ctx) => handleApproval(cmdCtx(ctx)));
|
|
@@ -445,202 +330,18 @@ export async function startTelegramBot(config, botConfig) {
|
|
|
445
330
|
bot.command('deescalate', (ctx) => handleDeescalate(cmdCtx(ctx)));
|
|
446
331
|
bot.command('restart_bot', (ctx) => handleRestartBot(cmdCtx(ctx)));
|
|
447
332
|
bot.command('git_status', (ctx) => handleGitStatus(cmdCtx(ctx)));
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const { loadRuntimes, redactConfig } = await import('../runtime/store.js');
|
|
451
|
-
const config = await loadRuntimes();
|
|
452
|
-
const redacted = redactConfig(config);
|
|
453
|
-
if (!redacted.hosts.length) {
|
|
454
|
-
await ctx.reply('No hosts configured. Use `idlehands hosts add` in CLI.');
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
const lines = redacted.hosts.map((h) => `${h.enabled ? '🟢' : '🔴'} *${h.display_name}* (\`${h.id}\`)\n Transport: ${h.transport}`);
|
|
458
|
-
await ctx.reply(lines.join('\n\n'), { parse_mode: 'Markdown' });
|
|
459
|
-
}
|
|
460
|
-
catch (e) {
|
|
461
|
-
await ctx.reply(`❌ Failed to load hosts: ${e?.message ?? String(e)}`);
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
bot.command('backends', async (ctx) => {
|
|
465
|
-
try {
|
|
466
|
-
const { loadRuntimes, redactConfig } = await import('../runtime/store.js');
|
|
467
|
-
const config = await loadRuntimes();
|
|
468
|
-
const redacted = redactConfig(config);
|
|
469
|
-
if (!redacted.backends.length) {
|
|
470
|
-
await ctx.reply('No backends configured. Use `idlehands backends add` in CLI.');
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
const lines = redacted.backends.map((b) => `${b.enabled ? '🟢' : '🔴'} *${b.display_name}* (\`${b.id}\`)\n Type: ${b.type}`);
|
|
474
|
-
await ctx.reply(lines.join('\n\n'), { parse_mode: 'Markdown' });
|
|
475
|
-
}
|
|
476
|
-
catch (e) {
|
|
477
|
-
await ctx.reply(`❌ Failed to load backends: ${e?.message ?? String(e)}`);
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
const handleRuntimeModels = async (ctx) => {
|
|
481
|
-
try {
|
|
482
|
-
const { loadRuntimes } = await import('../runtime/store.js');
|
|
483
|
-
const config = await loadRuntimes();
|
|
484
|
-
if (!config.models.length) {
|
|
485
|
-
await ctx.reply('No runtime models configured.');
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
// Show enabled models with inline buttons for selection
|
|
489
|
-
const enabledModels = config.models.filter((m) => m.enabled);
|
|
490
|
-
if (!enabledModels.length) {
|
|
491
|
-
await ctx.reply('No enabled runtime models. Use `idlehands models enable <id>` in CLI.');
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
// Create inline keyboard with model buttons (max 8 per row, 2 columns)
|
|
495
|
-
const buttons = enabledModels.map((m) => [{
|
|
496
|
-
text: `🟢 ${m.display_name}`,
|
|
497
|
-
callback_data: `model_select:${m.id}`,
|
|
498
|
-
}]);
|
|
499
|
-
// Split into rows of 2
|
|
500
|
-
const keyboard = [];
|
|
501
|
-
for (let i = 0; i < buttons.length; i += 2) {
|
|
502
|
-
const row = buttons.slice(i, i + 2).flat();
|
|
503
|
-
keyboard.push(row);
|
|
504
|
-
}
|
|
505
|
-
await ctx.reply('📋 *Select a model to switch to:*', {
|
|
506
|
-
parse_mode: 'Markdown',
|
|
507
|
-
reply_markup: { inline_keyboard: keyboard },
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
catch (e) {
|
|
511
|
-
await ctx.reply(`❌ Failed to load runtime models: ${e?.message ?? String(e)}`);
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
// Preferred name: /models (keep /rtmodels as backward-compatible alias)
|
|
515
|
-
bot.command('models', handleRuntimeModels);
|
|
516
|
-
bot.command('rtmodels', handleRuntimeModels);
|
|
517
|
-
bot.command('rtstatus', async (ctx) => {
|
|
518
|
-
try {
|
|
519
|
-
const { loadActiveRuntime } = await import('../runtime/executor.js');
|
|
520
|
-
const active = await loadActiveRuntime();
|
|
521
|
-
if (!active) {
|
|
522
|
-
await ctx.reply('No active runtime.');
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
const lines = [
|
|
526
|
-
'*Active Runtime*',
|
|
527
|
-
`Model: \`${active.modelId}\``,
|
|
528
|
-
`Backend: \`${active.backendId ?? 'none'}\``,
|
|
529
|
-
`Hosts: ${active.hostIds.map((id) => `\`${id}\``).join(', ') || 'none'}`,
|
|
530
|
-
`Healthy: ${active.healthy ? '✅ yes' : '❌ no'}`,
|
|
531
|
-
`Endpoint: \`${active.endpoint ?? 'unknown'}\``,
|
|
532
|
-
`Started: \`${active.startedAt}\``,
|
|
533
|
-
];
|
|
534
|
-
await ctx.reply(lines.join('\n'), { parse_mode: 'Markdown' });
|
|
535
|
-
}
|
|
536
|
-
catch (e) {
|
|
537
|
-
await ctx.reply(`❌ Failed to read runtime status: ${e?.message ?? String(e)}`);
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
bot.command('switch', async (ctx) => {
|
|
541
|
-
try {
|
|
542
|
-
const modelId = ctx.match?.trim();
|
|
543
|
-
if (!modelId) {
|
|
544
|
-
await ctx.reply('Usage: /switch <model-id>');
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
const { plan } = await import('../runtime/planner.js');
|
|
548
|
-
const { execute, loadActiveRuntime } = await import('../runtime/executor.js');
|
|
549
|
-
const { loadRuntimes } = await import('../runtime/store.js');
|
|
550
|
-
const rtConfig = await loadRuntimes();
|
|
551
|
-
const active = await loadActiveRuntime();
|
|
552
|
-
const result = plan({ modelId, mode: 'live' }, rtConfig, active);
|
|
553
|
-
if (!result.ok) {
|
|
554
|
-
await ctx.reply(`❌ Plan failed: ${result.reason}`);
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
if (result.reuse) {
|
|
558
|
-
await ctx.reply('✅ Runtime already active and healthy.');
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
const statusMsg = await ctx.reply(`⏳ Switching to *${result.model.display_name}*...`, {
|
|
562
|
-
parse_mode: 'Markdown',
|
|
563
|
-
});
|
|
564
|
-
const execResult = await execute(result, {
|
|
565
|
-
onStep: async (step, status) => {
|
|
566
|
-
if (status === 'done') {
|
|
567
|
-
await ctx.api
|
|
568
|
-
.editMessageText(ctx.chat.id, statusMsg.message_id, `⏳ ${step.description}... ✓`)
|
|
569
|
-
.catch(() => { });
|
|
570
|
-
}
|
|
571
|
-
},
|
|
572
|
-
confirm: async (prompt) => {
|
|
573
|
-
await ctx.reply(`⚠️ ${prompt}\nAuto-approving for bot context.`);
|
|
574
|
-
return true;
|
|
575
|
-
},
|
|
576
|
-
});
|
|
577
|
-
if (execResult.ok) {
|
|
578
|
-
await ctx.reply(`✅ Switched to *${result.model.display_name}*`, {
|
|
579
|
-
parse_mode: 'Markdown',
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
else {
|
|
583
|
-
await ctx.reply(`❌ Switch failed: ${execResult.error || 'unknown error'}`);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
catch (e) {
|
|
587
|
-
await ctx.reply(`❌ Switch failed: ${e?.message ?? String(e)}`);
|
|
588
|
-
}
|
|
589
|
-
});
|
|
333
|
+
// Runtime command handlers (hosts, backends, models, rtstatus, switch)
|
|
334
|
+
registerRuntimeCommands(bot);
|
|
590
335
|
// ---------------------------------------------------------------------------
|
|
591
336
|
// Callback query handler (inline button presses for confirmations + model selection)
|
|
592
337
|
// ---------------------------------------------------------------------------
|
|
593
338
|
bot.on('callback_query:data', async (ctx) => {
|
|
594
|
-
const data = ctx.callbackQuery.data;
|
|
595
339
|
const chatId = ctx.chat?.id;
|
|
596
340
|
if (!chatId)
|
|
597
341
|
return;
|
|
598
|
-
// Handle model selection callback
|
|
599
|
-
if (
|
|
600
|
-
const modelId = data.slice('model_select:'.length);
|
|
601
|
-
await ctx.answerCallbackQuery({ text: `Switching to ${modelId}...` }).catch(() => { });
|
|
602
|
-
try {
|
|
603
|
-
const { plan } = await import('../runtime/planner.js');
|
|
604
|
-
const { execute, loadActiveRuntime } = await import('../runtime/executor.js');
|
|
605
|
-
const { loadRuntimes } = await import('../runtime/store.js');
|
|
606
|
-
const rtConfig = await loadRuntimes();
|
|
607
|
-
const active = await loadActiveRuntime();
|
|
608
|
-
const result = plan({ modelId, mode: 'live' }, rtConfig, active);
|
|
609
|
-
if (!result.ok) {
|
|
610
|
-
await ctx.editMessageText(`❌ Plan failed: ${result.reason}`).catch(() => { });
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
if (result.reuse) {
|
|
614
|
-
await ctx.editMessageText(`✅ Already using *${result.model.display_name}*`, {
|
|
615
|
-
parse_mode: 'Markdown',
|
|
616
|
-
}).catch(() => { });
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
const execResult = await execute(result, {
|
|
620
|
-
onStep: async (step, status) => {
|
|
621
|
-
if (status === 'done') {
|
|
622
|
-
await ctx.editMessageText(`⏳ ${step.description}... ✓`).catch(() => { });
|
|
623
|
-
}
|
|
624
|
-
},
|
|
625
|
-
confirm: async (prompt) => {
|
|
626
|
-
await ctx.reply(`⚠️ ${prompt}\nAuto-approving for bot context.`);
|
|
627
|
-
return true;
|
|
628
|
-
},
|
|
629
|
-
});
|
|
630
|
-
if (execResult.ok) {
|
|
631
|
-
await ctx.editMessageText(`✅ Switched to *${result.model.display_name}*`, {
|
|
632
|
-
parse_mode: 'Markdown',
|
|
633
|
-
}).catch(() => { });
|
|
634
|
-
}
|
|
635
|
-
else {
|
|
636
|
-
await ctx.editMessageText(`❌ Switch failed: ${execResult.error || 'unknown error'}`).catch(() => { });
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
catch (e) {
|
|
640
|
-
await ctx.editMessageText(`❌ Switch failed: ${e?.message ?? String(e)}`).catch(() => { });
|
|
641
|
-
}
|
|
342
|
+
// Handle model selection callback (delegated to telegram-commands)
|
|
343
|
+
if (await handleModelSelectCallback(ctx))
|
|
642
344
|
return;
|
|
643
|
-
}
|
|
644
345
|
// Handle confirmation callbacks
|
|
645
346
|
const managed = sessions.get(chatId);
|
|
646
347
|
if (!managed?.confirmProvider) {
|
|
@@ -648,7 +349,7 @@ export async function startTelegramBot(config, botConfig) {
|
|
|
648
349
|
return;
|
|
649
350
|
}
|
|
650
351
|
const provider = managed.confirmProvider;
|
|
651
|
-
const handled = await provider.handleCallback(data);
|
|
352
|
+
const handled = await provider.handleCallback(ctx.callbackQuery.data);
|
|
652
353
|
await ctx
|
|
653
354
|
.answerCallbackQuery(handled ? undefined : { text: 'Unknown action.' })
|
|
654
355
|
.catch(() => { });
|