humanish 0.112.0 → 0.114.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 (140) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/CONTEXT.md +1 -0
  3. package/README.md +12 -1
  4. package/TELEMETRY.md +21 -0
  5. package/dist/actors/codex/restricted-participant-policy.d.ts +21 -1
  6. package/dist/actors/codex/restricted-participant-policy.js +20 -2
  7. package/dist/actors/codex/restricted-participant.d.ts +1 -1
  8. package/dist/actors/codex/restricted-participant.js +2 -1
  9. package/dist/actors/computer-use/loop/debrief.d.ts +12 -4
  10. package/dist/actors/computer-use/loop/debrief.js +57 -20
  11. package/dist/actors/computer-use/loop/ending.d.ts +5 -2
  12. package/dist/actors/computer-use/loop/impressions.d.ts +14 -0
  13. package/dist/actors/computer-use/loop/impressions.js +40 -0
  14. package/dist/actors/computer-use/loop/session.d.ts +3 -1
  15. package/dist/actors/computer-use/loop/session.js +2 -0
  16. package/dist/actors/computer-use/loop/trace.js +2 -0
  17. package/dist/actors/computer-use/loop/types.d.ts +8 -1
  18. package/dist/actors/computer-use/loop.d.ts +2 -1
  19. package/dist/actors/computer-use/loop.js +15 -3
  20. package/dist/actors/computer-use/openai-conversation.d.ts +9 -4
  21. package/dist/actors/computer-use/openai-conversation.js +60 -29
  22. package/dist/actors/computer-use/openai-provider.js +18 -12
  23. package/dist/actors/contract.d.ts +47 -5
  24. package/dist/actors/contract.js +13 -0
  25. package/dist/analysis/admission.d.ts +37 -0
  26. package/dist/analysis/admission.js +60 -0
  27. package/dist/analysis/automatic-completion.d.ts +0 -6
  28. package/dist/analysis/automatic-completion.js +3 -3
  29. package/dist/analysis/automatic-config.d.ts +10 -2
  30. package/dist/analysis/automatic-config.js +29 -14
  31. package/dist/analysis/automatic.js +7 -0
  32. package/dist/analysis/evidence-sources.d.ts +3 -1
  33. package/dist/analysis/evidence-sources.js +10 -1
  34. package/dist/analysis/evidence.js +2 -2
  35. package/dist/analysis/execute.d.ts +20 -6
  36. package/dist/analysis/execute.js +61 -28
  37. package/dist/analysis/image-tokens.d.ts +2 -0
  38. package/dist/analysis/image-tokens.js +1 -1
  39. package/dist/analysis/job.d.ts +9 -1
  40. package/dist/analysis/job.js +11 -2
  41. package/dist/analysis/service.js +6 -3
  42. package/dist/cli/commands/analyze.js +7 -2
  43. package/dist/cli/commands/notes.d.ts +3 -0
  44. package/dist/cli/commands/notes.js +144 -0
  45. package/dist/cli/commands/observe.js +1 -1
  46. package/dist/cli/commands/runs.js +33 -10
  47. package/dist/cli/commands/setup.js +95 -6
  48. package/dist/cli/commands/study-format.js +7 -4
  49. package/dist/cli/commands/study-run.js +2 -1
  50. package/dist/cli/commands/study.js +1 -1
  51. package/dist/cli/commands/tui.js +1 -1
  52. package/dist/cli/doctor.d.ts +2 -0
  53. package/dist/cli/doctor.js +18 -0
  54. package/dist/cli/findings.js +4 -1
  55. package/dist/cli/io.d.ts +3 -0
  56. package/dist/cli/io.js +11 -12
  57. package/dist/cli/program.js +37 -1
  58. package/dist/cli/secret-prompt.d.ts +5 -1
  59. package/dist/cli/secret-prompt.js +6 -2
  60. package/dist/cli/update-check-worker.d.ts +1 -0
  61. package/dist/cli/update-check-worker.js +5 -0
  62. package/dist/cli/update-check.d.ts +46 -0
  63. package/dist/cli/update-check.js +203 -0
  64. package/dist/evidence/encoded-text.d.ts +15 -2
  65. package/dist/evidence/encoded-text.js +68 -4
  66. package/dist/evidence/secret-scrub.d.ts +1 -1
  67. package/dist/evidence/secret-scrub.js +23 -2
  68. package/dist/feedback/draft.d.ts +19 -0
  69. package/dist/feedback/draft.js +69 -1
  70. package/dist/feedback/export-bundle.js +2 -2
  71. package/dist/feedback/export.js +47 -3
  72. package/dist/feedback/feedback.js +42 -2
  73. package/dist/keys/key-resolution.js +2 -0
  74. package/dist/keys/key-status.d.ts +37 -0
  75. package/dist/keys/key-status.js +68 -0
  76. package/dist/observer/artifact.d.ts +5 -0
  77. package/dist/observer/artifact.js +18 -2
  78. package/dist/observer/notes-route.d.ts +21 -0
  79. package/dist/observer/notes-route.js +140 -0
  80. package/dist/observer/pinned-files.d.ts +8 -1
  81. package/dist/observer/pinned-files.js +32 -3
  82. package/dist/observer/render.js +38 -14
  83. package/dist/observer/run-routes.d.ts +8 -2
  84. package/dist/observer/run-routes.js +47 -6
  85. package/dist/observer/serve.d.ts +3 -0
  86. package/dist/observer/serve.js +28 -3
  87. package/dist/observer-app.html +7 -6
  88. package/dist/routes/computer-use/local-vm.js +9 -2
  89. package/dist/routes/computer-use/participant-desktop.d.ts +6 -0
  90. package/dist/routes/computer-use/participant-execution.js +9 -1
  91. package/dist/routes/computer-use/rerun-selection.js +23 -21
  92. package/dist/routes/computer-use/result.js +3 -2
  93. package/dist/routes/computer-use/self-report.js +7 -1
  94. package/dist/routes/computer-use/setup.js +14 -0
  95. package/dist/routes/computer-use/types.d.ts +4 -1
  96. package/dist/routes/shared-world/bundle-records.js +8 -7
  97. package/dist/routes/shared-world/bundle.js +1 -1
  98. package/dist/run/contained-output.d.ts +7 -0
  99. package/dist/run/contained-output.js +35 -1
  100. package/dist/run/detail.js +4 -4
  101. package/dist/run/note-files.d.ts +73 -0
  102. package/dist/run/note-files.js +184 -0
  103. package/dist/run/notes.d.ts +37 -0
  104. package/dist/run/notes.js +198 -0
  105. package/dist/run/participant-caption.d.ts +11 -1
  106. package/dist/run/participant-caption.js +30 -1
  107. package/dist/run/participant-records.d.ts +3 -1
  108. package/dist/run/participant-records.js +6 -0
  109. package/dist/run-study.js +17 -1
  110. package/dist/study/discover.js +6 -1
  111. package/dist/study/doctor.d.ts +2 -0
  112. package/dist/study/doctor.js +46 -8
  113. package/dist/study/init-templates.js +10 -5
  114. package/dist/study/plan.js +3 -2
  115. package/dist/study/preflight-result.d.ts +1 -0
  116. package/dist/study/preflight-result.js +3 -2
  117. package/dist/study/preflight.d.ts +2 -0
  118. package/dist/study/preflight.js +54 -14
  119. package/dist/study/summary.js +2 -2
  120. package/dist/study/warnings.d.ts +5 -0
  121. package/dist/study/warnings.js +79 -2
  122. package/dist/substrates/local/capacity.d.ts +75 -0
  123. package/dist/substrates/local/capacity.js +143 -0
  124. package/dist/substrates/local/firecracker-desktop.d.ts +8 -0
  125. package/dist/substrates/local/firecracker-desktop.js +34 -3
  126. package/dist/substrates/local/runtime-host.d.ts +16 -3
  127. package/dist/substrates/local/runtime-host.js +49 -4
  128. package/dist/substrates/local/runtime.d.ts +10 -0
  129. package/dist/substrates/local/runtime.js +44 -5
  130. package/dist/tui-app.js +58 -58
  131. package/docs/architecture/actor-contract.md +20 -6
  132. package/docs/contracts/errors.md +2 -2
  133. package/docs/contracts/feedback.md +8 -1
  134. package/docs/contracts/run-bundle.md +57 -0
  135. package/docs/contracts/schemas.md +62 -36
  136. package/docs/contracts/study-analysis.md +41 -8
  137. package/docs/principles/invariants-and-defaults.md +2 -2
  138. package/docs/product/automatic-analysis.md +22 -16
  139. package/package.json +2 -2
  140. package/skills/humanish/SKILL.md +26 -10
