humanish 0.110.1 → 0.110.3

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 (121) hide show
  1. package/ARCHITECTURE.md +11 -16
  2. package/CHANGELOG.md +26 -0
  3. package/CONTEXT.md +1 -1
  4. package/README.md +8 -7
  5. package/dist/actors/computer-use/actor.js +2 -2
  6. package/dist/actors/computer-use/loop/ending.js +1 -1
  7. package/dist/actors/local-agent/cli.d.ts +6 -0
  8. package/dist/actors/local-agent/cli.js +8 -0
  9. package/dist/actors/local-agent/readiness.js +2 -1
  10. package/dist/actors/registry.js +1 -1
  11. package/dist/analysis/automatic-completion.js +8 -0
  12. package/dist/cli/commands/observe.js +18 -4
  13. package/dist/cli/commands/run-signals.js +13 -4
  14. package/dist/cli/commands/runs.js +36 -13
  15. package/dist/cli/commands/setup.js +23 -14
  16. package/dist/cli/commands/study-route-computer-use.js +7 -3
  17. package/dist/cli/commands/study-route-open.js +1 -0
  18. package/dist/cli/commands/study-route-preview.js +1 -0
  19. package/dist/cli/commands/study-route-shared-world.js +4 -2
  20. package/dist/cli/commands/study-run.js +9 -19
  21. package/dist/cli/commands/watch.js +14 -4
  22. package/dist/cli/doctor.js +9 -6
  23. package/dist/cli/findings.d.ts +5 -0
  24. package/dist/cli/findings.js +28 -6
  25. package/dist/cli/first-run-path.d.ts +27 -7
  26. package/dist/cli/first-run-path.js +112 -75
  27. package/dist/cli/invocation.d.ts +7 -0
  28. package/dist/cli/invocation.js +32 -0
  29. package/dist/cli/io.d.ts +5 -2
  30. package/dist/cli/io.js +9 -16
  31. package/dist/cli/observer-follow.d.ts +24 -0
  32. package/dist/cli/observer-follow.js +48 -3
  33. package/dist/cli/orientation.js +16 -14
  34. package/dist/cli/version.d.ts +1 -0
  35. package/dist/cli/version.js +11 -0
  36. package/dist/evidence/encoded-text.d.ts +9 -6
  37. package/dist/evidence/encoded-text.js +84 -19
  38. package/dist/evidence/redaction.d.ts +2 -0
  39. package/dist/evidence/redaction.js +134 -21
  40. package/dist/feedback/export-bundle.js +2 -2
  41. package/dist/keys/key-resolution.js +6 -5
  42. package/dist/observer/exposure.js +1 -1
  43. package/dist/observer/library.js +3 -2
  44. package/dist/observer/render.js +2 -1
  45. package/dist/observer/run-routes.js +8 -1
  46. package/dist/observer/serve.js +2 -1
  47. package/dist/routes/computer-use/participant-runs.d.ts +6 -9
  48. package/dist/routes/computer-use/participant-runs.js +16 -36
  49. package/dist/routes/computer-use/plan.d.ts +2 -2
  50. package/dist/routes/computer-use/plan.js +6 -1
  51. package/dist/routes/computer-use/route.d.ts +5 -18
  52. package/dist/routes/computer-use/route.js +17 -78
  53. package/dist/routes/computer-use/setup.d.ts +3 -2
  54. package/dist/routes/computer-use/types.d.ts +6 -10
  55. package/dist/routes/scripted/route.d.ts +4 -17
  56. package/dist/routes/scripted/route.js +10 -42
  57. package/dist/routes/scripted/types.d.ts +1 -6
  58. package/dist/routes/shared-world/plan.js +5 -1
  59. package/dist/routes/shared-world/route.d.ts +5 -11
  60. package/dist/routes/shared-world/route.js +24 -63
  61. package/dist/routes/shared-world/types.d.ts +2 -6
  62. package/dist/routes/terminal/bundle.js +2 -1
  63. package/dist/routes/terminal/credentials.d.ts +5 -5
  64. package/dist/routes/terminal/credentials.js +14 -6
  65. package/dist/routes/terminal/plan.js +8 -0
  66. package/dist/routes/terminal/route.d.ts +3 -13
  67. package/dist/routes/terminal/route.js +24 -63
  68. package/dist/routes/terminal/runtime-auth.d.ts +6 -0
  69. package/dist/routes/terminal/runtime-auth.js +5 -0
  70. package/dist/routes/terminal/session.d.ts +2 -3
  71. package/dist/routes/terminal/session.js +2 -3
  72. package/dist/routes/terminal/types.d.ts +2 -7
  73. package/dist/run/dry-run.js +2 -1
  74. package/dist/run/judge.js +2 -1
  75. package/dist/run/local-only-files.d.ts +13 -0
  76. package/dist/run/local-only-files.js +35 -0
  77. package/dist/run/reclaim.d.ts +5 -0
  78. package/dist/run/reclaim.js +34 -2
  79. package/dist/run/route-shell.d.ts +60 -0
  80. package/dist/run/route-shell.js +51 -0
  81. package/dist/run/run-not-found.js +3 -2
  82. package/dist/run/run.d.ts +6 -1
  83. package/dist/run/run.js +11 -2
  84. package/dist/run/sandbox-creates.js +5 -3
  85. package/dist/run/sandbox-receipts.d.ts +6 -5
  86. package/dist/run/sandbox-receipts.js +12 -7
  87. package/dist/run/status.d.ts +2 -0
  88. package/dist/run/status.js +5 -0
  89. package/dist/study/agents-section.d.ts +21 -0
  90. package/dist/study/agents-section.js +86 -0
  91. package/dist/study/composition-rules.js +1 -1
  92. package/dist/study/doctor.js +5 -4
  93. package/dist/study/init-templates.d.ts +1 -1
  94. package/dist/study/init-templates.js +43 -129
  95. package/dist/study/init.js +32 -35
  96. package/dist/study/local-browser-host.d.ts +11 -0
  97. package/dist/study/local-browser-host.js +42 -0
  98. package/dist/study/parse/actors.js +9 -1
  99. package/dist/study/parse/subject.js +13 -1
  100. package/dist/study/parse/url-credentials.d.ts +9 -0
  101. package/dist/study/parse/url-credentials.js +50 -0
  102. package/dist/study/preflight-probes.js +2 -1
  103. package/dist/study/types.d.ts +8 -6
  104. package/dist/study/url-credentials.d.ts +6 -0
  105. package/dist/study/url-credentials.js +34 -0
  106. package/dist/substrates/e2b/sandbox.js +33 -15
  107. package/dist/substrates/local/firecracker-desktop.js +3 -2
  108. package/dist/substrates/local/runtime-host.js +5 -4
  109. package/dist/substrates/local/runtime.js +5 -4
  110. package/dist/tui/contract.js +5 -4
  111. package/dist/verify/liveness.js +2 -1
  112. package/dist/verify/verify.js +2 -1
  113. package/docs/architecture/actor-contract.md +1 -1
  114. package/docs/architecture/external-public-shared-world.md +1 -1
  115. package/docs/architecture/terminal-product-route.md +26 -24
  116. package/docs/contracts/run-bundle.md +20 -4
  117. package/docs/contracts/schemas.md +6 -5
  118. package/docs/contracts/study-analysis.md +5 -2
  119. package/docs/product/automatic-analysis.md +1 -1
  120. package/package.json +4 -2
  121. package/skills/humanish/SKILL.md +22 -18
