@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 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
- ...(0, file_store_1.decryptMessages)([message], privateKeys)[0].private.from,
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
- return (0, file_store_1.decryptMessages)(messages, resolved.config.privateKeys).map(renderMessageSummary).join("\n\n");
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 matching = (0, file_store_1.decryptMessages)(all, resolved.config.privateKeys)
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)}...`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.471",
3
+ "version": "0.1.0-alpha.472",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",