colana 1.0.0-beta.49 → 1.0.0-beta.51
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 +1 -1
- package/server/personal-agent-routes.js +16 -12
- package/server/pty-manager.js +59 -38
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getOpenClawAuthToken, getOpenClawGatewayPort, getOpenClawDefaultModel, getOpenClawConfig, isChatCompletionsEnabled, ensureChatCompletionsEnabled, ensureAuthProfile, readAuthStore } from './openclaw-config.js';
|
|
2
2
|
import { validate, personalChatSchema, openclawModelSetSchema, openclawModelAuthSchema } from './validation.js';
|
|
3
3
|
import { setSetting } from './settings-store.js';
|
|
4
|
-
import { restartOpenClawGateway } from './pty-manager.js';
|
|
4
|
+
import { restartOpenClawGateway, isGatewaySpawnedByColana } from './pty-manager.js';
|
|
5
5
|
import { addChatMessage, getChatHistory, clearChatHistory } from './personal-chat-store.js';
|
|
6
6
|
import config from './config.js';
|
|
7
7
|
import { trackEvent } from './analytics.js';
|
|
@@ -102,12 +102,6 @@ export function registerPersonalAgentRoutes(app, { sensitiveLimiter }) {
|
|
|
102
102
|
const authToken = getOpenClawAuthToken();
|
|
103
103
|
headers['Authorization'] = `Bearer ${authToken}`;
|
|
104
104
|
|
|
105
|
-
// Diagnostic: log token source for debugging auth failures
|
|
106
|
-
const cfg = getOpenClawConfig();
|
|
107
|
-
const nativeToken = cfg?.gateway?.auth?.token;
|
|
108
|
-
const tokenSource = nativeToken ? 'native-config' : 'colana-managed';
|
|
109
|
-
console.log(`[personal-agent] Auth: ${tokenSource}, token=${authToken?.slice(0, 8)}..., port=${port}`);
|
|
110
|
-
|
|
111
105
|
// 60-second timeout
|
|
112
106
|
const controller = new AbortController();
|
|
113
107
|
const timeout = setTimeout(() => controller.abort(), 60000);
|
|
@@ -157,15 +151,25 @@ export function registerPersonalAgentRoutes(app, { sensitiveLimiter }) {
|
|
|
157
151
|
const providerLabel = provider || null;
|
|
158
152
|
const envVar = provider ? PROVIDER_ENV_MAP[provider] : null;
|
|
159
153
|
|
|
160
|
-
//
|
|
154
|
+
// Distinguish gateway-level 401 (our token rejected) from upstream
|
|
155
|
+
// provider 401 (API key invalid/missing). The gateway proxies upstream
|
|
156
|
+
// 401s as-is, so we can't rely on the error body format alone.
|
|
157
|
+
//
|
|
158
|
+
// Strategy: check if Colana spawned this gateway with our current token
|
|
159
|
+
// (marker file). If yes, gateway auth is fine → upstream provider issue.
|
|
160
|
+
// Also detect known provider error patterns in the response body.
|
|
161
161
|
const isProviderAuth = errText.includes('api_key') || errText.includes('API key')
|
|
162
|
-
|| errText.includes('invalid_api_key') || errText.includes('authentication_error')
|
|
162
|
+
|| errText.includes('invalid_api_key') || errText.includes('authentication_error')
|
|
163
|
+
|| errText.includes('"type":"unauthorized"') || errText.includes('"type": "unauthorized"');
|
|
164
|
+
|
|
165
|
+
// If we spawned this gateway with our token, gateway auth is guaranteed OK
|
|
166
|
+
const gatewayAuthOk = isProviderAuth || isGatewaySpawnedByColana();
|
|
163
167
|
|
|
164
|
-
if (
|
|
168
|
+
if (gatewayAuthOk) {
|
|
165
169
|
return res.status(401).json({
|
|
166
170
|
error: providerLabel
|
|
167
171
|
? `Authentication failed — your ${providerLabel} API key may be missing or invalid.`
|
|
168
|
-
: 'Authentication failed — your API key may be missing or invalid.',
|
|
172
|
+
: 'Authentication failed — your model provider API key may be missing or invalid.',
|
|
169
173
|
code: 'GATEWAY_AUTH_FAILED',
|
|
170
174
|
fix: envVar
|
|
171
175
|
? `Add or update your ${envVar} in Settings > Personal AI Agents.`
|
|
@@ -174,7 +178,7 @@ export function registerPersonalAgentRoutes(app, { sensitiveLimiter }) {
|
|
|
174
178
|
});
|
|
175
179
|
}
|
|
176
180
|
|
|
177
|
-
//
|
|
181
|
+
// Gateway itself rejected our token — needs restart to resync
|
|
178
182
|
return res.status(401).json({
|
|
179
183
|
error: 'Gateway authentication failed — the auth token may be stale.',
|
|
180
184
|
code: 'GATEWAY_TOKEN_STALE',
|
package/server/pty-manager.js
CHANGED
|
@@ -315,41 +315,49 @@ async function ensureOpenClawConfigured() {
|
|
|
315
315
|
});
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
// Marker file: tracks which gateway instance Colana spawned and with which token.
|
|
319
|
+
// On startup, if the running gateway wasn't spawned by us (no marker / token mismatch),
|
|
320
|
+
// we kill it and restart with our AUTH_TOKEN env var.
|
|
321
|
+
const GATEWAY_MARKER_PATH = path.join(os.homedir(), '.openclaw', '.colana-gateway-marker');
|
|
322
|
+
|
|
318
323
|
/**
|
|
319
|
-
* Verify that the running gateway
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
324
|
+
* Verify that the running gateway was spawned by Colana with our current token.
|
|
325
|
+
* The OpenClaw gateway's /v1/models endpoint does NOT require auth, so HTTP
|
|
326
|
+
* probing is unreliable. Instead, we use a marker file written at spawn time.
|
|
327
|
+
*
|
|
328
|
+
* Returns true if gateway was spawned by us with the matching token.
|
|
323
329
|
*/
|
|
324
|
-
|
|
330
|
+
function verifyGatewayToken(_port, token) {
|
|
325
331
|
try {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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,
|
|
341
|
-
});
|
|
342
|
-
clearTimeout(timeout);
|
|
343
|
-
// 401 = token rejected. Anything else (200, 400, 500, etc.) = token accepted.
|
|
344
|
-
const accepted = res.status !== 401;
|
|
345
|
-
if (!accepted) {
|
|
346
|
-
const body = await res.text().catch(() => '');
|
|
347
|
-
console.log(`[pty-manager] Gateway token probe: 401 — ${body.slice(0, 200)}`);
|
|
332
|
+
if (fs.existsSync(GATEWAY_MARKER_PATH)) {
|
|
333
|
+
const marker = JSON.parse(fs.readFileSync(GATEWAY_MARKER_PATH, 'utf-8'));
|
|
334
|
+
if (marker.token === token) {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
console.log(`[pty-manager] Gateway marker token mismatch (marker=${marker.token?.slice(0, 8)}..., ours=${token?.slice(0, 8)}...)`);
|
|
338
|
+
} else {
|
|
339
|
+
console.log('[pty-manager] No gateway marker file — gateway was not started by Colana');
|
|
348
340
|
}
|
|
349
|
-
return accepted;
|
|
350
341
|
} catch (err) {
|
|
351
|
-
console.log(`[pty-manager]
|
|
352
|
-
|
|
342
|
+
console.log(`[pty-manager] Failed to read gateway marker: ${err.message}`);
|
|
343
|
+
}
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Write a marker file after successfully spawning the gateway.
|
|
349
|
+
* Records the token and timestamp so subsequent startups can verify.
|
|
350
|
+
*/
|
|
351
|
+
function writeGatewayMarker(token) {
|
|
352
|
+
try {
|
|
353
|
+
const dir = path.dirname(GATEWAY_MARKER_PATH);
|
|
354
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
355
|
+
fs.writeFileSync(GATEWAY_MARKER_PATH, JSON.stringify({
|
|
356
|
+
token,
|
|
357
|
+
timestamp: Date.now(),
|
|
358
|
+
}), 'utf-8');
|
|
359
|
+
} catch (err) {
|
|
360
|
+
console.warn(`[pty-manager] Failed to write gateway marker: ${err.message}`);
|
|
353
361
|
}
|
|
354
362
|
}
|
|
355
363
|
|
|
@@ -399,17 +407,17 @@ async function ensureOpenClawGateway(port) {
|
|
|
399
407
|
}
|
|
400
408
|
|
|
401
409
|
if (await tcpProbe('127.0.0.1', port)) {
|
|
402
|
-
// Gateway is listening —
|
|
403
|
-
// The gateway
|
|
404
|
-
//
|
|
410
|
+
// Gateway is listening — check if we spawned it with our current token.
|
|
411
|
+
// The gateway's /v1/models endpoint does NOT require auth, so HTTP probing
|
|
412
|
+
// is unreliable. We use a marker file written at spawn time instead.
|
|
405
413
|
const token = getOrCreateGatewayToken();
|
|
406
|
-
const tokenValid =
|
|
414
|
+
const tokenValid = verifyGatewayToken(port, token);
|
|
407
415
|
if (tokenValid) {
|
|
408
|
-
console.log('[pty-manager] OpenClaw gateway already running, token valid');
|
|
416
|
+
console.log('[pty-manager] OpenClaw gateway already running, token valid (marker match)');
|
|
409
417
|
return;
|
|
410
418
|
}
|
|
411
|
-
//
|
|
412
|
-
console.log('[pty-manager] Gateway running but token
|
|
419
|
+
// Gateway was not started by us or token changed — kill and restart with our token
|
|
420
|
+
console.log('[pty-manager] Gateway running but not started by Colana (or token changed) — restarting with AUTH_TOKEN...');
|
|
413
421
|
await killGatewayOnPort(port);
|
|
414
422
|
// Fall through to spawn new gateway
|
|
415
423
|
}
|
|
@@ -425,13 +433,14 @@ async function ensureOpenClawGateway(port) {
|
|
|
425
433
|
const logFile = path.join(logDir, 'openclaw-gateway.log');
|
|
426
434
|
const logFd = fs.openSync(logFile, 'a');
|
|
427
435
|
|
|
436
|
+
// Hoist token so it's accessible in the poll loop (for marker file write)
|
|
437
|
+
const gatewayToken = getOrCreateGatewayToken();
|
|
428
438
|
let gwProcess;
|
|
429
439
|
try {
|
|
430
440
|
const { buildSafeSpawnEnv: buildGwEnv } = await import('./spawn-env.js');
|
|
431
441
|
// Pass AUTH_TOKEN so the gateway uses a known bearer token for auth.
|
|
432
442
|
// On platforms where OpenClaw config has gateway.auth: false (Windows),
|
|
433
443
|
// this env var is the only way to enable token auth on the gateway.
|
|
434
|
-
const gatewayToken = getOrCreateGatewayToken();
|
|
435
444
|
const gwEnv = buildGwEnv({
|
|
436
445
|
GOG_KEYRING_PASSWORD: process.env.GOG_KEYRING_PASSWORD || config.deriveGogKeyringPassword(),
|
|
437
446
|
AUTH_TOKEN: gatewayToken,
|
|
@@ -502,6 +511,7 @@ async function ensureOpenClawGateway(port) {
|
|
|
502
511
|
|
|
503
512
|
if (await tcpProbe('127.0.0.1', port)) {
|
|
504
513
|
fs.closeSync(logFd);
|
|
514
|
+
writeGatewayMarker(gatewayToken);
|
|
505
515
|
console.log('[pty-manager] OpenClaw gateway is now running');
|
|
506
516
|
return;
|
|
507
517
|
}
|
|
@@ -2099,6 +2109,8 @@ export async function restartOpenClawGateway() {
|
|
|
2099
2109
|
|
|
2100
2110
|
if (isRunning) {
|
|
2101
2111
|
console.log('[pty-manager] Restarting OpenClaw gateway to reload MCP config...');
|
|
2112
|
+
// Clear marker before kill so ensureOpenClawGateway knows to respawn
|
|
2113
|
+
try { fs.unlinkSync(GATEWAY_MARKER_PATH); } catch { /* may not exist */ }
|
|
2102
2114
|
await killGatewayOnPort(gwPort);
|
|
2103
2115
|
} else {
|
|
2104
2116
|
console.log('[pty-manager] OpenClaw gateway not running — starting fresh...');
|
|
@@ -2118,6 +2130,15 @@ export async function restartOpenClawGateway() {
|
|
|
2118
2130
|
// Start idle cleanup on module load
|
|
2119
2131
|
startIdleCleanup();
|
|
2120
2132
|
|
|
2133
|
+
/**
|
|
2134
|
+
* Check if the running gateway was spawned by Colana with the current token.
|
|
2135
|
+
* Used by personal-agent-routes to distinguish gateway-level 401 from provider-level 401.
|
|
2136
|
+
*/
|
|
2137
|
+
export function isGatewaySpawnedByColana() {
|
|
2138
|
+
const token = getOrCreateGatewayToken();
|
|
2139
|
+
return verifyGatewayToken(null, token);
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2121
2142
|
export { ensureOpenClawGateway };
|
|
2122
2143
|
|
|
2123
2144
|
export default {
|