humanish 0.140.0 → 0.141.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 (80) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/actors/codex/restricted-participant.js +1 -1
  3. package/dist/actors/computer-use/external-handoff.d.ts +22 -0
  4. package/dist/actors/computer-use/external-handoff.js +83 -0
  5. package/dist/actors/computer-use/loop/observation.d.ts +7 -0
  6. package/dist/actors/computer-use/loop/observation.js +17 -1
  7. package/dist/actors/computer-use/loop/types.d.ts +6 -0
  8. package/dist/analysis/analysis-limits.d.ts +6 -0
  9. package/dist/analysis/analysis-limits.js +6 -0
  10. package/dist/analysis/analysis-lock.d.ts +8 -0
  11. package/dist/analysis/analysis-lock.js +129 -0
  12. package/dist/analysis/automatic-completion.js +7 -0
  13. package/dist/analysis/execute.js +1 -1
  14. package/dist/analysis/job.d.ts +1 -0
  15. package/dist/analysis/job.js +6 -2
  16. package/dist/analysis/recording-ended.d.ts +16 -0
  17. package/dist/analysis/recording-ended.js +29 -0
  18. package/dist/analysis/service.d.ts +1 -3
  19. package/dist/analysis/service.js +23 -50
  20. package/dist/analysis/source.d.ts +1 -1
  21. package/dist/browser-control/protocol.d.ts +8 -0
  22. package/dist/browser-control/protocol.js +21 -0
  23. package/dist/cli/commands/analysis-signals.d.ts +10 -4
  24. package/dist/cli/commands/analysis-signals.js +28 -6
  25. package/dist/cli/commands/runs.js +8 -2
  26. package/dist/cli/commands/study-route-computer-use.js +2 -2
  27. package/dist/cli/commands/study-route-scripted.js +2 -2
  28. package/dist/cli/commands/study-route-shared-world.js +2 -2
  29. package/dist/cli/commands/study-route-terminal.js +2 -2
  30. package/dist/cli/findings.d.ts +23 -0
  31. package/dist/cli/findings.js +80 -4
  32. package/dist/cli/io.js +1 -0
  33. package/dist/evidence/browser-hygiene.d.ts +12 -1
  34. package/dist/evidence/browser-hygiene.js +45 -2
  35. package/dist/feedback/export-bundle.js +1 -1
  36. package/dist/guest/external-links.d.ts +14 -0
  37. package/dist/guest/external-links.js +48 -0
  38. package/dist/guest/runtime-desktop.js +5 -2
  39. package/dist/observer/data.d.ts +4 -0
  40. package/dist/observer/run-routes.js +12 -3
  41. package/dist/observer-app.html +2 -2
  42. package/dist/routes/computer-use/e2b-desktop/desktop.js +27 -17
  43. package/dist/routes/computer-use/e2b-desktop/teardown.js +1 -1
  44. package/dist/routes/computer-use/in-process-desktop.js +2 -2
  45. package/dist/routes/computer-use/live-phase.js +1 -1
  46. package/dist/routes/computer-use/setup.js +2 -2
  47. package/dist/routes/shared-world/route.js +1 -1
  48. package/dist/run/display.d.ts +6 -1
  49. package/dist/run/display.js +11 -0
  50. package/dist/run/host-suspension.js +4 -1
  51. package/dist/run/preflight-receipts.d.ts +3 -12
  52. package/dist/run/preflight-receipts.js +7 -67
  53. package/dist/run/process-owner.d.ts +28 -0
  54. package/dist/run/process-owner.js +83 -0
  55. package/dist/run/projection.js +1 -1
  56. package/dist/run/reclaim.d.ts +6 -0
  57. package/dist/run/reclaim.js +32 -2
  58. package/dist/run/route-shell.js +1 -1
  59. package/dist/run/run-index.d.ts +3 -1
  60. package/dist/run/run-index.js +23 -11
  61. package/dist/run/run.d.ts +16 -5
  62. package/dist/run/run.js +25 -10
  63. package/dist/run/status.d.ts +57 -2
  64. package/dist/run/status.js +87 -1
  65. package/dist/run/stored-runs.d.ts +3 -0
  66. package/dist/run/stored-runs.js +9 -4
  67. package/dist/study/run-study-events.d.ts +7 -1
  68. package/dist/substrates/e2b/desktop-browser.js +3 -1
  69. package/dist/substrates/e2b/desktop-executor.d.ts +7 -0
  70. package/dist/substrates/e2b/desktop-executor.js +3 -0
  71. package/dist/substrates/e2b/desktop-handoff.d.ts +22 -0
  72. package/dist/substrates/e2b/desktop-handoff.js +86 -0
  73. package/dist/substrates/local/runtime-release.js +8 -8
  74. package/dist/tui-app.js +115 -115
  75. package/docs/architecture/observer.md +7 -3
  76. package/docs/contracts/schemas.md +24 -3
  77. package/docs/contracts/study-analysis.md +1 -1
  78. package/docs/product/automatic-analysis.md +29 -7
  79. package/package.json +1 -1
  80. package/skills/humanish/SKILL.md +108 -5
