@ouro.bot/cli 0.1.0-alpha.469 → 0.1.0-alpha.470
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 +8 -0
- package/dist/heart/daemon/doctor.js +51 -0
- package/dist/heart/outlook/readers/mail.js +35 -1
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/core.js +88 -0
- package/dist/mailroom/outbound.js +191 -12
- package/dist/mailroom/reader.js +4 -0
- package/dist/outlook-ui/assets/{index-BBM5EysT.js → index-BbOjyIms.js} +14 -14
- package/dist/outlook-ui/index.html +1 -1
- package/dist/repertoire/tools-mail.js +17 -6
- package/package.json +1 -1
|
@@ -33,15 +33,20 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseAcsEmailDeliveryReportEvent = void 0;
|
|
37
|
+
exports.createAcsEmailProviderClient = createAcsEmailProviderClient;
|
|
36
38
|
exports.resolveOutboundTransport = resolveOutboundTransport;
|
|
37
39
|
exports.createMailDraft = createMailDraft;
|
|
38
40
|
exports.confirmMailDraftSend = confirmMailDraftSend;
|
|
39
41
|
exports.listMailOutboundRecords = listMailOutboundRecords;
|
|
42
|
+
exports.reconcileOutboundDeliveryEvent = reconcileOutboundDeliveryEvent;
|
|
40
43
|
const crypto = __importStar(require("node:crypto"));
|
|
41
44
|
const fs = __importStar(require("node:fs"));
|
|
42
45
|
const path = __importStar(require("node:path"));
|
|
43
46
|
const runtime_1 = require("../nerves/runtime");
|
|
47
|
+
const autonomy_1 = require("./autonomy");
|
|
44
48
|
const core_1 = require("./core");
|
|
49
|
+
Object.defineProperty(exports, "parseAcsEmailDeliveryReportEvent", { enumerable: true, get: function () { return core_1.parseAcsEmailDeliveryReportEvent; } });
|
|
45
50
|
function isRecord(value) {
|
|
46
51
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
47
52
|
}
|
|
@@ -49,12 +54,100 @@ function textField(value, key) {
|
|
|
49
54
|
const raw = value[key];
|
|
50
55
|
return typeof raw === "string" ? raw.trim() : "";
|
|
51
56
|
}
|
|
57
|
+
function credentialFields(value) {
|
|
58
|
+
if (!isRecord(value))
|
|
59
|
+
return undefined;
|
|
60
|
+
const accessKey = textField(value, "accessKey");
|
|
61
|
+
const connectionString = textField(value, "connectionString");
|
|
62
|
+
const fields = {
|
|
63
|
+
...(accessKey ? { accessKey } : {}),
|
|
64
|
+
...(connectionString ? { connectionString } : {}),
|
|
65
|
+
};
|
|
66
|
+
return Object.keys(fields).length > 0 ? fields : undefined;
|
|
67
|
+
}
|
|
52
68
|
function normalizeList(values) {
|
|
53
69
|
return values
|
|
54
70
|
.map((value) => value.trim())
|
|
55
71
|
.filter(Boolean)
|
|
56
72
|
.map(core_1.normalizeMailAddress);
|
|
57
73
|
}
|
|
74
|
+
function contentHash(body) {
|
|
75
|
+
return crypto.createHash("sha256").update(body, "utf-8").digest("base64");
|
|
76
|
+
}
|
|
77
|
+
function hmacSignature(input) {
|
|
78
|
+
const stringToSign = `${input.method}\n${input.pathAndQuery}\n${input.date};${input.host};${input.contentHash}`;
|
|
79
|
+
return crypto.createHmac("sha256", Buffer.from(input.accessKey, "base64")).update(stringToSign, "utf-8").digest("base64");
|
|
80
|
+
}
|
|
81
|
+
function recipientObjects(addresses) {
|
|
82
|
+
return addresses.map((address) => ({ address }));
|
|
83
|
+
}
|
|
84
|
+
function providerMessageIdFromOperationLocation(operationLocation) {
|
|
85
|
+
const match = operationLocation.match(/\/operations\/([^?/#]+)/);
|
|
86
|
+
return match?.[1] ?? "";
|
|
87
|
+
}
|
|
88
|
+
function createAcsEmailProviderClient(input) {
|
|
89
|
+
const endpoint = input.endpoint.replace(/\/+$/, "");
|
|
90
|
+
const fetchImpl = input.fetch ?? fetch;
|
|
91
|
+
return {
|
|
92
|
+
async submit(submitInput) {
|
|
93
|
+
const url = new URL(`${endpoint}/emails:send?api-version=2025-09-01`);
|
|
94
|
+
const senderAddress = submitInput.transport.senderAddress ?? submitInput.draft.from;
|
|
95
|
+
const body = JSON.stringify({
|
|
96
|
+
senderAddress,
|
|
97
|
+
recipients: {
|
|
98
|
+
to: recipientObjects(submitInput.draft.to),
|
|
99
|
+
cc: recipientObjects(submitInput.draft.cc),
|
|
100
|
+
bcc: recipientObjects(submitInput.draft.bcc),
|
|
101
|
+
},
|
|
102
|
+
content: {
|
|
103
|
+
subject: submitInput.draft.subject,
|
|
104
|
+
plainText: submitInput.draft.text,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
const date = (input.now ?? (() => new Date()))().toUTCString();
|
|
108
|
+
const hash = contentHash(body);
|
|
109
|
+
const signature = hmacSignature({
|
|
110
|
+
method: "POST",
|
|
111
|
+
pathAndQuery: `${url.pathname}${url.search}`,
|
|
112
|
+
date,
|
|
113
|
+
host: url.host,
|
|
114
|
+
contentHash: hash,
|
|
115
|
+
accessKey: input.accessKey,
|
|
116
|
+
});
|
|
117
|
+
const response = await fetchImpl(url.toString(), {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: {
|
|
120
|
+
"content-type": "application/json",
|
|
121
|
+
"x-ms-date": date,
|
|
122
|
+
"x-ms-content-sha256": hash,
|
|
123
|
+
authorization: `HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=${signature}`,
|
|
124
|
+
},
|
|
125
|
+
body,
|
|
126
|
+
});
|
|
127
|
+
const operationLocation = response.headers.get("operation-location") ?? undefined;
|
|
128
|
+
const providerRequestId = response.headers.get("x-ms-request-id") ?? undefined;
|
|
129
|
+
const payload = await response.json().catch(() => ({}));
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
const reason = typeof payload.error?.message === "string" ? payload.error.message : `HTTP ${response.status}`;
|
|
132
|
+
throw new Error(`ACS outbound send failed: ${reason}`);
|
|
133
|
+
}
|
|
134
|
+
const providerMessageId = typeof payload.id === "string"
|
|
135
|
+
? payload.id
|
|
136
|
+
: operationLocation
|
|
137
|
+
? providerMessageIdFromOperationLocation(operationLocation)
|
|
138
|
+
: "";
|
|
139
|
+
if (!providerMessageId)
|
|
140
|
+
throw new Error("ACS outbound send did not return an operation id");
|
|
141
|
+
return {
|
|
142
|
+
provider: "azure-communication-services",
|
|
143
|
+
providerMessageId,
|
|
144
|
+
...(operationLocation ? { operationLocation } : {}),
|
|
145
|
+
...(providerRequestId ? { providerRequestId } : {}),
|
|
146
|
+
submittedAt: submitInput.submittedAt,
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
58
151
|
function draftId() {
|
|
59
152
|
return `draft_${crypto.randomBytes(12).toString("hex")}`;
|
|
60
153
|
}
|
|
@@ -75,14 +168,21 @@ function resolveOutboundTransport(config) {
|
|
|
75
168
|
return { kind: "local-sink", sinkPath };
|
|
76
169
|
}
|
|
77
170
|
if (transport === "azure-communication-services") {
|
|
171
|
+
if ("credentialItemNoteQuery" in outbound || "noteQuery" in outbound || "notes" in outbound) {
|
|
172
|
+
throw new Error("outbound provider binding must not infer credentials from vault notes");
|
|
173
|
+
}
|
|
78
174
|
const endpoint = textField(outbound, "endpoint");
|
|
79
175
|
if (!endpoint)
|
|
80
176
|
throw new Error("outbound Azure Communication Services transport is missing endpoint");
|
|
81
177
|
const senderAddress = textField(outbound, "senderAddress");
|
|
178
|
+
const credentialItem = textField(outbound, "credentialItem");
|
|
179
|
+
const fields = credentialFields(outbound.credentialFields);
|
|
82
180
|
return {
|
|
83
181
|
kind: "azure-communication-services",
|
|
84
182
|
endpoint,
|
|
85
183
|
...(senderAddress ? { senderAddress } : {}),
|
|
184
|
+
...(credentialItem ? { credentialItem } : {}),
|
|
185
|
+
...(fields ? { credentialFields: fields } : {}),
|
|
86
186
|
};
|
|
87
187
|
}
|
|
88
188
|
throw new Error("outbound mail transport is not configured; human-required: choose local-sink or azure-communication-services");
|
|
@@ -134,36 +234,94 @@ function appendLocalSink(transport, record, sentAt) {
|
|
|
134
234
|
bcc: record.bcc,
|
|
135
235
|
subject: record.subject,
|
|
136
236
|
text: record.text,
|
|
237
|
+
sendMode: record.sendMode,
|
|
238
|
+
policyId: record.policyDecision?.policyId ?? null,
|
|
137
239
|
sentAt,
|
|
138
240
|
})}\n`, "utf-8");
|
|
139
241
|
return transportMessageId;
|
|
140
242
|
}
|
|
141
243
|
function transportSend(transport, record, sentAt) {
|
|
142
|
-
|
|
143
|
-
return appendLocalSink(transport, record, sentAt);
|
|
144
|
-
throw new Error("Azure Communication Services outbound send is configured but not enabled on this machine; human-required setup is still needed");
|
|
244
|
+
return appendLocalSink(transport, record, sentAt);
|
|
145
245
|
}
|
|
146
246
|
async function confirmMailDraftSend(input) {
|
|
147
|
-
if (input.autonomous) {
|
|
148
|
-
throw new Error("Autonomous mail sending is disabled; create a draft and require explicit confirmation instead");
|
|
149
|
-
}
|
|
150
|
-
if (input.confirmation !== "CONFIRM_SEND") {
|
|
151
|
-
throw new Error("mail_send requires confirmation=CONFIRM_SEND before any outbound mail leaves the agent");
|
|
152
|
-
}
|
|
153
247
|
const draft = await input.store.getMailOutbound(input.draftId);
|
|
154
248
|
if (!draft || draft.agentId !== input.agentId)
|
|
155
249
|
throw new Error(`No draft found for ${input.draftId}`);
|
|
156
250
|
if (draft.status !== "draft")
|
|
157
251
|
throw new Error(`Draft ${input.draftId} is already ${draft.status}`);
|
|
158
252
|
const sentAt = (input.now ?? (() => new Date()))().toISOString();
|
|
159
|
-
const
|
|
160
|
-
const
|
|
253
|
+
const recentOutbound = await input.store.listMailOutbound(input.agentId);
|
|
254
|
+
const policyDecision = input.autonomous
|
|
255
|
+
? (() => {
|
|
256
|
+
if (!input.autonomyPolicy) {
|
|
257
|
+
throw new Error("Autonomous mail sending requires an enabled native-agent policy");
|
|
258
|
+
}
|
|
259
|
+
const decision = (0, autonomy_1.evaluateNativeMailSendPolicy)({
|
|
260
|
+
policy: input.autonomyPolicy,
|
|
261
|
+
draft,
|
|
262
|
+
recentOutbound,
|
|
263
|
+
now: new Date(sentAt),
|
|
264
|
+
});
|
|
265
|
+
if (!decision.allowed) {
|
|
266
|
+
if (decision.mode === "confirmation-required") {
|
|
267
|
+
throw new Error(`Autonomous mail send ${decision.code} requires confirmation=CONFIRM_SEND: ${decision.reason}`);
|
|
268
|
+
}
|
|
269
|
+
throw new Error(`${decision.code}: ${decision.reason}`);
|
|
270
|
+
}
|
|
271
|
+
return decision;
|
|
272
|
+
})()
|
|
273
|
+
: (() => {
|
|
274
|
+
if (input.confirmation !== "CONFIRM_SEND") {
|
|
275
|
+
throw new Error("mail_send requires confirmation=CONFIRM_SEND before any outbound mail leaves the agent");
|
|
276
|
+
}
|
|
277
|
+
return (0, autonomy_1.buildConfirmedMailSendDecision)({
|
|
278
|
+
draft,
|
|
279
|
+
policy: input.autonomyPolicy,
|
|
280
|
+
now: new Date(sentAt),
|
|
281
|
+
});
|
|
282
|
+
})();
|
|
283
|
+
const pendingSent = {
|
|
161
284
|
...draft,
|
|
162
|
-
status: "sent",
|
|
285
|
+
status: input.transport.kind === "local-sink" ? "sent" : "submitted",
|
|
163
286
|
actor: input.actor,
|
|
164
287
|
reason: input.reason,
|
|
165
288
|
updatedAt: sentAt,
|
|
289
|
+
sendMode: input.autonomous ? "autonomous" : "confirmed",
|
|
290
|
+
policyDecision,
|
|
166
291
|
sentAt,
|
|
292
|
+
};
|
|
293
|
+
if (input.transport.kind === "azure-communication-services") {
|
|
294
|
+
if (!input.providerClient) {
|
|
295
|
+
throw new Error("Azure Communication Services outbound send is configured but not enabled on this machine; human-required setup is still needed");
|
|
296
|
+
}
|
|
297
|
+
const submission = await input.providerClient.submit({
|
|
298
|
+
draft: pendingSent,
|
|
299
|
+
transport: input.transport,
|
|
300
|
+
submittedAt: sentAt,
|
|
301
|
+
});
|
|
302
|
+
const submitted = {
|
|
303
|
+
...(0, core_1.buildMailProviderSubmission)({
|
|
304
|
+
draft: pendingSent,
|
|
305
|
+
provider: submission.provider,
|
|
306
|
+
providerMessageId: submission.providerMessageId,
|
|
307
|
+
submittedAt: submission.submittedAt ?? sentAt,
|
|
308
|
+
...(submission.operationLocation ? { operationLocation: submission.operationLocation } : {}),
|
|
309
|
+
...(submission.providerRequestId ? { providerRequestId: submission.providerRequestId } : {}),
|
|
310
|
+
}),
|
|
311
|
+
transport: input.transport.kind,
|
|
312
|
+
};
|
|
313
|
+
await input.store.upsertMailOutbound(submitted);
|
|
314
|
+
(0, runtime_1.emitNervesEvent)({
|
|
315
|
+
component: "senses",
|
|
316
|
+
event: "senses.mail_draft_submitted",
|
|
317
|
+
message: "mail draft submitted to outbound provider",
|
|
318
|
+
meta: { agentId: submitted.agentId, id: submitted.id, provider: submitted.provider, providerMessageId: submitted.providerMessageId },
|
|
319
|
+
});
|
|
320
|
+
return submitted;
|
|
321
|
+
}
|
|
322
|
+
const transportMessageId = transportSend(input.transport, pendingSent, sentAt);
|
|
323
|
+
const sent = {
|
|
324
|
+
...pendingSent,
|
|
167
325
|
transport: input.transport.kind,
|
|
168
326
|
transportMessageId,
|
|
169
327
|
};
|
|
@@ -179,3 +337,24 @@ async function confirmMailDraftSend(input) {
|
|
|
179
337
|
function listMailOutboundRecords(store, agentId) {
|
|
180
338
|
return store.listMailOutbound(agentId);
|
|
181
339
|
}
|
|
340
|
+
async function reconcileOutboundDeliveryEvent(input) {
|
|
341
|
+
const records = await input.store.listMailOutbound(input.agentId);
|
|
342
|
+
const outbound = records.find((record) => record.providerMessageId === input.event.providerMessageId);
|
|
343
|
+
if (!outbound)
|
|
344
|
+
throw new Error(`No outbound record found for provider message ${input.event.providerMessageId}`);
|
|
345
|
+
const updated = (0, core_1.reconcileMailDeliveryEvent)({ outbound, event: input.event });
|
|
346
|
+
await input.store.upsertMailOutbound(updated);
|
|
347
|
+
(0, runtime_1.emitNervesEvent)({
|
|
348
|
+
component: "senses",
|
|
349
|
+
event: "senses.mail_delivery_event_reconciled",
|
|
350
|
+
message: "mail delivery event reconciled",
|
|
351
|
+
meta: {
|
|
352
|
+
agentId: updated.agentId,
|
|
353
|
+
id: updated.id,
|
|
354
|
+
provider: input.event.provider,
|
|
355
|
+
providerEventId: input.event.providerEventId,
|
|
356
|
+
outcome: input.event.outcome,
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
return updated;
|
|
360
|
+
}
|
package/dist/mailroom/reader.js
CHANGED
|
@@ -77,6 +77,9 @@ function parseMailroomConfig(value) {
|
|
|
77
77
|
const host = textField(value, "host");
|
|
78
78
|
const attentionIntervalMs = numberField(value, "attentionIntervalMs");
|
|
79
79
|
const outbound = isRecord(value.outbound) ? { ...value.outbound } : undefined;
|
|
80
|
+
const autonomousSendPolicy = isRecord(value.autonomousSendPolicy)
|
|
81
|
+
? { ...value.autonomousSendPolicy }
|
|
82
|
+
: undefined;
|
|
80
83
|
return {
|
|
81
84
|
mailboxAddress,
|
|
82
85
|
...(registryPath ? { registryPath } : {}),
|
|
@@ -89,6 +92,7 @@ function parseMailroomConfig(value) {
|
|
|
89
92
|
...(host ? { host } : {}),
|
|
90
93
|
...(attentionIntervalMs !== undefined ? { attentionIntervalMs } : {}),
|
|
91
94
|
...(outbound ? { outbound } : {}),
|
|
95
|
+
...(autonomousSendPolicy ? { autonomousSendPolicy } : {}),
|
|
92
96
|
privateKeys,
|
|
93
97
|
};
|
|
94
98
|
}
|