@@ -4,6 +4,8 @@ import { tmpdir } from "node:os";
4
4
  import path from "node:path";
5
5
  import { promisify } from "node:util";
6
6
  import { cli } from "../../cli/invocation.js";
7
+ import { defaultVmSize, machineSize, vmSizeProblem, } from "./capacity.js";
8
+ import { plural } from "../../run/text.js";
7
9
  const exec = promisify(execFile);
8
10
  export const LIMA_INSTANCE = "humanish-runtime";
9
11
  export const usesLima = (options = {}) => (options.platform ?? process.platform) === "darwin";
@@ -44,12 +46,12 @@ export async function runtimeDocker(args, options = {}, timeout = 15_000) {
44
46
  }
45
47
  // No host mounts, agent forwarding or automatic app-port discovery. Per-study
46
48
  // OpenSSH forwards grant just the selected app and browser-control sockets.
47
- const LIMA_TEMPLATE = `minimumLimaVersion: 2.2.0
49
+ const limaTemplate = (size) => `minimumLimaVersion: 2.2.0
48
50
  vmType: vz
49
51
  arch: aarch64
50
52
  nestedVirtualization: true
51
- cpus: 6
52
- memory: 8GiB
53
+ cpus: ${size.cpus}
54
+ memory: ${size.memoryGiB}GiB
53
55
  disk: 80GiB