@@ -0,0 +1,14 @@
1
+ import type { CuaExecutor } from "../actors/computer-use/loop.js";
2
+ /** Where the image keeps the stand-ins. */
3
+ export declare const GUEST_EXTERNAL_LINK_BIN = "/opt/humanish/control/bin";
4
+ /**
5
+ * Write the browser profile's Preferences before the first launch, so the initial app's origin
6
+ * opens tel:, sms: and the other app schemes without Chromium's prompt. No initial URL, no file.
7
+ */
8
+ export declare function prepareGuestBrowserProfile(profileDir: string, initialUrl: string | undefined): Promise<void>;
9
+ /**
10
+ * The executor with each observation carrying the links appended to `log` since the previous
11
+ * observation, at most EXTERNAL_HANDOFF_LIMITS.perObservation. A line still being written waits
12
+ * for the next observation. Every other property of the executor is kept.
13
+ */
14
+ export declare function withExternalHandoffs(executor: CuaExecutor, log: string): CuaExecutor;
@@ -0,0 +1,48 @@
1
+ // Links the guest browser hands to another app (mailto:, tel:, sms:). The guest has no email,
2
+ // phone or messaging app. The image's stand-ins for xdg-open and xdg-email, first on the guest's
3
+ // search path, append each such link to a log in the runtime directory, and every observation the
4
+ // executor returns carries the links appended since the previous one.
5
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
6
+ import { EXTERNAL_HANDOFF_LIMITS, externalHandoffOf, } from "../actors/computer-use/external-handoff.js";
7
+ import { externalAppPreferences } from "../evidence/browser-hygiene.js";
8
+ /** Where the image keeps the stand-ins. */
9
+ export const GUEST_EXTERNAL_LINK_BIN = "/opt/humanish/control/bin";
10
+ /**
11
+ * Write the browser profile's Preferences before the first launch, so the initial app's origin
12
+ * opens tel:, sms: and the other app schemes without Chromium's prompt. No initial URL, no file.
13
+ */
14
+ export async function prepareGuestBrowserProfile(profileDir, initialUrl) {
15
+ const preferences = externalAppPreferences(initialUrl);
16
+ if (preferences === undefined)
17
+ return;
18
+ await mkdir(`${profileDir}/Default`, { recursive: true, mode: 0o700 });
19
+ await writeFile(`${profileDir}/Default/Preferences`, JSON.stringify(preferences), {
20
+ mode: 0o600,
21
+ flag: "wx",
22
+ });
23
+ }
24
+ /**
25
+ * The executor with each observation carrying the links appended to `log` since the previous
26
+ * observation, at most EXTERNAL_HANDOFF_LIMITS.perObservation. A line still being written waits
27
+ * for the next observation. Every other property of the executor is kept.
28
+ */
29
+ export function withExternalHandoffs(executor, log) {
30
+ let consumed = 0;
31
+ return {
32
+ ...executor,
33
+ observe: async () => {
34
+ const observation = await executor.observe();
35
+ const text = await readFile(log, "utf8").catch(() => "");
36
+ const lines = text
37
+ .split("\n")
38
+ .slice(0, -1)
39
+ .slice(consumed, consumed + EXTERNAL_HANDOFF_LIMITS.perObservation);
40
+ consumed += lines.length;
41
+ const externalHandoffs = lines.flatMap((line) => {
42
+ const handoff = externalHandoffOf(line);
43
+ return handoff === undefined ? [] : [handoff];
44
+ });
45
+ return externalHandoffs.length === 0 ? observation : { ...observation, externalHandoffs };
46
+ },
47
+ };
48
+ }
@@ -14,6 +14,7 @@ import { GUEST_BOOTSTRAP_LIMITS, validateGuestInitialUrl } from "./bootstrap.js"
14
14
  import { startDesktopMedia, } from "./desktop-media.js";
15
15
  import { startDesktopRecorder } from "../evidence/desktop-recorder.js";
16
16
  import { createGuestProcesses, setupFailed } from "./runtime-processes.js";
17
+ import { GUEST_EXTERNAL_LINK_BIN, prepareGuestBrowserProfile, withExternalHandoffs, } from "./external-links.js";
17
18
  /** The guest image's fixed layout. Tests derive guest paths from it rather than spelling them. */
