humanish 0.111.2 → 0.111.4
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/ARCHITECTURE.md +1 -1
- package/CHANGELOG.md +32 -0
- package/dist/actors/scripted-browser/actor.d.ts +5 -0
- package/dist/actors/scripted-browser/actor.js +40 -19
- package/dist/actors/scripted-browser/journey.d.ts +2 -0
- package/dist/actors/scripted-browser/journey.js +2 -1
- package/dist/actors/scripted-browser/steps.d.ts +2 -0
- package/dist/actors/scripted-browser/steps.js +17 -0
- package/dist/cli/commands/study-route-computer-use.js +3 -8
- package/dist/cli/commands/study-route-preview.js +4 -6
- package/dist/cli/commands/study-route-shared-world.js +6 -7
- package/dist/cli/findings.d.ts +15 -1
- package/dist/cli/findings.js +43 -5
- package/dist/evidence/redaction.d.ts +2 -2
- package/dist/evidence/redaction.js +3 -3
- package/dist/evidence/secret-scrub.d.ts +7 -0
- package/dist/evidence/secret-scrub.js +21 -12
- package/dist/routes/computer-use/bundle-parts.d.ts +1 -32
- package/dist/routes/computer-use/bundle-parts.js +5 -92
- package/dist/routes/computer-use/e2b-desktop/prepare.js +11 -4
- package/dist/routes/computer-use/fanout-bundle.js +3 -2
- package/dist/routes/computer-use/live-phase.js +3 -7
- package/dist/routes/computer-use/participant-deps.d.ts +46 -0
- package/dist/routes/computer-use/participant-deps.js +48 -0
- package/dist/routes/computer-use/participant-facts.d.ts +5 -4
- package/dist/routes/computer-use/participant-facts.js +37 -2
- package/dist/routes/computer-use/participant-feedback.d.ts +33 -0
- package/dist/routes/computer-use/participant-feedback.js +92 -0
- package/dist/routes/computer-use/participant-model.d.ts +11 -0
- package/dist/routes/computer-use/participant-model.js +15 -0
- package/dist/routes/computer-use/preflight.js +33 -65
- package/dist/routes/computer-use/result.js +13 -52
- package/dist/routes/computer-use/route.js +6 -7
- package/dist/routes/computer-use/setup.d.ts +2 -6
- package/dist/routes/computer-use/setup.js +47 -93
- package/dist/routes/computer-use/single-bundle.js +3 -2
- package/dist/routes/computer-use/types.d.ts +2 -2
- package/dist/routes/preview.js +2 -8
- package/dist/routes/scripted/bundle.d.ts +2 -6
- package/dist/routes/scripted/result.d.ts +2 -1
- package/dist/routes/scripted/result.js +4 -4
- package/dist/routes/scripted/route.js +14 -38
- package/dist/routes/scripted/setup.d.ts +2 -1
- package/dist/routes/scripted/setup.js +71 -35
- package/dist/routes/scripted/subject.d.ts +3 -18
- package/dist/routes/scripted/subject.js +22 -78
- package/dist/routes/scripted/surface-sessions.d.ts +1 -1
- package/dist/routes/scripted/surface-sessions.js +2 -0
- package/dist/routes/scripted/types.d.ts +2 -2
- package/dist/routes/shared-world/bundle.js +36 -1
- package/dist/routes/shared-world/comms.d.ts +2 -1
- package/dist/routes/shared-world/comms.js +3 -7
- package/dist/routes/shared-world/handoff.d.ts +2 -2
- package/dist/routes/shared-world/handoff.js +1 -1
- package/dist/routes/shared-world/participant-specs.d.ts +4 -5
- package/dist/routes/shared-world/participant-specs.js +19 -25
- package/dist/routes/shared-world/plan.js +11 -7
- package/dist/routes/shared-world/provisioned.d.ts +2 -1
- package/dist/routes/shared-world/provisioned.js +37 -80
- package/dist/routes/shared-world/result.js +17 -35
- package/dist/routes/shared-world/route.js +33 -36
- package/dist/routes/shared-world/setup.d.ts +2 -3
- package/dist/routes/shared-world/setup.js +11 -29
- package/dist/routes/shared-world/types.d.ts +6 -15
- package/dist/routes/terminal/credentials.js +14 -1
- package/dist/routes/terminal/dry-run.js +14 -24
- package/dist/routes/terminal/live-finish.d.ts +0 -1
- package/dist/routes/terminal/live-finish.js +8 -5
- package/dist/routes/terminal/route.js +9 -15
- package/dist/routes/terminal/session.d.ts +2 -3
- package/dist/routes/terminal/session.js +28 -40
- package/dist/routes/terminal/trace.d.ts +4 -2
- package/dist/routes/terminal/trace.js +30 -35
- package/dist/routes/terminal/types.d.ts +2 -2
- package/dist/run/run.d.ts +55 -1
- package/dist/run/run.js +38 -2
- package/dist/run/secrets.d.ts +35 -0
- package/dist/run/secrets.js +158 -0
- package/dist/run/study-result.d.ts +34 -0
- package/dist/run/study-result.js +16 -0
- package/dist/study/requirements.d.ts +60 -5
- package/dist/study/requirements.js +89 -2
- package/dist/study/run-study-events.d.ts +4 -3
- package/dist/study/run-study-events.js +9 -14
- package/dist/study/run-study-options.d.ts +4 -1
- package/dist/study/run-study-options.js +22 -32
- package/dist/substrates/e2b/subject-sandbox.d.ts +58 -0
- package/dist/substrates/e2b/subject-sandbox.js +94 -0
- package/docs/contracts/core.md +5 -4
- package/docs/contracts/feedback.md +12 -5
- package/docs/contracts/study-analysis.md +6 -2
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CuaActorDescriptor } from "../../actors/registry.js";
|
|
2
2
|
import type { StudyConfig, StudySubjectServe, StudySubjectStateCheckpoint } from "../../study/types.js";
|
|
3
3
|
import type { RunScope } from "../../run/run.js";
|
|
4
|
+
import type { RunSecrets } from "../../run/secrets.js";
|
|
4
5
|
import type { ConcurrentSharedWorldStudyErrorCode, ConcurrentSharedWorldStudyResult, ConcurrentSharedWorldPlaneClass, FinishFacts, LiveParticipants, PlaneContext, PlaneResults, PlaneSelection, SharedWorldRunInput } from "./types.js";
|
|
5
6
|
import type { StudyDeps } from "../../study/study-deps.js";
|
|
6
7
|
import type { SharedWorldPlan } from "../../study/plan-types.js";
|
|
@@ -16,7 +17,6 @@ interface AdmittedStudy {
|
|
|
16
17
|
env: Record<string, string | undefined>;
|
|
17
18
|
descriptor: CuaActorDescriptor;
|
|
18
19
|
planeClass: ConcurrentSharedWorldPlaneClass;
|
|
19
|
-
runBudget: PlaneContext["runBudget"];
|
|
20
20
|
runSession: PlaneContext["runSession"];
|
|
21
21
|
serve: StudySubjectServe | undefined;
|
|
22
22
|
localTreeRoute: boolean;
|
|
@@ -25,8 +25,7 @@ interface AdmittedStudy {
|
|
|
25
25
|
checkpoints: StudySubjectStateCheckpoint[];
|
|
26
26
|
openaiApiKey: string;
|
|
27
27
|
e2bApiKey: string;
|
|
28
|
-
|
|
29
|
-
scrubKnownValues: (text: string) => string;
|
|
28
|
+
secrets: RunSecrets;
|
|
30
29
|
publicRepo: string;
|
|
31
30
|
hasGithubToken: boolean;
|
|
32
31
|
fail: (code: ConcurrentSharedWorldStudyErrorCode, message: string, actorLabel?: string) => ConcurrentSharedWorldStudyResult;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// Starts a concurrent shared-world run and prepares what its plane reads: the physical project,
|
|
2
2
|
// the run, the participant specs, comms, the packed tree and email receiving.
|
|
3
|
-
import { randomBytes } from "node:crypto";
|
|
4
3
|
import { realpath } from "node:fs/promises";
|
|
5
|
-
import {
|
|
4
|
+
import {} from "../../run/bundle.js";
|
|
6
5
|
import { prepareSelectedOutputDirectory } from "../../run/contained-output.js";
|
|
7
6
|
import { renderConcurrentReviewMarkdown } from "./bundle.js";
|
|
8
7
|
import { seedRecipeDigest } from "./checkpoints.js";
|
|
@@ -15,10 +14,6 @@ import { planeStateOf } from "./plan.js";
|
|
|
15
14
|
import path from "node:path";
|
|
16
15
|
import { e2bRequestTimeoutMs } from "../../substrates/e2b/lifetime.js";
|
|
17
16
|
const DEFAULT_PROBER_CADENCE_MS = 1000;
|
|
18
|
-
function makeRunId() {
|
|
19
|
-
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
20
|
-
return `concurrent-shared-world-${stamp}-${randomBytes(4).toString("hex")}`;
|
|
21
|
-
}
|
|
22
17
|
/** The provisioned plane's setup, or undefined when the study declares no `subject.serve`. */
|
|
23
18
|
function provisionedSetup(study, prepared) {
|
|
24
19
|
if (!study.serve)
|
|
@@ -49,16 +44,11 @@ async function bindPhysicalProject(requestedCwd) {
|
|
|
49
44
|
.physicalPath;
|
|
50
45
|
}
|
|
51
46
|
function startConcurrentRun(study, cwd, scope) {
|
|
52
|
-
|
|
53
|
-
return scope.startRun({
|
|
47
|
+
return scope.startRun(study.plan, study.input, {
|
|
54
48
|
cwd,
|
|
55
|
-
|
|
56
|
-
mintRunId: makeRunId,
|
|
57
|
-
mode: plan.dryRun ? "dry-run" : "live",
|
|
58
|
-
study: plan.study,
|
|
59
|
-
warnings: plan.warnings,
|
|
49
|
+
prefix: "concurrent-shared-world",
|
|
60
50
|
renderReview: renderConcurrentReviewMarkdown,
|
|
61
|
-
|
|
51
|
+
secrets: study.secrets,
|
|
62
52
|
});
|
|
63
53
|
}
|
|
64
54
|
/**
|
|
@@ -67,8 +57,8 @@ function startConcurrentRun(study, cwd, scope) {
|
|
|
67
57
|
*/
|
|
68
58
|
export async function prepareConcurrentRun(study, scope) {
|
|
69
59
|
const { plan, input, config, requestedCwd, deps, env, descriptor, planeClass, fail } = study;
|
|
70
|
-
const {
|
|
71
|
-
const { openaiApiKey, e2bApiKey,
|
|
60
|
+
const { runSession, localTreeRoute, subjectEnvNames, publicRepo } = study;
|
|
61
|
+
const { openaiApiKey, e2bApiKey, secrets } = study;
|
|
72
62
|
const { dryRun, concurrency } = plan;
|
|
73
63
|
const cwd = await bindPhysicalProject(requestedCwd);
|
|
74
64
|
const warnings = [];
|
|
@@ -81,7 +71,7 @@ export async function prepareConcurrentRun(study, scope) {
|
|
|
81
71
|
// Pack the working tree once per run, on the host, before any sandbox exists: a packing
|
|
82
72
|
// failure fails the run closed without sandbox cost. Dry-run packs nothing.
|
|
83
73
|
const packed = localTreeRoute && !dryRun
|
|
84
|
-
? await packSubjectTree(cwd, plan.residual, deps,
|
|
74
|
+
? await packSubjectTree(cwd, plan.residual, deps, secrets.scrub)
|
|
85
75
|
: { ok: true, archive: undefined, buffer: undefined };
|
|
86
76
|
if (!packed.ok) {
|
|
87
77
|
return {
|
|
@@ -95,7 +85,7 @@ export async function prepareConcurrentRun(study, scope) {
|
|
|
95
85
|
if (!started.ok)
|
|
96
86
|
return { ok: false, result: fail(started.code, started.message, descriptor.id) };
|
|
97
87
|
const { run } = started;
|
|
98
|
-
const { runId, createdAt, paths: runPaths } = run;
|
|
88
|
+
const { runId, createdAt, paths: runPaths, source } = run;
|
|
99
89
|
const artifactRoot = runPaths.absoluteRunRoot;
|
|
100
90
|
const timeoutMs = plan.sessionTimeoutMs ?? defaultSessionTimeoutMs(plan);
|
|
101
91
|
const requestTimeoutMs = e2bRequestTimeoutMs(env);
|
|
@@ -104,15 +94,9 @@ export async function prepareConcurrentRun(study, scope) {
|
|
|
104
94
|
const now = deps.now ?? Date.now;
|
|
105
95
|
const proberCadenceMs = deps.proberCadenceMs ?? DEFAULT_PROBER_CADENCE_MS;
|
|
106
96
|
const seedDigest = seedRecipeDigest(planeStateOf(plan));
|
|
107
|
-
const source = await buildRunSource({
|
|
108
|
-
capturedAt: createdAt,
|
|
109
|
-
cwd,
|
|
110
|
-
humanishSource: "present",
|
|
111
|
-
packageName: "humanish",
|
|
112
|
-
});
|
|
113
97
|
const stateStepRecords = [];
|
|
114
98
|
const stateSnapshots = [];
|
|
115
|
-
const actorSpecs = await buildParticipantSpecs(plan.plane.participants, cwd,
|
|
99
|
+
const actorSpecs = await buildParticipantSpecs(plan.plane.participants, cwd, run.secrets.scrub);
|
|
116
100
|
const results = emptyPlaneResults();
|
|
117
101
|
const live = { streamUrls: [] };
|
|
118
102
|
const subjectComms = subjectCommsOf(plan.residual, planeClass);
|
|
@@ -124,7 +108,7 @@ export async function prepareConcurrentRun(study, scope) {
|
|
|
124
108
|
env,
|
|
125
109
|
participants: actorSpecs.map((spec) => spec.planned.id),
|
|
126
110
|
runPaths,
|
|
127
|
-
|
|
111
|
+
secrets: run.secrets,
|
|
128
112
|
dryRun,
|
|
129
113
|
});
|
|
130
114
|
if (!email.ok) {
|
|
@@ -148,12 +132,10 @@ export async function prepareConcurrentRun(study, scope) {
|
|
|
148
132
|
deps,
|
|
149
133
|
env,
|
|
150
134
|
concurrency,
|
|
151
|
-
runBudget,
|
|
152
135
|
runSession,
|
|
153
136
|
openaiApiKey,
|
|
154
137
|
e2bApiKey,
|
|
155
|
-
scrubKnownValues,
|
|
156
|
-
knownSecretValues,
|
|
138
|
+
scrubKnownValues: run.secrets.scrub,
|
|
157
139
|
cwd,
|
|
158
140
|
run,
|
|
159
141
|
runId,
|
|
@@ -7,7 +7,7 @@ import type { AutomaticAnalysisResult } from "../../analysis/automatic-completio
|
|
|
7
7
|
import type { CuaActorDescriptor } from "../../actors/registry.js";
|
|
8
8
|
import type { CommsReceivingRun } from "../../comms/receiving.js";
|
|
9
9
|
import type { StudyCommsEmail, StudyCommsExternal, StudyConfig } from "../../study/types.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type { SubjectDesktopUsage } from "../../substrates/e2b/subject-sandbox.js";
|
|
11
11
|
import type { ObserverResult } from "../../observer/render.js";
|
|
12
12
|
import type { ObserverRuntimeStreamUrl } from "../../observer/run-routes.js";
|
|
13
13
|
import type { BundleRun, RunBundle, RunScorerProvenance, RunSubjectProvenance, RunSubjectStateStepRecord } from "../../run/bundle.js";
|
|
@@ -15,10 +15,10 @@ import type { RunScope } from "../../run/run.js";
|
|
|
15
15
|
import type { SharedWorldStateSnapshot } from "../../run/shared-world-evidence.js";
|
|
16
16
|
import type { LocalTreeArchive } from "../../subject/local-tree-archive.js";
|
|
17
17
|
import type { LiveTraceFlush } from "../computer-use/live-flush.js";
|
|
18
|
-
import type { CuaParticipantDeps, DesktopParticipantRun,
|
|
18
|
+
import type { CuaParticipantDeps, DesktopParticipantRun, ParticipantRunOutcome } from "../computer-use/types.js";
|
|
19
19
|
import type { ProvisionedPlaneSetup } from "./provisioned.js";
|
|
20
20
|
import type { SharedWorldPlan } from "../../study/plan-types.js";
|
|
21
|
-
import { type StudyResultIdentity } from "../../run/study-result.js";
|
|
21
|
+
import { type StudyResultErrorCode, type StudyResultIdentity } from "../../run/study-result.js";
|
|
22
22
|
export declare const CONCURRENT_SHARED_WORLD_PROVIDER_METADATA: {
|
|
23
23
|
readonly mode: "concurrent-shared-world-lab";
|
|
24
24
|
readonly tool: "humanish";
|
|
@@ -54,13 +54,13 @@ export interface SharedWorldRunInput {
|
|
|
54
54
|
* core-stamped onto the bundle as evidence. Absent for library callers. */
|
|
55
55
|
scorerProvenance?: RunScorerProvenance;
|
|
56
56
|
}
|
|
57
|
-
export type ConcurrentSharedWorldStudyErrorCode =
|
|
57
|
+
export type ConcurrentSharedWorldStudyErrorCode = StudyResultErrorCode | "HUMANISH_SHARED_WORLD_FAILED" | "HUMANISH_SHARED_WORLD_ACTOR_UNSUPPORTED" | "HUMANISH_SHARED_WORLD_INVALID" | "HUMANISH_SHARED_WORLD_KEYS_MISSING"
|
|
58
58
|
/** A local-agent brain's CLI is not on `PATH`. */
|
|
59
59
|
| "HUMANISH_SHARED_WORLD_AGENT_MISSING"
|
|
60
60
|
/** A local-agent brain's CLI is signed out, or could not report its sign-in status. */
|
|
61
61
|
| "HUMANISH_SHARED_WORLD_AGENT_SIGNIN_REQUIRED"
|
|
62
62
|
/** A dollar cap that cannot be priced: an unpriced model, or a ChatGPT-account Codex brain. */
|
|
63
|
-
| "HUMANISH_SHARED_WORLD_UNPRICED_CAP" | "HUMANISH_SHARED_WORLD_SUBJECT_ENV_MISSING" | "HUMANISH_SHARED_WORLD_GETHOST_UNAVAILABLE" | "HUMANISH_SHARED_WORLD_HANDOFF_TIMEOUT"
|
|
63
|
+
| "HUMANISH_SHARED_WORLD_UNPRICED_CAP" | "HUMANISH_SHARED_WORLD_SUBJECT_ENV_MISSING" | "HUMANISH_SHARED_WORLD_GETHOST_UNAVAILABLE" | "HUMANISH_SHARED_WORLD_HANDOFF_TIMEOUT"
|
|
64
64
|
/** A declared adopter-hosted comms catch did not answer as a humanish catch. Fail closed
|
|
65
65
|
* before any actor spend, since the funnel would silently collect nothing. */
|
|
66
66
|
| "HUMANISH_SHARED_WORLD_COMMS_CATCH_UNREACHABLE"
|
|
@@ -134,12 +134,6 @@ export interface ConcurrentSharedWorldStudyResult extends AutomaticAnalysisResul
|
|
|
134
134
|
message: string;
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
|
-
/** The provisioned plane's own desktop, for the run's cost estimate. */
|
|
138
|
-
export interface SubjectDesktopUsage {
|
|
139
|
-
durationMs: number | undefined;
|
|
140
|
-
observation: DesktopResourceObservation | undefined;
|
|
141
|
-
killed: boolean;
|
|
142
|
-
}
|
|
143
137
|
/** One actor's measured run (internal). */
|
|
144
138
|
export interface ActorRunResult {
|
|
145
139
|
spec: DesktopParticipantRun;
|
|
@@ -163,14 +157,11 @@ export interface PlaneContext {
|
|
|
163
157
|
deps: StudyDeps;
|
|
164
158
|
env: Record<string, string | undefined>;
|
|
165
159
|
concurrency: number;
|
|
166
|
-
runBudget: CuaRunBudget | undefined;
|
|
167
160
|
runSession: CuaParticipantDeps["runSession"];
|
|
168
161
|
openaiApiKey: string;
|
|
169
162
|
e2bApiKey: string;
|
|
163
|
+
/** `run.secrets.scrub`, which every plane step that writes text reads. */
|
|
170
164
|
scrubKnownValues: (text: string) => string;
|
|
171
|
-
/** The values scrubKnownValues removes. Real email receiving adds its secrets before participants
|
|
172
|
-
* start. */
|
|
173
|
-
knownSecretValues: readonly string[];
|
|
174
165
|
cwd: string;
|
|
175
166
|
run: StartedRun;
|
|
176
167
|
runId: string;
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { DEFAULT_RUNTIME_AUTH, E2B_SYSTEM_CA_BUNDLE, OPENAI_EGRESS_PLACEHOLDER, } from "./runtime-auth.js";
|
|
2
2
|
import { TERMINAL_PRODUCT_STUDY_PROVIDER_METADATA } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Where the study's runtime auth puts the key, as a sentence for the missing-key refusal. Typed
|
|
5
|
+
* `unknown` because a config that skipped the planner may hold a mode the type does not list.
|
|
6
|
+
*/
|
|
7
|
+
function keyPlacement(runtimeAuth) {
|
|
8
|
+
if (runtimeAuth === undefined)
|
|
9
|
+
return ` \`execution.runtimeAuth\` is unset, so the default ${DEFAULT_RUNTIME_AUTH} keeps the key outside the sandbox in an E2B header transform.`;
|
|
10
|
+
if (runtimeAuth === "openai-egress")
|
|
11
|
+
return " `execution.runtimeAuth: openai-egress` keeps the key outside the sandbox in an E2B header transform.";
|
|
12
|
+
if (runtimeAuth === "openai-env")
|
|
13
|
+
return " `execution.runtimeAuth: openai-env` passes the key to the codex command only.";
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
3
16
|
/**
|
|
4
17
|
* Resolve the runtime key on the host. openai-egress, the default, returns an inert command env
|
|
5
18
|
* while retaining the actual value for the external transform and literal redaction; openai-env
|
|
@@ -38,7 +51,7 @@ export function buildRuntimeAuth(args) {
|
|
|
38
51
|
return {
|
|
39
52
|
ok: false,
|
|
40
53
|
code: "HUMANISH_TERMINAL_RUNTIME_AUTH_MISSING",
|
|
41
|
-
message: `Live terminal-product studies
|
|
54
|
+
message: `Live terminal-product studies need ${ALLOWED_RUNTIME_KEY_NAMES.join(" or ")} in the environment, and neither is set.${keyPlacement(args.runtimeAuth)} Pass one with --dotenv; the value is never persisted.`,
|
|
42
55
|
};
|
|
43
56
|
}
|
|
44
57
|
const keyValue = args.env[keyName];
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
// The terminal route's dry run: a contract-only bundle with the persona and prompt digest bound,
|
|
2
2
|
// published through the run scope with no sandbox, key or spend. session.ts holds the live path.
|
|
3
|
-
import { digestText, redactText
|
|
3
|
+
import { digestText, redactText } from "../../evidence/redaction.js";
|
|
4
4
|
import { participantAssignment } from "../../study/participant-assignment.js";
|
|
5
|
-
import {
|
|
5
|
+
import {} from "../../run/bundle.js";
|
|
6
6
|
import { judgeExecution, judgeTerminal, OUTCOME_POLICIES, resultOk } from "../../run/judge.js";
|
|
7
7
|
import { validatePreparedRunArtifactPaths } from "../../run/paths.js";
|
|
8
|
+
import { RunSecrets } from "../../run/secrets.js";
|
|
8
9
|
import { buildTerminalProductBundle, renderTerminalReviewMarkdown } from "./bundle.js";
|
|
9
10
|
import { resolveTerminalPersona, terminalPersonaRef } from "./persona.js";
|
|
10
11
|
import { declaredRuntimeProvenance } from "./runtime.js";
|
|
11
|
-
import { defaultMission
|
|
12
|
+
import { defaultMission } from "./session.js";
|
|
12
13
|
import {} from "./types.js";
|
|
13
14
|
import { studyResultIdentity } from "../../run/study-result.js";
|
|
14
15
|
/**
|
|
@@ -18,32 +19,22 @@ import { studyResultIdentity } from "../../run/study-result.js";
|
|
|
18
19
|
export async function runDryTerminalStudy(args) {
|
|
19
20
|
const { plan, input, cwd, warnings, failed, scope } = args;
|
|
20
21
|
const { product } = plan;
|
|
21
|
-
const { evidenceMission, physicalCwd, persona } = await prepareDryPersona({
|
|
22
|
+
const { evidenceMission, physicalCwd, persona, secrets } = await prepareDryPersona({
|
|
22
23
|
plan,
|
|
23
24
|
cwd,
|
|
24
25
|
env: input.env ?? process.env,
|
|
25
26
|
warnings,
|
|
26
27
|
});
|
|
27
|
-
const started = await scope.startRun({
|
|
28
|
+
const started = await scope.startRun(plan, input, {
|
|
28
29
|
cwd: physicalCwd,
|
|
29
|
-
|
|
30
|
-
mintRunId: makeTerminalRunId,
|
|
31
|
-
mode: "dry-run",
|
|
32
|
-
study: plan.study,
|
|
33
|
-
warnings: plan.warnings,
|
|
30
|
+
prefix: "terminal",
|
|
34
31
|
renderReview: renderTerminalReviewMarkdown,
|
|
35
|
-
|
|
32
|
+
secrets,
|
|
36
33
|
});
|
|
37
34
|
if (!started.ok)
|
|
38
35
|
return failed(started.code, started.message);
|
|
39
36
|
const { run } = started;
|
|
40
|
-
const { runId, createdAt } = run;
|
|
41
|
-
const source = await buildRunSource({
|
|
42
|
-
capturedAt: createdAt,
|
|
43
|
-
cwd: physicalCwd,
|
|
44
|
-
humanishSource: "present",
|
|
45
|
-
packageName: "humanish",
|
|
46
|
-
});
|
|
37
|
+
const { runId, createdAt, source } = run;
|
|
47
38
|
const policies = plan.residual.policies;
|
|
48
39
|
const judgment = judgeTerminal({ dryRun: true, participant: undefined });
|
|
49
40
|
const bundle = buildTerminalProductBundle({
|
|
@@ -107,10 +98,9 @@ async function prepareDryPersona(args) {
|
|
|
107
98
|
const { plan, cwd, env, warnings } = args;
|
|
108
99
|
const { product } = plan;
|
|
109
100
|
const mission = plan.mission ?? defaultMission(product.name);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const evidenceMission = participantAssignment({ mission }, scrubLiterals(knownSecretValues)).mission;
|
|
101
|
+
// A dry run checks no key, so it scrubs every key the live run could read.
|
|
102
|
+
const secrets = new RunSecrets([env.CODEX_API_KEY, env.OPENAI_API_KEY, env.E2B_API_KEY].map((value) => value?.trim() ?? ""));
|
|
103
|
+
const evidenceMission = participantAssignment({ mission }, secrets.scrub).mission;
|
|
114
104
|
const terminalPersona = await resolveTerminalPersona({ plan, cwd, warnings });
|
|
115
105
|
// The composed prompt = mission + persona + public-surface manifest. Only the author mission
|
|
116
106
|
// goes plaintext into evidence (it is public-safe committed study text); the full composed prompt
|
|
@@ -121,9 +111,9 @@ async function prepareDryPersona(args) {
|
|
|
121
111
|
productName: product.name,
|
|
122
112
|
publicSurfaces: product.publicSurfaces,
|
|
123
113
|
});
|
|
124
|
-
const persona = terminalPersonaRef(terminalPersona, digestText(composedPrompt),
|
|
114
|
+
const persona = terminalPersonaRef(terminalPersona, digestText(composedPrompt), secrets.scrub);
|
|
125
115
|
const { physicalCwd } = terminalPersona;
|
|
126
|
-
return { evidenceMission, physicalCwd, persona };
|
|
116
|
+
return { evidenceMission, physicalCwd, persona, secrets };
|
|
127
117
|
}
|
|
128
118
|
/** The declared runtime provenance as a dry-run event; nothing is observed without a sandbox. */
|
|
129
119
|
function runtimeDeclaredEvent(runtime, createdAt) {
|
|
@@ -13,7 +13,6 @@ export interface LiveFinishInputs {
|
|
|
13
13
|
cwd: string;
|
|
14
14
|
sanitize: (text: string) => string;
|
|
15
15
|
nowIso: () => string;
|
|
16
|
-
knownSecretValues: string[];
|
|
17
16
|
runtimeEnv: LiveSandboxInputs["runtimeEnv"];
|
|
18
17
|
runtime: LiveSandboxInputs["runtime"];
|
|
19
18
|
persona: ActorPersonaRef;
|
|
@@ -14,19 +14,22 @@ import { terminalExecutionFailures, terminalStudyResult, terminalParticipantFact
|
|
|
14
14
|
import { parseTerminalTokenUsage } from "./token-usage.js";
|
|
15
15
|
import { buildTerminalActorTrace, scrubSplitKnownValues, tailOf } from "./trace.js";
|
|
16
16
|
function buildLiveTrace(inputs) {
|
|
17
|
-
const { persona, sanitize, nowIso, runtimeEnv, runtime
|
|
17
|
+
const { persona, sanitize, nowIso, runtimeEnv, runtime } = inputs;
|
|
18
18
|
const { product } = inputs.plan;
|
|
19
19
|
const { session } = inputs;
|
|
20
20
|
const { createdAt } = inputs.run;
|
|
21
21
|
const { terminalEvents, commandLog, discardedPrefixes } = inputs.recorder;
|
|
22
|
-
// Prefix reconciliation may cut through a known key. Scrub
|
|
23
|
-
// chunks before any transcript/trace/event artifact is
|
|
22
|
+
// Prefix reconciliation may cut through a known key. Scrub the known values, as written and in
|
|
23
|
+
// each encoded form, across the retained chunks before any transcript/trace/event artifact is
|
|
24
|
+
// persisted. Check both each stream and
|
|
24
25
|
// the combined event order that the transcript uses; either view can assemble a split value.
|
|
25
|
-
scrubSplitKnownValues(terminalEvents,
|
|
26
|
+
scrubSplitKnownValues(terminalEvents, (text) => inputs.run.secrets.spans(text), discardedPrefixes);
|
|
26
27
|
// Build the actor trace first (the cost ledger reads its tokenUsage). A session past the cap
|
|
27
28
|
// ends its transcript with what was not stored, and its trace says the same.
|
|
28
29
|
const cut = inputs.recorder.transcriptCut();
|
|
29
|
-
|
|
30
|
+
// Normalizing removes terminal escape sequences, which rejoins a value one of them split, so the
|
|
31
|
+
// transcript is sanitized again after it.
|
|
32
|
+
const normalizedTranscript = `${sanitize(normalizeLocalActorTranscript(terminalEvents.map((e) => e.chunk).join("")))}${cut ? `\n[humanish] ${cut.notice}` : ""}`;
|
|
30
33
|
// Parsed from the full stream: usage records arrive once per turn, and the tail
|
|
31
34
|
// would drop all but the last. Records past the cap were kept as they arrived.
|
|
32
35
|
const terminalTokenUsage = parseTerminalTokenUsage([normalizedTranscript, ...(cut?.usage ?? [])].join("\n"));
|
|
@@ -53,21 +53,20 @@ import { checkLiveTerminalMachine, runLiveTerminalSession } from "./session.js";
|
|
|
53
53
|
import { terminalRouteScorer } from "../../study/adapter-scorer-loader.js";
|
|
54
54
|
import { withLateScorer } from "../../study/route-inputs.js";
|
|
55
55
|
import {} from "./types.js";
|
|
56
|
-
import {
|
|
56
|
+
import { refusedResult } from "../../run/study-result.js";
|
|
57
57
|
/** A refused terminal study's result: the route's envelope, and the analysis record a refusal gets. */
|
|
58
58
|
export function terminalStudyRefusal(options, refusal) {
|
|
59
59
|
const { config, dryRun } = options;
|
|
60
|
-
const refused = {
|
|
61
|
-
|
|
62
|
-
ok: false,
|
|
60
|
+
const refused = refusedResult("terminal", {
|
|
61
|
+
studyId: config.id,
|
|
63
62
|
cwd: path.resolve(options.cwd),
|
|
63
|
+
error: { code: refusal.code, message: refusal.message },
|
|
64
|
+
}, {
|
|
64
65
|
actor: refusal.actor ?? config.actor?.type ?? "",
|
|
65
66
|
product: config.subject.product?.name ?? "",
|
|
66
67
|
dryRun,
|
|
67
68
|
runId: options.runId ?? "not-created",
|
|
68
|
-
|
|
69
|
-
error: { code: refusal.code, message: refusal.message },
|
|
70
|
-
};
|
|
69
|
+
});
|
|
71
70
|
return completeRefusalAnalysis(refused, config, options);
|
|
72
71
|
}
|
|
73
72
|
/**
|
|
@@ -86,11 +85,11 @@ export function admitTerminalPlan(plan, input) {
|
|
|
86
85
|
});
|
|
87
86
|
}
|
|
88
87
|
/** A live plan's local checks, made outside any run scope. */
|
|
89
|
-
function admitTerminalRun(plan, input) {
|
|
88
|
+
async function admitTerminalRun(plan, input) {
|
|
90
89
|
const warnings = [];
|
|
91
90
|
if (plan.dryRun)
|
|
92
91
|
return { ok: true, admitted: { plan, warnings } };
|
|
93
|
-
const checked = checkLiveTerminalMachine(plan, input, warnings);
|
|
92
|
+
const checked = await checkLiveTerminalMachine(plan, input, warnings);
|
|
94
93
|
if (checked.ok)
|
|
95
94
|
return { ok: true, admitted: { plan, warnings, runtimeEnv: checked.runtimeEnv } };
|
|
96
95
|
return {
|
|
@@ -101,16 +100,11 @@ function admitTerminalRun(plan, input) {
|
|
|
101
100
|
/** The route's envelope for a run that stops before its bundle. */
|
|
102
101
|
function terminalFailure(plan, input, warnings) {
|
|
103
102
|
const cwd = path.resolve(input.cwd);
|
|
104
|
-
return (code, message) => ({
|
|
105
|
-
...studyResultIdentity("terminal", plan.studyId),
|
|
106
|
-
ok: false,
|
|
107
|
-
cwd,
|
|
103
|
+
return (code, message) => refusedResult("terminal", { studyId: plan.studyId, cwd, warnings, error: { code, message } }, {
|
|
108
104
|
actor: plan.actor,
|
|
109
105
|
product: plan.product.name,
|
|
110
106
|
dryRun: plan.dryRun,
|
|
111
107
|
runId: input.runId ?? "not-created",
|
|
112
|
-
warnings,
|
|
113
|
-
error: { code, message },
|
|
114
108
|
});
|
|
115
109
|
}
|
|
116
110
|
async function runTerminalPlanInScope(admitted, input, scope) {
|
|
@@ -4,14 +4,14 @@ import type { LiveTerminalAuth, LiveTerminalPlan, RunLiveTerminalSessionArgs, Te
|
|
|
4
4
|
* E2B_API_KEY. They read only the plan and the environment, so the CLI makes them before it loads
|
|
5
5
|
* a declared scorer. Returns the runtime key's command-scoped placement, or the refusal.
|
|
6
6
|
*/
|
|
7
|
-
export declare function checkLiveTerminalMachine(plan: LiveTerminalPlan, input: TerminalRunInput, warnings: string[]): {
|
|
7
|
+
export declare function checkLiveTerminalMachine(plan: LiveTerminalPlan, input: TerminalRunInput, warnings: string[]): Promise<{
|
|
8
8
|
readonly ok: true;
|
|
9
9
|
readonly runtimeEnv: LiveTerminalAuth;
|
|
10
10
|
} | {
|
|
11
11
|
readonly ok: false;
|
|
12
12
|
readonly code: TerminalProductStudyErrorCode;
|
|
13
13
|
readonly message: string;
|
|
14
|
-
}
|
|
14
|
+
}>;
|
|
15
15
|
/**
|
|
16
16
|
* The live in-sandbox agent session orchestrator. Enforces the 8-point safety contract by
|
|
17
17
|
* construction; fails closed before any sandbox/key/spend on any precondition miss. Persists the substrate-lifecycle/command-log/interventions/cleanup ledgers,
|
|
@@ -21,4 +21,3 @@ export declare function checkLiveTerminalMachine(plan: LiveTerminalPlan, input:
|
|
|
21
21
|
export declare function runLiveTerminalSession(args: RunLiveTerminalSessionArgs): Promise<TerminalProductStudyResult>;
|
|
22
22
|
/** The default mission when the study omits one. Public-safe, product-neutral author text. */
|
|
23
23
|
export declare function defaultMission(productName: string): string;
|
|
24
|
-
export declare function makeTerminalRunId(): string;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { keysCheck } from "../../study/requirements.js";
|
|
3
3
|
import { declaredRuntimeProvenance } from "./runtime.js";
|
|
4
|
-
import { digestText, redactText
|
|
5
|
-
import {
|
|
6
|
-
import { buildRunSource } from "../../run/bundle.js";
|
|
4
|
+
import { digestText, redactText } from "../../evidence/redaction.js";
|
|
5
|
+
import { RunSecrets } from "../../run/secrets.js";
|
|
7
6
|
import { renderTerminalReviewMarkdown } from "./bundle.js";
|
|
8
7
|
import { buildRuntimeAuth, buildSandboxMetadata } from "./credentials.js";
|
|
9
8
|
import { resolveTerminalPersona, terminalPersonaRef } from "./persona.js";
|
|
@@ -15,7 +14,7 @@ import { finishLiveTerminalSession } from "./live-finish.js";
|
|
|
15
14
|
* E2B_API_KEY. They read only the plan and the environment, so the CLI makes them before it loads
|
|
16
15
|
* a declared scorer. Returns the runtime key's command-scoped placement, or the refusal.
|
|
17
16
|
*/
|
|
18
|
-
export function checkLiveTerminalMachine(plan, input, warnings) {
|
|
17
|
+
export async function checkLiveTerminalMachine(plan, input, warnings) {
|
|
19
18
|
const env = input.env ?? process.env;
|
|
20
19
|
const { maxUsd } = plan.caps;
|
|
21
20
|
// planTerminalStudy refuses a positive maxUsd without a costProbe, so one is present here.
|
|
@@ -28,13 +27,14 @@ export function checkLiveTerminalMachine(plan, input, warnings) {
|
|
|
28
27
|
return runtimeEnv;
|
|
29
28
|
// The sandbox is created with E2B_API_KEY, so a missing key is refused before the run starts.
|
|
30
29
|
// The runtime key above is the plan's `key-one-of`; E2B_API_KEY is its only `key`.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
const keys = await keysCheck({
|
|
31
|
+
requirements: plan.requirements,
|
|
32
|
+
env,
|
|
33
|
+
code: "HUMANISH_TERMINAL_KEYS_MISSING",
|
|
34
|
+
need: (names) => `Live terminal-product studies need ${names} in the environment (values are never persisted).`,
|
|
35
|
+
});
|
|
36
|
+
if (keys)
|
|
37
|
+
return { ok: false, ...keys };
|
|
38
38
|
return { ok: true, runtimeEnv };
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
@@ -53,32 +53,26 @@ export async function runLiveTerminalSession(args) {
|
|
|
53
53
|
const nowIso = () => new Date(now()).toISOString();
|
|
54
54
|
const prepared = await prepareLivePrompt({ plan, cwd, runtimeEnv, env, warnings });
|
|
55
55
|
const { mission, physicalCwd, persona, composedPrompt, verdictNonce } = prepared;
|
|
56
|
-
const {
|
|
57
|
-
const started = await scope.startRun({
|
|
56
|
+
const { secrets, sanitize } = prepared;
|
|
57
|
+
const started = await scope.startRun(plan, input, {
|
|
58
58
|
cwd: physicalCwd,
|
|
59
|
-
|
|
60
|
-
mintRunId: makeTerminalRunId,
|
|
61
|
-
// This entry point is the live terminal route; its dry-run sibling is a separate function.
|
|
62
|
-
mode: "live",
|
|
63
|
-
study: plan.study,
|
|
64
|
-
warnings: plan.warnings,
|
|
59
|
+
prefix: "terminal",
|
|
65
60
|
renderReview: renderTerminalReviewMarkdown,
|
|
66
|
-
observer: { open: input.open === true, render: deps.renderObserver },
|
|
67
61
|
now,
|
|
62
|
+
secrets,
|
|
68
63
|
});
|
|
69
64
|
if (!started.ok)
|
|
70
65
|
return failed(started.code, started.message);
|
|
71
66
|
const { run } = started;
|
|
72
|
-
const { runId,
|
|
73
|
-
const source = await buildRunSource({
|
|
74
|
-
capturedAt: createdAt,
|
|
75
|
-
cwd: physicalCwd,
|
|
76
|
-
humanishSource: "present",
|
|
77
|
-
packageName: "humanish",
|
|
78
|
-
});
|
|
67
|
+
const { runId, paths: runPaths, source } = run;
|
|
79
68
|
const e2bApiKey = env.E2B_API_KEY?.trim() ?? "";
|
|
80
69
|
// The ledgers + capture buffers, mutated through the live lifecycle.
|
|
81
|
-
const recorder = createTerminalRecorder({
|
|
70
|
+
const recorder = createTerminalRecorder({
|
|
71
|
+
nowIso,
|
|
72
|
+
sanitize,
|
|
73
|
+
knownSecretValues: run.secrets.forms(),
|
|
74
|
+
verdictNonce,
|
|
75
|
+
});
|
|
82
76
|
const { version, model, modelSource, reasoningEffort } = plan.runtime;
|
|
83
77
|
const runtime = declaredRuntimeProvenance({
|
|
84
78
|
...(version === undefined ? {} : { version }),
|
|
@@ -128,7 +122,6 @@ export async function runLiveTerminalSession(args) {
|
|
|
128
122
|
cwd,
|
|
129
123
|
sanitize,
|
|
130
124
|
nowIso,
|
|
131
|
-
knownSecretValues,
|
|
132
125
|
runtimeEnv,
|
|
133
126
|
runtime,
|
|
134
127
|
persona,
|
|
@@ -166,17 +159,16 @@ async function prepareLivePrompt(args) {
|
|
|
166
159
|
// anything persists (a key has no detectable "shape" if it is an arbitrary token); redactText is
|
|
167
160
|
// the second pass for secret-shaped content. Applied pre-truncation so a cut can never split a
|
|
168
161
|
// value past the scrubber.
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const persona = terminalPersonaRef(terminalPersona, promptDigest, scrubKnownValues);
|
|
162
|
+
const secrets = new RunSecrets([runtimeEnv.keyValue, env.E2B_API_KEY?.trim() ?? ""]);
|
|
163
|
+
const sanitize = (text) => redactText(secrets.scrub(text));
|
|
164
|
+
const persona = terminalPersonaRef(terminalPersona, promptDigest, secrets.scrub);
|
|
173
165
|
return {
|
|
174
166
|
mission,
|
|
175
167
|
physicalCwd: terminalPersona.physicalCwd,
|
|
176
168
|
persona,
|
|
177
169
|
composedPrompt,
|
|
178
170
|
verdictNonce,
|
|
179
|
-
|
|
171
|
+
secrets,
|
|
180
172
|
sanitize,
|
|
181
173
|
};
|
|
182
174
|
}
|
|
@@ -197,7 +189,3 @@ function composeLivePrompt(args) {
|
|
|
197
189
|
export function defaultMission(productName) {
|
|
198
190
|
return `You are an autonomous agent. Discover ${productName} from its public surfaces and determine whether it can help with a durable real task. Stay within the declared no-spend caps. Leave feedback if the workflow is confusing.`;
|
|
199
191
|
}
|
|
200
|
-
export function makeTerminalRunId() {
|
|
201
|
-
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
202
|
-
return `terminal-${stamp}-${randomBytes(4).toString("hex")}`;
|
|
203
|
-
}
|
|
@@ -5,9 +5,11 @@ import { type CommandLogRecord, type TerminalEventRecord } from "./types.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* Per-chunk sanitization cannot recognize a value split across deliveries. Redact those complete
|
|
7
7
|
* known values before persistence without collapsing events or changing stdout/stderr ordering.
|
|
8
|
-
*
|
|
8
|
+
* `spansOf` finds them in the joined text: RunSecrets.spans, which also finds a value in an
|
|
9
|
+
* encoded form or split by a terminal escape sequence. Work backwards through the spans so edits
|
|
10
|
+
* to later text leave earlier offsets valid.
|
|
9
11
|
*/
|
|
10
|
-
export declare function scrubSplitKnownValues(events: TerminalEventRecord[],
|
|
12
|
+
export declare function scrubSplitKnownValues(events: TerminalEventRecord[], spansOf: (text: string) => ReadonlyArray<readonly [number, number]>, discardedPrefixes: Record<"stdout" | "stderr" | "combined", string>): void;
|
|
11
13
|
/** Redacted, ellipsis-prefixed tail of a captured stream/log for a message field. */
|
|
12
14
|
export declare function tailOf(text: string): string;
|
|
13
15
|
/**
|