humanish 0.89.0 → 0.90.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.
- package/README.md +15 -0
- package/dist/automatic-analysis-completion.d.ts +25 -0
- package/dist/automatic-analysis-completion.js +54 -0
- package/dist/automatic-analysis-completion.js.map +1 -0
- package/dist/automatic-analysis-config.d.ts +17 -0
- package/dist/automatic-analysis-config.js +31 -0
- package/dist/automatic-analysis-config.js.map +1 -0
- package/dist/automatic-study-analysis.d.ts +12 -0
- package/dist/automatic-study-analysis.js +168 -0
- package/dist/automatic-study-analysis.js.map +1 -0
- package/dist/concurrent-shared-world-lab.d.ts +4 -2
- package/dist/concurrent-shared-world-lab.js +10 -3
- package/dist/concurrent-shared-world-lab.js.map +1 -1
- package/dist/cua-actor-lab.d.ts +4 -2
- package/dist/cua-actor-lab.js +10 -5
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/e2b-terminal-lab.d.ts +4 -2
- package/dist/e2b-terminal-lab.js +10 -3
- package/dist/e2b-terminal-lab.js.map +1 -1
- package/dist/export-bundle.js +2 -0
- package/dist/export-bundle.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lab-config.d.ts +5 -0
- package/dist/lab-config.js +21 -2
- package/dist/lab-config.js.map +1 -1
- package/dist/lab-engine.d.ts +2 -0
- package/dist/lab-engine.js +12 -3
- package/dist/lab-engine.js.map +1 -1
- package/dist/lab-summary.d.ts +4 -0
- package/dist/lab-summary.js +3 -0
- package/dist/lab-summary.js.map +1 -1
- package/dist/observer-app.html +9 -9
- package/dist/observer.d.ts +3 -0
- package/dist/observer.js +23 -6
- package/dist/observer.js.map +1 -1
- package/dist/oss-lab.d.ts +1 -1
- package/dist/oss-lab.js.map +1 -1
- package/dist/oss-meta-lab.d.ts +1 -1
- package/dist/oss-meta-lab.js.map +1 -1
- package/dist/program.d.ts +5 -0
- package/dist/program.js +42 -7
- package/dist/program.js.map +1 -1
- package/dist/run-detail.d.ts +2 -0
- package/dist/run-detail.js +3 -0
- package/dist/run-detail.js.map +1 -1
- package/dist/run.d.ts +1 -1
- package/dist/run.js.map +1 -1
- package/dist/scripted-browser-lab.d.ts +4 -2
- package/dist/scripted-browser-lab.js +10 -5
- package/dist/scripted-browser-lab.js.map +1 -1
- package/dist/shared-world-lab.d.ts +4 -2
- package/dist/shared-world-lab.js +10 -3
- package/dist/shared-world-lab.js.map +1 -1
- package/dist/study-analysis-engine.d.ts +6 -1
- package/dist/study-analysis-engine.js +17 -3
- package/dist/study-analysis-engine.js.map +1 -1
- package/dist/study-analysis-evidence.js +45 -17
- package/dist/study-analysis-evidence.js.map +1 -1
- package/dist/study-analysis-job.d.ts +87 -0
- package/dist/study-analysis-job.js +197 -0
- package/dist/study-analysis-job.js.map +1 -0
- package/dist/study-analysis-service.d.ts +10 -1
- package/dist/study-analysis-service.js +31 -8
- package/dist/study-analysis-service.js.map +1 -1
- package/dist/study-analysis-sharing.js +13 -5
- package/dist/study-analysis-sharing.js.map +1 -1
- package/dist/study-analysis-store.d.ts +4 -0
- package/dist/study-analysis-store.js +40 -1
- package/dist/study-analysis-store.js.map +1 -1
- package/dist/study-analysis-validation.d.ts +2 -1
- package/dist/study-analysis-validation.js +12 -5
- package/dist/study-analysis-validation.js.map +1 -1
- package/dist/study-analysis.d.ts +6 -0
- package/dist/study-analysis.js.map +1 -1
- package/dist/tui-actions.d.ts +1 -1
- package/dist/tui-actions.js +15 -1
- package/dist/tui-actions.js.map +1 -1
- package/dist/tui-app.js +116 -116
- package/dist/tui-contract.d.ts +3 -2
- package/dist/tui-contract.js.map +1 -1
- package/docs/contracts/schemas.md +1 -1
- package/docs/contracts/study-analysis.md +18 -0
- package/docs/goals/current.md +4 -4
- package/docs/product/automatic-analysis.md +63 -0
- package/docs/ramp/README.md +8 -1
- package/docs/release/0.89.1-analysis-finished-notice.md +24 -0
- package/docs/release/0.90.0-automatic-analysis.md +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,6 +104,21 @@ beside Participants in Observer, with evidence links and separate participant
|
|
|
104
104
|
feedback. See the [analysis contract](docs/contracts/study-analysis.md) for
|
|
105
105
|
coverage limits, estimated cost controls, corrections and sharing behavior.
|
|
106
106
|
|
|
107
|
+
To generate findings automatically after each live run of a lab, add:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
review:
|
|
111
|
+
analysis:
|
|
112
|
+
maxCostUsd: 3
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This is a separate analysis budget, in addition to participant execution costs.
|
|
116
|
+
`humanish run <lab>`, `humanish lab run <lab>`, `humanish watch <lab>`, and live
|
|
117
|
+
starts in the TUI all honor it. Dry runs skip analysis. The TUI and Observer show
|
|
118
|
+
its progress after participants finish; opening either view never spends money.
|
|
119
|
+
See [automatic analysis](docs/product/automatic-analysis.md) for configuration,
|
|
120
|
+
cancellation, and failure behavior.
|
|
121
|
+
|
|
107
122
|
## Public-Safety Boundary
|
|
108
123
|
|
|
109
124
|
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
|
+
}
|
|
15
|
+
/** Internal producer receipt: call only after final source publication, never for an early refusal. */
|
|
16
|
+
export declare function markFinalizedStudyResult<T extends object>(result: T, prepared: PreparedRunArtifactPaths): T;
|
|
17
|
+
/** One post-completion boundary shared by all live recording producers. */
|
|
18
|
+
export declare function completeAutomaticAnalysis<T extends {
|
|
19
|
+
cwd: string;
|
|
20
|
+
runId: string;
|
|
21
|
+
dryRun: boolean;
|
|
22
|
+
}>(result: T, config: StudyAnalysisConfig | undefined, hooks?: AutomaticAnalysisHooks): Promise<T & AutomaticAnalysisResult>;
|
|
23
|
+
export declare function automaticAnalysisSucceeded(result: {
|
|
24
|
+
automaticAnalysis?: AutomaticStudyAnalysisOutcome;
|
|
25
|
+
}): boolean;
|
|
@@ -0,0 +1,54 @@
|
|
|
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) {
|
|
12
|
+
if (config === undefined)
|
|
13
|
+
return result;
|
|
14
|
+
if (result.dryRun)
|
|
15
|
+
return { ...result, automaticAnalysis: { state: "skipped", reason: "analysis_dry_run" } };
|
|
16
|
+
// An early refusal can echo a caller-supplied ID belonging to an older run.
|
|
17
|
+
// Require this invocation's internal final-publication receipt before reading that ID.
|
|
18
|
+
const prepared = finalizedResults.get(result);
|
|
19
|
+
if (!result.runId || result.runId === "not-created" || prepared === undefined) {
|
|
20
|
+
return { ...result, automaticAnalysis: { state: "skipped", reason: "analysis_source_unavailable" } };
|
|
21
|
+
}
|
|
22
|
+
let cleanup = undefined;
|
|
23
|
+
try {
|
|
24
|
+
cleanup = hooks?.onStart?.();
|
|
25
|
+
try {
|
|
26
|
+
await validatePreparedRunRootIdentity(prepared);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { ...result, automaticAnalysis: { state: "failed", reason: "analysis_source_changed" } };
|
|
30
|
+
}
|
|
31
|
+
const sourceCwd = path.dirname(path.dirname(prepared.physicalRunsRoot));
|
|
32
|
+
const automaticAnalysis = await (hooks?.run ?? runAutomaticStudyAnalysis)(sourceCwd, result.runId, config, { ...hooks?.deps, expectedRun: prepared });
|
|
33
|
+
return { ...result, automaticAnalysis };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Preserve the producer result. Never put a hook exception or provider response in the envelope.
|
|
37
|
+
return { ...result, automaticAnalysis: { state: "failed", reason: "analysis_automatic_failed" } };
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
try {
|
|
41
|
+
cleanup?.();
|
|
42
|
+
}
|
|
43
|
+
catch { /* Cleanup cannot erase accounting or the completed recording. */ }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function automaticAnalysisSucceeded(result) {
|
|
47
|
+
const value = result.automaticAnalysis;
|
|
48
|
+
if (value === undefined)
|
|
49
|
+
return true;
|
|
50
|
+
if (value.state === "skipped")
|
|
51
|
+
return value.reason === "analysis_dry_run";
|
|
52
|
+
return (value.state === "complete" || value.state === "partial") && value.result?.ok === true;
|
|
53
|
+
}
|
|
54
|
+
//# 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;AAe3G,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;IACnF,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,IAAI,MAAM,CAAC,MAAM;QAAE,OAAO,EAAE,GAAG,MAAM,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,CAAC;IAC7G,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,iBAAiB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,6BAA6B,EAAE,EAAE,CAAC;IACvG,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,iBAAiB,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,yBAAyB,EAAE,EAAE,CAAC;QAAC,CAAC;QAC1G,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,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7C,OAAO,EAAE,GAAG,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,iGAAiG;QACjG,OAAO,EAAE,GAAG,MAAM,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,2BAA2B,EAAE,EAAE,CAAC;IACpG,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,MAA6D;IACtG,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,CAAC;IAC1E,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,17 @@
|
|
|
1
|
+
import type { StudyAnalysisConfig } from "./study-analysis.js";
|
|
2
|
+
/** A separate, explicitly priced 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
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
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 === undefined)
|
|
8
|
+
return { ok: true, config: undefined };
|
|
9
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)
|
|
10
|
+
|| Object.keys(raw).some(key => !FIELDS.has(key))) {
|
|
11
|
+
return { ok: false, message: "review.analysis must be a mapping containing only maxCostUsd, model, question, timeoutMs and maxOutputTokens." };
|
|
12
|
+
}
|
|
13
|
+
const value = raw;
|
|
14
|
+
const { maxCostUsd } = value;
|
|
15
|
+
const model = value.model === undefined ? "gpt-6-astra" : value.model;
|
|
16
|
+
const question = value.question === undefined ? null : value.question;
|
|
17
|
+
const timeoutMs = value.timeoutMs === undefined ? 300_000 : value.timeoutMs;
|
|
18
|
+
const maxOutputTokens = value.maxOutputTokens === undefined ? 16_384 : value.maxOutputTokens;
|
|
19
|
+
if (typeof maxCostUsd !== "number" || !Number.isFinite(maxCostUsd) || maxCostUsd <= 0 || maxCostUsd > 1000
|
|
20
|
+
|| typeof model !== "string" || !MODELS.has(model)
|
|
21
|
+
|| typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 600_000
|
|
22
|
+
|| typeof maxOutputTokens !== "number" || !Number.isSafeInteger(maxOutputTokens) || maxOutputTokens < 256 || maxOutputTokens > 32_768
|
|
23
|
+
|| (value.question !== undefined && (typeof question !== "string" || question.length > 4000))) {
|
|
24
|
+
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." };
|
|
25
|
+
}
|
|
26
|
+
if (question !== null && containsSensitive(question)) {
|
|
27
|
+
return { ok: false, message: "review.analysis.question contains sensitive text and cannot be sent for analysis." };
|
|
28
|
+
}
|
|
29
|
+
return { ok: true, config: { model, maxCostUsd, timeoutMs, maxOutputTokens, question: question } };
|
|
30
|
+
}
|
|
31
|
+
//# 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,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC9D,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,+GAA+G,EAAE,CAAC;IACjJ,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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
/** Small read-only TUI/CLI projection. Neither this nor Observer can resume a job. */
|
|
7
|
+
export declare function readAutomaticStudyAnalysis(cwd: string, runId: string): Promise<AutomaticStudyAnalysisView | undefined>;
|
|
8
|
+
export declare function requestAutomaticStudyAnalysisCancellation(cwd: string, runId: string): Promise<AutomaticStudyAnalysisCancellation>;
|
|
9
|
+
/** One permanent claim per completed run, consumed even when preparation/cancellation fails.
|
|
10
|
+
* Only the original opted-in producer calls this after all recording writes return.
|
|
11
|
+
* No file reader, restart recovery, Observer poll or export calls this function. */
|
|
12
|
+
export declare function runAutomaticStudyAnalysis(cwdInput: string, runId: string, configInput: StudyAnalysisConfig, deps?: AutomaticStudyAnalysisDeps): Promise<AutomaticStudyAnalysisOutcome>;
|
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
/** Small read-only TUI/CLI projection. Neither this nor Observer can resume a job. */
|
|
12
|
+
export async function readAutomaticStudyAnalysis(cwd, runId) {
|
|
13
|
+
if (!exactId(runId))
|
|
14
|
+
return undefined;
|
|
15
|
+
const prepared = await resolveRunPath(path.resolve(cwd), runId).catch(() => null);
|
|
16
|
+
return prepared ? readAutomaticStudyAnalysisPrepared(prepared) : undefined;
|
|
17
|
+
}
|
|
18
|
+
export async function requestAutomaticStudyAnalysisCancellation(cwd, runId) {
|
|
19
|
+
if (!exactId(runId))
|
|
20
|
+
return { requested: false, reason: "AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE" };
|
|
21
|
+
const prepared = await resolveRunPath(path.resolve(cwd), runId).catch(() => null);
|
|
22
|
+
return prepared ? requestAutomaticStudyAnalysisCancellationPrepared(prepared)
|
|
23
|
+
: { requested: false, reason: "AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE" };
|
|
24
|
+
}
|
|
25
|
+
function outcomeOf(result) {
|
|
26
|
+
if (result.error?.code === "ANALYSIS_PUBLICATION_FAILED")
|
|
27
|
+
return { state: "failed", reason: "AUTOMATIC_ANALYSIS_PUBLICATION_FAILED", result };
|
|
28
|
+
if (result.status)
|
|
29
|
+
return { state: result.status, result, reason: result.reused ? "AUTOMATIC_ANALYSIS_REUSED"
|
|
30
|
+
: result.status === "partial" ? (result.error?.code === "analysis_admission_estimate_exceeded"
|
|
31
|
+
? "AUTOMATIC_ANALYSIS_ADMISSION_EXCEEDED" : result.ok ? "AUTOMATIC_ANALYSIS_LIMITATIONS" : "AUTOMATIC_ANALYSIS_FAILED")
|
|
32
|
+
: result.status === "failed" ? "AUTOMATIC_ANALYSIS_FAILED"
|
|
33
|
+
: result.status === "cancelled" ? "AUTOMATIC_ANALYSIS_CANCELLED" : null };
|
|
34
|
+
const code = result.error?.code;
|
|
35
|
+
if (code === "ANALYSIS_CANCELLED")
|
|
36
|
+
return { state: "cancelled", reason: "AUTOMATIC_ANALYSIS_CANCELLED", result };
|
|
37
|
+
if (code === "ANALYSIS_API_KEY_MISSING")
|
|
38
|
+
return { ...skipped("AUTOMATIC_ANALYSIS_KEY_MISSING"), result };
|
|
39
|
+
if (code === "ANALYSIS_BUSY")
|
|
40
|
+
return { ...skipped("AUTOMATIC_ANALYSIS_BUSY"), result };
|
|
41
|
+
if (code === "ANALYSIS_HISTORY_UNAVAILABLE")
|
|
42
|
+
return { ...skipped("AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE"), result };
|
|
43
|
+
if (code === "ANALYSIS_CONFIG_INVALID" || code === "ANALYSIS_QUESTION_UNSAFE" || code?.startsWith("analysis_")) {
|
|
44
|
+
return { ...skipped("AUTOMATIC_ANALYSIS_ADMISSION_REFUSED"), result };
|
|
45
|
+
}
|
|
46
|
+
return { state: "unknown", reason: "AUTOMATIC_ANALYSIS_OUTCOME_UNKNOWN", result };
|
|
47
|
+
}
|
|
48
|
+
/** One permanent claim per completed run, consumed even when preparation/cancellation fails.
|
|
49
|
+
* Only the original opted-in producer calls this after all recording writes return.
|
|
50
|
+
* No file reader, restart recovery, Observer poll or export calls this function. */
|
|
51
|
+
export async function runAutomaticStudyAnalysis(cwdInput, runId, configInput, deps = {}) {
|
|
52
|
+
if (!exactId(runId))
|
|
53
|
+
return skipped("AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE");
|
|
54
|
+
let cwd = path.resolve(cwdInput);
|
|
55
|
+
const prepared = await resolveStudyAnalysisRun(cwd, runId, deps.expectedRun).catch(() => null);
|
|
56
|
+
if (!prepared)
|
|
57
|
+
return skipped("AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE");
|
|
58
|
+
cwd = path.dirname(path.dirname(prepared.physicalRunsRoot));
|
|
59
|
+
// Do not consume a future run's one claim while a producer is still writing it.
|
|
60
|
+
try {
|
|
61
|
+
const bytes = await readCompletedStudyAnalysisSource(cwd, prepared);
|
|
62
|
+
const bundle = JSON.parse(bytes.toString("utf8"));
|
|
63
|
+
if (bundle.streams.some(stream => stream.actor?.stopCause === "harness_aborted"))
|
|
64
|
+
return skipped("AUTOMATIC_ANALYSIS_ACTOR_CANCELLED");
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return skipped("AUTOMATIC_ANALYSIS_SOURCE_UNAVAILABLE");
|
|
68
|
+
}
|
|
69
|
+
const config = structuredClone(configInput);
|
|
70
|
+
let job;
|
|
71
|
+
try {
|
|
72
|
+
const claimed = await claimAutomaticStudyAnalysis(prepared, { configDigest: hashStudyAnalysisValue(config), promptVersion: STUDY_ANALYSIS_PROMPT_VERSION });
|
|
73
|
+
if (!claimed)
|
|
74
|
+
return skipped("AUTOMATIC_ANALYSIS_ALREADY_REQUESTED");
|
|
75
|
+
job = claimed;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return { state: "unknown", reason: "AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE" };
|
|
79
|
+
}
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const signal = deps.signal ? AbortSignal.any([deps.signal, controller.signal]) : controller.signal;
|
|
82
|
+
let polling = false;
|
|
83
|
+
let storageFailed = false;
|
|
84
|
+
let cancellationFailed = false;
|
|
85
|
+
const poll = async () => {
|
|
86
|
+
if (polling)
|
|
87
|
+
return;
|
|
88
|
+
polling = true;
|
|
89
|
+
try {
|
|
90
|
+
if (await job.cancellationRequested())
|
|
91
|
+
controller.abort();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
cancellationFailed = true;
|
|
95
|
+
controller.abort();
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
polling = false;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const cancellationTimer = setInterval(() => { void poll(); }, 250);
|
|
102
|
+
cancellationTimer.unref();
|
|
103
|
+
const heartbeat = setInterval(() => {
|
|
104
|
+
void job.touch().catch(() => { storageFailed = true; controller.abort(); });
|
|
105
|
+
}, 5000);
|
|
106
|
+
heartbeat.unref();
|
|
107
|
+
let outcome;
|
|
108
|
+
try {
|
|
109
|
+
await poll();
|
|
110
|
+
const result = await analyzeStudy(cwd, runId, { config }, {
|
|
111
|
+
...deps, signal, expectedRun: prepared, analysisId: job.attemptId,
|
|
112
|
+
beforeDispatch: async (context) => {
|
|
113
|
+
if (await job.cancellationRequested())
|
|
114
|
+
controller.abort();
|
|
115
|
+
if (signal.aborted)
|
|
116
|
+
return;
|
|
117
|
+
await job.update({ state: "running", analysisId: context.id, startedAt: new Date().toISOString(),
|
|
118
|
+
sourceRunSha256: context.sourceRunSha256, inputDigest: context.inputDigest });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
outcome = outcomeOf(result);
|
|
122
|
+
if (cancellationFailed)
|
|
123
|
+
outcome = { state: "unknown", reason: "AUTOMATIC_ANALYSIS_CANCELLATION_UNAVAILABLE", result };
|
|
124
|
+
if (storageFailed)
|
|
125
|
+
outcome = { state: "unknown", reason: "AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE", result };
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
outcome = { state: "unknown", reason: "AUTOMATIC_ANALYSIS_OUTCOME_UNKNOWN" };
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
clearInterval(cancellationTimer);
|
|
132
|
+
clearInterval(heartbeat);
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
// Bind terminal metadata to the exact safely published execution. Reads never
|
|
136
|
+
// promote a completed-looking sidecar without rechecking these bindings.
|
|
137
|
+
const analysisId = outcome.result?.analysisId;
|
|
138
|
+
const [entry, receipt] = analysisId ? await Promise.all([
|
|
139
|
+
readStudyAnalysisVersion(prepared, analysisId), readStudyAnalysisExecution(prepared, analysisId)
|
|
140
|
+
]) : [null, null];
|
|
141
|
+
await job.update({ state: outcome.state, reason: outcome.reason,
|
|
142
|
+
analysisId: analysisId ?? null,
|
|
143
|
+
...(receipt === null ? {} : { sourceRunSha256: receipt.sourceRunSha256, inputDigest: receipt.inputDigest,
|
|
144
|
+
receiptSha256: hashStudyAnalysisValue(receipt) }),
|
|
145
|
+
...(entry?.analysis ? { analysisSha256: hashStudyAnalysisValue(entry.analysis) } : {}) });
|
|
146
|
+
const persisted = await readAutomaticStudyAnalysisPrepared(prepared);
|
|
147
|
+
if (persisted?.state === "unknown")
|
|
148
|
+
outcome = { ...outcome, state: "unknown", reason: persisted.reason };
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return { ...outcome, state: "unknown", reason: "AUTOMATIC_ANALYSIS_STORAGE_UNAVAILABLE" };
|
|
152
|
+
}
|
|
153
|
+
// The service may render while this owner is still running. Freeze the final
|
|
154
|
+
// job projection for direct file opening too, including no-request outcomes.
|
|
155
|
+
// A failed refresh must never erase the durable outcome or measured usage.
|
|
156
|
+
try {
|
|
157
|
+
const rendered = await renderObserver(cwd, runId, { open: false, expectedRun: prepared });
|
|
158
|
+
if (!rendered.ok)
|
|
159
|
+
throw new Error("AUTOMATIC_ANALYSIS_OBSERVER_UNAVAILABLE");
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
if (outcome.result)
|
|
163
|
+
outcome = { ...outcome, result: { ...outcome.result,
|
|
164
|
+
warnings: [...outcome.result.warnings, "Automatic analysis status was saved, but Observer could not be refreshed. Run humanish observe again."] } };
|
|
165
|
+
}
|
|
166
|
+
return outcome;
|
|
167
|
+
}
|
|
168
|
+
//# 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
|
-
|
|
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);
|
|
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. */
|