@link-assistant/hive-mind 2.18.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 +17 -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/hive-models.lib.mjs +181 -0
- package/src/hive-models.mjs +20 -0
- package/src/locales/en.lino +2 -1
- package/src/locales/hi.lino +2 -1
- package/src/locales/ru.lino +2 -1
- package/src/locales/zh.lino +2 -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/task.config.lib.mjs +5 -0
- package/src/task.mjs +12 -0
- package/src/telegram-bot.mjs +4 -1
- package/src/telegram-models-command.lib.mjs +157 -0
- package/src/telegram-ui-messages.lib.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 2.19.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ac07514: Stop the model list from ageing with the release: add the models that shipped since 2.17.0, hot-load the live catalogue from every source that costs nothing to read, expose it as `/models` and `hive-models`, teach `--use-router` both of the router's route dialects, and check the agentic CLI for a newer version before a task starts (issue #2202).
|
|
8
|
+
|
|
9
|
+
A provider can ship a model on a Tuesday, and until now Hive Mind could not name it until the next release: `src/models/index.mjs` was a static table and `--model` validated against nothing else. Six changes close that gap, none of which can spend a token or fail a run.
|
|
10
|
+
|
|
11
|
+
- **The new models are in the bundled table.** Fable 5.1, Mythos 5.1, GPT-6 Astra and GPT-5.6 Cyber, with their aliases, context windows and fallback chains. The additions pushed `src/models/index.mjs` past the 1350-line early warning of `scripts/check-file-line-limits.sh`, so the catalogue itself moved to `src/models/catalog.mjs` and is re-exported — the public surface is unchanged.
|
|
12
|
+
- **The catalogue hot-loads.** `src/model-catalogue.lib.mjs` merges six sources in a fixed precedence: the Link.Assistant Router's live catalogue, `codex debug models`, Anthropic's and OpenAI's `GET /v1/models`, models.dev for specifications, and the bundled table as the offline floor. models.dev only annotates; it never adds a model to the available list. Answers are cached per source and per tool for at least an hour, and `HIVE_MIND_MODEL_CATALOGUE_TTL_MINUTES` can only raise that floor.
|
|
13
|
+
- **Listing models cannot cost a token, structurally.** `assertTokenFreeSource` rejects any source that has not explicitly declared `billable: false`, so a source cannot become billable by omission, and `assertTokenFreeUrl` rejects any URL whose path is a completion endpoint no matter which descriptor carried it — a typo turning `/v1/models` into `/v1/messages` throws instead of spending. The four extraction methods that were considered and rejected, including driving either CLI's `/model` picker through its TUI, are recorded with their reasons so a future contributor does not re-derive them.
|
|
14
|
+
- **`/models` and `hive-models` show the whole spectrum.** Models are grouped as bundled-and-live, hot-loaded (a live source has it, this installation does not ship it), and bundled-only (shipped, unconfirmed), with `--tool`, `--details`, `--refresh`, `--json`, and `--all`. A footer names every source actually consulted, its outcome, and the age of the cache, so "why don't I see X" is answerable from the output — and a source that failed is described from its stderr, never by quoting the command line, so the router's leased token cannot ride a failure message into the footer. In Telegram the same flags are accepted the way a chat writes them: `/models codex`, `/models --tool codex` and `/models --tool=codex` are one command.
|
|
15
|
+
- **`--use-router` speaks both route dialects.** Router 1.0 removed every root, `/v1/*` and overlapping `/api/*` alias and moved each service under `/api/services/<service>/…` — measured, not assumed: on `1.2.0` every path that answers on `0.119.0` is a 404 and vice versa. `src/router-routes.lib.mjs` derives every base URL, the health probe and the catalogue path from the pinned image, so the pin can move in one line. It stays on `0.x` today, at `0.125.4`, because router `1.x` has no `gh`-reachable REST base and `gh` exposes no path-prefix option; the trade is reported by `describeRouterCoverageGaps` rather than discovered at runtime, and the upstream issues are filed.
|
|
16
|
+
- **A stale CLI is refreshed before the run needs it.** `/solve`, `/hive`, `/task`, `/fix`, `/models` and `hive-models` check the agentic CLI they are about to drive, because an outdated binary is the usual reason a brand-new model name is rejected. The check is throttled to once every six hours, narrowed to the one CLI in play, deferred entirely while other tasks are running, skipped for `--dry-run` and `--only-prepare-command`, and never fatal. Each run excludes its own task from the idle gate — otherwise a `/solve` asking for an update would find itself in the process table and defer forever. Opt out with `--no-tool-update` or `HIVE_MIND_AGENTIC_CLI_AUTO_UPDATE=0`.
|
|
17
|
+
|
|
18
|
+
Documentation is in `docs/MODELS.md` and its `zh`/`hi`/`ru` siblings; the full analysis — the requirement-by-requirement plan, the router route measurement, and what shipped differently from the plan — is in `docs/case-studies/issue-2202/`.
|
|
19
|
+
|
|
3
20
|
## 2.18.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/README.hi.md
CHANGED
|
@@ -515,6 +515,8 @@ Free Models via Kilo Gateway (with --tool agent):
|
|
|
515
515
|
|
|
516
516
|
> **📖 मुफ्त मॉडल गाइड**: OpenCode Zen और Kilo Gateway प्रदाताओं सहित सभी मुफ्त मॉडलों के बारे में व्यापक जानकारी के लिए [docs/FREE_MODELS.hi.md](./docs/FREE_MODELS.hi.md) देखें।
|
|
517
517
|
|
|
518
|
+
> **📖 लाइव मॉडल सूची**: यह देखने के लिए कि इस समय कौन-से मॉडल उपलब्ध हैं — उनमें वे भी जो इस installation के प्रकाशित होने के बाद जारी हुए — `hive-models` चलाएँ (या Telegram में `/models`)। देखें [docs/MODELS.hi.md](./docs/MODELS.hi.md)।
|
|
519
|
+
|
|
518
520
|
#### `/hive` - Hive ऑर्केस्ट्रेशन चलाएँ
|
|
519
521
|
|
|
520
522
|
```
|
package/README.md
CHANGED
|
@@ -533,6 +533,8 @@ See [docs/CONFIGURATION.md](./docs/CONFIGURATION.md) for the full per-tool defau
|
|
|
533
533
|
|
|
534
534
|
> **📖 Free Models Guide**: See [docs/FREE_MODELS.md](./docs/FREE_MODELS.md) for comprehensive information about all free models including OpenCode Zen and Kilo Gateway providers.
|
|
535
535
|
|
|
536
|
+
> **📖 Live model list**: run `hive-models` (or `/models` in Telegram) to see which models are reachable right now, including ones released after this installation was published. See [docs/MODELS.md](./docs/MODELS.md).
|
|
537
|
+
|
|
536
538
|
#### `/hive` - Run Hive Orchestration
|
|
537
539
|
|
|
538
540
|
```
|
package/README.ru.md
CHANGED
|
@@ -516,6 +516,8 @@ Free Models via Kilo Gateway (with --tool agent):
|
|
|
516
516
|
|
|
517
517
|
> **📖 Руководство по бесплатным моделям**: см. [docs/FREE_MODELS.ru.md](./docs/FREE_MODELS.ru.md) для получения полной информации обо всех бесплатных моделях, включая провайдеры OpenCode Zen и Kilo Gateway.
|
|
518
518
|
|
|
519
|
+
> **📖 Живой список моделей**: запустите `hive-models` (или `/models` в Telegram), чтобы увидеть, какие модели доступны прямо сейчас, включая вышедшие уже после публикации этой установки. См. [docs/MODELS.ru.md](./docs/MODELS.ru.md).
|
|
520
|
+
|
|
519
521
|
#### `/hive` — Запуск оркестрации Hive
|
|
520
522
|
|
|
521
523
|
```
|
package/README.zh.md
CHANGED
|
@@ -512,6 +512,8 @@ Free Models via Kilo Gateway (with --tool agent):
|
|
|
512
512
|
|
|
513
513
|
> **📖 免费模型指南**:有关所有免费模型(包括 OpenCode Zen 和 Kilo Gateway 提供商)的全面信息,请参见 [docs/FREE_MODELS.zh.md](./docs/FREE_MODELS.zh.md)。
|
|
514
514
|
|
|
515
|
+
> **📖 实时模型列表**:运行 `hive-models`(或在 Telegram 中使用 `/models`)即可查看当前可用的模型,包括本次安装发布之后才推出的模型。参见 [docs/MODELS.zh.md](./docs/MODELS.zh.md)。
|
|
516
|
+
|
|
515
517
|
#### `/hive` - 运行蜂群编排
|
|
516
518
|
|
|
517
519
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.0",
|
|
4
4
|
"description": "AI-powered issue solver and hive mind for collaborative problem solving",
|
|
5
5
|
"main": "src/hive.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"configure-claude": "./src/configure-claude.mjs",
|
|
15
15
|
"start-screen": "./src/start-screen.mjs",
|
|
16
16
|
"hive-screens": "./src/hive-screens.mjs",
|
|
17
|
+
"hive-models": "./src/hive-models.mjs",
|
|
17
18
|
"hive-telegram-bot": "./src/telegram-bot.mjs"
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"changeset": "changeset",
|
|
31
32
|
"changeset:version": "changeset version",
|
|
32
33
|
"changeset:publish": "npm run build:pre && changeset publish",
|
|
33
|
-
"build:pre": "chmod +x src/hive.mjs && chmod +x src/solve.mjs && chmod +x src/task.mjs && chmod +x src/fix.mjs && chmod +x src/cleanup.mjs && chmod +x src/review.mjs && chmod +x src/configure-claude.mjs && chmod +x src/start-screen.mjs && chmod +x src/hive-screens.mjs && chmod +x src/telegram-bot.mjs",
|
|
34
|
+
"build:pre": "chmod +x src/hive.mjs && chmod +x src/solve.mjs && chmod +x src/task.mjs && chmod +x src/fix.mjs && chmod +x src/cleanup.mjs && chmod +x src/review.mjs && chmod +x src/configure-claude.mjs && chmod +x src/start-screen.mjs && chmod +x src/hive-screens.mjs && chmod +x src/hive-models.mjs && chmod +x src/telegram-bot.mjs",
|
|
34
35
|
"prepare": "husky"
|
|
35
36
|
},
|
|
36
37
|
"repository": {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-flight CLI freshness check (issue #2202, R6).
|
|
3
|
+
*
|
|
4
|
+
* R6: "/models and each /solve and other commands that relevant for
|
|
5
|
+
* claude/codex tools should check if new version available, and before
|
|
6
|
+
* starting task execution or before providing new models list - we should
|
|
7
|
+
* update them."
|
|
8
|
+
*
|
|
9
|
+
* `updateAgenticClisWhenIdle` already knows how to do the refresh safely — it
|
|
10
|
+
* throttles registry reads, takes a state lock, and refuses to swap a binary
|
|
11
|
+
* out from under a running task. What it lacked was a caller other than the
|
|
12
|
+
* Telegram maintenance tick, and two things a command entry point needs:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Narrowing.** `hive-models --tool codex` should not reinstall Gemini.
|
|
15
|
+
* 2. **Not counting itself as busy.** The idle gate scans `/proc` for running
|
|
16
|
+
* solve/task processes by issue reference. A solve run that checks for
|
|
17
|
+
* updates after it has started would find *itself* and defer forever, so
|
|
18
|
+
* the caller passes its own task reference to be ignored.
|
|
19
|
+
*
|
|
20
|
+
* Everything here is best-effort: a refresh failure must never stop the command
|
|
21
|
+
* the operator actually asked for.
|
|
22
|
+
*
|
|
23
|
+
* @see https://github.com/link-assistant/hive-mind/issues/2202
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { AGENTIC_CLI_TARGETS, isAgenticCliAutoUpdateEnabled, updateAgenticClisWhenIdle } from './agentic-cli-updater.lib.mjs';
|
|
27
|
+
|
|
28
|
+
const KNOWN_TOOL_IDS = new Set(AGENTIC_CLI_TARGETS.map(target => target.id));
|
|
29
|
+
|
|
30
|
+
/** Tool aliases Hive Mind commands use that are not the updater's target id. */
|
|
31
|
+
export const FRESHNESS_TOOL_ALIASES = Object.freeze({
|
|
32
|
+
'claude-code': 'claude',
|
|
33
|
+
'gemini-cli': 'gemini',
|
|
34
|
+
'qwen-code': 'qwen',
|
|
35
|
+
'github-copilot': 'copilot',
|
|
36
|
+
'opencode-ai': 'opencode',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** Normalize whatever a command calls its tool into updater target ids. */
|
|
40
|
+
export const resolveFreshnessTools = tools => {
|
|
41
|
+
const requested = (Array.isArray(tools) ? tools : [tools])
|
|
42
|
+
.flatMap(entry =>
|
|
43
|
+
String(entry ?? '')
|
|
44
|
+
.split(',')
|
|
45
|
+
.map(part => part.trim().toLowerCase())
|
|
46
|
+
)
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
const resolved = [];
|
|
49
|
+
for (const entry of requested) {
|
|
50
|
+
const id = FRESHNESS_TOOL_ALIASES[entry] ?? entry;
|
|
51
|
+
if (KNOWN_TOOL_IDS.has(id) && !resolved.includes(id)) resolved.push(id);
|
|
52
|
+
}
|
|
53
|
+
return resolved;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** `https://github.com/o/r/issues/7` → `{owner:'o', repo:'r', number:7}`. */
|
|
57
|
+
export const parseTaskRef = value => {
|
|
58
|
+
if (!value) return null;
|
|
59
|
+
if (typeof value === 'object') {
|
|
60
|
+
const number = Number(value.number);
|
|
61
|
+
if (!value.owner || !value.repo || !Number.isFinite(number)) return null;
|
|
62
|
+
return { owner: String(value.owner), repo: String(value.repo), number };
|
|
63
|
+
}
|
|
64
|
+
const match = String(value).match(/github\.com\/([^/\s]+)\/([^/\s]+)\/(?:issues|pull)\/(\d+)/i);
|
|
65
|
+
if (!match) return null;
|
|
66
|
+
return { owner: match[1], repo: match[2], number: Number(match[3]) };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const sameRef = (a, b) => a.owner.toLowerCase() === b.owner.toLowerCase() && a.repo.toLowerCase() === b.repo.toLowerCase() && a.number === b.number;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Refresh the agentic CLIs a command is about to drive.
|
|
73
|
+
*
|
|
74
|
+
* Never throws: every failure path returns a status the caller can log and
|
|
75
|
+
* ignore. Statuses come from `updateAgenticClisWhenIdle`
|
|
76
|
+
* (`checked`/`throttled`/`busy`/`disabled`) plus `skipped` for a caller opt-out
|
|
77
|
+
* and `error` when the refresh itself blew up.
|
|
78
|
+
*
|
|
79
|
+
* @param {object} options
|
|
80
|
+
* @param {string|string[]} options.tools tool ids/aliases the command needs
|
|
81
|
+
* @param {boolean} options.enabled false for `--no-update`
|
|
82
|
+
* @param {Array} options.ignoreTasks task refs or GitHub URLs that are *this* run
|
|
83
|
+
*/
|
|
84
|
+
export const ensureAgenticCliFreshness = async ({ tools = [], env = process.env, log = null, verbose = false, force = false, enabled = true, ignoreTasks = [], getActiveTasksImpl = null, updateImpl = updateAgenticClisWhenIdle, minIntervalMs = undefined } = {}) => {
|
|
85
|
+
const only = resolveFreshnessTools(tools);
|
|
86
|
+
if (!enabled) return { status: 'skipped', reason: 'the caller disabled the update check (--no-update)', tools: only, updated: [], upToDate: [], failed: [] };
|
|
87
|
+
if (!isAgenticCliAutoUpdateEnabled(env)) return { status: 'disabled', reason: 'HIVE_MIND_AGENTIC_CLI_AUTO_UPDATE is off', tools: only, updated: [], upToDate: [], failed: [] };
|
|
88
|
+
|
|
89
|
+
const ignored = ignoreTasks.map(parseTaskRef).filter(Boolean);
|
|
90
|
+
let activeTasks = getActiveTasksImpl;
|
|
91
|
+
if (ignored.length > 0) {
|
|
92
|
+
const inner = getActiveTasksImpl ?? (await import('./cleanup.os.lib.mjs')).getActiveTasks;
|
|
93
|
+
activeTasks = async options => {
|
|
94
|
+
const tasks = await inner(options);
|
|
95
|
+
return tasks.filter(task => !ignored.some(ref => sameRef(ref, { owner: task.owner, repo: task.repo, number: Number(task.number) })));
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const result = await updateImpl({ env, log, verbose, force, only, ...(activeTasks ? { getActiveTasksImpl: activeTasks } : {}), ...(minIntervalMs === undefined ? {} : { minIntervalMs }) });
|
|
101
|
+
return { tools: only, updated: [], upToDate: [], failed: [], ...result };
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const message = String(error?.message ?? error);
|
|
104
|
+
if (verbose && log) await log(`[VERBOSE] agentic-cli-freshness: refresh failed — ${message}`);
|
|
105
|
+
return { status: 'error', reason: message, tools: only, updated: [], upToDate: [], failed: [] };
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** One human-readable line summarising a freshness result, or null when there is nothing to say. */
|
|
110
|
+
export const describeFreshnessResult = result => {
|
|
111
|
+
if (!result) return null;
|
|
112
|
+
if (result.updated?.length > 0) return `⬆️ Updated ${result.updated.map(entry => `${entry.id} ${entry.from} → ${entry.to}`).join(', ')}`;
|
|
113
|
+
if (result.failed?.length > 0) return `⚠️ Could not update ${result.failed.map(entry => entry.id).join(', ')}`;
|
|
114
|
+
if (result.status === 'busy') return 'Skipped the CLI update check: other tasks are running.';
|
|
115
|
+
return null;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export default { FRESHNESS_TOOL_ALIASES, describeFreshnessResult, ensureAgenticCliFreshness, parseTaskRef, resolveFreshnessTools };
|
|
@@ -81,10 +81,14 @@ const parseIdList = value =>
|
|
|
81
81
|
* `HIVE_MIND_AGENTIC_CLI_UPDATE_ONLY` is an allow-list and
|
|
82
82
|
* `HIVE_MIND_AGENTIC_CLI_UPDATE_EXCLUDE` a deny-list, both by target id.
|
|
83
83
|
*/
|
|
84
|
-
export const listAgenticCliUpdateTargets = (env = process.env) => {
|
|
84
|
+
export const listAgenticCliUpdateTargets = (env = process.env, { only: requested = [] } = {}) => {
|
|
85
85
|
const only = parseIdList(env.HIVE_MIND_AGENTIC_CLI_UPDATE_ONLY);
|
|
86
86
|
const excluded = new Set(parseIdList(env.HIVE_MIND_AGENTIC_CLI_UPDATE_EXCLUDE));
|
|
87
|
-
|
|
87
|
+
// A caller-supplied narrowing (issue #2202, R6: refresh the CLIs a command is
|
|
88
|
+
// about to drive) intersects with the operator's allow-list rather than
|
|
89
|
+
// overriding it — an operator who excluded a CLI still gets it excluded.
|
|
90
|
+
const caller = parseIdList(Array.isArray(requested) ? requested.join(',') : requested);
|
|
91
|
+
return AGENTIC_CLI_TARGETS.filter(target => (only.length === 0 || only.includes(target.id)) && (caller.length === 0 || caller.includes(target.id)) && !excluded.has(target.id));
|
|
88
92
|
};
|
|
89
93
|
|
|
90
94
|
/** First semantic version in a CLI's `--version` output, which is rarely bare. */
|
|
@@ -147,7 +151,7 @@ export const installAgenticCli = async (target, { run = execFileAsync, timeoutMs
|
|
|
147
151
|
*
|
|
148
152
|
* @returns {Promise<{status: 'disabled'|'busy'|'throttled'|'checked', updated: object[], upToDate: object[], failed: object[]}>}
|
|
149
153
|
*/
|
|
150
|
-
export const updateAgenticClisWhenIdle = async ({ env = process.env, fsImpl = fs, run = execFileAsync, log = null, verbose = false, getActiveTasksImpl = null, now = () => new Date(), minIntervalMs = DEFAULT_CLI_UPDATE_INTERVAL_MS, force = false, lockOptions = {} } = {}) => {
|
|
154
|
+
export const updateAgenticClisWhenIdle = async ({ env = process.env, fsImpl = fs, run = execFileAsync, log = null, verbose = false, getActiveTasksImpl = null, now = () => new Date(), minIntervalMs = DEFAULT_CLI_UPDATE_INTERVAL_MS, force = false, only = [], lockOptions = {} } = {}) => {
|
|
151
155
|
if (!isAgenticCliAutoUpdateEnabled(env)) {
|
|
152
156
|
if (verbose && log) await log('[VERBOSE] agentic-cli-updater: disabled by HIVE_MIND_AGENTIC_CLI_AUTO_UPDATE');
|
|
153
157
|
return { status: 'disabled', updated: [], upToDate: [], failed: [] };
|
|
@@ -176,7 +180,7 @@ export const updateAgenticClisWhenIdle = async ({ env = process.env, fsImpl = fs
|
|
|
176
180
|
const failed = [];
|
|
177
181
|
const tools = { ...state.tools };
|
|
178
182
|
|
|
179
|
-
for (const target of listAgenticCliUpdateTargets(env)) {
|
|
183
|
+
for (const target of listAgenticCliUpdateTargets(env, { only })) {
|
|
180
184
|
const installed = await readInstalledCliVersion(target, { run });
|
|
181
185
|
if (!installed) {
|
|
182
186
|
// Not installed on this host (image variants differ); nothing to refresh.
|
|
@@ -49,8 +49,24 @@ export const dockerOk = async (run, args, options) => {
|
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Mask the secrets a `docker` argv carries.
|
|
54
|
+
*
|
|
55
|
+
* `execFile` builds a failed command's message as "Command failed: <argv…>",
|
|
56
|
+
* and the argvs assembled here carry real secrets: the router's
|
|
57
|
+
* `--env TOKEN_SECRET=…`, a leased `--env ROUTER_CATALOGUE_TOKEN=…`, a
|
|
58
|
+
* provider's `--api-key …`. stderr is preferred over that message precisely
|
|
59
|
+
* because it describes the failure without the command line, but stderr is
|
|
60
|
+
* empty when the process is killed on a timeout — so whatever survives is
|
|
61
|
+
* masked before anyone reads it.
|
|
62
|
+
*/
|
|
63
|
+
const maskDockerArgvSecrets = text =>
|
|
64
|
+
String(text ?? '')
|
|
65
|
+
.replace(/(--env[= ])([A-Za-z_][A-Za-z0-9_]*)=(\S+)/g, '$1$2=***')
|
|
66
|
+
.replace(/(--(?:api-key|token|secret|password)[= ])(\S+)/g, '$1***');
|
|
67
|
+
|
|
52
68
|
/** The message a failed `docker` invocation should be reported with. */
|
|
53
|
-
export const dockerErrorMessage = error => error?.stderr?.toString?.().trim() || error?.message || String(error);
|
|
69
|
+
export const dockerErrorMessage = error => maskDockerArgvSecrets(error?.stderr?.toString?.().trim() || error?.message || String(error));
|
|
54
70
|
|
|
55
71
|
/**
|
|
56
72
|
* Inspect a container without treating "absent" as an error.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared runner for the `hive-models` bin command (issue #2202, R5).
|
|
5
|
+
*
|
|
6
|
+
* R5 asks for a listing that merges what this installation ships with what the
|
|
7
|
+
* providers are serving right now, "from fully supported, to hot loaded", per
|
|
8
|
+
* tool. This module is the CLI half of that; `telegram-models-command.lib.mjs`
|
|
9
|
+
* is the `/models` half, and both render through
|
|
10
|
+
* `model-catalogue-render.lib.mjs` so the two can never disagree.
|
|
11
|
+
*
|
|
12
|
+
* R6 is honoured here too: before printing a catalogue the runner gives the
|
|
13
|
+
* agentic CLIs a chance to update, because a stale `codex` binary is exactly
|
|
14
|
+
* what makes a new model look unavailable.
|
|
15
|
+
*
|
|
16
|
+
* @see https://github.com/link-assistant/hive-mind/issues/2202
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { ensureAgenticCliFreshness, describeFreshnessResult } from './agentic-cli-freshness.lib.mjs';
|
|
20
|
+
import { parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
|
|
21
|
+
import { MODEL_CATALOGUE_TOOLS, getMergedModelCatalogue } from './model-catalogue.lib.mjs';
|
|
22
|
+
import { formatModelCatalogueText } from './model-catalogue-render.lib.mjs';
|
|
23
|
+
|
|
24
|
+
export const HIVE_MODELS_HELP = `Usage: hive-models [--tool <name>...] [--refresh] [--details] [--json] [--no-update] [--verbose]
|
|
25
|
+
|
|
26
|
+
List the models Hive Mind can drive, merged from every source that can be read
|
|
27
|
+
without spending a token — the router's live catalogue, the provider listing
|
|
28
|
+
endpoints, the codex CLI's own catalogue, models.dev metadata, and the models
|
|
29
|
+
bundled with this installation.
|
|
30
|
+
|
|
31
|
+
Models are grouped so it is obvious what each one is:
|
|
32
|
+
Bundled and live shipped here and confirmed reachable now
|
|
33
|
+
Hot loaded a live source has it, this installation does not ship it
|
|
34
|
+
Bundled only shipped here, no live source confirmed it
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
-t, --tool <name> Restrict to one tool (${MODEL_CATALOGUE_TOOLS.join(', ')}).
|
|
38
|
+
Repeatable; defaults to every tool.
|
|
39
|
+
--refresh Ignore the cached answer and re-read every live source
|
|
40
|
+
--details Show context window, pricing, and which source had it
|
|
41
|
+
--json Print machine-readable JSON instead of text
|
|
42
|
+
--no-update Do not check the agentic CLIs for a newer version first
|
|
43
|
+
(also spelled --no-tool-update, as in /solve and /task)
|
|
44
|
+
-v, --verbose Print diagnostics to stderr
|
|
45
|
+
-h, --help Show this help and exit
|
|
46
|
+
|
|
47
|
+
Environment:
|
|
48
|
+
HIVE_MIND_MODELS_HOT_LOAD=0 Only list the bundled catalogue
|
|
49
|
+
HIVE_MIND_MODELS_ROUTER=0 Skip the router source specifically
|
|
50
|
+
HIVE_MIND_MODEL_CATALOGUE_TTL_MINUTES Raise the 60 minute cache lifetime
|
|
51
|
+
HIVE_MIND_AGENTIC_CLI_AUTO_UPDATE=0 Never update the CLIs
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
hive-models # every tool, cached answers
|
|
55
|
+
hive-models --tool codex # just codex
|
|
56
|
+
hive-models --tool claude --details --refresh
|
|
57
|
+
hive-models --json | jq '.tools.claude.liveOnly'
|
|
58
|
+
|
|
59
|
+
Reference:
|
|
60
|
+
https://github.com/link-assistant/hive-mind/issues/2202
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const VALUE_FLAGS = new Set(['--tool', '-t']);
|
|
64
|
+
const BOOLEAN_FLAGS = new Set(['--refresh', '--details', '--json', '--no-update', '--no-tool-update', '--verbose', '-v', '--help', '-h']);
|
|
65
|
+
|
|
66
|
+
// `/solve`, `/hive` and `/task` spell the opt-out `--no-tool-update` (it lives in
|
|
67
|
+
// their `tool-*` namespace). Accept that spelling here too, so the flag an
|
|
68
|
+
// operator already knows works everywhere it makes sense (issue #2202, R6).
|
|
69
|
+
const normaliseUpdateFlag = arg => (arg === '--no-tool-update' ? '--no-update' : arg);
|
|
70
|
+
|
|
71
|
+
const createHiveModelsYargsConfig = yargsInstance => yargsInstance.usage('Usage: hive-models [--tool <name>...] [--refresh] [--details] [--json] [--no-update] [--verbose]').option('tool', { type: 'array', alias: 't', default: [] }).option('refresh', { type: 'boolean', default: false }).option('details', { type: 'boolean', default: false }).option('json', { type: 'boolean', default: false }).option('update', { type: 'boolean', default: true }).option('verbose', { type: 'boolean', alias: 'v', default: false }).option('help', { type: 'boolean', alias: 'h', default: false }).help(false).version(false).strict(false);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse argv for `hive-models`. Returns `error` as a string rather than
|
|
75
|
+
* throwing, so the bin can print it and exit non-zero.
|
|
76
|
+
*/
|
|
77
|
+
export const parseHiveModelsArgs = argv => {
|
|
78
|
+
const result = { tools: [], refresh: false, details: false, json: false, update: true, verbose: false, help: false, error: null };
|
|
79
|
+
const help = argv.includes('--help') || argv.includes('-h');
|
|
80
|
+
|
|
81
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
82
|
+
const arg = argv[index];
|
|
83
|
+
const [name] = arg.split('=');
|
|
84
|
+
if (VALUE_FLAGS.has(name)) {
|
|
85
|
+
if (!arg.includes('=')) index += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (!BOOLEAN_FLAGS.has(arg)) {
|
|
89
|
+
result.error = `Unknown option: ${arg}`;
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = parseCliArgumentsWithLino({
|
|
97
|
+
argv: argv.filter(arg => arg !== '--help' && arg !== '-h').map(normaliseUpdateFlag),
|
|
98
|
+
commandName: 'hive-models',
|
|
99
|
+
createYargsConfig: createHiveModelsYargsConfig,
|
|
100
|
+
lenv: { enabled: false },
|
|
101
|
+
getenv: { enabled: false },
|
|
102
|
+
});
|
|
103
|
+
} catch (err) {
|
|
104
|
+
result.error = err.message || String(err);
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
result.help = help;
|
|
109
|
+
result.refresh = parsed.refresh === true;
|
|
110
|
+
result.details = parsed.details === true;
|
|
111
|
+
result.json = parsed.json === true;
|
|
112
|
+
result.update = parsed.update !== false;
|
|
113
|
+
result.verbose = parsed.verbose === true || parsed.v === true;
|
|
114
|
+
|
|
115
|
+
const requested = []
|
|
116
|
+
.concat(parsed.tool ?? [])
|
|
117
|
+
.flatMap(entry =>
|
|
118
|
+
String(entry)
|
|
119
|
+
.split(',')
|
|
120
|
+
.map(part => part.trim().toLowerCase())
|
|
121
|
+
)
|
|
122
|
+
.filter(Boolean);
|
|
123
|
+
for (const tool of requested) {
|
|
124
|
+
if (!MODEL_CATALOGUE_TOOLS.includes(tool)) {
|
|
125
|
+
result.error = `Unknown tool: ${tool}. Known tools: ${MODEL_CATALOGUE_TOOLS.join(', ')}`;
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
if (!result.tools.includes(tool)) result.tools.push(tool);
|
|
129
|
+
}
|
|
130
|
+
if (result.tools.length === 0) result.tools = [...MODEL_CATALOGUE_TOOLS];
|
|
131
|
+
return result;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Top-level orchestrator used by the bin. `deps` is injected so tests can run
|
|
136
|
+
* the whole command without a network, a router, or a package registry.
|
|
137
|
+
*/
|
|
138
|
+
export const runHiveModels = async (argv, deps = {}) => {
|
|
139
|
+
const { env = process.env, log = (...args) => console.log(...args), error = (...args) => console.error(...args), loadCatalogue = getMergedModelCatalogue, freshness = ensureAgenticCliFreshness } = deps;
|
|
140
|
+
|
|
141
|
+
const args = parseHiveModelsArgs(argv);
|
|
142
|
+
if (args.help) {
|
|
143
|
+
log(HIVE_MODELS_HELP);
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
if (args.error) {
|
|
147
|
+
error(args.error);
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const debug = args.verbose ? (...parts) => error('[hive-models]', ...parts) : () => {};
|
|
152
|
+
|
|
153
|
+
// R6: refresh the CLIs before answering, so the list describes the binaries
|
|
154
|
+
// the next run will actually use. Best-effort — never fatal.
|
|
155
|
+
const refreshed = await freshness({ tools: args.tools, env, verbose: args.verbose, enabled: args.update, log: async message => debug(message) });
|
|
156
|
+
debug(`cli freshness: ${refreshed.status}${refreshed.reason ? ` (${refreshed.reason})` : ''}`);
|
|
157
|
+
const freshnessLine = describeFreshnessResult(refreshed);
|
|
158
|
+
|
|
159
|
+
const results = {};
|
|
160
|
+
let failures = 0;
|
|
161
|
+
for (const tool of args.tools) {
|
|
162
|
+
try {
|
|
163
|
+
results[tool] = await loadCatalogue({ tool, env, refresh: args.refresh, log: async message => debug(message) });
|
|
164
|
+
} catch (err) {
|
|
165
|
+
failures += 1;
|
|
166
|
+
error(`Could not build the ${tool} catalogue: ${err?.message ?? err}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (args.json) {
|
|
171
|
+
log(JSON.stringify({ generatedAt: new Date().toISOString(), cliUpdate: refreshed, tools: results }, null, 2));
|
|
172
|
+
return failures > 0 && Object.keys(results).length === 0 ? 1 : 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (freshnessLine) log(freshnessLine);
|
|
176
|
+
const sections = Object.values(results).map(merged => formatModelCatalogueText(merged, { details: args.details, defaultModel: merged.default }));
|
|
177
|
+
log(sections.join('\n\n'));
|
|
178
|
+
return failures > 0 && sections.length === 0 ? 1 : 0;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export default { HIVE_MODELS_HELP, parseHiveModelsArgs, runHiveModels };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `hive-models` — list every model Hive Mind can drive, merging the models
|
|
5
|
+
* bundled with this installation with the ones the providers are serving right
|
|
6
|
+
* now (issue #2202, R5).
|
|
7
|
+
*
|
|
8
|
+
* Live sources are read only through endpoints that cannot bill a token, and
|
|
9
|
+
* the merged answer is cached for an hour, so running this repeatedly is free.
|
|
10
|
+
*
|
|
11
|
+
* See issue #2202.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { runHiveModels } from './hive-models.lib.mjs';
|
|
15
|
+
import { setupStdioLogInterceptor } from './lib.mjs';
|
|
16
|
+
|
|
17
|
+
setupStdioLogInterceptor();
|
|
18
|
+
|
|
19
|
+
const exitCode = await runHiveModels(process.argv.slice(2));
|
|
20
|
+
process.exit(exitCode);
|
package/src/locales/en.lino
CHANGED
|
@@ -590,6 +590,7 @@ en
|
|
|
590
590
|
usage "Usage: `/hive <github-url> [options]`"
|
|
591
591
|
example "Example: `/hive https://github.com/owner/repo`"
|
|
592
592
|
disabled "*/hive* - ❌ Disabled"
|
|
593
|
+
models "*/models* - List available models, merged from this installation and every live source. Usage: `/models [--tool claude|codex|...] [--details] [--refresh] [--all]`"
|
|
593
594
|
limits "*/limits* - Show usage limits"
|
|
594
595
|
version "*/version* - Show bot and runtime versions"
|
|
595
596
|
language "*/language* `[en|ru|zh|hi]` - Set or show your preferred reply language (in-memory only, per-user)"
|
|
@@ -611,7 +612,7 @@ en
|
|
|
611
612
|
isolation
|
|
612
613
|
mode "🔒 *Isolation Mode:* `{{isolationBackend}}` (experimental)"
|
|
613
614
|
group
|
|
614
|
-
note "⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /watch, /subscribe and /unsubscribe work in private and group chats."
|
|
615
|
+
note "⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /models, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /watch, /subscribe and /unsubscribe work in private and group chats."
|
|
615
616
|
common
|
|
616
617
|
options "🔧 *Common Options:*"
|
|
617
618
|
model
|
package/src/locales/hi.lino
CHANGED
|
@@ -590,6 +590,7 @@ hi
|
|
|
590
590
|
usage "उपयोग: `/hive <github-url> [options]`"
|
|
591
591
|
example "उदाहरण: `/hive https://github.com/owner/repo`"
|
|
592
592
|
disabled "*/hive* - ❌ अक्षम"
|
|
593
|
+
models "*/models* - उपलब्ध models दिखाएँ, जो इस installation और सभी live sources से मिलाकर बनाई गई हैं। उपयोग: `/models [--tool claude|codex|...] [--details] [--refresh] [--all]`"
|
|
593
594
|
limits "*/limits* - उपयोग सीमाएँ दिखाएँ"
|
|
594
595
|
version "*/version* - bot और runtime versions दिखाएँ"
|
|
595
596
|
language "*/language* `[en|ru|zh|hi]` - अपनी पसंदीदा reply language सेट या दिखाएँ (in-memory, per-user)"
|
|
@@ -611,7 +612,7 @@ hi
|
|
|
611
612
|
isolation
|
|
612
613
|
mode "🔒 *Isolation Mode:* `{{isolationBackend}}` (experimental)"
|
|
613
614
|
group
|
|
614
|
-
note "⚠️ *नोट:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /limits, /version, /accept\\_invites, /merge, /stop और /start commands केवल group chats में काम करती हैं। /terminal\\_watch, /watch, /subscribe और /unsubscribe private और group chats में काम करती हैं।"
|
|
615
|
+
note "⚠️ *नोट:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /models, /limits, /version, /accept\\_invites, /merge, /stop और /start commands केवल group chats में काम करती हैं। /terminal\\_watch, /watch, /subscribe और /unsubscribe private और group chats में काम करती हैं।"
|
|
615
616
|
common
|
|
616
617
|
options "🔧 *Common Options:*"
|
|
617
618
|
model
|
package/src/locales/ru.lino
CHANGED
|
@@ -590,6 +590,7 @@ ru
|
|
|
590
590
|
usage "Использование: `/hive <github-url> [options]`"
|
|
591
591
|
example "Пример: `/hive https://github.com/owner/repo`"
|
|
592
592
|
disabled "*/hive* - ❌ Отключено"
|
|
593
|
+
models "*/models* - Показать доступные модели, объединённые из этой установки и всех живых источников. Использование: `/models [--tool claude|codex|...] [--details] [--refresh] [--all]`"
|
|
593
594
|
limits "*/limits* - Показать лимиты использования"
|
|
594
595
|
version "*/version* - Показать версии бота и среды выполнения"
|
|
595
596
|
language "*/language* `[en|ru|zh|hi]` - Установить или показать предпочитаемый язык ответов (в памяти, для пользователя)"
|
|
@@ -611,7 +612,7 @@ ru
|
|
|
611
612
|
isolation
|
|
612
613
|
mode "🔒 *Режим изоляции:* `{{isolationBackend}}` (экспериментально)"
|
|
613
614
|
group
|
|
614
|
-
note "⚠️ *Замечание:* команды /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /limits, /version, /accept\\_invites, /merge, /stop и /start работают только в групповых чатах. /terminal\\_watch, /watch, /subscribe и /unsubscribe работают в личных и групповых чатах."
|
|
615
|
+
note "⚠️ *Замечание:* команды /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /models, /limits, /version, /accept\\_invites, /merge, /stop и /start работают только в групповых чатах. /terminal\\_watch, /watch, /subscribe и /unsubscribe работают в личных и групповых чатах."
|
|
615
616
|
common
|
|
616
617
|
options "🔧 *Общие опции:*"
|
|
617
618
|
model
|
package/src/locales/zh.lino
CHANGED
|
@@ -590,6 +590,7 @@ zh
|
|
|
590
590
|
usage "用法:`/hive <github-url> [options]`"
|
|
591
591
|
example "示例:`/hive https://github.com/owner/repo`"
|
|
592
592
|
disabled "*/hive* - ❌ 已禁用"
|
|
593
|
+
models "*/models* - 列出可用模型,合并本次安装自带的模型与所有实时来源的模型。用法:`/models [--tool claude|codex|...] [--details] [--refresh] [--all]`"
|
|
593
594
|
limits "*/limits* - 显示使用限额"
|
|
594
595
|
version "*/version* - 显示机器人和运行时版本"
|
|
595
596
|
language "*/language* `[en|ru|zh|hi]` - 设置或显示首选回复语言(内存中,按用户)"
|
|
@@ -611,7 +612,7 @@ zh
|
|
|
611
612
|
isolation
|
|
612
613
|
mode "🔒 *隔离模式:* `{{isolationBackend}}`(实验性)"
|
|
613
614
|
group
|
|
614
|
-
note "⚠️ *注意:* /solve、/do、/continue、/claude、/codex、/opencode、/agent、/gemini、/qwen、/task、/split、/fix、/hive、/queue、/limits、/version、/accept\\_invites、/merge、/stop 和 /start 仅在群聊中有效。/terminal\\_watch、/watch、/subscribe 和 /unsubscribe 在私聊和群聊中有效。"
|
|
615
|
+
note "⚠️ *注意:* /solve、/do、/continue、/claude、/codex、/opencode、/agent、/gemini、/qwen、/task、/split、/fix、/hive、/queue、/models、/limits、/version、/accept\\_invites、/merge、/stop 和 /start 仅在群聊中有效。/terminal\\_watch、/watch、/subscribe 和 /unsubscribe 在私聊和群聊中有效。"
|
|
615
616
|
common
|
|
616
617
|
options "🔧 *常用选项:*"
|
|
617
618
|
model
|