humanish 0.49.0 → 0.51.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 +52 -3
- package/dist/concurrent-shared-world-lab.d.ts +5 -0
- package/dist/concurrent-shared-world-lab.js +28 -0
- package/dist/concurrent-shared-world-lab.js.map +1 -1
- package/dist/cua-actor-lab.d.ts +7 -0
- package/dist/cua-actor-lab.js +64 -3
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/e2b-terminal-lab.d.ts +7 -0
- package/dist/e2b-terminal-lab.js +54 -0
- package/dist/e2b-terminal-lab.js.map +1 -1
- package/dist/init-templates.js +7 -2
- package/dist/init-templates.js.map +1 -1
- package/dist/lab-engine.d.ts +11 -0
- package/dist/lab-engine.js +18 -0
- package/dist/lab-engine.js.map +1 -1
- package/dist/lab-summary.d.ts +36 -0
- package/dist/lab-summary.js +86 -0
- package/dist/lab-summary.js.map +1 -0
- package/dist/oss-lab.d.ts +3 -0
- package/dist/oss-lab.js.map +1 -1
- package/dist/oss-meta-lab.d.ts +3 -0
- package/dist/oss-meta-lab.js +13 -0
- package/dist/oss-meta-lab.js.map +1 -1
- package/dist/program.d.ts +14 -0
- package/dist/program.js +131 -9
- package/dist/program.js.map +1 -1
- package/dist/run-detail.d.ts +59 -0
- package/dist/run-detail.js +108 -0
- package/dist/run-detail.js.map +1 -0
- package/dist/run-index.d.ts +71 -0
- package/dist/run-index.js +206 -0
- package/dist/run-index.js.map +1 -0
- package/dist/run-paths.js +16 -1
- package/dist/run-paths.js.map +1 -1
- package/dist/run-projection.d.ts +182 -0
- package/dist/run-projection.js +349 -0
- package/dist/run-projection.js.map +1 -0
- package/dist/run-status.d.ts +139 -0
- package/dist/run-status.js +218 -0
- package/dist/run-status.js.map +1 -0
- package/dist/run.d.ts +15 -0
- package/dist/run.js +91 -6
- package/dist/run.js.map +1 -1
- package/dist/scripted-browser-lab.d.ts +5 -0
- package/dist/scripted-browser-lab.js +27 -0
- package/dist/scripted-browser-lab.js.map +1 -1
- package/dist/shared-world-lab.d.ts +5 -0
- package/dist/shared-world-lab.js +27 -0
- package/dist/shared-world-lab.js.map +1 -1
- package/dist/tui-app.js +402 -0
- package/dist/tui-contract.d.ts +84 -0
- package/dist/tui-contract.js +32 -0
- package/dist/tui-contract.js.map +1 -0
- package/dist/tui-launch.d.ts +48 -0
- package/dist/tui-launch.js +159 -0
- package/dist/tui-launch.js.map +1 -0
- package/dist/tui-project.d.ts +9 -0
- package/dist/tui-project.js +18 -0
- package/dist/tui-project.js.map +1 -0
- package/docs/contracts/run-bundle.md +8 -0
- package/docs/contracts/schemas.md +70 -1
- package/docs/goals/current.md +3 -2
- package/docs/ramp/README.md +1 -1
- package/package.json +8 -5
- package/skills/humanish/SKILL.md +19 -0
package/dist/program.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import { discoverProviderKeys } from "./key-resolution.js";
|
|
3
3
|
import type { ObserverResult, ObserverServer } from "./observer.js";
|
|
4
4
|
import type { OssMetaLabResult } from "./oss-meta-lab.js";
|
|
5
|
+
import { type TuiModule } from "./tui-contract.js";
|
|
5
6
|
export declare const CLI_RESPONSE_SCHEMA = "humanish.cli-response.v1";
|
|
6
7
|
export interface CliIo {
|
|
7
8
|
writeOut(text: string): void;
|
|
@@ -18,7 +19,20 @@ export interface UnexpectedErrorEnvelope {
|
|
|
18
19
|
}
|
|
19
20
|
export declare function createProgram(io?: Partial<CliIo> & {
|
|
20
21
|
keyDiscovery?: typeof discoverProviderKeys;
|
|
22
|
+
tuiRuntime?: Partial<TuiRuntime>;
|
|
21
23
|
}): Command;
|
|
24
|
+
/**
|
|
25
|
+
* The pieces of the outside world the `tui` command touches. Injectable for the same reason
|
|
26
|
+
* `keyDiscovery` is: the refusals ARE the behavior worth testing, and a test cannot make a real
|
|
27
|
+
* TTY, an old Node, or a missing bundle appear.
|
|
28
|
+
*/
|
|
29
|
+
export interface TuiRuntime {
|
|
30
|
+
stdin: NodeJS.ReadStream;
|
|
31
|
+
stdout: NodeJS.WriteStream;
|
|
32
|
+
nodeVersion: string;
|
|
33
|
+
/** Resolves the bundle, or null when it is not present. */
|
|
34
|
+
loadTui(bundle: URL): Promise<TuiModule | null>;
|
|
35
|
+
}
|
|
22
36
|
/**
|
|
23
37
|
* Default browser-open policy for a lab backend run. Mirrors the observe/watch gate:
|
|
24
38
|
* an explicit --open/--no-open wins; --json (machine mode) never auto-opens; otherwise a
|
package/dist/program.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { formatOrientationHuman, readOrientation } from "./orientation.js";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -24,6 +24,12 @@ import { DEFAULT_OSS_REPOS, runOssLab } from "./oss-lab.js";
|
|
|
24
24
|
import { cleanupOssMetaLabSandboxes, cleanupStaleOssMetaLabSandboxes, runOssMetaLab, startOssMetaLabLiveRefresh } from "./oss-meta-lab.js";
|
|
25
25
|
import { cleanupRun, doctor, listRuns, readReview, runDryRun, verifyRun } from "./run.js";
|
|
26
26
|
import { reclaimRunSandboxes } from "./reclaim.js";
|
|
27
|
+
import { RunIndexCache, readRunIndex } from "./run-index.js";
|
|
28
|
+
import { readLabSummary } from "./lab-summary.js";
|
|
29
|
+
import { readProjectState } from "./tui-project.js";
|
|
30
|
+
import { readRunDetail } from "./run-detail.js";
|
|
31
|
+
import { launchRun, readLaunchLogTail } from "./tui-launch.js";
|
|
32
|
+
import { TUI_MIN_NODE_MAJOR, nodeSupportsTui, tuiBundleUrl } from "./tui-contract.js";
|
|
27
33
|
import { runCommsCatchHost } from "./comms-catch-host.js";
|
|
28
34
|
import { DEFAULT_SANDBOX_CATCH_PORT } from "./comms-sandbox-catch.js";
|
|
29
35
|
export const CLI_RESPONSE_SCHEMA = "humanish.cli-response.v1";
|
|
@@ -138,6 +144,7 @@ function reportUnexpectedActionError(command, io, error) {
|
|
|
138
144
|
export function createProgram(io = {}) {
|
|
139
145
|
const cliIo = { ...defaultIo, ...io };
|
|
140
146
|
keyDiscoveryFn = io.keyDiscovery ?? discoverProviderKeys;
|
|
147
|
+
tuiRuntime = { ...defaultTuiRuntime, ...io.tuiRuntime };
|
|
141
148
|
const program = new HumanishCommand(undefined, cliIo);
|
|
142
149
|
// Bare `humanish` orients instead of printing sixteen subcommands (#367). --help is untouched;
|
|
143
150
|
// this is only what happens when no command was chosen at all.
|
|
@@ -179,6 +186,7 @@ export function createProgram(io = {}) {
|
|
|
179
186
|
].join("\n"));
|
|
180
187
|
registerInitCommand(program, cliIo);
|
|
181
188
|
registerDoctorCommand(program, cliIo);
|
|
189
|
+
registerTuiCommand(program, cliIo);
|
|
182
190
|
registerKeysCommand(program, cliIo);
|
|
183
191
|
registerRunCommand(program, cliIo);
|
|
184
192
|
registerVerifyCommand(program, cliIo);
|
|
@@ -237,6 +245,106 @@ function registerDoctorCommand(parent, io) {
|
|
|
237
245
|
io.setExitCode(result.ok ? 0 : 2);
|
|
238
246
|
});
|
|
239
247
|
}
|
|
248
|
+
const defaultTuiRuntime = {
|
|
249
|
+
stdin: process.stdin,
|
|
250
|
+
stdout: process.stdout,
|
|
251
|
+
nodeVersion: process.version,
|
|
252
|
+
loadTui: async (bundle) => {
|
|
253
|
+
if (!existsSync(bundle))
|
|
254
|
+
return null;
|
|
255
|
+
return (await import(bundle.href));
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
let tuiRuntime = defaultTuiRuntime;
|
|
259
|
+
const TUI_RESULT_SCHEMA = "humanish.tui-result.v1";
|
|
260
|
+
function refuseTui(command, io, refusal) {
|
|
261
|
+
if (wantsJson(command)) {
|
|
262
|
+
io.writeOut(`${JSON.stringify(refusal, null, 2)}\n`);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
io.writeErr(`${refusal.error.message}\n`);
|
|
266
|
+
}
|
|
267
|
+
markInvocationEnvelopeWritten(command);
|
|
268
|
+
io.setExitCode(2);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The stakeholder surface (#455). Every other command is written so an agent can drive it; this one
|
|
272
|
+
* is the opposite — it takes the screen and waits for a person.
|
|
273
|
+
*
|
|
274
|
+
* That inversion is why it refuses rather than degrades. An agent that runs `humanish tui` with a
|
|
275
|
+
* piped stdout has asked for something that cannot exist, and the useful answer is a structured
|
|
276
|
+
* error naming the command that WOULD have answered the question. A TUI that quietly rendered
|
|
277
|
+
* frames into a pipe would poison a transcript with escape codes and look like a hang.
|
|
278
|
+
*/
|
|
279
|
+
function registerTuiCommand(parent, io) {
|
|
280
|
+
parent
|
|
281
|
+
.command("tui")
|
|
282
|
+
.description("Open the interactive terminal surface for browsing labs and runs (humans only).")
|
|
283
|
+
.summary("Open the interactive terminal surface.")
|
|
284
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
285
|
+
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
286
|
+
.action(async (options, command) => {
|
|
287
|
+
const { stdin, stdout } = tuiRuntime;
|
|
288
|
+
if (stdin.isTTY !== true || stdout.isTTY !== true) {
|
|
289
|
+
refuseTui(command, io, {
|
|
290
|
+
schema: TUI_RESULT_SCHEMA,
|
|
291
|
+
ok: false,
|
|
292
|
+
error: {
|
|
293
|
+
code: "HUMANISH_TUI_REQUIRES_TTY",
|
|
294
|
+
message: "humanish tui needs an interactive terminal. For scripted or agent use, `humanish runs --json` lists the same runs and `humanish lab run --json` starts one."
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (!nodeSupportsTui(tuiRuntime.nodeVersion)) {
|
|
300
|
+
refuseTui(command, io, {
|
|
301
|
+
schema: TUI_RESULT_SCHEMA,
|
|
302
|
+
ok: false,
|
|
303
|
+
error: {
|
|
304
|
+
code: "HUMANISH_TUI_UNSUPPORTED_NODE",
|
|
305
|
+
message: `humanish tui needs Node ${TUI_MIN_NODE_MAJOR} or newer (this is ${tuiRuntime.nodeVersion}). Every other humanish command still works on this runtime.`
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
// The Ink app ships as a pre-built bundle beside the compiled CLI and is loaded ONLY here, so
|
|
311
|
+
// no agent-facing command pays its parse cost.
|
|
312
|
+
const bundle = tuiBundleUrl(import.meta.url);
|
|
313
|
+
const runIndexCache = new RunIndexCache();
|
|
314
|
+
const loaded = await tuiRuntime.loadTui(bundle);
|
|
315
|
+
if (loaded === null) {
|
|
316
|
+
refuseTui(command, io, {
|
|
317
|
+
schema: TUI_RESULT_SCHEMA,
|
|
318
|
+
ok: false,
|
|
319
|
+
error: {
|
|
320
|
+
code: "HUMANISH_TUI_BUNDLE_MISSING",
|
|
321
|
+
message: `The terminal surface bundle is missing at ${bundle.pathname}. In a checkout, run \`pnpm build\`; in an install, this package is incomplete — please report it.`
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const exitCode = await loaded.startTui({
|
|
327
|
+
cwd: resolve(options.cwd),
|
|
328
|
+
version: { cli: CLI_VERSION },
|
|
329
|
+
capabilities: {
|
|
330
|
+
// One cache for the life of the surface: it refreshes on a cadence, and re-walking every
|
|
331
|
+
// run tree each tick is the cost this index exists to avoid.
|
|
332
|
+
readRunIndex: (target, readOptions) => readRunIndex(target, { ...readOptions, cache: runIndexCache }),
|
|
333
|
+
listLabs: listLabManifests,
|
|
334
|
+
startRun: launchRun,
|
|
335
|
+
readLaunchLog: readLaunchLogTail,
|
|
336
|
+
readRunDetail,
|
|
337
|
+
readLabSummary,
|
|
338
|
+
readProjectState
|
|
339
|
+
},
|
|
340
|
+
stdin,
|
|
341
|
+
stdout
|
|
342
|
+
});
|
|
343
|
+
// The surface owned the screen; it has already told the operator whatever there was to say.
|
|
344
|
+
markInvocationEnvelopeWritten(command);
|
|
345
|
+
io.setExitCode(exitCode);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
240
348
|
// The discovery fn the env seam calls; injectable via createProgram for hermetic CLI tests.
|
|
241
349
|
let keyDiscoveryFn = discoverProviderKeys;
|
|
242
350
|
const KEYS_RESULT_SCHEMA = "humanish.keys-result.v1";
|
|
@@ -1755,6 +1863,11 @@ async function runLabCommand(args) {
|
|
|
1755
1863
|
args.io.writeErr(`warning: ${warning}\n`);
|
|
1756
1864
|
}
|
|
1757
1865
|
const config = resolved.config;
|
|
1866
|
+
// The run's identity (#455): which manifest, where it lives, and whether it is committed, a
|
|
1867
|
+
// local overlay, or an explicit path. Resolved once here — the only place that knows all three —
|
|
1868
|
+
// and carried into the run's status record and bundle so the filesystem can answer "which lab
|
|
1869
|
+
// produced this run" without the old `persona.source = "lab:<id>"` string convention.
|
|
1870
|
+
const lab = { id: config.id, path: resolved.path, origin: resolved.origin };
|
|
1758
1871
|
const backend = selectLabBackend(config);
|
|
1759
1872
|
if (backend !== "cua" && labRerunFlagsRequested(args.options)) {
|
|
1760
1873
|
writeUnsupportedRerunFlagsResult(args, backend);
|
|
@@ -1806,28 +1919,28 @@ async function runLabCommand(args) {
|
|
|
1806
1919
|
}
|
|
1807
1920
|
switch (backend) {
|
|
1808
1921
|
case "synthetic":
|
|
1809
|
-
await runSyntheticBackend({ ...args, config });
|
|
1922
|
+
await runSyntheticBackend({ ...args, config, labProvenance: lab });
|
|
1810
1923
|
return;
|
|
1811
1924
|
case "meta":
|
|
1812
|
-
await runMetaBackend({ ...args, config });
|
|
1925
|
+
await runMetaBackend({ ...args, config, labProvenance: lab });
|
|
1813
1926
|
return;
|
|
1814
1927
|
case "smoke":
|
|
1815
|
-
await runSmokeBackend({ ...args, config });
|
|
1928
|
+
await runSmokeBackend({ ...args, config, labProvenance: lab });
|
|
1816
1929
|
return;
|
|
1817
1930
|
case "cua":
|
|
1818
|
-
await runCuaBackend({ ...args, config, ...(scorer ? { scorer } : {}) });
|
|
1931
|
+
await runCuaBackend({ ...args, config, labProvenance: lab, ...(scorer ? { scorer } : {}) });
|
|
1819
1932
|
return;
|
|
1820
1933
|
case "scripted":
|
|
1821
|
-
await runScriptedBackend({ ...args, config });
|
|
1934
|
+
await runScriptedBackend({ ...args, config, labProvenance: lab });
|
|
1822
1935
|
return;
|
|
1823
1936
|
case "terminal":
|
|
1824
|
-
await runTerminalBackend({ ...args, config, ...(scorer ? { scorer } : {}) });
|
|
1937
|
+
await runTerminalBackend({ ...args, config, labProvenance: lab, ...(scorer ? { scorer } : {}) });
|
|
1825
1938
|
return;
|
|
1826
1939
|
case "shared-world":
|
|
1827
|
-
await runSharedWorldBackend({ ...args, config, ...(scorer ? { scorer } : {}) });
|
|
1940
|
+
await runSharedWorldBackend({ ...args, config, labProvenance: lab, ...(scorer ? { scorer } : {}) });
|
|
1828
1941
|
return;
|
|
1829
1942
|
case "concurrent-shared-world":
|
|
1830
|
-
await runConcurrentSharedWorldBackend({ ...args, config, ...(scorer ? { scorer } : {}) });
|
|
1943
|
+
await runConcurrentSharedWorldBackend({ ...args, config, labProvenance: lab, ...(scorer ? { scorer } : {}) });
|
|
1831
1944
|
return;
|
|
1832
1945
|
default:
|
|
1833
1946
|
// Compile-time exhaustiveness: a future backend must be handled here, not silently no-op.
|
|
@@ -1870,6 +1983,7 @@ async function runSyntheticBackend(args) {
|
|
|
1870
1983
|
}
|
|
1871
1984
|
const outcome = await runLab(args.config, {
|
|
1872
1985
|
cwd: args.options.cwd,
|
|
1986
|
+
...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
|
|
1873
1987
|
count: simCount,
|
|
1874
1988
|
...(args.options.dryRun === undefined ? {} : { dryRun: args.options.dryRun }),
|
|
1875
1989
|
...(args.options.runId === undefined ? {} : { runId: args.options.runId })
|
|
@@ -1977,6 +2091,7 @@ async function runCuaBackend(args) {
|
|
|
1977
2091
|
try {
|
|
1978
2092
|
outcome = await runLab(args.config, {
|
|
1979
2093
|
cwd: args.options.cwd,
|
|
2094
|
+
...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
|
|
1980
2095
|
// Watch mode opens the served Observer below (or prints the phone target under --expose)
|
|
1981
2096
|
// instead of the static render — preserved byte-for-byte from the pre-0.18 open policy so a
|
|
1982
2097
|
// non-follow watch (dry-run/--json) does not double-open. Run mode keeps the static open.
|
|
@@ -2112,6 +2227,7 @@ async function runScriptedBackend(args) {
|
|
|
2112
2227
|
});
|
|
2113
2228
|
const outcome = await runLab(args.config, {
|
|
2114
2229
|
cwd: args.options.cwd,
|
|
2230
|
+
...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
|
|
2115
2231
|
// Watch mode opens the served Observer below instead of the static render.
|
|
2116
2232
|
open: args.mode === "watch" ? false : shouldOpen,
|
|
2117
2233
|
...(args.options.dryRun === undefined ? {} : { dryRun: args.options.dryRun }),
|
|
@@ -2148,6 +2264,7 @@ async function runTerminalBackend(args) {
|
|
|
2148
2264
|
});
|
|
2149
2265
|
const outcome = await runLab(args.config, {
|
|
2150
2266
|
cwd: args.options.cwd,
|
|
2267
|
+
...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
|
|
2151
2268
|
open: args.mode === "watch" ? false : shouldOpen,
|
|
2152
2269
|
...(args.options.dryRun === undefined ? {} : { dryRun: args.options.dryRun }),
|
|
2153
2270
|
...(args.options.runId === undefined ? {} : { runId: args.options.runId }),
|
|
@@ -2181,6 +2298,7 @@ async function runSharedWorldBackend(args) {
|
|
|
2181
2298
|
});
|
|
2182
2299
|
const outcome = await runLab(args.config, {
|
|
2183
2300
|
cwd: args.options.cwd,
|
|
2301
|
+
...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
|
|
2184
2302
|
open: args.mode === "watch" ? false : shouldOpen,
|
|
2185
2303
|
...(args.options.dryRun === undefined ? {} : { dryRun: args.options.dryRun }),
|
|
2186
2304
|
...(args.options.runId === undefined ? {} : { runId: args.options.runId }),
|
|
@@ -2262,6 +2380,7 @@ async function runConcurrentSharedWorldBackend(args) {
|
|
|
2262
2380
|
try {
|
|
2263
2381
|
outcome = await runLab(args.config, {
|
|
2264
2382
|
cwd: args.options.cwd,
|
|
2383
|
+
...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
|
|
2265
2384
|
open: wantsFollow ? false : shouldOpen,
|
|
2266
2385
|
dryRun,
|
|
2267
2386
|
...(wantsFollow
|
|
@@ -2423,6 +2542,7 @@ async function runSmokeBackend(args) {
|
|
|
2423
2542
|
const repos = labReposOverride(args.options);
|
|
2424
2543
|
const outcome = await runLab(args.config, {
|
|
2425
2544
|
cwd: args.options.cwd,
|
|
2545
|
+
...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
|
|
2426
2546
|
count: limit,
|
|
2427
2547
|
...(repos === undefined ? {} : { repos }),
|
|
2428
2548
|
...(args.options.keep === undefined ? {} : { keep: args.options.keep }),
|
|
@@ -2479,6 +2599,8 @@ async function runMetaBackend(args) {
|
|
|
2479
2599
|
try {
|
|
2480
2600
|
const outcome = await runLab(args.config, {
|
|
2481
2601
|
cwd: args.options.cwd,
|
|
2602
|
+
...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
|
|
2603
|
+
...(args.labProvenance === undefined ? {} : { lab: args.labProvenance }),
|
|
2482
2604
|
...(wantsFollow ? { completionTimeoutMs: 0 } : {}),
|
|
2483
2605
|
...(codexAppServer === undefined ? {} : { codexAppServer }),
|
|
2484
2606
|
...(wantsFollow
|