@ouro.bot/cli 0.1.0-alpha.364 → 0.1.0-alpha.365
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/README.md +15 -6
- package/changelog.json +9 -0
- package/dist/heart/auth/auth-flow.js +25 -110
- package/dist/heart/config.js +69 -55
- package/dist/heart/core.js +83 -33
- package/dist/heart/daemon/agent-config-check.js +41 -238
- package/dist/heart/daemon/agentic-repair.js +1 -1
- package/dist/heart/daemon/cli-defaults.js +15 -68
- package/dist/heart/daemon/cli-exec.js +246 -89
- package/dist/heart/daemon/cli-parse.js +71 -0
- package/dist/heart/daemon/daemon-cli.js +1 -2
- package/dist/heart/daemon/daemon-entry.js +1 -3
- package/dist/heart/daemon/doctor.js +9 -29
- package/dist/heart/daemon/provider-discovery.js +32 -59
- package/dist/heart/hatch/hatch-flow.js +9 -12
- package/dist/heart/hatch/specialist-prompt.js +1 -1
- package/dist/heart/hatch/specialist-tools.js +21 -1
- package/dist/heart/migrate-config.js +15 -42
- package/dist/heart/provider-binding-resolver.js +6 -7
- package/dist/heart/provider-credentials.js +379 -0
- package/dist/heart/provider-failover.js +3 -11
- package/dist/heart/provider-ping.js +13 -3
- package/dist/heart/provider-state.js +8 -0
- package/dist/heart/provider-visibility.js +3 -6
- package/dist/heart/providers/anthropic-token.js +15 -47
- package/dist/heart/providers/anthropic.js +4 -9
- package/dist/heart/providers/azure.js +3 -3
- package/dist/heart/providers/github-copilot.js +2 -2
- package/dist/heart/providers/minimax-vlm.js +2 -2
- package/dist/heart/providers/minimax.js +1 -1
- package/dist/heart/providers/openai-codex.js +4 -9
- package/dist/repertoire/bitwarden-store.js +63 -17
- package/dist/repertoire/bundle-templates.js +2 -2
- package/dist/repertoire/credential-access.js +47 -467
- package/dist/repertoire/tools-attachments.js +5 -4
- package/dist/repertoire/tools-vault.js +10 -80
- package/dist/repertoire/vault-unlock.js +359 -0
- package/dist/senses/bluebubbles/client.js +39 -4
- package/dist/senses/pipeline.js +0 -1
- package/package.json +1 -1
- package/dist/heart/provider-credential-pool.js +0 -395
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Credential access layer
|
|
3
|
+
* Credential access layer.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - AacCredentialStore: delegates to the `aac` CLI (Bitwarden Agent Access)
|
|
5
|
+
* Bitwarden/Vaultwarden is the sole runtime credential store. The only local
|
|
6
|
+
* secret is the vault unlock material held by an explicit local unlock store.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Each agent owns one credential vault. getCredentialStore() returns that
|
|
9
|
+
* agent's vault directly; item names are not shared or namespaced across
|
|
10
|
+
* agents.
|
|
11
11
|
*/
|
|
12
12
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
13
|
if (k2 === undefined) k2 = k;
|
|
@@ -43,485 +43,65 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
43
43
|
};
|
|
44
44
|
})();
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
-
exports.AacCredentialStore = exports.BuiltInCredentialStore = void 0;
|
|
47
|
-
exports.domainToSlug = domainToSlug;
|
|
48
|
-
exports.ensureVaultDefaults = ensureVaultDefaults;
|
|
49
46
|
exports.getCredentialStore = getCredentialStore;
|
|
50
47
|
exports.resetCredentialStore = resetCredentialStore;
|
|
51
48
|
const crypto = __importStar(require("node:crypto"));
|
|
52
49
|
const fs = __importStar(require("node:fs"));
|
|
53
|
-
const path = __importStar(require("node:path"));
|
|
54
50
|
const os = __importStar(require("node:os"));
|
|
55
|
-
const
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
56
52
|
const runtime_1 = require("../nerves/runtime");
|
|
57
53
|
const identity = __importStar(require("../heart/identity"));
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return domain
|
|
64
|
-
.toLowerCase()
|
|
65
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
66
|
-
.replace(/^-+|-+$/g, "");
|
|
67
|
-
}
|
|
68
|
-
class BuiltInCredentialStore {
|
|
69
|
-
vaultDir;
|
|
70
|
-
keyPath;
|
|
71
|
-
masterKey = null;
|
|
72
|
-
constructor(agentName, vaultDir, keyDir) {
|
|
73
|
-
const homeBase = process.env.WEBSITE_SITE_NAME ? "/home" : os.homedir();
|
|
74
|
-
this.vaultDir = vaultDir ?? path.join(homeBase, "AgentBundles", `${agentName}.ouro`, "vault");
|
|
75
|
-
const effectiveKeyDir = keyDir ?? path.join(homeBase, ".agentsecrets", agentName);
|
|
76
|
-
this.keyPath = path.join(effectiveKeyDir, "vault.key");
|
|
77
|
-
}
|
|
78
|
-
isReady() {
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
async get(domain) {
|
|
82
|
-
(0, runtime_1.emitNervesEvent)({
|
|
83
|
-
event: "repertoire.credential_get_start",
|
|
84
|
-
component: "repertoire",
|
|
85
|
-
message: `getting credential for ${domain}`,
|
|
86
|
-
meta: { domain },
|
|
87
|
-
});
|
|
88
|
-
const stored = this.readEncrypted(domain);
|
|
89
|
-
if (!stored) {
|
|
90
|
-
(0, runtime_1.emitNervesEvent)({
|
|
91
|
-
event: "repertoire.credential_get_end",
|
|
92
|
-
component: "repertoire",
|
|
93
|
-
message: `no credential found for ${domain}`,
|
|
94
|
-
meta: { domain, found: false },
|
|
95
|
-
});
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
(0, runtime_1.emitNervesEvent)({
|
|
99
|
-
event: "repertoire.credential_get_end",
|
|
100
|
-
component: "repertoire",
|
|
101
|
-
message: `credential found for ${domain}`,
|
|
102
|
-
meta: { domain, found: true },
|
|
103
|
-
});
|
|
104
|
-
return {
|
|
105
|
-
domain: stored.domain,
|
|
106
|
-
username: stored.username,
|
|
107
|
-
notes: stored.notes,
|
|
108
|
-
createdAt: stored.createdAt,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
async getRawSecret(domain, field) {
|
|
112
|
-
const stored = this.readEncrypted(domain);
|
|
113
|
-
if (!stored) {
|
|
114
|
-
(0, runtime_1.emitNervesEvent)({
|
|
115
|
-
level: "error",
|
|
116
|
-
event: "repertoire.credential_get_error",
|
|
117
|
-
component: "repertoire",
|
|
118
|
-
message: `no credential found for domain "${domain}"`,
|
|
119
|
-
meta: { domain, field, reason: `no credential found for domain "${domain}"` },
|
|
120
|
-
});
|
|
121
|
-
throw new Error(`no credential found for domain "${domain}"`);
|
|
122
|
-
}
|
|
123
|
-
const value = stored[field];
|
|
124
|
-
if (value === undefined || value === null) {
|
|
125
|
-
(0, runtime_1.emitNervesEvent)({
|
|
126
|
-
level: "error",
|
|
127
|
-
event: "repertoire.credential_get_error",
|
|
128
|
-
component: "repertoire",
|
|
129
|
-
message: `field "${field}" not found for domain "${domain}"`,
|
|
130
|
-
meta: { domain, field, reason: `field "${field}" not found for domain "${domain}"` },
|
|
131
|
-
});
|
|
132
|
-
throw new Error(`field "${field}" not found for domain "${domain}"`);
|
|
133
|
-
}
|
|
134
|
-
return String(value);
|
|
135
|
-
}
|
|
136
|
-
async store(domain, data) {
|
|
137
|
-
(0, runtime_1.emitNervesEvent)({
|
|
138
|
-
event: "repertoire.credential_store_start",
|
|
139
|
-
component: "repertoire",
|
|
140
|
-
message: `storing credential for ${domain}`,
|
|
141
|
-
meta: { domain },
|
|
142
|
-
});
|
|
143
|
-
this.ensureDirs();
|
|
144
|
-
const key = this.getOrCreateKey();
|
|
145
|
-
const credential = {
|
|
146
|
-
domain,
|
|
147
|
-
username: data.username,
|
|
148
|
-
password: data.password,
|
|
149
|
-
notes: data.notes,
|
|
150
|
-
createdAt: new Date().toISOString(),
|
|
151
|
-
};
|
|
152
|
-
const plaintext = JSON.stringify(credential);
|
|
153
|
-
const iv = crypto.randomBytes(12);
|
|
154
|
-
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
155
|
-
const encrypted = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
156
|
-
const tag = cipher.getAuthTag();
|
|
157
|
-
const payload = {
|
|
158
|
-
iv: iv.toString("hex"),
|
|
159
|
-
tag: tag.toString("hex"),
|
|
160
|
-
data: encrypted.toString("hex"),
|
|
161
|
-
};
|
|
162
|
-
const slug = domainToSlug(domain);
|
|
163
|
-
const encPath = path.join(this.vaultDir, `${slug}.enc`);
|
|
164
|
-
fs.writeFileSync(encPath, JSON.stringify(payload), "utf-8");
|
|
165
|
-
(0, runtime_1.emitNervesEvent)({
|
|
166
|
-
event: "repertoire.credential_store_end",
|
|
167
|
-
component: "repertoire",
|
|
168
|
-
message: `credential stored for ${domain}`,
|
|
169
|
-
meta: { domain },
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
async list() {
|
|
173
|
-
(0, runtime_1.emitNervesEvent)({
|
|
174
|
-
event: "repertoire.credential_list_start",
|
|
175
|
-
component: "repertoire",
|
|
176
|
-
message: "listing credentials",
|
|
177
|
-
meta: {},
|
|
178
|
-
});
|
|
179
|
-
if (!fs.existsSync(this.vaultDir)) {
|
|
180
|
-
(0, runtime_1.emitNervesEvent)({
|
|
181
|
-
event: "repertoire.credential_list_end",
|
|
182
|
-
component: "repertoire",
|
|
183
|
-
message: "credential list complete (no vault dir)",
|
|
184
|
-
meta: { count: 0 },
|
|
185
|
-
});
|
|
186
|
-
return [];
|
|
187
|
-
}
|
|
188
|
-
const files = fs.readdirSync(this.vaultDir).filter((f) => f.endsWith(".enc"));
|
|
189
|
-
const results = [];
|
|
190
|
-
for (const file of files) {
|
|
191
|
-
const slug = file.replace(/\.enc$/, "");
|
|
192
|
-
let stored = null;
|
|
193
|
-
try {
|
|
194
|
-
stored = this.readEncryptedBySlug(slug);
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
// Skip corrupt/unreadable files
|
|
198
|
-
}
|
|
199
|
-
if (stored) {
|
|
200
|
-
results.push({
|
|
201
|
-
domain: stored.domain,
|
|
202
|
-
username: stored.username,
|
|
203
|
-
notes: stored.notes,
|
|
204
|
-
createdAt: stored.createdAt,
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
(0, runtime_1.emitNervesEvent)({
|
|
209
|
-
event: "repertoire.credential_list_end",
|
|
210
|
-
component: "repertoire",
|
|
211
|
-
message: "credential list complete",
|
|
212
|
-
meta: { count: results.length },
|
|
213
|
-
});
|
|
214
|
-
return results;
|
|
215
|
-
}
|
|
216
|
-
async delete(domain) {
|
|
217
|
-
(0, runtime_1.emitNervesEvent)({
|
|
218
|
-
event: "repertoire.credential_delete_start",
|
|
219
|
-
component: "repertoire",
|
|
220
|
-
message: `deleting credential for ${domain}`,
|
|
221
|
-
meta: { domain },
|
|
222
|
-
});
|
|
223
|
-
const slug = domainToSlug(domain);
|
|
224
|
-
const encPath = path.join(this.vaultDir, `${slug}.enc`);
|
|
225
|
-
if (!fs.existsSync(encPath)) {
|
|
226
|
-
(0, runtime_1.emitNervesEvent)({
|
|
227
|
-
event: "repertoire.credential_delete_end",
|
|
228
|
-
component: "repertoire",
|
|
229
|
-
message: `no credential to delete for ${domain}`,
|
|
230
|
-
meta: { domain, deleted: false },
|
|
231
|
-
});
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
fs.unlinkSync(encPath);
|
|
235
|
-
(0, runtime_1.emitNervesEvent)({
|
|
236
|
-
event: "repertoire.credential_delete_end",
|
|
237
|
-
component: "repertoire",
|
|
238
|
-
message: `credential deleted for ${domain}`,
|
|
239
|
-
meta: { domain, deleted: true },
|
|
240
|
-
});
|
|
241
|
-
return true;
|
|
242
|
-
}
|
|
243
|
-
// --- Private ---
|
|
244
|
-
ensureDirs() {
|
|
245
|
-
if (!fs.existsSync(this.vaultDir)) {
|
|
246
|
-
fs.mkdirSync(this.vaultDir, { recursive: true });
|
|
247
|
-
}
|
|
248
|
-
const keyDir = path.dirname(this.keyPath);
|
|
249
|
-
if (!fs.existsSync(keyDir)) {
|
|
250
|
-
fs.mkdirSync(keyDir, { recursive: true, mode: 0o700 });
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
getOrCreateKey() {
|
|
254
|
-
if (this.masterKey)
|
|
255
|
-
return this.masterKey;
|
|
256
|
-
if (fs.existsSync(this.keyPath)) {
|
|
257
|
-
this.masterKey = fs.readFileSync(this.keyPath);
|
|
258
|
-
return this.masterKey;
|
|
259
|
-
}
|
|
260
|
-
// Generate new 256-bit key
|
|
261
|
-
const key = crypto.randomBytes(32);
|
|
262
|
-
fs.writeFileSync(this.keyPath, key, { mode: 0o600 });
|
|
263
|
-
this.masterKey = key;
|
|
264
|
-
return key;
|
|
265
|
-
}
|
|
266
|
-
readEncrypted(domain) {
|
|
267
|
-
return this.readEncryptedBySlug(domainToSlug(domain));
|
|
268
|
-
}
|
|
269
|
-
readEncryptedBySlug(slug) {
|
|
270
|
-
const encPath = path.join(this.vaultDir, `${slug}.enc`);
|
|
271
|
-
if (!fs.existsSync(encPath))
|
|
272
|
-
return null;
|
|
273
|
-
const key = this.getOrCreateKey();
|
|
274
|
-
const raw = fs.readFileSync(encPath, "utf-8");
|
|
275
|
-
const payload = JSON.parse(raw);
|
|
276
|
-
const iv = Buffer.from(payload.iv, "hex");
|
|
277
|
-
const tag = Buffer.from(payload.tag, "hex");
|
|
278
|
-
const encrypted = Buffer.from(payload.data, "hex");
|
|
279
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
|
280
|
-
decipher.setAuthTag(tag);
|
|
281
|
-
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
282
|
-
return JSON.parse(decrypted.toString("utf-8"));
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
exports.BuiltInCredentialStore = BuiltInCredentialStore;
|
|
286
|
-
// ---------------------------------------------------------------------------
|
|
287
|
-
// AacCredentialStore — delegates to `aac` CLI
|
|
288
|
-
// ---------------------------------------------------------------------------
|
|
289
|
-
function execAac(args) {
|
|
290
|
-
return new Promise((resolve, reject) => {
|
|
291
|
-
(0, node_child_process_1.execFile)("aac", args, { timeout: 10_000 }, (err, stdout) => {
|
|
292
|
-
if (err) {
|
|
293
|
-
reject(new Error(`aac CLI error: ${err.message}`));
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
resolve(stdout);
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
class AacCredentialStore {
|
|
301
|
-
aacAvailable = null;
|
|
302
|
-
isReady() {
|
|
303
|
-
// Synchronous check — returns last-known state or false if unchecked
|
|
304
|
-
return this.aacAvailable === true;
|
|
305
|
-
}
|
|
306
|
-
async checkReady() {
|
|
307
|
-
try {
|
|
308
|
-
const stdout = await execAac(["connections", "list"]);
|
|
309
|
-
const parsed = JSON.parse(stdout);
|
|
310
|
-
this.aacAvailable = Array.isArray(parsed) && parsed.length > 0;
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
this.aacAvailable = false;
|
|
314
|
-
}
|
|
315
|
-
return this.aacAvailable;
|
|
316
|
-
}
|
|
317
|
-
async get(domain) {
|
|
318
|
-
(0, runtime_1.emitNervesEvent)({
|
|
319
|
-
event: "repertoire.credential_get_start",
|
|
320
|
-
component: "repertoire",
|
|
321
|
-
message: `getting credential via aac for ${domain}`,
|
|
322
|
-
meta: { domain, backend: "aac" },
|
|
323
|
-
});
|
|
324
|
-
try {
|
|
325
|
-
const stdout = await execAac(["--domain", domain, "--output", "json"]);
|
|
326
|
-
const parsed = JSON.parse(stdout);
|
|
327
|
-
if (!parsed.success) {
|
|
328
|
-
(0, runtime_1.emitNervesEvent)({
|
|
329
|
-
event: "repertoire.credential_get_end",
|
|
330
|
-
component: "repertoire",
|
|
331
|
-
message: `no aac credential for ${domain}`,
|
|
332
|
-
meta: { domain, found: false, backend: "aac" },
|
|
333
|
-
});
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
const cred = parsed.credential;
|
|
337
|
-
(0, runtime_1.emitNervesEvent)({
|
|
338
|
-
event: "repertoire.credential_get_end",
|
|
339
|
-
component: "repertoire",
|
|
340
|
-
message: `aac credential found for ${domain}`,
|
|
341
|
-
meta: { domain, found: true, backend: "aac" },
|
|
342
|
-
});
|
|
343
|
-
return {
|
|
344
|
-
domain,
|
|
345
|
-
username: cred.username,
|
|
346
|
-
notes: cred.notes,
|
|
347
|
-
createdAt: cred.createdAt ?? new Date().toISOString(),
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
catch {
|
|
351
|
-
(0, runtime_1.emitNervesEvent)({
|
|
352
|
-
event: "repertoire.credential_get_end",
|
|
353
|
-
component: "repertoire",
|
|
354
|
-
message: `aac credential lookup failed for ${domain}`,
|
|
355
|
-
meta: { domain, found: false, backend: "aac" },
|
|
356
|
-
});
|
|
357
|
-
return null;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
async getRawSecret(domain, field) {
|
|
361
|
-
const stdout = await execAac(["--domain", domain, "--output", "json"]);
|
|
362
|
-
const parsed = JSON.parse(stdout);
|
|
363
|
-
if (!parsed.success) {
|
|
364
|
-
throw new Error(`aac lookup failed for domain "${domain}": ${parsed.error ?? "unknown error"}`);
|
|
365
|
-
}
|
|
366
|
-
const value = parsed.credential[field];
|
|
367
|
-
if (value === undefined || value === null) {
|
|
368
|
-
throw new Error(`field "${field}" not found for domain "${domain}"`);
|
|
369
|
-
}
|
|
370
|
-
return String(value);
|
|
371
|
-
}
|
|
372
|
-
async store(_domain, _data) {
|
|
373
|
-
throw new Error("store() is not supported in aac mode — aac is read-from-vault only. " +
|
|
374
|
-
"Use BuiltInCredentialStore for agent-owned credentials, or manage vault items " +
|
|
375
|
-
"directly in Bitwarden.");
|
|
376
|
-
}
|
|
377
|
-
async list() {
|
|
378
|
-
(0, runtime_1.emitNervesEvent)({
|
|
379
|
-
event: "repertoire.credential_list_start",
|
|
380
|
-
component: "repertoire",
|
|
381
|
-
message: "listing aac connections",
|
|
382
|
-
meta: { backend: "aac" },
|
|
383
|
-
});
|
|
384
|
-
try {
|
|
385
|
-
const stdout = await execAac(["connections", "list"]);
|
|
386
|
-
const parsed = JSON.parse(stdout);
|
|
387
|
-
const results = (Array.isArray(parsed) ? parsed : []).map((item) => ({
|
|
388
|
-
domain: String(item.domain ?? ""),
|
|
389
|
-
username: item.username ? String(item.username) : undefined,
|
|
390
|
-
notes: item.notes ? String(item.notes) : undefined,
|
|
391
|
-
createdAt: item.createdAt ? String(item.createdAt) : new Date().toISOString(),
|
|
392
|
-
}));
|
|
393
|
-
(0, runtime_1.emitNervesEvent)({
|
|
394
|
-
event: "repertoire.credential_list_end",
|
|
395
|
-
component: "repertoire",
|
|
396
|
-
message: "aac connections listed",
|
|
397
|
-
meta: { backend: "aac", count: results.length },
|
|
398
|
-
});
|
|
399
|
-
return results;
|
|
400
|
-
}
|
|
401
|
-
catch {
|
|
402
|
-
(0, runtime_1.emitNervesEvent)({
|
|
403
|
-
event: "repertoire.credential_list_end",
|
|
404
|
-
component: "repertoire",
|
|
405
|
-
message: "aac connections list failed",
|
|
406
|
-
meta: { backend: "aac", count: 0 },
|
|
407
|
-
});
|
|
408
|
-
return [];
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
async delete(_domain) {
|
|
412
|
-
throw new Error("delete() is not supported in aac mode — manage vault items directly in Bitwarden.");
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
exports.AacCredentialStore = AacCredentialStore;
|
|
416
|
-
// ---------------------------------------------------------------------------
|
|
417
|
-
// Singleton + auto-detection
|
|
418
|
-
// ---------------------------------------------------------------------------
|
|
419
|
-
let _store = null;
|
|
420
|
-
/**
|
|
421
|
-
* Auto-add vault defaults to agent.json when no vault section exists.
|
|
422
|
-
* Returns the vault config (either existing or freshly written defaults).
|
|
423
|
-
*/
|
|
424
|
-
function ensureVaultDefaults(agentName, agentRoot, existingVault) {
|
|
425
|
-
if (existingVault) {
|
|
426
|
-
return existingVault;
|
|
427
|
-
}
|
|
428
|
-
const configPath = path.join(agentRoot, "agent.json");
|
|
429
|
-
let raw;
|
|
54
|
+
const bitwarden_store_1 = require("./bitwarden-store");
|
|
55
|
+
const vault_unlock_1 = require("./vault-unlock");
|
|
56
|
+
let stores = new Map();
|
|
57
|
+
function loadVaultSectionForAgent(agentName) {
|
|
58
|
+
const configPath = path.join(identity.getAgentRoot(agentName), "agent.json");
|
|
430
59
|
try {
|
|
431
|
-
|
|
60
|
+
const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
61
|
+
return parsed.vault;
|
|
432
62
|
}
|
|
433
63
|
catch {
|
|
434
|
-
// agent.json unreadable — cannot auto-configure
|
|
435
64
|
return undefined;
|
|
436
65
|
}
|
|
437
|
-
const defaults = {
|
|
438
|
-
email: `${agentName}@ouro.bot`,
|
|
439
|
-
serverUrl: "https://vault.ouro.bot",
|
|
440
|
-
};
|
|
441
|
-
raw.vault = defaults;
|
|
442
|
-
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
|
|
443
|
-
(0, runtime_1.emitNervesEvent)({
|
|
444
|
-
event: "repertoire.vault_defaults_auto_configured",
|
|
445
|
-
component: "repertoire",
|
|
446
|
-
message: "auto-configured vault defaults in agent.json",
|
|
447
|
-
meta: { agentName, email: defaults.email, serverUrl: defaults.serverUrl },
|
|
448
|
-
});
|
|
449
|
-
return defaults;
|
|
450
66
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
// Try to load vault config from agent.json + secrets.json. Only this block
|
|
477
|
-
// is defended with try/catch — bitwarden wiring can legitimately fail
|
|
478
|
-
// (missing vault config, no bw CLI, unreachable server) and the fallback
|
|
479
|
-
// to BuiltInCredentialStore is the intended behavior.
|
|
480
|
-
let backend = "built-in";
|
|
481
|
-
try {
|
|
482
|
-
const config = identity.loadAgentConfig?.();
|
|
483
|
-
// Auto-configure vault defaults if missing
|
|
484
|
-
const vaultSection = ensureVaultDefaults(agentName, agentRoot, config?.vault);
|
|
485
|
-
const vaultConfig = identity.resolveVaultConfig?.(agentName, vaultSection);
|
|
486
|
-
const secretsPath = identity.getAgentSecretsPath?.(agentName);
|
|
487
|
-
let vaultSecrets;
|
|
488
|
-
/* v8 ignore start -- requires real agent secrets.json + vault config + bw CLI @preserve */
|
|
489
|
-
if (secretsPath) {
|
|
490
|
-
try {
|
|
491
|
-
const fsSync = require("fs");
|
|
492
|
-
const secrets = JSON.parse(fsSync.readFileSync(secretsPath, "utf-8"));
|
|
493
|
-
vaultSecrets = secrets.vault;
|
|
494
|
-
}
|
|
495
|
-
catch {
|
|
496
|
-
// No secrets file or no vault section — fall through to built-in
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
const serverUrl = vaultConfig?.serverUrl;
|
|
500
|
-
const email = vaultConfig?.email;
|
|
501
|
-
const masterPassword = vaultSecrets?.masterPassword;
|
|
502
|
-
if (serverUrl && email && masterPassword) {
|
|
503
|
-
const { BitwardenCredentialStore } = require("./bitwarden-store");
|
|
504
|
-
_store = new BitwardenCredentialStore(serverUrl, email, masterPassword);
|
|
505
|
-
backend = "bitwarden";
|
|
506
|
-
}
|
|
507
|
-
/* v8 ignore stop */
|
|
508
|
-
}
|
|
509
|
-
catch {
|
|
510
|
-
// Bitwarden wiring failed — fall through to built-in store below.
|
|
511
|
-
}
|
|
512
|
-
/* v8 ignore next -- false branch only reachable when BitwardenCredentialStore was created above @preserve */
|
|
513
|
-
if (!_store) {
|
|
514
|
-
_store = new BuiltInCredentialStore(agentName);
|
|
515
|
-
}
|
|
67
|
+
function bitwardenAppDataDir(agentName, vaultConfig) {
|
|
68
|
+
const digest = crypto
|
|
69
|
+
.createHash("sha256")
|
|
70
|
+
.update(`${agentName}:${vaultConfig.serverUrl}:${vaultConfig.email}`)
|
|
71
|
+
.digest("hex")
|
|
72
|
+
.slice(0, 24);
|
|
73
|
+
return path.join(os.homedir(), ".ouro-cli", "bitwarden", digest);
|
|
74
|
+
}
|
|
75
|
+
function getCredentialStore(agentNameInput) {
|
|
76
|
+
const agentName = agentNameInput ?? identity.getAgentName();
|
|
77
|
+
if (agentName === "SerpentGuide") {
|
|
78
|
+
throw new Error("SerpentGuide does not have a persistent credential vault; hatch bootstrap uses provider credentials in memory only.");
|
|
79
|
+
}
|
|
80
|
+
const vaultConfig = identity.resolveVaultConfig(agentName, loadVaultSectionForAgent(agentName));
|
|
81
|
+
const cacheKey = `${agentName}:${vaultConfig.serverUrl}:${vaultConfig.email}`;
|
|
82
|
+
const cached = stores.get(cacheKey);
|
|
83
|
+
if (cached)
|
|
84
|
+
return cached;
|
|
85
|
+
const unlock = (0, vault_unlock_1.readVaultUnlockSecret)({
|
|
86
|
+
agentName,
|
|
87
|
+
email: vaultConfig.email,
|
|
88
|
+
serverUrl: vaultConfig.serverUrl,
|
|
89
|
+
});
|
|
90
|
+
const store = new bitwarden_store_1.BitwardenCredentialStore(vaultConfig.serverUrl, vaultConfig.email, unlock.secret, { appDataDir: bitwardenAppDataDir(agentName, vaultConfig) });
|
|
91
|
+
stores.set(cacheKey, store);
|
|
516
92
|
(0, runtime_1.emitNervesEvent)({
|
|
517
93
|
event: "repertoire.credential_store_init",
|
|
518
94
|
component: "repertoire",
|
|
519
95
|
message: "credential store initialized",
|
|
520
|
-
meta: {
|
|
96
|
+
meta: {
|
|
97
|
+
backend: "bitwarden",
|
|
98
|
+
agentName,
|
|
99
|
+
serverUrl: vaultConfig.serverUrl,
|
|
100
|
+
email: vaultConfig.email,
|
|
101
|
+
},
|
|
521
102
|
});
|
|
522
|
-
return
|
|
103
|
+
return store;
|
|
523
104
|
}
|
|
524
|
-
/** Reset the singleton — for tests only. */
|
|
525
105
|
function resetCredentialStore() {
|
|
526
|
-
|
|
106
|
+
stores = new Map();
|
|
527
107
|
}
|
|
@@ -35,8 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.attachmentToolDefinitions = void 0;
|
|
37
37
|
const fs = __importStar(require("node:fs/promises"));
|
|
38
|
-
const config_1 = require("../heart/config");
|
|
39
38
|
const identity_1 = require("../heart/identity");
|
|
39
|
+
const provider_credentials_1 = require("../heart/provider-credentials");
|
|
40
40
|
const minimax_1 = require("../heart/providers/minimax");
|
|
41
41
|
const minimax_vlm_1 = require("../heart/providers/minimax-vlm");
|
|
42
42
|
const store_1 = require("../heart/attachments/store");
|
|
@@ -275,7 +275,8 @@ exports.attachmentToolDefinitions = [
|
|
|
275
275
|
try {
|
|
276
276
|
const materialized = await (0, materialize_1.materializeAttachment)(agentName, attachmentId, { variant: "vision_safe" });
|
|
277
277
|
const imageDataUrl = await buildImageDataUrl(materialized.path, materialized.mimeType);
|
|
278
|
-
const
|
|
278
|
+
const credential = await (0, provider_credentials_1.readProviderCredentialRecord)(agentName, "minimax");
|
|
279
|
+
const apiKey = credential.ok ? credential.record.credentials.apiKey : undefined;
|
|
279
280
|
if (!apiKey) {
|
|
280
281
|
return (0, tool_results_1.frictionToolResult)("describe_image", {
|
|
281
282
|
kind: "external_blocker",
|
|
@@ -285,13 +286,13 @@ exports.attachmentToolDefinitions = [
|
|
|
285
286
|
suggested_next_actions: [
|
|
286
287
|
{
|
|
287
288
|
kind: "message",
|
|
288
|
-
message: "
|
|
289
|
+
message: "Run `ouro auth --agent <agent> --provider minimax`, then retry describe_image.",
|
|
289
290
|
},
|
|
290
291
|
],
|
|
291
292
|
});
|
|
292
293
|
}
|
|
293
294
|
const description = await (0, minimax_vlm_1.minimaxVlmDescribe)({
|
|
294
|
-
apiKey,
|
|
295
|
+
apiKey: String(apiKey),
|
|
295
296
|
prompt,
|
|
296
297
|
imageDataUrl,
|
|
297
298
|
baseURL: minimax_1.MINIMAX_PROVIDER_BASE_URL,
|