humanish 0.20.4 → 0.20.5
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/dist/comms-faux-inbox.d.ts +37 -0
- package/dist/comms-faux-inbox.js +147 -0
- package/dist/comms-faux-inbox.js.map +1 -0
- package/dist/comms-resend-catch.d.ts +30 -0
- package/dist/comms-resend-catch.js +120 -0
- package/dist/comms-resend-catch.js.map +1 -0
- package/dist/comms-types.d.ts +63 -0
- package/dist/comms-types.js +10 -0
- package/dist/comms-types.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/docs/contracts/schemas.md +1 -1
- package/docs/goals/current.md +1 -1
- package/docs/ramp/README.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CommsAddress, CommsChannel, CommsChannelKind, CommsMessage, InboundRaw, OutboundMessage } from "./comms-types.js";
|
|
2
|
+
/** Extract actionable http(s) links from a message body (href="…" and bare URLs), de-duped, in order.
|
|
3
|
+
* The verification magic-link the persona would tap. Pure; body is runtime-only. */
|
|
4
|
+
export declare function extractLinks(body: string): string[];
|
|
5
|
+
/** Extract OTP-shaped tokens from a message body. Labeled codes ("your code is 481920",
|
|
6
|
+
* "verification code 8A3F2K") are high-precision and preferred; if none are labeled, fall back to an
|
|
7
|
+
* isolated 4–8 digit run (a bare OTP). Pure; tokens are runtime-only literal-scrub targets. */
|
|
8
|
+
export declare function extractOtpCodes(body: string): string[];
|
|
9
|
+
export interface FauxInboxOptions {
|
|
10
|
+
/** "email" (default) or "sms" — the address shape + surface differ; machinery is identical. */
|
|
11
|
+
channel?: CommsChannelKind;
|
|
12
|
+
/** Email domain for minted addresses. Default example.test (an RFC 6761 reserved, unroutable test
|
|
13
|
+
* domain — public-safe; override to a branded reserved domain once it's added to the email allowlist). */
|
|
14
|
+
domain?: string;
|
|
15
|
+
/** Injected clock (ms) for deterministic tests. Default Date.now. */
|
|
16
|
+
now?: () => number;
|
|
17
|
+
}
|
|
18
|
+
/** The in-process faux adapter. Implements the same CommsChannel port a real provider adapter would. */
|
|
19
|
+
export declare class FauxInbox implements CommsChannel {
|
|
20
|
+
readonly channel: CommsChannelKind;
|
|
21
|
+
readonly kind: "faux";
|
|
22
|
+
private readonly domain;
|
|
23
|
+
private readonly clock;
|
|
24
|
+
private readonly byActor;
|
|
25
|
+
private readonly byValue;
|
|
26
|
+
private readonly queues;
|
|
27
|
+
private counter;
|
|
28
|
+
constructor(options?: FauxInboxOptions);
|
|
29
|
+
provision(actorId: string): Promise<CommsAddress>;
|
|
30
|
+
private route;
|
|
31
|
+
send(message: OutboundMessage): Promise<CommsMessage>;
|
|
32
|
+
deliverRaw(inbound: InboundRaw): Promise<CommsMessage[]>;
|
|
33
|
+
poll(address: CommsAddress, since?: number): Promise<CommsMessage[]>;
|
|
34
|
+
teardown(): Promise<void>;
|
|
35
|
+
/** Inspection helper (tests / a surface): every inbox currently provisioned. */
|
|
36
|
+
addresses(): CommsAddress[];
|
|
37
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// The FAUX in-process email/SMS bus (#297 Stage 2). Deterministic, $0, offline, public-safe: a
|
|
2
|
+
// message an app-under-test "sends" (via an ingress like the Resend catch) is routed to the
|
|
3
|
+
// addressed actor inbox and read back through the same CommsChannel port a real provider adapter
|
|
4
|
+
// would implement. Nothing leaves the process. See comms-types.ts for the port + public-safety notes.
|
|
5
|
+
import { digestText } from "./redaction.js";
|
|
6
|
+
/** Extract actionable http(s) links from a message body (href="…" and bare URLs), de-duped, in order.
|
|
7
|
+
* The verification magic-link the persona would tap. Pure; body is runtime-only. */
|
|
8
|
+
export function extractLinks(body) {
|
|
9
|
+
if (typeof body !== "string" || body.length === 0)
|
|
10
|
+
return [];
|
|
11
|
+
const out = [];
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
const push = (raw) => {
|
|
14
|
+
const url = raw.trim().replace(/[).,;'"]+$/, "");
|
|
15
|
+
if (/^https?:\/\//i.test(url) && !seen.has(url)) {
|
|
16
|
+
seen.add(url);
|
|
17
|
+
out.push(url);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
let m;
|
|
21
|
+
const href = /href\s*=\s*["']([^"']+)["']/gi;
|
|
22
|
+
while ((m = href.exec(body)) !== null)
|
|
23
|
+
push(m[1] ?? "");
|
|
24
|
+
const bare = /https?:\/\/[^\s"'<>)\]]+/gi;
|
|
25
|
+
while ((m = bare.exec(body)) !== null)
|
|
26
|
+
push(m[0]);
|
|
27
|
+
return out.slice(0, 50);
|
|
28
|
+
}
|
|
29
|
+
function stripTags(html) {
|
|
30
|
+
return html
|
|
31
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
32
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
33
|
+
.replace(/<[^>]+>/g, " ")
|
|
34
|
+
.replace(/ /gi, " ")
|
|
35
|
+
.replace(/\s+/g, " ")
|
|
36
|
+
.trim();
|
|
37
|
+
}
|
|
38
|
+
/** Extract OTP-shaped tokens from a message body. Labeled codes ("your code is 481920",
|
|
39
|
+
* "verification code 8A3F2K") are high-precision and preferred; if none are labeled, fall back to an
|
|
40
|
+
* isolated 4–8 digit run (a bare OTP). Pure; tokens are runtime-only literal-scrub targets. */
|
|
41
|
+
export function extractOtpCodes(body) {
|
|
42
|
+
if (typeof body !== "string" || body.length === 0)
|
|
43
|
+
return [];
|
|
44
|
+
const text = stripTags(body);
|
|
45
|
+
const labeled = [];
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
const push = (list, code) => {
|
|
48
|
+
const c = code.toUpperCase();
|
|
49
|
+
if (c && !seen.has(c)) {
|
|
50
|
+
seen.add(c);
|
|
51
|
+
list.push(c);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const labeledRe = /(?:one[-\s]?time\s+(?:pass)?code|verification\s+code|security\s+code|access\s+code|login\s+code|confirmation\s+code|passcode|\bOTP\b|\bPIN\b|\bcode\b)\D{0,15}\b([0-9]{4,8}|[A-Z0-9]{6,8})\b/gi;
|
|
55
|
+
let m;
|
|
56
|
+
while ((m = labeledRe.exec(text)) !== null)
|
|
57
|
+
push(labeled, m[1] ?? "");
|
|
58
|
+
if (labeled.length > 0)
|
|
59
|
+
return labeled.slice(0, 10);
|
|
60
|
+
// Fallback: an isolated 4–8 digit run (a bare, unlabeled OTP), not embedded in a longer token.
|
|
61
|
+
const bare = [];
|
|
62
|
+
const bareRe = /(?<![0-9A-Za-z])([0-9]{4,8})(?![0-9A-Za-z])/g;
|
|
63
|
+
while ((m = bareRe.exec(text)) !== null)
|
|
64
|
+
push(bare, m[1] ?? "");
|
|
65
|
+
return bare.slice(0, 10);
|
|
66
|
+
}
|
|
67
|
+
function sanitizeLocalPart(actorId) {
|
|
68
|
+
return actorId.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "actor";
|
|
69
|
+
}
|
|
70
|
+
function smsAddressFor(actorId) {
|
|
71
|
+
const digits = digestText(actorId, 16).replace(/[a-f]/g, (c) => String(c.charCodeAt(0) % 10)).slice(0, 7);
|
|
72
|
+
return `+1555${digits}`;
|
|
73
|
+
}
|
|
74
|
+
/** The in-process faux adapter. Implements the same CommsChannel port a real provider adapter would. */
|
|
75
|
+
export class FauxInbox {
|
|
76
|
+
channel;
|
|
77
|
+
kind = "faux";
|
|
78
|
+
domain;
|
|
79
|
+
clock;
|
|
80
|
+
byActor = new Map();
|
|
81
|
+
byValue = new Map();
|
|
82
|
+
queues = new Map();
|
|
83
|
+
counter = 0;
|
|
84
|
+
constructor(options = {}) {
|
|
85
|
+
this.channel = options.channel ?? "email";
|
|
86
|
+
this.domain = options.domain ?? "example.test";
|
|
87
|
+
this.clock = options.now ?? (() => Date.now());
|
|
88
|
+
}
|
|
89
|
+
async provision(actorId) {
|
|
90
|
+
const existing = this.byActor.get(actorId);
|
|
91
|
+
if (existing)
|
|
92
|
+
return existing;
|
|
93
|
+
const value = this.channel === "sms" ? smsAddressFor(actorId) : `${sanitizeLocalPart(actorId)}@${this.domain}`;
|
|
94
|
+
const address = { channel: this.channel, actorId, value, digest: digestText(value, 16) };
|
|
95
|
+
this.byActor.set(actorId, address);
|
|
96
|
+
this.byValue.set(value.toLowerCase(), address);
|
|
97
|
+
this.queues.set(value.toLowerCase(), []);
|
|
98
|
+
return address;
|
|
99
|
+
}
|
|
100
|
+
route(from, to, subject, body) {
|
|
101
|
+
const at = this.clock();
|
|
102
|
+
const message = {
|
|
103
|
+
id: `comms-${(this.counter += 1).toString().padStart(4, "0")}`,
|
|
104
|
+
channel: this.channel,
|
|
105
|
+
from,
|
|
106
|
+
to,
|
|
107
|
+
...(subject === undefined ? {} : { subject }),
|
|
108
|
+
body,
|
|
109
|
+
links: extractLinks(body),
|
|
110
|
+
codes: extractOtpCodes(body),
|
|
111
|
+
sentAt: at,
|
|
112
|
+
deliveredAt: at
|
|
113
|
+
};
|
|
114
|
+
for (const addr of to) {
|
|
115
|
+
const queue = this.queues.get(addr.value.toLowerCase());
|
|
116
|
+
if (queue)
|
|
117
|
+
queue.push(message);
|
|
118
|
+
}
|
|
119
|
+
return message;
|
|
120
|
+
}
|
|
121
|
+
async send(message) {
|
|
122
|
+
return this.route(message.from.value, message.to, message.subject, message.body);
|
|
123
|
+
}
|
|
124
|
+
async deliverRaw(inbound) {
|
|
125
|
+
const to = (inbound.to ?? [])
|
|
126
|
+
.map((raw) => this.byValue.get(String(raw).trim().toLowerCase()))
|
|
127
|
+
.filter((address) => address !== undefined);
|
|
128
|
+
if (to.length === 0)
|
|
129
|
+
return []; // no provisioned inbox matched → nothing to deliver to
|
|
130
|
+
return [this.route(inbound.from, to, inbound.subject, inbound.body)];
|
|
131
|
+
}
|
|
132
|
+
async poll(address, since = 0) {
|
|
133
|
+
const queue = this.queues.get(address.value.toLowerCase()) ?? [];
|
|
134
|
+
return queue.filter((message) => message.deliveredAt > since);
|
|
135
|
+
}
|
|
136
|
+
async teardown() {
|
|
137
|
+
this.byActor.clear();
|
|
138
|
+
this.byValue.clear();
|
|
139
|
+
this.queues.clear();
|
|
140
|
+
this.counter = 0;
|
|
141
|
+
}
|
|
142
|
+
/** Inspection helper (tests / a surface): every inbox currently provisioned. */
|
|
143
|
+
addresses() {
|
|
144
|
+
return [...this.byActor.values()];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=comms-faux-inbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comms-faux-inbox.js","sourceRoot":"","sources":["../src/comms-faux-inbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CommsChannel } from "./comms-types.js";
|
|
2
|
+
/** A Resend send payload we accepted, normalized (runtime-only; for inspection/tests). */
|
|
3
|
+
export interface ResendEmailPayload {
|
|
4
|
+
from: string;
|
|
5
|
+
to: string[];
|
|
6
|
+
subject?: string;
|
|
7
|
+
html?: string;
|
|
8
|
+
text?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ResendCatchServer {
|
|
11
|
+
/** Point the app here: `RESEND_BASE_URL=<url>` (loopback only). */
|
|
12
|
+
readonly url: string;
|
|
13
|
+
readonly port: number;
|
|
14
|
+
/** Every payload accepted this run (runtime-only). */
|
|
15
|
+
readonly received: ResendEmailPayload[];
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export interface ResendCatchOptions {
|
|
19
|
+
/** Bind host. Default 127.0.0.1 (loopback ONLY — never bind a public interface). */
|
|
20
|
+
host?: string;
|
|
21
|
+
/** Port. Default 0 (ephemeral — read the chosen port off the returned server). */
|
|
22
|
+
port?: number;
|
|
23
|
+
/** Deterministic id for the Resend-shaped response (tests). Default a zero-padded counter. */
|
|
24
|
+
idFor?: (n: number) => string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Start the Resend-shaped catch server, routing accepted sends into `channel`. Returns the loopback
|
|
28
|
+
* URL to hand the app as `RESEND_BASE_URL`. `close()` in a finally (mirror by-id teardown).
|
|
29
|
+
*/
|
|
30
|
+
export declare function startResendCatchServer(channel: CommsChannel, options?: ResendCatchOptions): Promise<ResendCatchServer>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// A LOOPBACK catch server that speaks the Resend `POST /emails` wire shape, so an app hardwired to
|
|
2
|
+
// Resend is redirected into the faux bus with ONE env var and no code change: point the app at this
|
|
3
|
+
// server via `RESEND_BASE_URL=<url>` (the official `resend` Node SDK reads that env var / a `baseUrl`
|
|
4
|
+
// option). The app's real verification email POSTs here instead of api.resend.com; we route it into
|
|
5
|
+
// the CommsChannel and the persona reads it. Nothing is delivered onward. (#297 — the API-first
|
|
6
|
+
// analog of pointing SMTP at a local catcher, since Resend is HTTPS-first and its own SMTP delivers
|
|
7
|
+
// for real.) 127.0.0.1 only; bodies are never logged.
|
|
8
|
+
import { createServer } from "node:http";
|
|
9
|
+
/** "Name <email@host>" → "email@host"; a bare address passes through. */
|
|
10
|
+
function bareEmail(value) {
|
|
11
|
+
const angle = value.match(/<([^>]+)>/);
|
|
12
|
+
return (angle ? angle[1] ?? value : value).trim();
|
|
13
|
+
}
|
|
14
|
+
function asStringArray(value) {
|
|
15
|
+
if (Array.isArray(value))
|
|
16
|
+
return value.map((entry) => String(entry));
|
|
17
|
+
if (typeof value === "string")
|
|
18
|
+
return [value];
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
function normalizeResendPayload(payload) {
|
|
22
|
+
return {
|
|
23
|
+
from: typeof payload.from === "string" ? payload.from : "",
|
|
24
|
+
to: asStringArray(payload.to).map(bareEmail),
|
|
25
|
+
...(typeof payload.subject === "string" ? { subject: payload.subject } : {}),
|
|
26
|
+
...(typeof payload.html === "string" ? { html: payload.html } : {}),
|
|
27
|
+
...(typeof payload.text === "string" ? { text: payload.text } : {})
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function readBody(req, limit) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
let size = 0;
|
|
33
|
+
const chunks = [];
|
|
34
|
+
req.on("data", (chunk) => {
|
|
35
|
+
size += chunk.length;
|
|
36
|
+
if (size > limit) {
|
|
37
|
+
req.destroy();
|
|
38
|
+
reject(new Error("request body too large"));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
chunks.push(chunk);
|
|
42
|
+
});
|
|
43
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
44
|
+
req.on("error", reject);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const MAX_BODY_BYTES = 5 * 1024 * 1024;
|
|
48
|
+
/**
|
|
49
|
+
* Start the Resend-shaped catch server, routing accepted sends into `channel`. Returns the loopback
|
|
50
|
+
* URL to hand the app as `RESEND_BASE_URL`. `close()` in a finally (mirror by-id teardown).
|
|
51
|
+
*/
|
|
52
|
+
export async function startResendCatchServer(channel, options = {}) {
|
|
53
|
+
const host = options.host ?? "127.0.0.1";
|
|
54
|
+
const received = [];
|
|
55
|
+
let idCounter = 0;
|
|
56
|
+
const idFor = options.idFor ?? ((n) => `humanish-catch-${n.toString().padStart(6, "0")}`);
|
|
57
|
+
const respondJson = (res, status, value) => {
|
|
58
|
+
res.statusCode = status;
|
|
59
|
+
res.setHeader("content-type", "application/json");
|
|
60
|
+
res.end(JSON.stringify(value));
|
|
61
|
+
};
|
|
62
|
+
const handle = async (req, res) => {
|
|
63
|
+
const path = (req.url ?? "/").split("?")[0];
|
|
64
|
+
if (req.method === "GET" && (path === "/" || path === "/health")) {
|
|
65
|
+
respondJson(res, 200, { ok: true, service: "humanish-resend-catch", channel: channel.channel });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Resend's send endpoints: POST /emails (single) and /emails/batch. Batch is an array of sends.
|
|
69
|
+
if (req.method === "POST" && (path === "/emails" || path === "/emails/batch")) {
|
|
70
|
+
const raw = await readBody(req, MAX_BODY_BYTES);
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(raw.length > 0 ? raw : "{}");
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
respondJson(res, 422, { error: "invalid JSON body" });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
80
|
+
const ids = [];
|
|
81
|
+
for (const item of items) {
|
|
82
|
+
if (typeof item !== "object" || item === null)
|
|
83
|
+
continue;
|
|
84
|
+
const payload = normalizeResendPayload(item);
|
|
85
|
+
received.push(payload);
|
|
86
|
+
const inbound = {
|
|
87
|
+
from: payload.from,
|
|
88
|
+
to: payload.to,
|
|
89
|
+
...(payload.subject === undefined ? {} : { subject: payload.subject }),
|
|
90
|
+
body: payload.html ?? payload.text ?? ""
|
|
91
|
+
};
|
|
92
|
+
await channel.deliverRaw(inbound);
|
|
93
|
+
idCounter += 1;
|
|
94
|
+
ids.push(idFor(idCounter));
|
|
95
|
+
}
|
|
96
|
+
// Resend-shaped response: a single send returns { id }; a batch returns { data: [{ id }, …] }.
|
|
97
|
+
if (path === "/emails/batch")
|
|
98
|
+
respondJson(res, 200, { data: ids.map((id) => ({ id })) });
|
|
99
|
+
else
|
|
100
|
+
respondJson(res, 200, { id: ids[0] ?? idFor(idCounter) });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
respondJson(res, 404, { error: "not found" });
|
|
104
|
+
};
|
|
105
|
+
const server = createServer((req, res) => {
|
|
106
|
+
void handle(req, res).catch(() => {
|
|
107
|
+
if (!res.headersSent)
|
|
108
|
+
respondJson(res, 500, { error: "catch server error" });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
await new Promise((resolve) => server.listen(options.port ?? 0, host, () => resolve()));
|
|
112
|
+
const port = server.address().port;
|
|
113
|
+
return {
|
|
114
|
+
url: `http://${host}:${port}`,
|
|
115
|
+
port,
|
|
116
|
+
received,
|
|
117
|
+
close: () => new Promise((resolve) => server.close(() => resolve()))
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=comms-resend-catch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comms-resend-catch.js","sourceRoot":"","sources":["../src/comms-resend-catch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type CommsChannelKind = "email" | "sms";
|
|
2
|
+
/** One actor's inbox identity. `value` is runtime-only; `digest` is the only form meant to persist. */
|
|
3
|
+
export interface CommsAddress {
|
|
4
|
+
channel: CommsChannelKind;
|
|
5
|
+
/** Which lane owns this inbox. */
|
|
6
|
+
actorId: string;
|
|
7
|
+
/** Runtime-only raw address, e.g. patient-07@example.test | +15550137. */
|
|
8
|
+
value: string;
|
|
9
|
+
/** sha256-short(value) — the only form persisted (redaction.digestText). */
|
|
10
|
+
digest: string;
|
|
11
|
+
}
|
|
12
|
+
/** A message that arrived to (or was sent from) an inbox. Body/links/codes are runtime-only. */
|
|
13
|
+
export interface CommsMessage {
|
|
14
|
+
id: string;
|
|
15
|
+
channel: CommsChannelKind;
|
|
16
|
+
/** Raw sender — an app-under-test address, or another actor's address. Runtime-only. */
|
|
17
|
+
from: string;
|
|
18
|
+
/** Resolved recipient inboxes this message was delivered to. */
|
|
19
|
+
to: CommsAddress[];
|
|
20
|
+
subject?: string;
|
|
21
|
+
/** Runtime-only for real; local-only for faux (never a share path without redaction — #108). */
|
|
22
|
+
body: string;
|
|
23
|
+
/** Actionable links extracted from the body (magic-link / invite / reset). Runtime-only. */
|
|
24
|
+
links: string[];
|
|
25
|
+
/** OTP-shaped tokens extracted from the body. Runtime-only; literal-scrub targets. */
|
|
26
|
+
codes: string[];
|
|
27
|
+
sentAt: number;
|
|
28
|
+
deliveredAt: number;
|
|
29
|
+
}
|
|
30
|
+
/** An actor sending OUT (a reply/compose); recipients are known CommsAddresses. */
|
|
31
|
+
export interface OutboundMessage {
|
|
32
|
+
from: CommsAddress;
|
|
33
|
+
to: CommsAddress[];
|
|
34
|
+
subject?: string;
|
|
35
|
+
body: string;
|
|
36
|
+
}
|
|
37
|
+
/** A raw inbound from an INGRESS (the Resend catch, an SMTP sink, …): recipients are raw address
|
|
38
|
+
* strings the bus resolves against its provisioned inboxes. */
|
|
39
|
+
export interface InboundRaw {
|
|
40
|
+
from: string;
|
|
41
|
+
to: string[];
|
|
42
|
+
subject?: string;
|
|
43
|
+
body: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The port. Faux (in-process) + real-email + real-sms adapters implement it identically. Async so a
|
|
47
|
+
* real (network) adapter fits without changing callers; the faux adapter just resolves immediately.
|
|
48
|
+
*/
|
|
49
|
+
export interface CommsChannel {
|
|
50
|
+
readonly channel: CommsChannelKind;
|
|
51
|
+
readonly kind: "faux" | "real";
|
|
52
|
+
/** Mint/allocate an inbox for an actor (address auto-generated). Idempotent per actor. */
|
|
53
|
+
provision(actorId: string): Promise<CommsAddress>;
|
|
54
|
+
/** Route a composed message from one actor to addressed inboxes. Returns the delivered record. */
|
|
55
|
+
send(message: OutboundMessage): Promise<CommsMessage>;
|
|
56
|
+
/** Route a raw ingress delivery (app-under-test → recipient strings). Returns the messages that
|
|
57
|
+
* matched a provisioned inbox (unmatched recipients are dropped — no inbox to deliver to). */
|
|
58
|
+
deliverRaw(inbound: InboundRaw): Promise<CommsMessage[]>;
|
|
59
|
+
/** New messages delivered to `address` since `since` (exclusive), oldest-first. Drives the surface. */
|
|
60
|
+
poll(address: CommsAddress, since?: number): Promise<CommsMessage[]>;
|
|
61
|
+
/** Release inboxes BY id (mirror the by-id sandbox teardown rail). */
|
|
62
|
+
teardown(): Promise<void>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// The addressed message bus (#297) — the seam that makes "off-app" comms (email/SMS the persona
|
|
2
|
+
// actually lives in) a first-class, persona-driven testable surface. A single port, addressed by
|
|
3
|
+
// actor; faux (in-process) and real (provider-backed) adapters implement it identically, so the
|
|
4
|
+
// persona surface + evidence writer consume it without knowing which is behind it.
|
|
5
|
+
//
|
|
6
|
+
// PUBLIC-SAFETY: raw address values, message bodies, links, and codes are RUNTIME-ONLY. Only the
|
|
7
|
+
// address DIGEST (sha256-short, via redaction.digestText) is ever meant to reach a persisted bundle;
|
|
8
|
+
// a verification link / OTP has "no secret shape" (like the lobby code) → literal-scrub + digest.
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=comms-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comms-types.js","sourceRoot":"","sources":["../src/comms-types.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,iGAAiG;AACjG,gGAAgG;AAChG,mFAAmF;AACnF,EAAE;AACF,iGAAiG;AACjG,qGAAqG;AACrG,kGAAkG"}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export type { FetchLike, OpenAiResponsesProviderOptions } from "./openai-respons
|
|
|
17
17
|
export { adapterScoreFailureMessage, applyAdapterScoreFailureToReview, applyBrowserAdapterHooks } from "./adapter-extension.js";
|
|
18
18
|
export type { BrowserAdapterBackend, BrowserLabAdapterHooks, BrowserLabScoringContext } from "./adapter-extension.js";
|
|
19
19
|
export type { RedactionHooks } from "./redaction.js";
|
|
20
|
+
export { FauxInbox, extractLinks, extractOtpCodes } from "./comms-faux-inbox.js";
|
|
21
|
+
export type { FauxInboxOptions } from "./comms-faux-inbox.js";
|
|
22
|
+
export { startResendCatchServer } from "./comms-resend-catch.js";
|
|
23
|
+
export type { ResendCatchOptions, ResendCatchServer, ResendEmailPayload } from "./comms-resend-catch.js";
|
|
24
|
+
export type { CommsAddress, CommsChannel, CommsChannelKind, CommsMessage, InboundRaw, OutboundMessage } from "./comms-types.js";
|
|
20
25
|
export { DESKTOP_RATE, MODEL_RATES, PRICING_SCHEMA, estimateActorCost, estimateDesktopCost } from "./pricing.js";
|
|
21
26
|
export type { ActorEstimatedCost, DesktopCostEstimate, DesktopRate, ModelRate } from "./pricing.js";
|
|
22
27
|
export { normalizeCliArgv } from "./argv.js";
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,11 @@ export { createE2BDesktopExecutor } from "./e2b-desktop-executor.js";
|
|
|
7
7
|
export { loadE2BDesktopModule } from "./e2b-desktop-launch.js";
|
|
8
8
|
export { DEFAULT_OPENAI_CU_MODEL, OPENAI_RESPONSES_CU_CAPABILITIES, createOpenAiResponsesProvider } from "./openai-responses-cu.js";
|
|
9
9
|
export { adapterScoreFailureMessage, applyAdapterScoreFailureToReview, applyBrowserAdapterHooks } from "./adapter-extension.js";
|
|
10
|
+
// Addressed comms bus (#297): faux email/SMS inboxes + the Resend-shaped ingress that redirects an
|
|
11
|
+
// API-first app into the faux bus with one env var. Real (provider-backed) adapters implement the
|
|
12
|
+
// same CommsChannel port.
|
|
13
|
+
export { FauxInbox, extractLinks, extractOtpCodes } from "./comms-faux-inbox.js";
|
|
14
|
+
export { startResendCatchServer } from "./comms-resend-catch.js";
|
|
10
15
|
export { DESKTOP_RATE, MODEL_RATES, PRICING_SCHEMA, estimateActorCost, estimateDesktopCost } from "./pricing.js";
|
|
11
16
|
export { normalizeCliArgv } from "./argv.js";
|
|
12
17
|
export { CODEX_APP_SERVER_UI_SCHEMA, startCodexAppServerUi } from "./codex-app-server-ui.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Date: 2026-06-02 (current-state note updated 2026-07-14)
|
|
4
4
|
|
|
5
5
|
Status: reference map for the major contracts shipped through source version
|
|
6
|
-
`0.20.
|
|
6
|
+
`0.20.5`; it is not an exhaustive inventory of command/result envelopes. Exported types,
|
|
7
7
|
schema constants, parsers, and validators in `src/` are authoritative. Rows
|
|
8
8
|
marked "reserved" name layering intent only — no code emits or validates them
|
|
9
9
|
yet. Do not emit a reserved schema.
|
package/docs/goals/current.md
CHANGED
|
@@ -16,7 +16,7 @@ Humanish should be the open-source CLI that lets a maintainer ask:
|
|
|
16
16
|
The answer should be observable, verifiable, public-safe, and easy to turn into
|
|
17
17
|
actionable feedback.
|
|
18
18
|
|
|
19
|
-
## Current Program Truth (source `0.20.
|
|
19
|
+
## Current Program Truth (source `0.20.5`)
|
|
20
20
|
|
|
21
21
|
The package source and repository implementation in this tree agree on these
|
|
22
22
|
points:
|
package/docs/ramp/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Status: public-safe contributor and agent ramp.
|
|
4
4
|
|
|
5
|
-
Package/source version in this tree: `0.20.
|
|
5
|
+
Package/source version in this tree: `0.20.5` (2026-08-03). The containment boundary introduced in
|
|
6
6
|
`0.15.1` remains in force: managed run and output paths bind to validated
|
|
7
7
|
physical filesystem identities, and stored provider IDs are evidence, not
|
|
8
8
|
cleanup authority. The bundled OSS meta-lab is dry-run only until
|
package/package.json
CHANGED