@link-assistant/hive-mind 2.17.0 → 2.19.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/CHANGELOG.md +36 -0
- package/README.hi.md +2 -0
- package/README.md +2 -0
- package/README.ru.md +2 -0
- package/README.zh.md +2 -0
- package/package.json +3 -2
- package/src/agentic-cli-freshness.lib.mjs +118 -0
- package/src/agentic-cli-updater.lib.mjs +8 -4
- package/src/docker-sidecar.lib.mjs +17 -1
- package/src/github-url-parser.lib.mjs +80 -23
- package/src/github-url-recovery.lib.mjs +514 -0
- package/src/hive-models.lib.mjs +181 -0
- package/src/hive-models.mjs +20 -0
- package/src/hive.mjs +10 -0
- package/src/locales/en.lino +9 -1
- package/src/locales/hi.lino +9 -1
- package/src/locales/ru.lino +9 -1
- package/src/locales/zh.lino +9 -1
- package/src/model-catalogue-fetch.lib.mjs +333 -0
- package/src/model-catalogue-render.lib.mjs +191 -0
- package/src/model-catalogue-sources.lib.mjs +224 -0
- package/src/model-catalogue.lib.mjs +385 -0
- package/src/models/catalog.mjs +408 -0
- package/src/models/index.mjs +23 -362
- package/src/router-isolation.lib.mjs +103 -19
- package/src/router-routes.lib.mjs +250 -0
- package/src/router-sidecar.lib.mjs +33 -13
- package/src/solve.config.lib.mjs +5 -0
- package/src/solve.escalate.lib.mjs +3 -0
- package/src/solve.mjs +12 -0
- package/src/solve.validation.lib.mjs +16 -0
- package/src/task.config.lib.mjs +5 -0
- package/src/task.mjs +12 -0
- package/src/telegram-bot.mjs +24 -5
- package/src/telegram-models-command.lib.mjs +157 -0
- package/src/telegram-ui-messages.lib.mjs +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram /models command implementation (issue #2202, R5).
|
|
3
|
+
*
|
|
4
|
+
* Shows the merged model catalogue for a tool: what this installation ships,
|
|
5
|
+
* what the live sources are serving right now, and which of the two a given
|
|
6
|
+
* model is in. Every source it reads is a listing endpoint that cannot bill a
|
|
7
|
+
* token (R7), and the answer is cached for an hour (R9), so asking often is
|
|
8
|
+
* free.
|
|
9
|
+
*
|
|
10
|
+
* Usage in chat:
|
|
11
|
+
* /models -> the default tool (claude)
|
|
12
|
+
* /models --tool codex -> one specific tool
|
|
13
|
+
* /models --all -> every tool, one message each
|
|
14
|
+
* /models --details -> add context window and pricing (R8)
|
|
15
|
+
* /models --refresh -> ignore the cache and re-read the sources
|
|
16
|
+
* /models --no-update -> skip the CLI version check (R6)
|
|
17
|
+
*
|
|
18
|
+
* @see https://github.com/link-assistant/hive-mind/issues/2202
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { ensureAgenticCliFreshness, describeFreshnessResult } from './agentic-cli-freshness.lib.mjs';
|
|
22
|
+
import { MODEL_CATALOGUE_TOOLS, getMergedModelCatalogue } from './model-catalogue.lib.mjs';
|
|
23
|
+
import { formatModelCatalogueTelegram } from './model-catalogue-render.lib.mjs';
|
|
24
|
+
import { safeReply as defaultSafeReply } from './telegram-safe-reply.lib.mjs';
|
|
25
|
+
|
|
26
|
+
const GROUP_ONLY_MESSAGE = '❌ The /models command only works in group chats. Please add this bot to a group and make it an admin.';
|
|
27
|
+
|
|
28
|
+
/** The tool answered when the operator names none — the one Hive Mind drives by default. */
|
|
29
|
+
export const DEFAULT_MODELS_COMMAND_TOOL = 'claude';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse the argument tail of a `/models` message.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately forgiving: a chat is not a shell, so `--tool codex`,
|
|
35
|
+
* `--tool=codex`, and a bare `codex` all mean the same thing. Anything it
|
|
36
|
+
* cannot make sense of comes back as `error` so the handler can say so instead
|
|
37
|
+
* of silently answering a different question.
|
|
38
|
+
*/
|
|
39
|
+
export const parseModelsCommandArgs = (text = '') => {
|
|
40
|
+
const result = { tools: [], all: false, refresh: false, details: false, update: true, error: null };
|
|
41
|
+
const tokens = String(text).trim().split(/\s+/).slice(1).filter(Boolean);
|
|
42
|
+
|
|
43
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
44
|
+
const token = tokens[index];
|
|
45
|
+
const lower = token.toLowerCase();
|
|
46
|
+
if (lower === '--all' || lower === 'all') {
|
|
47
|
+
result.all = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (lower === '--refresh' || lower === 'refresh') {
|
|
51
|
+
result.refresh = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (lower === '--details' || lower === '--detail' || lower === 'details') {
|
|
55
|
+
result.details = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (lower === '--no-update' || lower === '--no-tool-update') {
|
|
59
|
+
result.update = false;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
let value;
|
|
63
|
+
if (lower.startsWith('--tool=')) value = lower.slice('--tool='.length);
|
|
64
|
+
else if (lower === '--tool' || lower === '-t') value = (tokens[++index] ?? '').toLowerCase();
|
|
65
|
+
else if (!lower.startsWith('-')) value = lower;
|
|
66
|
+
else {
|
|
67
|
+
result.error = `Unknown option: ${token}`;
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const entry of value.split(',').filter(Boolean)) {
|
|
72
|
+
if (!MODEL_CATALOGUE_TOOLS.includes(entry)) {
|
|
73
|
+
result.error = `Unknown tool: ${entry}. Known tools: ${MODEL_CATALOGUE_TOOLS.join(', ')}`;
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
if (!result.tools.includes(entry)) result.tools.push(entry);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (result.all) result.tools = [...MODEL_CATALOGUE_TOOLS];
|
|
81
|
+
if (result.tools.length === 0) result.tools = [DEFAULT_MODELS_COMMAND_TOOL];
|
|
82
|
+
return result;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Registers the /models command handler with the bot.
|
|
87
|
+
*
|
|
88
|
+
* @param {Object} bot Telegraf bot instance
|
|
89
|
+
* @param {Object} options the shared command options every telegram command takes
|
|
90
|
+
* @returns {{ handleModelsCommand: Function }} the handler, for the text fallback
|
|
91
|
+
*/
|
|
92
|
+
export function registerModelsCommand(bot, options = {}) {
|
|
93
|
+
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage, addBreadcrumb, safeReply, loadCatalogue = getMergedModelCatalogue, freshness = ensureAgenticCliFreshness, env = process.env } = options;
|
|
94
|
+
|
|
95
|
+
async function handleModelsCommand(ctx) {
|
|
96
|
+
VERBOSE && console.log('[VERBOSE] /models command received');
|
|
97
|
+
|
|
98
|
+
if (addBreadcrumb) {
|
|
99
|
+
await addBreadcrumb({
|
|
100
|
+
category: 'telegram.command',
|
|
101
|
+
message: '/models command received',
|
|
102
|
+
level: 'info',
|
|
103
|
+
data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const reply = (text, replyOptions = {}) => (safeReply || defaultSafeReply)(ctx, text, { reply_to_message_id: ctx.message?.message_id, ...replyOptions });
|
|
108
|
+
|
|
109
|
+
if (isOldMessage?.(ctx)) {
|
|
110
|
+
VERBOSE && console.log('[VERBOSE] /models ignored: old message');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (isForwardedOrReply?.(ctx)) {
|
|
114
|
+
VERBOSE && console.log('[VERBOSE] /models ignored: forwarded or reply');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (isGroupChat && !isGroupChat(ctx)) {
|
|
118
|
+
VERBOSE && console.log('[VERBOSE] /models ignored: not a group chat');
|
|
119
|
+
await reply(GROUP_ONLY_MESSAGE);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const authorize = isTopicAuthorized || (isChatAuthorized ? context => isChatAuthorized(context.chat.id) : () => true);
|
|
123
|
+
if (!authorize(ctx)) {
|
|
124
|
+
VERBOSE && console.log('[VERBOSE] /models ignored: not authorized');
|
|
125
|
+
await reply(buildAuthErrorMessage ? buildAuthErrorMessage(ctx) : `❌ This chat (ID: ${ctx.chat.id}) is not authorized.`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const args = parseModelsCommandArgs(ctx.message?.text ?? '');
|
|
130
|
+
if (args.error) {
|
|
131
|
+
await reply(`❌ ${args.error}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// R6: give the agentic CLIs a chance to update before we describe what they
|
|
136
|
+
// can run. Best-effort — a failed refresh must not cost the operator their
|
|
137
|
+
// answer, so the outcome is reported and then ignored.
|
|
138
|
+
const refreshed = await freshness({ tools: args.tools, env, enabled: args.update, verbose: VERBOSE, log: async message => VERBOSE && console.log(`[VERBOSE] /models ${message}`) });
|
|
139
|
+
const freshnessLine = describeFreshnessResult(refreshed);
|
|
140
|
+
if (freshnessLine) await reply(freshnessLine);
|
|
141
|
+
|
|
142
|
+
for (const tool of args.tools) {
|
|
143
|
+
try {
|
|
144
|
+
const merged = await loadCatalogue({ tool, env, refresh: args.refresh });
|
|
145
|
+
await reply(formatModelCatalogueTelegram(merged, { details: args.details }));
|
|
146
|
+
} catch (error) {
|
|
147
|
+
await reply(`⚠️ Could not build the ${tool} catalogue: ${error?.message ?? error}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
bot.command(/^models$/i, handleModelsCommand);
|
|
153
|
+
|
|
154
|
+
return { handleModelsCommand };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export default { DEFAULT_MODELS_COMMAND_TOOL, parseModelsCommandArgs, registerModelsCommand };
|
|
@@ -92,7 +92,7 @@ export function buildTelegramHelpMessage({ locale = null, chatId, chatType = '',
|
|
|
92
92
|
message.push('');
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
const simpleCommandKeys = ['telegram.help_solve_queue', 'telegram.help_limits', 'telegram.help_version', 'telegram.help_language', 'telegram.help_accept_invites', 'telegram.help_merge', 'telegram.help_merge_usage', 'telegram.help_merge_description', 'telegram.help_subscribe', 'telegram.help_help', 'telegram.help_stop_start', 'telegram.help_stop_uuid', 'telegram.help_log', 'telegram.help_terminal_watch'];
|
|
95
|
+
const simpleCommandKeys = ['telegram.help_solve_queue', 'telegram.help_models', 'telegram.help_limits', 'telegram.help_version', 'telegram.help_language', 'telegram.help_accept_invites', 'telegram.help_merge', 'telegram.help_merge_usage', 'telegram.help_merge_description', 'telegram.help_subscribe', 'telegram.help_help', 'telegram.help_stop_start', 'telegram.help_stop_uuid', 'telegram.help_log', 'telegram.help_terminal_watch'];
|
|
96
96
|
for (const key of simpleCommandKeys) addLine(message, key, {}, locale);
|
|
97
97
|
message.push('');
|
|
98
98
|
addLine(message, 'telegram.help_notifications', {}, locale);
|