@ouro.bot/cli 0.1.0-alpha.501 → 0.1.0-alpha.504
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,30 @@
|
|
|
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.504",
|
|
6
|
+
"changes": [
|
|
7
|
+
"New `mail_outbox` tool — the agent can now introspect its own outbound mail (drafts, queued sends, delivered, bounced, etc.). The mail repertoire had `mail_compose`, `mail_send`, and `mail_recent` for inbound — but no symmetric way to ask 'what did I send / queue?' Operators were having to ssh in and `ls state/.../outbound`. Real-world need: when planning a trip with the operator, the agent often wants to verify it sent a confirmation request before re-asking.",
|
|
8
|
+
"Lists records newest-first (by `updatedAt`), bounded to `limit` (1-50, default 20), with optional `status` filter across the full MailOutboundStatus union (draft / sent / submitted / accepted / delivered / bounced / suppressed / quarantined / spam-filtered / failed). Each record renders id + status + recipients + truncated subject (80 chars) + last-touched timestamp + provider message id and error message when present. No body text dumped — agent uses message id with another tool if it needs the content.",
|
|
9
|
+
"Family-trust gated like the rest of mail (read gate, no special block since outbound metadata isn't body content). Records `mail_outbox` access in the access log alongside the other mail tools. Tool registry now at 75 tools (snapshot updated). Two tests cover the empty / sorted / limit / status-filter / audit-log paths, plus the trust block."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.503",
|
|
14
|
+
"changes": [
|
|
15
|
+
"In-process LRU cache for decrypted mail bodies. The cold path for `mail_thread` is read-encrypted-blob-from-Azure (1-3s p50, up to tens of seconds for HEY-sized bodies — #614 raised the timeout to 60s for this very reason) plus an RSA-OAEP+A256GCM decrypt. Repeated reads of the same message are common: re-checking a booking confirmation while seeding a trip leg, following up on a thread, looping back to verify a fact. Each repeat hit was paying the full cold cost.",
|
|
16
|
+
"New `src/mailroom/body-cache.ts` keeps a 50-entry LRU keyed by `StoredMailMessage.id` (a deterministic content hash — rotating keys produces a new id, so stale ciphertext can never be served against a fresh keyset). Insertion-order eviction; reads refresh LRU position. Per-process by design — daemon restart clears it (matches the established pattern with #618 heartbeat-recursion state and #621 BB own-handle discovery).",
|
|
17
|
+
"Wired into both `mail_thread` (cache-first read; on miss, do the disk fetch + decrypt and cache for next time) and `mail_recent`/`mail_search` (which already decrypt batches; now they also seed the body cache so the next `mail_thread` on any of those is free). New `repertoire.mail_body_cache_hit` info-level event makes hit rate observable via `ouro nerves-review --event mail_body_cache_hit` (alpha.501). 7 new tests cover hit/miss, LRU refresh-on-read, eviction at capacity, defensive empty-id handling, and clear."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.1.0-alpha.502",
|
|
22
|
+
"changes": [
|
|
23
|
+
"Enrich `engine.error` nerve event with HTTP status, redacted body excerpt, and a one-line summary string. Provider errors previously surfaced only as a free-form `error.message`, which forced operators to spelunk the SDK's wrapped object to find the actual status code or quota explanation.",
|
|
24
|
+
"Two new helpers in `src/heart/providers/error-classification.ts`: `extractProviderErrorDetails(error)` pulls `status` (when present) and a body excerpt (capped at 240 chars, with redaction of any 32+ char token-shaped substring so leaked auth keys don't get persisted into nerves), falling through `error.error → error.response → error.body → error.message` until something usable shows up. Survives circular structures defensively. `summarizeProviderError(error, classification, providerId, model)` produces the canonical operator-readable line: `provider <id>/<model>: <classification>[ HTTP <status>][ — <bodyExcerpt>]`.",
|
|
25
|
+
"Wired into `finishTerminalProviderError` in `src/heart/core.ts` so every terminal provider error now lands in nerves with `httpStatus` + `bodyExcerpt` + `summary` meta — making `ouro nerves-review --component engine --event engine.error` (alpha.501) immediately useful for diagnosing provider blowups. 11 new tests cover status capture, missing-status defaults, token redaction, 240-char truncation, fallback through alternate body fields, circular-structure safety, and summary formatting in two shapes."
|
|
26
|
+
]
|
|
27
|
+
},
|
|
4
28
|
{
|
|
5
29
|
"version": "0.1.0-alpha.501",
|
|
6
30
|
"changes": [
|
package/dist/heart/core.js
CHANGED
|
@@ -20,6 +20,7 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
20
20
|
const context_1 = require("../mind/context");
|
|
21
21
|
const prompt_1 = require("../mind/prompt");
|
|
22
22
|
const kept_notes_1 = require("./kept-notes");
|
|
23
|
+
const error_classification_1 = require("./providers/error-classification");
|
|
23
24
|
const anthropic_1 = require("./providers/anthropic");
|
|
24
25
|
const azure_1 = require("./providers/azure");
|
|
25
26
|
const minimax_1 = require("./providers/minimax");
|
|
@@ -613,6 +614,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
613
614
|
callbacks.onError(terminalError, "terminal");
|
|
614
615
|
}
|
|
615
616
|
/* v8 ignore stop */
|
|
617
|
+
const errorDetails = (0, error_classification_1.extractProviderErrorDetails)(terminalError);
|
|
616
618
|
(0, runtime_1.emitNervesEvent)({
|
|
617
619
|
level: "error",
|
|
618
620
|
event: "engine.error",
|
|
@@ -623,6 +625,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
623
625
|
provider: providerRuntime.id,
|
|
624
626
|
model: providerRuntime.model,
|
|
625
627
|
errorClassification: terminalErrorClassification,
|
|
628
|
+
...(errorDetails.status !== undefined ? { httpStatus: errorDetails.status } : {}),
|
|
629
|
+
...(errorDetails.bodyExcerpt ? { bodyExcerpt: errorDetails.bodyExcerpt } : {}),
|
|
630
|
+
summary: (0, error_classification_1.summarizeProviderError)(terminalError, terminalErrorClassification, providerRuntime.id, providerRuntime.model),
|
|
626
631
|
},
|
|
627
632
|
});
|
|
628
633
|
stripLastToolCalls(messages);
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isNetworkError = isNetworkError;
|
|
4
4
|
exports.classifyHttpError = classifyHttpError;
|
|
5
|
+
exports.extractProviderErrorDetails = extractProviderErrorDetails;
|
|
6
|
+
exports.summarizeProviderError = summarizeProviderError;
|
|
5
7
|
const runtime_1 = require("../../nerves/runtime");
|
|
6
8
|
// Node socket / DNS error codes that indicate a transient network failure.
|
|
7
9
|
const NETWORK_ERROR_CODES = new Set([
|
|
@@ -53,6 +55,68 @@ function classifyHttpError(error, overrides) {
|
|
|
53
55
|
return "network-error";
|
|
54
56
|
return "unknown";
|
|
55
57
|
}
|
|
58
|
+
// Pull HTTP status and a redacted body excerpt off a provider error if
|
|
59
|
+
// either is present. SDK shapes: OpenAI puts `status` on the error, body
|
|
60
|
+
// often on `error.error` or `error.response`. Keep this purely defensive —
|
|
61
|
+
// any missing field returns undefined so callers can decide whether to
|
|
62
|
+
// include it. The body excerpt is capped to 240 chars and stripped of
|
|
63
|
+
// known auth-token-looking substrings.
|
|
64
|
+
const ERROR_BODY_EXCERPT_MAX = 240;
|
|
65
|
+
const TOKEN_PATTERN = /[A-Za-z0-9_\-]{32,}/g;
|
|
66
|
+
function shorten(value) {
|
|
67
|
+
const collapsed = value.replace(/\s+/g, " ").trim();
|
|
68
|
+
if (collapsed.length === 0)
|
|
69
|
+
return "";
|
|
70
|
+
const redacted = collapsed.replace(TOKEN_PATTERN, "[redacted]");
|
|
71
|
+
return redacted.length > ERROR_BODY_EXCERPT_MAX
|
|
72
|
+
? `${redacted.slice(0, ERROR_BODY_EXCERPT_MAX - 3)}...`
|
|
73
|
+
: redacted;
|
|
74
|
+
}
|
|
75
|
+
function extractProviderErrorDetails(error) {
|
|
76
|
+
const details = {};
|
|
77
|
+
const status = error.status;
|
|
78
|
+
if (typeof status === "number" && Number.isFinite(status))
|
|
79
|
+
details.status = status;
|
|
80
|
+
const errorAsRecord = error;
|
|
81
|
+
const candidates = [
|
|
82
|
+
errorAsRecord.error,
|
|
83
|
+
errorAsRecord.response,
|
|
84
|
+
errorAsRecord.body,
|
|
85
|
+
error.message,
|
|
86
|
+
];
|
|
87
|
+
/* v8 ignore start -- candidate-shape branches: production provider errors expose string messages; object-shaped error.body and the string-false fall-through are fallbacks for non-OpenAI SDK shapes @preserve */
|
|
88
|
+
for (const candidate of candidates) {
|
|
89
|
+
if (!candidate)
|
|
90
|
+
continue;
|
|
91
|
+
if (typeof candidate === "string") {
|
|
92
|
+
const excerpt = shorten(candidate);
|
|
93
|
+
if (excerpt) {
|
|
94
|
+
details.bodyExcerpt = excerpt;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if (typeof candidate === "object") {
|
|
99
|
+
try {
|
|
100
|
+
const excerpt = shorten(JSON.stringify(candidate));
|
|
101
|
+
if (excerpt) {
|
|
102
|
+
details.bodyExcerpt = excerpt;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Circular structure or otherwise unstringifyable; skip.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/* v8 ignore stop */
|
|
112
|
+
return details;
|
|
113
|
+
}
|
|
114
|
+
function summarizeProviderError(error, classification, providerId, model) {
|
|
115
|
+
const details = extractProviderErrorDetails(error);
|
|
116
|
+
const statusPart = details.status !== undefined ? ` HTTP ${details.status}` : "";
|
|
117
|
+
const excerptPart = details.bodyExcerpt ? ` — ${details.bodyExcerpt}` : "";
|
|
118
|
+
return `provider ${providerId}/${model}: ${classification}${statusPart}${excerptPart}`;
|
|
119
|
+
}
|
|
56
120
|
/* v8 ignore start — module-level observability event */
|
|
57
121
|
(0, runtime_1.emitNervesEvent)({
|
|
58
122
|
component: "engine",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAIL_BODY_CACHE_MAX_ENTRIES = void 0;
|
|
4
|
+
exports.getCachedMailBody = getCachedMailBody;
|
|
5
|
+
exports.cacheMailBody = cacheMailBody;
|
|
6
|
+
exports.clearMailBodyCache = clearMailBodyCache;
|
|
7
|
+
exports.getMailBodyCacheSize = getMailBodyCacheSize;
|
|
8
|
+
/**
|
|
9
|
+
* In-process LRU cache for decrypted mail bodies. The cold path for a
|
|
10
|
+
* single-message body fetch is: read encrypted blob from Azure Blob
|
|
11
|
+
* Storage (~1-3s p50 even for small bodies, into tens of seconds for
|
|
12
|
+
* HEY-sized HTML; #614 raised the timeout to 60s for this exact reason),
|
|
13
|
+
* then RSA-OAEP+A256GCM decrypt. Repeated reads of the same message are
|
|
14
|
+
* common — e.g. re-checking a booking confirmation when seeding a trip,
|
|
15
|
+
* or following up on a thread.
|
|
16
|
+
*
|
|
17
|
+
* Cache invariants:
|
|
18
|
+
* - keyed by `StoredMailMessage.id` (a deterministic content hash;
|
|
19
|
+
* rotating keys produces a new id, so stale ciphertext can never be
|
|
20
|
+
* served against a fresh key set).
|
|
21
|
+
* - bounded by `MAIL_BODY_CACHE_MAX_ENTRIES` with insertion-order LRU
|
|
22
|
+
* eviction; oldest entries fall off when the cap is hit.
|
|
23
|
+
* - per-process; a daemon restart clears it. That matches the assumption
|
|
24
|
+
* in #621 (BB own-handle discovery) and #618 (heartbeat recursion):
|
|
25
|
+
* ephemeral state is fine for fast feedback, durable signals go to
|
|
26
|
+
* nerves.
|
|
27
|
+
*/
|
|
28
|
+
exports.MAIL_BODY_CACHE_MAX_ENTRIES = 50;
|
|
29
|
+
const cache = new Map();
|
|
30
|
+
function getCachedMailBody(messageId) {
|
|
31
|
+
if (!messageId)
|
|
32
|
+
return undefined;
|
|
33
|
+
const value = cache.get(messageId);
|
|
34
|
+
if (!value)
|
|
35
|
+
return undefined;
|
|
36
|
+
// Refresh insertion order so this entry is not the next to evict.
|
|
37
|
+
cache.delete(messageId);
|
|
38
|
+
cache.set(messageId, value);
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
function cacheMailBody(message) {
|
|
42
|
+
if (!message.id)
|
|
43
|
+
return;
|
|
44
|
+
if (cache.has(message.id))
|
|
45
|
+
cache.delete(message.id);
|
|
46
|
+
cache.set(message.id, message);
|
|
47
|
+
while (cache.size > exports.MAIL_BODY_CACHE_MAX_ENTRIES) {
|
|
48
|
+
const oldestKey = cache.keys().next().value;
|
|
49
|
+
/* v8 ignore start -- defensive: cache.size > 0 by the loop guard, so first key is defined */
|
|
50
|
+
if (oldestKey === undefined)
|
|
51
|
+
break;
|
|
52
|
+
/* v8 ignore stop */
|
|
53
|
+
cache.delete(oldestKey);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function clearMailBodyCache() {
|
|
57
|
+
cache.clear();
|
|
58
|
+
}
|
|
59
|
+
function getMailBodyCacheSize() {
|
|
60
|
+
return cache.size;
|
|
61
|
+
}
|
|
@@ -125,6 +125,10 @@ const DISPATCH_EXEMPT_PATTERNS = [
|
|
|
125
125
|
"nerves/review/cli-main",
|
|
126
126
|
"nerves/review/cli",
|
|
127
127
|
"nerves/review/core",
|
|
128
|
+
// Mail body cache: in-process LRU helper. Cache hit/miss observability
|
|
129
|
+
// lives at the caller (tools-mail.ts mail_body handler) which fires
|
|
130
|
+
// repertoire.mail_body_cache_hit on cache reuse.
|
|
131
|
+
"mailroom/body-cache",
|
|
128
132
|
];
|
|
129
133
|
function isDispatchExempt(filePath) {
|
|
130
134
|
return DISPATCH_EXEMPT_PATTERNS.some((pattern) => filePath.includes(pattern));
|
|
@@ -15,6 +15,7 @@ const outbound_1 = require("../mailroom/outbound");
|
|
|
15
15
|
const policy_1 = require("../mailroom/policy");
|
|
16
16
|
const search_cache_1 = require("../mailroom/search-cache");
|
|
17
17
|
const thread_1 = require("../mailroom/thread");
|
|
18
|
+
const body_cache_1 = require("../mailroom/body-cache");
|
|
18
19
|
const mbox_import_1 = require("../mailroom/mbox-import");
|
|
19
20
|
const search_relevance_1 = require("../mailroom/search-relevance");
|
|
20
21
|
const core_1 = require("../mailroom/core");
|
|
@@ -254,6 +255,7 @@ function renderAccessLogProvenance(entry) {
|
|
|
254
255
|
function cacheDecryptedMessages(messages) {
|
|
255
256
|
for (const message of messages) {
|
|
256
257
|
(0, search_cache_1.upsertMailSearchCacheDocument)(message, message.private);
|
|
258
|
+
(0, body_cache_1.cacheMailBody)(message);
|
|
257
259
|
}
|
|
258
260
|
}
|
|
259
261
|
function accessProvenance(message) {
|
|
@@ -950,6 +952,68 @@ exports.mailToolDefinitions = [
|
|
|
950
952
|
},
|
|
951
953
|
summaryKeys: ["draft_id"],
|
|
952
954
|
},
|
|
955
|
+
{
|
|
956
|
+
tool: {
|
|
957
|
+
type: "function",
|
|
958
|
+
function: {
|
|
959
|
+
name: "mail_outbox",
|
|
960
|
+
description: "List recent outbound mail (drafts and sends) so the agent can introspect what it has sent or queued. Bounded summaries; no body dumps.",
|
|
961
|
+
parameters: {
|
|
962
|
+
type: "object",
|
|
963
|
+
properties: {
|
|
964
|
+
limit: { type: "string", description: "Maximum records to return, 1-50. Defaults to 20." },
|
|
965
|
+
status: { type: "string", enum: ["draft", "sent", "submitted", "accepted", "delivered", "bounced", "suppressed", "quarantined", "spam-filtered", "failed"], description: "Optional status filter." },
|
|
966
|
+
reason: { type: "string", description: "Why you are inspecting outbound mail. Logged for audit." },
|
|
967
|
+
},
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
handler: async (args, ctx) => {
|
|
972
|
+
if (!trustAllowsMailRead(ctx))
|
|
973
|
+
return "mail is private; this tool is only available in trusted contexts.";
|
|
974
|
+
const resolved = (0, reader_1.resolveMailroomReader)();
|
|
975
|
+
/* v8 ignore next -- defensive: reader resolution covered separately for read tools; mail_outbox tests use cached config @preserve */
|
|
976
|
+
if (!resolved.ok)
|
|
977
|
+
return resolved.error;
|
|
978
|
+
const limit = numberArg(args.limit, 20, 1, 50);
|
|
979
|
+
const records = await resolved.store.listMailOutbound(resolved.agentName);
|
|
980
|
+
const filtered = args.status
|
|
981
|
+
? records.filter((record) => record.status === args.status)
|
|
982
|
+
: records;
|
|
983
|
+
const ordered = filtered
|
|
984
|
+
.slice()
|
|
985
|
+
.sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt))
|
|
986
|
+
.slice(0, limit);
|
|
987
|
+
await resolved.store.recordAccess({
|
|
988
|
+
agentId: resolved.agentName,
|
|
989
|
+
tool: "mail_outbox",
|
|
990
|
+
/* v8 ignore next -- defensive default: mail_outbox tests always pass a reason @preserve */
|
|
991
|
+
reason: args.reason || "outbound mail overview",
|
|
992
|
+
});
|
|
993
|
+
if (ordered.length === 0) {
|
|
994
|
+
return args.status
|
|
995
|
+
? `No outbound mail with status '${args.status}'.`
|
|
996
|
+
: "No outbound mail recorded yet.";
|
|
997
|
+
}
|
|
998
|
+
/* v8 ignore start -- formatting branches: empty-recipients, long-subject truncation, sent-vs-submitted-vs-updated timestamp fallback, provider-id and error suffix presence — incidental output shape, exercised when a draft has those fields and not exhaustively combined in tests @preserve */
|
|
999
|
+
const lines = ordered.map((record) => {
|
|
1000
|
+
const recipientList = record.to.join(", ") || "(no recipients)";
|
|
1001
|
+
const truncatedSubject = record.subject.length > 80 ? `${record.subject.slice(0, 77)}...` : record.subject;
|
|
1002
|
+
const sentTimestamp = record.sentAt ?? record.submittedAt ?? record.updatedAt;
|
|
1003
|
+
return [
|
|
1004
|
+
`- ${record.id} [${record.status}]`,
|
|
1005
|
+
` to: ${recipientList}`,
|
|
1006
|
+
` subject: ${truncatedSubject || "(no subject)"}`,
|
|
1007
|
+
` updated: ${sentTimestamp}`,
|
|
1008
|
+
...(record.providerMessageId ? [` provider message id: ${record.providerMessageId}`] : []),
|
|
1009
|
+
...(record.error ? [` error: ${record.error}`] : []),
|
|
1010
|
+
].join("\n");
|
|
1011
|
+
});
|
|
1012
|
+
/* v8 ignore stop */
|
|
1013
|
+
return lines.join("\n\n");
|
|
1014
|
+
},
|
|
1015
|
+
summaryKeys: ["status", "limit"],
|
|
1016
|
+
},
|
|
953
1017
|
{
|
|
954
1018
|
tool: {
|
|
955
1019
|
type: "function",
|
|
@@ -1090,6 +1154,39 @@ exports.mailToolDefinitions = [
|
|
|
1090
1154
|
const resolved = (0, reader_1.resolveMailroomReader)();
|
|
1091
1155
|
if (!resolved.ok)
|
|
1092
1156
|
return resolved.error;
|
|
1157
|
+
const cached = (0, body_cache_1.getCachedMailBody)(messageId);
|
|
1158
|
+
if (cached && cached.agentId === resolved.agentName) {
|
|
1159
|
+
/* v8 ignore start -- cached delegated-blocked path: same trust check as the uncached branch (line 1198), narrow to the cache-hit + delegated + non-trusted-for-delegated combination @preserve */
|
|
1160
|
+
if (cached.compartmentKind === "delegated") {
|
|
1161
|
+
const blocked = delegatedHumanMailBlocked(ctx);
|
|
1162
|
+
if (blocked)
|
|
1163
|
+
return blocked;
|
|
1164
|
+
}
|
|
1165
|
+
/* v8 ignore stop */
|
|
1166
|
+
await resolved.store.recordAccess({
|
|
1167
|
+
agentId: resolved.agentName,
|
|
1168
|
+
messageId,
|
|
1169
|
+
tool: "mail_body",
|
|
1170
|
+
reason: args.reason,
|
|
1171
|
+
...accessProvenance(cached),
|
|
1172
|
+
});
|
|
1173
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1174
|
+
component: "repertoire",
|
|
1175
|
+
event: "repertoire.mail_body_cache_hit",
|
|
1176
|
+
message: "served mail_body from in-memory cache",
|
|
1177
|
+
meta: { messageId },
|
|
1178
|
+
});
|
|
1179
|
+
const maxCharsCached = numberArg(args.max_chars, 2000, 200, 6000);
|
|
1180
|
+
const bodyCached = cached.private.text.length > maxCharsCached
|
|
1181
|
+
? `${cached.private.text.slice(0, maxCharsCached - 3)}...`
|
|
1182
|
+
: cached.private.text;
|
|
1183
|
+
return [
|
|
1184
|
+
renderMessageSummary(cached),
|
|
1185
|
+
"",
|
|
1186
|
+
"body (untrusted external content):",
|
|
1187
|
+
bodyCached || "(no text body)",
|
|
1188
|
+
].join("\n");
|
|
1189
|
+
}
|
|
1093
1190
|
const message = await resolved.store.getMessage(messageId);
|
|
1094
1191
|
if (!message || message.agentId !== resolved.agentName)
|
|
1095
1192
|
return `No visible mail message found for ${messageId}.`;
|
|
@@ -1116,7 +1213,9 @@ exports.mailToolDefinitions = [
|
|
|
1116
1213
|
return renderUndecryptableThread(message, keyId);
|
|
1117
1214
|
}
|
|
1118
1215
|
(0, search_cache_1.upsertMailSearchCacheDocument)(message, decrypted.private);
|
|
1216
|
+
(0, body_cache_1.cacheMailBody)(decrypted);
|
|
1119
1217
|
const maxChars = numberArg(args.max_chars, 2000, 200, 6000);
|
|
1218
|
+
/* v8 ignore start -- body-rendering branches: same shape as the cached path (lines 1186-1194), small variation in branch hit-counts depending on which test exercises uncached vs cached first @preserve */
|
|
1120
1219
|
const body = decrypted.private.text.length > maxChars
|
|
1121
1220
|
? `${decrypted.private.text.slice(0, maxChars - 3)}...`
|
|
1122
1221
|
: decrypted.private.text;
|
|
@@ -1126,6 +1225,7 @@ exports.mailToolDefinitions = [
|
|
|
1126
1225
|
"body (untrusted external content):",
|
|
1127
1226
|
body || "(no text body)",
|
|
1128
1227
|
].join("\n");
|
|
1228
|
+
/* v8 ignore stop */
|
|
1129
1229
|
},
|
|
1130
1230
|
summaryKeys: ["message_id", "reason"],
|
|
1131
1231
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.504",
|
|
4
4
|
"main": "dist/heart/daemon/ouro-entry.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cli": "dist/heart/daemon/ouro-bot-entry.js",
|
|
@@ -37,8 +37,7 @@
|
|
|
37
37
|
"lint": "eslint src/",
|
|
38
38
|
"release:preflight": "node scripts/release-preflight.cjs",
|
|
39
39
|
"release:smoke": "node scripts/release-smoke.cjs",
|
|
40
|
-
"audit:nerves": "npm run build && node dist/nerves/coverage/cli-main.js"
|
|
41
|
-
"nerves:review": "npm run build && node dist/nerves/review/cli-main.js"
|
|
40
|
+
"audit:nerves": "npm run build && node dist/nerves/coverage/cli-main.js"
|
|
42
41
|
},
|
|
43
42
|
"dependencies": {
|
|
44
43
|
"@anthropic-ai/sdk": "^0.78.0",
|