humanish 0.89.1 → 0.91.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 (99) hide show
  1. package/README.md +20 -1
  2. package/dist/automatic-analysis-completion.d.ts +25 -0
  3. package/dist/automatic-analysis-completion.js +56 -0
  4. package/dist/automatic-analysis-completion.js.map +1 -0
  5. package/dist/automatic-analysis-config.d.ts +25 -0
  6. package/dist/automatic-analysis-config.js +44 -0
  7. package/dist/automatic-analysis-config.js.map +1 -0
  8. package/dist/automatic-study-analysis.d.ts +15 -0
  9. package/dist/automatic-study-analysis.js +187 -0
  10. package/dist/automatic-study-analysis.js.map +1 -0
  11. package/dist/concurrent-shared-world-lab.d.ts +4 -2
  12. package/dist/concurrent-shared-world-lab.js +10 -3
  13. package/dist/concurrent-shared-world-lab.js.map +1 -1
  14. package/dist/cua-actor-lab.d.ts +4 -2
  15. package/dist/cua-actor-lab.js +10 -5
  16. package/dist/cua-actor-lab.js.map +1 -1
  17. package/dist/e2b-terminal-lab.d.ts +4 -2
  18. package/dist/e2b-terminal-lab.js +15 -3
  19. package/dist/e2b-terminal-lab.js.map +1 -1
  20. package/dist/export-bundle.js +2 -0
  21. package/dist/export-bundle.js.map +1 -1
  22. package/dist/index.d.ts +6 -1
  23. package/dist/index.js +2 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/lab-config.d.ts +5 -0
  26. package/dist/lab-config.js +21 -2
  27. package/dist/lab-config.js.map +1 -1
  28. package/dist/lab-engine.d.ts +2 -0
  29. package/dist/lab-engine.js +12 -3
  30. package/dist/lab-engine.js.map +1 -1
  31. package/dist/lab-preflight.d.ts +3 -0
  32. package/dist/lab-preflight.js +3 -0
  33. package/dist/lab-preflight.js.map +1 -1
  34. package/dist/lab-summary.d.ts +4 -0
  35. package/dist/lab-summary.js +6 -1
  36. package/dist/lab-summary.js.map +1 -1
  37. package/dist/observer-app.html +8 -8
  38. package/dist/observer.d.ts +3 -0
  39. package/dist/observer.js +23 -6
  40. package/dist/observer.js.map +1 -1
  41. package/dist/oss-lab.d.ts +1 -1
  42. package/dist/oss-lab.js.map +1 -1
  43. package/dist/oss-meta-lab.d.ts +1 -1
  44. package/dist/oss-meta-lab.js.map +1 -1
  45. package/dist/program.d.ts +5 -0
  46. package/dist/program.js +49 -8
  47. package/dist/program.js.map +1 -1
  48. package/dist/run-detail.d.ts +2 -0
  49. package/dist/run-detail.js +3 -0
  50. package/dist/run-detail.js.map +1 -1
  51. package/dist/run.d.ts +1 -1
  52. package/dist/run.js.map +1 -1
  53. package/dist/scripted-browser-lab.d.ts +4 -2
  54. package/dist/scripted-browser-lab.js +14 -9
  55. package/dist/scripted-browser-lab.js.map +1 -1
  56. package/dist/shared-world-lab.d.ts +4 -2
  57. package/dist/shared-world-lab.js +10 -3
  58. package/dist/shared-world-lab.js.map +1 -1
  59. package/dist/study-analysis-engine.d.ts +7 -2
  60. package/dist/study-analysis-engine.js +26 -6
  61. package/dist/study-analysis-engine.js.map +1 -1
  62. package/dist/study-analysis-evidence.js +273 -59
  63. package/dist/study-analysis-evidence.js.map +1 -1
  64. package/dist/study-analysis-job.d.ts +88 -0
  65. package/dist/study-analysis-job.js +197 -0
  66. package/dist/study-analysis-job.js.map +1 -0
  67. package/dist/study-analysis-service.d.ts +10 -1
  68. package/dist/study-analysis-service.js +31 -8
  69. package/dist/study-analysis-service.js.map +1 -1
  70. package/dist/study-analysis-sharing.js +13 -5
  71. package/dist/study-analysis-sharing.js.map +1 -1
  72. package/dist/study-analysis-store.d.ts +4 -0
  73. package/dist/study-analysis-store.js +40 -1
  74. package/dist/study-analysis-store.js.map +1 -1
  75. package/dist/study-analysis-validation.d.ts +135 -1
  76. package/dist/study-analysis-validation.js +62 -26
  77. package/dist/study-analysis-validation.js.map +1 -1
  78. package/dist/study-analysis.d.ts +14 -0
  79. package/dist/study-analysis.js.map +1 -1
  80. package/dist/terminal-participant-activity.d.ts +6 -0
  81. package/dist/terminal-participant-activity.js +34 -0
  82. package/dist/terminal-participant-activity.js.map +1 -0
  83. package/dist/tui-actions.d.ts +1 -1
  84. package/dist/tui-actions.js +15 -1
  85. package/dist/tui-actions.js.map +1 -1
  86. package/dist/tui-app.js +116 -116
  87. package/dist/tui-contract.d.ts +3 -2
  88. package/dist/tui-contract.js.map +1 -1
  89. package/docs/architecture/examples/state-driven-local-app/README.md +5 -0
  90. package/docs/architecture/examples/state-driven-local-app/runner.mjs +1 -0
  91. package/docs/contracts/schemas.md +1 -1
  92. package/docs/contracts/study-analysis.md +47 -3
  93. package/docs/goals/current.md +4 -4
  94. package/docs/principles/invariants-and-defaults.md +7 -3
  95. package/docs/product/automatic-analysis.md +81 -0
  96. package/docs/ramp/README.md +11 -2
  97. package/docs/release/0.90.0-automatic-analysis.md +21 -0
  98. package/docs/release/0.91.0-analysis-quality-and-defaults.md +33 -0
  99. package/package.json +1 -1
