humanish 0.36.1 → 0.38.0

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.
@@ -1,3 +1,4 @@
1
+ import type { AffordanceUse } from "./affordance.js";
1
2
  import type { CodexAppServerRunResult, CodexAppServerStatus } from "./codex-app-server.js";
2
3
  import type { ActorEstimatedCost } from "./pricing.js";
3
4
  export declare const ACTOR_TRACE_SCHEMA = "humanish.actor-trace.v1";
@@ -88,6 +89,17 @@ export interface ActorTrace {
88
89
  model?: string;
89
90
  };
90
91
  counts: Record<string, number>;
92
+ /**
93
+ * ADDITIVE + OPTIONAL affordance record (humanish.affordance-use.v1, #369): which KIND of route
94
+ * this actor took — pointer, keyboard, url-navigation, script-execution, devtools,
95
+ * browser-internal, observation — as per-class counts over the run's dispatched actions.
96
+ * Present on computer-use lanes that dispatched at least one action; absent elsewhere and on
97
+ * every pre-existing bundle (its absence is tolerated by verify). The harness records the class
98
+ * and states NO verdict: whether a class is faithful depends on the population the study
99
+ * declares, which is product semantics and belongs to the adopter's scorer. See
100
+ * docs/principles/actor-fidelity.md.
101
+ */
102
+ affordanceUse?: AffordanceUse;
91
103
  items: ActorTraceItem[];
92
104
  tokenUsage?: ActorTokenUsage;
