@ouro.bot/cli 0.1.0-alpha.519 → 0.1.0-alpha.520

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.
@@ -39,6 +39,8 @@ exports.readRuntimeCredentialConfig = readRuntimeCredentialConfig;
39
39
  exports.readMachineRuntimeCredentialConfig = readMachineRuntimeCredentialConfig;
40
40
  exports.cacheRuntimeCredentialConfig = cacheRuntimeCredentialConfig;
41
41
  exports.cacheMachineRuntimeCredentialConfig = cacheMachineRuntimeCredentialConfig;
42
+ exports.applyRuntimeCredentialBootstrapMessage = applyRuntimeCredentialBootstrapMessage;
43
+ exports.waitForRuntimeCredentialBootstrap = waitForRuntimeCredentialBootstrap;
42
44
  exports.refreshRuntimeCredentialConfig = refreshRuntimeCredentialConfig;
43
45
  exports.refreshMachineRuntimeCredentialConfig = refreshMachineRuntimeCredentialConfig;
44
46
  exports.upsertRuntimeCredentialConfig = upsertRuntimeCredentialConfig;
@@ -48,6 +50,7 @@ const crypto = __importStar(require("node:crypto"));
48
50
  const runtime_1 = require("../nerves/runtime");
49
51
  const credential_access_1 = require("../repertoire/credential-access");
50
52
  const identity_1 = require("./identity");
53
+ const provider_credentials_1 = require("./provider-credentials");
51
54
  exports.RUNTIME_CONFIG_ITEM_NAME = "runtime/config";
52
55
  exports.MACHINE_RUNTIME_CONFIG_ITEM_PREFIX = "runtime/machines";
53
56
  let cachedRuntimeConfigs = new Map();
@@ -55,6 +58,36 @@ let cachedMachineRuntimeConfigs = new Map();
55
58
  function isRecord(value) {
56
59
  return !!value && typeof value === "object" && !Array.isArray(value);
57
60
  }
61
+ function isCredentialValue(value) {
62
+ return typeof value === "string" || typeof value === "number";
63
+ }
64
+ function isCredentialRecord(value) {
65
+ if (!isRecord(value))
66
+ return false;
67
+ return Object.values(value).every(isCredentialValue);
68
+ }
69
+ function isProviderCredentialRecord(value) {
70
+ if (!isRecord(value))
71
+ return false;
72
+ if (typeof value.provider !== "string")
73
+ return false;
74
+ if (typeof value.revision !== "string" || value.revision.trim().length === 0)
75
+ return false;
76
+ if (typeof value.updatedAt !== "string" || value.updatedAt.trim().length === 0)
77
+ return false;
78
+ if (!isCredentialRecord(value.credentials))
79
+ return false;
80
+ if (!isCredentialRecord(value.config))
81
+ return false;
82
+ const provenance = value.provenance;
83
+ if (!isRecord(provenance))
84
+ return false;
85
+ if (provenance.source !== "auth-flow" && provenance.source !== "manual")
86
+ return false;
87
+ if (typeof provenance.updatedAt !== "string" || provenance.updatedAt.trim().length === 0)
88
+ return false;
89
+ return true;
90
+ }
58
91
  function stableJson(value) {
59
92
  if (Array.isArray(value))
60
93
  return `[${value.map(stableJson).join(",")}]`;
@@ -159,6 +192,80 @@ function cacheMachineRuntimeCredentialConfig(agentName, config, now = new Date()
159
192
  };
160
193
  return cacheMachineResult(agentName, resultFromPayloadForItem(agentName, machineRuntimeConfigItemName(machineId), payload));
161
194
  }
