Repository navigation
fix: hermes edition handling for status, configure, catalog and clawkeep #364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
51b360d
b657b53
ad01000
8c82c72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,9 +15,13 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applyModelOverrideToAllAgentSessions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parseFullyQualifiedModel, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setProviderPlugins, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| openclawIsAbsent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OpenclawUnavailableError, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@/lib/openclaw-config"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getActiveHarness } from "@/lib/harness"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { applyLocalAiToHermes } from "@/lib/hermes-local-ai"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { applyLocalAiToHermes, HermesLocalApplyError } from "@/lib/hermes-local-ai"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { applyClawaiToHermes, ClawaiApplyError } from "@/lib/hermes-clawai"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { applyCloudProviderKeyToHermes, HermesCloudApplyError } from "@/lib/hermes-cloud-provider"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getDefaultLlamaCppModel, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getLlamaCppContextWindow, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -436,6 +440,11 @@ async function writeOpenAICompatProvider(opts: { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function POST(request: Request) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Hoisted so the catch can classify the failure without re-parsing the body — | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // a local-model or wrong-edition failure must not be reported as a credential | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // problem (there is no credential to check). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let requestProvider: string | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let requestScope: ConfigureScope = "primary"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let body: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| provider?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -505,6 +514,8 @@ export async function POST(request: Request) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { provider, apiKey, authMode = "token", idToken, refreshToken, expiresIn, projectId, scope = "primary", model: bodyModel } = body; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestProvider = provider; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestScope = scope; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const requestedClawboxAiTier = normalizeClawboxAiTier(body.clawaiTier); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const normalizedApiKey = typeof apiKey === "string" ? apiKey.trim() : ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isOllama = provider === "ollama"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -681,6 +692,73 @@ export async function POST(request: Request) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ── Hermes edition: no openclaw binary exists here ────────────────────── | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Everything below configures OpenClaw — it writes ~/.openclaw, runs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // `openclaw config set …`, and restarts the gateway. On the Hermes SKU there | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // is no openclaw binary, so the API-key path used to fail with `spawn | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // openclaw ENOENT` and then blame the user's credentials, while the local | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // (Gemma) switch failed the same way. Route to Hermes' own config store | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // instead — the same one the OAuth/token path already writes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (openclawIsAbsent()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isLocalScope && (isLlamaCpp || isOllama)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // On-device model (Gemma via llama.cpp, or Ollama): persist the same | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // config-store keys the OpenClaw path would, then register it with | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Hermes as an available provider (activating it only on a fresh | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // device, matching shouldPromoteLocalToPrimary). No gateway restart. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await setMany({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local_ai_configured: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local_ai_provider: ocProvider, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local_ai_model: config.defaultModel, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local_ai_configured_at: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await applyLocalAiToHermes({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| provider: ocProvider as "llamacpp" | "ollama", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Hermes wants the bare model id, not the `llamacpp/…` qualified form. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model: config.defaultModel.replace(/^(?:llamacpp|ollama)\//, ""), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| makeDefault: shouldPromoteLocalToPrimary, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ success: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+702
to
+722
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Local providers with primary scope fall into the cloud-provider branch on Hermes. Branch A gates on Trace
For llama.cpp the
Gate Branch A on the provider, not the scope. 🐛 Proposed fix for the branch gate- if (isLocalScope && (isLlamaCpp || isOllama)) {
+ if (isLlamaCpp || isOllama) {
// On-device model (Gemma via llama.cpp, or Ollama): persist the same
// config-store keys the OpenClaw path would, then register it with
// Hermes as an available provider (activating it only on a fresh
// device, matching shouldPromoteLocalToPrimary). No gateway restart.
await setMany({
local_ai_configured: true,
local_ai_provider: ocProvider,
local_ai_model: config.defaultModel,
local_ai_configured_at: new Date().toISOString(),
});
await applyLocalAiToHermes({
provider: ocProvider as "llamacpp" | "ollama",
// Hermes wants the bare model id, not the `llamacpp/…` qualified form.
model: config.defaultModel.replace(/^(?:llamacpp|ollama)\//, ""),
- makeDefault: shouldPromoteLocalToPrimary,
+ // A primary-scope request selects the model explicitly, so activate
+ // it; a local-scope request activates only on a fresh device.
+ makeDefault: !isLocalScope || shouldPromoteLocalToPrimary,
});
return NextResponse.json({ success: true });
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isClawAI) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await applyClawaiToHermes(clawboxAiToken, resolvedClawboxTier ?? CLAWBOX_AI_DEFAULT_TIER); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ success: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (authMode !== "subscription" && normalizedApiKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Cloud API-key providers Hermes supports (anthropic, google→gemini, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // openrouter). The credential lands in Hermes' own store and the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // provider is activated through Hermes' catalog. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await applyCloudProviderKeyToHermes({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| openclawProvider: provider, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| apiKey: normalizedApiKey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (result.activated) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ success: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { error: "Key saved. Open the Hermes provider panel to pick a model for it." }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { status: 409 }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+735
to
+741
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find the client code that posts to the configure route and inspect its status handling.
rg -nP -C 10 'ai-models/configure' src/components/ src/hooks/ src/app/ -g '!**/route.ts'Repository: ID-Robots/clawbox Length of output: 4835 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- route Hermes branch and error mapping ---'
sed -n '680,770p' src/app/setup-api/ai-models/configure/route.ts
printf '%s\n' '--- Hermes helper definitions and error classes ---'
rg -n -P -C 12 'class Hermes(Local|Cloud)ApplyError|apply(LocalAi|Clawai|CloudProviderKey)ToHermes|Hermes.*ApplyError' src
printf '%s\n' '--- client status and error handling ---'
rg -n -P -C 18 'showError|extractError|selectedProvider|provider.*hermes|hermes' src/components/AIModelsStep.tsxRepository: ID-Robots/clawbox Length of output: 50374 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Hermes helper error behavior ---'
sed -n '1,180p' src/lib/hermes-local-ai.ts
sed -n '1,150p' src/lib/hermes-cloud-provider.ts
sed -n '1,130p' src/lib/hermes-clawai.ts
printf '%s\n' '--- wizard response handling ---'
sed -n '930,990p' src/components/AIModelsStep.tsx
printf '%s\n' '--- route tests for the disputed responses ---'
rg -n -P -C 10 '409|502|activated|saved|ApplyError|status\)' src/tests/routes/ai-models/configure-hermes.test.tsRepository: ID-Robots/clawbox Length of output: 25599 🏁 Script executed: #!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
client = Path("src/components/AIModelsStep.tsx").read_text()
route = Path("src/app/setup-api/ai-models/configure/route.ts").read_text()
local = Path("src/lib/hermes-local-ai.ts").read_text()
cloud = Path("src/lib/hermes-cloud-provider.ts").read_text()
clawai = Path("src/lib/hermes-clawai.ts").read_text()
assert 'if (!res.ok) return showError(await extractError(res, "Failed to configure"));' in client
assert 'showSuccessAndContinue();' in client
assert '{ status: 409 }' in route
assert '{ error: err.message }, { status: 502 }' in route
assert 'Local model id is missing or malformed.' in local
assert 'An API key is required.' in cloud
assert 'class HermesLocalApplyError extends Error {}' in local
assert 'class HermesCloudApplyError extends Error {' in cloud
assert 'class ClawaiApplyError extends Error {}' in clawai
print("wizard treats every non-2xx configure response as an error")
print("configure route returns 409 for stored-but-unactivated keys")
print("configure route returns 502 for all caught Hermes helper errors")
print("validation messages share helper error classes with CLI failure messages")
PYRepository: ID-Robots/clawbox Length of output: 411 Align Hermes response semantics with the wizard.
🤖 Prompt for AI AgentsSource: Path instructions |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // OAuth / subscription providers (and OpenAI, which is OAuth-only on | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Hermes) sign in through the Hermes provider panel, not this route. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { error: "This provider is set up through the Hermes provider panel on this edition." }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { status: 400 }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err instanceof HermesCloudApplyError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || err instanceof HermesLocalApplyError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || err instanceof ClawaiApplyError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Author-controlled, non-credential message — safe to echo. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: err.message }, { status: 502 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw err; // unexpected — fall to the outer catch, which classifies it | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 1. Write token to auth-profiles.json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ── AUDIT: schema-drift risk ─────────────────────────────────── | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1128,9 +1206,20 @@ export async function POST(request: Request) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // paths. Log it server-side for diagnosis and return a generic, actionable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // message (mirrors the sanitized gateway-restart branch above). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("[configure] Failed to configure AI model:", err instanceof Error ? err.message : err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { error: "Failed to configure AI model. Please check your credentials and try again." }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { status: 500 } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Classify so the message matches the cause. A local on-device model has no | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // credentials, and an edition without the openclaw binary is not something | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // the user can fix by re-checking a key — "check your credentials" is wrong | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // and un-actionable for both. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isLocalRequest = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestScope === "local" || requestProvider === "ollama" || requestProvider === "llamacpp"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let message: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (err instanceof OpenclawUnavailableError) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message = "This action isn't available on this edition."; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (isLocalRequest) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message = "Couldn't set up the on-device model. Please try again."; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message = "Failed to configure AI model. Please check your credentials and try again."; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: message }, { status: 500 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Persist the local-model state only after Hermes accepts it.
setManymarkslocal_ai_configured: truebeforeapplyLocalAiToHermesruns. If the Hermes CLI call fails, the route returns 502 while the config store already reports the local model as configured. Hermes has no matching provider block at that point, so the status route reports a model that the device cannot serve.applyCloudProviderKeyToHermesinsrc/lib/hermes-cloud-provider.ts(Lines 121-127) uses the opposite order: it writesai_model_configuredonly after activation succeeds. Apply the same order here.🐛 Proposed reorder
📝 Committable suggestion
🤖 Prompt for AI Agents