humanish 0.55.0 → 0.57.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/dist/actor-contract.d.ts +3 -2
- package/dist/actor-contract.js.map +1 -1
- package/dist/agent-session.d.ts +11 -0
- package/dist/agent-session.js +43 -0
- package/dist/agent-session.js.map +1 -0
- package/dist/cua-actor-lab.d.ts +10 -0
- package/dist/cua-actor-lab.js +145 -21
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/e2b-terminal-lab.js +36 -1
- package/dist/e2b-terminal-lab.js.map +1 -1
- package/dist/lab-config.d.ts +29 -4
- package/dist/lab-config.js +47 -8
- package/dist/lab-config.js.map +1 -1
- package/dist/lab-engine.js +6 -0
- package/dist/lab-engine.js.map +1 -1
- package/dist/labs.d.ts +6 -0
- package/dist/labs.js +4 -1
- package/dist/labs.js.map +1 -1
- package/dist/program.d.ts +9 -0
- package/dist/program.js +79 -4
- package/dist/program.js.map +1 -1
- package/dist/reasoning-effort.js +4 -2
- package/dist/reasoning-effort.js.map +1 -1
- package/dist/run-projection.d.ts +4 -0
- package/dist/run-projection.js +1 -0
- package/dist/run-projection.js.map +1 -1
- package/dist/run.d.ts +9 -0
- package/dist/run.js +23 -15
- package/dist/run.js.map +1 -1
- package/dist/terminal-encoding.d.ts +17 -0
- package/dist/terminal-encoding.js +80 -0
- package/dist/terminal-encoding.js.map +1 -0
- package/dist/tui-app.js +118 -118
- package/dist/tui-contract.d.ts +17 -0
- package/dist/tui-contract.js +22 -0
- package/dist/tui-contract.js.map +1 -1
- package/docs/contracts/schemas.md +13 -5
- package/docs/goals/current.md +2 -2
- package/docs/principles/actor-fidelity.md +23 -1
- package/docs/ramp/README.md +1 -1
- package/package.json +1 -1
package/dist/program.js
CHANGED
|
@@ -31,6 +31,8 @@ import { openObserverArtifact, stopRun } from "./tui-actions.js";
|
|
|
31
31
|
import { readRunDetail } from "./run-detail.js";
|
|
32
32
|
import { launchRun, readLaunchLogTail } from "./tui-launch.js";
|
|
33
33
|
import { TUI_MIN_NODE_MAJOR, nodeSupportsTui, tuiBundleUrl } from "./tui-contract.js";
|
|
34
|
+
import { forTerminal } from "./terminal-encoding.js";
|
|
35
|
+
import { detectAgentSession } from "./agent-session.js";
|
|
34
36
|
import { runCommsCatchHost } from "./comms-catch-host.js";
|
|
35
37
|
import { DEFAULT_SANDBOX_CATCH_PORT } from "./comms-sandbox-catch.js";
|
|
36
38
|
export const CLI_RESPONSE_SCHEMA = "humanish.cli-response.v1";
|
|
@@ -43,9 +45,14 @@ const CLI_VERSION = readCliVersion();
|
|
|
43
45
|
// Shared so the ~20 leaf commands that declare their own --json flag cannot drift
|
|
44
46
|
// from each other in wording.
|
|
45
47
|
const JSON_OPTION_DESCRIPTION = "Print a machine-readable JSON response.";
|
|
48
|
+
// Transcode ONLY for a terminal. A pipe carries bytes to another program — mangling those would
|
|
49
|
+
// corrupt a JSON payload for a reader that handles UTF-8 perfectly well — while a TTY carries them
|
|
50
|
+
// to a font, through a locale that may not decode them. See src/terminal-encoding.ts for what a
|
|
51
|
+
// participant actually read back off the screen.
|
|
52
|
+
const forStream = (stream, text) => stream.isTTY === true ? forTerminal(text) : text;
|
|
46
53
|
const defaultIo = {
|
|
47
|
-
writeOut: (text) => process.stdout.write(text),
|
|
48
|
-
writeErr: (text) => process.stderr.write(text),
|
|
54
|
+
writeOut: (text) => process.stdout.write(forStream(process.stdout, text)),
|
|
55
|
+
writeErr: (text) => process.stderr.write(forStream(process.stderr, text)),
|
|
49
56
|
setExitCode: (code) => {
|
|
50
57
|
process.exitCode = code;
|
|
51
58
|
}
|
|
@@ -142,6 +149,41 @@ function reportUnexpectedActionError(command, io, error) {
|
|
|
142
149
|
}
|
|
143
150
|
io.setExitCode(2);
|
|
144
151
|
}
|
|
152
|
+
/** `unknown option '--x'` -> the sibling commands that DO declare `--x`. */
|
|
153
|
+
function commandsDeclaring(root, flag) {
|
|
154
|
+
const found = [];
|
|
155
|
+
const walk = (command, trail) => {
|
|
156
|
+
const names = [...trail, command.name()];
|
|
157
|
+
if (trail.length > 0 && command.options.some((option) => option.long === flag || option.short === flag)) {
|
|
158
|
+
found.push(names.slice(1).join(" "));
|
|
159
|
+
}
|
|
160
|
+
for (const child of command.commands)
|
|
161
|
+
walk(child, names);
|
|
162
|
+
};
|
|
163
|
+
walk(root, []);
|
|
164
|
+
return found;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Enrich commander's flag rejections with where the flag actually lives. A bare "unknown option"
|
|
168
|
+
* is accurate and unhelpful in the same way `no labs here yet` was: it reports a fact about this
|
|
169
|
+
* command and says nothing the reader can act on. Silence is preserved when no sibling has it —
|
|
170
|
+
* inventing a suggestion would be worse than none.
|
|
171
|
+
*/
|
|
172
|
+
export function withSiblingFlagHint(text, root) {
|
|
173
|
+
const match = /unknown option '([^']+)'/.exec(text);
|
|
174
|
+
if (match === null)
|
|
175
|
+
return text;
|
|
176
|
+
const owners = commandsDeclaring(root, match[1]);
|
|
177
|
+
if (owners.length === 0)
|
|
178
|
+
return text;
|
|
179
|
+
// Truncation is REPORTED, never silent: a list that quietly drops owners would send a reader
|
|
180
|
+
// looking in the wrong place and think it had answered them.
|
|
181
|
+
const shown = owners.slice(0, 3);
|
|
182
|
+
const list = shown.map((owner) => `\`humanish ${owner}\``).join(", ");
|
|
183
|
+
const rest = owners.length - shown.length;
|
|
184
|
+
const tail = rest > 0 ? ` (and ${rest} more)` : "";
|
|
185
|
+
return `${text.replace(/\n+$/, "")}\n${match[1]} is an option of ${list}${tail}, not of this command.\n`;
|
|
186
|
+
}
|
|
145
187
|
export function createProgram(io = {}) {
|
|
146
188
|
const cliIo = { ...defaultIo, ...io };
|
|
147
189
|
keyDiscoveryFn = io.keyDiscovery ?? discoverProviderKeys;
|
|
@@ -167,7 +209,14 @@ export function createProgram(io = {}) {
|
|
|
167
209
|
.option("--json", "Print machine-readable JSON responses where supported.")
|
|
168
210
|
.configureOutput({
|
|
169
211
|
writeOut: (text) => cliIo.writeOut(text),
|
|
170
|
-
writeErr: (text) => cliIo.writeErr(text)
|
|
212
|
+
writeErr: (text) => cliIo.writeErr(text),
|
|
213
|
+
// A rejected flag should name the command that WOULD have taken it. Found by a real
|
|
214
|
+
// first-contact study (labs/first-contact.yaml): a participant reached for
|
|
215
|
+
// `humanish run --no-open` by analogy with `lab run`, got a bare "unknown option", and
|
|
216
|
+
// filed it as a documentation mismatch. The flag is genuinely absent — `run` opens
|
|
217
|
+
// nothing — but "unknown" says that badly, because the reader's actual question is
|
|
218
|
+
// "then where does it live?".
|
|
219
|
+
outputError: (text, write) => write(withSiblingFlagHint(text, program))
|
|
171
220
|
})
|
|
172
221
|
.addHelpText("after", [
|
|
173
222
|
"",
|
|
@@ -249,6 +298,7 @@ function registerDoctorCommand(parent, io) {
|
|
|
249
298
|
const defaultTuiRuntime = {
|
|
250
299
|
stdin: process.stdin,
|
|
251
300
|
stdout: process.stdout,
|
|
301
|
+
env: process.env,
|
|
252
302
|
nodeVersion: process.version,
|
|
253
303
|
loadTui: async (bundle) => {
|
|
254
304
|
if (!existsSync(bundle))
|
|
@@ -283,9 +333,31 @@ function registerTuiCommand(parent, io) {
|
|
|
283
333
|
.description("Open the interactive terminal surface for browsing labs and runs (humans only).")
|
|
284
334
|
.summary("Open the interactive terminal surface.")
|
|
285
335
|
.option("--cwd <path>", "Target project directory.", ".")
|
|
336
|
+
.option("--force", "Open it anyway in a session that looks like an agent's.")
|
|
286
337
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
287
338
|
.action(async (options, command) => {
|
|
288
339
|
const { stdin, stdout } = tuiRuntime;
|
|
340
|
+
// An agent runner, even with a real terminal. `codex exec` allocates a PTY for the commands
|
|
341
|
+
// it runs, so the TTY check below passes and the surface used to open: a study watched an
|
|
342
|
+
// agent navigate the labs list and start a run it did not mean to start
|
|
343
|
+
// (labs/handed-a-human-surface.yaml). A TTY says a terminal exists, not that anyone is
|
|
344
|
+
// reading it. `--force` is the escape for the person who really is at this keyboard —
|
|
345
|
+
// capturing frames from inside an agent session is exactly that case.
|
|
346
|
+
const agent = options.force === true ? undefined : detectAgentSession(tuiRuntime.env);
|
|
347
|
+
if (agent !== undefined) {
|
|
348
|
+
refuseTui(command, io, {
|
|
349
|
+
schema: TUI_RESULT_SCHEMA,
|
|
350
|
+
ok: false,
|
|
351
|
+
error: {
|
|
352
|
+
code: "HUMANISH_TUI_AGENT_SESSION",
|
|
353
|
+
message: `humanish tui is a surface for a person, and ${agent.marker} says this session belongs to ${agent.runner}. `
|
|
354
|
+
+ "It renders frames of escape codes into a transcript, and its keys can start runs. "
|
|
355
|
+
+ "`humanish runs --json` lists runs, `humanish lab list --json` lists the studies in this project, "
|
|
356
|
+
+ "and `humanish lab run <lab> --json` starts one. If you are a person at this keyboard, add --force."
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
289
361
|
if (stdin.isTTY !== true || stdout.isTTY !== true) {
|
|
290
362
|
refuseTui(command, io, {
|
|
291
363
|
schema: TUI_RESULT_SCHEMA,
|
|
@@ -2811,7 +2883,10 @@ function formatDoctorHuman(result) {
|
|
|
2811
2883
|
return [
|
|
2812
2884
|
`humanish doctor ${result.ok ? "ok" : "needs setup"}`,
|
|
2813
2885
|
`cwd: ${result.cwd}`,
|
|
2814
|
-
|
|
2886
|
+
// "missing" is a VERDICT, and a row that never ran has none. A participant reading doctor on a
|
|
2887
|
+
// fresh desktop got `- missing package.json: package.json is present and safe to read`, which
|
|
2888
|
+
// contradicts itself in eleven words (labs/tui-self-study.yaml).
|
|
2889
|
+
...result.checks.map((check) => `- ${check.ok ? "ok" : check.checked === false ? "not checked" : "missing"} ${check.name}: ${check.message}`)
|
|
2815
2890
|
].join("\n") + "\n";
|
|
2816
2891
|
}
|
|
2817
2892
|
function formatRunHuman(result) {
|