humanish 0.17.0 → 0.18.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.
@@ -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.17.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
6
+ `0.18.0`; 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.
@@ -601,7 +601,11 @@ Core-owned fields:
601
601
  - `startedAt` / `completedAt` / `durationMs`
602
602
  - `status` / `completionReason` / `reason` (`completionReason` includes
603
603
  `step_failed`: a deterministic scripted step/expectation evaluated false —
604
- the subject failed the script while the harness executed faithfully)
604
+ the subject failed the script while the harness executed faithfully; and
605
+ `budget_reached`: an open-ended watch session that hit the wall-clock time
606
+ budget AFTER productive activity — status `passed`, a NON-FAILURE completion,
607
+ distinct from `timed_out`, which stays reserved for a zero-progress deadline
608
+ hit and remains a failure)
605
609
  - `ids`, `counts`, `items[]`, optional `tokenUsage`, `capabilities`
606
610
 
607
611
  Unexpected actor-loop diagnostics live inside `items[]` as
@@ -652,19 +656,24 @@ inside run bundles (per-stream transport and status) and lab execution config
652
656
 
653
657
  `humanish serve` reports `humanish.serve-result.v1`. The exported `ServeResult`
654
658
  type and `SERVE_SCHEMA` constant in `src/observer-serve.ts` are authoritative:
