@ouro.bot/cli 0.1.0-alpha.471 → 0.1.0-alpha.472
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 +7 -0
- package/dist/repertoire/tools-mail.js +76 -6
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.472",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Mail tools now stay usable when a visible message was encrypted to a missing old private key: `mail_recent`, `mail_search`, and `mail_thread` skip or explain only the undecryptable record with body-safe message/key ids.",
|
|
8
|
+
"Agent Mail recovery docs now distinguish restoring an old private key from hosted key rotation, so future agents do not repeatedly rotate when only an already-lost message is affected."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
4
11
|
{
|
|
5
12
|
"version": "0.1.0-alpha.471",
|
|
6
13
|
"changes": [
|
|
@@ -91,6 +91,55 @@ function parseMailList(value) {
|
|
|
91
91
|
.map((entry) => entry.trim())
|
|
92
92
|
.filter(Boolean);
|
|
93
93
|
}
|
|
94
|
+
function missingPrivateMailKeyId(error) {
|
|
95
|
+
const match = /^(?:Error: )?Missing private mail key ([^\s]+)$/.exec(String(error));
|
|
96
|
+
return match?.[1] ?? null;
|
|
97
|
+
}
|
|
98
|
+
function decryptVisibleMessages(messages, privateKeys) {
|
|
99
|
+
const decrypted = [];
|
|
100
|
+
const skipped = [];
|
|
101
|
+
for (const message of messages) {
|
|
102
|
+
try {
|
|
103
|
+
decrypted.push((0, file_store_1.decryptMessages)([message], privateKeys)[0]);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
const keyId = missingPrivateMailKeyId(error);
|
|
107
|
+
if (!keyId)
|
|
108
|
+
throw error;
|
|
109
|
+
skipped.push({ messageId: message.id, keyId });
|
|
110
|
+
(0, runtime_1.emitNervesEvent)({
|
|
111
|
+
component: "repertoire",
|
|
112
|
+
event: "repertoire.mail_decrypt_skipped",
|
|
113
|
+
message: "mail message skipped because its private key is missing",
|
|
114
|
+
meta: { messageId: message.id, keyId },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { decrypted, skipped };
|
|
119
|
+
}
|
|
120
|
+
function renderDecryptSkips(skipped) {
|
|
121
|
+
if (skipped.length === 0)
|
|
122
|
+
return "";
|
|
123
|
+
const noun = skipped.length === 1 ? "message" : "messages";
|
|
124
|
+
const sample = skipped.slice(0, 3).map((entry) => `${entry.messageId} (${entry.keyId})`).join(", ");
|
|
125
|
+
const more = skipped.length > 3 ? `; ${skipped.length - 3} more` : "";
|
|
126
|
+
return [
|
|
127
|
+
`${skipped.length} mail ${noun} could not be decrypted because this agent's vault is missing private mail key material.`,
|
|
128
|
+
`skipped: ${sample}${more}`,
|
|
129
|
+
"recovery: restore the missing private key if available; hosted key rotation can repair future mail, but rotation cannot recover mail already encrypted to a lost private key.",
|
|
130
|
+
].join("\n");
|
|
131
|
+
}
|
|
132
|
+
function appendDecryptSkips(body, skipped) {
|
|
133
|
+
const warning = renderDecryptSkips(skipped);
|
|
134
|
+
return warning ? `${body}\n\n${warning}` : body;
|
|
135
|
+
}
|
|
136
|
+
function renderUndecryptableThread(message, keyId) {
|
|
137
|
+
return [
|
|
138
|
+
`Mail message ${message.id} could not be decrypted because this agent's vault is missing private mail key ${keyId}.`,
|
|
139
|
+
"No body or subject was decrypted.",
|
|
140
|
+
"recovery: restore the missing private key if available; hosted key rotation can repair future mail, but rotation cannot recover mail already encrypted to a lost private key.",
|
|
141
|
+
].join("\n");
|
|
142
|
+
}
|
|
94
143
|
function renderMessageSummary(message) {
|
|
95
144
|
const scope = message.compartmentKind === "delegated"
|
|
96
145
|
? `delegated:${message.ownerEmail ?? "unknown"}:${message.source ?? "source"}`
|
|
@@ -250,9 +299,16 @@ function policyScopeForMessage(message) {
|
|
|
250
299
|
return message.source ? `source:${message.source.toLowerCase()}` : message.compartmentKind;
|
|
251
300
|
}
|
|
252
301
|
function normalizePolicySender(candidate, message, privateKeys) {
|
|
302
|
+
let decryptedFrom = [];
|
|
303
|
+
try {
|
|
304
|
+
decryptedFrom = (0, file_store_1.decryptMessages)([message], privateKeys)[0].private.from;
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
decryptedFrom = [];
|
|
308
|
+
}
|
|
253
309
|
const candidates = [
|
|
254
310
|
candidate?.senderEmail,
|
|
255
|
-
...
|
|
311
|
+
...decryptedFrom,
|
|
256
312
|
message.envelope.mailFrom,
|
|
257
313
|
].filter((value) => typeof value === "string" && value.trim().length > 0 && value !== "(unknown)");
|
|
258
314
|
for (const candidateValue of candidates) {
|
|
@@ -383,7 +439,11 @@ exports.mailToolDefinitions = [
|
|
|
383
439
|
...(args.source ? { source: args.source } : {}),
|
|
384
440
|
});
|
|
385
441
|
}
|
|
386
|
-
|
|
442
|
+
const result = decryptVisibleMessages(messages, resolved.config.privateKeys);
|
|
443
|
+
if (result.decrypted.length === 0) {
|
|
444
|
+
return appendDecryptSkips("No decryptable mail to show.", result.skipped);
|
|
445
|
+
}
|
|
446
|
+
return appendDecryptSkips(result.decrypted.map(renderMessageSummary).join("\n\n"), result.skipped);
|
|
387
447
|
},
|
|
388
448
|
summaryKeys: ["scope", "placement", "source", "limit"],
|
|
389
449
|
},
|
|
@@ -559,7 +619,8 @@ exports.mailToolDefinitions = [
|
|
|
559
619
|
source: args.source,
|
|
560
620
|
limit: 200,
|
|
561
621
|
});
|
|
562
|
-
const
|
|
622
|
+
const result = decryptVisibleMessages(all, resolved.config.privateKeys);
|
|
623
|
+
const matching = result.decrypted
|
|
563
624
|
.filter((message) => [
|
|
564
625
|
message.private.subject,
|
|
565
626
|
message.private.snippet,
|
|
@@ -582,8 +643,8 @@ exports.mailToolDefinitions = [
|
|
|
582
643
|
});
|
|
583
644
|
}
|
|
584
645
|
if (matching.length === 0)
|
|
585
|
-
return "No matching mail.";
|
|
586
|
-
return matching.map(renderMessageSummary).join("\n\n");
|
|
646
|
+
return appendDecryptSkips("No matching mail.", result.skipped);
|
|
647
|
+
return appendDecryptSkips(matching.map(renderMessageSummary).join("\n\n"), result.skipped);
|
|
587
648
|
},
|
|
588
649
|
summaryKeys: ["query", "limit"],
|
|
589
650
|
},
|
|
@@ -621,7 +682,6 @@ exports.mailToolDefinitions = [
|
|
|
621
682
|
if (blocked)
|
|
622
683
|
return blocked;
|
|
623
684
|
}
|
|
624
|
-
const decrypted = (0, file_store_1.decryptMessages)([message], resolved.config.privateKeys)[0];
|
|
625
685
|
await resolved.store.recordAccess({
|
|
626
686
|
agentId: resolved.agentName,
|
|
627
687
|
messageId,
|
|
@@ -629,6 +689,16 @@ exports.mailToolDefinitions = [
|
|
|
629
689
|
reason: args.reason,
|
|
630
690
|
...accessProvenance(message),
|
|
631
691
|
});
|
|
692
|
+
let decrypted;
|
|
693
|
+
try {
|
|
694
|
+
decrypted = (0, file_store_1.decryptMessages)([message], resolved.config.privateKeys)[0];
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
const keyId = missingPrivateMailKeyId(error);
|
|
698
|
+
if (!keyId)
|
|
699
|
+
throw error;
|
|
700
|
+
return renderUndecryptableThread(message, keyId);
|
|
701
|
+
}
|
|
632
702
|
const maxChars = numberArg(args.max_chars, 2000, 200, 6000);
|
|
633
703
|
const body = decrypted.private.text.length > maxChars
|
|
634
704
|
? `${decrypted.private.text.slice(0, maxChars - 3)}...`
|