humanish 0.48.0 → 0.50.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.
Files changed (74) hide show
  1. package/README.md +65 -6
  2. package/dist/actor-contract.d.ts +14 -0
  3. package/dist/actor-contract.js.map +1 -1
  4. package/dist/computer-use-actor.d.ts +3 -7
  5. package/dist/computer-use.d.ts +4 -7
  6. package/dist/computer-use.js +39 -3
  7. package/dist/computer-use.js.map +1 -1
  8. package/dist/concurrent-shared-world-lab.d.ts +5 -0
  9. package/dist/concurrent-shared-world-lab.js +30 -1
  10. package/dist/concurrent-shared-world-lab.js.map +1 -1
  11. package/dist/cua-actor-lab.d.ts +7 -0
  12. package/dist/cua-actor-lab.js +50 -9
  13. package/dist/cua-actor-lab.js.map +1 -1
  14. package/dist/e2b-terminal-lab.d.ts +7 -0
  15. package/dist/e2b-terminal-lab.js +54 -0
  16. package/dist/e2b-terminal-lab.js.map +1 -1
  17. package/dist/init-templates.js +7 -2
  18. package/dist/init-templates.js.map +1 -1
  19. package/dist/key-resolution.d.ts +67 -0
  20. package/dist/key-resolution.js +373 -0
  21. package/dist/key-resolution.js.map +1 -0
  22. package/dist/lab-engine.d.ts +11 -0
  23. package/dist/lab-engine.js +18 -0
  24. package/dist/lab-engine.js.map +1 -1
  25. package/dist/openai-responses-cu.d.ts +1 -1
  26. package/dist/openai-responses-cu.js +8 -2
  27. package/dist/openai-responses-cu.js.map +1 -1
  28. package/dist/oss-lab.d.ts +3 -0
  29. package/dist/oss-lab.js.map +1 -1
  30. package/dist/oss-meta-lab.d.ts +3 -0
  31. package/dist/oss-meta-lab.js +13 -0
  32. package/dist/oss-meta-lab.js.map +1 -1
  33. package/dist/pricing.d.ts +20 -0
  34. package/dist/pricing.js +109 -17
  35. package/dist/pricing.js.map +1 -1
  36. package/dist/program.d.ts +18 -1
  37. package/dist/program.js +291 -17
  38. package/dist/program.js.map +1 -1
  39. package/dist/run-detail.d.ts +59 -0
  40. package/dist/run-detail.js +108 -0
  41. package/dist/run-detail.js.map +1 -0
  42. package/dist/run-index.d.ts +71 -0
  43. package/dist/run-index.js +206 -0
  44. package/dist/run-index.js.map +1 -0
  45. package/dist/run-paths.js +16 -1
  46. package/dist/run-paths.js.map +1 -1
  47. package/dist/run-projection.d.ts +182 -0
  48. package/dist/run-projection.js +349 -0
  49. package/dist/run-projection.js.map +1 -0
  50. package/dist/run-status.d.ts +139 -0
  51. package/dist/run-status.js +218 -0
  52. package/dist/run-status.js.map +1 -0
  53. package/dist/run.d.ts +15 -0
  54. package/dist/run.js +115 -6
  55. package/dist/run.js.map +1 -1
  56. package/dist/scripted-browser-lab.d.ts +5 -0
  57. package/dist/scripted-browser-lab.js +29 -1
  58. package/dist/scripted-browser-lab.js.map +1 -1
  59. package/dist/shared-world-lab.d.ts +5 -0
  60. package/dist/shared-world-lab.js +29 -1
  61. package/dist/shared-world-lab.js.map +1 -1
  62. package/dist/tui-app.js +402 -0
  63. package/dist/tui-contract.d.ts +73 -0
  64. package/dist/tui-contract.js +32 -0
  65. package/dist/tui-contract.js.map +1 -0
  66. package/dist/tui-launch.d.ts +48 -0
  67. package/dist/tui-launch.js +159 -0
  68. package/dist/tui-launch.js.map +1 -0
  69. package/docs/contracts/run-bundle.md +8 -0
  70. package/docs/contracts/schemas.md +85 -7
  71. package/docs/goals/current.md +16 -7
  72. package/docs/ramp/README.md +1 -1
  73. package/package.json +8 -5
  74. package/skills/humanish/SKILL.md +19 -0