93
105
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"actor-contract.js","sourceRoot":"","sources":["../src/actor-contract.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"actor-contract.js","sourceRoot":"","sources":["../src/actor-contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { CuaAction } from "./computer-use.js";
2
+ export declare const AFFORDANCE_CLASS_SCHEMA = "humanish.affordance-use.v1";
3
+ /**
4
+ * The affordance classes, named after the surrounding literature rather than invented here:
5
+ * "naturalistic actions" (AndroidWorld) for the human-modality subset, "nav" (BrowserGym) for
6
+ * direct navigation, "shortcut" (MAS-Bench) for a non-UI route to the same outcome.
7
+ */
8
+ export type AffordanceClass =
9
+ /** Pointer or drag interaction with what is rendered on screen — the naturalistic core. */
10
+ "pointer"
11
+ /** Keyboard input into the page: typed text that is not a URL and not script. */
12
+ | "keyboard"
13
+ /** Direct navigation by URL (the `nav` subset). A human affordance; see the note above. */
14
+ | "url-navigation"
15
+ /** Script execution via the address bar (a `javascript:` URL). Not a human affordance. */
16
+ | "script-execution"
17
+ /** Developer tooling opened by keyboard shortcut. Not a human affordance. */
18
+ | "devtools"
19
+ /** A browser-internal surface rather than the product: chrome://, about:, view-source:, file:. */
20
+ | "browser-internal"
21
+ /** The actor observing or pausing rather than acting: screenshots, waits, pointer moves. */
22
+ | "observation";
23
+ /** Classes that a person operating this product through its own surfaces could produce. */
24
+ export declare const NATURALISTIC_AFFORDANCE_CLASSES: readonly AffordanceClass[];
25
+ /** Classes that reach past the rendered product surface. Recorded, never blocked. */
26
+ export declare const SHORTCUT_AFFORDANCE_CLASSES: readonly AffordanceClass[];
27
+ export interface AffordanceObservation {
28
+ affordance: AffordanceClass;
29
+ /**
30
+ * A public-safe hint about WHY this class was assigned, when one exists — a URL scheme or the
31
+ * devtools chord. Never the typed text, never a full URL (which can carry a session token).
32
+ */
33
+ signal?: string;
34
+ }
35
+ /** Aggregate per-run record: how many actions fell into each class. */
36
+ export interface AffordanceUse {
37
+ schema: typeof AFFORDANCE_CLASS_SCHEMA;
38
+ /** Counts by class; a class with zero actions is omitted so the record stays small and honest. */
39
+ counts: Partial<Record<AffordanceClass, number>>;
40
+ /** Total classified actions (the denominator for any rate an adopter computes). */
41
+ total: number;
42
+ /** Convenience roll-up: actions in the shortcut classes. Zero is a meaningful, common value. */
43
+ shortcutTotal: number;
44
+ }
45
+ /**
46
+ * Classify one computer-use action. PURE: same action in, same class out, no I/O, no clock. The
47
+ * returned signal is public-safe (a scheme or a chord), never the action's text.
48
+ */
49
+ export declare function classifyCuaAction(action: CuaAction): AffordanceObservation;
50
+ /** Fold a sequence of observations into the per-run aggregate. */
51
+ export declare function summarizeAffordanceUse(observations: readonly AffordanceObservation[]): AffordanceUse;
@@ -0,0 +1,142 @@
1
+ // Affordance classification (#369): WHICH KIND of route an actor took to accomplish a step.
2
+ //
3
+ // Why this exists. A computer-use actor in a human-persona study typed a `javascript:` URL into
4
+ // the address bar to get past a step, and the run finished green: the actor routed around the
5
+ // friction the study existed to measure, and nothing in the evidence said so. Whether that is a
6
+ // defect depends on a question the bundle never recorded — who is this study's user? For a human
7
+ // population it invalidates the run; for an agent-facing product whose users ARE agents the same
8
+ // act is faithful, and the fact the agent reached for it is itself a finding about how legible
9
+ // the surface is. So the harness records the class and bakes NO verdict: the adopter's scorer
10
+ // (review.scorer.ref) decides what it means, because that judgment is product semantics.
11
+ //
12
+ // Two design facts drive the shape:
13
+ //
14
+ // 1. The computer-use action space is mouse + keyboard only — there is no `goto` action. Typing a
15
+ // URL is a `type` action whose TEXT happens to be a URL, and typing script is a `type` action
16
+ // whose text happens to start with `javascript:`. Classification therefore has to look at the
17
+ // typed text, which exists only at dispatch time.
18
+ // 2. Raw typed text is never persisted (describeCuaAction deliberately renders `type [N chars]`),
19
+ // so this module returns a CLASS and at most a scheme-shaped signal — never the text. The
20
+ // class is public-safe by construction.
21
+ //
22
+ // Direct URL navigation is deliberately its own class and NOT lumped with script execution:
23
+ // `load(url)` appears in 99.4% of 2,337 real human web demonstrations (WebLINX), so address-bar
24
+ // use is ordinary human behavior. The anomalous classes are script execution and developer
25
+ // tooling. See docs/principles/actor-fidelity.md for the evidence and the scoping of the claim.
26
+ export const AFFORDANCE_CLASS_SCHEMA = "humanish.affordance-use.v1";
27
+ /** Classes that a person operating this product through its own surfaces could produce. */
28
+ export const NATURALISTIC_AFFORDANCE_CLASSES = [
29
+ "pointer",
30
+ "keyboard",
31
+ "url-navigation",
32
+ "observation"
33
+ ];
34
+ /** Classes that reach past the rendered product surface. Recorded, never blocked. */
35
+ export const SHORTCUT_AFFORDANCE_CLASSES = ["script-execution", "devtools", "browser-internal"];
36
+ // A `javascript:` URL typed into the address bar. Tolerates leading whitespace and mixed case;
37
+ // the scheme is what matters, not the payload (which is never recorded).
38
+ const SCRIPT_URL = /^\s*(?:javascript|data):/i;
39
+ // Browser-internal surfaces: not the product under study, and not something a study of the
40
+ // product's UX can attribute to the product.
41
+ const BROWSER_INTERNAL_URL = /^\s*(?:chrome|about|view-source|devtools|file|edge|brave):/i;
42
+ // An http(s) URL, or a bare host-looking string a browser resolves as navigation.
43
+ const HTTP_URL = /^\s*https?:\/\//i;
44
+ const BARE_HOST = /^\s*(?:www\.)?[a-z0-9-]+(?:\.[a-z0-9-]+)+(?:[/:?#]\S*)?\s*$/i;
45
+ // The common devtools chords across platforms.
46
+ const DEVTOOLS_CHORDS = new Set([
47
+ "f12",
48
+ "ctrl+shift+i",
49
+ "ctrl+shift+j",
50
+ "ctrl+shift+c",
51
+ "cmd+alt+i",
52
+ "cmd+alt+j",
53
+ "cmd+alt+c",
54
+ "meta+alt+i",
55
+ "meta+alt+j",
56
+ "meta+alt+c"
57
+ ]);
58
+ /**
59
+ * Classify one computer-use action. PURE: same action in, same class out, no I/O, no clock. The
60
+ * returned signal is public-safe (a scheme or a chord), never the action's text.
61
+ */
62
+ export function classifyCuaAction(action) {
63
+ switch (action.kind) {
64
+ case "click":
65
+ case "double_click":
66
+ case "drag":
67
+ case "scroll":
68
+ return { affordance: "pointer" };
69
+ case "wait":
70
+ case "screenshot":
71
+ // A bare pointer move actuates nothing on its own; it is the actor looking around.
72
+ case "move":
73
+ return { affordance: "observation" };
74
+ case "keypress": {
75
+ const chord = normalizeChord(action.keys);
76
+ if (DEVTOOLS_CHORDS.has(chord)) {
77
+ return { affordance: "devtools", signal: chord };
78
+ }
79
+ return { affordance: "keyboard" };
80
+ }
81
+ case "type": {
82
+ const text = action.text;
83
+ if (SCRIPT_URL.test(text)) {
84
+ // The payload after the scheme is deliberately dropped: it is arbitrary author text and
85
+ // could carry anything. The scheme alone is the finding. `data:` rides here with
86
+ // `javascript:` because a data URL can carry executable HTML.
87
+ return { affordance: "script-execution", signal: schemeOf(text) };
88
+ }
89
+ if (BROWSER_INTERNAL_URL.test(text)) {
90
+ return { affordance: "browser-internal", signal: schemeOf(text) };
91
+ }
92
+ if (HTTP_URL.test(text)) {
93
+ // Scheme only — a full URL can carry a session token or an identifying path.
94
+ return { affordance: "url-navigation", signal: schemeOf(text) };
95
+ }
96
+ if (BARE_HOST.test(text)) {
97
+ return { affordance: "url-navigation", signal: "bare-host" };
98
+ }
99
+ return { affordance: "keyboard" };
100
+ }
101
+ default:
102
+ // The wire->CuaAction mapper drops unrecognized action types before they reach here, so
103
+ // this is unreachable today; classify defensively rather than throwing inside a run.
104
+ return { affordance: "observation" };
105
+ }
106
+ }
107
+ /**
108
+ * Normalize a key chord so the same physical shortcut matches however a provider spells it:
109
+ * `Control`/`ctrl`, `Meta`/`Command`/`cmd`, `Option`/`alt` all collapse, and modifier ORDER is
110
+ * normalized so ["Shift","Control","J"] and ["Control","Shift","J"] are one chord.
111
+ */
112
+ function normalizeChord(keys) {
113
+ const alias = {
114
+ control: "ctrl", ctl: "ctrl",
115
+ meta: "cmd", command: "cmd", super: "cmd", os: "cmd",
116
+ option: "alt", opt: "alt"
117
+ };
118
+ const parts = keys.map((key) => {
119
+ const lowered = key.trim().toLowerCase();
120
+ return alias[lowered] ?? lowered;
121
+ });
122
+ const order = ["ctrl", "cmd", "alt", "shift"];
123
+ const modifiers = parts.filter((part) => order.includes(part)).sort((a, b) => order.indexOf(a) - order.indexOf(b));
124
+ const rest = parts.filter((part) => !order.includes(part));
125
+ return [...modifiers, ...rest].join("+");
126
+ }
127
+ /** The scheme alone, lowercased — never the rest of a URL, which can carry a session token. */
128
+ function schemeOf(text) {
129
+ const trimmed = text.trim();
130
+ const colon = trimmed.indexOf(":");
131
+ return colon === -1 ? "" : `${trimmed.slice(0, colon).toLowerCase()}:`;
132
+ }
133
+ /** Fold a sequence of observations into the per-run aggregate. */
134
+ export function summarizeAffordanceUse(observations) {
135
+ const counts = {};
136
+ for (const observation of observations) {
137
+ counts[observation.affordance] = (counts[observation.affordance] ?? 0) + 1;
138
+ }
139
+ const shortcutTotal = SHORTCUT_AFFORDANCE_CLASSES.reduce((sum, klass) => sum + (counts[klass] ?? 0), 0);
140
+ return { schema: AFFORDANCE_CLASS_SCHEMA, counts, total: observations.length, shortcutTotal };
141
+ }
142
+ //# sourceMappingURL=affordance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"affordance.js","sourceRoot":"","sources":["../src/affordance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /** The CLI writer surface this command needs (structurally compatible with program.ts CliIo). */
2
+ interface CatchHostIo {
3
+ writeOut(text: string): void;
4
+ writeErr(text: string): void;
5
+ setExitCode(code: number): void;
6
+ }
7
+ export interface CommsCatchHostOptions {
8
+ port: number;
9
+ dir: string;
10
+ /** Bearer token required on GET /deliveries. Strongly recommended when the host is reachable. */
11
+ token?: string;
12
+ }
13
+ /**
14
+ * Write the catch script and run it in the foreground. Resolves when the child exits; the caller's
15
+ * Ctrl-C reaches the child through the shared process group, so the normal way to stop it is the
16
+ * normal way to stop any foreground server.
17
+ */
18
+ export declare function runCommsCatchHost(options: CommsCatchHostOptions, io: CatchHostIo): Promise<void>;
19
+ export {};
@@ -0,0 +1,69 @@
1
+ // `humanish comms catch` (#328): run the email catch on the OPERATOR's own host.
2
+ //
3
+ // Why this exists. The in-sandbox catch only works when humanish provisions the subject itself —
4
+ // it clones the app into a sandbox, injects the email-API base URL at boot, and hosts the catch
5
+ // alongside it. An adopter whose study runs against their OWN deployed environment (an app-url or
6
+ // operator-provisioned plane) hands humanish a URL instead, so humanish never boots the app and
7
+ // has nowhere to put a catch. Before this, a `comms:` block on that route was warned inert and the
8
+ // personas simply stalled at the verification screen.
9
+ //
10
+ // The fix is to let the adopter host the same catch and declare it (`comms.email.external`).
11
+ // Shipping it as a COMMAND rather than a spec matters: the adopter runs the identical
12
+ // implementation humanish deploys in-sandbox, so the capture shape, the inbox surface, and the
13
+ // drain contract cannot drift between the two planes. Point the app's email-API base URL at this
14
+ // server and point the lab's `catchBaseUrl` at it too.
15
+ //
16
+ // Runtime: the catch is a python3 stdlib server with no dependencies (that was the 0.29.0 lesson —
17
+ // a co-located catcher must use a runtime the environment guarantees). This command writes the
18
+ // same script and runs it in the foreground until interrupted.
19
+ import { spawn } from "node:child_process";
20
+ import { mkdir, writeFile } from "node:fs/promises";
21
+ import path from "node:path";
22
+ import { SANDBOX_CATCH_SCRIPT } from "./comms-sandbox-catch.js";
23
+ /**
24
+ * Write the catch script and run it in the foreground. Resolves when the child exits; the caller's
25
+ * Ctrl-C reaches the child through the shared process group, so the normal way to stop it is the
26
+ * normal way to stop any foreground server.
27
+ */
28
+ export async function runCommsCatchHost(options, io) {
29
+ const dir = path.resolve(options.dir);
30
+ const scriptPath = path.join(dir, "catch.py");
31
+ const deliveriesPath = path.join(dir, "deliveries.ndjson");
32
+ const surfaceDir = path.join(dir, "surface");
33
+ await mkdir(surfaceDir, { recursive: true });
34
+ await writeFile(scriptPath, SANDBOX_CATCH_SCRIPT, "utf8");
35
+ const args = [scriptPath, String(options.port), deliveriesPath, surfaceDir, "0", options.token ?? ""];
36
+ io.writeOut([
37
+ `humanish comms catch listening on http://127.0.0.1:${options.port}`,
38
+ ` POST /emails <- point your app's email-API base URL here`,
39
+ ` GET /inbox <- the persona opens this`,
40
+ ` GET /deliveries <- humanish drains this${options.token ? " (bearer token required)" : ""}`,
41
+ ` GET /health <- readiness marker humanish probes before a run`,
42
+ ``,
43
+ `Declare it in the lab:`,
44
+ ` comms:`,
45
+ ` email:`,
46
+ ` external:`,
47
+ ` catchBaseUrl: http://<this-host>:${options.port}`,
48
+ ...(options.token ? [` authTokenEnv: HUMANISH_COMMS_TOKEN # value read at runtime, never persisted`] : []),
49
+ ``,
50
+ `Captured mail is written to ${deliveriesPath}. Raw bodies stay on THIS host: the run bundle`,
51
+ `only ever receives digests (from/to/subject/link) and an OTP count.`,
52
+ ``
53
+ ].join("\n") + "\n");
54
+ await new Promise((resolve) => {
55
+ const child = spawn("python3", args, { stdio: ["ignore", "inherit", "inherit"] });
56
+ child.on("error", (error) => {
57
+ io.writeErr(`comms catch failed to start (python3 is required): ${error.message}\n`);
58
+ io.setExitCode(2);
59
+ resolve();
60
+ });
61
+ child.on("exit", (code) => {
62
+ if (code !== 0 && code !== null) {
63
+ io.setExitCode(2);
64
+ }
65
+ resolve();
66
+ });
67
+ });
68
+ }
69
+ //# sourceMappingURL=comms-catch-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comms-catch-host.js","sourceRoot":"","sources":["../src/comms-catch-host.ts"],"names":[],"mappings":"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"}
@@ -23,7 +23,7 @@ export declare const DEFAULT_SANDBOX_CATCH_PORT = 8025;
23
23
  * listener on 0.0.0.0:<inboxPort> so getHost can proxy the persona's inbox reads to it; that listener
24
24
  * serves GET only (POST → 405). The CUA same-sandbox route omits it and stays loopback-only.
25
25
  */
26
- export declare const SANDBOX_CATCH_SCRIPT = "import json\nimport os\nimport random\nimport sys\nimport threading\nimport time\nfrom http.server import BaseHTTPRequestHandler, ThreadingHTTPServer\nfrom urllib.parse import unquote\n\nPORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8025\nOUT_FILE = sys.argv[2] if len(sys.argv) > 2 else \"/tmp/humanish-comms/deliveries.ndjson\"\nSERVED_DIR = sys.argv[3] if len(sys.argv) > 3 else (os.path.dirname(OUT_FILE) + \"/surface\")\nINBOX_PORT = int(sys.argv[4]) if len(sys.argv) > 4 else 0\ntry:\n os.makedirs(os.path.dirname(OUT_FILE), exist_ok=True)\nexcept Exception:\n pass\nMAX_BODY = 5 * 1024 * 1024\nCSP = \"default-src 'self'; script-src 'none'; object-src 'none'; base-uri 'none'; frame-src 'none'; img-src * data:; style-src 'unsafe-inline'; font-src * data:\"\n\n\ndef message_id():\n return \"humanish-catch-\" + format(int(time.time() * 1000), \"x\") + format(random.randrange(16 ** 8), \"08x\")\n\n\nclass BaseHandler(BaseHTTPRequestHandler):\n def log_message(self, *args):\n return\n\n def _json(self, status, obj, extra_headers=None):\n payload = json.dumps(obj).encode(\"utf-8\")\n self.send_response(status)\n self.send_header(\"content-type\", \"application/json; charset=utf-8\")\n for key, value in (extra_headers or {}).items():\n self.send_header(key, value)\n self.end_headers()\n self.wfile.write(payload)\n\n def do_GET(self):\n path = self.path.split(\"?\")[0]\n if path == \"/\" or path == \"/health\":\n self._json(200, {\"ok\": True, \"service\": \"humanish-comms-catch\"})\n return\n if path == \"/inbox\" or path.startswith(\"/inbox/\") or path == \"/api/inbox\" or path.startswith(\"/api/inbox/\"):\n rel = unquote(path)\n if \"..\" in rel or chr(0) in rel:\n self.send_response(400)\n self.end_headers()\n return\n data = None\n for candidate in (SERVED_DIR + rel, SERVED_DIR + rel + \"/index\"):\n try:\n with open(candidate, \"rb\") as handle:\n data = handle.read()\n break\n except Exception:\n data = None\n if data is None:\n self.send_response(404)\n self.send_header(\"content-type\", \"text/html; charset=utf-8\")\n self.end_headers()\n self.wfile.write(b\"<p>message not found</p>\")\n return\n is_api = rel.startswith(\"/api/\")\n self.send_response(200)\n self.send_header(\"content-type\", \"application/json; charset=utf-8\" if is_api else \"text/html; charset=utf-8\")\n if not is_api:\n self.send_header(\"content-security-policy\", CSP)\n self.end_headers()\n self.wfile.write(data)\n return\n self._json(404, {\"error\": \"not found\"})\n\n\nclass CaptureHandler(BaseHandler):\n def do_POST(self):\n path = self.path.split(\"?\")[0]\n try:\n length = int(self.headers.get(\"content-length\") or 0)\n except Exception:\n length = 0\n if length > MAX_BODY:\n self.send_response(413)\n self.end_headers()\n return\n body = self.rfile.read(length).decode(\"utf-8\", \"replace\") if length > 0 else \"\"\n try:\n with open(OUT_FILE, \"a\", encoding=\"utf-8\") as handle:\n print(json.dumps({\"t\": int(time.time() * 1000), \"path\": path, \"body\": body}), file=handle)\n except Exception:\n pass\n mid = message_id()\n if path == \"/v3/mail/send\":\n self.send_response(202)\n self.send_header(\"x-message-id\", mid)\n self.end_headers()\n elif path.endswith(\"/batch\"):\n self._json(200, {\"data\": [{\"id\": mid}]})\n else:\n self._json(200, {\"id\": mid})\n\n\nclass ReadOnlyHandler(BaseHandler):\n def do_POST(self):\n self.send_response(405)\n self.end_headers()\n\n\n# Optional read-only inbox listener on 0.0.0.0 (getHost-reachable from a DIFFERENT sandbox on the\n# shared-world route). Serves GET /inbox + /api/inbox + /health only; POST capture stays on the\n# 127.0.0.1 listener so nothing on the internet can inject a fake captured send. Started only when a\n# distinct inbox port is provided (the CUA same-sandbox route omits it and stays loopback-only).\nif INBOX_PORT and INBOX_PORT != PORT:\n threading.Thread(target=lambda: ThreadingHTTPServer((\"0.0.0.0\", INBOX_PORT), ReadOnlyHandler).serve_forever(), daemon=True).start()\n\nThreadingHTTPServer((\"127.0.0.1\", PORT), CaptureHandler).serve_forever()\n";
26
+ export declare const SANDBOX_CATCH_SCRIPT = "import json\nimport os\nimport random\nimport sys\nimport threading\nimport time\nfrom http.server import BaseHTTPRequestHandler, ThreadingHTTPServer\nfrom urllib.parse import unquote\n\nPORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8025\nOUT_FILE = sys.argv[2] if len(sys.argv) > 2 else \"/tmp/humanish-comms/deliveries.ndjson\"\nSERVED_DIR = sys.argv[3] if len(sys.argv) > 3 else (os.path.dirname(OUT_FILE) + \"/surface\")\nINBOX_PORT = int(sys.argv[4]) if len(sys.argv) > 4 else 0\n# Optional shared token guarding GET /deliveries (the drain read). Empty = unguarded, which is the\n# in-sandbox default: the capture listener binds loopback there, so nothing external can reach it.\n# An ADOPTER-HOSTED catch is reachable over the network, so it should pass one.\nDELIVERIES_TOKEN = sys.argv[5] if len(sys.argv) > 5 else \"\"\ntry:\n os.makedirs(os.path.dirname(OUT_FILE), exist_ok=True)\nexcept Exception:\n pass\nMAX_BODY = 5 * 1024 * 1024\nCSP = \"default-src 'self'; script-src 'none'; object-src 'none'; base-uri 'none'; frame-src 'none'; img-src * data:; style-src 'unsafe-inline'; font-src * data:\"\n\n\ndef message_id():\n return \"humanish-catch-\" + format(int(time.time() * 1000), \"x\") + format(random.randrange(16 ** 8), \"08x\")\n\n\nclass BaseHandler(BaseHTTPRequestHandler):\n def log_message(self, *args):\n return\n\n def _json(self, status, obj, extra_headers=None):\n payload = json.dumps(obj).encode(\"utf-8\")\n self.send_response(status)\n self.send_header(\"content-type\", \"application/json; charset=utf-8\")\n for key, value in (extra_headers or {}).items():\n self.send_header(key, value)\n self.end_headers()\n self.wfile.write(payload)\n\n def do_GET(self):\n path = self.path.split(\"?\")[0]\n if path == \"/\" or path == \"/health\":\n self._json(200, {\"ok\": True, \"service\": \"humanish-comms-catch\"})\n return\n if path == \"/deliveries\":\n # The drain read. In-sandbox humanish reads the NDJSON file directly; an adopter-hosted\n # catch is on another machine, so the same bytes are served over HTTP. Capture bodies\n # can contain a verification link, so this is the one route worth guarding.\n if DELIVERIES_TOKEN:\n supplied = self.headers.get(\"authorization\", \"\")\n if supplied != (\"Bearer \" + DELIVERIES_TOKEN):\n self._json(401, {\"error\": \"unauthorized\"})\n return\n try:\n with open(OUT_FILE, \"rb\") as handle:\n body = handle.read()\n except Exception:\n body = b\"\"\n self.send_response(200)\n self.send_header(\"content-type\", \"application/x-ndjson; charset=utf-8\")\n self.send_header(\"cache-control\", \"no-store\")\n self.end_headers()\n self.wfile.write(body)\n return\n if path == \"/inbox\" or path.startswith(\"/inbox/\") or path == \"/api/inbox\" or path.startswith(\"/api/inbox/\"):\n rel = unquote(path)\n if \"..\" in rel or chr(0) in rel:\n self.send_response(400)\n self.end_headers()\n return\n data = None\n for candidate in (SERVED_DIR + rel, SERVED_DIR + rel + \"/index\"):\n try:\n with open(candidate, \"rb\") as handle:\n data = handle.read()\n break\n except Exception:\n data = None\n if data is None:\n self.send_response(404)\n self.send_header(\"content-type\", \"text/html; charset=utf-8\")\n self.end_headers()\n self.wfile.write(b\"<p>message not found</p>\")\n return\n is_api = rel.startswith(\"/api/\")\n self.send_response(200)\n self.send_header(\"content-type\", \"application/json; charset=utf-8\" if is_api else \"text/html; charset=utf-8\")\n if not is_api:\n self.send_header(\"content-security-policy\", CSP)\n self.end_headers()\n self.wfile.write(data)\n return\n self._json(404, {\"error\": \"not found\"})\n\n\nclass CaptureHandler(BaseHandler):\n def do_POST(self):\n path = self.path.split(\"?\")[0]\n try:\n length = int(self.headers.get(\"content-length\") or 0)\n except Exception:\n length = 0\n if length > MAX_BODY:\n self.send_response(413)\n self.end_headers()\n return\n body = self.rfile.read(length).decode(\"utf-8\", \"replace\") if length > 0 else \"\"\n try:\n with open(OUT_FILE, \"a\", encoding=\"utf-8\") as handle:\n print(json.dumps({\"t\": int(time.time() * 1000), \"path\": path, \"body\": body}), file=handle)\n except Exception:\n pass\n mid = message_id()\n if path == \"/v3/mail/send\":\n self.send_response(202)\n self.send_header(\"x-message-id\", mid)\n self.end_headers()\n elif path.endswith(\"/batch\"):\n self._json(200, {\"data\": [{\"id\": mid}]})\n else:\n self._json(200, {\"id\": mid})\n\n\nclass ReadOnlyHandler(BaseHandler):\n def do_POST(self):\n self.send_response(405)\n self.end_headers()\n\n\n# Optional read-only inbox listener on 0.0.0.0 (getHost-reachable from a DIFFERENT sandbox on the\n# shared-world route). Serves GET /inbox + /api/inbox + /health only; POST capture stays on the\n# 127.0.0.1 listener so nothing on the internet can inject a fake captured send. Started only when a\n# distinct inbox port is provided (the CUA same-sandbox route omits it and stays loopback-only).\nif INBOX_PORT and INBOX_PORT != PORT:\n threading.Thread(target=lambda: ThreadingHTTPServer((\"0.0.0.0\", INBOX_PORT), ReadOnlyHandler).serve_forever(), daemon=True).start()\n\nThreadingHTTPServer((\"127.0.0.1\", PORT), CaptureHandler).serve_forever()\n";
27
27
  export interface DeployCommsCatchOptions {
28
28
  /** Fixed loopback port the catch listens on (default 8025). Must be free inside the sandbox. */
29
29
  port?: number;
@@ -111,6 +111,52 @@ export declare function collectCommsThread(args: {
111
111
  profiles?: EmailSendProfile[];
112
112
  requestTimeoutMs?: number;
113
113
  }): Promise<CommsThreadCollection>;
114
+ /** An adopter-hosted catch: humanish never provisioned it, so it is addressed over HTTP (#328). */
115
+ export interface ExternalCommsCatch {
116
+ /** Base URL of the catch the ADOPTER runs (its POST capture endpoint and GET /deliveries). */
117
+ catchBaseUrl: string;
118
+ /** Base URL the persona opens to read mail. Defaults to catchBaseUrl (same server serves /inbox). */
119
+ inboxBaseUrl?: string;
120
+ /** Bearer token for the drain read, when the adopter guarded it. Value is used, never persisted. */
121
+ authToken?: string;
122
+ }
123
+ /** The URL a persona is told to open to read its mail on an adopter-hosted plane. */
124
+ export declare function externalInboxUrl(external: ExternalCommsCatch): string;
125
+ /**
126
+ * Probe an adopter-hosted catch the way the in-sandbox one is probed: assert OUR service marker in
127
+ * /health, not merely any 2xx — an adopter's reverse proxy or a captive portal will happily return
128
+ * 200 for anything, and a comms lab whose catch is not actually there collects nothing while
129
+ * looking fine. Fail-closed callers treat `false` as a hard stop before spending on a run.
130
+ */
131
+ export declare function externalCatchHealthy(external: ExternalCommsCatch, options?: {
132
+ timeoutMs?: number;
133
+ fetchFn?: typeof fetch;
134
+ }): Promise<boolean>;
135
+ /**
136
+ * Drain an adopter-hosted catch over HTTP. Same NDJSON contract and same partial-line discipline as
137
+ * the in-sandbox `cat` drain: a body that does not end in a newline may have a torn final append, so
138
+ * that line is dropped rather than parsed into a half-message.
139
+ */
140
+ export declare function drainExternalCommsCatch(external: ExternalCommsCatch, cursor?: number, options?: {
141
+ timeoutMs?: number;
142
+ fetchFn?: typeof fetch;
143
+ }): Promise<{
144
+ sends: RawCapturedSend[];
145
+ cursor: number;
146
+ }>;
147
+ /**
148
+ * The adopter-hosted analogue of collectCommsThread: drain over HTTP, route into the host inbox bus,
149
+ * and build the SAME digest-only humanish.comms-thread.v1 artifact. Evidence shape does not depend
150
+ * on who hosted the catch — only the transport does.
151
+ */
152
+ export declare function collectExternalCommsThread(args: {
153
+ external: ExternalCommsCatch;
154
+ channel: CommsChannel;
155
+ inboxes: CommsAddress[];
156
+ profiles?: EmailSendProfile[];
157
+ timeoutMs?: number;
158
+ fetchFn?: typeof fetch;
159
+ }): Promise<CommsThreadCollection>;
114
160
  /**
115
161
  * Render the persona-facing inbox surface (host-side, typed — see comms-inbox.ts) and write the files
116
162
  * into the sandbox's served dir, so the catch serves a LIVE inbox the persona opens and clicks. Creates
@@ -48,6 +48,10 @@ PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8025
48
48
  OUT_FILE = sys.argv[2] if len(sys.argv) > 2 else "/tmp/humanish-comms/deliveries.ndjson"
49
49
  SERVED_DIR = sys.argv[3] if len(sys.argv) > 3 else (os.path.dirname(OUT_FILE) + "/surface")
50
50
  INBOX_PORT = int(sys.argv[4]) if len(sys.argv) > 4 else 0
51
+ # Optional shared token guarding GET /deliveries (the drain read). Empty = unguarded, which is the
52
+ # in-sandbox default: the capture listener binds loopback there, so nothing external can reach it.
53
+ # An ADOPTER-HOSTED catch is reachable over the network, so it should pass one.
54
+ DELIVERIES_TOKEN = sys.argv[5] if len(sys.argv) > 5 else ""
51
55
  try:
52
56
  os.makedirs(os.path.dirname(OUT_FILE), exist_ok=True)
53
57
  except Exception:
@@ -78,6 +82,26 @@ class BaseHandler(BaseHTTPRequestHandler):
78
82
  if path == "/" or path == "/health":
79
83
  self._json(200, {"ok": True, "service": "humanish-comms-catch"})
80
84
  return
85
+ if path == "/deliveries":
86
+ # The drain read. In-sandbox humanish reads the NDJSON file directly; an adopter-hosted
87
+ # catch is on another machine, so the same bytes are served over HTTP. Capture bodies
88
+ # can contain a verification link, so this is the one route worth guarding.
89
+ if DELIVERIES_TOKEN:
90
+ supplied = self.headers.get("authorization", "")
91
+ if supplied != ("Bearer " + DELIVERIES_TOKEN):
92
+ self._json(401, {"error": "unauthorized"})
93
+ return
94
+ try:
95
+ with open(OUT_FILE, "rb") as handle:
96
+ body = handle.read()
97
+ except Exception:
98
+ body = b""
99
+ self.send_response(200)
100
+ self.send_header("content-type", "application/x-ndjson; charset=utf-8")
101
+ self.send_header("cache-control", "no-store")
102
+ self.end_headers()
103
+ self.wfile.write(body)
104
+ return
81
105
  if path == "/inbox" or path.startswith("/inbox/") or path == "/api/inbox" or path.startswith("/api/inbox/"):
82
106
  rel = unquote(path)
83
107
  if ".." in rel or chr(0) in rel:
@@ -300,6 +324,93 @@ export async function collectCommsThread(args) {
300
324
  messages.sort((a, b) => a.deliveredAt - b.deliveredAt || a.id.localeCompare(b.id));
301
325
  return { artifact: buildCommsThreadArtifact(messages), captured: sends.length, matched: messages.length };
302
326
  }
327
+ /** Trim one trailing slash so `${base}/deliveries` never becomes a double slash. */
328
+ function baseOf(url) {
329
+ return url.replace(/\/+$/, "");
330
+ }
331
+ /** The URL a persona is told to open to read its mail on an adopter-hosted plane. */
332
+ export function externalInboxUrl(external) {
333
+ return `${baseOf(external.inboxBaseUrl ?? external.catchBaseUrl)}/inbox`;
334
+ }
335
+ /**
336
+ * Probe an adopter-hosted catch the way the in-sandbox one is probed: assert OUR service marker in
337
+ * /health, not merely any 2xx — an adopter's reverse proxy or a captive portal will happily return
338
+ * 200 for anything, and a comms lab whose catch is not actually there collects nothing while
339
+ * looking fine. Fail-closed callers treat `false` as a hard stop before spending on a run.
340
+ */
341
+ export async function externalCatchHealthy(external, options = {}) {
342
+ const fetchFn = options.fetchFn ?? fetch;
343
+ try {
344
+ const response = await fetchFn(`${baseOf(external.catchBaseUrl)}/health`, {
345
+ signal: AbortSignal.timeout(options.timeoutMs ?? 15_000)
346
+ });
347
+ if (!response.ok)
348
+ return false;
349
+ const body = await response.text();
350
+ return body.includes("humanish-comms-catch");
351
+ }
352
+ catch {
353
+ return false;
354
+ }
355
+ }
356
+ /**
357
+ * Drain an adopter-hosted catch over HTTP. Same NDJSON contract and same partial-line discipline as
358
+ * the in-sandbox `cat` drain: a body that does not end in a newline may have a torn final append, so
359
+ * that line is dropped rather than parsed into a half-message.
360
+ */
361
+ export async function drainExternalCommsCatch(external, cursor = 0, options = {}) {
362
+ const fetchFn = options.fetchFn ?? fetch;
363
+ const response = await fetchFn(`${baseOf(external.catchBaseUrl)}/deliveries`, {
364
+ signal: AbortSignal.timeout(options.timeoutMs ?? 30_000),
365
+ ...(external.authToken ? { headers: { authorization: `Bearer ${external.authToken}` } } : {})
366
+ });
367
+ if (!response.ok) {
368
+ throw new Error(`comms catch GET /deliveries returned ${response.status}`);
369
+ }
370
+ const body = await response.text();
371
+ let lines = body.split("\n").filter((line) => line.trim().length > 0);
372
+ if (!body.endsWith("\n") && lines.length > 0)
373
+ lines = lines.slice(0, -1);
374
+ const sends = [];
375
+ for (const line of lines.slice(cursor)) {
376
+ try {
377
+ const parsed = JSON.parse(line);
378
+ if (typeof parsed.path === "string" && typeof parsed.body === "string") {
379
+ sends.push({ path: parsed.path, body: parsed.body, t: typeof parsed.t === "number" ? parsed.t : 0 });
380
+ }
381
+ }
382
+ catch {
383
+ // skip a malformed line
384
+ }
385
+ }
386
+ return { sends, cursor: lines.length };
387
+ }
388
+ /**
389
+ * The adopter-hosted analogue of collectCommsThread: drain over HTTP, route into the host inbox bus,
390
+ * and build the SAME digest-only humanish.comms-thread.v1 artifact. Evidence shape does not depend
391
+ * on who hosted the catch — only the transport does.
392
+ */
393
+ export async function collectExternalCommsThread(args) {
394
+ const drainOptions = { ...(args.timeoutMs === undefined ? {} : { timeoutMs: args.timeoutMs }), ...(args.fetchFn ? { fetchFn: args.fetchFn } : {}) };
395
+ const { sends } = await drainExternalCommsCatch(args.external, 0, drainOptions);
396
+ if (sends.length === 0)
397
+ return { captured: 0, matched: 0 };
398
+ await routeCapturedSends(sends, args.channel, args.profiles);
399
+ const seen = new Set();
400
+ const messages = [];
401
+ for (const inbox of args.inboxes) {
402
+ for (const message of await args.channel.poll(inbox, 0)) {
403
+ if (seen.has(message.id))
404
+ continue;
405
+ seen.add(message.id);
406
+ messages.push(message);
407
+ }
408
+ }
409
+ if (messages.length === 0)
410
+ return { captured: sends.length, matched: 0 };
411
+ messages.sort((a, b) => a.deliveredAt - b.deliveredAt || a.id.localeCompare(b.id));
412
+ return { artifact: buildCommsThreadArtifact(messages), captured: sends.length, matched: messages.length };
413
+ }
303
414
  /**
304
415
  * Render the persona-facing inbox surface (host-side, typed — see comms-inbox.ts) and write the files
305
416
  * into the sandbox's served dir, so the catch serves a LIVE inbox the persona opens and clicks. Creates