@ouro.bot/cli 0.1.0-alpha.472 → 0.1.0-alpha.474
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,22 @@
|
|
|
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.474",
|
|
6
|
+
"changes": [
|
|
7
|
+
"DNS workflow planning now handles duplicate-capable records safely by matching exact type/name/content identity before singleton replacement, so adding an ACS apex TXT verification record cannot rewrite unrelated Google, Microsoft, or SPF TXT records.",
|
|
8
|
+
"DNS rollback planning can now delete only the extra allowlisted duplicate record that was not present in the backup while preserving sibling TXT records with the same name.",
|
|
9
|
+
"`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the DNS duplicate-record safety release."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.473",
|
|
14
|
+
"changes": [
|
|
15
|
+
"`mail_send` now resolves Azure Communication Services outbound provider clients from explicit generic vault-item bindings, so production ACS sends can leave the agent mailbox after `CONFIRM_SEND` without parsing vault notes.",
|
|
16
|
+
"Outbound provider bindings now require an explicit credential item and secret field for ACS access keys, with tests covering missing, blank, malformed, non-object, and legacy-shaped vault payloads without leaking secret values.",
|
|
17
|
+
"The `ouro.bot` wrapper stays version-synced for the outbound provider-client release."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
4
20
|
{
|
|
5
21
|
"version": "0.1.0-alpha.472",
|
|
6
22
|
"changes": [
|
|
@@ -266,28 +266,57 @@ function recordsEqual(left, right) {
|
|
|
266
266
|
left.ttl === right.ttl &&
|
|
267
267
|
priorityEqual;
|
|
268
268
|
}
|
|
269
|
+
function recordIdentityKey(record) {
|
|
270
|
+
const priority = record.type === "MX" ? String(record.priority ?? 0) : "";
|
|
271
|
+
return `${recordKey(record)}:${record.content}:${priority}`;
|
|
272
|
+
}
|
|
273
|
+
function sameRecordIdentity(left, right) {
|
|
274
|
+
return recordIdentityKey(left) === recordIdentityKey(right);
|
|
275
|
+
}
|
|
276
|
+
function recordsWithKey(records, key) {
|
|
277
|
+
return records.filter((record) => recordKey(record) === key);
|
|
278
|
+
}
|
|
279
|
+
function findCurrentRecordForDesired(input) {
|
|
280
|
+
const key = recordKey(input.desired);
|
|
281
|
+
const currentSameKey = recordsWithKey(input.currentRecords, key);
|
|
282
|
+
const exact = currentSameKey.find((record) => sameRecordIdentity(record, input.desired));
|
|
283
|
+
if (exact)
|
|
284
|
+
return exact;
|
|
285
|
+
const desiredSameKey = recordsWithKey(input.desiredRecords, key);
|
|
286
|
+
if (currentSameKey.length === 1 && desiredSameKey.length === 1)
|
|
287
|
+
return currentSameKey[0];
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
269
290
|
function planDnsWorkflow(input) {
|
|
270
291
|
assertDesiredRecordsAllowed(input.binding);
|
|
271
292
|
const allowedKeys = new Set(input.binding.resources.records.map(recordKey));
|
|
272
|
-
const desiredKeys = new Set(input.binding.desired.records.map(recordKey));
|
|
273
293
|
const changes = [];
|
|
294
|
+
const matchedCurrentRecords = new Set();
|
|
274
295
|
for (const desired of input.binding.desired.records) {
|
|
275
|
-
const current =
|
|
296
|
+
const current = findCurrentRecordForDesired({
|
|
297
|
+
desired,
|
|
298
|
+
desiredRecords: input.binding.desired.records,
|
|
299
|
+
currentRecords: input.currentRecords,
|
|
300
|
+
});
|
|
276
301
|
if (!current) {
|
|
277
302
|
changes.push({ action: "create", record: desired, reason: "desired record is missing" });
|
|
278
303
|
}
|
|
279
304
|
else if (!recordsEqual(current, desired)) {
|
|
305
|
+
matchedCurrentRecords.add(current);
|
|
280
306
|
changes.push({ action: "update", record: desired, currentRecord: current, reason: "desired record differs from current provider record" });
|
|
281
307
|
}
|
|
308
|
+
else {
|
|
309
|
+
matchedCurrentRecords.add(current);
|
|
310
|
+
}
|
|
282
311
|
}
|
|
283
312
|
if (input.deleteExtraAllowedRecords) {
|
|
284
313
|
for (const current of input.currentRecords) {
|
|
285
|
-
if (allowedKeys.has(recordKey(current)) && !
|
|
314
|
+
if (allowedKeys.has(recordKey(current)) && !matchedCurrentRecords.has(current)) {
|
|
286
315
|
changes.push({ action: "delete", record: current, currentRecord: current, reason: "allowlisted record is absent from rollback backup" });
|
|
287
316
|
}
|
|
288
317
|
}
|
|
289
318
|
}
|
|
290
|
-
const preservedRecords = input.currentRecords.filter((record) => !
|
|
319
|
+
const preservedRecords = input.currentRecords.filter((record) => !matchedCurrentRecords.has(record));
|
|
291
320
|
return {
|
|
292
321
|
backup: { domain: input.binding.domain, records: input.currentRecords },
|
|
293
322
|
changes,
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.parseAcsEmailDeliveryReportEvent = void 0;
|
|
37
37
|
exports.createAcsEmailProviderClient = createAcsEmailProviderClient;
|
|
38
|
+
exports.resolveOutboundProviderClient = resolveOutboundProviderClient;
|
|
38
39
|
exports.resolveOutboundTransport = resolveOutboundTransport;
|
|
39
40
|
exports.createMailDraft = createMailDraft;
|
|
40
41
|
exports.confirmMailDraftSend = confirmMailDraftSend;
|
|
@@ -148,6 +149,25 @@ function createAcsEmailProviderClient(input) {
|
|
|
148
149
|
},
|
|
149
150
|
};
|
|
150
151
|
}
|
|
152
|
+
async function resolveOutboundProviderClient(transport, reader, options = {}) {
|
|
153
|
+
if (transport.kind !== "azure-communication-services")
|
|
154
|
+
return undefined;
|
|
155
|
+
const credentialItem = transport.credentialItem?.trim();
|
|
156
|
+
if (!credentialItem) {
|
|
157
|
+
throw new Error("outbound Azure Communication Services transport is missing credentialItem");
|
|
158
|
+
}
|
|
159
|
+
const accessKeyField = transport.credentialFields?.accessKey?.trim() || "accessKey";
|
|
160
|
+
const accessKey = (await reader.readSecretField(credentialItem, accessKeyField)).trim();
|
|
161
|
+
if (!accessKey) {
|
|
162
|
+
throw new Error(`outbound Azure Communication Services required secret field ${accessKeyField} is blank`);
|
|
163
|
+
}
|
|
164
|
+
return createAcsEmailProviderClient({
|
|
165
|
+
endpoint: transport.endpoint,
|
|
166
|
+
accessKey,
|
|
167
|
+
...(options.fetch ? { fetch: options.fetch } : {}),
|
|
168
|
+
...(options.now ? { now: options.now } : {}),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
151
171
|
function draftId() {
|
|
152
172
|
return `draft_${crypto.randomBytes(12).toString("hex")}`;
|
|
153
173
|
}
|
|
@@ -42,6 +42,7 @@ const outbound_1 = require("../mailroom/outbound");
|
|
|
42
42
|
const policy_1 = require("../mailroom/policy");
|
|
43
43
|
const core_1 = require("../mailroom/core");
|
|
44
44
|
const runtime_1 = require("../nerves/runtime");
|
|
45
|
+
const credential_access_1 = require("./credential-access");
|
|
45
46
|
function trustAllowsMailRead(ctx) {
|
|
46
47
|
const trustLevel = ctx?.context?.friend?.trustLevel;
|
|
47
48
|
const allowed = trustLevel === undefined || (0, types_1.isTrustedLevel)(trustLevel);
|
|
@@ -133,6 +134,34 @@ function appendDecryptSkips(body, skipped) {
|
|
|
133
134
|
const warning = renderDecryptSkips(skipped);
|
|
134
135
|
return warning ? `${body}\n\n${warning}` : body;
|
|
135
136
|
}
|
|
137
|
+
function isRecord(value) {
|
|
138
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
139
|
+
}
|
|
140
|
+
function vaultItemSecretField(rawSecret, item, field) {
|
|
141
|
+
let payload;
|
|
142
|
+
try {
|
|
143
|
+
payload = JSON.parse(rawSecret);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new Error(`vault item ${item} secret payload must be valid JSON`);
|
|
147
|
+
}
|
|
148
|
+
if (!isRecord(payload))
|
|
149
|
+
throw new Error(`vault item ${item} secret payload must be an object`);
|
|
150
|
+
const secretFields = isRecord(payload.secretFields) ? payload.secretFields : {};
|
|
151
|
+
const value = [secretFields[field], payload[field]]
|
|
152
|
+
.find((candidate) => typeof candidate === "string" && candidate.trim().length > 0);
|
|
153
|
+
if (!value)
|
|
154
|
+
throw new Error(`vault item ${item} is missing required secret field ${field}`);
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
async function outboundProviderClientForTransport(agentName, transport) {
|
|
158
|
+
return (0, outbound_1.resolveOutboundProviderClient)(transport, {
|
|
159
|
+
readSecretField: async (item, field) => {
|
|
160
|
+
const rawSecret = await (0, credential_access_1.getCredentialStore)(agentName).getRawSecret(item, "password");
|
|
161
|
+
return vaultItemSecretField(rawSecret, item, field);
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
136
165
|
function renderUndecryptableThread(message, keyId) {
|
|
137
166
|
return [
|
|
138
167
|
`Mail message ${message.id} could not be decrypted because this agent's vault is missing private mail key ${keyId}.`,
|
|
@@ -536,14 +565,16 @@ exports.mailToolDefinitions = [
|
|
|
536
565
|
if (!resolved.ok)
|
|
537
566
|
return resolved.error;
|
|
538
567
|
try {
|
|
568
|
+
const transport = (0, outbound_1.resolveOutboundTransport)(resolved.config);
|
|
539
569
|
const sent = await (0, outbound_1.confirmMailDraftSend)({
|
|
540
570
|
store: resolved.store,
|
|
541
571
|
agentId: resolved.agentName,
|
|
542
572
|
draftId,
|
|
543
|
-
transport
|
|
573
|
+
transport,
|
|
544
574
|
confirmation: args.confirmation ?? "",
|
|
545
575
|
autonomous: args.autonomous === "true",
|
|
546
576
|
autonomyPolicy: resolved.config.autonomousSendPolicy,
|
|
577
|
+
providerClient: await outboundProviderClientForTransport(resolved.agentName, transport),
|
|
547
578
|
actor: actorFromContext(ctx, resolved.agentName),
|
|
548
579
|
reason: args.reason ?? "confirmed outbound send",
|
|
549
580
|
});
|