@@ -0,0 +1,73 @@
1
+ import type { LabListResult } from "./labs.js";
2
+ import type { RunDetail } from "./run-detail.js";
3
+ import type { ReadRunIndexOptions, RunIndexResult } from "./run-index.js";
4
+ import type { LaunchRunOptions, LaunchRunResult } from "./tui-launch.js";
5
+ /** The humanish version string shown in the frame, so a screenshot in a bug report is datable. */
6
+ export interface TuiVersionInfo {
7
+ cli: string;
8
+ }
9
+ /**
10
+ * What the surface may do to the project. Deliberately a small, explicit list rather than a handle
11
+ * to the whole library: the set of verbs a stakeholder surface can perform should be readable in
12
+ * one place, and anything absent here is something the TUI simply cannot do.
13
+ */
14
+ export interface TuiCapabilities {
15
+ /** Read every run in the project, cheapest source first. */
16
+ readRunIndex(cwd: string, options?: ReadRunIndexOptions): Promise<RunIndexResult>;
17
+ /**
18
+ * The labs DECLARED in this project. Listed separately from run history because neither side is
19
+ * the whole truth: a fresh project has manifests and no runs, and a long-lived one has runs from
20
+ * manifests since renamed or deleted.
21
+ */
22
+ listLabs(cwd: string): Promise<LabListResult>;
23
+ /**
24
+ * Start a run and return once it is running. The run is DETACHED: it outlives this surface, so
25
+ * quitting the TUI — or losing the connection it runs over — does not kill a study that costs
26
+ * real money. The surface then follows it through `status.json` like any other reader.
27
+ */
28
+ startRun(options: Omit<LaunchRunOptions, "spawn" | "cliPath" | "now">): Promise<LaunchRunResult>;
29
+ /** The tail of a launch log: the only account of a run that died before writing evidence. */
30
+ readLaunchLog(logPath: string): Promise<string>;
31
+ /**
32
+ * Who is in ONE run and what they are thinking. Opens that run's bundle, which the index
33
+ * deliberately does not — affordable because it is asked only for the run being watched.
34
+ * `null` when the run has not written a bundle yet.
35
+ */
36
+ readRunDetail(cwd: string, runId: string): Promise<RunDetail | null>;
37
+ }
38
+ export interface TuiOptions {
39
+ /** The project the surface is reading. Already resolved by the CLI. */
40
+ cwd: string;
41
+ version: TuiVersionInfo;
42
+ capabilities: TuiCapabilities;
43
+ /**
44
+ * Terminal streams, injected so a test can drive the surface without a real TTY. The CLI passes
45
+ * the real ones; both are known to be TTYs by the time this is called, because the command
46
+ * refuses to start otherwise.
47
+ */
48
+ stdin: NodeJS.ReadStream;
49
+ stdout: NodeJS.WriteStream;
50
+ /** Test seam: render one frame and resolve, instead of waiting for the operator to quit. */
51
+ exitAfterFirstFrame?: boolean;
52
+ }
53
+ /**
54
+ * Start the surface. Resolves with the process exit code when the operator quits — the TUI owns the
55
+ * screen until then, so the CLI must not write to stdout while this is pending.
56
+ */
57
+ export type StartTui = (options: TuiOptions) => Promise<number>;
58
+ /** The shape `dist/tui-app.js` exports. Asserted at the load boundary in program.ts. */
59
+ export interface TuiModule {
60
+ startTui: StartTui;
61
+ }
62
+ /** Node version the Ink runtime requires (ink@7 declares `engines.node >= 22`). */
63
+ export declare const TUI_MIN_NODE_MAJOR = 22;
64
+ /**
65
+ * Whether this runtime can host the surface. Kept here, beside the reason, so the CLI's refusal and
66
+ * `doctor`'s readiness row can never disagree about the answer.
67
+ */
68
+ export declare function nodeSupportsTui(versionString?: string): boolean;
69
+ /**
70
+ * Where the bundle sits relative to the compiled CLI. Shared so the command that loads it and the
71
+ * readiness row that reports it can never look in different places.
72
+ */
73
+ export declare function tuiBundleUrl(baseUrl: string): URL;
@@ -0,0 +1,32 @@
1
+ // The boundary between the CLI and the terminal UI (#455).
2
+ //
3
+ // `humanish tui` loads a PRE-BUILT bundle (dist/tui-app.js) that contains Ink, React and the
4
+ // screens — and nothing else. Everything the surface needs to KNOW is passed across this interface
5
+ // by the CLI, which imports it from the same modules every other command uses.
6
+ //
7
+ // The reason for the seam: a terminal UI that reads the filesystem itself would become a second
8
+ // implementation of "what is a run, which lab does it belong to, is it alive" — one that ships
9
+ // minified, is invisible to the root test suite, and drifts from `humanish runs` the first time
10
+ // either side changes. Injection keeps exactly one implementation, already unit-tested, and leaves
11
+ // the bundle a view layer that can be reasoned about as one.
12
+ //
13
+ // It also makes the UI testable without a terminal: a test hands `startTui` a fake index and reads
14
+ // the frames back.
15
+ /** Node version the Ink runtime requires (ink@7 declares `engines.node >= 22`). */
16
+ export const TUI_MIN_NODE_MAJOR = 22;
17
+ /**
18
+ * Whether this runtime can host the surface. Kept here, beside the reason, so the CLI's refusal and
19
+ * `doctor`'s readiness row can never disagree about the answer.
20
+ */
21
+ export function nodeSupportsTui(versionString = process.version) {
22
+ const major = Number.parseInt(versionString.replace(/^v/, "").split(".")[0] ?? "", 10);
23
+ return Number.isFinite(major) && major >= TUI_MIN_NODE_MAJOR;
24
+ }
25
+ /**
26
+ * Where the bundle sits relative to the compiled CLI. Shared so the command that loads it and the
27
+ * readiness row that reports it can never look in different places.
28
+ */
29
+ export function tuiBundleUrl(baseUrl) {
30
+ return new URL("./tui-app.js", baseUrl);
31
+ }
32
+ //# sourceMappingURL=tui-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-contract.js","sourceRoot":"","sources":["../src/tui-contract.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,6FAA6F;AAC7F,mGAAmG;AACnG,+EAA+E;AAC/E,EAAE;AACF,gGAAgG;AAChG,+FAA+F;AAC/F,gGAAgG;AAChG,mGAAmG;AACnG,6DAA6D;AAC7D,EAAE;AACF,mGAAmG;AACnG,mBAAmB;AAqEnB,mFAAmF;AACnF,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,gBAAwB,OAAO,CAAC,OAAO;IACrE,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACvF,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,kBAAkB,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,IAAI,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,48 @@
1
+ import { spawn } from "node:child_process";
2
+ export declare function isSafeLabHandle(value: string): boolean;
3
+ export interface LaunchRunOptions {
4
+ cwd: string;
5
+ /** The manifest handle (filename stem), as `humanish lab run` takes it. */
6
+ lab: string;
7
+ mode: "dry-run" | "live";
8
+ /** Injected in tests; defaults to the real spawn. */
9
+ spawn?: typeof spawn;
10
+ /** Injected in tests; defaults to the CLI beside this module. */
11
+ cliPath?: string;
12
+ env?: NodeJS.ProcessEnv;
13
+ /** Injected clock so a test can pin the log filename. */
14
+ now?: () => Date;
15
+ }
16
+ export interface LaunchedRun {
17
+ /** The spawned CLI's pid. The run's `status.json` stamps the same value. */
18
+ pid: number;
19
+ /**
20
+ * When the launch happened. A pid ALONE cannot identify a run: pids are recycled by the OS and a
21
+ * finished run keeps its pid in `status.json` forever, so a week-old record can carry the pid the
22
+ * kernel just handed this child. Anything matching on pid must also require the record to be
23
+ * newer than this.
24
+ */
25
+ launchedAt: string;
26
+ /** Absolute path to the launch log; the only diagnosis when a run dies before writing evidence. */
27
+ logPath: string;
28
+ /** Exactly what was executed, so a failure can be reproduced by hand. */
29
+ command: readonly string[];
30
+ }
31
+ export type LaunchRunResult = {
32
+ ok: true;
33
+ run: LaunchedRun;
34
+ } | {
35
+ ok: false;
36
+ error: {
37
+ code: LaunchErrorCode;
38
+ message: string;
39
+ };
40
+ };
41
+ export type LaunchErrorCode = "HUMANISH_LAUNCH_INVALID_LAB" | "HUMANISH_LAUNCH_FAILED";
42
+ /**
43
+ * Start a run and return as soon as it is running. Never throws: a launch that cannot happen is a
44
+ * result the surface can render, not an exception that would tear down the screen.
45
+ */
46
+ export declare function launchRun(options: LaunchRunOptions): Promise<LaunchRunResult>;
47
+ /** Read the tail of a launch log — the only account of a run that died before writing evidence. */
48
+ export declare function readLaunchLogTail(logPath: string, maxBytes?: number): Promise<string>;
@@ -0,0 +1,159 @@
1
+ // Starting a run from the terminal surface (#455).
2
+ //
3
+ // THE RUN MUST OUTLIVE THE SURFACE. A study can take minutes and costs real money, so a run
4
+ // started from the TUI cannot be a child of the TUI's event loop: closing the surface, or losing
5
+ // the SSH session it is running over, must not kill it. So this spawns the CLI the same way a
6
+ // person would type it — detached, in its own process group, with its output going to a file
7
+ // rather than to a terminal that may be about to disappear.
8
+ //
9
+ // The surface then learns what happened the same way any other reader does: from `status.json`.
10
+ // It does not hold a handle to the run, parse its stdout, or track it in memory. That is what
11
+ // makes the surface restartable — quit the TUI mid-run, reopen it, and the run is still there,
12
+ // because the filesystem was always the source of truth rather than a process handle.
13
+ import { spawn } from "node:child_process";
14
+ import { constants as fsConstants } from "node:fs";
15
+ import { open } from "node:fs/promises";
16
+ import path from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import { prepareManagedHumanishOutputDirectory } from "./selected-output-paths.js";
19
+ /**
20
+ * A lab handle is the manifest FILENAME, which is what `humanish lab run` resolves. Restricted to
21
+ * characters a manifest name can actually contain, and — the part that matters — never allowed to
22
+ * begin with `-`, because argv is positional: a lab called `--json` would otherwise be handed to
23
+ * the CLI as a flag. There is no shell involved, so this is the whole injection surface.
24
+ *
25
+ * A leading underscore IS allowed: `_wip.yaml` is an ordinary way to name a work-in-progress
26
+ * manifest, `humanish lab run _wip` resolves it, and refusing it here would leave the surface
27
+ * listing a lab it will not start. A leading dot stays out — that names a hidden file, not a lab.
28
+ */
29
+ const SAFE_LAB_HANDLE = /^[A-Za-z0-9_][A-Za-z0-9._:-]*$/;
30
+ export function isSafeLabHandle(value) {
31
+ return SAFE_LAB_HANDLE.test(value) && value.length <= 128;
32
+ }
33
+ /** Where the CLI lives, relative to this compiled module. */
34
+ function defaultCliPath() {
35
+ return fileURLToPath(new URL("./cli.js", import.meta.url));
36
+ }
37
+ /**
38
+ * Start a run and return as soon as it is running. Never throws: a launch that cannot happen is a
39
+ * result the surface can render, not an exception that would tear down the screen.
40
+ */
41
+ export async function launchRun(options) {
42
+ if (!isSafeLabHandle(options.lab)) {
43
+ return {
44
+ ok: false,
45
+ error: {
46
+ code: "HUMANISH_LAUNCH_INVALID_LAB",
47
+ message: `"${options.lab}" is not a usable lab handle. Run it by path with \`humanish lab run <path>\` instead.`
48
+ }
49
+ };
50
+ }
51
+ const cwd = path.resolve(options.cwd);
52
+ const now = options.now ?? (() => new Date());
53
+ const spawnFn = options.spawn ?? spawn;
54
+ const launchedAt = now().toISOString();
55
+ let logPath;
56
+ let handle;
57
+ try {
58
+ // Contained under `.humanish/`, through the same guard every other output path uses, so a
59
+ // symlinked directory cannot redirect the log somewhere outside the project.
60
+ const logDir = await prepareManagedHumanishOutputDirectory(cwd, "launches");
61
+ const stamp = launchedAt.replace(/[:.]/g, "-");
62
+ logPath = path.join(logDir.physicalPath, `${stamp}-${options.lab}.log`);
63
+ // O_NOFOLLOW so a symlink planted at this path cannot redirect a run's output — which may carry
64
+ // provider error text — outside the project, and cannot defeat the 0600 mode by pointing at a
65
+ // file that already exists with looser permissions. O_CREAT|O_APPEND keeps ordinary reuse
66
+ // working; only a symlink is refused (ELOOP).
67
+ handle = await open(logPath, fsConstants.O_CREAT | fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_NOFOLLOW, 0o600);
68
+ }
69
+ catch (cause) {
70
+ return {
71
+ ok: false,
72
+ error: {
73
+ code: "HUMANISH_LAUNCH_FAILED",
74
+ message: `Could not open a launch log: ${cause instanceof Error ? cause.message : String(cause)}`
75
+ }
76
+ };
77
+ }
78
+ // `--json` because nothing reads this stream interactively; `--no-open` because the surface owns
79
+ // the operator's attention and must not have a browser thrown over it.
80
+ const args = [
81
+ options.cliPath ?? defaultCliPath(),
82
+ "lab",
83
+ "run",
84
+ "--cwd",
85
+ cwd,
86
+ "--json",
87
+ "--no-open",
88
+ ...(options.mode === "dry-run" ? ["--dry-run"] : []),
89
+ // `--` ends option parsing, so the handle can only ever be read as the positional argument.
90
+ "--",
91
+ options.lab
92
+ ];
93
+ const spawnOptions = {
94
+ cwd,
95
+ env: options.env ?? process.env,
96
+ // A new process group, so the SIGHUP that arrives when a terminal closes is not delivered here.
97
+ detached: true,
98
+ // stdin closed, output to the log: a detached process must never hold the terminal, and
99
+ // inheriting a pipe nobody reads is how a run blocks forever on a full buffer.
100
+ stdio: ["ignore", handle.fd, handle.fd]
101
+ };
102
+ try {
103
+ const child = spawnFn(process.execPath, args, spawnOptions);
104
+ // A spawn failure is delivered ASYNCHRONOUSLY as an 'error' event (EAGAIN, EMFILE, ENOMEM, a
105
+ // vanished node binary). An 'error' event with no listener is re-thrown by EventEmitter as an
106
+ // uncaught exception — which would tear down the whole surface, the one thing this module
107
+ // promises never to do. The run is already unref'd and unobserved, so recording it is all that
108
+ // is available; the operator learns about it from the launch log and the missing record.
109
+ child.on("error", () => {
110
+ // Deliberately empty: see above. The failure surfaces as a run that never reports in.
111
+ });
112
+ if (child.pid === undefined) {
113
+ await handle.close();
114
+ return {
115
+ ok: false,
116
+ error: { code: "HUMANISH_LAUNCH_FAILED", message: "The run process did not start." }
117
+ };
118
+ }
119
+ // Release the surface's hold: the parent can now exit whenever it likes and the run continues,
120
+ // reparented to init.
121
+ child.unref();
122
+ // The child owns the descriptor now.
123
+ await handle.close();
124
+ return {
125
+ ok: true,
126
+ run: { pid: child.pid, launchedAt, logPath, command: [process.execPath, ...args] }
127
+ };
128
+ }
129
+ catch (cause) {
130
+ await handle.close().catch(() => undefined);
131
+ return {
132
+ ok: false,
133
+ error: {
134
+ code: "HUMANISH_LAUNCH_FAILED",
135
+ message: cause instanceof Error ? cause.message : String(cause)
136
+ }
137
+ };
138
+ }
139
+ }
140
+ /** Read the tail of a launch log — the only account of a run that died before writing evidence. */
141
+ export async function readLaunchLogTail(logPath, maxBytes = 4_000) {
142
+ try {
143
+ const handle = await open(logPath, "r");
144
+ try {
145
+ const { size } = await handle.stat();
146
+ const start = Math.max(0, size - maxBytes);
147
+ const buffer = Buffer.alloc(Math.min(size, maxBytes));
148
+ await handle.read(buffer, 0, buffer.length, start);
149
+ return buffer.toString("utf8").trim();
150
+ }
151
+ finally {
152
+ await handle.close();
153
+ }
154
+ }
155
+ catch {
156
+ return "";
157
+ }
158
+ }
159
+ //# sourceMappingURL=tui-launch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-launch.js","sourceRoot":"","sources":["../src/tui-launch.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,10 @@ runId: "<core run id>"
21
21
  mode: "dry-run|live"
