humanish 0.17.0 → 0.19.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.
- package/README.md +53 -21
- package/dist/actor-contract.d.ts +11 -1
- package/dist/actor-contract.js.map +1 -1
- package/dist/computer-use-actor.d.ts +4 -0
- package/dist/computer-use-actor.js +3 -1
- package/dist/computer-use-actor.js.map +1 -1
- package/dist/computer-use.d.ts +16 -0
- package/dist/computer-use.js +59 -6
- package/dist/computer-use.js.map +1 -1
- package/dist/concurrent-shared-world-lab.js +1 -0
- package/dist/concurrent-shared-world-lab.js.map +1 -1
- package/dist/cua-actor-lab.d.ts +33 -3
- package/dist/cua-actor-lab.js +169 -2
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lab-config.d.ts +12 -0
- package/dist/lab-config.js +12 -0
- package/dist/lab-config.js.map +1 -1
- package/dist/observer-assets.js +37 -3
- package/dist/observer-assets.js.map +1 -1
- package/dist/observer-data.d.ts +7 -1
- package/dist/observer-data.js +1 -0
- package/dist/observer-data.js.map +1 -1
- package/dist/observer-library.d.ts +5 -1
- package/dist/observer-library.js +9 -7
- package/dist/observer-library.js.map +1 -1
- package/dist/observer-serve.d.ts +11 -23
- package/dist/observer-serve.js +12 -92
- package/dist/observer-serve.js.map +1 -1
- package/dist/observer.d.ts +6 -0
- package/dist/observer.js +52 -1
- package/dist/observer.js.map +1 -1
- package/dist/pricing.d.ts +78 -0
- package/dist/pricing.js +100 -0
- package/dist/pricing.js.map +1 -0
- package/dist/program.js +294 -122
- package/dist/program.js.map +1 -1
- package/dist/run.d.ts +54 -0
- package/dist/run.js +86 -0
- package/dist/run.js.map +1 -1
- package/dist/serve-exposure.d.ts +62 -0
- package/dist/serve-exposure.js +129 -0
- package/dist/serve-exposure.js.map +1 -0
- package/dist/serve-http.d.ts +8 -0
- package/dist/serve-http.js +37 -0
- package/dist/serve-http.js.map +1 -0
- package/dist/serve-tunnel.d.ts +6 -2
- package/dist/serve-tunnel.js +9 -0
- package/dist/serve-tunnel.js.map +1 -1
- package/docs/architecture/actor-contract.md +41 -3
- package/docs/architecture/observer.md +30 -0
- package/docs/architecture/serve.md +138 -82
- package/docs/contracts/run-bundle.md +23 -0
- package/docs/contracts/schemas.md +111 -15
- package/docs/goals/current.md +1 -1
- package/docs/principles/invariants-and-defaults.md +2 -1
- package/docs/ramp/README.md +1 -1
- package/package.json +1 -1
- package/dist/observer-auth.d.ts +0 -21
- package/dist/observer-auth.js +0 -92
- package/dist/observer-auth.js.map +0 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ServeMode } from "./serve-http.js";
|
|
2
|
+
import { type ServeTunnel, type StartNgrokTunnelOptions } from "./serve-tunnel.js";
|
|
3
|
+
export type ExposureSurface = "serve" | "watch";
|
|
4
|
+
export type ExposureErrorCode = "HUMANISH_SERVE_ALLOW_REQUIRES_OAUTH" | "HUMANISH_SERVE_OAUTH_REQUIRES_TUNNEL" | "HUMANISH_SERVE_OPTION_CONFLICT" | "HUMANISH_SERVE_TUNNEL_REQUIRES_EXPOSE" | "HUMANISH_SERVE_EXPOSE_REQUIRES_EDGE_AUTH_OR_SAFE" | "HUMANISH_SERVE_EXPOSE_REQUIRES_ORIGIN" | "HUMANISH_WATCH_ALLOW_REQUIRES_OAUTH" | "HUMANISH_WATCH_OAUTH_REQUIRES_TUNNEL" | "HUMANISH_WATCH_OPTION_CONFLICT" | "HUMANISH_WATCH_TUNNEL_REQUIRES_EXPOSE" | "HUMANISH_WATCH_EXPOSE_REQUIRES_EDGE_AUTH" | "HUMANISH_WATCH_EXPOSE_REQUIRES_LIVE_FOLLOW" | "HUMANISH_WATCH_SAFE_NOT_APPLICABLE";
|
|
5
|
+
export interface ExposureRequest {
|
|
6
|
+
expose: boolean;
|
|
7
|
+
tunnel?: "ngrok" | undefined;
|
|
8
|
+
tunnelDomain?: string | undefined;
|
|
9
|
+
oauth?: "google" | undefined;
|
|
10
|
+
allowEmails: string[];
|
|
11
|
+
allowDomains: string[];
|
|
12
|
+
publicUrl?: string | undefined;
|
|
13
|
+
safe: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface WatchLiveContext {
|
|
16
|
+
dryRun: boolean;
|
|
17
|
+
detach: boolean;
|
|
18
|
+
json: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ExposurePlan {
|
|
21
|
+
exposed: boolean;
|
|
22
|
+
edgeAuthed: boolean;
|
|
23
|
+
mode: ServeMode;
|
|
24
|
+
safe: boolean;
|
|
25
|
+
tunnel?: "ngrok";
|
|
26
|
+
tunnelDomain?: string;
|
|
27
|
+
oauth?: {
|
|
28
|
+
provider: "google";
|
|
29
|
+
allowEmails: string[];
|
|
30
|
+
allowDomains: string[];
|
|
31
|
+
};
|
|
32
|
+
publicOrigin?: {
|
|
33
|
+
origin: string;
|
|
34
|
+
host: string;
|
|
35
|
+
scheme: "http" | "https";
|
|
36
|
+
};
|
|
37
|
+
warnings: string[];
|
|
38
|
+
}
|
|
39
|
+
export type ExposureValidation = {
|
|
40
|
+
ok: true;
|
|
41
|
+
plan: ExposurePlan;
|
|
42
|
+
} | {
|
|
43
|
+
ok: false;
|
|
44
|
+
error: {
|
|
45
|
+
code: ExposureErrorCode;
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export declare function validateExposure(surface: ExposureSurface, request: ExposureRequest, live?: WatchLiveContext): ExposureValidation;
|
|
50
|
+
export interface ExposableServer {
|
|
51
|
+
readonly port: number;
|
|
52
|
+
readonly url: string;
|
|
53
|
+
addPublicOrigin(origin: string): void;
|
|
54
|
+
}
|
|
55
|
+
export interface ExposureResult {
|
|
56
|
+
tunnel?: ServeTunnel;
|
|
57
|
+
publicUrl?: string;
|
|
58
|
+
warnings: string[];
|
|
59
|
+
}
|
|
60
|
+
export declare function startExposedObserver(server: ExposableServer, plan: ExposurePlan, deps?: {
|
|
61
|
+
startTunnel?: (options: StartNgrokTunnelOptions) => Promise<ServeTunnel>;
|
|
62
|
+
}): Promise<ExposureResult>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Shared fail-closed exposure validation + tunnel orchestration for BOTH `serve` (a library of
|
|
2
|
+
// finished runs) and `watch` (one live run). Exposure auth is TUNNEL-EDGE only: humanish carries no
|
|
3
|
+
// in-process auth. Exposure is admitted only behind edge auth (ngrok --oauth google, or an operator
|
|
4
|
+
// --public-url they secure) OR, for serve, behind --safe (share_ready runs only). A live watch run
|
|
5
|
+
// is never share_ready (raw, unverified screenshots), so watch --expose ALWAYS requires edge auth.
|
|
6
|
+
//
|
|
7
|
+
// This module is pure with respect to the fail-closed matrix (validateExposure) and thin over the
|
|
8
|
+
// tunnel launcher (startExposedObserver), so the CLI just maps flags in and results out.
|
|
9
|
+
import { parsePublicOrigin } from "./serve-http.js";
|
|
10
|
+
import { startNgrokTunnel } from "./serve-tunnel.js";
|
|
11
|
+
function code(surface, suffix) {
|
|
12
|
+
return `HUMANISH_${surface.toUpperCase()}_${suffix}`;
|
|
13
|
+
}
|
|
14
|
+
export function validateExposure(surface, request, live) {
|
|
15
|
+
const fail = (suffix, message) => ({
|
|
16
|
+
ok: false,
|
|
17
|
+
error: { code: code(surface, suffix), message }
|
|
18
|
+
});
|
|
19
|
+
// Structural guards (both surfaces), in the fail-closed order documented in the matrix. These run
|
|
20
|
+
// before any bind/spawn so a mis-configured exposure aborts before sandbox/provider spend.
|
|
21
|
+
if ((request.allowEmails.length > 0 || request.allowDomains.length > 0) && !request.oauth) {
|
|
22
|
+
return fail("ALLOW_REQUIRES_OAUTH", "--allow-email/--allow-domain configure the ngrok edge OAuth allow-list; they require --oauth google.");
|
|
23
|
+
}
|
|
24
|
+
if (request.oauth && !request.tunnel) {
|
|
25
|
+
return fail("OAUTH_REQUIRES_TUNNEL", "--oauth turns on edge OAuth on the ngrok tunnel; it requires --tunnel ngrok (a --public-url operator brings their own edge auth).");
|
|
26
|
+
}
|
|
27
|
+
if (request.tunnel && request.publicUrl !== undefined) {
|
|
28
|
+
return fail("OPTION_CONFLICT", "Use either --tunnel or --public-url as the public origin, not both.");
|
|
29
|
+
}
|
|
30
|
+
if (request.tunnelDomain !== undefined && !request.tunnel) {
|
|
31
|
+
return fail("OPTION_CONFLICT", "--tunnel-domain requires --tunnel.");
|
|
32
|
+
}
|
|
33
|
+
const publicOrigin = request.publicUrl !== undefined ? parsePublicOrigin(request.publicUrl) : null;
|
|
34
|
+
if (request.publicUrl !== undefined && !publicOrigin) {
|
|
35
|
+
return fail("OPTION_CONFLICT", "--public-url must be an http(s) origin like https://observer.example.com.");
|
|
36
|
+
}
|
|
37
|
+
if (!request.expose) {
|
|
38
|
+
// Exposure flags without --expose are refused (no silent wide-open). --safe is orthogonal and
|
|
39
|
+
// stays valid without --expose (a loopback share_ready filter).
|
|
40
|
+
if (request.tunnel) {
|
|
41
|
+
return fail("TUNNEL_REQUIRES_EXPOSE", "--tunnel exposes the surface; declare that intent with --expose.");
|
|
42
|
+
}
|
|
43
|
+
if (request.publicUrl !== undefined) {
|
|
44
|
+
return fail("OPTION_CONFLICT", "--public-url only applies with --expose.");
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
plan: { exposed: false, edgeAuthed: false, mode: "loopback", safe: request.safe, warnings: [] }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const oauth = request.oauth
|
|
52
|
+
? { provider: "google", allowEmails: request.allowEmails, allowDomains: request.allowDomains }
|
|
53
|
+
: undefined;
|
|
54
|
+
const edgeAuthed = Boolean(request.oauth) || Boolean(publicOrigin);
|
|
55
|
+
const warnings = [];
|
|
56
|
+
if (surface === "watch") {
|
|
57
|
+
// A live, in-progress run is never share_ready (raw screenshots, unverified), so --safe would
|
|
58
|
+
// admit nothing and expose nothing: watch --expose ALWAYS requires edge auth, and the live-follow
|
|
59
|
+
// preconditions are checked first so `--dry-run`/`--detach`/`--json` fail with the clearer reason.
|
|
60
|
+
if (live && (live.dryRun || live.detach || live.json)) {
|
|
61
|
+
return fail("EXPOSE_REQUIRES_LIVE_FOLLOW", "watch --expose streams a live desktop over an attached follow channel; it cannot combine with --dry-run, --detach, or --json.");
|
|
62
|
+
}
|
|
63
|
+
// --safe is a share_ready LIBRARY filter for `serve`; watch streams a single live run that is
|
|
64
|
+
// never share_ready, so --safe would silently do nothing here. Reject it rather than ignore it.
|
|
65
|
+
if (request.safe) {
|
|
66
|
+
return fail("SAFE_NOT_APPLICABLE", "watch streams a single live run that is never share_ready; --safe (a share_ready library filter) applies to `serve`, not `watch`. Restrict viewers with edge auth: --allow-email / --allow-domain.");
|
|
67
|
+
}
|
|
68
|
+
if (!edgeAuthed) {
|
|
69
|
+
return fail("EXPOSE_REQUIRES_EDGE_AUTH", "watch --expose serves a live run that is never share_ready, so --safe cannot gate it; require edge auth: --tunnel ngrok --oauth google, or an operator-secured --public-url.");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// serve: --expose must ALWAYS resolve to a reachable public origin (a tunnel or a --public-url),
|
|
74
|
+
// even under --safe. Without one the exposed server is an unreachable loopback no-op, so fail
|
|
75
|
+
// closed before the origin-less bind. When an origin IS present, keep the edge-auth-OR-safe gate.
|
|
76
|
+
if (!request.tunnel && !publicOrigin) {
|
|
77
|
+
return fail("EXPOSE_REQUIRES_ORIGIN", "--expose needs a declared public origin: pass --tunnel ngrok or --public-url <origin>.");
|
|
78
|
+
}
|
|
79
|
+
if (!edgeAuthed && !request.safe) {
|
|
80
|
+
return fail("EXPOSE_REQUIRES_EDGE_AUTH_OR_SAFE", "--expose opens a public URL to local run bundles; require edge auth (--oauth google with --tunnel, or a --public-url you secure) OR --safe (share_ready runs only).");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (request.oauth && request.allowEmails.length === 0 && request.allowDomains.length === 0) {
|
|
84
|
+
warnings.push("ngrok --oauth google with NO --allow-email/--allow-domain lets ANY Google account that reaches the URL in; add at least one allow rule to restrict who can watch.");
|
|
85
|
+
}
|
|
86
|
+
const mode = edgeAuthed ? "exposed" : "share-safe-open";
|
|
87
|
+
return {
|
|
88
|
+
ok: true,
|
|
89
|
+
plan: {
|
|
90
|
+
exposed: true,
|
|
91
|
+
edgeAuthed,
|
|
92
|
+
mode,
|
|
93
|
+
safe: request.safe,
|
|
94
|
+
...(request.tunnel ? { tunnel: request.tunnel } : {}),
|
|
95
|
+
...(request.tunnelDomain ? { tunnelDomain: request.tunnelDomain } : {}),
|
|
96
|
+
...(oauth ? { oauth } : {}),
|
|
97
|
+
...(publicOrigin ? { publicOrigin } : {}),
|
|
98
|
+
warnings
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Orchestrate the edge in front of an already-bound loopback server: spawn the ngrok tunnel (with
|
|
103
|
+
// oauth/allow args) or declare the operator's --public-url, then extend the server's Host allowlist.
|
|
104
|
+
// May throw a ServeTunnelError (ngrok missing/failed); callers close the loopback server on throw.
|
|
105
|
+
export async function startExposedObserver(server, plan, deps = {}) {
|
|
106
|
+
const warnings = [...plan.warnings];
|
|
107
|
+
if (plan.tunnel === "ngrok") {
|
|
108
|
+
const startTunnel = deps.startTunnel ?? startNgrokTunnel;
|
|
109
|
+
const tunnel = await startTunnel({
|
|
110
|
+
port: server.port,
|
|
111
|
+
...(plan.tunnelDomain ? { domain: plan.tunnelDomain } : {}),
|
|
112
|
+
...(plan.oauth
|
|
113
|
+
? {
|
|
114
|
+
oauthProvider: plan.oauth.provider,
|
|
115
|
+
oauthAllowEmails: plan.oauth.allowEmails,
|
|
116
|
+
oauthAllowDomains: plan.oauth.allowDomains
|
|
117
|
+
}
|
|
118
|
+
: {})
|
|
119
|
+
});
|
|
120
|
+
server.addPublicOrigin(tunnel.url);
|
|
121
|
+
return { tunnel, publicUrl: tunnel.url.replace(/\/$/, ""), warnings };
|
|
122
|
+
}
|
|
123
|
+
if (plan.publicOrigin) {
|
|
124
|
+
server.addPublicOrigin(plan.publicOrigin.origin);
|
|
125
|
+
return { publicUrl: plan.publicOrigin.origin, warnings };
|
|
126
|
+
}
|
|
127
|
+
return { warnings };
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=serve-exposure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve-exposure.js","sourceRoot":"","sources":["../src/serve-exposure.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ServeMode = "loopback" | "exposed" | "share-safe-open";
|
|
2
|
+
export declare function buildServeSecurityHeaders(): Record<string, string>;
|
|
3
|
+
export declare function hostAllowed(hostHeader: string | undefined, allowlist: ReadonlySet<string>): boolean;
|
|
4
|
+
export declare function parsePublicOrigin(value: string): {
|
|
5
|
+
origin: string;
|
|
6
|
+
host: string;
|
|
7
|
+
scheme: "http" | "https";
|
|
8
|
+
} | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Shared HTTP hardening primitives for the serve surfaces. Extracted here so BOTH the live
|
|
2
|
+
// Observer server (src/observer.ts, exposed mode) and the run-library server
|
|
3
|
+
// (src/observer-serve.ts) can enforce the identical Host allowlist + security-header posture
|
|
4
|
+
// without a module cycle. This file imports nothing from the serve modules.
|
|
5
|
+
export function buildServeSecurityHeaders() {
|
|
6
|
+
return {
|
|
7
|
+
"cache-control": "no-store",
|
|
8
|
+
"referrer-policy": "no-referrer",
|
|
9
|
+
"x-content-type-options": "nosniff",
|
|
10
|
+
"x-frame-options": "DENY",
|
|
11
|
+
"x-robots-tag": "noindex, nofollow"
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function hostAllowed(hostHeader, allowlist) {
|
|
15
|
+
return typeof hostHeader === "string" && allowlist.has(hostHeader.trim().toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
export function parsePublicOrigin(value) {
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = new URL(value);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if ((parsed.pathname !== "/" && parsed.pathname !== "") || parsed.search || parsed.hash || !parsed.host) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
origin: parsed.origin,
|
|
33
|
+
host: parsed.host.toLowerCase(),
|
|
34
|
+
scheme: parsed.protocol === "https:" ? "https" : "http"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=serve-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve-http.js","sourceRoot":"","sources":["../src/serve-http.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,6EAA6E;AAC7E,6FAA6F;AAC7F,4EAA4E;AAS5E,MAAM,UAAU,yBAAyB;IACvC,OAAO;QACL,eAAe,EAAE,UAAU;QAC3B,iBAAiB,EAAE,aAAa;QAChC,wBAAwB,EAAE,SAAS;QACnC,iBAAiB,EAAE,MAAM;QACzB,cAAc,EAAE,mBAAmB;KACpC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,UAA8B,EAAE,SAA8B;IACxF,OAAO,OAAO,UAAU,KAAK,QAAQ,IAAI,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,GAAG,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACxG,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE;QAC/B,MAAM,EAAE,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;KACxD,CAAC;AACJ,CAAC"}
|
package/dist/serve-tunnel.d.ts
CHANGED
|
@@ -8,9 +8,13 @@ export interface ServeTunnel {
|
|
|
8
8
|
url: string;
|
|
9
9
|
close(): Promise<void>;
|
|
10
10
|
}
|
|
11
|
-
export
|
|
11
|
+
export interface StartNgrokTunnelOptions {
|
|
12
12
|
port: number;
|
|
13
13
|
domain?: string;
|
|
14
|
+
oauthProvider?: "google";
|
|
15
|
+
oauthAllowEmails?: string[];
|
|
16
|
+
oauthAllowDomains?: string[];
|
|
14
17
|
timeoutMs?: number;
|
|
15
18
|
spawnImpl?: typeof spawn;
|
|
16
|
-
}
|
|
19
|
+
}
|
|
20
|
+
export declare function startNgrokTunnel(options: StartNgrokTunnelOptions): Promise<ServeTunnel>;
|
package/dist/serve-tunnel.js
CHANGED
|
@@ -10,6 +10,14 @@ export class ServeTunnelError extends Error {
|
|
|
10
10
|
export async function startNgrokTunnel(options) {
|
|
11
11
|
const spawnImpl = options.spawnImpl ?? spawn;
|
|
12
12
|
const timeoutMs = options.timeoutMs ?? 15_000;
|
|
13
|
+
const oauthArgs = options.oauthProvider
|
|
14
|
+
? [
|
|
15
|
+
"--oauth",
|
|
16
|
+
options.oauthProvider,
|
|
17
|
+
...(options.oauthAllowEmails ?? []).flatMap((email) => ["--oauth-allow-email", email]),
|
|
18
|
+
...(options.oauthAllowDomains ?? []).flatMap((domain) => ["--oauth-allow-domain", domain])
|
|
19
|
+
]
|
|
20
|
+
: [];
|
|
13
21
|
const args = [
|
|
14
22
|
"http",
|
|
15
23
|
"--log",
|
|
@@ -17,6 +25,7 @@ export async function startNgrokTunnel(options) {
|
|
|
17
25
|
"--log-format",
|
|
18
26
|
"json",
|
|
19
27
|
...(options.domain ? ["--url", options.domain] : []),
|
|
28
|
+
...oauthArgs,
|
|
20
29
|
String(options.port)
|
|
21
30
|
];
|
|
22
31
|
const child = spawnImpl("ngrok", args, { stdio: ["ignore", "pipe", "ignore"] });
|
package/dist/serve-tunnel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve-tunnel.js","sourceRoot":"","sources":["../src/serve-tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAO3C,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,IAAI,CAAuB;IAEpC,YAAY,IAA0B,EAAE,OAAe;QACrD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"serve-tunnel.js","sourceRoot":"","sources":["../src/serve-tunnel.ts"],"names":[],"mappings":"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"}
|
|
@@ -111,7 +111,10 @@ export type ActorCompletionReason =
|
|
|
111
111
|
| "turn_completed" // harness saw an explicit done signal, no predicate
|
|
112
112
|
| "gave_up" // persona abandoned in character: friction exceeded its tolerance
|
|
113
113
|
| "blocked_approval" // an action was auto-declined and the actor could not proceed
|
|
114
|
-
| "timed_out"
|
|
114
|
+
| "timed_out" // wall-clock deadline hit with ZERO material progress → still a FAILURE
|
|
115
|
+
| "budget_reached" // wall-clock time budget hit AFTER productive activity (>=1 material
|
|
116
|
+
// action) → ActorStatus "passed", a NON-FAILURE open-ended-watch
|
|
117
|
+
// completion; distinct from goal_satisfied (no goal was claimed)
|
|
115
118
|
| "actor_error"
|
|
116
119
|
| "step_failed" // a deterministic scripted step/expectation evaluated false: the
|
|
117
120
|
// SUBJECT failed the script; the harness executed faithfully
|
|
@@ -162,6 +165,7 @@ export interface ActorTrace {
|
|
|
162
165
|
counts: Record<string, number>;
|
|
163
166
|
items: ActorTraceItem[];
|
|
164
167
|
tokenUsage?: { input?: number; output?: number; total?: number; costUsd?: number };
|
|
168
|
+
estimatedCost?: ActorEstimatedCost; // humanish.actor-estimated-cost.v1 (additive)
|
|
165
169
|
capabilities: ActorCapabilities;
|
|
166
170
|
}
|
|
167
171
|
|
|
@@ -229,6 +233,17 @@ export interface Actor {
|
|
|
229
233
|
target URLs, or unredacted provider payloads in the trace.
|
|
230
234
|
- **Capabilities.** Declare them honestly; the registry uses them to refuse
|
|
231
235
|
unsuitable dispatch.
|
|
236
|
+
- **Cost (estimate vs. charge).** `tokenUsage.costUsd` stays RESERVED for a
|
|
237
|
+
real, provider-returned charge (the codex/agent-SDK path) — a bare `costUsd`
|
|
238
|
+
always means "the provider billed this". The optional `estimatedCost`
|
|
239
|
+
(`humanish.actor-estimated-cost.v1`) is a SEPARATE, differently-named field: a
|
|
240
|
+
token-derived rate-table multiply from the operator-editable `src/pricing.ts`,
|
|
241
|
+
labeled honestly as an estimate and projected up into `RunBundle.cost` (see
|
|
242
|
+
[`../contracts/schemas.md`](../contracts/schemas.md) → Run Cost Summary And
|
|
243
|
+
Estimated Actor Cost). The CUA lab computes and attaches `estimatedCost` at the
|
|
244
|
+
lab boundary before persisting the trace, so the pure computer-use loop never
|
|
245
|
+
depends on the pricing table. An unknown model yields
|
|
246
|
+
`estimatedCostUsd: null` + a `reason`, never a guessed charge.
|
|
232
247
|
|
|
233
248
|
## The scripted-browser lane (shipped)
|
|
234
249
|
|
|
@@ -245,8 +260,31 @@ Completion semantics: `goal_satisfied` means the scenario's `expect` blocks —
|
|
|
245
260
|
predicate — all held ("the app still affords this exact journey", nothing about user
|
|
246
261
|
behavior); `step_failed` means a deterministic step or expectation evaluated false (the
|
|
247
262
|
subject failed the script; the harness ran faithfully); `timed_out` is the journey wall-clock
|
|
248
|
-
budget; `harness_error` is a browser that could
|
|
249
|
-
are unreachable — no persona patience, no
|
|
263
|
+
budget hit with NO material progress (still a failure); `harness_error` is a browser that could
|
|
264
|
+
not launch. `gave_up` and `blocked_approval` are unreachable — no persona patience, no
|
|
265
|
+
approvals exist on a deterministic replay.
|
|
266
|
+
|
|
267
|
+
### The time budget vs. a stuck timeout (`budget_reached`)
|
|
268
|
+
|
|
269
|
+
`execution.timeoutMs` is a GENEROUS wall-clock SAFETY cap, not a goal. For an open-ended
|
|
270
|
+
"watch it play" session there is no success predicate — productive play IS the outcome — so the
|
|
271
|
+
computer-use loop distinguishes two ways to hit the cap:
|
|
272
|
+
|
|
273
|
+
- **`budget_reached`** — the deadline was reached AFTER at least one material (non-idle) action.
|
|
274
|
+
This maps to `ActorStatus: "passed"`: a non-failure completion, `laneOutcomeOk` returns true,
|
|
275
|
+
the verdict is `pass`, and the CLI exits `0`. It stays a distinct `completionReason` (never
|
|
276
|
+
`goal_satisfied`), and the trace `reason` says it reached the budget after productive activity,
|
|
277
|
+
so a reviewer of a strictly goal-directed lab sees it hit the cap rather than reaching a goal
|
|
278
|
+
(goal-directed labs should set a tight `timeoutMs`).
|
|
279
|
+
- **`timed_out`** — the deadline was reached with ZERO material actions (a hung provider, an
|
|
280
|
+
idle-only stall). This maps to `ActorStatus: "timed_out"`, `laneOutcomeOk` is false, the
|
|
281
|
+
verdict is `fail`, and the CLI exits `2`. "Made zero progress then timed out" stays a failure.
|
|
282
|
+
|
|
283
|
+
`ActorStatus` intentionally gains NO new member — the honest distinction lives in
|
|
284
|
+
`completionReason`/`reason` — which keeps the change from rippling through ~10 provider mappers.
|
|
285
|
+
`statusForCompletion` is an exhaustive switch with no default, so a new completion reason forces a
|
|
286
|
+
compile-time decision about its status. ~30 min (`1_800_000`) is a reasonable default for
|
|
287
|
+
open-ended watch; the persona still stops early on `goal_satisfied`/`gave_up`/`stopWhen`.
|
|
250
288
|
|
|
251
289
|
Actuation-vs-spend gate: on the scripted lab route `scenario.mode: live` is still required
|
|
252
290
|
even though provider spend is $0 by mechanism. The gate's justification there is ACTUATION,
|
|
@@ -78,6 +78,36 @@ LIBRARY surface — every run under `.humanish/runs/` — and never serves runti
|
|
|
78
78
|
stream URLs in any mode; remote viewers see persisted evidence only. See
|
|
79
79
|
[Serve: the run library surface](serve.md).
|
|
80
80
|
|
|
81
|
+
### Exposed hardening and `watch --expose`
|
|
82
|
+
|
|
83
|
+
The live `serveObserver` server binds `127.0.0.1` and, by default, is a
|
|
84
|
+
permissive local-dev server (no Host allowlist, no security headers). Under its
|
|
85
|
+
`exposed` option — set by `watch --expose` — it enforces the SAME
|
|
86
|
+
DNS-rebinding defense as the library surface: a strict Host allowlist (loopback
|
|
87
|
+
names at bind, extended by `addPublicOrigin(tunnel.url | public-url)`, `421
|
|
88
|
+
Misdirected Request` otherwise) and the shared `buildServeSecurityHeaders()` on
|
|
89
|
+
every response (both live in `src/serve-http.ts`, shared without a module cycle).
|
|
90
|
+
Loopback (non-exposed) behavior is byte-identical to before.
|
|
91
|
+
|
|
92
|
+
Exposed mode also SCOPES the surface to the attached live run (`result.run`): the
|
|
93
|
+
`/_humanish/history.json` index is filtered to that one run, and `/_humanish/runs/<id>/…`
|
|
94
|
+
404s byte-identically to a nonexistent run for any other id. A remote viewer who
|
|
95
|
+
clears the edge auth can therefore see only the run being watched — never enumerate
|
|
96
|
+
or fetch a prior run's raw, unverified evidence. Loopback keeps the full cross-run
|
|
97
|
+
library (history + any run by id) exactly as before.
|
|
98
|
+
|
|
99
|
+
`watch --expose` is the ONE surface that DELIBERATELY streams the live E2B
|
|
100
|
+
desktop to a remote viewer: the attached watch process genuinely holds the
|
|
101
|
+
runtime stream URLs (in the in-memory `WeakMap`, never persisted), and streaming
|
|
102
|
+
them is the whole point of watching from a phone. It is safe only because the
|
|
103
|
+
ngrok edge (Google OAuth + allow rules) or an operator `--public-url` edge
|
|
104
|
+
authenticates the viewer first — `watch --expose` therefore always requires edge
|
|
105
|
+
auth (a live run is never `share_ready`, so `--safe` alone cannot gate it). The
|
|
106
|
+
attached server comes up DURING the run and survives a `timed_out`/`failed` run
|
|
107
|
+
(serving is not gated on pass/fail), so a failed run's evidence stays inspectable
|
|
108
|
+
to Ctrl-C. `serve` still never injects stream URLs. See
|
|
109
|
+
[Serve: the run library surface](serve.md).
|
|
110
|
+
|
|
81
111
|
## UI Shape
|
|
82
112
|
|
|
83
113
|
The Observer shell has:
|