@ouro.bot/cli 0.1.0-alpha.465 → 0.1.0-alpha.467
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/README.md +4 -0
- package/changelog.json +16 -0
- package/dist/heart/daemon/cli-exec.js +422 -76
- package/dist/heart/daemon/cli-help.js +19 -4
- package/dist/heart/daemon/cli-parse.js +180 -4
- package/dist/heart/daemon/dns-workflow.js +365 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/outlook/readers/mail.js +34 -1
- package/dist/mailroom/attention.js +13 -0
- package/dist/mailroom/core.js +27 -0
- package/dist/mailroom/outbound.js +4 -0
- package/dist/nerves/coverage/file-completeness.js +1 -1
- package/dist/repertoire/tools-credential.js +37 -17
- package/dist/repertoire/tools-mail.js +22 -1
- package/dist/senses/mail.js +33 -25
- package/package.json +1 -1
- package/dist/heart/daemon/porkbun-ops.js +0 -25
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PORKBUN_OPS_COMPATIBILITY_ALIAS = exports.PORKBUN_OPS_CREDENTIAL_PREFIX = void 0;
|
|
4
|
+
exports.isVaultItemTemplate = isVaultItemTemplate;
|
|
5
|
+
exports.normalizeVaultItemName = normalizeVaultItemName;
|
|
6
|
+
exports.normalizeVaultItemFieldName = normalizeVaultItemFieldName;
|
|
7
|
+
exports.vaultItemTemplateSecretFields = vaultItemTemplateSecretFields;
|
|
8
|
+
exports.normalizePorkbunOpsAccount = normalizePorkbunOpsAccount;
|
|
9
|
+
exports.porkbunOpsCredentialItemName = porkbunOpsCredentialItemName;
|
|
10
|
+
exports.porkbunOpsAccountFromItemName = porkbunOpsAccountFromItemName;
|
|
11
|
+
exports.requireVaultItemSecret = requireVaultItemSecret;
|
|
12
|
+
exports.PORKBUN_OPS_CREDENTIAL_PREFIX = "ops/registrars/porkbun/accounts";
|
|
13
|
+
exports.PORKBUN_OPS_COMPATIBILITY_ALIAS = "vault ops porkbun";
|
|
14
|
+
const VAULT_ITEM_NAME_FORBIDDEN = /[\r\n\t]/;
|
|
15
|
+
const VAULT_ITEM_FIELD_FORBIDDEN = /[\r\n\t=]/;
|
|
16
|
+
const PORKBUN_OPS_ACCOUNT_FORBIDDEN = /[\/\r\n\t]/;
|
|
17
|
+
function isVaultItemTemplate(value) {
|
|
18
|
+
return value === "porkbun-api";
|
|
19
|
+
}
|
|
20
|
+
function normalizeVaultItemName(item) {
|
|
21
|
+
const normalized = item?.trim() ?? "";
|
|
22
|
+
if (!normalized || VAULT_ITEM_NAME_FORBIDDEN.test(normalized) || normalized.startsWith("/") || normalized.endsWith("/")) {
|
|
23
|
+
throw new Error("Vault item name/path must be non-empty, relative, and free of control characters.");
|
|
24
|
+
}
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
27
|
+
function normalizeVaultItemFieldName(field) {
|
|
28
|
+
const normalized = field?.trim() ?? "";
|
|
29
|
+
if (!normalized || VAULT_ITEM_FIELD_FORBIDDEN.test(normalized)) {
|
|
30
|
+
throw new Error("Vault item field names must be non-empty and free of control characters or '='.");
|
|
31
|
+
}
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
function vaultItemTemplateSecretFields(_template) {
|
|
35
|
+
return ["apiKey", "secretApiKey"];
|
|
36
|
+
}
|
|
37
|
+
function normalizePorkbunOpsAccount(account) {
|
|
38
|
+
const normalized = account?.trim() ?? "";
|
|
39
|
+
if (!normalized || PORKBUN_OPS_ACCOUNT_FORBIDDEN.test(normalized)) {
|
|
40
|
+
throw new Error("Porkbun account must be a non-empty account label without slashes or control characters.");
|
|
41
|
+
}
|
|
42
|
+
return normalized;
|
|
43
|
+
}
|
|
44
|
+
function porkbunOpsCredentialItemName(account) {
|
|
45
|
+
return `${exports.PORKBUN_OPS_CREDENTIAL_PREFIX}/${normalizePorkbunOpsAccount(account)}`;
|
|
46
|
+
}
|
|
47
|
+
function porkbunOpsAccountFromItemName(itemName) {
|
|
48
|
+
const prefix = `${exports.PORKBUN_OPS_CREDENTIAL_PREFIX}/`;
|
|
49
|
+
return itemName.startsWith(prefix) ? itemName.slice(prefix.length) : undefined;
|
|
50
|
+
}
|
|
51
|
+
function requireVaultItemSecret(value, label) {
|
|
52
|
+
const trimmed = value.trim();
|
|
53
|
+
if (!trimmed)
|
|
54
|
+
throw new Error(`${label} cannot be blank`);
|
|
55
|
+
return trimmed;
|
|
56
|
+
}
|
|
@@ -5,6 +5,7 @@ exports.readMailMessageView = readMailMessageView;
|
|
|
5
5
|
const runtime_1 = require("../../../nerves/runtime");
|
|
6
6
|
const file_store_1 = require("../../../mailroom/file-store");
|
|
7
7
|
const reader_1 = require("../../../mailroom/reader");
|
|
8
|
+
const core_1 = require("../../../mailroom/core");
|
|
8
9
|
const OUTLOOK_MAIL_LIST_LIMIT = 50;
|
|
9
10
|
const OUTLOOK_MAIL_COUNT_LIMIT = 500;
|
|
10
11
|
const OUTLOOK_MAIL_BODY_LIMIT = 12_000;
|
|
@@ -92,13 +93,27 @@ function buildFolders(messages, outbound) {
|
|
|
92
93
|
{ id: "native", label: "Native", count: messages.filter((message) => message.compartmentKind === "native").length },
|
|
93
94
|
];
|
|
94
95
|
const sourceCounts = new Map();
|
|
96
|
+
const sourceOwnerCounts = new Map();
|
|
95
97
|
for (const message of messages) {
|
|
96
98
|
if (!message.source)
|
|
97
99
|
continue;
|
|
98
100
|
sourceCounts.set(message.source, (sourceCounts.get(message.source) ?? 0) + 1);
|
|
101
|
+
const owner = message.ownerEmail ?? "";
|
|
102
|
+
const ownerCounts = sourceOwnerCounts.get(message.source) ?? new Map();
|
|
103
|
+
ownerCounts.set(owner, (ownerCounts.get(owner) ?? 0) + 1);
|
|
104
|
+
sourceOwnerCounts.set(message.source, ownerCounts);
|
|
99
105
|
}
|
|
100
106
|
for (const [source, count] of [...sourceCounts.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
101
|
-
|
|
107
|
+
const ownerCounts = sourceOwnerCounts.get(source);
|
|
108
|
+
if (!ownerCounts || ownerCounts.size <= 1) {
|
|
109
|
+
folders.push({ id: `source:${source}`, label: source.toUpperCase(), count });
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
for (const [owner, ownerCount] of [...ownerCounts.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
113
|
+
const ownerLabel = owner || "unknown owner";
|
|
114
|
+
const ownerId = owner || "unknown-owner";
|
|
115
|
+
folders.push({ id: `source:${source}:${ownerId}`, label: `${source.toUpperCase()} / ${ownerLabel}`, count: ownerCount });
|
|
116
|
+
}
|
|
102
117
|
}
|
|
103
118
|
return folders;
|
|
104
119
|
}
|
|
@@ -123,6 +138,10 @@ function outboundRecord(record) {
|
|
|
123
138
|
return {
|
|
124
139
|
id: record.id,
|
|
125
140
|
status: record.status,
|
|
141
|
+
mailboxRole: record.mailboxRole ?? "agent-native-mailbox",
|
|
142
|
+
sendAuthority: record.sendAuthority ?? "agent-native",
|
|
143
|
+
ownerEmail: record.ownerEmail ?? null,
|
|
144
|
+
source: record.source ?? null,
|
|
126
145
|
from: record.from,
|
|
127
146
|
to: record.to,
|
|
128
147
|
cc: record.cc,
|
|
@@ -152,9 +171,22 @@ function accessEntries(entries) {
|
|
|
152
171
|
threadId: entry.threadId ?? null,
|
|
153
172
|
tool: entry.tool,
|
|
154
173
|
reason: entry.reason,
|
|
174
|
+
mailboxRole: entry.mailboxRole ?? null,
|
|
175
|
+
compartmentKind: entry.compartmentKind ?? null,
|
|
176
|
+
ownerEmail: entry.ownerEmail ?? null,
|
|
177
|
+
source: entry.source ?? null,
|
|
155
178
|
accessedAt: entry.accessedAt,
|
|
156
179
|
}));
|
|
157
180
|
}
|
|
181
|
+
function accessProvenance(message) {
|
|
182
|
+
const provenance = (0, core_1.describeMailProvenance)(message);
|
|
183
|
+
return {
|
|
184
|
+
mailboxRole: provenance.mailboxRole,
|
|
185
|
+
compartmentKind: message.compartmentKind,
|
|
186
|
+
ownerEmail: provenance.ownerEmail,
|
|
187
|
+
source: provenance.source,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
158
190
|
function emitMailRead(agentName, mode, status) {
|
|
159
191
|
(0, runtime_1.emitNervesEvent)({
|
|
160
192
|
component: "heart",
|
|
@@ -238,6 +270,7 @@ async function readMailMessageView(agentName, messageId) {
|
|
|
238
270
|
messageId,
|
|
239
271
|
tool: "outlook_mail_message",
|
|
240
272
|
reason: "outlook read-only message body",
|
|
273
|
+
...accessProvenance(decrypted),
|
|
241
274
|
});
|
|
242
275
|
const body = decrypted.private.text.length > OUTLOOK_MAIL_BODY_LIMIT
|
|
243
276
|
? decrypted.private.text.slice(0, OUTLOOK_MAIL_BODY_LIMIT)
|
|
@@ -74,6 +74,14 @@ function displaySender(candidate) {
|
|
|
74
74
|
}
|
|
75
75
|
return candidate.senderEmail;
|
|
76
76
|
}
|
|
77
|
+
function candidateCompartmentKind(candidate) {
|
|
78
|
+
return candidate.ownerEmail || candidate.source ? "delegated" : "native";
|
|
79
|
+
}
|
|
80
|
+
function candidateMailboxRole(candidate) {
|
|
81
|
+
return candidateCompartmentKind(candidate) === "delegated"
|
|
82
|
+
? "delegated-human-mailbox"
|
|
83
|
+
: "agent-native-mailbox";
|
|
84
|
+
}
|
|
77
85
|
function renderAttentionContent(candidate) {
|
|
78
86
|
return [
|
|
79
87
|
"[Mail Screener]",
|
|
@@ -92,6 +100,7 @@ function renderAttentionContent(candidate) {
|
|
|
92
100
|
].filter(Boolean).join("\n");
|
|
93
101
|
}
|
|
94
102
|
function queuedSummary(candidate, queuedAt) {
|
|
103
|
+
const compartmentKind = candidateCompartmentKind(candidate);
|
|
95
104
|
return {
|
|
96
105
|
candidateId: candidate.id,
|
|
97
106
|
messageId: candidate.messageId,
|
|
@@ -99,6 +108,10 @@ function queuedSummary(candidate, queuedAt) {
|
|
|
99
108
|
senderDisplay: candidate.senderDisplay,
|
|
100
109
|
recipient: candidate.recipient,
|
|
101
110
|
placement: candidate.placement,
|
|
111
|
+
mailboxRole: candidateMailboxRole(candidate),
|
|
112
|
+
compartmentKind,
|
|
113
|
+
ownerEmail: candidate.ownerEmail ?? null,
|
|
114
|
+
source: candidate.source ?? null,
|
|
102
115
|
queuedAt,
|
|
103
116
|
};
|
|
104
117
|
}
|
package/dist/mailroom/core.js
CHANGED
|
@@ -42,6 +42,7 @@ exports.decryptMailPayload = decryptMailPayload;
|
|
|
42
42
|
exports.encryptJsonForMailKey = encryptJsonForMailKey;
|
|
43
43
|
exports.decryptMailJson = decryptMailJson;
|
|
44
44
|
exports.resolveMailAddress = resolveMailAddress;
|
|
45
|
+
exports.describeMailProvenance = describeMailProvenance;
|
|
45
46
|
exports.buildStoredMailMessage = buildStoredMailMessage;
|
|
46
47
|
exports.decryptStoredMailMessage = decryptStoredMailMessage;
|
|
47
48
|
exports.provisionMailboxRegistry = provisionMailboxRegistry;
|
|
@@ -237,6 +238,32 @@ function resolveMailAddress(registry, address) {
|
|
|
237
238
|
defaultPlacement: grant.defaultPlacement,
|
|
238
239
|
};
|
|
239
240
|
}
|
|
241
|
+
function describeMailProvenance(message) {
|
|
242
|
+
if (message.compartmentKind === "delegated") {
|
|
243
|
+
const ownerEmail = message.ownerEmail ?? null;
|
|
244
|
+
const source = message.source ?? null;
|
|
245
|
+
const ownerLabel = ownerEmail ?? "unknown owner";
|
|
246
|
+
const sourceLabel = source ?? "unknown source";
|
|
247
|
+
return {
|
|
248
|
+
mailboxRole: "delegated-human-mailbox",
|
|
249
|
+
mailboxLabel: `${ownerLabel} / ${sourceLabel} delegated to ${message.agentId}`,
|
|
250
|
+
agentId: message.agentId,
|
|
251
|
+
ownerEmail,
|
|
252
|
+
source,
|
|
253
|
+
recipient: message.recipient,
|
|
254
|
+
sendAsHumanAllowed: false,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
mailboxRole: "agent-native-mailbox",
|
|
259
|
+
mailboxLabel: `${message.recipient} (native agent mail)`,
|
|
260
|
+
agentId: message.agentId,
|
|
261
|
+
ownerEmail: null,
|
|
262
|
+
source: null,
|
|
263
|
+
recipient: message.recipient,
|
|
264
|
+
sendAsHumanAllowed: false,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
240
267
|
function addressList(values) {
|
|
241
268
|
/* v8 ignore next -- parsedAddressList filters undefined top-level values; this guards malformed address-group entries. @preserve */
|
|
242
269
|
return (values ?? [])
|
|
@@ -96,6 +96,10 @@ async function createMailDraft(input) {
|
|
|
96
96
|
id: draftId(),
|
|
97
97
|
agentId: input.agentId,
|
|
98
98
|
status: "draft",
|
|
99
|
+
mailboxRole: "agent-native-mailbox",
|
|
100
|
+
sendAuthority: "agent-native",
|
|
101
|
+
ownerEmail: null,
|
|
102
|
+
source: null,
|
|
99
103
|
from: (0, core_1.normalizeMailAddress)(input.from),
|
|
100
104
|
to,
|
|
101
105
|
cc: normalizeList(input.cc ?? []),
|
|
@@ -63,7 +63,7 @@ const DISPATCH_EXEMPT_PATTERNS = [
|
|
|
63
63
|
"daemon/cli-parse",
|
|
64
64
|
"daemon/cli-render",
|
|
65
65
|
"daemon/cli-help",
|
|
66
|
-
"daemon/
|
|
66
|
+
"daemon/vault-items",
|
|
67
67
|
// Shared utility modules: pure helpers consumed by modules that own observability.
|
|
68
68
|
"arc/json-store",
|
|
69
69
|
"repertoire/api-client",
|
|
@@ -72,6 +72,11 @@ function optionalTrimmedText(value, fieldName) {
|
|
|
72
72
|
const trimmed = value.trim();
|
|
73
73
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
74
74
|
}
|
|
75
|
+
function resolveVaultItemArg(args, legacyFieldName = "domain") {
|
|
76
|
+
if (args.item !== undefined)
|
|
77
|
+
return requireTrimmedText(args.item, "item");
|
|
78
|
+
return requireTrimmedText(args[legacyFieldName], legacyFieldName);
|
|
79
|
+
}
|
|
75
80
|
function parsePasswordLength(value) {
|
|
76
81
|
if (value === undefined || value === null || value === "")
|
|
77
82
|
return DEFAULT_PASSWORD_LENGTH;
|
|
@@ -127,13 +132,17 @@ exports.credentialToolDefinitions = [
|
|
|
127
132
|
type: "function",
|
|
128
133
|
function: {
|
|
129
134
|
name: "credential_get",
|
|
130
|
-
description: "Get credential metadata for a
|
|
135
|
+
description: "Get credential metadata for a vault item name/path. Returns username, notes, and creation date. Never returns passwords — the credential gateway handles secret injection internally.",
|
|
131
136
|
parameters: {
|
|
132
137
|
type: "object",
|
|
133
138
|
properties: {
|
|
139
|
+
item: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "Vault item name/path to look up (e.g. 'airbnb.com' or 'ops/porkbun/account')",
|
|
142
|
+
},
|
|
134
143
|
domain: {
|
|
135
144
|
type: "string",
|
|
136
|
-
description: "
|
|
145
|
+
description: "compatibility alias for item when the vault item name is a service domain",
|
|
137
146
|
},
|
|
138
147
|
},
|
|
139
148
|
required: ["domain"],
|
|
@@ -141,17 +150,18 @@ exports.credentialToolDefinitions = [
|
|
|
141
150
|
},
|
|
142
151
|
},
|
|
143
152
|
handler: async (args) => {
|
|
153
|
+
const itemName = resolveVaultItemArg(args);
|
|
144
154
|
(0, runtime_1.emitNervesEvent)({
|
|
145
155
|
component: "repertoire",
|
|
146
156
|
event: "repertoire.credential_tool_call",
|
|
147
157
|
message: "credential_get invoked",
|
|
148
|
-
meta: { tool: "credential_get", domain:
|
|
158
|
+
meta: { tool: "credential_get", domain: itemName, item: itemName },
|
|
149
159
|
});
|
|
150
160
|
try {
|
|
151
161
|
const store = (0, credential_access_1.getCredentialStore)();
|
|
152
|
-
const meta = await store.get(
|
|
162
|
+
const meta = await store.get(itemName);
|
|
153
163
|
if (!meta) {
|
|
154
|
-
return `No credential found for "${
|
|
164
|
+
return `No credential found for "${itemName}".`;
|
|
155
165
|
}
|
|
156
166
|
return JSON.stringify(meta, null, 2);
|
|
157
167
|
}
|
|
@@ -220,13 +230,17 @@ exports.credentialToolDefinitions = [
|
|
|
220
230
|
type: "function",
|
|
221
231
|
function: {
|
|
222
232
|
name: "credential_store",
|
|
223
|
-
description: "Store credentials the agent acquired or just used successfully
|
|
233
|
+
description: "Store credentials in a vault item name/path after the agent acquired or just used them successfully. Prefer credential_generate_password for new passwords, then call this tool once the site accepts the exact password. Stored passwords are never returned later — only metadata is visible.",
|
|
224
234
|
parameters: {
|
|
225
235
|
type: "object",
|
|
226
236
|
properties: {
|
|
237
|
+
item: {
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "Vault item name/path to store under; domains are examples, not the schema",
|
|
240
|
+
},
|
|
227
241
|
domain: {
|
|
228
242
|
type: "string",
|
|
229
|
-
description: "
|
|
243
|
+
description: "compatibility alias for item when the vault item name is a service domain",
|
|
230
244
|
},
|
|
231
245
|
username: {
|
|
232
246
|
type: "string",
|
|
@@ -238,7 +252,7 @@ exports.credentialToolDefinitions = [
|
|
|
238
252
|
},
|
|
239
253
|
notes: {
|
|
240
254
|
type: "string",
|
|
241
|
-
description: "Optional notes about this credential",
|
|
255
|
+
description: "Optional human/agent orientation notes about this credential; not parsed by code",
|
|
242
256
|
},
|
|
243
257
|
},
|
|
244
258
|
required: ["domain", "username", "password"],
|
|
@@ -250,14 +264,15 @@ exports.credentialToolDefinitions = [
|
|
|
250
264
|
let username = "";
|
|
251
265
|
let password = "";
|
|
252
266
|
let notes;
|
|
267
|
+
const itemNameForEvent = typeof args.item === "string" && args.item.trim() ? args.item.trim() : args.domain;
|
|
253
268
|
(0, runtime_1.emitNervesEvent)({
|
|
254
269
|
component: "repertoire",
|
|
255
270
|
event: "repertoire.credential_tool_call",
|
|
256
271
|
message: "credential_store invoked",
|
|
257
|
-
meta: { tool: "credential_store", domain:
|
|
272
|
+
meta: { tool: "credential_store", domain: itemNameForEvent, item: itemNameForEvent },
|
|
258
273
|
});
|
|
259
274
|
try {
|
|
260
|
-
domain =
|
|
275
|
+
domain = resolveVaultItemArg(args);
|
|
261
276
|
username = requireTrimmedText(args.username, "username");
|
|
262
277
|
password = requireNonBlankSecret(args.password, "password");
|
|
263
278
|
notes = optionalTrimmedText(args.notes, "notes");
|
|
@@ -281,13 +296,13 @@ exports.credentialToolDefinitions = [
|
|
|
281
296
|
type: "function",
|
|
282
297
|
function: {
|
|
283
298
|
name: "credential_list",
|
|
284
|
-
description: "List stored
|
|
299
|
+
description: "List stored vault items. Returns metadata only (item/domain name, username, notes, creation date). Never returns passwords.",
|
|
285
300
|
parameters: {
|
|
286
301
|
type: "object",
|
|
287
302
|
properties: {
|
|
288
303
|
search: {
|
|
289
304
|
type: "string",
|
|
290
|
-
description: "Optional search filter to match against
|
|
305
|
+
description: "Optional search filter to match against vault item names/paths",
|
|
291
306
|
},
|
|
292
307
|
},
|
|
293
308
|
},
|
|
@@ -323,13 +338,17 @@ exports.credentialToolDefinitions = [
|
|
|
323
338
|
type: "function",
|
|
324
339
|
function: {
|
|
325
340
|
name: "credential_delete",
|
|
326
|
-
description: "Delete stored credentials for a
|
|
341
|
+
description: "Delete stored credentials for a vault item name/path.",
|
|
327
342
|
parameters: {
|
|
328
343
|
type: "object",
|
|
329
344
|
properties: {
|
|
345
|
+
item: {
|
|
346
|
+
type: "string",
|
|
347
|
+
description: "Vault item name/path whose credentials should be deleted",
|
|
348
|
+
},
|
|
330
349
|
domain: {
|
|
331
350
|
type: "string",
|
|
332
|
-
description: "
|
|
351
|
+
description: "compatibility alias for item when the vault item name is a service domain",
|
|
333
352
|
},
|
|
334
353
|
},
|
|
335
354
|
required: ["domain"],
|
|
@@ -344,12 +363,13 @@ exports.credentialToolDefinitions = [
|
|
|
344
363
|
meta: { tool: "credential_delete", domain: args.domain },
|
|
345
364
|
});
|
|
346
365
|
try {
|
|
366
|
+
const itemName = resolveVaultItemArg(args);
|
|
347
367
|
const store = (0, credential_access_1.getCredentialStore)();
|
|
348
|
-
const deleted = await store.delete(
|
|
368
|
+
const deleted = await store.delete(itemName);
|
|
349
369
|
if (deleted) {
|
|
350
|
-
return `Credentials for "${
|
|
370
|
+
return `Credentials for "${itemName}" deleted.`;
|
|
351
371
|
}
|
|
352
|
-
return `No credential found for "${
|
|
372
|
+
return `No credential found for "${itemName}".`;
|
|
353
373
|
}
|
|
354
374
|
catch (err) {
|
|
355
375
|
/* v8 ignore next -- defensive: store.delete wraps errors @preserve */
|
|
@@ -125,10 +125,29 @@ function renderAccessLog(entries) {
|
|
|
125
125
|
.reverse()
|
|
126
126
|
.map((entry) => {
|
|
127
127
|
const target = entry.messageId ? `message=${entry.messageId}` : entry.threadId ? `thread=${entry.threadId}` : "mailbox";
|
|
128
|
-
|
|
128
|
+
const provenance = renderAccessLogProvenance(entry);
|
|
129
|
+
return `- ${entry.accessedAt} ${entry.tool} ${target}${provenance} reason="${entry.reason}"`;
|
|
129
130
|
})
|
|
130
131
|
.join("\n");
|
|
131
132
|
}
|
|
133
|
+
function renderAccessLogProvenance(entry) {
|
|
134
|
+
if (entry.mailboxRole === "delegated-human-mailbox") {
|
|
135
|
+
return ` delegated human mailbox: ${entry.ownerEmail ?? "unknown owner"} / ${entry.source ?? "unknown source"}`;
|
|
136
|
+
}
|
|
137
|
+
if (entry.mailboxRole === "agent-native-mailbox") {
|
|
138
|
+
return " native agent mailbox";
|
|
139
|
+
}
|
|
140
|
+
return "";
|
|
141
|
+
}
|
|
142
|
+
function accessProvenance(message) {
|
|
143
|
+
const provenance = (0, core_1.describeMailProvenance)(message);
|
|
144
|
+
return {
|
|
145
|
+
mailboxRole: provenance.mailboxRole,
|
|
146
|
+
compartmentKind: message.compartmentKind,
|
|
147
|
+
ownerEmail: provenance.ownerEmail,
|
|
148
|
+
source: provenance.source,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
132
151
|
function renderSourceGrantStatus(config, agentId) {
|
|
133
152
|
if (!config.registryPath) {
|
|
134
153
|
return [
|
|
@@ -597,6 +616,7 @@ exports.mailToolDefinitions = [
|
|
|
597
616
|
messageId,
|
|
598
617
|
tool: "mail_thread",
|
|
599
618
|
reason: args.reason,
|
|
619
|
+
...accessProvenance(message),
|
|
600
620
|
});
|
|
601
621
|
const maxChars = numberArg(args.max_chars, 2000, 200, 6000);
|
|
602
622
|
const body = decrypted.private.text.length > maxChars
|
|
@@ -717,6 +737,7 @@ exports.mailToolDefinitions = [
|
|
|
717
737
|
messageId,
|
|
718
738
|
tool: "mail_decide",
|
|
719
739
|
reason,
|
|
740
|
+
...accessProvenance(message),
|
|
720
741
|
});
|
|
721
742
|
const senderPolicyLine = persistSenderPolicyForDecision({
|
|
722
743
|
registryPath: resolved.config.registryPath,
|
package/dist/senses/mail.js
CHANGED
|
@@ -113,22 +113,28 @@ async function startMailSenseApp(options) {
|
|
|
113
113
|
if (!resolved.ok) {
|
|
114
114
|
throw new Error(resolved.error);
|
|
115
115
|
}
|
|
116
|
-
|
|
116
|
+
const hostedReaderOnly = resolved.storeKind === "azure-blob" && !resolved.config.registryPath;
|
|
117
|
+
if (!resolved.config.registryPath && !hostedReaderOnly) {
|
|
117
118
|
throw new Error(`missing mailroom.registryPath for ${options.agentName}; agent-runnable repair: 'ouro connect mail --agent ${options.agentName}'`);
|
|
118
119
|
}
|
|
119
|
-
const registry = readRegistry(resolved.config.registryPath);
|
|
120
120
|
const host = resolved.config.host ?? "127.0.0.1";
|
|
121
|
-
const ingress =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
121
|
+
const ingress = hostedReaderOnly
|
|
122
|
+
? null
|
|
123
|
+
: (options.startIngress ?? smtp_ingress_1.startMailroomIngress)({
|
|
124
|
+
registry: readRegistry(resolved.config.registryPath),
|
|
125
|
+
store: resolved.store,
|
|
126
|
+
smtpPort: validPort(resolved.config.smtpPort),
|
|
127
|
+
httpPort: validPort(resolved.config.httpPort),
|
|
128
|
+
host,
|
|
129
|
+
});
|
|
130
|
+
if (ingress) {
|
|
131
|
+
await Promise.all([
|
|
132
|
+
waitForListening(ingress.smtp),
|
|
133
|
+
waitForListening(ingress.health),
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
const activeSmtpPort = () => ingress ? serverPort(ingress.smtp) : null;
|
|
137
|
+
const activeHttpPort = () => ingress ? serverPort(ingress.health) : null;
|
|
132
138
|
const runtimePath = runtimeStatePath(options.agentName);
|
|
133
139
|
const attentionPath = attentionStatePath(options.agentName);
|
|
134
140
|
let lastScanAt = null;
|
|
@@ -150,8 +156,8 @@ async function startMailSenseApp(options) {
|
|
|
150
156
|
agentName: options.agentName,
|
|
151
157
|
status: "running",
|
|
152
158
|
mailboxAddress: resolved.config.mailboxAddress,
|
|
153
|
-
smtpPort:
|
|
154
|
-
httpPort:
|
|
159
|
+
smtpPort: activeSmtpPort(),
|
|
160
|
+
httpPort: activeHttpPort(),
|
|
155
161
|
host,
|
|
156
162
|
storeKind: resolved.storeKind,
|
|
157
163
|
storeLabel: resolved.storeLabel,
|
|
@@ -182,30 +188,32 @@ async function startMailSenseApp(options) {
|
|
|
182
188
|
meta: {
|
|
183
189
|
agentName: options.agentName,
|
|
184
190
|
mailboxAddress: resolved.config.mailboxAddress,
|
|
185
|
-
smtpPort:
|
|
186
|
-
httpPort:
|
|
191
|
+
smtpPort: activeSmtpPort(),
|
|
192
|
+
httpPort: activeHttpPort(),
|
|
187
193
|
intervalMs,
|
|
188
194
|
},
|
|
189
195
|
});
|
|
190
196
|
return {
|
|
191
197
|
runtimeStatePath: runtimePath,
|
|
192
198
|
attentionStatePath: attentionPath,
|
|
193
|
-
smtpPort:
|
|
194
|
-
httpPort:
|
|
199
|
+
smtpPort: activeSmtpPort(),
|
|
200
|
+
httpPort: activeHttpPort(),
|
|
195
201
|
async stop() {
|
|
196
202
|
;
|
|
197
203
|
(options.clearIntervalFn ?? ((activeTimer) => clearInterval(activeTimer)))(timer);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
204
|
+
if (ingress) {
|
|
205
|
+
await Promise.all([
|
|
206
|
+
closeServer(ingress.smtp),
|
|
207
|
+
closeServer(ingress.health),
|
|
208
|
+
]);
|
|
209
|
+
}
|
|
202
210
|
writeRuntimeState(runtimePath, {
|
|
203
211
|
schemaVersion: 1,
|
|
204
212
|
agentName: options.agentName,
|
|
205
213
|
status: "stopped",
|
|
206
214
|
mailboxAddress: resolved.config.mailboxAddress,
|
|
207
|
-
smtpPort:
|
|
208
|
-
httpPort:
|
|
215
|
+
smtpPort: activeSmtpPort(),
|
|
216
|
+
httpPort: activeHttpPort(),
|
|
209
217
|
host,
|
|
210
218
|
storeKind: resolved.storeKind,
|
|
211
219
|
storeLabel: resolved.storeLabel,
|
package/package.json
CHANGED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PORKBUN_OPS_CREDENTIAL_PREFIX = exports.PORKBUN_OPS_CREDENTIAL_KIND = void 0;
|
|
4
|
-
exports.normalizePorkbunOpsAccount = normalizePorkbunOpsAccount;
|
|
5
|
-
exports.porkbunOpsCredentialItemName = porkbunOpsCredentialItemName;
|
|
6
|
-
exports.requirePorkbunOpsSecret = requirePorkbunOpsSecret;
|
|
7
|
-
exports.PORKBUN_OPS_CREDENTIAL_KIND = "ops-credential/porkbun";
|
|
8
|
-
exports.PORKBUN_OPS_CREDENTIAL_PREFIX = "ops/registrars/porkbun/accounts";
|
|
9
|
-
const PORKBUN_OPS_ACCOUNT_FORBIDDEN = /[\/\r\n\t]/;
|
|
10
|
-
function normalizePorkbunOpsAccount(account) {
|
|
11
|
-
const normalized = account?.trim() ?? "";
|
|
12
|
-
if (!normalized || PORKBUN_OPS_ACCOUNT_FORBIDDEN.test(normalized)) {
|
|
13
|
-
throw new Error("Porkbun account must be a non-empty account label without slashes or control characters.");
|
|
14
|
-
}
|
|
15
|
-
return normalized;
|
|
16
|
-
}
|
|
17
|
-
function porkbunOpsCredentialItemName(account) {
|
|
18
|
-
return `${exports.PORKBUN_OPS_CREDENTIAL_PREFIX}/${normalizePorkbunOpsAccount(account)}`;
|
|
19
|
-
}
|
|
20
|
-
function requirePorkbunOpsSecret(value, label) {
|
|
21
|
-
const trimmed = value.trim();
|
|
22
|
-
if (!trimmed)
|
|
23
|
-
throw new Error(`${label} cannot be blank`);
|
|
24
|
-
return trimmed;
|
|
25
|
-
}
|