package/README.md CHANGED
@@ -50,7 +50,9 @@ npx humanish observe --run latest --open
50
50
 
51
51
  Existing `E2B_API_KEY` and `OPENAI_API_KEY` environment variables also work.
52
52
  `try-live` clones and studies drawDB, not your project. Its **$2 cap covers
53
- estimated model spend**; hosted desktop time is additional. Caps are checked
53
+ estimated participant model spend**. Post-run analysis defaults to a separate
54
+ **$3 admission estimate limit**; set `review.analysis: false` to disable it.
55
+ Hosted desktop time is additional. Participant caps are checked
54
56
  between turns and are not provider billing ceilings. Allow a few minutes for
55
57
  the app to build and the participant to work. See [budgets and privacy](https://humanish.dev/docs/budgets-and-privacy).
56
58
 
@@ -104,6 +106,23 @@ beside Participants in Observer, with evidence links and separate participant
104
106
  feedback. See the [analysis contract](docs/contracts/study-analysis.md) for
105
107
  coverage limits, estimated cost controls, corrections and sharing behavior.
106
108
 
109
+ Supported live studies generate findings automatically after participants finish,
110
+ using a separate $3 admission estimate limit with `gpt-6-astra` and high reasoning.
111
+ This is additional to participant execution costs, and is not a hard provider
112
+ billing cap. To disable the extra request:
113
+
114
+ ```yaml
115
+ review:
116
+ analysis: false
117
+ ```
118
+
119
+ `humanish run <lab>`, `humanish lab run <lab>`, `humanish watch <lab>`, and live
120
+ starts in the TUI share this default. Dry runs and unsupported routes dispatch
121
+ nothing. Without `OPENAI_API_KEY`, default analysis is skipped and a successful
122
+ recording stays successful. Opening the TUI or Observer never starts a request.
123
+ See [automatic analysis](docs/product/automatic-analysis.md) for configuration,
124
+ cancellation, and failure behavior.
125
+
107
126
  ## Public-Safety Boundary
108
127
 
109
128
  Humanish is designed for public repositories and public issue queues. The
@@ -0,0 +1,25 @@
1
+ import { type PreparedRunArtifactPaths } from "./run-paths.js";
2
+ import type { StudyAnalysisConfig } from "./study-analysis.js";
3
+ import { runAutomaticStudyAnalysis, type AutomaticStudyAnalysisDeps } from "./automatic-study-analysis.js";
4
+ import type { AutomaticStudyAnalysisOutcome } from "./study-analysis-job.js";
5
+ export interface AutomaticAnalysisHooks {
6
+ /** Provider/test dependencies apply only to analysis, never to the participant. */
7
+ deps?: AutomaticStudyAnalysisDeps;
8
+ /** Called after the source is finalized; the returned cleanup always runs. */
9
+ onStart?: () => void | (() => void);
10
+ run?: typeof runAutomaticStudyAnalysis;
11
+ }
12
+ export interface AutomaticAnalysisResult {
13
+ automaticAnalysis?: AutomaticStudyAnalysisOutcome;
14
+ /** Distinguishes the default missing-key skip from failure of an explicit request. */
15
+ automaticAnalysisTrigger?: "default" | "explicit";
16
+ }
17
+ /** Internal producer receipt: call only after final source publication, never for an early refusal. */
18
+ export declare function markFinalizedStudyResult<T extends object>(result: T, prepared: PreparedRunArtifactPaths): T;
19
+ /** One post-completion boundary shared by all live recording producers. */
20
+ export declare function completeAutomaticAnalysis<T extends {
21
+ cwd: string;
22
+ runId: string;
23
+ dryRun: boolean;
24
+ }>(result: T, config: StudyAnalysisConfig | undefined, hooks?: AutomaticAnalysisHooks, trigger?: "default" | "explicit"): Promise<T & AutomaticAnalysisResult>;
25
+ export declare function automaticAnalysisSucceeded(result: AutomaticAnalysisResult): boolean;
@@ -0,0 +1,56 @@
1
+ import path from "node:path";
2
+ import { validatePreparedRunRootIdentity } from "./run-paths.js";
3
+ import { runAutomaticStudyAnalysis } from "./automatic-study-analysis.js";
4
+ const finalizedResults = new WeakMap();
5
+ /** Internal producer receipt: call only after final source publication, never for an early refusal. */
6
+ export function markFinalizedStudyResult(result, prepared) {
7
+ finalizedResults.set(result, prepared);
8
+ return result;
9
+ }
10
+ /** One post-completion boundary shared by all live recording producers. */
11
+ export async function completeAutomaticAnalysis(result, config, hooks, trigger = "explicit") {
12
+ if (config === undefined)
13
+ return result;
14
+ const origin = trigger === "default" ? { automaticAnalysisTrigger: trigger } : {};
15
+ if (result.dryRun)
16
+ return { ...result, ...origin, automaticAnalysis: { state: "skipped", reason: "analysis_dry_run" } };
17
+ // An early refusal can echo a caller-supplied ID belonging to an older run.
18
+ // Require this invocation's internal final-publication receipt before reading that ID.
19
+ const prepared = finalizedResults.get(result);
20
+ if (!result.runId || result.runId === "not-created" || prepared === undefined) {
21
+ return { ...result, ...origin, automaticAnalysis: { state: "skipped", reason: "analysis_source_unavailable" } };
22
+ }
23
+ let cleanup = undefined;
24
+ try {
25
+ cleanup = hooks?.onStart?.();
26
+ try {
27
+ await validatePreparedRunRootIdentity(prepared);
28
+ }
29
+ catch {
30
+ return { ...result, ...origin, automaticAnalysis: { state: "failed", reason: "analysis_source_changed" } };
31
+ }
32
+ const sourceCwd = path.dirname(path.dirname(prepared.physicalRunsRoot));
33
+ const automaticAnalysis = await (hooks?.run ?? runAutomaticStudyAnalysis)(sourceCwd, result.runId, config, { ...hooks?.deps, ...(trigger === "default" ? { defaultRequest: true } : {}), expectedRun: prepared });
34
+ return { ...result, ...origin, automaticAnalysis };
35
+ }
36
+ catch {
37
+ // Preserve the producer result. Never put a hook exception or provider response in the envelope.
38
+ return { ...result, ...origin, automaticAnalysis: { state: "failed", reason: "analysis_automatic_failed" } };
39
+ }
40
+ finally {
41
+ try {
42
+ cleanup?.();
43
+ }
44
+ catch { /* Cleanup cannot erase accounting or the completed recording. */ }
45
+ }
46
+ }
47
+ export function automaticAnalysisSucceeded(result) {
48
+ const value = result.automaticAnalysis;
49
+ if (value === undefined)
50
+ return true;
51
+ if (value.state === "skipped")
52
+ return value.reason === "analysis_dry_run"
53
+ || (result.automaticAnalysisTrigger === "default" && ["AUTOMATIC_ANALYSIS_KEY_MISSING", "AUTOMATIC_ANALYSIS_NO_PARTICIPANT_EVIDENCE"].includes(value.reason ?? ""));
54
+ return (value.state === "complete" || value.state === "partial") && value.result?.ok === true;
55
+ }
56
+ //# sourceMappingURL=automatic-analysis-completion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automatic-analysis-completion.js","sourceRoot":"","sources":["../src/automatic-analysis-completion.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,+BAA+B,EAAiC,MAAM,gBAAgB,CAAC;AAEhG,OAAO,EAAE,yBAAyB,EAAmC,MAAM,+BAA+B,CAAC;AAiB3G,MAAM,gBAAgB,GAAG,IAAI,OAAO,EAAoC,CAAC;AAEzE,uGAAuG;AACvG,MAAM,UAAU,wBAAwB,CAAmB,MAAS,EAAE,QAAkC;IACtG,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAE5C,MAAS,EAAE,MAAuC,EAAE,KAA8B,EAAE,UAAkC,UAAU;IACjI,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,MAAM,MAAM,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,wBAAwB,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAClF,IAAI,MAAM,CAAC,MAAM;QAAE,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,CAAC;IACxH,4EAA4E;IAC5E,uFAAuF;IACvF,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,KAAK,aAAa,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC9E,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,6BAA6B,EAAE,EAAE,CAAC;IAClH,CAAC;IACD,IAAI,OAAO,GAAwB,SAAS,CAAC;IAC7C,IAAI,CAAC;QACH,OAAO,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;QAC7B,IAAI,CAAC;YAAC,MAAM,+BAA+B,CAAC,QAAQ,CAAC,CAAC;QAAC,CAAC;QACxD,MAAM,CAAC;YAAC,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,yBAAyB,EAAE,EAAE,CAAC;QAAC,CAAC;QACrH,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACxE,MAAM,iBAAiB,GAAG,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,yBAAyB,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EACvG,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzG,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,iBAAiB,EAAE,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,iGAAiG;QACjG,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,2BAA2B,EAAE,EAAE,CAAC;IAC/G,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YAAC,OAAO,EAAE,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,iEAAiE,CAAC,CAAC;IAClG,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAA+B;IACxE,MAAM,KAAK,GAAG,MAAM,CAAC,iBAAiB,CAAC;IACvC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,MAAM,KAAK,kBAAkB;eACpE,CAAC,MAAM,CAAC,wBAAwB,KAAK,SAAS,IAAI,CAAC,gCAAgC,EAAE,4CAA4C,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC;IACtK,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,UAAU,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC;AAChG,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { StudyAnalysisConfig } from "./study-analysis.js";
2
+ /** A separately budgeted review after a live participant study. */
3
+ export interface LabAnalysis {
4
+ maxCostUsd: number;
5
+ model?: string;
6
+ question?: string;
7
+ timeoutMs?: number;
8
+ maxOutputTokens?: number;
9
+ }
10
+ /** Called for parsed manifests AND direct library configs, before participant execution. */
11
+ export declare function resolveAutomaticAnalysis(raw: unknown): {
12
+ ok: true;
13
+ config: StudyAnalysisConfig | undefined;
14
+ } | {
15
+ ok: false;
16
+ message: string;
17
+ };
18
+ export interface AutomaticAnalysisBudget {
19
+ model: string;
20
+ maxCostUsd: number;
21
+ trigger: "default" | "explicit";
22
+ }
23
+ /** Metadata only: resolving the future live-run budget never reads keys or dispatches. */
24
+ export declare function automaticAnalysisBudget(raw: unknown, backend: string): AutomaticAnalysisBudget | undefined;
25
+ export declare function formatAutomaticAnalysisBudget(budget: AutomaticAnalysisBudget): string;
@@ -0,0 +1,44 @@
1
+ import { SUPPORTED_STUDY_ANALYSIS_MODELS } from "./study-analysis-engine.js";
2
+ import { containsSensitive } from "./redaction.js";
3
+ const MODELS = new Set(SUPPORTED_STUDY_ANALYSIS_MODELS);
4
+ const FIELDS = new Set(["maxCostUsd", "model", "question", "timeoutMs", "maxOutputTokens"]);
5
+ /** Called for parsed manifests AND direct library configs, before participant execution. */
6
+ export function resolveAutomaticAnalysis(raw) {
7
+ if (raw === false)
8
+ return { ok: true, config: undefined };
9
+ if (raw === undefined)
10
+ raw = { maxCostUsd: 3 };
11
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)
12
+ || Object.keys(raw).some(key => !FIELDS.has(key))) {
13
+ return { ok: false, message: "review.analysis must be false or a mapping containing only maxCostUsd, model, question, timeoutMs and maxOutputTokens." };
14
+ }
15
+ const value = raw;
16
+ const { maxCostUsd } = value;
17
+ const model = value.model === undefined ? "gpt-6-astra" : value.model;
18
+ const question = value.question === undefined ? null : value.question;
19
+ const timeoutMs = value.timeoutMs === undefined ? 300_000 : value.timeoutMs;
20
+ const maxOutputTokens = value.maxOutputTokens === undefined ? 16_384 : value.maxOutputTokens;
21
+ if (typeof maxCostUsd !== "number" || !Number.isFinite(maxCostUsd) || maxCostUsd <= 0 || maxCostUsd > 1000
22
+ || typeof model !== "string" || !MODELS.has(model)
23
+ || typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 600_000
24
+ || typeof maxOutputTokens !== "number" || !Number.isSafeInteger(maxOutputTokens) || maxOutputTokens < 256 || maxOutputTokens > 32_768
25
+ || (value.question !== undefined && (typeof question !== "string" || question.length > 4000))) {
26
+ return { ok: false, message: "review.analysis requires a positive maxCostUsd up to 1000, a supported analysis model, timeoutMs 1–600000, maxOutputTokens 256–32768, and an optional question of at most 4000 characters." };
27
+ }
28
+ if (question !== null && containsSensitive(question)) {
29
+ return { ok: false, message: "review.analysis.question contains sensitive text and cannot be sent for analysis." };
30
+ }
31
+ return { ok: true, config: { model, maxCostUsd, timeoutMs, maxOutputTokens, question: question } };
32
+ }
33
+ /** Metadata only: resolving the future live-run budget never reads keys or dispatches. */
34
+ export function automaticAnalysisBudget(raw, backend) {
35
+ if (!["cua", "scripted", "terminal", "shared-world", "concurrent-shared-world"].includes(backend))
36
+ return undefined;
37
+ const resolved = resolveAutomaticAnalysis(raw);
38
+ return resolved.ok && resolved.config ? { model: resolved.config.model, maxCostUsd: resolved.config.maxCostUsd,
39
+ trigger: raw === undefined ? "default" : "explicit" } : undefined;
40
+ }
41
+ export function formatAutomaticAnalysisBudget(budget) {
42
+ return `After live runs: ${budget.trigger} analysis · ${budget.model} · separate $${budget.maxCostUsd} admission estimate limit (not a provider billing cap). Set review.analysis: false to disable.`;
43
+ }
44
+ //# sourceMappingURL=automatic-analysis-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automatic-analysis-config.js","sourceRoot":"","sources":["../src/automatic-analysis-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAYnD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAS,+BAA+B,CAAC,CAAC;AAChE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAE5F,4FAA4F;AAC5F,MAAM,UAAU,wBAAwB,CAAC,GAAY;IAGnD,IAAI,GAAG,KAAK,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC1D,IAAI,GAAG,KAAK,SAAS;QAAE,GAAG,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IAC/C,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;WAC5D,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACpD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,wHAAwH,EAAE,CAAC;IAC1J,CAAC;IACD,MAAM,KAAK,GAAG,GAA8B,CAAC;IAC7C,MAAM,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;IACtE,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;IACtE,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC;IAC5E,MAAM,eAAe,GAAG,KAAK,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC;IAC7F,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,IAAI,CAAC,IAAI,UAAU,GAAG,IAAI;WACrG,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;WAC/C,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,OAAO;WACzG,OAAO,eAAe,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,IAAI,eAAe,GAAG,GAAG,IAAI,eAAe,GAAG,MAAM;WAClI,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC;QAChG,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,4LAA4L,EAAE,CAAC;IAC9N,CAAC;IACD,IAAI,QAAQ,KAAK,IAAI,IAAI,iBAAiB,CAAC,QAAkB,CAAC,EAAE,CAAC;QAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,mFAAmF,EAAE,CAAC;IACrH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAyB,EAAE,EAAE,CAAC;AACtH,CAAC;AAQD,0FAA0F;AAC1F,MAAM,UAAU,uBAAuB,CAAC,GAAY,EAAE,OAAe;IACnE,IAAI,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,yBAAyB,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IACpH,MAAM,QAAQ,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAC/C,OAAO,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU;QAC5G,OAAO,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,MAA+B;IAC3E,OAAO,oBAAoB,MAAM,CAAC,OAAO,eAAe,MAAM,CAAC,KAAK,gBAAgB,MAAM,CAAC,UAAU,gGAAgG,CAAC;AACxM,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type AnalyzeDeps } from "./study-analysis-service.js";
2
+ import { type AutomaticStudyAnalysisView, type AutomaticStudyAnalysisOutcome, type AutomaticStudyAnalysisCancellation } from "./study-analysis-job.js";
3
+ import type { StudyAnalysisConfig } from "./study-analysis.js";
4
+ export type { AutomaticStudyAnalysisView, AutomaticStudyAnalysisOutcome, AutomaticStudyAnalysisCancellation } from "./study-analysis-job.js";
5
+ export type AutomaticStudyAnalysisDeps = Omit<AnalyzeDeps, "analysisId" | "beforeDispatch"> & {
6
+ /** A missing default key records a skip before admission, preserving a successful recording. */
7
+ defaultRequest?: boolean;
8
+ };
9
+ /** Small read-only TUI/CLI projection. Neither this nor Observer can resume a job. */
10
+ export declare function readAutomaticStudyAnalysis(cwd: string, runId: string): Promise<AutomaticStudyAnalysisView | undefined>;
11
+ export declare function requestAutomaticStudyAnalysisCancellation(cwd: string, runId: string): Promise<AutomaticStudyAnalysisCancellation>;
12
+ /** One permanent claim per completed run, consumed even when preparation/cancellation fails.
13
+ * Only the original producer calls this after all recording writes return.
14
+ * No file reader, restart recovery, Observer poll or export calls this function. */
15
+ export declare function runAutomaticStudyAnalysis(cwdInput: string, runId: string, configInput: StudyAnalysisConfig, deps?: AutomaticStudyAnalysisDeps): Promise<AutomaticStudyAnalysisOutcome>;
@@ -0,0 +1,187 @@
1
+ import path from "node:path";
2
+ import { renderObserver } from "./observer.js";
3
+ import { resolveRunPath } from "./run.js";
4
+ import { analyzeStudy, readCompletedStudyAnalysisSource, resolveStudyAnalysisRun } from "./study-analysis-service.js";
5
+ import { STUDY_ANALYSIS_PROMPT_VERSION } from "./study-analysis-engine.js";
6
+ import { hashStudyAnalysisValue } from "./study-analysis-validation.js";
7
+ import { claimAutomaticStudyAnalysis, readAutomaticStudyAnalysisPrepared, requestAutomaticStudyAnalysisCancellationPrepared } from "./study-analysis-job.js";
8
+ import { readStudyAnalysisExecution, readStudyAnalysisVersion } from "./study-analysis-store.js";
9
+ const exactId = (runId) => runId !== "latest" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(runId);
10
+ const skipped = (reason) => ({ state: "skipped", reason });
11
+ function hasParticipantEvidence(bundle) {
12
+ return bundle.streams.some(stream => {
13
+ // Terminal commands and transcript messages also include launcher output.
14
+ // Fresh producers count recognized participant runtime items separately.
15
+ if (stream.actor?.lane === "terminal")
16
+ return (stream.actor.counts.runtimeParticipantItems ?? 0) > 0;
17
+ return stream.actor?.items.some(item => ["screenshot", "ui_action", "command", "tool_call", "file_change", "approval"].includes(item.kind)
18
+ || (["message", "reasoning"].includes(item.kind) && !!item.text?.trim()));
19
+ });
20
+ }
21
+ /** Small read-only TUI/CLI projection. Neither this nor Observer can resume a job. */
22
+ export async function readAutomaticStudyAnalysis(cwd, runId) {
23
+ if (!exactId(runId))
24
+ return undefined;
25
+ const prepared = await resolveRunPath(path.resolve(cwd), runId).catch(() => null);
26
+ return prepared ? readAutomaticStudyAnalysisPrepared(prepared) : undefined;
27
+ }
28
+ export async function requestAutomaticStudyAnalysisCancellation(cwd, runId) {
29
+ if (!exactId(runId))
30
+ return { requested: false, reason: "AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE" };
31
+ const prepared = await resolveRunPath(path.resolve(cwd), runId).catch(() => null);
32
+ return prepared ? requestAutomaticStudyAnalysisCancellationPrepared(prepared)
33
+ : { requested: false, reason: "AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE" };
34
+ }
35
+ function outcomeOf(result) {
36
+ if (result.error?.code === "ANALYSIS_PUBLICATION_FAILED")
37
+ return { state: "failed", reason: "AUTOMATIC_ANALYSIS_PUBLICATION_FAILED", result };
38
+ if (result.status)
39
+ return { state: result.status, result, reason: result.reused ? "AUTOMATIC_ANALYSIS_REUSED"
40
+ : result.status === "partial" ? (result.error?.code === "analysis_admission_estimate_exceeded"
41
+ ? "AUTOMATIC_ANALYSIS_ADMISSION_EXCEEDED" : result.ok ? "AUTOMATIC_ANALYSIS_LIMITATIONS" : "AUTOMATIC_ANALYSIS_FAILED")
42
+ : result.status === "failed" ? "AUTOMATIC_ANALYSIS_FAILED"
43
+ : result.status === "cancelled" ? "AUTOMATIC_ANALYSIS_CANCELLED" : null };
44
+ const code = result.error?.code;
45
+ if (code === "ANALYSIS_CANCELLED")
46
+ return { state: "cancelled", reason: "AUTOMATIC_ANALYSIS_CANCELLED", result };
47
+ if (code === "ANALYSIS_API_KEY_MISSING")
48
+ return { ...skipped("AUTOMATIC_ANALYSIS_KEY_MISSING"), result };
49
+ if (code === "ANALYSIS_BUSY")
50
+ return { ...skipped("AUTOMATIC_ANALYSIS_BUSY"), result };
51
+ if (code === "ANALYSIS_HISTORY_UNAVAILABLE")
52
+ return { ...skipped("AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE"), result };
53
+ if (code === "ANALYSIS_CONFIG_INVALID" || code === "ANALYSIS_QUESTION_UNSAFE" || code?.startsWith("analysis_")) {
54
+ return { ...skipped("AUTOMATIC_ANALYSIS_ADMISSION_REFUSED"), result };
55
+ }
56
+ return { state: "unknown", reason: "AUTOMATIC_ANALYSIS_OUTCOME_UNKNOWN", result };
57
+ }
58
+ /** One permanent claim per completed run, consumed even when preparation/cancellation fails.
59
+ * Only the original producer calls this after all recording writes return.
60
+ * No file reader, restart recovery, Observer poll or export calls this function. */
61
+ export async function runAutomaticStudyAnalysis(cwdInput, runId, configInput, deps = {}) {
62
+ if (!exactId(runId))
63
+ return skipped("AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE");
64
+ let cwd = path.resolve(cwdInput);
65
+ const prepared = await resolveStudyAnalysisRun(cwd, runId, deps.expectedRun).catch(() => null);
66
+ if (!prepared)
67
+ return skipped("AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE");
68
+ cwd = path.dirname(path.dirname(prepared.physicalRunsRoot));
69
+ let participantEvidence = false;
70
+ // Do not consume a future run's one claim while a producer is still writing it.
71
+ try {
72
+ const bytes = await readCompletedStudyAnalysisSource(cwd, prepared);
73
+ const bundle = JSON.parse(bytes.toString("utf8"));
74
+ if (bundle.streams.some(stream => stream.actor?.stopCause === "harness_aborted"))
75
+ return skipped("AUTOMATIC_ANALYSIS_ACTOR_CANCELLED");
76
+ participantEvidence = hasParticipantEvidence(bundle);
77
+ }
78
+ catch {
79
+ return skipped("AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE");
80
+ }
81
+ const config = structuredClone(configInput);
82
+ let job;
83
+ try {
84
+ const claimed = await claimAutomaticStudyAnalysis(prepared, { configDigest: hashStudyAnalysisValue(config), promptVersion: STUDY_ANALYSIS_PROMPT_VERSION });
85
+ if (!claimed)
86
+ return skipped("AUTOMATIC_ANALYSIS_ALREADY_REQUESTED");
87
+ job = claimed;
88
+ }
89
+ catch {
90
+ return { state: "unknown", reason: "AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE" };
91
+ }
92
+ const controller = new AbortController();
93
+ const signal = deps.signal ? AbortSignal.any([deps.signal, controller.signal]) : controller.signal;
94
+ let polling = false;
95
+ let storageFailed = false;
96
+ let cancellationFailed = false;
97
+ const poll = async () => {
98
+ if (polling)
99
+ return;
100
+ polling = true;
101
+ try {
102
+ if (await job.cancellationRequested())
103
+ controller.abort();
104
+ }
105
+ catch {
106
+ cancellationFailed = true;
107
+ controller.abort();
108
+ }
109
+ finally {
110
+ polling = false;
111
+ }
112
+ };
113
+ const cancellationTimer = setInterval(() => { void poll(); }, 250);
114
+ cancellationTimer.unref();
115
+ const heartbeat = setInterval(() => {
116
+ void job.touch().catch(() => { storageFailed = true; controller.abort(); });
117
+ }, 5000);
118
+ heartbeat.unref();
119
+ let outcome;
120
+ try {
121
+ await poll();
122
+ const missingKey = !(deps.apiKey ?? process.env.OPENAI_API_KEY)?.trim();
123
+ if (deps.defaultRequest === true && (missingKey || !participantEvidence)) {
124
+ outcome = signal.aborted ? { state: "cancelled", reason: "AUTOMATIC_ANALYSIS_CANCELLED" }
125
+ : skipped(missingKey ? "AUTOMATIC_ANALYSIS_KEY_MISSING" : "AUTOMATIC_ANALYSIS_NO_PARTICIPANT_EVIDENCE");
126
+ }
127
+ else {
128
+ const result = await analyzeStudy(cwd, runId, { config }, {
129
+ ...deps, signal, expectedRun: prepared, analysisId: job.attemptId,
130
+ beforeDispatch: async (context) => {
131
+ if (await job.cancellationRequested())
132
+ controller.abort();
133
+ if (signal.aborted)
134
+ return;
135
+ await job.update({ state: "running", analysisId: context.id, startedAt: new Date().toISOString(),
136
+ sourceRunSha256: context.sourceRunSha256, inputDigest: context.inputDigest });
137
+ }
138
+ });
139
+ outcome = outcomeOf(result);
140
+ }
141
+ if (cancellationFailed)
142
+ outcome = { ...outcome, state: "unknown", reason: "AUTOMATIC_ANALYSIS_CANCELLATION_UNAVAILABLE" };
143
+ if (storageFailed)
144
+ outcome = { ...outcome, state: "unknown", reason: "AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE" };
145
+ }
146
+ catch {
147
+ outcome = { state: "unknown", reason: "AUTOMATIC_ANALYSIS_OUTCOME_UNKNOWN" };
148
+ }
149
+ finally {
150
+ clearInterval(cancellationTimer);
151
+ clearInterval(heartbeat);
152
+ }
153
+ try {
154
+ // Bind terminal metadata to the exact safely published execution. Reads never
155
+ // promote a completed-looking sidecar without rechecking these bindings.
156
+ const analysisId = outcome.result?.analysisId;
157
+ const [entry, receipt] = analysisId ? await Promise.all([
158
+ readStudyAnalysisVersion(prepared, analysisId), readStudyAnalysisExecution(prepared, analysisId)
159
+ ]) : [null, null];
160
+ await job.update({ state: outcome.state, reason: outcome.reason,
161
+ analysisId: analysisId ?? null,
162
+ ...(receipt === null ? {} : { sourceRunSha256: receipt.sourceRunSha256, inputDigest: receipt.inputDigest,
163
+ receiptSha256: hashStudyAnalysisValue(receipt) }),
164
+ ...(entry?.analysis ? { analysisSha256: hashStudyAnalysisValue(entry.analysis) } : {}) });
165
+ const persisted = await readAutomaticStudyAnalysisPrepared(prepared);
166
+ if (persisted?.state === "unknown")
167
+ outcome = { ...outcome, state: "unknown", reason: persisted.reason };
168
+ }
169
+ catch {
170
+ return { ...outcome, state: "unknown", reason: "AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE" };
171
+ }
172
+ // The service may render while this owner is still running. Freeze the final
173
+ // job projection for direct file opening too, including no-request outcomes.
174
+ // A failed refresh must never erase the durable outcome or measured usage.
175
+ try {
176
+ const rendered = await renderObserver(cwd, runId, { open: false, expectedRun: prepared });
177
+ if (!rendered.ok)
178
+ throw new Error("AUTOMATIC_ANALYSIS_OBSERVER_UNAVAILABLE");
179
+ }
180
+ catch {
181
+ if (outcome.result)
182
+ outcome = { ...outcome, result: { ...outcome.result,
183
+ warnings: [...outcome.result.warnings, "Automatic analysis status was saved, but Observer could not be refreshed. Run humanish observe again."] } };
184
+ }
185
+ return outcome;
186
+ }
187
+ //# sourceMappingURL=automatic-study-analysis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automatic-study-analysis.js","sourceRoot":"","sources":["../src/automatic-study-analysis.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { type AutomaticAnalysisHooks, type AutomaticAnalysisResult } from "./automatic-analysis-completion.js";
1
2
  import { type RunLabProvenance } from "./run-status.js";