@@ -3,26 +3,124 @@ import { realpathSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { PNG } from "pngjs";
5
5
  import { SCREENSHOT_MAX_WIDTH_CAP, pngDecodeRefusal } from "./image.js";
6
+ import { isRecord } from "../run/type-guards.js";
6
7
  // Single source of truth for public-safety redaction patterns. Producers and the verify gate
7
8
  // both use these, so the denylist cannot drift between them. See docs/contracts/policy.md for
8
9
  // the enforcement-scope policy.
10
+ // A whole value that stands in for a secret rather than being one, as written or percent-encoded:
11
+ // a redaction marker, a shell or template variable, a placeholder in angle brackets, or a mask of
12
+ // asterisks. A context pattern skips a value only when one of these is all of it, up to a
13
+ // character that ends that pattern's value, so a password that merely starts with `*` or `$`
14
+ // still counts.
15
+ const PLACEHOLDER = [
16
+ String.raw `\[REDACTED_[A-Z0-9_]{1,40}\]`,
17
+ String.raw `%5[Bb]REDACTED_[A-Z0-9_]{1,40}%5[Dd]`,
18
+ String.raw `\$\{[A-Za-z_][A-Za-z0-9_]{0,63}\}`,
19
+ String.raw `%24%7[Bb][A-Za-z_][A-Za-z0-9_]{0,63}%7[Dd]`,
20
+ String.raw `\$[A-Za-z_][A-Za-z0-9_]{0,63}`,
21
+ String.raw `%24[A-Za-z_][A-Za-z0-9_]{0,63}`,
22
+ String.raw `\{\{[A-Za-z0-9_. -]{1,64}\}\}`,
23
+ String.raw `<[A-Za-z0-9_. -]{1,64}>`,
24
+ String.raw `%3[Cc][A-Za-z0-9_.-]{1,64}%3[Ee]`,
25
+ String.raw `\*{3,64}`,
26
+ String.raw `(?:%2[Aa]){3,64}`,
27
+ ].join("|");
28
+ const notAPlaceholder = (valueEnd) => String.raw `(?!(?:${PLACEHOLDER})(?:${valueEnd}|$))`;
29
+ // An optional quote around a name or a value, also as JSON writes it inside other JSON strings,
30
+ // four levels deep (\", \\\", ...).
31
+ const QUOTE = String.raw `(?:\\{0,15}["'])?`;
32
+ // A JSON number, which a credential-named key can hold and redaction must not turn into a string.
33
+ // The bounds are far past any number a run writes and keep the lookahead linear.
34
+ const NOT_A_NUMBER = String.raw `(?!-?[0-9]{1,512}(?:\.[0-9]{0,512})?(?:[eE][+-]?[0-9]{1,64})?(?:[\s,}\]]|$))`;
35
+ // The characters that end each context pattern's value.
36
+ const USERINFO_END = String.raw `[\s@/?#"'<>\\]`;
37
+ const PARAMETER_END = String.raw `[\s&#"'<>\\]`;
38
+ const VARIABLE_END = String.raw `[\s"'\\,;&]`;
39
+ // Query and fragment parameters that carry a credential. `page_token` and other cursors match too;
40
+ // their values are long and opaque, so a reader cannot tell them from a credential either.
41
+ const CREDENTIAL_PARAMETER = [
42
+ String.raw `(?:[a-z0-9]{1,24}[_-]){0,2}token`,
43
+ String.raw `api[_-]?key`,
44
+ String.raw `(?:client[_-])?secret`,
45
+ "password",
46
+ "passwd",
47
+ "pwd",
48
+ "sig",
49
+ "signature",
50
+ String.raw `session[_-]?id`,
51
+ "jwt",
52
+ "x-vercel-protection-bypass",
53
+ "_vercel_share",
54
+ String.raw `x-amz-(?:signature|security-token|credential)`,
55
+ String.raw `x-goog-(?:signature|credential)`,
56
+ "auth",
57
+ ].join("|");
58
+ // Upper-case variable names that hold a credential, as an env file, a shell or a JSON dump writes
59
+ // them: GITHUB_TOKEN, VERCEL_TOKEN, AWS_SECRET_ACCESS_KEY, DATABASE_PASSWORD.
60
+ const CREDENTIAL_VARIABLE = String.raw `\b(?:[A-Z][A-Z0-9_]{0,48}_)?(?:TOKEN|SECRET|SECRET_KEY|PASSWORD|PASSWD|API_KEY|APIKEY|ACCESS_KEY|PRIVATE_KEY)`;
61
+ // The public E2B pages its errors link to, which name no sandbox and carry no credential: the
62
+ // dashboard as the SDK's missing-key error names it (https://e2b.dev/dashboard?tab=keys), with no
63
+ // path below /dashboard and no query but one short `tab`, and a docs page as the API's 401 names
64
+ // it (https://docs.e2b.dev/api-key), whose path segments hold only letters and hyphens and which
65
+ // has no query. Neither has user info, and only closing punctuation, ")" included, may follow
66
+ // before the next space or the end of the text.
67
+ const E2B_PUBLIC_PAGE = [
68
+ String.raw `(?:www\.)?e2b\.dev\/dashboard\/?(?:\?tab=[a-z]{1,16})?`,
69
+ String.raw `(?:docs\.e2b\.dev|(?:www\.)?e2b\.dev\/docs)(?:\/[a-z][a-z-]{0,39}){0,8}\/?`,
70
+ ].join("|");
71
+ // A pattern that matches a credential by its context puts the context in a `keep` group, which
72
+ // redaction leaves in place: `?token=[REDACTED_SECRET]`. Context goes in a group and not in a
73
+ // lookbehind, because a pattern that starts with a lookbehind is tried at every position of the
74
+ // text and scans many times slower. An open-ended repeat is written `[...]{n}[...]*`: V8 runs
75
+ // `{n,}` with a backtrack entry per character and overflows its stack on a run of several
76
+ // megabytes, and a star loop does not.
9
77
  const SECRET_PATTERNS = [
10
- /\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{20,}\b/g,
11
- /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
12
- /\be2b_[A-Za-z0-9]{16,}\b/g,
13
- /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g,
14
- /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
15
- /\bAKIA[0-9A-Z]{16}\b/g,
16
- /\bAIza[0-9A-Za-z_-]{20,}\b/g,
17
- /\b(?:sk|rk)_live_[A-Za-z0-9]{16,}\b/g,
18
- /\bhf_[A-Za-z0-9]{30,}\b/g,
19
- /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/g,
20
- /\beyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}\b/g,
78
+ /\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{20}[A-Za-z0-9_-]*\b/g,
79
+ /\bsk-ant-[A-Za-z0-9_-]{20}[A-Za-z0-9_-]*\b/g,
80
+ /\be2b_[A-Za-z0-9]{16}[A-Za-z0-9]*\b/g,
81
+ /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20}[A-Za-z0-9_]*\b/g,
82
+ /\bgithub_pat_[A-Za-z0-9_]{20}[A-Za-z0-9_]*\b/g,
83
+ /\bglpat-[A-Za-z0-9_-]{20}[A-Za-z0-9_-]*/g,
84
+ /\bnpm_[A-Za-z0-9]{36}\b/g,
85
+ // Vercel personal, integration, app access, app refresh and API key tokens.
86
+ /\bvc[pciark]_[A-Za-z0-9]{24}[A-Za-z0-9]*\b/g,
87
+ /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
88
+ /\bAIza[0-9A-Za-z_-]{20}[0-9A-Za-z_-]*\b/g,
89
+ /\bya29\.[0-9A-Za-z_-]{20}[0-9A-Za-z_-]*/g,
90
+ /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16}[A-Za-z0-9]*\b/g,
91
+ /\bwhsec_[A-Za-z0-9]{24}[A-Za-z0-9]*\b/g,
92
+ /\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b/g,
93
+ /\bhf_[A-Za-z0-9]{30}[A-Za-z0-9]*\b/g,
94
+ /\bxox[abeoprs]-[A-Za-z0-9-]{20}[A-Za-z0-9-]*\b/g,
95
+ /\bxapp-[0-9]-[A-Za-z0-9-]{20}[A-Za-z0-9-]*\b/g,
96
+ /\bhooks\.slack\.com\/(?:services|workflows|triggers)\/[A-Za-z0-9_/-]{20}[A-Za-z0-9_/-]*/g,
97
+ // A JWT starts only where a token run starts, so a run of `eyJ-` repeated is read once.
98
+ /eyJ(?<![A-Za-z0-9_-]eyJ)[A-Za-z0-9_-]{20}[A-Za-z0-9_-]*\.[A-Za-z0-9_-]{20}[A-Za-z0-9_-]*\.[A-Za-z0-9_-]{10}[A-Za-z0-9_-]*\b/g,
21
99
  /-----BEGIN [A-Z ]*PRIVATE KEY-----/g,
