humanish 0.57.0 → 0.59.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.
@@ -0,0 +1,337 @@
1
+ // A computer-use brain that is already installed on the operator's machine.
2
+ //
3
+ // WHY: the fastest thing humanish can do for someone new is show them a persona driving a real
4
+ // desktop. Today that needs a provider API key before anything happens, and "go make an API key"
5
+ // is where most people stop. But a developer trying humanish very often ALREADY has a coding agent
6
+ // signed in — Codex on a ChatGPT plan, Claude Code on a Max plan — and those CLIs will look at a
7
+ // screenshot and answer with the next action. Measured, not assumed: with every OPENAI_* variable
8
+ // explicitly unset, `codex exec --image` returned a correct click on the Applications menu of a
9
+ // real desktop screenshot, and `claude -p` independently agreed within three pixels.
10
+ //
11
+ // WHAT THIS IS NOT: a way to avoid paying. Subscription usage consumes the operator's own plan,
12
+ // which is why the cost line for these runs says "not priced" rather than $0 — $0 would be a lie.
13
+ // It is also not marketed as free API access, and it fails closed on a rate limit rather than
14
+ // hammering a plan that was sold for interactive coding.
15
+ //
16
+ // WHERE IT IS SAFE, and this inverts the intuitive reading: the local agent only DECIDES. humanish
17
+ // executes the action inside the E2B sandbox, so nothing the persona chooses ever runs on the
18
+ // operator's machine. The same trick on the TERMINAL lane would be the opposite — it would move
19
+ // code execution out of the sandbox and onto a real disk — which is why this is a computer-use
20
+ // provider and nothing else. Even so, these are coding agents with their own shell and file tools,
21
+ // so each one is spawned tool-restricted, in a scratch directory, with a per-turn timeout.
22
+ import { spawn } from "node:child_process";
23
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
24
+ import { tmpdir } from "node:os";
25
+ import path from "node:path";
26
+ export const LOCAL_AGENTS = [
27
+ { id: "codex", bin: "codex", label: "Codex", credentialPath: ".codex/auth.json" },
28
+ { id: "claude", bin: "claude", label: "Claude Code", credentialPath: ".claude/.credentials.json" }
29
+ ];
30
+ /** The action vocabulary the local agent is asked to answer in — a strict subset of CuaAction. */
31
+ const ACTION_KINDS = ["click", "double_click", "type", "keypress", "scroll", "wait", "done"];
32
+ /**
33
+ * OpenAI structured outputs run in STRICT mode: every property must appear in `required`, so
34
+ * "optional" is expressed as a nullable type. Getting this wrong is a 400 before any thinking
35
+ * happens, which is how this shape was arrived at.
36
+ */
37
+ export function localAgentTurnSchema() {
38
+ return {
39
+ type: "object",
40
+ additionalProperties: false,
41
+ required: ["reasoning", "done", "message", "actions"],
42
+ properties: {
43
+ reasoning: { type: "string" },
44
+ done: { type: "boolean" },
45
+ message: { type: ["string", "null"] },
46
+ actions: {
47
+ type: "array",
48
+ items: {
49
+ type: "object",
50
+ additionalProperties: false,
51
+ required: ["kind", "x", "y", "text", "keys", "ms"],
52
+ properties: {
53
+ kind: { type: "string", enum: [...ACTION_KINDS] },
54
+ x: { type: ["integer", "null"] },
55
+ y: { type: ["integer", "null"] },
56
+ text: { type: ["string", "null"] },
57
+ keys: { type: ["array", "null"], items: { type: "string" } },
58
+ ms: { type: ["integer", "null"] }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ };
64
+ }
65
+ /**
66
+ * Map the agent's answer onto the harness action vocabulary. Anything unrecognized is DROPPED
67
+ * rather than guessed at: a coordinate we invented would be recorded as the participant's choice.
68
+ */
69
+ export function toCuaActions(raw) {
70
+ const actions = [];
71
+ for (const item of raw) {
72
+ const x = typeof item.x === "number" ? Math.round(item.x) : undefined;
73
+ const y = typeof item.y === "number" ? Math.round(item.y) : undefined;
74
+ switch (item.kind) {
75
+ case "click":
76
+ if (x !== undefined && y !== undefined)
77
+ actions.push({ kind: "click", x, y });
78
+ break;
79
+ case "double_click":
80
+ if (x !== undefined && y !== undefined)
81
+ actions.push({ kind: "double_click", x, y });
82
+ break;
83
+ case "type":
84
+ if (typeof item.text === "string" && item.text.length > 0)
85
+ actions.push({ kind: "type", text: item.text });
86
+ break;
87
+ case "keypress":
88
+ if (Array.isArray(item.keys) && item.keys.length > 0)
89
+ actions.push({ kind: "keypress", keys: [...item.keys] });
90
+ break;
91
+ case "scroll":
92
+ if (x !== undefined && y !== undefined) {
93
+ actions.push({ kind: "scroll", x, y, dx: 0, dy: typeof item.ms === "number" ? item.ms : 300 });
94
+ }
95
+ break;
96
+ case "wait":
97
+ actions.push({ kind: "wait", ...(typeof item.ms === "number" ? { ms: item.ms } : {}) });
98
+ break;
99
+ default:
100
+ break; // "done" carries no action; unknown kinds are dropped on purpose
101
+ }
102
+ }
103
+ return actions;
104
+ }
105
+ /**
106
+ * Pull the JSON object out of whatever the CLI printed. Codex writes clean JSON to
107
+ * `--output-last-message`; Claude Code wraps it in a ```json fence. Both are handled here rather
108
+ * than in two places, and a response with no object at all is a turn error, never an empty turn —
109
+ * an empty turn would read to the loop as "the participant chose to do nothing".
110
+ */
111
+ export function parseAgentJson(text) {
112
+ // ORDER MATTERS, and a test caught it: Claude Code's envelope is valid JSON whose `result`
113
+ // STRING contains a ```json fence. Stripping fences first reached inside that string and
114
+ // mangled the envelope. So: parse what we were given, and only go fence-hunting if it is not
115
+ // already JSON.
116
+ const attempts = [text.trim()];
117
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(text);
118
+ if (fenced?.[1] !== undefined)
119
+ attempts.push(fenced[1].trim());
120
+ const bare = text.slice(text.indexOf("{"), text.lastIndexOf("}") + 1);
121
+ if (text.indexOf("{") >= 0 && bare.length > 1)
122
+ attempts.push(bare);
123
+ for (const attempt of attempts) {
124
+ if (attempt.length === 0)
125
+ continue;
126
+ try {
127
+ const parsed = JSON.parse(attempt);
128
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
129
+ return parsed;
130
+ }
131
+ }
132
+ catch {
133
+ // try the next shape
134
+ }
135
+ }
136
+ throw new Error("the local agent did not return a JSON object");
137
+ }
138
+ const defaultSpawn = async (bin, args, options) => await new Promise((resolve) => {
139
+ const child = spawn(bin, [...args], { cwd: options.cwd, stdio: ["ignore", "pipe", "pipe"] });
140
+ let stdout = "";
141
+ let stderr = "";
142
+ const timer = setTimeout(() => child.kill("SIGKILL"), options.timeoutMs);
143
+ // Stopping a run must stop the thinking too: a local agent mid-turn can hold a terminal for
144
+ // minutes, and a Stop that leaves it running is not a stop.
145
+ const onAbort = () => { child.kill("SIGKILL"); };
146
+ options.signal?.addEventListener("abort", onAbort, { once: true });
147
+ const cleanup = () => {
148
+ clearTimeout(timer);
149
+ options.signal?.removeEventListener("abort", onAbort);
150
+ };
151
+ child.stdout?.on("data", (chunk) => { stdout += chunk.toString("utf8"); });
152
+ child.stderr?.on("data", (chunk) => { stderr += chunk.toString("utf8"); });
153
+ child.on("error", (error) => {
154
+ cleanup();
155
+ resolve({ code: null, stdout, stderr: `${stderr}${String(error)}` });
156
+ });
157
+ child.on("close", (code) => {
158
+ cleanup();
159
+ resolve({ code, stdout, stderr });
160
+ });
161
+ });
162
+ export const LOCAL_AGENT_CAPABILITIES = {
163
+ headless: true,
164
+ structuredTrace: true,
165
+ lanes: ["computer-use"],
166
+ producesScreenshots: true,
167
+ // The operator brings the model by being signed into it already; humanish never sees a key.
168
+ byoModel: true,
169
+ preGrantableApprovals: false,
170
+ inProcessTools: false,
171
+ license: "open"
172
+ };
173
+ function promptFor(request, screenshotPath, agent) {
174
+ const hint = request.contextHint === undefined ? "" : `\n\nNote from the harness: ${request.contextHint}`;
175
+ // Claude Code has no --output-schema, so the shape is stated in the prompt for both; codex gets
176
+ // it enforced as well. Saying it twice costs nothing and keeps one prompt for both adapters.
177
+ const shape = '{"reasoning":string,"done":boolean,"message":string|null,'
178
+ + '"actions":[{"kind":"click|double_click|type|keypress|scroll|wait|done",'
179
+ + '"x":int|null,"y":int|null,"text":string|null,"keys":[string]|null,"ms":int|null}]}';
180
+ const readFile = agent === "claude" ? `Read the image file ${screenshotPath}. ` : "";
181
+ return [
182
+ request.instructions,
183
+ "",
184
+ `${readFile}That image is the CURRENT SCREEN. You are the participant: decide what to do next, `
185
+ + "as this person would. Coordinates are pixels from the top-left of the screenshot.",
186
+ "Return between one and three actions. Set done=true ONLY when the task is finished or you are "
187
+ + "giving up, and put your closing words in message.",
188
+ `Reply with ONLY a JSON object of this shape: ${shape}`,
189
+ hint
190
+ ].join("\n");
191
+ }
192
+ /**
193
+ * A CuaProvider backed by a coding agent that is already signed in on this machine.
194
+ *
195
+ * Deliberately NOT a new lane: the loop, the executor, the trace, the affordance record and the
196
+ * Observer are all unchanged, because the only thing that differs is where the next action comes
197
+ * from. That is also why it is honest to compare a local-agent run against an API run.
198
+ */
199
+ export function createLocalAgentProvider(options) {
200
+ const spawnFn = options.spawnFn ?? defaultSpawn;
201
+ const timeoutMs = options.timeoutMs ?? 180_000;
202
+ const effort = options.reasoningEffort ?? "low";
203
+ const descriptor = LOCAL_AGENTS.find((candidate) => candidate.id === options.agent);
204
+ if (descriptor === undefined) {
205
+ throw new Error(`unknown local agent "${options.agent}"`);
206
+ }
207
+ return {
208
+ id: `local-agent-${descriptor.id}`,
209
+ version: options.model ?? `${descriptor.bin} (local, operator-authenticated)`,
210
+ modelSettings: { reasoningEffort: effort },
211
+ capabilities: LOCAL_AGENT_CAPABILITIES,
212
+ // It reasons over pixels, so the loop must hand it a frame or fail closed.
213
+ requiresFrame: true,
214
+ async nextTurn(request, signal) {
215
+ const frame = request.observation.screenshot;
216
+ if (frame === undefined) {
217
+ throw new Error("the local-agent provider needs a screenshot and this observation has none");
218
+ }
219
+ const work = await mkdtemp(path.join(options.workRoot ?? tmpdir(), "humanish-local-agent-"));
220
+ try {
221
+ const screenshotPath = path.join(work, "screen.png");
222
+ await writeFile(screenshotPath, frame);
223
+ const prompt = promptFor(request, screenshotPath, descriptor.id);
224
+ let args;
225
+ if (descriptor.id === "codex") {
226
+ const schemaPath = path.join(work, "turn-schema.json");
227
+ await writeFile(schemaPath, JSON.stringify(localAgentTurnSchema()), "utf8");
228
+ args = [
229
+ "exec",
230
+ "--image", screenshotPath,
231
+ "--output-schema", schemaPath,
232
+ "--output-last-message", path.join(work, "turn.json"),
233
+ "--skip-git-repo-check",
234
+ // The agent's OWN shell tools stay read-only: it is here to look at a picture, and a
235
+ // coding agent that decides to go exploring is exploring the operator's disk.
236
+ "--sandbox", "read-only",
237
+ "-c", `model_reasoning_effort=${effort}`,
238
+ ...(options.model === undefined ? [] : ["--model", options.model]),
239
+ prompt
240
+ ];
241
+ }
242
+ else {
243
+ args = [
244
+ "-p",
245
+ "--output-format", "json",
246
+ // Read is the only tool it needs — the screenshot — and the only one it gets.
247
+ "--allowedTools", "Read",
248
+ ...(options.model === undefined ? [] : ["--model", options.model]),
249
+ prompt
250
+ ];
251
+ }
252
+ const result = await spawnFn(descriptor.bin, args, { cwd: work, timeoutMs, ...(signal === undefined ? {} : { signal }) });
253
+ if (result.code !== 0) {
254
+ // Fail loud with the CLI's own words. A rate-limited plan says so here, and that is a
255
+ // sentence the operator can act on, unlike "turn failed".
256
+ const detail = (result.stderr || result.stdout).trim().slice(-400);
257
+ throw new Error(`${descriptor.label} exited ${result.code ?? "on a signal"}: ${detail}`);
258
+ }
259
+ // Codex writes the structured answer to a file; Claude Code returns an envelope on stdout
260
+ // whose `result` field holds the text.
261
+ let payload = result.stdout;
262
+ if (descriptor.id === "codex") {
263
+ const { readFile: read } = await import("node:fs/promises");
264
+ payload = await read(path.join(work, "turn.json"), "utf8");
265
+ }
266
+ else {
267
+ const envelope = parseAgentJson(result.stdout);
268
+ payload = typeof envelope.result === "string" ? envelope.result : result.stdout;
269
+ }
270
+ const turn = parseAgentJson(payload);
271
+ const actions = toCuaActions(Array.isArray(turn.actions) ? turn.actions : []);
272
+ const done = turn.done === true || (actions.length === 0 && typeof turn.message === "string");
273
+ return {
274
+ actions,
275
+ pendingSafetyChecks: [],
276
+ done,
277
+ ...(typeof turn.reasoning === "string" && turn.reasoning.length > 0 ? { reasoning: turn.reasoning } : {}),
278
+ ...(typeof turn.message === "string" && turn.message.length > 0 ? { message: turn.message } : {})
279
+ // No `usage`: a subscription CLI does not report tokens we can price, and inventing a
280
+ // number here is what would make the run's cost line a lie.
281
+ };
282
+ }
283
+ finally {
284
+ await rm(work, { recursive: true, force: true }).catch(() => undefined);
285
+ }
286
+ }
287
+ };
288
+ }
289
+ /**
290
+ * Which coding agents are installed and signed in on this machine.
291
+ *
292
+ * This is the whole point of the feature at the surface: someone new does not have to go and make
293
+ * an API key if the thing that can drive the study is already on their laptop. `doctor` says so,
294
+ * and says it as a capability rather than a gate — a machine with no local agent is not broken,
295
+ * it just needs a key.
296
+ */
297
+ export async function detectLocalAgents(options = {}) {
298
+ const home = options.home ?? process.env.HOME ?? "";
299
+ const which = options.which ?? (async (bin) => {
300
+ const found = await defaultSpawn("sh", ["-lc", `command -v ${bin} 2>/dev/null || true`], {
301
+ cwd: home || ".",
302
+ timeoutMs: 10_000
303
+ });
304
+ const resolved = found.stdout.trim().split("\n")[0]?.trim();
305
+ return resolved !== undefined && resolved.length > 0 ? resolved : undefined;
306
+ });
307
+ const exists = options.exists ?? (async (file) => {
308
+ const { access } = await import("node:fs/promises");
309
+ return await access(file).then(() => true).catch(() => false);
310
+ });
311
+ const found = [];
312
+ for (const descriptor of LOCAL_AGENTS) {
313
+ const binPath = await which(descriptor.bin);
314
+ if (binPath === undefined)
315
+ continue;
316
+ found.push({
317
+ ...descriptor,
318
+ binPath,
319
+ credentialsPresent: home.length > 0 && (await exists(path.join(home, descriptor.credentialPath)))
320
+ });
321
+ }
322
+ return found;
323
+ }
324
+ /** One line for `doctor`, in the register the other rows use. */
325
+ export function localAgentDoctorMessage(found) {
326
+ if (found.length === 0) {
327
+ return "no local coding agent found — a live run needs a provider API key (`humanish keys set openai`)";
328
+ }
329
+ const ready = found.filter((agent) => agent.credentialsPresent);
330
+ if (ready.length === 0) {
331
+ const names = found.map((agent) => agent.label).join(", ");
332
+ return `${names} installed but not signed in — sign in, or use a provider API key`;
333
+ }
334
+ const names = ready.map((agent) => agent.label).join(", ");
335
+ return `${names} signed in — a live run can use ${ready.length === 1 ? "it" : "one"} instead of a provider API key (actors[0].type: local-agent)`;
336
+ }
337
+ //# sourceMappingURL=local-agent-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-agent-cli.js","sourceRoot":"","sources":["../src/local-agent-cli.ts"],"names":[],"mappings":"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"}
package/dist/program.js CHANGED
@@ -27,7 +27,7 @@ import { reclaimRunSandboxes } from "./reclaim.js";
27
27
  import { RunIndexCache, readRunIndex } from "./run-index.js";
28
28
  import { readLabSummary } from "./lab-summary.js";
29
29
  import { readProjectState } from "./tui-project.js";
30
- import { openObserverArtifact, stopRun } from "./tui-actions.js";
30
+ import { openObserverArtifact, stopRun, TUI_ACTION_SCHEMA } 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";
@@ -411,7 +411,21 @@ function registerTuiCommand(parent, io) {
411
411
  readProjectState,
412
412
  openObserver: openObserverArtifact,
413
413
  reclaimRun: (target, runId) => reclaimRunSandboxes(target, runId),
414
- stopRun
414
+ stopRun,
415
+ initProject: async (target) => {
416
+ const result = await runInit({ cwd: target, yes: true });
417
+ return result.ok
418
+ ? {
419
+ schema: TUI_ACTION_SCHEMA,
420
+ ok: true,
421
+ message: `set up humanish here — ${result.changes.filter((change) => change.action !== "skip").length} files written`
422
+ }
423
+ : {
424
+ schema: TUI_ACTION_SCHEMA,
425
+ ok: false,
426
+ message: result.error?.message ?? "humanish init could not set this directory up"
427
+ };
428
+ }
415
429
  },
416
430
  stdin,
417
431
  stdout
@@ -3247,6 +3261,11 @@ function formatInitHuman(result) {
3247
3261
  if (result.mode === "needs-confirmation") {
3248
3262
  lines.push("", "Run with --dry-run --json to inspect or --yes to apply.");
3249
3263
  }
3264
+ else if (result.ok && result.nextSteps !== undefined) {
3265
+ // Twenty files and no next step is where the funnel died (#505). Increasingly the reader here
3266
+ // is a coding agent doing setup for someone, and an agent does what stdout tells it to.
3267
+ lines.push(...result.nextSteps);
3268
+ }
3250
3269
  return `${lines.join("\n")}\n`;
3251
3270
  }
3252
3271
  function formatInitChange(change) {