@tokagent/tokagentos 2.0.32 → 2.0.34
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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Remove LLM provider API keys (OPENROUTER, ANTHROPIC, OPENAI, GOOGLE, GROQ)
|
|
4
|
+
* from the agents DB table. mergeDbSettings() in @elizaos/core loads
|
|
5
|
+
* agents.secrets and agents.settings.secrets into character.settings.secrets
|
|
6
|
+
* on every boot, and runtime.getSetting() checks character.settings.secrets
|
|
7
|
+
* BEFORE process.env — so a stale DB-stored key shadows the .env value
|
|
8
|
+
* forever. After running this, .env becomes the source of truth.
|
|
9
|
+
*
|
|
10
|
+
* Run with the agent STOPPED (`Ctrl-C` your `bun run dev`):
|
|
11
|
+
* bun run scripts/clear-db-llm-secrets.mjs
|
|
12
|
+
*/
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const PROJECT_ROOT = path.resolve(__dirname, "..");
|
|
19
|
+
const DB_DIR = path.join(PROJECT_ROOT, ".eliza", ".elizadb");
|
|
20
|
+
|
|
21
|
+
if (!existsSync(DB_DIR)) {
|
|
22
|
+
console.error(`PGLite directory not found at ${DB_DIR}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const LLM_KEYS = [
|
|
27
|
+
"OPENROUTER_API_KEY",
|
|
28
|
+
"ANTHROPIC_API_KEY",
|
|
29
|
+
"OPENAI_API_KEY",
|
|
30
|
+
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
31
|
+
"GOOGLE_API_KEY",
|
|
32
|
+
"GROQ_API_KEY",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const { PGlite } = await import("@electric-sql/pglite");
|
|
36
|
+
const db = new PGlite(DB_DIR);
|
|
37
|
+
await db.waitReady;
|
|
38
|
+
|
|
39
|
+
const before = await db.query(
|
|
40
|
+
"SELECT id, name, secrets, settings FROM agents",
|
|
41
|
+
);
|
|
42
|
+
console.log(`Agents in DB: ${before.rows.length}`);
|
|
43
|
+
for (const row of before.rows) {
|
|
44
|
+
const id = row.id;
|
|
45
|
+
const name = row.name;
|
|
46
|
+
const topSecrets = row.secrets && typeof row.secrets === "object" ? row.secrets : {};
|
|
47
|
+
const settingsSecrets =
|
|
48
|
+
row.settings &&
|
|
49
|
+
typeof row.settings === "object" &&
|
|
50
|
+
row.settings.secrets &&
|
|
51
|
+
typeof row.settings.secrets === "object"
|
|
52
|
+
? row.settings.secrets
|
|
53
|
+
: {};
|
|
54
|
+
const allKeys = new Set([
|
|
55
|
+
...Object.keys(topSecrets),
|
|
56
|
+
...Object.keys(settingsSecrets),
|
|
57
|
+
]);
|
|
58
|
+
const llmKeysPresent = [...allKeys].filter((k) => LLM_KEYS.includes(k));
|
|
59
|
+
if (llmKeysPresent.length === 0) {
|
|
60
|
+
console.log(` ${name} (${id}): no LLM keys stored ✓`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
console.log(` ${name} (${id}): clearing ${llmKeysPresent.join(", ")}`);
|
|
64
|
+
const newTopSecrets = { ...topSecrets };
|
|
65
|
+
const newSettings =
|
|
66
|
+
row.settings && typeof row.settings === "object" ? { ...row.settings } : {};
|
|
67
|
+
const newSettingsSecrets = { ...settingsSecrets };
|
|
68
|
+
for (const k of llmKeysPresent) {
|
|
69
|
+
delete newTopSecrets[k];
|
|
70
|
+
delete newSettingsSecrets[k];
|
|
71
|
+
}
|
|
72
|
+
newSettings.secrets = newSettingsSecrets;
|
|
73
|
+
await db.query(
|
|
74
|
+
"UPDATE agents SET secrets = $1, settings = $2 WHERE id = $3",
|
|
75
|
+
[
|
|
76
|
+
JSON.stringify(newTopSecrets),
|
|
77
|
+
JSON.stringify(newSettings),
|
|
78
|
+
id,
|
|
79
|
+
],
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log("\nDone. Restart `bun run dev` — .env is now the source of truth.");
|
|
84
|
+
await db.close();
|
|
@@ -149,6 +149,47 @@ function readDotenv() {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
const env = readDotenv();
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Shell-env vs .env conflict check.
|
|
155
|
+
*
|
|
156
|
+
* Failure mode observed in the field: user rotates an API key in .env, but
|
|
157
|
+
* an old value is still exported in their shell from a prior `export` or a
|
|
158
|
+
* line in ~/.zshrc / ~/.bashrc. dotenv defaults to `override: false`, which
|
|
159
|
+
* means existing process.env values are NOT overwritten by .env. The agent
|
|
160
|
+
* sees the stale shell value, OpenRouter returns 401 "User not found", and
|
|
161
|
+
* the AI SDK surfaces the opaque `AI_NoOutputGeneratedError`. Changing the
|
|
162
|
+
* .env does nothing because the shell value still wins.
|
|
163
|
+
*
|
|
164
|
+
* We don't flip dotenv's `override` flag — that breaks legitimate CI/devops
|
|
165
|
+
* setups that intentionally pass secrets via env vars. Instead we detect
|
|
166
|
+
* the mismatch and tell the user which value will actually be used and how
|
|
167
|
+
* to fix it.
|
|
168
|
+
*/
|
|
169
|
+
const SHADOWED_KEYS = [
|
|
170
|
+
"OPENROUTER_API_KEY",
|
|
171
|
+
"ANTHROPIC_API_KEY",
|
|
172
|
+
"OPENAI_API_KEY",
|
|
173
|
+
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
174
|
+
"GROQ_API_KEY",
|
|
175
|
+
"TAVILY_API_KEY",
|
|
176
|
+
];
|
|
177
|
+
for (const key of SHADOWED_KEYS) {
|
|
178
|
+
const dotenvValue = env[key];
|
|
179
|
+
const shellValue = process.env[key];
|
|
180
|
+
if (dotenvValue && shellValue && dotenvValue !== shellValue) {
|
|
181
|
+
fail(
|
|
182
|
+
`${key} differs between your shell environment and .env.\n` +
|
|
183
|
+
` shell : ${shellValue.slice(0, 12)}…${shellValue.slice(-4)} (this is what the agent reads)\n` +
|
|
184
|
+
` .env : ${dotenvValue.slice(0, 12)}…${dotenvValue.slice(-4)} (this is what you probably edited)\n` +
|
|
185
|
+
` dotenv runs with override:false by default, so the shell value wins. To fix:\n` +
|
|
186
|
+
` unset ${key} # remove from the current shell\n` +
|
|
187
|
+
` grep -n ${key} ~/.zshrc ~/.zprofile ~/.bashrc ~/.bash_profile 2>/dev/null\n` +
|
|
188
|
+
` # delete any matching lines, restart the shell, then re-run \`bun run dev\``,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
152
193
|
if (env.OPENROUTER_API_KEY) {
|
|
153
194
|
const MODEL_KEYS = [
|
|
154
195
|
"OPENROUTER_SMALL_MODEL",
|
|
@@ -188,6 +229,87 @@ if (env.OPENROUTER_API_KEY) {
|
|
|
188
229
|
}
|
|
189
230
|
}
|
|
190
231
|
|
|
232
|
+
/**
|
|
233
|
+
* DB-shadow check.
|
|
234
|
+
*
|
|
235
|
+
* Failure mode observed in the field: an LLM key set during first-boot
|
|
236
|
+
* onboarding is persisted into the `agents` table (`secrets` and
|
|
237
|
+
* `settings.secrets` columns). On every restart, mergeDbSettings() in
|
|
238
|
+
* @elizaos/core copies it into character.settings.secrets, and
|
|
239
|
+
* runtime.getSetting() reads that BEFORE process.env. So rotating .env
|
|
240
|
+
* does nothing — the DB value wins forever, even after .env is changed.
|
|
241
|
+
*
|
|
242
|
+
* We open PGLite read-only, scan agents.secrets and agents.settings.secrets,
|
|
243
|
+
* and warn loudly if any LLM key differs from .env. We don't auto-mutate
|
|
244
|
+
* the DB — clearing secrets is a destructive operation that should be
|
|
245
|
+
* explicit (use scripts/clear-db-llm-secrets.mjs).
|
|
246
|
+
*
|
|
247
|
+
* Best-effort only: skipped if PGLite isn't installed, the DB dir doesn't
|
|
248
|
+
* exist (fresh project), or the `agents` table isn't there yet (pre-boot).
|
|
249
|
+
*/
|
|
250
|
+
async function checkDbShadow() {
|
|
251
|
+
const dbDir = path.join(PROJECT_ROOT, ".eliza", ".elizadb");
|
|
252
|
+
if (!existsSync(dbDir)) return;
|
|
253
|
+
let PGlite;
|
|
254
|
+
try {
|
|
255
|
+
({ PGlite } = await import("@electric-sql/pglite"));
|
|
256
|
+
} catch {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
let db;
|
|
260
|
+
try {
|
|
261
|
+
db = new PGlite(dbDir);
|
|
262
|
+
await db.waitReady;
|
|
263
|
+
} catch {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const result = await db.query(
|
|
268
|
+
"SELECT id, name, secrets, settings FROM agents",
|
|
269
|
+
);
|
|
270
|
+
for (const row of result.rows) {
|
|
271
|
+
const topSecrets =
|
|
272
|
+
row.secrets && typeof row.secrets === "object" ? row.secrets : {};
|
|
273
|
+
const settingsSecrets =
|
|
274
|
+
row.settings &&
|
|
275
|
+
typeof row.settings === "object" &&
|
|
276
|
+
row.settings.secrets &&
|
|
277
|
+
typeof row.settings.secrets === "object"
|
|
278
|
+
? row.settings.secrets
|
|
279
|
+
: {};
|
|
280
|
+
for (const key of SHADOWED_KEYS) {
|
|
281
|
+
const dbValue = topSecrets[key] ?? settingsSecrets[key];
|
|
282
|
+
const dotenvValue = env[key];
|
|
283
|
+
if (!dbValue) continue;
|
|
284
|
+
if (!dotenvValue) {
|
|
285
|
+
// DB has it, .env doesn't. Probably set via onboarding UI and not
|
|
286
|
+
// mirrored. Not necessarily a bug — but flag it so the user knows.
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (dbValue !== dotenvValue) {
|
|
290
|
+
fail(
|
|
291
|
+
`${key} in PGLite database (agent "${row.name}") differs from .env.\n` +
|
|
292
|
+
` db : ${String(dbValue).slice(0, 12)}…${String(dbValue).slice(-4)} (this is what the agent reads)\n` +
|
|
293
|
+
` .env : ${String(dotenvValue).slice(0, 12)}…${String(dotenvValue).slice(-4)} (this is what you probably edited)\n` +
|
|
294
|
+
` mergeDbSettings() in @elizaos/core loads the DB value into character.settings.secrets, and\n` +
|
|
295
|
+
` runtime.getSetting() reads that BEFORE process.env. Editing .env doesn't help.\n` +
|
|
296
|
+
` Fix: stop \`bun run dev\` and run \`bun run scripts/clear-db-llm-secrets.mjs\`,\n` +
|
|
297
|
+
` then start again. .env will then be the source of truth.`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
// agents table doesn't exist yet (fresh boot) — nothing to check.
|
|
304
|
+
} finally {
|
|
305
|
+
try {
|
|
306
|
+
await db.close();
|
|
307
|
+
} catch {}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
await checkDbShadow();
|
|
312
|
+
|
|
191
313
|
if (hadFailure) {
|
|
192
314
|
console.error(
|
|
193
315
|
"\n\x1b[31m[verify-llm-plugins]\x1b[0m One or more LLM-provider checks failed. Chat will not work until this is resolved.\n",
|