22
22
  simCount: 1
23
23
  createdAt: "<ISO timestamp>"
24
+ lab: # optional, additive: which manifest produced this run
25
+ id: "<lab id>"
26
+ path: "humanish/labs/<lab id>.yaml"
27
+ origin: "committed|ignored|explicit"
24
28
  cwd: "[target-cwd]"
25
29
  artifactRoot: ".humanish/runs/<run-id>"
26
30
  source:
@@ -360,6 +364,10 @@ History entries use:
360
364
  schema: humanish.run-history-entry.v1
361
365
  runId: "<run-id>"
362
366
  createdAt: "<ISO timestamp>"
367
+ lab: # optional, additive: which manifest produced this run
368
+ id: "<lab id>"
369
+ path: "humanish/labs/<lab id>.yaml"
370
+ origin: "committed|ignored|explicit"
363
371
  mode: "dry-run|live"
364
372
  path: ".humanish/runs/<run-id>"
365
373
  ```
@@ -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.48.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
6
+ `0.50.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.
@@ -371,6 +371,14 @@ Core-owned fields:
371
371
  - `artifacts`
372
372
  - `review`
373
373
  - `feedbackCandidates`
374
+ - `lab` (optional, additive): which manifest produced the run —
375
+ `{ id, path?, origin? }`, where `origin` is `committed` (humanish/labs),
376
+ `ignored` (a local overlay), or `explicit` (a path the operator passed).
377
+ Absent on bundles written before this contract and on library callers who
378
+ hand a `LabConfig` directly — the run is then honestly lab-less rather than
379
+ guessed. Readers wanting attribution for an older bundle may fall back to
380
+ `inferLegacyLabId`, which reads only the historical
381
+ `persona.source = "lab:<id>"` convention and nothing else.
374
382
  - `subject` (optional, additive): structured subject provenance —