22
- /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis):\/\/[^:@/\s]+:[^@/\s]+@\S+/g,
23
- /_authToken\s*=\s*[A-Za-z0-9._~+/=-]{20,}/g,
24
- /\bBearer\s+[A-Za-z0-9._~+/-]{24,}\b/g,
25
- /https?:\/\/[^/\s]*e2b[^)\s]+/gi,
100
+ /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis):\/\/[^:@/\s"'<>\\]+:[^@/\s"'<>\\]+@[^\s"'<>\\]+/g,
101
+ /_authToken\s*=\s*[A-Za-z0-9._~+/=-]{20}[A-Za-z0-9._~+/=-]*/g,
102
+ /\bBearer\s+[A-Za-z0-9._~+/-]{24}[A-Za-z0-9._~+/-]*\b/g,
103
+ new RegExp(String.raw `(?<keep>\bAuthorization${QUOTE}[ \t]{0,4}[:=][ \t]{0,4}${QUOTE}Basic[ \t]{1,4})[A-Za-z0-9+/]{16}[A-Za-z0-9+/]*={0,2}`, "gi"),
104
+ // The password in a URL's userinfo, any scheme, with or without a user name:
105
+ // https://user:password@host, https://:password@host.
106
+ new RegExp(String.raw `(?<keep>\b[a-z][a-z0-9+.-]{0,31}:\/\/[^\s:@/?#"'<>\\[\]]{0,256}:)${notAPlaceholder(USERINFO_END)}[^\s@/?#"'<>\\]{1,256}(?=@)`, "gi"),
107
+ // A credential-named query or fragment parameter's value, up to the next delimiter, so a
108
+ // password with punctuation in it counts whole. `;` also follows an HTML `&amp;`.
109
+ new RegExp(String.raw `(?<keep>[?&#;](?:${CREDENTIAL_PARAMETER})=)${notAPlaceholder(PARAMETER_END)}[^\s&#"'<>\\]{16}[^\s&#"'<>\\]*`, "gi"),
110
+ // An Amazon Web Services secret access key next to its name, as a credentials file, an env
111
+ // line or a temporary-credentials JSON response writes it. The key is 40 characters, no prefix.
112
+ new RegExp(String.raw `(?<keep>\b(?:aws_?)?secret_?access_?key${QUOTE}[ \t]{0,4}[:=][ \t]{0,4}${QUOTE})${NOT_A_NUMBER}[A-Za-z0-9/+]{40}(?![A-Za-z0-9/+=])`, "gi"),
113
+ // A credential-named variable set to a value of 16 characters or more, up to the next space,
114
+ // quote or separator, with a digit and a letter in its first 256 characters. Words, placeholders
115
+ // such as humanish-egress-auth-placeholder and JSON numbers do not count. The lookaheads are
116
+ // bounded, so a long run of `TOKEN=` costs linear time.
117
+ new RegExp(String.raw `(?<keep>${CREDENTIAL_VARIABLE}${QUOTE}[ \t]{0,4}[:=][ \t]{0,4}${QUOTE})${notAPlaceholder(VARIABLE_END)}${NOT_A_NUMBER}(?=[^\s"'\\,;&]{0,255}[0-9])(?=[^\s"'\\,;&]{0,255}[A-Za-z])[^\s"'\\,;&]{16}[^\s"'\\,;&]*`, "g"),
118
+ // Any URL on an E2B host: a sandbox host names its sandbox, and a stream URL carries its auth
119
+ // key. The slashes may be JSON-escaped (`https:\/\/`). User info runs to its last `@`, as a URL
120
+ // parser reads it, and may hold any character but a slash or space. The host part stops at a
121
+ // backslash, quote or angle bracket, so a URL followed by an escaped line break and an `E2B_...`
122
+ // variable in JSON text is not read as an E2B host. A public page followed by `)@` is user info.
123
+ new RegExp(String.raw `(?!https:\/\/(?:${E2B_PUBLIC_PAGE})[.,;:!?'")]*(?:\s|$))https?:\\?\/\\?\/(?:[^/\s@]*@)*[^/\s\\"'<>@]*e2b[^)\s]+`, "gi"),
26
124
  /BEGIN (RSA|OPENSSH|PRIVATE) KEY/gi,