195
+ function isRuntimeCredentialBootstrapMessage(value) {
196
+ if (!value || typeof value !== "object" || Array.isArray(value))
197
+ return false;
198
+ const record = value;
199
+ if (record.type !== "ouro.runtimeCredentialBootstrap")
200
+ return false;
201
+ if (typeof record.agentName !== "string" || record.agentName.trim().length === 0)
202
+ return false;
203
+ if (record.runtimeConfig !== undefined && !isRecord(record.runtimeConfig))
204
+ return false;
205
+ if (record.machineRuntimeConfig !== undefined && !isRecord(record.machineRuntimeConfig))
206
+ return false;
207
+ if (record.machineId !== undefined && (typeof record.machineId !== "string" || record.machineId.trim().length === 0))
208
+ return false;
209
+ if (record.providerCredentialRecords !== undefined
210
+ && (!Array.isArray(record.providerCredentialRecords)
211
+ || !record.providerCredentialRecords.every(isProviderCredentialRecord)))
212
+ return false;
213
+ return true;
214
+ }
215
+ function applyRuntimeCredentialBootstrapMessage(message) {
216
+ if (!isRuntimeCredentialBootstrapMessage(message))
217
+ return false;
218
+ const agentName = message.agentName.trim();
219
+ const now = new Date();
220
+ if (message.runtimeConfig) {
221
+ cacheRuntimeCredentialConfig(agentName, message.runtimeConfig, now);
222
+ }
223
+ if (message.machineRuntimeConfig) {
224
+ cacheMachineRuntimeCredentialConfig(agentName, message.machineRuntimeConfig, now, message.machineId ?? "<this-machine>");
225
+ }
226
+ if (message.providerCredentialRecords && message.providerCredentialRecords.length > 0) {
227
+ (0, provider_credentials_1.cacheProviderCredentialRecords)(agentName, message.providerCredentialRecords, now);
228
+ }
229
+ (0, runtime_1.emitNervesEvent)({
230
+ component: "config/identity",
231
+ event: "config.runtime_credentials_bootstrapped",
232
+ message: "loaded runtime credentials from daemon bootstrap",
233
+ meta: {
234
+ agentName,
235
+ runtimeConfig: !!message.runtimeConfig,
236
+ machineRuntimeConfig: !!message.machineRuntimeConfig,
237
+ providerCredentialRecords: message.providerCredentialRecords?.length ?? 0,
238
+ },
239
+ });
240
+ return true;
241
+ }
242
+ function waitForRuntimeCredentialBootstrap(agentName, options = {}) {
243
+ const timeoutMs = options.timeoutMs ?? 1_500;
244
+ return new Promise((resolve) => {
245
+ let settled = false;
246
+ let timer = null;
247
+ const finish = (value) => {
248
+ /* v8 ignore next -- defensive: listener cleanup and timer cleanup prevent double settlement @preserve */
249
+ if (settled)
250
+ return;
251
+ settled = true;
252
+ /* v8 ignore next -- defensive: timer is assigned immediately after listener registration @preserve */
253
+ if (timer)
254
+ clearTimeout(timer);
255
+ process.off?.("message", onMessage);
256
+ resolve(value);
257
+ };
258
+ const onMessage = (message) => {
259
+ if (!isRuntimeCredentialBootstrapMessage(message))
260
+ return;
261
+ if (message.agentName.trim() !== agentName.trim())
262
+ return;
263
+ finish(applyRuntimeCredentialBootstrapMessage(message));
264
+ };
265
+ process.on?.("message", onMessage);
266
+ timer = setTimeout(() => finish(false), timeoutMs);
267
+ });
268
+ }
162
269
  async function refreshRuntimeCredentialConfigItem(agentName, itemName, cache, options = {}) {
163
270
  try {
164
271
  const store = (0, credential_access_1.getCredentialStore)(agentName);
@@ -1090,15 +1090,14 @@ function loadFullEventHistory(sessPath) {
1090
1090
  catch {
1091
1091
  // Archive file doesn't exist or can't be read -- that's fine
1092
1092
  }
1093
- // Merge, deduplicate by id, sort by sequence
1094
- const seen = new Set();
1095
- const merged = [];
1096
- for (const event of [...archiveEvents, ...envelopeEvents]) {
1097
- if (seen.has(event.id))
1098
- continue;
1099
- seen.add(event.id);
1100
- merged.push(event);
1101
- }
1093
+ // Merge, deduplicate by id, sort by sequence. The live envelope is the
1094
+ // current projection, so it wins if an older archive line has a colliding id.
1095
+ const mergedById = new Map();
1096
+ for (const event of archiveEvents)
1097
+ mergedById.set(event.id, event);
1098
+ for (const event of envelopeEvents)
1099
+ mergedById.set(event.id, event);
1100
+ const merged = [...mergedById.values()];
1102
1101
  merged.sort((a, b) => a.sequence - b.sequence);
1103
1102
  return merged;
1104
1103
  }
@@ -4,15 +4,62 @@ exports.summarizeSessionTail = summarizeSessionTail;
4
4
  exports.searchSessionTranscript = searchSessionTranscript;
5
5
  const runtime_1 = require("../nerves/runtime");
6
6
  const session_events_1 = require("./session-events");
7
- function normalizeSessionMessages(events) {
8
- return events
7
+ function shouldIncludeToolMessages(friendId, channel) {
8
+ return friendId === "self" && channel === "inner";
9
+ }
10
+ function sortEventsForTranscript(events) {
11
+ return [...events].sort((a, b) => {
12
+ const byTime = Date.parse((0, session_events_1.bestEventTimestamp)(a)) - Date.parse((0, session_events_1.bestEventTimestamp)(b));
13
+ return byTime === 0 ? a.sequence - b.sequence : byTime;
14
+ });
15
+ }
16
+ function parseToolArguments(toolCall) {
17
+ try {
18
+ const parsed = JSON.parse(toolCall.function.arguments);
19
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
20
+ ? parsed
21
+ : null;
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ function extractHumanVisibleToolText(event, context) {
28
+ if (event.role !== "assistant" || event.toolCalls.length === 0)
29
+ return "";
30
+ for (const toolCall of event.toolCalls) {
31
+ const args = parseToolArguments(toolCall);
32
+ if (!args)
33
+ continue;
34
+ if (toolCall.function.name === "settle" && typeof args.answer === "string") {
35
+ return args.answer.trim();
36
+ }
37
+ if (toolCall.function.name === "send_message" && typeof args.content === "string") {
38
+ const targetChannel = typeof args.channel === "string" ? args.channel : "";
39
+ const targetFriendId = typeof args.friendId === "string" ? args.friendId : "";
40
+ const targetKey = typeof args.key === "string" ? args.key : "session";
41
+ if (targetChannel === context.channel && targetFriendId === context.friendId && targetKey === context.key) {
42
+ return args.content.trim();
43
+ }
44
+ }
45
+ }
46
+ return "";
47
+ }
48
+ function normalizeSessionMessages(events, context) {
49
+ return sortEventsForTranscript(events)
9
50
  .map((event) => ({
10
51
  id: event.id,
11
52
  role: event.role,
12
- content: (0, session_events_1.extractEventText)(event),
53
+ content: (0, session_events_1.extractEventText)(event) || extractHumanVisibleToolText(event, context),
13
54
  timestamp: (0, session_events_1.formatSessionEventTimestamp)(event),
14
55
  }))
15
- .filter((message) => message.role !== "system" && message.content.length > 0);
56
+ .filter((message) => {
57
+ if (message.role === "system")
58
+ return false;
59
+ if (message.role === "tool" && !context.includeToolMessages)
60
+ return false;
61
+ return message.content.length > 0;
62
+ });
16
63
  }
17
64
  function buildSummaryInstruction(friendId, channel, trustLevel) {
18
65
  if (friendId === "self" && channel === "inner") {
@@ -36,6 +83,18 @@ function buildSnapshot(summary, tailMessages) {
36
83
  }
37
84
  return lines.join("\n");
38
85
  }
86
+ function selectSessionTailMessages(messages, messageCount) {
87
+ const requestedCount = Number.isFinite(messageCount) && messageCount > 0 ? Math.floor(messageCount) : 20;
88
+ const tail = messages.slice(-requestedCount);
89
+ const selectedIds = new Set(tail.map((message) => message.id));
90
+ const latestUser = [...messages].reverse().find((message) => message.role === "user");
91
+ const latestAssistant = [...messages].reverse().find((message) => message.role === "assistant");
92
+ if (latestUser)
93
+ selectedIds.add(latestUser.id);
94
+ if (latestAssistant)
95
+ selectedIds.add(latestAssistant.id);
96
+ return messages.filter((message) => selectedIds.has(message.id));
97
+ }
39
98
  function buildSearchSnapshot(query, messages, includeLatestTurn = true) {
40
99
  const lines = [`history query: "${clip(query, 120)}"`];
41
100
  if (!includeLatestTurn) {
@@ -106,7 +165,19 @@ async function summarizeSessionTail(options) {
106
165
  const envelope = (0, session_events_1.loadSessionEnvelopeFile)(options.sessionPath);
107
166
  if (!envelope)
108
167
  return { kind: "missing" };
109
- const tailMessages = normalizeSessionMessages(envelope.events).slice(-options.messageCount);
168
+ const transcriptContext = {
169
+ friendId: options.friendId,
170
+ channel: options.channel,
171
+ key: options.key,
172
+ includeToolMessages: shouldIncludeToolMessages(options.friendId, options.channel),
173
+ };
174
+ let visibleMessages = normalizeSessionMessages(envelope.events, transcriptContext);
175
+ if (options.archiveFallback && !visibleMessages.some((message) => message.role === "user")) {
176
+ const fullHistoryMessages = normalizeSessionMessages((0, session_events_1.loadFullEventHistory)(options.sessionPath), transcriptContext);
177
+ if (fullHistoryMessages.length > 0)
178
+ visibleMessages = fullHistoryMessages;
179
+ }
180
+ const tailMessages = selectSessionTailMessages(visibleMessages, options.messageCount);
110
181
  if (tailMessages.length === 0) {
111
182
  return { kind: "empty" };
112
183
  }
@@ -145,7 +216,12 @@ async function searchSessionTranscript(options) {
145
216
  return { kind: "missing" };
146
217
  return { kind: "empty" };
147
218
  }
148
- const messages = normalizeSessionMessages(allEvents);
219
+ const messages = normalizeSessionMessages(allEvents, {
220
+ friendId: options.friendId,
221
+ channel: options.channel,
222
+ key: options.key,
223
+ includeToolMessages: shouldIncludeToolMessages(options.friendId, options.channel),
224
+ });
149
225
  if (messages.length === 0) {
150
226
  return { kind: "empty" };
151
227
  }
@@ -57,6 +57,7 @@ const target_resolution_1 = require("./target-resolution");
57
57
  const episodes_1 = require("../arc/episodes");
58
58
  const cares_1 = require("../arc/cares");
59
59
  const config_1 = require("./config");
60
+ const runtime_credentials_1 = require("./runtime-credentials");
60
61
  const daemon_health_1 = require("./daemon/daemon-health");
61
62
  const prompt_1 = require("../mind/prompt");
62
63
  const provider_visibility_1 = require("./provider-visibility");
@@ -140,6 +141,9 @@ function checkDaemonRunning() {
140
141
  function hasTextField(record, key) {
141
142
  return typeof record?.[key] === "string" && record[key].trim().length > 0;
142
143
  }
144
+ function recordOrUndefined(value) {
145
+ return !!value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
146
+ }
143
147
  function readSenseStatusLines() {
144
148
  const config = (0, identity_1.loadAgentConfig)();
145
149
  const configuredSenses = config.senses ?? {};
@@ -151,9 +155,14 @@ function readSenseStatusLines() {
151
155
  mail: configuredSenses.mail ?? { enabled: false },
152
156
  };
153
157
  const payload = (0, config_1.loadConfig)();
154
- const teams = payload.teams;
155
- const bluebubbles = payload.bluebubbles;
156
- const mailroom = payload.mailroom;
158
+ const agentName = (0, identity_1.getAgentName)();
159
+ const runtimeConfig = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agentName);
160
+ const machineRuntimeConfig = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agentName);
161
+ const runtimePayload = runtimeConfig.ok ? runtimeConfig.config : {};
162
+ const machinePayload = machineRuntimeConfig.ok ? machineRuntimeConfig.config : {};
163
+ const teams = recordOrUndefined(runtimePayload.teams) ?? recordOrUndefined(payload.teams);
164
+ const bluebubbles = recordOrUndefined(machinePayload.bluebubbles) ?? recordOrUndefined(payload.bluebubbles);
165
+ const mailroom = recordOrUndefined(runtimePayload.mailroom) ?? recordOrUndefined(payload.mailroom);
157
166
  const privateKeys = mailroom?.privateKeys;
158
167
  const configured = {
159
168
  cli: true,
@@ -37,6 +37,7 @@ exports.parseMailroomConfig = parseMailroomConfig;
37
37
  exports.readMailroomRegistry = readMailroomRegistry;
38
38
  exports.writeMailroomRegistry = writeMailroomRegistry;
39
39
  exports.resolveMailroomReader = resolveMailroomReader;
40
+ exports.resolveMailroomReaderWithRefresh = resolveMailroomReaderWithRefresh;
40
41
  const fs = __importStar(require("node:fs"));
41
42
  const path = __importStar(require("node:path"));
42
43
  const storage_blob_1 = require("@azure/storage-blob");
@@ -217,3 +218,11 @@ function resolveMailroomReader(agentName = (0, identity_2.getAgentName)()) {
217
218
  });
218
219
  return result;
219
220
  }
221
+ async function resolveMailroomReaderWithRefresh(agentName = (0, identity_2.getAgentName)()) {
222
+ const resolved = resolveMailroomReader(agentName);
223
+ if (resolved.ok || resolved.reason !== "auth-required" || !resolved.error.includes(" is unavailable")) {
224
+ return resolved;
225
+ }
226
+ await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agentName, { preserveCachedOnFailure: true }).catch(() => undefined);
227
+ return resolveMailroomReader(agentName);
228
+ }
@@ -66,6 +66,7 @@ const tools_1 = require("../repertoire/tools");
66
66
  const skills_1 = require("../repertoire/skills");
67
67
  const identity_1 = require("../heart/identity");
68
68
  const config_1 = require("../heart/config");
69
+ const runtime_credentials_1 = require("../heart/runtime-credentials");
69
70
  const runtime_mode_1 = require("../heart/daemon/runtime-mode");
70
71
  const types_1 = require("./friends/types");
71
72
  const trust_explanation_1 = require("./friends/trust-explanation");
@@ -88,7 +89,6 @@ function flattenSystemPrompt(sp) {
88
89
  }
89
90
  // Lazy-loaded psyche text cache
90
91
  let _psycheCache = null;
91
- let _senseStatusLinesCache = null;
92
92
  function loadPsycheFile(name) {
93
93
  try {
94
94
  const psycheDir = path.join((0, identity_1.getAgentRoot)(), "psyche");
@@ -112,7 +112,6 @@ function loadPsyche() {
112
112
  }
113
113
  function resetPsycheCache() {
114
114
  _psycheCache = null;
115
- _senseStatusLinesCache = null;
116
115
  }
117
116
  const DEFAULT_ACTIVE_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours
118
117
  function buildSessionSummary(options) {
@@ -409,10 +408,10 @@ function runtimeInfoSection(channel, options) {
409
408
  function hasTextField(record, key) {
410
409
  return typeof record?.[key] === "string" && record[key].trim().length > 0;
411
410
  }
411
+ function recordOrUndefined(value) {
412
+ return !!value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
413
+ }
412
414
  function localSenseStatusLines() {
413
- if (_senseStatusLinesCache) {
414
- return [..._senseStatusLinesCache];
415
- }
416
415
  const config = (0, identity_1.loadAgentConfig)();
417
416
  const configuredSenses = config.senses ?? {};
418
417
  const senses = {
@@ -423,9 +422,13 @@ function localSenseStatusLines() {
423
422
  mail: configuredSenses.mail ?? { enabled: false },
424
423
  };
425
424
  const payload = (0, config_1.loadConfig)();
426
- const teams = payload.teams;
427
- const bluebubbles = payload.bluebubbles;
428
- const mailroom = payload.mailroom;
425
+ const runtimeConfig = (0, runtime_credentials_1.readRuntimeCredentialConfig)((0, identity_1.getAgentName)());
426
+ const machineRuntimeConfig = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)((0, identity_1.getAgentName)());
427
+ const runtimePayload = runtimeConfig.ok ? runtimeConfig.config : {};
428
+ const machinePayload = machineRuntimeConfig.ok ? machineRuntimeConfig.config : {};
429
+ const teams = recordOrUndefined(runtimePayload.teams) ?? recordOrUndefined(payload.teams);
430
+ const bluebubbles = recordOrUndefined(machinePayload.bluebubbles) ?? recordOrUndefined(payload.bluebubbles);
431
+ const mailroom = recordOrUndefined(runtimePayload.mailroom) ?? recordOrUndefined(payload.mailroom);
429
432
  const privateKeys = mailroom?.privateKeys;
430
433
  const configured = {
431
434
  cli: true,
@@ -448,8 +451,7 @@ function localSenseStatusLines() {
448
451
  status: !senses.mail.enabled ? "disabled" : configured.mail ? "ready" : "needs_config",
449
452
  },
450
453
  ];
451
- _senseStatusLinesCache = rows.map((row) => `- ${row.label}: ${row.status}`);
452
- return [..._senseStatusLinesCache];
454
+ return rows.map((row) => `- ${row.label}: ${row.status}`);
453
455
  }
454
456
  function senseRuntimeGuidance(channel, preReadStatusLines) {
455
457
  const lines = ["available senses:"];
@@ -50,6 +50,7 @@ const runtime_1 = require("../nerves/runtime");
50
50
  const bw_installer_1 = require("./bw-installer");
51
51
  const MAX_ERROR_DETAIL_LENGTH = 500;
52
52
  const LONG_ENCODED_TOKEN_PATTERN = /[A-Za-z0-9+/=]{32,}/g;
53
+ const BW_PASSWORD_ENV = "OURO_BW_MASTER_PASSWORD";
53
54
  function uniqueSecrets(secrets) {
54
55
  return [...new Set(secrets.filter((value) => typeof value === "string" && value.length >= 4))].sort((left, right) => right.length - left.length);
55
56
  }
@@ -131,9 +132,19 @@ function isBwConfigLogoutRequired(err) {
131
132
  const message = err.message.toLowerCase();
132
133
  return message.includes("logout") && message.includes("required");
133
134
  }
134
- function shouldPreferExactItemLookup(domain) {
135
+ function isBwAlreadyLoggedInError(err) {
136
+ return err.message.toLowerCase().includes("already logged in");
137
+ }
138
+ function shouldUseStructuredItemLookup(domain) {
135
139
  return domain.includes("/");
136
140
  }
141
+ function shouldUseFullListForStructuredLookup(domain, appDataDir) {
142
+ return domain.includes("/") && !appDataDir;
143
+ }
144
+ function isBwItemNotFoundError(error) {
145
+ const message = error.message.toLowerCase();
146
+ return message.includes("not found") || message.includes("no item");
147
+ }
137
148
  // ---------------------------------------------------------------------------
138
149
  // Cross-process bw CLI lock
139
150
  // ---------------------------------------------------------------------------
@@ -242,11 +253,12 @@ async function withBwLock(appDataDir, fn) {
242
253
  }
243
254
  }
244
255
  }
245
- function execBw(args, sessionToken, appDataDir, stdin, bwBinaryPath = "bw") {
256
+ function execBw(args, sessionToken, appDataDir, stdin, bwBinaryPath = "bw", extraEnv = {}) {
246
257
  const env = {
247
258
  ...process.env,
248
259
  ...(sessionToken ? { BW_SESSION: sessionToken } : {}),
249
260
  ...(appDataDir ? { BITWARDENCLI_APPDATA_DIR: appDataDir } : {}),
261
+ ...extraEnv,
250
262
  };
251
263
  const runCommand = () => new Promise((resolve, reject) => {
252
264
  const child = (0, node_child_process_1.execFile)(bwBinaryPath, args, { timeout: 30_000, env }, (err, stdout, stderr) => {
@@ -395,6 +407,9 @@ class BitwardenCredentialStore {
395
407
  execBw(args, sessionToken, stdin) {
396
408
  return execBw(args, sessionToken, this.appDataDir, stdin, this.bwBinaryPath);
397
409
  }
410
+ execBwWithPasswordEnv(args) {
411
+ return execBw([...args, "--passwordenv", BW_PASSWORD_ENV], undefined, this.appDataDir, undefined, this.bwBinaryPath, { [BW_PASSWORD_ENV]: this.masterPassword });
412
+ }
398
413
  /**
399
414
  * Ensure the bw CLI is authenticated and unlocked.
400
415
  * Handles three states: logged out → login, locked → unlock, already unlocked → no-op.
@@ -464,12 +479,22 @@ class BitwardenCredentialStore {
464
479
  }
465
480
  if (status.status === "locked") {
466
481
  // Already logged in, just needs unlock
467
- const unlockOutput = await this.execBw(["unlock", this.masterPassword, "--raw"]);
482
+ const unlockOutput = await this.execBwWithPasswordEnv(["unlock", "--raw"]);
468
483
  this.sessionToken = unlockOutput.trim();
469
484
  }
470
485
  else if (status.status === "unauthenticated" || !status.status) {
471
486
  // Not logged in — full login
472
- const loginOutput = await this.execBw(["login", this.email, this.masterPassword, "--raw"]);
487
+ let loginOutput;
488
+ try {
489
+ loginOutput = await this.execBwWithPasswordEnv(["login", this.email, "--raw"]);
490
+ }
491
+ catch (error) {
492
+ const err = error;
493
+ if (!isBwAlreadyLoggedInError(err)) {
494
+ throw err;
495
+ }
496
+ loginOutput = await this.execBwWithPasswordEnv(["unlock", "--raw"]);
497
+ }
473
498
  try {
474
499
  const parsed = JSON.parse(loginOutput);
475
500
  this.sessionToken = parsed.access_token ?? loginOutput.trim();
@@ -480,7 +505,7 @@ class BitwardenCredentialStore {
480
505
  }
481
506
  else {
482
507
  // Status is "unlocked" — already good, just need the session token
483
- const unlockOutput = await this.execBw(["unlock", this.masterPassword, "--raw"]);
508
+ const unlockOutput = await this.execBwWithPasswordEnv(["unlock", "--raw"]);
484
509
  this.sessionToken = unlockOutput.trim();
485
510
  }
486
511
  if (this.shouldSyncVaultAfterSession(status)) {
@@ -555,7 +580,7 @@ class BitwardenCredentialStore {
555
580
  message: `getting credential via bw for ${domain}`,
556
581
  meta: { domain, backend: "bitwarden" },
557
582
  });
558
- const item = await this.withTransientRetry(() => this.withSessionRetry((session) => this.findItemByDomain(domain, session)));
583
+ const item = await this.withTransientRetry(() => this.withSessionRetry((session) => this.findItemByDomain(domain, session, { preferExactStructured: true })));
559
584
  if (!item) {
560
585
  (0, runtime_1.emitNervesEvent)({
561
586
  event: "repertoire.bw_credential_get_end",
@@ -579,7 +604,7 @@ class BitwardenCredentialStore {
579
604
  };
580
605
  }
581
606
  async getRawSecret(domain, field) {
582
- const item = await this.withTransientRetry(() => this.withSessionRetry((session) => this.findItemByDomain(domain, session)));
607
+ const item = await this.withTransientRetry(() => this.withSessionRetry((session) => this.findItemByDomain(domain, session, { preferExactStructured: true })));
583
608
  if (!item) {
584
609
  throw new Error(`no credential found for domain "${domain}"`);
585
610
  }
@@ -695,8 +720,11 @@ class BitwardenCredentialStore {
695
720
  return true;
696
721
  }
697
722
  // --- Private ---
698
- async findItemByDomain(domain, session) {
699
- if (shouldPreferExactItemLookup(domain)) {
723
+ async findItemByDomain(domain, session, options = {}) {
724
+ if (options.preferExactStructured && shouldUseStructuredItemLookup(domain)) {
725
+ return this.findStructuredItemByName(domain, session);
726
+ }
727
+ if (shouldUseFullListForStructuredLookup(domain, this.appDataDir)) {
700
728
  const items = await this.readStructuredItemCache(session);
701
729
  return items.get(domain) ?? null;
702
730
  }
@@ -705,6 +733,20 @@ class BitwardenCredentialStore {
705
733
  // Find exact match by name
706
734
  return items.find((item) => item.name === domain) ?? null;
707
735
  }
736
+ async findStructuredItemByName(domain, session) {
737
+ try {
738
+ const stdout = await this.execBw(["get", "item", domain], session);
739
+ const item = parseBwItem(stdout, "bw get item");
740
+ return item.name === domain ? item : null;
741
+ }
742
+ catch (error) {
743
+ /* v8 ignore next -- defensive: execBw rejects with Error instances @preserve */
744
+ const err = error instanceof Error ? error : new Error(String(error));
745
+ if (isBwItemNotFoundError(err))
746
+ return null;
747
+ throw err;
748
+ }
749
+ }
708
750
  shouldSyncVaultAfterSession(status) {
709
751
  if (status.status === "unauthenticated" || !status.status)
710
752
  return true;
@@ -740,6 +782,10 @@ class BitwardenCredentialStore {
740
782
  // If the marker cannot be written, fall back to syncing next time.
741
783
  }
742
784
  }
785
+ async findItemById(id, session) {
786
+ const stdout = await this.execBw(["get", "item", id], session);
787
+ return parseBwItem(stdout, "bw get item");
788
+ }
743
789
  async readStructuredItemCache(session) {
744
790
  if (this.structuredItemCache)
745
791
  return this.structuredItemCache;
@@ -748,10 +794,6 @@ class BitwardenCredentialStore {
748
794
  this.structuredItemCache = new Map(items.map((item) => [item.name, item]));
749
795
  return this.structuredItemCache;
750
796
  }
751
- async findItemById(id, session) {
752
- const stdout = await this.execBw(["get", "item", id], session);
753
- return parseBwItem(stdout, "bw get item");
754
- }
755
797
  assertStoredCredentialMatches(domain, data, item) {
756
798
  if (!item) {
757
799
  throw new Error(`bw CLI error: credential save verification failed for ${domain}: saved item could not be read back after write`);
@@ -103,6 +103,7 @@ exports.bridgeToolDefinitions = [
103
103
  messageCount: 20,
104
104
  trustLevel: ctx?.context?.friend?.trustLevel,
105
105
  summarize: ctx?.summarize,
106
+ archiveFallback: true,
106
107
  });
107
108
  if (sessionTail.kind === "missing") {
108
109
  return NO_SESSION_FOUND_MESSAGE;
@@ -747,7 +747,7 @@ exports.mailToolDefinitions = [
747
747
  const blocked = delegatedHumanMailBlocked(ctx);
748
748
  if (blocked)
749
749
  return blocked;
750
- const resolved = (0, reader_1.resolveMailroomReader)();
750
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
751
751
  if (!resolved.ok)
752
752
  return resolved.error;
753
753
  await resolved.store.recordAccess({
@@ -786,7 +786,7 @@ exports.mailToolDefinitions = [
786
786
  if (blocked)
787
787
  return blocked;
788
788
  }
789
- const resolved = (0, reader_1.resolveMailroomReader)();
789
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
790
790
  if (!resolved.ok)
791
791
  return resolved.error;
792
792
  const scope = requestedScope === "all"
@@ -845,7 +845,7 @@ exports.mailToolDefinitions = [
845
845
  handler: async (args, ctx) => {
846
846
  if (!trustAllowsMailRead(ctx))
847
847
  return "mail is private; this tool is only available in trusted contexts.";
848
- const resolved = (0, reader_1.resolveMailroomReader)();
848
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
849
849
  if (!resolved.ok)
850
850
  return resolved.error;
851
851
  try {
@@ -907,7 +907,7 @@ exports.mailToolDefinitions = [
907
907
  const draftId = (args.draft_id ?? "").trim();
908
908
  if (!draftId)
909
909
  return "draft_id is required.";
910
- const resolved = (0, reader_1.resolveMailroomReader)();
910
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
911
911
  if (!resolved.ok)
912
912
  return resolved.error;
913
913
  try {
@@ -971,7 +971,7 @@ exports.mailToolDefinitions = [
971
971
  handler: async (args, ctx) => {
972
972
  if (!trustAllowsMailRead(ctx))
973
973
  return "mail is private; this tool is only available in trusted contexts.";
974
- const resolved = (0, reader_1.resolveMailroomReader)();
974
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
975
975
  /* v8 ignore next -- defensive: reader resolution covered separately for read tools; mail_outbox tests use cached config @preserve */
976
976
  if (!resolved.ok)
977
977
  return resolved.error;
@@ -1046,7 +1046,7 @@ exports.mailToolDefinitions = [
1046
1046
  if (!familyOrAgentSelf(ctx) && explicitScope && requestedScope !== "native") {
1047
1047
  return "delegated human mail requires family trust.";
1048
1048
  }
1049
- const resolved = (0, reader_1.resolveMailroomReader)();
1049
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
1050
1050
  if (!resolved.ok)
1051
1051
  return resolved.error;
1052
1052
  const scope = requestedScope === "all"
@@ -1151,7 +1151,7 @@ exports.mailToolDefinitions = [
1151
1151
  const messageId = (args.message_id ?? "").trim();
1152
1152
  if (!messageId)
1153
1153
  return "message_id is required.";
1154
- const resolved = (0, reader_1.resolveMailroomReader)();
1154
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
1155
1155
  if (!resolved.ok)
1156
1156
  return resolved.error;
1157
1157
  const cached = (0, body_cache_1.getCachedMailBody)(messageId);
@@ -1260,7 +1260,7 @@ exports.mailToolDefinitions = [
1260
1260
  if (blocked)
1261
1261
  return blocked;
1262
1262
  }
1263
- const resolved = (0, reader_1.resolveMailroomReader)();
1263
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
1264
1264
  if (!resolved.ok)
1265
1265
  return resolved.error;
1266
1266
  const seedStored = await resolved.store.getMessage(messageId);
@@ -1347,7 +1347,7 @@ exports.mailToolDefinitions = [
1347
1347
  const blocked = delegatedHumanMailBlocked(ctx);
1348
1348
  if (blocked)
1349
1349
  return blocked;
1350
- const resolved = (0, reader_1.resolveMailroomReader)();
1350
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
1351
1351
  if (!resolved.ok)
1352
1352
  return resolved.error;
1353
1353
  const candidates = await resolved.store.listScreenerCandidates({
@@ -1398,7 +1398,7 @@ exports.mailToolDefinitions = [
1398
1398
  const reason = (args.reason ?? "").trim();
1399
1399
  if (!reason)
1400
1400
  return "reason is required.";
1401
- const resolved = (0, reader_1.resolveMailroomReader)();
1401
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
1402
1402
  if (!resolved.ok)
1403
1403
  return resolved.error;
1404
1404
  let messageId = (args.message_id ?? "").trim();
@@ -1467,7 +1467,7 @@ exports.mailToolDefinitions = [
1467
1467
  const blocked = delegatedHumanMailBlocked(ctx);
1468
1468
  if (blocked)
1469
1469
  return blocked;
1470
- const resolved = (0, reader_1.resolveMailroomReader)();
1470
+ const resolved = await (0, reader_1.resolveMailroomReaderWithRefresh)();
1471
1471
  if (!resolved.ok)
1472
1472
  return resolved.error;
1473
1473
  return renderAccessLog(await resolved.store.listAccessLog(resolved.agentName));