375
383
  `{ source: clone | app-url | local-tree, repo?, commit?, archiveSha256?,
376
384
  dirty?, envNames?, state }` where `state` is `{ provenance: seeded |
@@ -724,7 +732,11 @@ Core-owned fields:
724
732
  budget AFTER productive activity — status `passed`, a NON-FAILURE completion,
725
733
  distinct from `timed_out`, which stays reserved for a zero-progress deadline
726
734
  hit and remains a failure)
727
- - `ids`, `counts`, `items[]`, optional `tokenUsage`, `capabilities`. Items may
735
+ - `ids`, `counts`, `items[]`, optional `tokenUsage`, `capabilities`. `tokenUsage`
736
+ may carry `cacheWriteInput` (tokens billed at the provider's cache-write rate,
737
+ OpenAI 5.6+) and `turns[]` (per provider-request usage, the recorded fact
738
+ long-context tier pricing needs) — both additive and honestly absent on
739
+ producers that do not report them (#334). Items may
728
740
  carry `at` (ISO-8601 recording stamp from the loop's clock) and, on
729
741
  click-like `ui_action` items, structured `coord` (`x`/`y`) — both additive
730
742
  (#441): absent on older bundles and non-stamping producers, and absence means
@@ -875,6 +887,67 @@ measure) and never grant a green pass (they surface as unmeasured). `verifyRun`
875
887
  fails closed when a live bundle lacks the cost ledger or no-spend proof, when the
876
888
  proof claims zero on a `null` line, or when known spend exceeds the declared cap.
877
889
 
890
+ ## Run Status (identity + liveness index)
891
+
892
+ `humanish.run-status.v1` — `status.json`, written inside each run directory by
893
+ every backend at run start, refreshed on a fixed cadence while the run is
894
+ alive, and finalized when it ends: `{ schema, runId, state: running |
895
+ finished, mode, lab?, pid, startedAt, updatedAt, completedAt?, outcome? }`.
896
+
897
+ It answers two questions the filesystem could not answer before: **which lab**
898
+ a run belongs to, and **whether it is still alive** — including for runs an
899
+ agent launched (`lab run --json`) or that were detached, which previously wrote
900
+ nothing at all until they completed.
901
+
902
+ It is a DERIVED INDEX, not evidence. `run.json` remains the evidence-of-record;
903
+ `verify` never gates on `status.json`, nothing in it is a claim about what a
904
+ participant did, and when the two disagree the bundle wins and the record is
905
+ rebuildable from it. A `running` record whose `updatedAt` is older than three
906
+ touch intervals is INTERRUPTED, not alive — a dropped connection or a killed
907
+ terminal leaves exactly that shape, and reading it as interrupted is the honest
908
+ outcome (`classifyRunStatus` is the one shared definition). Fields are
909
+ public-safe by construction: no hostname and no user paths, because a run
910
+ directory may be shared.
911
+
912
+ ## Run Index, Run Detail, And The Terminal Surface (#455)
913
+
914
+ Three derived projections that exist so a surface can list, classify and watch
915
+ runs without opening evidence for all of them. None is authoritative: `run.json`
916
+ remains the evidence-of-record, `verify` gates on none of these, and nothing in
917
+ them is a claim about what a participant did.
918
+
919
+ `humanish.run-index.v1` — the listing projection. One entry per run, read
920
+ cheapest-source-first: the `status.json` record, else the bundle, else the run
921
+ directory alone. `{ runId, derivedFrom: status | bundle | directory, liveness,
922
+ mode?, pid?, lab?, startedAt?, updatedAt?, completedAt?, verdict?,
923
+ participants?, estimatedCostUsd?, durationMs? }`. The point is cost: walking
924
+ every run tree and parsing every bundle measured 167ms on a 25-run project,
925
+ against 16ms cold and 2.8ms warm here, which is what makes a surface that
926
+ refreshes on a cadence affordable. `derivedFrom` is reported so a surprising row
927
+ can be traced to the file it came from. A run with receipts and no outcome is
928
+ `interrupted` — the shape a dropped connection leaves — and so is an
929
+ IN-PROGRESS bundle reached without a status record, because there is no
930
+ freshness to judge and "it started and nothing here says it finished" is the
931
+ honest reading.
932
+
933
+ `humanish.run-detail.v1` — the watching projection, for ONE run. Who is in it
934
+ and what they are thinking: `{ runId, participants: [{ id, label, personaId?,
935
+ traits, status?, completionReason?, thought?, turns?, actions?, thoughts?,
936
+ estimatedCostUsd? }], observerPath? }`. It reads the actor trace from
937
+ `stream.liveActor` while a run is in flight and `stream.actor` once it has
938
+ finished, preferring the live one, so a single screen renders a run the whole
939
+ way through. A reasoning item still being written is skipped rather than quoted
940
+ half-finished, and the text is carried verbatim — a surface may wrap it, nothing
941
+ paraphrases it. Unlike the index this DOES open the bundle, which is affordable
942
+ only because it is asked for the one run being watched.
943
+
944
+ `humanish.tui-result.v1` — what `humanish tui` emits when it refuses:
945
+ `{ schema, ok: false, error: { code, message } }` with `HUMANISH_TUI_REQUIRES_TTY`,
946
+ `HUMANISH_TUI_UNSUPPORTED_NODE`, or `HUMANISH_TUI_BUNDLE_MISSING`. Every other
947
+ command is built for an agent to drive; this one takes the screen and waits for
948
+ a person, so a non-interactive stdin or stdout fails closed naming the commands
949
+ that DO answer the question rather than rendering escape codes into a pipe.
950
+
878
951
  ## Run Cost Summary And Estimated Actor Cost
879
952
 
880
953
  The computer-use (CUA) lane surfaces an ADVISORY, additive cost ESTIMATE. It is
@@ -887,11 +960,16 @@ never authoritative: every dollar figure is a rate-table multiply, labeled
887
960
  dated per-model input/output USD-per-token rates and an E2B desktop
888
961
  USD-per-minute rate, each with a public pricing-page `source` and an `asOf`
889
962
  date. A prominent banner says these are estimates to update when providers
890
- change pricing. Some entries are `placeholder: true` stand-ins (the shipped
891
- `gpt-5.5` model rate and the E2B desktop rate) an operator MUST confirm them
892
- before trusting the magnitude; the flag propagates into every estimate so a
893
- stand-in is never mistaken for a live rate. An UNKNOWN model/desktop rate is
894
- DECLARED ABSENT (`estimatedCostUsd: null` + a `reason`), never guessed.
963
+ change pricing. Model rates may carry a `cacheWriteUsdPerToken` (OpenAI 5.6+
964
+ bills cache writes at 1.25x input as the total rate for written tokens) and a
965
+ `longContext` tier (a per-request input threshold that re-prices the whole
966
+ request; priced exactly only from the trace's per-request `turns` ledger
967
+ totals alone never re-tier, which is the under-estimate direction). Some
968
+ entries are `placeholder: true` stand-ins (the E2B desktop rate, pending a
969
+ live RAM-spec confirmation) — an operator MUST confirm them before trusting
970
+ the magnitude; the flag propagates into every estimate so a stand-in is never
971
+ mistaken for a live rate. An UNKNOWN model/desktop rate is DECLARED ABSENT
972
+ (`estimatedCostUsd: null` + a `reason`), never guessed.
895
973
  - `humanish.actor-estimated-cost.v1` — `ActorTrace.estimatedCost`: one lane's
896
974
  token-derived model cost, with `estimatedCostUsd` (or `null` + `reason`
897
975
  `no_rate_for_model`/`no_token_usage`), `ratesAsOf`, `source`, `modelId`,
@@ -29,7 +29,7 @@ study completed, reproduced, and produced a real accessibility finding via a
29
29
  keyboard-first participant
30
30
  ([docs/goals/email-gated-signup/receipts/](email-gated-signup/receipts/)).
31
31
 
32
- ## Current Program Truth (source `0.48.0`)
32
+ ## Current Program Truth (source `0.50.0`)
33
33
 
34
34
  The package source and repository implementation in this tree agree on these
35
35
  points:
@@ -45,7 +45,8 @@ The immutable 2026-06-10 proof-roadmap packet is paired with a
45
45
  | Subject sources/routes | Six declared sources: `this-repo`, `clone`, `app-url`, `local-app`, `terminal-product`, and `local-tree`; support is route-specific and `this-repo` remains dry-run-only | One centralized run/resource lifecycle boundary across all routes |
46
46
  | Public proof | A legible four-persona Observer hero from a verified real public-application study (commit-pinned drawDB) shipped in the npm payload (`0.16.0`) | Coverage beyond a single studied subject; the stratified breadth panel remains unbuilt |
47
47
  | OSS meta-lab | Dry-run contract and separate disposable smoke harness | Live meta-lab execution; disabled until repository instructions and actor credentials have an isolated boundary |
48
- | Observer serving | `watch`/`observe` loopback servers plus `serve` — the run-library surface with loopback default, capability-link exposure, `share_ready`-gated open mode, and optional operator-run tunnel; streams never served remotely | A remote live-stream (`--live-streams`) design; a persistent capability-link store; a control plane that can start runs |
48
+ | Observer serving | `watch`/`observe` loopback servers plus `serve` — the run-library surface with loopback default, capability-link exposure, `share_ready`-gated open mode, and optional operator-run tunnel; streams never served remotely | A remote live-stream (`--live-streams`) design; a persistent capability-link store |
49
+ | Stakeholder terminal surface | `humanish tui` (`0.50.0`): labs -> lab -> run, arrow-key navigation, and starting a dry or live run from the lab screen. The run is DETACHED and outlives the terminal — live-proven by killing the terminal 42s into a real run that then ran on for ~3.5 minutes and finished `pass` at $0.639751. The run screen leads with the participant and their recorded thinking, live-proven mid-flight against a real computer-use run. Ships as one bundled file loaded on demand; refuses a non-interactive stdin/stdout naming the JSON commands instead | Cancelling a run from the surface; per-user persisted config (#470); export (#471); aggregate stats (#472); agent-authored labs (#473); a reusable persona panel (#474) |
49
50
  | Off-app comms | Vendor-neutral in-sandbox email/SMS catch, a minimal persona inbox surface, and digest-only `humanish.comms-thread.v1` evidence; wired into the computer-use and shared-world routes over both HTTP and SMTP; live-proven end to end on 2026-08-08 — a persona signed up for a public app, read the emailed link in its inbox, and reached the signed-in product (`docs/goals/email-gated-signup/receipts/signup-verify-live-2026-08-08.md`); the adopter-hosted / app-url ingress plane is wired on the CUA and concurrent external-public routes (#387/#380, 2026-08-11) | Real-provider delivery; a live adopter-hosted receipt |
50
51
 
51
52
  Capability proof and adopter replacement are different gates. A deterministic
@@ -487,17 +488,25 @@ Deep links landed in `0.48.0` (#464): every participant and frame is
487
488
  addressable (`#/lane/<id>/f/<n>`), Back/Forward restore the view, and a