27
125
  ];
28
126
  const LOCAL_PATH_PATTERNS = [
@@ -32,6 +130,12 @@ const LOCAL_PATH_PATTERNS = [
32
130
  [/\/tmp\/[^\s"'`<>)]*/g, "[REDACTED_LOCAL_PATH]"],
33
131
  [/\/Users\/[A-Za-z0-9._-]+(?:\/[^\s"'`<>)]*)?/g, "[REDACTED_LOCAL_PATH]"],
34
132
  [/\/home\/[A-Za-z0-9._-]+(?:\/[^\s"'`<>)]*)?/g, "[REDACTED_RUNTIME_PATH]"],
133
+ // A Windows profile path, with `\`, `\\` (JSON-escaped) or `/` between segments. A match never
134
+ // ends in a backslash, so it cannot swallow the escape of a closing quote.
135
+ [
136
+ /\b[A-Za-z]:(?:\\\\|\\|\/)Users(?:\\\\|\\|\/)[^\\/\s"'`<>)]+[^\s"'`<>)]*/g,
137
+ "[REDACTED_LOCAL_PATH]",
138
+ ],
35
139
  ];
36
140
  /** Every pattern containsSensitive tests. A test pins each to ASCII, which scanEncodedText relies on. */
37
141
  export function sensitivePatterns() {
@@ -43,10 +147,13 @@ function matchesPattern(pattern, text) {
43
147
  pattern.lastIndex = 0;
44
148
  return pattern.test(text);
45
149
  }
150
+ /** True if the text contains a secret-shaped token. Local paths do not count. */
151
+ export function containsSecret(text) {
152
+ return SECRET_PATTERNS.some((pattern) => matchesPattern(pattern, text));
153
+ }
46
154
  /** True if the text contains any secret-shaped token or known local path. */
47
155
  export function containsSensitive(text) {
48
- return (SECRET_PATTERNS.some((pattern) => matchesPattern(pattern, text)) ||
49
- LOCAL_PATH_PATTERNS.some(([pattern]) => matchesPattern(pattern, text)));
156
+ return (containsSecret(text) || LOCAL_PATH_PATTERNS.some(([pattern]) => matchesPattern(pattern, text)));
50
157
  }
51
158
  // A local-path match may end in the backslashes escaping its closing quote in
52
159
  // serialized JSON (including JSON nested inside a terminal event). Keep that
@@ -142,15 +249,21 @@ export function redactSandboxIds(value) {
142
249
  entries.some(([key, child]) => child !== value[key]);
143
250
  return changed ? Object.fromEntries(entries) : value;
144
251
  }
252
+ /** Each secret replaced by [REDACTED_SECRET], with the context a pattern keeps left in place. */
253
+ function redactSecrets(text) {
254
+ return SECRET_PATTERNS.reduce((current, pattern) => current.replace(pattern, (...args) => {
255
+ const groups = args.at(-1);
256
+ const keep = isRecord(groups) && typeof groups.keep === "string" ? groups.keep : "";
257
+ return `${keep}[REDACTED_SECRET]`;
258
+ }), text);
259
+ }
145
260
  /** Redact secrets to [REDACTED_SECRET] and local paths to their path labels. */
146
261
  export function redactText(text) {
147
- const withoutSecrets = SECRET_PATTERNS.reduce((current, pattern) => current.replace(pattern, "[REDACTED_SECRET]"), text);
148
- return redactLocalPaths(withoutSecrets);
262
+ return redactLocalPaths(redactSecrets(text));
149
263
  }
150
264
  /** Redact every sensitive match (secrets and paths) to a single [REDACTED_SECRET] label. */
151
265
  export function redactToSecretLabel(text) {
152
- const withoutSecrets = SECRET_PATTERNS.reduce((current, pattern) => current.replace(pattern, "[REDACTED_SECRET]"), text);
153
- return redactLocalPaths(withoutSecrets, "[REDACTED_SECRET]");
266
+ return redactLocalPaths(redactSecrets(text), "[REDACTED_SECRET]");
154
267
  }
155
268
  function canonicalizePath(value) {
156
269
  const resolved = path.resolve(value);
@@ -18,6 +18,7 @@ import { loadRunBundlePrepared, resolveRunPath } from "../run/locate.js";
18
18
  import {} from "../run/bundle.js";
19
19
  import { runIdOf, isPathInside, prepareRunArtifactPaths, validatePreparedRunArtifactPaths, } from "../run/paths.js";
20
20
  import { isAnalysisRecordPath } from "../analysis/sharing.js";
21
+ import { LOCAL_ONLY_RUN_FILES } from "../run/local-only-files.js";
21
22
  import { assertPreparedSelectedOutputDirectory, prepareManagedHumanishOutputDirectory, prepareSelectedOutputDirectory, readContainedRegularFile, writeContainedOutputFile, } from "../run/contained-output.js";
22
23
  const DERIVATION_SCHEMA = "humanish.redacted-derivation.v1";
23
24
  const MAX_FILES = 10_000;
@@ -36,8 +37,7 @@ const OMITTED = new Map([
36
37
  ["observer/index.html", "regenerated Observer"],
37
38
  ["observer/observer-data.json", "regenerated Observer projection"],
38
39
  ["observer/study-analysis.json", "analysis must be regenerated against derivative evidence"],
39
- ["status.json", "local process status is not a new attempt"],
40
- ["sandbox-receipts.ndjson", "operational journal does not confer a derivative resource lease"],
40
+ ...LOCAL_ONLY_RUN_FILES,
41
41
  ]);
42
42
  const PNG_MAGIC = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
43
43
  function hash(bytes) {
@@ -21,6 +21,7 @@ import { chmodSync, closeSync, constants as fsConstants, existsSync, ftruncateSy
21
21
  import { homedir } from "node:os";
22
22
  import path from "node:path";
23
23
  import { loadEnvFile } from "./env-file.js";
24
+ import { cli } from "../cli/invocation.js";
24
25
  /** The only names implicit discovery may fill (and `humanish keys set` may store). Everything
25
26
  * else in an overlay/store file is ignored-and-named: a repo-planted NODE_OPTIONS/LD_PRELOAD
26
27
  * must never enter process env off a file the operator did not explicitly pass (an explicit
@@ -235,16 +236,16 @@ function storeLabel(env, deps) {
235
236
  export function missingKeyHint(name) {
236
237
  switch (name) {
237
238
  case "E2B_API_KEY":
238
- return "run `e2b auth login`, or `humanish keys set e2b`";
239
+ return `run \`e2b auth login\`, or \`${cli("keys set e2b")}\``;
239
240
  case "GH_TOKEN":
240
241
  case "GITHUB_TOKEN":
241
- return "run `gh auth login`, or `humanish keys set github`";
242
+ return `run \`gh auth login\`, or \`${cli("keys set github")}\``;
242
243
  case "OPENAI_API_KEY":
243
- return "run `humanish keys set openai`";
244
+ return `run \`${cli("keys set openai")}\``;
244
245
  case "ANTHROPIC_API_KEY":
245
- return "run `humanish keys set anthropic`";
246
+ return `run \`${cli("keys set anthropic")}\``;
246
247
  default:
247
- return `run \`humanish keys set ${name}\``;
248
+ return `run \`${cli(`keys set ${name}`)}\``;
248
249
  }
249
250
  }
250
251
  /** One shared suffix for *_KEYS_MISSING messages: where discovery looked, and what fills each
@@ -73,7 +73,7 @@ export function validateExposure(surface, request, live) {
73
73
  // admit nothing and expose nothing: watch --expose always requires edge auth, and the live-follow
74
74
  // preconditions are checked first so `--dry-run`/`--detach`/`--json` fail with the clearer reason.
75
75
  if (live && (live.dryRun || live.detach || live.json)) {
76
- return fail("EXPOSE_REQUIRES_LIVE_FOLLOW", "watch --expose streams a live desktop over an attached follow channel; it cannot combine with --dry-run, --detach, or --json.");
76
+ return fail("EXPOSE_REQUIRES_LIVE_FOLLOW", "watch --expose streams a live desktop over an attached follow channel. It cannot combine with --dry-run, --detach or --json, and without an interactive terminal it needs --serve.");
77
77
  }
78
78
  // --safe is a share_ready library filter for `serve`; watch streams a single live run that is
79
79
  // never share_ready, so --safe would silently do nothing here. Reject it rather than ignore it.
@@ -1,3 +1,4 @@
1
+ import { cli } from "../cli/invocation.js";
1
2
  // The pip color for each RunDisplay tone; a run with no display gets the neutral gray.
2
3
  const DISPLAY_TONES = {
3
4
  pass: "#3fb970",
@@ -8,8 +9,8 @@ const DISPLAY_TONES = {
8
9
  };
9
10
  export function renderLibraryHtml(history, opts) {
10
11
  const emptyState = opts.safe
11
- ? "No share_ready runs yet. Run `humanish verify` to see why."
12
- : "No runs yet. Run `humanish watch` to create one.";
12
+ ? `No share_ready runs yet. Run \`${cli("verify")}\` to see why.`
13
+ : `No runs yet. Run \`${cli("run first-run")}\` to create one.`;
13
14
  return `<!doctype html>
14
15
  <html lang="en" data-theme="dark">
15
16
  <head>
@@ -20,6 +20,7 @@ import { buildServeSecurityHeaders, hostAllowed, parsePublicOrigin } from "./htt
20
20
  import { listenOnLoopback } from "./listen.js";
21
21
  import { pinDirectChildDirectory, pinDirectory, readContainedFile, } from "./pinned-files.js";
22
22
  import { buildHistoryIndex, matchRunRoute, serveRunPath, writeResponse, } from "./run-routes.js";
23
+ import { cli } from "../cli/invocation.js";
23
24
  const OBSERVER_SCHEMA = "humanish.observer-result.v1";
24
25
  const observerRuntimeStreamUrls = new WeakMap();
25
26
  const observerPreparedRunPaths = new WeakMap();
@@ -136,7 +137,7 @@ export async function renderObserver(cwdInput, runInput, options = {}) {
136
137
  loaded.bundle.mode === "live"
137
138
  ? "Observer renders verified local evidence artifacts; runtime stream auth URLs are not persisted."
138
139
  : "This is a dry run: its participants are synthetic and no product behavior was tested.",
139
- "Before filing public feedback, use `humanish feedback issue` so redaction and public-safety checks gate the payload.",
140
+ `Before filing public feedback, use \`${cli("feedback issue")}\` so redaction and public-safety checks gate the payload.`,
140
141
  ...(openResult.warning ? [openResult.warning] : []),
141
142
  ],
142
143
  };
@@ -4,6 +4,7 @@
4
4
  import path from "node:path";
5
5
  import { isAnalysisRecordPath, projectShareCheckedAnalysis } from "../analysis/sharing.js";
6
6
  import { loadAnalysis } from "../analysis/load.js";
7
+ import { isLocalOnlyRunFile, isLocalOnlyRunFileIdentity } from "../run/local-only-files.js";
7
8
  import { listRuns } from "../run/stored-runs.js";
8
9
  import { readAnalysisAccounting } from "../run/costs.js";
9
10
  import { bindExistingRunArtifactPaths, isPathInside, isSafeRunIdSegment, } from "../run/paths.js";
@@ -70,9 +71,11 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
70
71
  // file serving bypass its current-content checks or a warmed source-only admission cache.
71
72
  const derivedRoot = cleanedRelativePath.split("/")[0];
72
73
  const derivedLeaf = path.posix.basename(cleanedRelativePath);
74
+ // Local-only files (raw sandbox ids, the recording pid) are read in process, never served.
73
75
  if (derivedRoot === ".analysis-lock" ||
74
76
  derivedLeaf.startsWith(".humanish-write-") ||
75
- isAnalysisRecordPath(cleanedRelativePath)) {
77
+ isAnalysisRecordPath(cleanedRelativePath) ||
78
+ isLocalOnlyRunFile(cleanedRelativePath)) {
76
79
  writeResponse(response, 404, "Not found", "text/plain; charset=utf-8");
77
80
  return;
78
81
  }
@@ -103,6 +106,10 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
103
106
  await serveContainedMedia(runRoot, filePath, response, request);
104
107
  return;
105
108
  }
109
+ if (await isLocalOnlyRunFileIdentity(root, filePath)) {
110
+ writeResponse(response, 404, "Not found", "text/plain; charset=utf-8");
111
+ return;
112
+ }
106
113
  try {
107
114
  const body = await readContainedFile(runRoot, filePath);
108
115
  if (!body) {
@@ -10,6 +10,7 @@ import { buildServeSecurityHeaders, hostAllowed, parsePublicOrigin, } from "./ht
10
10
  import { isSafeRunIdSegment } from "../run/paths.js";
11
11
  import { listRuns } from "../run/stored-runs.js";
12
12
  import { verifyRun } from "../verify/verify.js";
13
+ import { cli } from "../cli/invocation.js";
13
14
  export const SERVE_SCHEMA = "humanish.serve-result.v1";
14
15
  export function createShareSafetyAdmission(cwd, options = {}) {
15
16
  const verifyImpl = options.verifyImpl ?? verifyRun;
@@ -196,7 +197,7 @@ export async function serveObserverLibrary(cwdInput, options) {
196
197
  ok: false,
197
198
  error: {
198
199
  code: "HUMANISH_RUN_NOT_FOUND",
199
- message: "No run library found under .humanish/runs. Run `humanish watch` to create the first run.",
200
+ message: `No run library found under .humanish/runs. Run \`${cli("run first-run")}\` to create the first run.`,
200
201
  },
201
202
  };
202
203
  }
@@ -3,7 +3,7 @@ import { type StudyConfig } from "../../study/types.js";
3
3
  import { type ResolvedPersona } from "../../study/persona.js";
4
4
  import { type RunRerunLineage } from "../../run/bundle.js";
5
5
  import type { PreparedSelectedOutputDirectory } from "../../run/contained-output.js";
6
- import { type CuaActorStudyErrorCode, type CuaParticipantPlan, type CuaParticipantPlanEntry, type DesktopParticipantRun } from "./types.js";
6
+ import { type CuaActorStudyErrorCode, type CuaParticipantPlan, type CuaParticipantPlanEntry, type DesktopParticipantRun, type ParticipantRunsAndPlan } from "./types.js";
7
7
  export declare function defaultSessionTimeoutMs(config: StudyConfig): number;
8
8
  /** Per-participant sandbox deadline (each one owns its own desktop). Mirrors the
9
9
  * single-participant formula verbatim so N=1 stays byte-stable: explicit sandboxTimeoutMs, else session budget + (clone
@@ -21,17 +21,14 @@ export declare function resolveParticipantSandboxMs(config: StudyConfig): number
21
21
  */
22
22
  export declare function boundedConcurrency(declared: number | undefined, participantCount: number): number;
23
23
  /**
24
- * Pure pre-flight plan resolver (runs in dry-run and live). Returns the participant table, the
25
- * effective concurrency, the wave count, the per-participant session budget, and the worst-case total
26
- * sandbox-minutes, before any sandbox or provider call. The same plan appears in dry-run,
27
- * marked $0 (dryRun: true).
24
+ * Build the participant runs and the public plan from a computer-use plan (pure). The same plan
25
+ * appears in dry-run and live, marked $0 for a dry run. `env` may lower the planned concurrency;
26
+ * `personas` are the compiled committed personas the prompts carry.
28
27
  */
29
- export declare function resolveCuaParticipantPlan(config: StudyConfig, opts?: {
30
- countOverride?: number;
28
+ export declare function participantRunsAndPlan(plan: ComputerUsePlan, opts?: {
31
29
  env?: Record<string, string | undefined>;
32
- dryRun?: boolean;
33
30
  personas?: Map<string, ResolvedPersona>;
34
- }): CuaParticipantPlan;
31
+ }): ParticipantRunsAndPlan;
35
32
  /** Print the participant plan to stderr before any sandbox/provider call (public-safe: ids, devices,
36
33
  * digests, and budgets only; no prompt text, no secrets). */
37
34
  export declare function emitPreflightPlan(participantPlan: CuaParticipantPlan, studyId: string): void;
@@ -4,7 +4,6 @@
4
4
  // compiles the committed personas first and narrows a rerun to its selected participants.
5
5
  import { isLocalBrowserStudy, LOCAL_BROWSER_LIFETIME_MS, } from "../../substrates/local/runtime-config.js";
6
6
  import { DEFAULT_STATE_STEP_TIMEOUT_MS } from "../../subject/state.js";
7
- import { computerUseParticipants, } from "../../study/plan-participants.js";
8
7
  import { resolveParticipant } from "../../run/participant.js";
9
8
  import {} from "../../study/types.js";
10
9
  import { scrubPersonaBrief } from "../../study/persona.js";
@@ -71,23 +70,17 @@ function envLoweredConcurrency(planned, participantCount, env) {
71
70
  }
72
71
  return { bound: planned };
73
72
  }
74
- /** The same inputs from a config, for callers that have no plan. */
75
- function plannedParticipantsOf(config, countOverride) {
76
- const participants = computerUseParticipants(config, countOverride);
77
- return {
78
- participants,
79
- concurrency: boundedConcurrency(config.execution?.concurrency, participants.length),
80
- sessionBudgetMs: config.execution?.timeoutMs ?? defaultSessionTimeoutMs(config),
81
- sandboxMs: resolveParticipantSandboxMs(config),
82
- desktopCli: config.subject.source === "desktop-cli",
83
- };
84
- }
85
- /** Build the participant runs and the public plan from the planned participants (pure). */
86
- function participantRunsAndPlan(planned, opts = {}) {
73
+ /**
74
+ * Build the participant runs and the public plan from a computer-use plan (pure). The same plan
75
+ * appears in dry-run and live, marked $0 for a dry run. `env` may lower the planned concurrency;
76
+ * `personas` are the compiled committed personas the prompts carry.
77
+ */
78
+ export function participantRunsAndPlan(plan, opts = {}) {
87
79
  const env = opts.env ?? {};
88
80
  // Who each participant is (id, persona, focus, device, limits) comes from the plan; this adds
89
81
  // what the route derives from it: the prompt, bundle ids and artifact paths.
90
- const { participants } = planned;
82
+ const { participants } = plan.runner;
83
+ const desktopCli = plan.runner.subject.kind === "desktop-cli";
91
84
  const participantCount = participants.length;
92
85
  const runs = participants.map((participant) => {
93
86
  const mission = participant.assignment.mission ?? DEFAULT_MISSION;
@@ -102,7 +95,7 @@ function participantRunsAndPlan(planned, opts = {}) {
102
95
  ...(resolvedPersona === undefined ? {} : { resolvedPersona }),
103
96
  ...(focus === undefined ? {} : { instruction: focus }),
104
97
  device: { name: device.name, preset: device.preset },
105
- ...(planned.desktopCli ? { surface: "desktop-cli" } : {}),
98
+ ...(desktopCli ? { surface: "desktop-cli" } : {}),
106
99
  });
107
100
  const run = resolveParticipant(participant, {
108
101
  persona: composed.persona,
@@ -119,9 +112,9 @@ function participantRunsAndPlan(planned, opts = {}) {
119
112
  traceArtifactPath: participantCount === 1 ? "actor.json" : `actors/${run.streamId}.json`,
120
113
  };
121
114
  });
122
- const resolved = envLoweredConcurrency(planned.concurrency, participantCount, env);
115
+ const resolved = envLoweredConcurrency(plan.concurrency, participantCount, env);
123
116
  const concurrency = resolved.bound;
124
- const { sessionBudgetMs, sandboxMs } = planned;
117
+ const { sessionBudgetMs, sandboxMs } = plan;
125
118
  const participantPlan = {
126
119
  strategy: CUA_FANOUT_STRATEGY,
127
120
  laneCount: participantCount,
@@ -132,7 +125,7 @@ function participantRunsAndPlan(planned, opts = {}) {
132
125
  waves: Math.ceil(participantCount / concurrency),
133
126
  perLaneSessionBudgetMs: sessionBudgetMs,
134
127
  worstCaseSandboxMinutes: Math.round((participantCount * sandboxMs) / 60_000),
135
- dryRun: opts.dryRun === true,
128
+ dryRun: plan.dryRun,
136
129
  lanes: runs.map((spec) => ({
137
130
  id: spec.planned.id,
138
131
  ...(spec.planned.labels.actorType === undefined
@@ -162,16 +155,6 @@ function participantRunsAndPlan(planned, opts = {}) {
162
155
  };
163
156
  return { runs, participantPlan };
164
157
  }
165
- /**
166
- * Pure pre-flight plan resolver (runs in dry-run and live). Returns the participant table, the
167
- * effective concurrency, the wave count, the per-participant session budget, and the worst-case total
168
- * sandbox-minutes, before any sandbox or provider call. The same plan appears in dry-run,
169
- * marked $0 (dryRun: true).
170
- */
171
- export function resolveCuaParticipantPlan(config, opts = {}) {
172
- const { countOverride, ...rest } = opts;
173
- return participantRunsAndPlan(plannedParticipantsOf(config, countOverride), rest).participantPlan;
174
- }
175
158
  /** Print the participant plan to stderr before any sandbox/provider call (public-safe: ids, devices,
176
159
  * digests, and budgets only; no prompt text, no secrets). */
177
160
  export function emitPreflightPlan(participantPlan, studyId) {
@@ -216,13 +199,10 @@ export async function loadCuaParticipants(args) {
216
199
  // Compile any committed personas before planning, so the plan builder stays pure and each participant's
217
200
  // prompt carries real behavioral directives rather than a bare `Persona: <id>.` label.
218
201
  const personas = await compileParticipantPersonas(args.projectRoot, participants.map((participant) => participant.personaId));
219
- const { runs: participantRuns, participantPlan } = participantRunsAndPlan({
220
- participants,
221
- concurrency: plan.concurrency,
222
- sessionBudgetMs: plan.sessionBudgetMs,
223
- sandboxMs: plan.sandboxMs,
224
- desktopCli: plan.runner.subject.kind === "desktop-cli",
225
- }, { env: args.env, dryRun: plan.dryRun, personas });
202
+ const { runs: participantRuns, participantPlan } = participantRunsAndPlan(plan, {
203
+ env: args.env,
204
+ personas,
205
+ });
226
206
  const rerun = plan.rerun;
227
207
  if (!rerun)
228
208
  return { ok: true, participantRuns, participantPlan };
@@ -1,7 +1,7 @@
1
1
  import { type CuaActorDescriptor } from "../../actors/registry.js";
2
2
  import type { ComputerUsePlan, ComputerUseRunner, RoutePlanResult, RouteRefusal } from "../../study/plan-types.js";
3
3
  import type { StudyConfig, StudySubjectState } from "../../study/types.js";
4
- import { type CuaActorStudyErrorCode, type RunCuaActorStudyOptions } from "./types.js";
4
+ import { type ComputerUseRunInput, type CuaActorStudyErrorCode } from "./types.js";
5
5
  /** The error a computer-use study returns before a run starts. */
6
6
  export interface ComputerUseRefusal extends RouteRefusal<"computer-use", CuaActorStudyErrorCode> {
7
7
  /**
@@ -44,5 +44,5 @@ export declare function planComputerUseStudy(config: StudyConfig, input: {
44
44
  /** Which of the caller's driving homes are set; neither when absent. */
45
45
  readonly driving?: CallerDriving;
46
46
  readonly countOverride?: number;
47
- readonly rerun?: RunCuaActorStudyOptions["rerun"];
47
+ readonly rerun?: ComputerUseRunInput["rerun"];
48
48
  }): ComputerUsePlanResult;
@@ -5,6 +5,7 @@
5
5
  import { resolveAutomaticAnalysis } from "../../analysis/automatic-config.js";
6
6
  import { actorRegistry, isCuaActorDescriptor, } from "../../actors/registry.js";
7
7
  import { isHttpUrl, isLoopbackUrl } from "../../study/parse/subject.js";
8
+ import { studyUrlCredentialReason } from "../../study/url-credentials.js";
8
9
  import { subjectStateInvalidReason } from "../../study/parse/subject-state.js";
9
10
  import { brainOf, callerBrainOf, capsOf, desktopRequirements, planBase, provisionedSubject, } from "../../study/plan-base.js";
10
11
  import { computerUseParticipants, declaredTargets } from "../../study/plan-participants.js";
@@ -134,6 +135,10 @@ function subjectStructureReason(config, subjectRoute) {
134
135
  */
135
136
  function entryTargetReason(config, subjectRoute) {
136
137
  const { desktopCliRoute, provisionedRoute, localAppSubject, appUrl } = subjectRoute;
138
+ // A library caller's config skips the parser, which refuses these first.
139
+ const credential = studyUrlCredentialReason(config);
140
+ if (credential)
141
+ return { code: "HUMANISH_COMPUTER_USE_SUBJECT_UNSAFE", message: credential };
137
142
  if (desktopCliRoute)
138
143
  return undefined;
139
144
  const allowPublicTargets = config.policies?.allowPublicTargets === true;
@@ -227,7 +232,7 @@ export function planComputerUseStudy(config, input) {
227
232
  // participants. It comes after the rules above, so a shared-world config that breaks one of them,
228
233
  // which runStudyWith sends here, still gets that rule's message.
229
234
  if (config.subject.topology === "shared-world")
230
- return refuse("in-scope", "HUMANISH_COMPUTER_USE_SUBJECT_INVALID", "route: shared-world studies run every participant against one shared app on the shared-world route; this route runs independent participants. Run the study with runStudy or runConcurrentSharedWorld.", actor);
235
+ return refuse("in-scope", "HUMANISH_COMPUTER_USE_SUBJECT_INVALID", "route: shared-world studies run every participant against one shared app on the shared-world route; this route runs independent participants. The shared-world route takes subject.source clone or local-tree, or app-url with policies.allowPublicTargets: true, on execution.target e2b-desktop.", actor);
231
236
  // The in-process route drives subject.appUrl on this machine and creates no desktop, so it would
232
237
  // skip the subject a clone, local-tree or desktop-cli study declares.
233
238
  const source = config.subject.source;
@@ -1,33 +1,20 @@
1
+ import { type RefusedStudy } from "../../run/route-shell.js";
1
2
  import type { ComputerUsePlan } from "../../study/plan-types.js";
2
3
  import type { AdmittedPlan } from "../../run-study.js";
3
4
  import type { StudyConfig } from "../../study/types.js";
4
5
  import { type ComputerUseRefusal } from "./plan.js";
5
- import { type ComputerUseRunInput, type CuaActorStudyResult, type RunCuaActorStudyOptions } from "./types.js";
6
- /**
7
- * Plans and runs a computer-use study in one call. It is not exported from src/index.ts; tests call
8
- * it. It plans the config with planComputerUseStudy and runs
9
- * the plan with runComputerUsePlan, whose run scope and withTransientCommsSecrets wrapper cover the
10
- * run and its analysis.
11
- */
12
- export declare function runCuaActorStudy(options: RunCuaActorStudyOptions): Promise<CuaActorStudyResult>;
6
+ import { type ComputerUseRunInput, type CuaActorStudyResult } from "./types.js";
13
7
  /**
14
8
  * A refused computer-use study's result, at the refusal's stage: a before-scope refusal has its own
15
9
  * envelope and no analysis record; the others come after the cwd checks, and the participant cap after
16
10
  * the personas are read.
17
11
  */
18
- export declare function computerUseStudyRefusal(options: RunCuaActorStudyOptions, refusal: ComputerUseRefusal): Promise<CuaActorStudyResult>;
12
+ export declare function computerUseStudyRefusal(options: ComputerUseRunInput & RefusedStudy, refusal: ComputerUseRefusal): Promise<CuaActorStudyResult>;
19
13
  /**
20
14
  * runStudyWith's step for a computer-use plan. It runs the plan's local checks (admitCuaRun: the
21
15
  * participants, the preflight plan, the keys, the local agent, subject env and caps, and the
22
16
  * local-tree archive) before any run scope opens, so the CLI can present their refusal before it
23
- * loads a declared scorer, and returns the run that continues from them with that scorer.
17
+ * loads a declared scorer, and returns the run that continues from them with that scorer. The
18
+ * run and its analysis share one scope for the email secrets it registers.
24
19
  */
25
20
  export declare function admitComputerUsePlan(plan: ComputerUsePlan, input: ComputerUseRunInput, config: StudyConfig): Promise<AdmittedPlan<"computer-use">>;
26
- /**
27
- * Run a computer-use plan: its local checks, then the run. The run scope in runAdmittedCuaRun
28
- * finalizes any status record the run opened, on every exit, so a test or library caller does not
29
- * leave the 5 s status cadence writing into a directory something else is deleting (an unrelated
30
- * ENOTEMPTY). Participants, the brain, the subject, the caps and the residual config come from the
31
- * plan. `config` is what the caller's createProvider and inProcess executor receive.
32
- */
33
- export declare function runComputerUsePlan(plan: ComputerUsePlan, input: ComputerUseRunInput, config: StudyConfig): Promise<CuaActorStudyResult>;