colana 1.0.0-beta.49 → 1.0.0-beta.50

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "colana",
3
- "version": "1.0.0-beta.49",
3
+ "version": "1.0.0-beta.50",
4
4
  "description": "Agent-First. Multiplied. Multi-agent command center for AI coding agents.",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -157,15 +157,35 @@ export function registerPersonalAgentRoutes(app, { sensitiveLimiter }) {
157
157
  const providerLabel = provider || null;
158
158
  const envVar = provider ? PROVIDER_ENV_MAP[provider] : null;
159
159
 
160
- // Check if the error body hints at provider-level auth failure
160
+ // Check if the error body hints at provider-level auth failure.
161
+ // OpenClaw gateway proxies upstream 401s as-is. The upstream error body
162
+ // varies by provider — some include "api_key", others just "unauthorized".
163
+ // We also probe /v1/models with our token: if that works, the gateway
164
+ // accepts our token and the 401 must be from the upstream provider.
161
165
  const isProviderAuth = errText.includes('api_key') || errText.includes('API key')
162
- || errText.includes('invalid_api_key') || errText.includes('authentication_error');
166
+ || errText.includes('invalid_api_key') || errText.includes('authentication_error')
167
+ || errText.includes('"type":"unauthorized"') || errText.includes('"type": "unauthorized"');
168
+
169
+ // If provider-level detection didn't match, verify if gateway auth is actually fine
170
+ // by probing /v1/models (lightweight, always available)
171
+ let gatewayAuthOk = isProviderAuth;
172
+ if (!gatewayAuthOk) {
173
+ try {
174
+ const probeRes = await fetch(`http://127.0.0.1:${port}/v1/models`, {
175
+ headers: { 'Authorization': `Bearer ${authToken}` },
176
+ signal: AbortSignal.timeout(3000),
177
+ });
178
+ // If /v1/models returns 200, our gateway token is accepted —
179
+ // so the 401 on /v1/chat/completions is from the upstream provider
180
+ gatewayAuthOk = probeRes.ok;
181
+ } catch { /* probe failed — assume gateway token issue */ }
182
+ }
163
183
 
164
- if (isProviderAuth) {
184
+ if (gatewayAuthOk) {
165
185
  return res.status(401).json({
166
186
  error: providerLabel
167
187
  ? `Authentication failed — your ${providerLabel} API key may be missing or invalid.`
168
- : 'Authentication failed — your API key may be missing or invalid.',
188
+ : 'Authentication failed — your model provider API key may be missing or invalid.',
169
189
  code: 'GATEWAY_AUTH_FAILED',
170
190
  fix: envVar
171
191
  ? `Add or update your ${envVar} in Settings > Personal AI Agents.`
@@ -174,7 +194,7 @@ export function registerPersonalAgentRoutes(app, { sensitiveLimiter }) {
174
194
  });
175
195
  }
176
196
 
177
- // Default: gateway token mismatch (token exists locally but doesn't match gateway)
197
+ // Gateway itself rejected our token needs restart to resync
178
198
  return res.status(401).json({
179
199
  error: 'Gateway authentication failed — the auth token may be stale.',
180
200
  code: 'GATEWAY_TOKEN_STALE',
@@ -317,30 +317,18 @@ async function ensureOpenClawConfigured() {
317
317
 
318
318
  /**
319
319
  * Verify that the running gateway accepts our bearer token.
320
- * Uses POST /v1/chat/completions with a minimal body the same endpoint
321
- * the chat route uses to ensure auth is truly valid end-to-end.
320
+ * Uses GET /v1/models a lightweight endpoint that checks gateway-level
321
+ * auth without hitting any upstream provider (avoids false 401s from
322
+ * missing/invalid provider API keys).
322
323
  * Returns true if token is accepted, false if 401.
323
324
  */
324
325
  async function verifyGatewayToken(port, token) {
325
326
  try {
326
- const controller = new AbortController();
327
- const timeout = setTimeout(() => controller.abort(), 5000);
328
- // Use the actual chat endpoint — /v1/models may not require auth
329
- const res = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
330
- method: 'POST',
331
- headers: {
332
- 'Content-Type': 'application/json',
333
- 'Authorization': `Bearer ${token}`,
334
- 'x-openclaw-agent-id': 'main',
335
- },
336
- body: JSON.stringify({
337
- messages: [{ role: 'user', content: 'ping' }],
338
- max_tokens: 1,
339
- }),
340
- signal: controller.signal,
327
+ const res = await fetch(`http://127.0.0.1:${port}/v1/models`, {
328
+ headers: { 'Authorization': `Bearer ${token}` },
329
+ signal: AbortSignal.timeout(3000),
341
330
  });
342
- clearTimeout(timeout);
343
- // 401 = token rejected. Anything else (200, 400, 500, etc.) = token accepted.
331
+ // 401 = gateway rejected our token. Anything else = token accepted.
344
332
  const accepted = res.status !== 401;
345
333
  if (!accepted) {
346
334
  const body = await res.text().catch(() => '');