2
3
  import { type CuaActorDescriptor } from "./actor-registry.js";
3
4
  import { type CuaLaneSpec, type LaneRunOutcome } from "./cua-actor-lab.js";
@@ -12,6 +13,7 @@ export declare const CONCURRENT_SHARED_WORLD_PROVIDER_METADATA: {
12
13
  };
13
14
  export declare const CONCURRENT_ATTRIBUTION_LIMITS: readonly ["concurrent", "best-effort-causal-attribution", "non-deterministic-shared-state", "window-and-snapshot-granularity", "contention-observed-not-proven-safe", "state-change-not-isolated-to-actors"];
14
15
  export interface RunConcurrentSharedWorldLabOptions {
16
+ automaticAnalysis?: AutomaticAnalysisHooks;
15
17
  /** Which manifest produced this run (#455); threaded into the status record + bundle. */
16
18
  lab?: RunLabProvenance;
17
19
  cwd: string;
@@ -28,7 +30,7 @@ export interface RunConcurrentSharedWorldLabOptions {
28
30
  * (#316); core-stamped onto the bundle as evidence. Absent for library callers. */
29
31
  scorerProvenance?: RunScorerProvenance;
30
32
  }
31
- export type ConcurrentSharedWorldLabErrorCode = "HUMANISH_LAB_TASKS_UNSUPPORTED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_FAILED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_ACTOR_UNSUPPORTED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_INVALID" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_KEYS_MISSING" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_SUBJECT_ENV_MISSING" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_GETHOST_UNAVAILABLE" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_HANDOFF_TIMEOUT"
33
+ export type ConcurrentSharedWorldLabErrorCode = "HUMANISH_LAB_ANALYSIS_INVALID" | "HUMANISH_LAB_TASKS_UNSUPPORTED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_FAILED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_ACTOR_UNSUPPORTED" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_INVALID" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_KEYS_MISSING" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_SUBJECT_ENV_MISSING" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_GETHOST_UNAVAILABLE" | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_HANDOFF_TIMEOUT"
32
34
  /** A declared adopter-hosted comms catch (#328) did not answer as a humanish catch — fail closed
33
35
  * BEFORE any actor spend, since the funnel would silently collect nothing. */
34
36
  | "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_COMMS_CATCH_UNREACHABLE";
@@ -94,7 +96,7 @@ export interface ConcurrentSharedWorldRoleResult {
94
96
  message: string;
95
97
  };
96
98
  }
97
- export interface ConcurrentSharedWorldLabResult {
99
+ export interface ConcurrentSharedWorldLabResult extends AutomaticAnalysisResult {
98
100
  schema: typeof CONCURRENT_SHARED_WORLD_LAB_SCHEMA;
99
101
  ok: boolean;
100
102
  cwd: string;
@@ -32,6 +32,8 @@
32
32
  // synthetic-seeded-subjects ONLY. Verify fail-closes on subject.state.provenance != "seeded" and
33
33
  // requires the author attestation subject.exposure: synthetic. This is author-trust + a provenance
34
34
  // gate, NOT a no-real-data guarantee (Humanish cannot tell synthetic from real data).
35
+ import { resolveAutomaticAnalysis } from "./automatic-analysis-config.js";
36
+ import { completeAutomaticAnalysis, markFinalizedStudyResult } from "./automatic-analysis-completion.js";
35
37
  import { taskProtocolValidationReason } from "./lab-config.js";
36
38
  import { randomBytes } from "node:crypto";
37
39
  import { describeMissingKeys } from "./key-resolution.js";
@@ -500,7 +502,9 @@ function observerResultForConcurrentArtifacts(cwd, runId, artifactRoot, warnings
500
502
  * ticking into a directory something else is deleting, which surfaces as an unrelated ENOTEMPTY.
501
503
  */
502
504
  export async function runConcurrentSharedWorld(options) {
503
- return withRunStatusScope(() => runConcurrentSharedWorldInScope(options));
505
+ const analysis = resolveAutomaticAnalysis(options.config.review?.analysis);
506
+ const result = await withRunStatusScope(() => runConcurrentSharedWorldInScope(options));
507
+ return completeAutomaticAnalysis(result, analysis.ok ? analysis.config : undefined, options.automaticAnalysis, options.config.review?.analysis === undefined ? "default" : "explicit");
504
508
  }
505
509
  async function runConcurrentSharedWorldInScope(options) {
506
510
  const { config, dryRun } = options;
@@ -530,6 +534,9 @@ async function runConcurrentSharedWorldInScope(options) {
530
534
  warnings: [],
531
535
  error: { code, message }
532
536
  });
537
+ const analysis = resolveAutomaticAnalysis(config.review?.analysis);
538
+ if (!analysis.ok)
539
+ return fail("HUMANISH_LAB_ANALYSIS_INVALID", analysis.message);
533
540
  const tasksReason = taskProtocolValidationReason(config, false);
534
541
  if (tasksReason)
535
542
  return fail("HUMANISH_LAB_TASKS_UNSUPPORTED", tasksReason);
@@ -1595,7 +1602,7 @@ async function runConcurrentSharedWorldInScope(options) {
1595
1602
  const passed = roleResults.filter((role) => role.ok).length;
1596
1603
  return { code: "HUMANISH_CONCURRENT_SHARED_WORLD_LAB_FAILED", message: `Concurrent shared-world run did not run coherently: ${passed}/${roles.length} actor(s) reached a terminal, engaged passed session.` };
1597
1604
  })();
1598
- return {
1605
+ return markFinalizedStudyResult({
1599
1606
  schema: CONCURRENT_SHARED_WORLD_LAB_SCHEMA,
1600
1607
  ok,
1601
1608
  cwd,
@@ -1616,7 +1623,7 @@ async function runConcurrentSharedWorldInScope(options) {
1616
1623
  observer,
1617
1624
  warnings: [...warnings, ...adapterWarnings, ...observer.warnings],
1618
1625
  ...(errorResult === undefined ? {} : { error: errorResult })
1619
- };
1626
+ }, runPaths);
1620
1627
  }
1621
1628
  /** Max windows live at the same instant (sweep over start/end points). The honest simultaneity
1622
1629
  * count: lane COUNT says how many seats existed; this says how many ever ran at once. */