18
19
  export const GUEST_RUNTIME_PATHS = Object.freeze({
19
20
  root: "/opt/humanish/control",
@@ -21,7 +22,8 @@ export const GUEST_RUNTIME_PATHS = Object.freeze({
21
22
  home: "/home/humanish",
22
23
  });
23
24
  const GUEST_RUNTIME_ENV = Object.freeze({
24
- PATH: "/usr/bin:/bin",
25
+ // The stand-ins come first, so the browser hands a mailto: or tel: link to them.
26
+ PATH: `${GUEST_EXTERNAL_LINK_BIN}:/usr/bin:/bin`,
25
27
  HOME: "/home/humanish",
26
28
  USER: "humanish",
27
29
  LOGNAME: "humanish",
@@ -390,6 +392,7 @@ export async function createGuestRuntimeDesktop(options) {
390
392
  progress("layout");
391
393
  check();
392
394
  await verifyGuestLayout(check);
395
+ await prepareGuestBrowserProfile(`${GUEST_RUNTIME_PATHS.home}/browser`, options.initialUrl);
393
396
  progress("xauthority");
394
397
  await authorizeDisplay(processes, check);
395
398
  progress("display");
@@ -434,7 +437,7 @@ export async function createGuestRuntimeDesktop(options) {
434
437
  await openGuestFixture(browser.page, check);
435
438
  }
436
439
  return {
437
- executor: state.media?.wrap(executor) ?? executor,
440
+ executor: withExternalHandoffs(state.media?.wrap(executor) ?? executor, `${GUEST_RUNTIME_ENV.XDG_RUNTIME_DIR}/humanish-links.log`),
438
441
  close,
439
442
  ...(options.recording === undefined
440
443
  ? {}
@@ -23,6 +23,10 @@ export interface ObserverData {
23
23
  state: "running" | "finished" | "interrupted" | "unknown";
24
24
  observedAt: string;
25
25
  source: "local-run-status";
26
+ /** Present while the run's own process analyzes it after the run ended: when it started. */
27
+ analysis?: {
28
+ startedAt: string;
29
+ };
26
30
  };
27
31
  run: {
28
32
  runId: string;
@@ -15,7 +15,7 @@ import { displayedTraces, loadStreamTraces, } from "../run/stream-trace.js";
15
15
  import { isNodeError, isRecord } from "../run/type-guards.js";
16
16
  import { MAX_NOTE_FILE_BYTES, readRunNotes, RUN_NOTES_DIR, } from "../run/note-files.js";
17
17
  import { analysisCostOf, runCost, runCostLabel } from "../run/run-cost.js";
18
- import { RUN_STATUS_FILE, RUN_STATUS_STALE_MS, isRunStatusRecord, } from "../run/status.js";
18
+ import { RUN_STATUS_FILE, RUN_STATUS_STALE_MS, runAnalysisPhase, isRunStatusRecord, } from "../run/status.js";
19
19
  import { renderObserverHtml } from "./artifact.js";
20
20
  import { parseFeedCursors } from "./feed-cursors.js";
21
21
  import { observerFeedBody } from "./page-data.js";
@@ -98,7 +98,9 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
98
98
  const derivedRoot = cleanedRelativePath.split("/")[0];
99
99
  const derivedLeaf = path.posix.basename(cleanedRelativePath);
100
100
  // Local-only files (raw sandbox ids, the recording pid) are read in process, never served.
101
- if (derivedRoot === ".analysis-lock" ||
101
+ if (
102
+ // `.analysis-lock-stale-*` is a lock whose dead owner's next writer moved it aside.
103
+ derivedRoot?.startsWith(".analysis-lock") === true ||
102
104
  derivedLeaf.startsWith(".humanish-write-") ||
103
105
  isAnalysisRecordPath(cleanedRelativePath) ||
104
106
  isLocalOnlyRunFile(cleanedRelativePath)) {
@@ -330,9 +332,16 @@ function withLocalRunStatus(record, input, outcome) {
330
332
  state = "running";
331
333
  }
332
334
  }
335
+ // The run's own process analyzes it after the run ended; the bundle outcome does not say so.
336
+ const analysis = timestampsValid ? runAnalysisPhase(record, now) : undefined;
333
337
  return {
334
338
  ...data,
335
- runtime: { state, observedAt: new Date(now).toISOString(), source: "local-run-status" },
339
+ runtime: {
340
+ state,
341
+ observedAt: new Date(now).toISOString(),
342
+ source: "local-run-status",
343
+ ...(analysis?.state === "running" ? { analysis: { startedAt: analysis.startedAt } } : {}),
344
+ },
336
345
  };
337
346
  }
338
347
  /** internal: exported for the stream lifecycle tests. */