@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.
- package/changelog.json +27 -0
- package/dist/heart/agent-entry.js +1 -1
- package/dist/heart/auth/auth-flow.js +1 -0
- package/dist/heart/core.js +12 -6
- package/dist/heart/daemon/agent-config-check.js +1 -1
- package/dist/heart/daemon/cli-exec.js +106 -83
- package/dist/heart/daemon/daemon-health.js +1 -0
- package/dist/heart/daemon/daemon.js +85 -6
- package/dist/heart/daemon/process-manager.js +223 -82
- package/dist/heart/daemon/sense-manager.js +86 -5
- package/dist/heart/daemon/startup-tui.js +67 -1
- package/dist/heart/outlook/readers/mail.js +2 -2
- package/dist/heart/provider-credentials.js +2 -1
- package/dist/heart/runtime-credentials.js +107 -0
- package/dist/heart/session-events.js +8 -9
- package/dist/heart/session-transcript.js +82 -6
- package/dist/heart/turn-context.js +12 -3
- package/dist/mailroom/reader.js +9 -0
- package/dist/mind/prompt.js +12 -10
- package/dist/repertoire/bitwarden-store.js +55 -13
- package/dist/repertoire/tools-bridge.js +1 -0
- package/dist/repertoire/tools-mail.js +11 -11
- package/dist/repertoire/tools-session.js +1 -0
- package/dist/senses/bluebubbles/entry.js +7 -3
- package/dist/senses/bluebubbles/index.js +45 -9
- package/dist/senses/cli-entry.js +1 -1
- package/dist/senses/teams-entry.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
1095
|
-
const
|
|
1096
|
-
for (const event of
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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
|
|
8
|
-
return
|
|
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) =>
|
|
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
|
|
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
|
|
155
|
-
const
|
|
156
|
-
const
|
|
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,
|
package/dist/mailroom/reader.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/mind/prompt.js
CHANGED
|
@@ -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
|
|
427
|
-
const
|
|
428
|
-
const
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
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`);
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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));
|