488
489
  reload or shared link lands on the exact moment — #441 closed entirely.
489
490
 
491
+ The npx-first-try adoption cluster closed in `0.49.0`, operator-prompted and
492
+ adversarially red-teamed before merge (both arcs): provider keys now resolve
493
+ through each vendor's native chain (#436 — the documented project overlay,
494
+ `e2b auth login`'s store, `gh auth token`, and a `humanish keys set` user
495
+ store; fills announced by name and source, never value; `HUMANISH_STRICT_KEYS=1`
496
+ opts out) and #346 closed on its receipts. The computer-use default moved to
497
+ `gpt-5.6-sol` with the whole 5.6 family priced, and the cost estimate now
498
+ models the two billing mechanics 5.6 introduced — cache writes at 1.25x and
499
+ long-context re-tiering — exactly, from a new per-request usage ledger on the
500
+ trace (#334). Both spend caps price through the same tier-aware estimator.
501
+
490
502
  The standing queue, in rough order:
491
503
 
492
504
  1. registry promotions: the wordmark (#431), the participant card, and the two
493
505
  vendored Base UI wrappers (drawer, popover) once a second surface consumes
494
506
  them;
495
- 2. the adoption cluster: vendor-native key resolution (#436) and the optional
496
- desktop peer-dep gap (#346) — the two biggest remaining "npx works
497
- first-try" violations;
498
- 3. shared-world honesty, remaining half: per-action evidence (#365) and
507
+ 2. shared-world honesty, remaining half: per-action evidence (#365) and
499
508
  exposure-flag coverage (#446);
500
- 4. the stakeholder TUI (#455): research + token-translated mocks on the
509
+ 3. the stakeholder TUI (#455): research + token-translated mocks on the
501
510
  operator review surface, design sign-off gated before any code.
502
511
 
503
512
  The depth-axis deletion front (an adopter's bespoke terminal-product sim,
@@ -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.48.0` (2026-08-17). The Observer is phone-usable as a stated requirement (observer/AGENTS.md); interactive primitives start from Base UI. The Observer renderer is the observer/ workspace artifact only; the legacy string-concat renderer was deleted at cutover (#426), and rollback is a version pin to 0.42.0. The containment boundary introduced in
5
+ Package/source version in this tree: `0.50.0` (2026-08-20). The Observer is phone-usable as a stated requirement (observer/AGENTS.md); interactive primitives start from Base UI. The Observer renderer is the observer/ workspace artifact only; the legacy string-concat renderer was deleted at cutover (#426), and rollback is a version pin to 0.42.0. 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.48.0",
3
+ "version": "0.50.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": [
@@ -49,8 +49,8 @@
49
49
  "access": "public"
50
50
  },
51
51
  "scripts": {
52
- "build": "tsc -p tsconfig.build.json && pnpm --filter humanish-observer build && node --eval \"import('node:fs').then(({ chmodSync, copyFileSync }) => { chmodSync('dist/cli.js', 0o755); copyFileSync('observer/dist/index.html', 'dist/observer-app.html'); })\"",
53
- "check": "pnpm typecheck && pnpm test && pnpm build",
52
+ "build": "tsc -p tsconfig.build.json && pnpm --filter humanish-observer build && pnpm --filter humanish-tui build && node scripts/finish-build.mjs",
53
+ "check": "pnpm typecheck && pnpm test && pnpm tui:test && pnpm build && pnpm tui:smoke",
54
54
  "humanish": "tsx src/cli.ts",
55
55
  "pack:dry-run": "npm pack --dry-run",
56
56
  "prepack": "pnpm build",
@@ -58,7 +58,7 @@
58
58
  "release:check": "pnpm check && pnpm public-surface:scan && pnpm skill:check && npm pack --dry-run",
59
59
  "skill:check": "DISABLE_TELEMETRY=1 npx skills add . --list",
60
60
  "test": "vitest run",
61
- "typecheck": "tsc -p tsconfig.json --noEmit && pnpm --filter humanish-observer typecheck",
61
+ "typecheck": "tsc -p tsconfig.json --noEmit && pnpm --filter humanish-observer typecheck && pnpm --filter humanish-tui typecheck",
62
62
  "humanish:doctor": "pnpm humanish -- doctor",
63
63
  "humanish:run": "pnpm humanish -- run --dry-run",
64
64
  "humanish:watch": "pnpm humanish -- watch",
@@ -69,7 +69,10 @@
69
69
  "humanish:lab:list": "pnpm humanish -- lab list",
70
70
  "humanish:lab:oss": "pnpm humanish -- lab run oss",
71
71
  "humanish:lab:oss:ci": "pnpm humanish -- lab run oss --dry-run --json --no-open",
72
- "humanish:lab:oss:smoke": "pnpm humanish -- lab run oss-smoke"
72
+ "humanish:lab:oss:smoke": "pnpm humanish -- lab run oss-smoke",
73
+ "tui:typecheck": "pnpm --filter humanish-tui typecheck",
74
+ "tui:smoke": "node scripts/tui-smoke.mjs",
75
+ "tui:test": "pnpm --filter humanish-tui test"
73
76
  },
74
77
  "repository": {
75
78
  "type": "git",
@@ -21,6 +21,25 @@ assume broad inherited job env is safe. Stop before live provider spend,
21
21
  hosted execution, deploys, public tunnels, or GitHub mutation unless the user
22
22
  explicitly approves that exact action.
23
23
 
24
+ ## Not For You: `humanish tui`
25
+
26
+ `humanish tui` is a human-only surface. It takes over the terminal and waits for
27
+ keystrokes, so it will block you and produce nothing you can read. It refuses a
28
+ non-interactive stdin or stdout with `HUMANISH_TUI_REQUIRES_TTY` rather than
29
+ rendering escape codes into your transcript — but do not invoke it at all.
30
+
31
+ Everything it shows has a machine-readable equivalent, which is what you want:
32
+
33
+ | Instead of the TUI | Use |
34
+ | --- | --- |
35
+ | browsing labs | `npx humanish lab list --json` |
36
+ | browsing runs | `npx humanish runs --json` |
37
+ | starting a run | `npx humanish lab run <lab> --json --no-open` |
38
+ | a run's outcome | `npx humanish review --run <id> --json` |
39
+
40
+ If a human asks you to "open the TUI", tell them the command to type; do not run
41
+ it on their behalf.
42
+
24
43
  ## Setup Workflow
25
44
 
26
45
  1. Inspect public target-repo files only: `package.json`, docs, route/app