54
56
  images:
55
57
  - location: https://cloud.debian.org/images/cloud/trixie/20260712-2537/debian-13-genericcloud-arm64-20260712-2537.qcow2
@@ -101,16 +103,44 @@ export async function limaStatus(options = {}) {
101
103
  if (instance &&
102
104
  (instance.name !== LIMA_INSTANCE || instance.vmType !== "vz" || instance.arch !== "aarch64"))
103
105
  throw new Error(`The existing ${LIMA_INSTANCE} Lima instance is incompatible. humanish will not replace it.`);
106
+ // `limactl list --json` reports the instance's configured memory in bytes.
107
+ const size = Number.isInteger(instance?.cpus) && Number.isFinite(instance?.memory)
108
+ ? { cpus: instance.cpus, memoryBytes: instance.memory }
109
+ : undefined;
104
110
  return {
105
111
  ready: instance?.status === "Running",
106
112
  exists: !!instance,
113
+ ...(size === undefined ? {} : { size }),
107
114
  message: instance
108
115
  ? `The humanish Lima host is stopped. Run ${cli("runtime setup")} to start it.`
109
116
  : `Run ${cli("runtime setup")} to create the humanish Lima host and install the browser runtime.`,
110
117
  };
111
118
  }
119
+ /**
120
+ * Start the humanish Lima VM, creating it at `size` (or the default size) when it does not exist.
121
+ * An existing VM keeps its size unless `size` differs, in which case it is stopped, resized and
122
+ * started again.
123
+ */
112
124
  export async function prepareLima(options = {}, progress) {
113
125
  const status = await limaStatus(options);
126
+ const mac = machineSize(options.machine);
127
+ const current = status.size === undefined
128
+ ? defaultVmSize(mac)
129
+ : { memoryGiB: status.size.memoryBytes / 1024 ** 3, cpus: status.size.cpus };
130
+ const size = { ...current, ...options.size };
131
+ if (options.size !== undefined) {
132
+ const problem = vmSizeProblem(size, mac);
133
+ if (problem !== undefined)
134
+ throw new Error(problem);
135
+ }
136
+ // An existing VM whose size Lima did not list gets the asked-for size without a comparison.
137
+ const differs = status.size === undefined
138
+ ? options.size !== undefined
139
+ : size.cpus !== current.cpus || size.memoryGiB !== current.memoryGiB;
140
+ if (status.exists && differs) {
141
+ await resizeLima(size, status.ready, options, progress);
142
+ return;
143
+ }
114
144
  if (status.ready)
115
145
  return;
116
146
  progress?.(status.exists
@@ -123,7 +153,7 @@ export async function prepareLima(options = {}, progress) {
123
153
  const work = await mkdtemp(path.join(tmpdir(), "humanish-lima-"));
124
154
  try {
125
155
  const file = path.join(work, "host.yaml");
126
- await writeFile(file, LIMA_TEMPLATE, { mode: 0o600 });
156
+ await writeFile(file, limaTemplate(size), { mode: 0o600 });
127
157
  await hostExec("limactl", ["start", "--tty=false", "--name", LIMA_INSTANCE, file], options, 15 * 60_000);
128
158
  }
129
159
  finally {
@@ -131,6 +161,21 @@ export async function prepareLima(options = {}, progress) {
131
161
  }
132
162
  }
133
163
  }
164
+ /** Lima changes the memory and CPUs of a stopped instance only. Running desktops keep it running. */
165
+ async function resizeLima(size, running, options, progress) {
166
+ if (running) {
167
+ const desktops = (await runtimeDocker(["ps", "--quiet", "--filter", "label=to.humanish.runtime.api=1"], options)).stdout
168
+ .split("\n")
169
+ .filter((line) => line.trim() !== "");
170
+ if (desktops.length > 0)
171
+ throw new Error(`${plural(desktops.length, "participant desktop")} ${desktops.length === 1 ? "is" : "are"} running in the humanish Lima VM, so it was not resized. Wait for the study to finish, then run ${cli(`runtime setup --memory ${size.memoryGiB} --cpus ${size.cpus}`)} again.`);
172
+ progress?.("Stopping the humanish Lima VM to resize it…");
173
+ await hostExec("limactl", ["stop", LIMA_INSTANCE], options, 5 * 60_000);
174
+ }
175
+ progress?.(`Resizing the humanish Lima VM to ${size.memoryGiB} GiB and ${size.cpus} CPUs…`);
176
+ await hostExec("limactl", ["edit", "--cpus", String(size.cpus), "--memory", String(size.memoryGiB), LIMA_INSTANCE], options);
177
+ await hostExec("limactl", ["start", "--tty=false", LIMA_INSTANCE], options, 15 * 60_000);
178
+ }
134
179
  /** Copy across the OS boundary explicitly; shared folders cannot carry sockets. */
135
180
  export async function loadRuntimeArchive(archive, options) {
136
181
  if (!usesLima(options)) {
@@ -1,4 +1,5 @@
1
1
  import type { LocalFirecrackerAssets } from "./firecracker-desktop.js";
2
+ import { type LocalCapacity, type VmSize } from "./capacity.js";
2
3
  import { type RuntimeHostOptions } from "./runtime-host.js";
3
4
  export interface LocalRuntimeRelease {
4
5
  url: string;
@@ -11,13 +12,22 @@ export interface LocalRuntimeStatus {
11
12
  installed: boolean;
12
13
  message: string;
13
14
  assets?: LocalFirecrackerAssets;
15
+ /** How many desktops fit, when the VM or this machine could be read. */
16
+ capacity?: LocalCapacity;
14
17
  }
15
18
  interface RuntimeOptions extends RuntimeHostOptions {
16
19
  media?: boolean;
20
+ /** The Lima VM size `runtime setup --memory --cpus` asks for. Macs only. */
21
+ size?: Partial<VmSize>;
17
22
  progress?: (message: string) => void;
18
23
  /** Explicit source-build/test override; never provided by a participant. */
19
24
  release?: LocalRuntimeRelease;
20
25
  }
26
+ /**
27
+ * How many desktops the local runtime holds, read without starting anything. Undefined when the
28
+ * host cannot be read, such as a Mac without Lima: preparing the runtime reports that.
29
+ */
30
+ export declare function localRuntimeCapacity(options?: RuntimeHostOptions): Promise<LocalCapacity | undefined>;
21
31
  /** Read-only host and cache inspection. Never pulls an image or starts a container. */
22
32
  export declare function localRuntimeStatus(options?: RuntimeOptions): Promise<LocalRuntimeStatus>;
23
33
  /** Docker owns the installed image/cache. The archive is verified before docker load. */
@@ -6,30 +6,67 @@ import path from "node:path";
6
6
  import { Readable, Transform } from "node:stream";
7
7
  import { pipeline } from "node:stream/promises";
8
8
  import { LOCAL_MEDIA_RUNTIME_RELEASES, LOCAL_RUNTIME_RELEASES } from "./runtime-release.js";
9
+ import { defaultVmSize, localCapacity, machineSize, } from "./capacity.js";
9
10
  import { limaStatus, loadRuntimeArchive, prepareLima, runtimeArchitecture, runtimeDocker, runtimeExec, usesLima, } from "./runtime-host.js";
10
11
  import { cli } from "../../cli/invocation.js";
11
12
  const docker = async (args, options) => (await runtimeDocker(args, options)).stdout.trim();
12
13
  const runtimeRelease = (options) => options.release ??
13
14
  (options.media ? LOCAL_MEDIA_RUNTIME_RELEASES : LOCAL_RUNTIME_RELEASES)[runtimeArchitecture(options) ?? "amd64"];
15
+ /** Desktops the existing Lima VM holds, the VM setup would create, or this Linux machine. */
16
+ function runtimeCapacity(options, lima) {
17
+ if (lima === undefined)
18
+ return localCapacity("linux-host", machineSize(options.machine));
19
+ const machine = machineSize(options.machine);
20
+ if (lima.size !== undefined)
21
+ return localCapacity("lima-vm", lima.size, { machine });
22
+ // An existing VM whose size Lima did not list has an unknown size, which no default replaces.
23
+ if (lima.exists)
24
+ return undefined;
25
+ const planned = defaultVmSize(machine);
26
+ return localCapacity("lima-vm", { memoryBytes: planned.memoryGiB * 1024 ** 3, cpus: planned.cpus }, { planned: true, machine });
27
+ }
28
+ /**
29
+ * How many desktops the local runtime holds, read without starting anything. Undefined when the
30
+ * host cannot be read, such as a Mac without Lima: preparing the runtime reports that.
31
+ */
32
+ export async function localRuntimeCapacity(options = {}) {
33
+ if (runtimeArchitecture(options) === undefined)
34
+ return undefined;
35
+ if (!usesLima(options))
36
+ return runtimeCapacity(options, undefined);
37
+ try {
38
+ return runtimeCapacity(options, await limaStatus(options));
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
14
44
  /** Read-only host and cache inspection. Never pulls an image or starts a container. */
15
45
  export async function localRuntimeStatus(options = {}) {
16
- const architecture = runtimeArchitecture(options), lima = usesLima(options);
46
+ const architecture = runtimeArchitecture(options);
17
47
  if (!architecture)
18
48
  return {
19
49
  ok: false,
20
50
  installed: false,
21
51
  message: "Local browsers need Linux x64 or an M3-or-newer Mac with Lima.",
22
52
  };
23
- if (lima) {
53
+ let lima;
54
+ if (usesLima(options)) {
24
55
  try {
25
- const status = await limaStatus(options);
26
- if (!status.ready)
27
- return { ok: true, installed: false, message: status.message };
56
+ lima = await limaStatus(options);
28
57
  }
29
58
  catch (error) {
30
59
  return { ok: false, installed: false, message: error.message };
31
60
  }
32
61
  }
62
+ const capacity = runtimeCapacity(options, lima);
63
+ const sized = capacity === undefined ? {} : { capacity };
64
+ if (lima !== undefined && !lima.ready)
65
+ return { ok: true, installed: false, message: lima.message, ...sized };
66
+ return { ...(await engineStatus(options, architecture, lima !== undefined)), ...sized };
67
+ }
68
+ /** The Docker engine, devices and cached image the desktops run on. */
69
+ async function engineStatus(options, architecture, lima) {
33
70
  const env = options.env ?? process.env;
34
71
  if (!lima && env.DOCKER_HOST && !env.DOCKER_HOST.startsWith("unix://")) {
35
72
  return {
@@ -142,6 +179,8 @@ export async function localRuntimeStatus(options = {}) {
142
179
  export async function prepareLocalRuntime(options = {}) {
143
180
  if (!runtimeArchitecture(options))
144
181
  throw new Error((await localRuntimeStatus(options)).message);
182
+ if (options.size !== undefined && !usesLima(options))
183
+ throw new Error("--memory and --cpus size the humanish Lima VM, which runs only on a Mac. On Linux, desktops use this machine's memory and CPUs directly.");
145
184
  if (usesLima(options))
146
185
  await prepareLima(options, options.progress);
147
186
  const before = await localRuntimeStatus(options);