655
- mode (`loopback | capability-link | share-safe-open`), the loopback host/port,
656
- capability/public URLs, runs listed, computed warnings, and the
657
- `ServeErrorCode` union. Capability URLs embed a live secret; they belong on the
658
- operator's terminal and never in a persisted or committed artifact.
659
-
660
- Reserved: `/_humanish/api/*` is the serve control-plane namespace. In v1 any
661
- request under it that clears the auth gate answers `501` with error code
662
- `HUMANISH_SERVE_CONTROL_PLANE_DISABLED`; under `--expose --auth link` a
663
- session-less request answers the uniform `401` first. The typed `ServeControlPlane`
664
- parameter exists in the handler options and is always `undefined` in v1; no
665
- code dispatches into it yet. Do not build against the namespace; the
666
- reservation guarantees only that no run artifact or observer asset will ever be
667
- served under it. See
659
+ mode (`loopback | exposed | share-safe-open`), the loopback host/port,
660
+ `publicUrl`, the `tunnel` provider/url, an `oauth` echo (`provider`,
661
+ `allowEmails`, `allowDomains` operator-supplied allow rules, public-safe to
662
+ echo to the operator's own stdout, never persisted into any bundle), runs
663
+ listed, computed warnings, and the `ServeErrorCode` union. Exposure auth is
664
+ tunnel-edge only as of 0.18.0 there are no `capabilityUrl`/`publicCapabilityUrl`
665
+ /`ttlMinutes` fields, no `--auth`/`--ttl` flags, and no `capability-link` mode
666
+ (the in-process `observer-auth.ts` capability-link was removed as a pre-1.0
667
+ breaking change).
668
+
669
+ Reserved: `/_humanish/api/*` is the serve control-plane namespace. Any request
670
+ under it answers `501` with error code `HUMANISH_SERVE_CONTROL_PLANE_DISABLED`.
671
+ Because the in-process auth gate is gone, a request that clears the edge (or a
672
+ loopback caller) reaches the `501` directly — there is no `401`-first anymore.
673
+ The typed `ServeControlPlane` parameter exists in the handler options and is
674
+ always `undefined` in v1; no code dispatches into it yet. Do not build against
675
+ the namespace; the reservation guarantees only that no run artifact or observer
676
+ asset will ever be served under it. See
668
677
  [`docs/architecture/serve.md`](../architecture/serve.md) for the v2 seam
669
678
  contract.
670
679
 
@@ -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.17.0`)
19
+ ## Current Program Truth (source `0.18.0`)
20
20
 
21
21
  The package source and repository implementation in this tree agree on these
22
22
  points:
@@ -74,7 +74,8 @@ silently drifting from one is not.
74
74
  | Single lane | Cost + evidence simplicity | Declared fan-out where the backend supports it — `actors[0].count: N` (homogeneous), explicit `actors[0].lanes[]` (differentiated persona/device/instruction), or compact `actors[0].roster[]` groups that normalize into lanes on the computer-use E2B route (per-lane worlds, cap 16; `execution.concurrency` bounds concurrent paid lanes) |
75
75
  | Stock `desktop` template | The stock E2B desktop image is right for most subjects; absent `execution.desktop.template` keeps `Sandbox.create(opts)` byte-stable | `execution.desktop.template` names a custom E2B desktop image (any name/id, no allowlist) for a subject needing baked-in runtimes the stock image lacks (e.g. node/bun/a local Postgres) — threaded to `Sandbox.create(template, opts)` on every desktop-creating route and recorded in the bundle as `desktopTemplate` (public-safe) |
76
76
  | Desktop default URL opener | Preserve the route/image's historical browser/default opener behavior when unset | `execution.desktop.browser: chrome | chromium | firefox` makes hosted CUA/shared-world browser choice explicit, fail-closed, and recorded as `desktopBrowser` in the run bundle |
77
- | Loopback-only observer serving | Run bundles are local by default (public-safety plank 3); the serve surface binds `127.0.0.1` unconditionally, and exposing the library is publishing-adjacent, so it carries declared friction | `humanish serve --expose` (secret capability link + a declared public origin via `--tunnel`/`--public-url`) or `--expose --safe --auth none` (open mode, gated on verify-`share_ready` runs only); both print computed warnings naming exactly what became reachable |
77
+ | Loopback-only observer serving | Run bundles are local by default (public-safety plank 3); the serve/watch surfaces bind `127.0.0.1` unconditionally, and exposing them is publishing-adjacent, so exposure is fail-closed and carries declared friction | `humanish serve --expose` behind TUNNEL-EDGE auth ngrok `--oauth google` (`--allow-email`/`--allow-domain` allow rules) or an operator `--public-url` you secure — OR `--expose --safe` (open, gated on verify-`share_ready` runs only). `watch --expose` streams a live run behind edge auth (a live run is never `share_ready`, so `--safe` alone is refused). humanish carries no in-process auth; both print computed warnings naming exactly what became reachable |
78
+ | Generous wall-clock safety cap | `execution.timeoutMs` bounds runaway spend, not a goal; ~30 min is reasonable for open-ended "watch it play" | A session that reaches the cap AFTER productive activity is recorded as `budget_reached` (status `passed`, a NON-FAILURE completion, exit `0`), distinct from a zero-progress `timed_out` (a failure, exit `2`). Goal-directed labs set a tight `timeoutMs` and read `completionReason` to confirm the goal, not the cap |
78
79
 
79
80
  ## The placement rule (worked example)
80
81
 
@@ -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.17.0` (2026-08-02). The containment boundary introduced in
5
+ Package/source version in this tree: `0.18.0` (2026-08-02). 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Open-source-safe CLI for persona simulation, observer review, and public-safe feedback drafts.",
5
5
  "author": "Daniel G Wilson <daniel@danielgwilson.com>",
6
6
  "keywords": [
@@ -1,21 +0,0 @@
1
- export declare const SERVE_COOKIE_NAME = "humanish_serve";
2
- export interface ServeSessionStore {
3
- mint(): {
4
- cookieValue: string;
5
- };
6
- validate(cookieHeader: string | undefined): boolean;
7
- revokeAll(): void;
8
- sessionCount(): number;
9
- }
10
- export declare function mintServeToken(): string;
11
- export declare function sha256Digest(value: string): Buffer;
12
- export declare function verifyTokenDigest(candidate: string, digest: Buffer): boolean;
13
- export declare function createServeSessionStore(options: {
14
- ttlMs: number;
15
- now?: () => number;
16
- }): ServeSessionStore;
17
- export declare function parseCookies(header: string | undefined): Record<string, string>;
18
- export declare function buildSessionCookie(cookieValue: string, options: {
19
- ttlSeconds: number;
20
- secure: boolean;
21
- }): string;
@@ -1,92 +0,0 @@
1
- import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
2
- export const SERVE_COOKIE_NAME = "humanish_serve";
3
- export function mintServeToken() {
4
- return randomBytes(32).toString("base64url");
5
- }
6
- export function sha256Digest(value) {
7
- return createHash("sha256").update(value, "utf8").digest();
8
- }
9
- // Hash-first comparison: both sides become fixed-length digests before
10
- // timingSafeEqual, so an attacker-controlled candidate can neither throw on
11
- // length mismatch nor learn the token length.
12
- export function verifyTokenDigest(candidate, digest) {
13
- return timingSafeEqual(sha256Digest(candidate), digest);
14
- }
15
- export function createServeSessionStore(options) {
16
- const now = options.now ?? (() => Date.now());
17
- const sessions = new Map();
18
- return {
19
- mint() {
20
- // Sweep expired records on each mint so the map cannot grow without bound
21
- // when many links are tapped: without this, a record is only pruned when
22
- // its own cookie is presented again, which an abandoned session never is.
23
- const currentTime = now();
24
- for (const [digest, record] of sessions) {
25
- if (record.expiresAt <= currentTime) {
26
- sessions.delete(digest);
27
- }
28
- }
29
- const cookieValue = randomBytes(32).toString("base64url");
30
- sessions.set(sha256Digest(cookieValue).toString("hex"), {
31
- expiresAt: currentTime + options.ttlMs,
32
- scope: "viewer"
33
- });
34
- return { cookieValue };
35
- },
36
- validate(cookieHeader) {
37
- const cookieValue = parseCookies(cookieHeader)[SERVE_COOKIE_NAME];
38
- if (!cookieValue) {
39
- return false;
40
- }
41
- const record = sessions.get(sha256Digest(cookieValue).toString("hex"));
42
- if (!record || record.scope !== "viewer") {
43
- return false;
44
- }
45
- if (record.expiresAt <= now()) {
46
- sessions.delete(sha256Digest(cookieValue).toString("hex"));
47
- return false;
48
- }
49
- return true;
50
- },
51
- revokeAll() {
52
- sessions.clear();
53
- },
54
- sessionCount() {
55
- return sessions.size;
56
- }
57
- };
58
- }
59
- export function parseCookies(header) {
60
- if (!header) {
61
- return {};
62
- }
63
- const cookies = {};
64
- for (const part of header.split(";")) {
65
- const separator = part.indexOf("=");
66
- if (separator <= 0) {
67
- continue;
68
- }
69
- const name = part.slice(0, separator).trim();
70
- const value = part.slice(separator + 1).trim();
71
- if (name) {
72
- cookies[name] = value;
73
- }
74
- }
75
- return cookies;
76
- }
77
- export function buildSessionCookie(cookieValue, options) {
78
- const attributes = [
79
- `${SERVE_COOKIE_NAME}=${cookieValue}`,
80
- "Path=/",
81
- "HttpOnly",
82
- "SameSite=Lax",
83
- `Max-Age=${options.ttlSeconds}`
84
- ];
85
- if (options.secure) {
86
- attributes.push("Secure");
87
- }
88
- // Deliberately no Domain attribute: a host-only cookie on the public origin
89
- // can never leak to sibling subdomains of a shared tunnel domain.
90
- return attributes.join("; ");
91
- }
92
- //# sourceMappingURL=observer-auth.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"observer-auth.js","sourceRoot":"","sources":["../src/observer-auth.ts"],"names":[],"mappings":"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"}