humanish 0.111.4 → 0.113.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/CHANGELOG.md +37 -0
- package/README.md +11 -0
- package/TELEMETRY.md +21 -0
- package/dist/actors/codex/restricted-participant-policy.d.ts +21 -1
- package/dist/actors/codex/restricted-participant-policy.js +20 -2
- package/dist/actors/codex/restricted-participant.d.ts +1 -1
- package/dist/actors/codex/restricted-participant.js +2 -1
- package/dist/actors/computer-use/loop/debrief.d.ts +12 -4
- package/dist/actors/computer-use/loop/debrief.js +57 -20
- package/dist/actors/computer-use/loop/ending.d.ts +5 -2
- package/dist/actors/computer-use/loop/impressions.d.ts +14 -0
- package/dist/actors/computer-use/loop/impressions.js +40 -0
- package/dist/actors/computer-use/loop/session.d.ts +3 -1
- package/dist/actors/computer-use/loop/session.js +2 -0
- package/dist/actors/computer-use/loop/trace.js +2 -0
- package/dist/actors/computer-use/loop/types.d.ts +8 -1
- package/dist/actors/computer-use/loop.d.ts +2 -1
- package/dist/actors/computer-use/loop.js +15 -3
- package/dist/actors/computer-use/openai-conversation.d.ts +96 -0
- package/dist/actors/computer-use/openai-conversation.js +238 -0
- package/dist/actors/computer-use/openai-provider.js +38 -48
- package/dist/actors/contract.d.ts +45 -3
- package/dist/actors/contract.js +13 -0
- package/dist/analysis/diagnostics.js +7 -12
- package/dist/analysis/evidence-sources.d.ts +3 -1
- package/dist/analysis/evidence-sources.js +10 -1
- package/dist/analysis/evidence.js +2 -2
- package/dist/analysis/execute.d.ts +1 -1
- package/dist/analysis/execute.js +37 -5
- package/dist/analysis/service.js +7 -1
- package/dist/analysis/types.d.ts +23 -0
- package/dist/analysis/validation.d.ts +80 -35
- package/dist/analysis/validation.js +58 -11
- package/dist/cli/commands/runs.js +37 -20
- package/dist/cli/commands/setup.js +95 -6
- package/dist/cli/commands/study-format.js +7 -4
- package/dist/cli/commands/study.js +1 -1
- package/dist/cli/commands/tui.js +1 -1
- package/dist/cli/doctor.d.ts +2 -0
- package/dist/cli/doctor.js +18 -0
- package/dist/cli/findings.d.ts +34 -3
- package/dist/cli/findings.js +136 -18
- package/dist/cli/io.d.ts +4 -1
- package/dist/cli/io.js +3 -2
- package/dist/cli/program.js +35 -1
- package/dist/cli/secret-prompt.d.ts +5 -1
- package/dist/cli/secret-prompt.js +6 -2
- package/dist/cli/update-check-worker.d.ts +1 -0
- package/dist/cli/update-check-worker.js +5 -0
- package/dist/cli/update-check.d.ts +46 -0
- package/dist/cli/update-check.js +203 -0
- package/dist/feedback/draft.js +32 -4
- package/dist/keys/key-resolution.js +2 -0
- package/dist/keys/key-status.d.ts +37 -0
- package/dist/keys/key-status.js +68 -0
- package/dist/observer/run-routes.js +8 -4
- package/dist/observer-app.html +4 -4
- package/dist/routes/computer-use/bundle.d.ts +1 -1
- package/dist/routes/computer-use/bundle.js +6 -16
- package/dist/routes/computer-use/fanout-bundle.js +40 -37
- package/dist/routes/computer-use/fanout-records.d.ts +2 -0
- package/dist/routes/computer-use/fanout-records.js +18 -26
- package/dist/routes/computer-use/local-vm.js +9 -2
- package/dist/routes/computer-use/participant-desktop.d.ts +6 -0
- package/dist/routes/computer-use/participant-execution.js +13 -12
- package/dist/routes/computer-use/participant-facts.d.ts +2 -2
- package/dist/routes/computer-use/participant-facts.js +10 -1
- package/dist/routes/computer-use/rerun-selection.js +23 -21
- package/dist/routes/computer-use/result.js +10 -17
- package/dist/routes/computer-use/self-report.js +7 -1
- package/dist/routes/computer-use/setup.js +14 -0
- package/dist/routes/computer-use/single-bundle.d.ts +2 -0
- package/dist/routes/computer-use/single-bundle.js +81 -37
- package/dist/routes/computer-use/types.d.ts +4 -1
- package/dist/routes/scripted/bundle.d.ts +1 -1
- package/dist/routes/scripted/bundle.js +9 -19
- package/dist/routes/scripted/surface-records.d.ts +0 -1
- package/dist/routes/scripted/surface-records.js +27 -13
- package/dist/routes/shared-world/bundle-records.d.ts +2 -2
- package/dist/routes/shared-world/bundle-records.js +24 -30
- package/dist/routes/shared-world/bundle.d.ts +1 -1
- package/dist/routes/shared-world/bundle.js +71 -79
- package/dist/routes/shared-world/participant-specs.d.ts +1 -2
- package/dist/routes/shared-world/participant-specs.js +0 -8
- package/dist/routes/shared-world/result.js +8 -12
- package/dist/routes/terminal/bundle.d.ts +1 -1
- package/dist/routes/terminal/bundle.js +10 -19
- package/dist/routes/terminal/dry-run.js +1 -0
- package/dist/routes/terminal/live-sandbox.js +9 -2
- package/dist/routes/terminal/plan.js +1 -0
- package/dist/routes/terminal/runtime.d.ts +1 -0
- package/dist/routes/terminal/runtime.js +1 -0
- package/dist/routes/terminal/session.js +1 -0
- package/dist/run/costs.js +9 -20
- package/dist/run/detail.d.ts +1 -1
- package/dist/run/detail.js +13 -4
- package/dist/run/display.d.ts +2 -2
- package/dist/run/display.js +15 -18
- package/dist/run/dry-run.js +10 -25
- package/dist/run/judge.d.ts +24 -0
- package/dist/run/judge.js +54 -0
- package/dist/run/liveness.d.ts +18 -0
- package/dist/run/liveness.js +24 -0
- package/dist/run/outcomes.d.ts +8 -1
- package/dist/run/outcomes.js +58 -12
- package/dist/run/participant-caption.d.ts +31 -0
- package/dist/run/participant-caption.js +60 -0
- package/dist/run/participant-records.d.ts +4 -2
- package/dist/run/participant-records.js +6 -0
- package/dist/run/review-markdown.d.ts +11 -0
- package/dist/run/review-markdown.js +22 -0
- package/dist/run/run-cost.d.ts +7 -0
- package/dist/run/run-cost.js +25 -5
- package/dist/run/run-index.d.ts +4 -50
- package/dist/run/run-index.js +57 -87
- package/dist/run/run.d.ts +2 -2
- package/dist/run/run.js +5 -3
- package/dist/run/stats.js +2 -12
- package/dist/run/transient-comms-secrets.d.ts +8 -3
- package/dist/run/transient-comms-secrets.js +21 -1
- package/dist/run-study.js +17 -1
- package/dist/study/discover.js +6 -1
- package/dist/study/doctor.d.ts +2 -0
- package/dist/study/doctor.js +45 -7
- package/dist/study/init-templates.js +10 -5
- package/dist/study/keys.d.ts +1 -0
- package/dist/study/keys.js +1 -1
- package/dist/study/parse/execution.js +7 -1
- package/dist/study/plan-types.d.ts +1 -0
- package/dist/study/plan.js +3 -2
- package/dist/study/preflight-result.d.ts +1 -0
- package/dist/study/preflight-result.js +1 -1
- package/dist/study/preflight.d.ts +2 -0
- package/dist/study/preflight.js +54 -14
- package/dist/study/types.d.ts +4 -1
- package/dist/study/warnings.d.ts +7 -1
- package/dist/study/warnings.js +82 -4
- package/dist/substrates/local/capacity.d.ts +75 -0
- package/dist/substrates/local/capacity.js +143 -0
- package/dist/substrates/local/firecracker-desktop.d.ts +8 -0
- package/dist/substrates/local/firecracker-desktop.js +34 -3
- package/dist/substrates/local/runtime-host.d.ts +16 -3
- package/dist/substrates/local/runtime-host.js +49 -4
- package/dist/substrates/local/runtime.d.ts +10 -0
- package/dist/substrates/local/runtime.js +44 -5
- package/dist/tui-app.js +75 -75
- package/dist/verify/liveness.d.ts +2 -2
- package/dist/verify/liveness.js +7 -4
- package/docs/architecture/actor-contract.md +20 -6
- package/docs/architecture/terminal-product-route.md +14 -0
- package/docs/contracts/feedback.md +8 -1
- package/docs/contracts/run-bundle.md +10 -0
- package/docs/contracts/schemas.md +65 -34
- package/docs/contracts/study-analysis.md +86 -22
- package/package.json +2 -1
- package/skills/humanish/SKILL.md +21 -10
- package/dist/actors/computer-use/openai-requests.d.ts +0 -62
- package/dist/actors/computer-use/openai-requests.js +0 -144
package/dist/run/run-index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnalysisCostCache } from "./run-index-analysis.js";
|
|
2
2
|
import type { RunAnalysisCost } from "./run-cost.js";
|
|
3
|
-
import { type RunLiveness
|
|
3
|
+
import { type RunLiveness } from "./status.js";
|
|
4
4
|
import { type RunStudyProvenance } from "./study-provenance.js";
|
|
5
5
|
import type { ExecutionFailure } from "./judge.js";
|
|
6
6
|
declare const RUN_INDEX_SCHEMA = "humanish.run-index.v1";
|
|
@@ -51,13 +51,6 @@ export interface RunIndexResult {
|
|
|
51
51
|
/** Directories that could not be read at all, by name: surfaced, never silently dropped. */
|
|
52
52
|
unreadable: string[];
|
|
53
53
|
}
|
|
54
|
-
/** What a cached entry was derived from, so a changed file invalidates exactly that entry. */
|
|
55
|
-
interface CacheKey {
|
|
56
|
-
file: string;
|
|
57
|
-
mtimeMs: number;
|
|
58
|
-
size: number;
|
|
59
|
-
ino: number;
|
|
60
|
-
}
|
|
61
54
|
/**
|
|
62
55
|
* A process-lifetime cache. Deliberately explicit rather than module-global state: a caller that
|
|
63
56
|
* refreshes on a cadence keeps one and passes it back, and a caller that wants a cold read passes
|
|
@@ -67,51 +60,12 @@ export declare class RunIndexCache {
|
|
|
67
60
|
private readonly slots;
|
|
68
61
|
/** Analysis costs, keyed on their own fingerprint: analysis lands after the status record. */
|
|
69
62
|
readonly analysis: AnalysisCostCache;
|
|
70
|
-
get(runId: string, key:
|
|
71
|
-
set(runId: string, key:
|
|
63
|
+
get(runId: string, key: string): RunIndexEntry | undefined;
|
|
64
|
+
set(runId: string, key: string, entry: RunIndexEntry): void;
|
|
72
65
|
/** Drop entries for runs that no longer exist, so a long-lived surface cannot leak. */
|
|
73
66
|
retain(runIds: Iterable<string>): void;
|
|
74
67
|
get size(): number;
|
|
75
68
|
}
|
|
76
|
-
/** The legacy shape this reads out of a bundle. Narrow on purpose: only listing facts. */
|
|
77
|
-
interface BundleFacts {
|
|
78
|
-
runId?: string;
|
|
79
|
-
mode?: string;
|
|
80
|
-
createdAt?: string;
|
|
81
|
-
study?: unknown;
|
|
82
|
-
lab?: unknown;
|
|
83
|
-
persona?: {
|
|
84
|
-
source?: string;
|
|
85
|
-
};
|
|
86
|
-
scenario?: {
|
|
87
|
-
source?: string;
|
|
88
|
-
};
|
|
89
|
-
simulations?: {
|
|
90
|
-
status?: string;
|
|
91
|
-
}[];
|
|
92
|
-
review?: {
|
|
93
|
-
verdict?: string;
|
|
94
|
-
participants?: {
|
|
95
|
-
total: number;
|
|
96
|
-
reachedGoal: number;
|
|
97
|
-
reportedFriction?: number;
|
|
98
|
-
};
|
|
99
|
-
};
|
|
100
|
-
cost?: {
|
|
101
|
-
estimatedTotalUsd?: number | null;
|
|
102
|
-
fullyEstimated?: unknown;
|
|
103
|
-
};
|
|
104
|
-
outcome?: unknown;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* One run's liveness from files already read: its status record when usable, else its bundle.
|
|
108
|
-
* `readRunIndex` reads in the same order; `humanish verify` calls this with both in hand. `record`
|
|
109
|
-
* is the status record the liveness came from, when there was one.
|
|
110
|
-
*/
|
|
111
|
-
export declare function runLiveness(runId: string, status: unknown, bundle: Pick<BundleFacts, "simulations" | "outcome">, nowMs: number): {
|
|
112
|
-
liveness: RunLiveness;
|
|
113
|
-
record?: RunStatusRecord;
|
|
114
|
-
};
|
|
115
69
|
export interface ReadRunIndexOptions {
|
|
116
70
|
/** Reused across refreshes so unchanged runs are not re-read. */
|
|
117
71
|
cache?: RunIndexCache;
|
|
@@ -122,7 +76,7 @@ export interface ReadRunIndexOptions {
|
|
|
122
76
|
limit?: number;
|
|
123
77
|
}
|
|
124
78
|
/**
|
|
125
|
-
* Read every run in `.humanish/runs`,
|
|
79
|
+
* Read every run in `.humanish/runs`, from its cached source files. Never throws for a bad run directory;
|
|
126
80
|
* an unreadable one is named in `unreadable`.
|
|
127
81
|
*/
|
|
128
82
|
export declare function readRunIndex(cwdInput: string, options?: ReadRunIndexOptions): Promise<RunIndexResult>;
|
package/dist/run/run-index.js
CHANGED
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
// The run index
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// then parses each `run.json`: measured 197ms cold / 152ms warm at 25 runs. That is fine for a
|
|
5
|
-
// command that prints once and exits, and much too hot for a surface that refreshes on a cadence
|
|
6
|
-
// over SSH. This module reads the small `status.json` record each run now writes (586 bytes beside
|
|
7
|
-
// a 92KB bundle) and caches per-run entries keyed on the stat of the file each was derived from.
|
|
8
|
-
//
|
|
9
|
-
// Rules, unchanged from the record itself:
|
|
10
|
-
// - `run.json` is the evidence-of-record. Every field here is a projection for listing and
|
|
11
|
-
// classification; nothing here is a claim about what a participant did.
|
|
12
|
-
// - A run with no status record is not assumed finished. It is classified from what is on disk:
|
|
13
|
-
// a bundle means finished, receipts without a bundle mean interrupted. That is the
|
|
14
|
-
// reading of a run whose process died (and, before this contract existed, of every run).
|
|
15
|
-
// - One unreadable run directory degrades that run, never the listing.
|
|
1
|
+
// The run index caches listing facts by both source files. run.json's outcome wins over status,
|
|
2
|
+
// including when a process stopped between publishing its bundle and finalizing its heartbeat.
|
|
3
|
+
// A status record supplies freshness only when the bundle has no outcome.
|
|
16
4
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
17
5
|
import path from "node:path";
|
|
6
|
+
import { runLiveness } from "./liveness.js";
|
|
18
7
|
import { AnalysisCostCache, readIndexedAnalysisCost } from "./run-index-analysis.js";
|
|
19
8
|
import { RUN_STATUS_FILE, classifyRunStatus, isRunStatusRecord, } from "./status.js";
|
|
20
9
|
import { studyProvenanceOf } from "./study-provenance.js";
|
|
@@ -34,11 +23,7 @@ export class RunIndexCache {
|
|
|
34
23
|
const slot = this.slots.get(runId);
|
|
35
24
|
if (slot === undefined)
|
|
36
25
|
return undefined;
|
|
37
|
-
|
|
38
|
-
slot.key.mtimeMs === key.mtimeMs &&
|
|
39
|
-
slot.key.size === key.size &&
|
|
40
|
-
slot.key.ino === key.ino;
|
|
41
|
-
return same ? slot.entry : undefined;
|
|
26
|
+
return slot.key === key ? slot.entry : undefined;
|
|
42
27
|
}
|
|
43
28
|
set(runId, key, entry) {
|
|
44
29
|
this.slots.set(runId, { key, entry });
|
|
@@ -81,7 +66,7 @@ function entryFromStatus(record, nowMs) {
|
|
|
81
66
|
return {
|
|
82
67
|
runId: record.runId,
|
|
83
68
|
derivedFrom: "status",
|
|
84
|
-
liveness:
|
|
69
|
+
liveness: runLiveness(record.runId, record, {}, nowMs).liveness,
|
|
85
70
|
mode: record.mode,
|
|
86
71
|
...(typeof record.pid === "number" ? { pid: record.pid } : {}),
|
|
87
72
|
...studyEntry(studyProvenanceOf(record)),
|
|
@@ -109,29 +94,6 @@ function entryFromStatus(record, nowMs) {
|
|
|
109
94
|
function usableStatusRecord(raw, runId) {
|
|
110
95
|
return isRunStatusRecord(raw) && raw.runId === runId;
|
|
111
96
|
}
|
|
112
|
-
/**
|
|
113
|
-
* The bundle-only reading, for a run with no usable status record. A bundle on disk usually means
|
|
114
|
-
* the run reached its final write. But a live run now flushes an in-progress bundle as it goes (so
|
|
115
|
-
* anything asking what a participant is doing has something to read), and that bundle marks its
|
|
116
|
-
* simulations `running`. With no status record there is no freshness to judge, and the
|
|
117
|
-
* reading of "it started, and nothing here says it finished" is interrupted, not finished.
|
|
118
|
-
*/
|
|
119
|
-
function bundleLiveness(bundle) {
|
|
120
|
-
if (isRunOutcome(bundle.outcome))
|
|
121
|
-
return bundle.outcome.state;
|
|
122
|
-
const inProgress = (bundle.simulations ?? []).some((simulation) => simulation?.status === "running");
|
|
123
|
-
return inProgress ? "interrupted" : "finished";
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* One run's liveness from files already read: its status record when usable, else its bundle.
|
|
127
|
-
* `readRunIndex` reads in the same order; `humanish verify` calls this with both in hand. `record`
|
|
128
|
-
* is the status record the liveness came from, when there was one.
|
|
129
|
-
*/
|
|
130
|
-
export function runLiveness(runId, status, bundle, nowMs) {
|
|
131
|
-
return usableStatusRecord(status, runId)
|
|
132
|
-
? { liveness: classifyRunStatus(status, nowMs), record: status }
|
|
133
|
-
: { liveness: bundleLiveness(bundle) };
|
|
134
|
-
}
|
|
135
97
|
function studyEntry(study) {
|
|
136
98
|
return study === undefined ? {} : { study };
|
|
137
99
|
}
|
|
@@ -141,7 +103,7 @@ function entryFromBundle(runId, bundle) {
|
|
|
141
103
|
return {
|
|
142
104
|
runId,
|
|
143
105
|
derivedFrom: "bundle",
|
|
144
|
-
liveness:
|
|
106
|
+
liveness: runLiveness(runId, undefined, { simulations: bundle.simulations, outcome }, Date.now()).liveness,
|
|
145
107
|
...(bundle.mode === "dry-run" || bundle.mode === "live" ? { mode: bundle.mode } : {}),
|
|
146
108
|
...studyEntry(studyProvenanceOf(bundle)),
|
|
147
109
|
...(bundle.createdAt === undefined ? {} : { startedAt: bundle.createdAt }),
|
|
@@ -160,7 +122,7 @@ function entryFromBundle(runId, bundle) {
|
|
|
160
122
|
};
|
|
161
123
|
}
|
|
162
124
|
/**
|
|
163
|
-
* Read every run in `.humanish/runs`,
|
|
125
|
+
* Read every run in `.humanish/runs`, from its cached source files. Never throws for a bad run directory;
|
|
164
126
|
* an unreadable one is named in `unreadable`.
|
|
165
127
|
*/
|
|
166
128
|
export async function readRunIndex(cwdInput, options = {}) {
|
|
@@ -199,52 +161,60 @@ export async function readRunIndex(cwdInput, options = {}) {
|
|
|
199
161
|
unreadable,
|
|
200
162
|
};
|
|
201
163
|
}
|
|
202
|
-
/** One run's entry,
|
|
164
|
+
/** One run's entry, from its cached source files, or "unreadable" when its bundle cannot be parsed. */
|
|
203
165
|
async function readEntry(runId, runDir, cache, nowMs) {
|
|
204
166
|
const statusFile = path.join(runDir, RUN_STATUS_FILE);
|
|
205
167
|
const bundleFile = path.join(runDir, RUN_BUNDLE_FILE);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
168
|
+
const [statusKey, bundleKey] = await Promise.all([statKey(statusFile), statKey(bundleFile)]);
|
|
169
|
+
const key = JSON.stringify([statusKey, bundleKey]);
|
|
170
|
+
const cached = cache?.get(runId, key);
|
|
171
|
+
if (cached !== undefined) {
|
|
172
|
+
return cached.derivedFrom === "status" &&
|
|
173
|
+
cached.updatedAt !== undefined &&
|
|
174
|
+
cached.completedAt === undefined
|
|
175
|
+
? {
|
|
176
|
+
...cached,
|
|
177
|
+
liveness: classifyRunStatus({ state: "running", updatedAt: cached.updatedAt }, nowMs),
|
|
178
|
+
}
|
|
179
|
+
: cached;
|
|
180
|
+
}
|
|
181
|
+
const [status, raw] = await Promise.all([
|
|
182
|
+
statusKey === null ? undefined : readJson(statusFile),
|
|
183
|
+
bundleKey === null ? undefined : readJson(bundleFile),
|
|
184
|
+
]);
|
|
185
|
+
const record = usableStatusRecord(status, runId) ? status : undefined;
|
|
186
|
+
const bundle = raw !== null && typeof raw === "object" ? raw : undefined;
|
|
187
|
+
let entry;
|
|
188
|
+
if (bundle !== undefined && isRunOutcome(bundle.outcome)) {
|
|
189
|
+
const metadata = record === undefined ? undefined : entryFromStatus(record, nowMs);
|
|
190
|
+
entry = {
|
|
191
|
+
...(metadata === undefined
|
|
192
|
+
? {}
|
|
193
|
+
: {
|
|
194
|
+
pid: metadata.pid,
|
|
195
|
+
startedAt: metadata.startedAt,
|
|
196
|
+
updatedAt: metadata.updatedAt,
|
|
197
|
+
...(metadata.completedAt === undefined ? {} : { completedAt: metadata.completedAt }),
|
|
198
|
+
...(metadata.durationMs === undefined ? {} : { durationMs: metadata.durationMs }),
|
|
199
|
+
...studyEntry(metadata.study),
|
|
200
|
+
}),
|
|
201
|
+
...entryFromBundle(runId, bundle),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
else if (record !== undefined) {
|
|
205
|
+
entry = entryFromStatus(record, nowMs);
|
|
206
|
+
}
|
|
207
|
+
else if (bundle !== undefined) {
|
|
208
|
+
entry = entryFromBundle(runId, bundle);
|
|
209
|
+
}
|
|
210
|
+
else if (bundleKey !== null) {
|
|
211
|
+
return "unreadable";
|
|
230
212
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const bundleKey = await statKey(bundleFile);
|
|
234
|
-
if (bundleKey !== null) {
|
|
235
|
-
const cached = cache?.get(runId, bundleKey);
|
|
236
|
-
if (cached !== undefined)
|
|
237
|
-
return cached;
|
|
238
|
-
const raw = await readJson(bundleFile);
|
|
239
|
-
if (raw === null || typeof raw !== "object")
|
|
240
|
-
return "unreadable";
|
|
241
|
-
const entry = entryFromBundle(runId, raw);
|
|
242
|
-
cache?.set(runId, bundleKey, entry);
|
|
243
|
-
return entry;
|
|
213
|
+
else {
|
|
214
|
+
return { runId, derivedFrom: "directory", liveness: "interrupted" };
|
|
244
215
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return { runId, derivedFrom: "directory", liveness: "interrupted" };
|
|
216
|
+
cache?.set(runId, key, entry);
|
|
217
|
+
return entry;
|
|
248
218
|
}
|
|
249
219
|
/** Newest-first ordering: the most recent thing known about a run, else its id's own timestamp. */
|
|
250
220
|
function sortKey(entry) {
|
package/dist/run/run.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ interface StartRunOptions {
|
|
|
15
15
|
/** `none` when the route creates no sandbox for this run; status.json records it for reclaim. */
|
|
16
16
|
sandboxes?: "none" | undefined;
|
|
17
17
|
/** review.md for the published bundle. */
|
|
18
|
-
renderReview: (bundle: RunBundle) => string;
|
|
18
|
+
renderReview: (bundle: RunBundle, status?: unknown) => string;
|
|
19
19
|
/** Used by `FinishedRun.renderObserver`; `render` is the `StudyDeps.renderObserver` seam. */
|
|
20
20
|
observer?: {
|
|
21
21
|
open: boolean;
|
|
@@ -53,7 +53,7 @@ interface RouteRunOptions {
|
|
|
53
53
|
cwd: string;
|
|
54
54
|
prefix: RunIdPrefix;
|
|
55
55
|
/** review.md for the published bundle. */
|
|
56
|
-
renderReview: (bundle: RunBundle) => string;
|
|
56
|
+
renderReview: (bundle: RunBundle, status?: unknown) => string;
|
|
57
57
|
/** `none` when the route creates no sandbox for this run; status.json records it for reclaim. */
|
|
58
58
|
sandboxes?: "none" | undefined;
|
|
59
59
|
/** Clock for `createdAt` and the latest pointer. The minted id reads the wall clock. */
|
package/dist/run/run.js
CHANGED
|
@@ -14,7 +14,8 @@ import { registerActiveRun } from "./active-runs.js";
|
|
|
14
14
|
import { writeContainedOutputFile, writePreparedRunLatestPointer } from "./contained-output.js";
|
|
15
15
|
import { scrubRunSandboxIds, withPublicSandboxIds } from "./sandbox-ids.js";
|
|
16
16
|
import { RunSecrets } from "./secrets.js";
|
|
17
|
-
import { beginRunStatus, runStatusOutcome, } from "./status.js";
|
|
17
|
+
import { RUN_STATUS_FILE, beginRunStatus, runStatusOutcome, } from "./status.js";
|
|
18
|
+
import { readRunJsonIfExists } from "./locate.js";
|
|
18
19
|
/** An entry point called in a state that does not admit it. */
|
|
19
20
|
class RunLifecycleError extends Error {
|
|
20
21
|
name = "RunLifecycleError";
|
|
@@ -165,11 +166,12 @@ function runPublisher(args) {
|
|
|
165
166
|
});
|
|
166
167
|
await writeContainedOutputFile(paths, RUN_BUNDLE_FILE, json(publicBundle), "utf8");
|
|
167
168
|
await afterBundle(publicBundle);
|
|
169
|
+
const status = recorded === undefined ? await readRunJsonIfExists(paths, RUN_STATUS_FILE) : undefined;
|
|
168
170
|
await writeContainedOutputFile(paths, "review.json", json(publicBundle.review), "utf8");
|
|
169
|
-
await writeContainedOutputFile(paths, "review.md", options.renderReview(publicBundle), "utf8");
|
|
171
|
+
await writeContainedOutputFile(paths, "review.md", options.renderReview(publicBundle, status), "utf8");
|
|
170
172
|
const events = publicBundle.events.map((event) => JSON.stringify(event)).join("\n");
|
|
171
173
|
await writeContainedOutputFile(paths, "events.ndjson", `${events}\n`, "utf8");
|
|
172
|
-
await writeContainedOutputFile(paths, "observer/observer-data.json", json(buildObserverData(publicBundle)), "utf8");
|
|
174
|
+
await writeContainedOutputFile(paths, "observer/observer-data.json", json(buildObserverData(publicBundle, undefined, status)), "utf8");
|
|
173
175
|
};
|
|
174
176
|
const snapshot = async (bundle) => {
|
|
175
177
|
await writeBundleFiles(bundle, undefined, async () => { });
|
package/dist/run/stats.js
CHANGED
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { runDisplay, runDisplayLabel } from "./display.js";
|
|
6
6
|
import { readRunIndex } from "./run-index.js";
|
|
7
7
|
import { addCostTotals, emptyCostTotals, readCostTotals, } from "./costs.js";
|
|
8
|
+
import { runEstimateUsd } from "./run-cost.js";
|
|
8
9
|
import { round6 } from "./pricing.js";
|
|
9
10
|
import { plural } from "./text.js";
|
|
10
11
|
const STATS_SCHEMA = "humanish.stats.v2";
|
|
@@ -27,17 +28,6 @@ function addParticipants(into, entry) {
|
|
|
27
28
|
into.reachedGoal += entry.participants.reachedGoal;
|
|
28
29
|
into.reportedFriction += entry.participants.reportedFriction ?? 0;
|
|
29
30
|
}
|
|
30
|
-
/**
|
|
31
|
-
* A run's participant and desktop estimate: its recorded figure, or $0 for a dry run that records
|
|
32
|
-
* none, since a dry run makes no model request and creates no desktop. Undefined when the cost is
|
|
33
|
-
* unknown, including a dry run that records an unknown (null) figure.
|
|
34
|
-
*/
|
|
35
|
-
function runEstimateUsd(entry) {
|
|
36
|
-
const usd = entry.estimatedCostUsd;
|
|
37
|
-
if (typeof usd === "number" && Number.isFinite(usd) && usd >= 0)
|
|
38
|
-
return usd;
|
|
39
|
-
return usd === undefined && entry.mode === "dry-run" ? 0 : undefined;
|
|
40
|
-
}
|
|
41
31
|
function entryTime(entry) {
|
|
42
32
|
return entry.startedAt ?? entry.completedAt ?? entry.updatedAt;
|
|
43
33
|
}
|
|
@@ -175,7 +165,7 @@ export async function computeStats(cwdInput, options = {}) {
|
|
|
175
165
|
totals.dryRun += 1;
|
|
176
166
|
if (entry.liveness === "running")
|
|
177
167
|
totals.running += 1;
|
|
178
|
-
const runUsd = runEstimateUsd(entry);
|
|
168
|
+
const runUsd = runEstimateUsd(entry) ?? undefined;
|
|
179
169
|
if (runUsd === undefined)
|
|
180
170
|
totals.unpricedRuns += 1;
|
|
181
171
|
else
|
|
@@ -4,7 +4,12 @@ export declare function registerTransientCommsSecrets(values: string[]): void;
|
|
|
4
4
|
/** Literal, longest-first replacement in one pass; replacement text is never matched recursively. */
|
|
5
5
|
export declare function scrubTransientCommsText(text: string): string;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* A scrub for text a model writes: each scope value as written and in its encoded forms
|
|
8
|
+
* (percent-encoded, JSON-escaped, base64, base64url, hex), and where escapes split one. Text that
|
|
9
|
+
* holds an encoded value is returned decoded with each value replaced. Text without one keeps its
|
|
10
|
+
* original spelling, so an exact quote that holds an escape still matches its evidence. The literal
|
|
11
|
+
* scrub runs first, before decoding can rewrite a value that itself holds an escape, and last,
|
|
12
|
+
* because the encoded scrub leaves text inside marker-shaped spans alone. Outside a scope it
|
|
13
|
+
* changes nothing.
|
|
9
14
|
*/
|
|
10
|
-
export declare function
|
|
15
|
+
export declare function transientCommsKnownValueScrub(): (text: string) => string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { decodeEscapes } from "../evidence/encoded-text.js";
|
|
2
3
|
import { scrubSecretValues } from "../evidence/secret-scrub.js";
|
|
3
4
|
import { escapeRegExp } from "./text.js";
|
|
4
5
|
const scopes = new AsyncLocalStorage();
|
|
@@ -75,10 +76,29 @@ export function scrubTransientCommsText(text) {
|
|
|
75
76
|
* The scope's values as a scrubSecretValues scrub, which also finds them percent-encoded, escaped or
|
|
76
77
|
* base64-encoded and returns decoded text. Outside a scope it changes nothing.
|
|
77
78
|
*/
|
|
78
|
-
|
|
79
|
+
function transientCommsEncodedScrub() {
|
|
79
80
|
const scope = scopes.getStore();
|
|
80
81
|
if (!scope)
|
|
81
82
|
return (text) => text;
|
|
82
83
|
usable(scope);
|
|
83
84
|
return scrubSecretValues([...scope.values]);
|
|
84
85
|
}
|
|
86
|
+
const REDACTED = "[REDACTED_SECRET]";
|
|
87
|
+
const markers = (text) => text.split(REDACTED).length - 1;
|
|
88
|
+
/**
|
|
89
|
+
* A scrub for text a model writes: each scope value as written and in its encoded forms
|
|
90
|
+
* (percent-encoded, JSON-escaped, base64, base64url, hex), and where escapes split one. Text that
|
|
91
|
+
* holds an encoded value is returned decoded with each value replaced. Text without one keeps its
|
|
92
|
+
* original spelling, so an exact quote that holds an escape still matches its evidence. The literal
|
|
93
|
+
* scrub runs first, before decoding can rewrite a value that itself holds an escape, and last,
|
|
94
|
+
* because the encoded scrub leaves text inside marker-shaped spans alone. Outside a scope it
|
|
95
|
+
* changes nothing.
|
|
96
|
+
*/
|
|
97
|
+
export function transientCommsKnownValueScrub() {
|
|
98
|
+
const encoded = transientCommsEncodedScrub();
|
|
99
|
+
return (text) => {
|
|
100
|
+
const literal = scrubTransientCommsText(text);
|
|
101
|
+
const found = encoded(literal);
|
|
102
|
+
return scrubTransientCommsText(markers(found) > markers(decodeEscapes(literal)) ? found : literal);
|
|
103
|
+
};
|
|
104
|
+
}
|
package/dist/run-study.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
// which runs the route's local checks that need no scorer (keys, subject env, the local agent) and
|
|
5
5
|
// returns the route's run. prepareStudy is the same path in two steps, so the CLI can present either
|
|
6
6
|
// refusal before it loads a declared review scorer.
|
|
7
|
+
import { participantList } from "./study/study-fields.js";
|
|
8
|
+
import { personaBackgroundWarnings } from "./study/warnings.js";
|
|
9
|
+
import { resolveCommittedPersonasForCwd, studyPersonaIds } from "./study/persona-resolve.js";
|
|
7
10
|
import { computerUseInput, scriptedInput, sharedWorldInput, terminalInput, } from "./study/route-inputs.js";
|
|
8
11
|
import { planStudy, resolveStudyDryRun, routeOf } from "./study/plan.js";
|
|
9
12
|
import { normalizeRunStudyOptions, optionRefusalOutcome, removedOptionRefusal, } from "./study/run-study-options.js";
|
|
@@ -70,7 +73,9 @@ export async function prepareStudy(config, options, deps = {}) {
|
|
|
70
73
|
outcome.result.warnings.push(...normalized.warnings);
|
|
71
74
|
return { ok: false, outcome };
|
|
72
75
|
}
|
|
73
|
-
const
|
|
76
|
+
const runWarnings = [...(planned.planned.plan.warnings ?? [])];
|
|
77
|
+
const plan = { ...planned.planned.plan, warnings: runWarnings };
|
|
78
|
+
const admitted = await admitPlan(study, planning, plan, deps, normalized.emit);
|
|
74
79
|
if (!admitted.ok) {
|
|
75
80
|
await vm?.close();
|
|
76
81
|
admitted.outcome.result.warnings.push(...normalized.warnings);
|
|
@@ -92,6 +97,17 @@ export async function prepareStudy(config, options, deps = {}) {
|
|
|
92
97
|
return outcome;
|
|
93
98
|
}
|
|
94
99
|
}
|
|
100
|
+
// Admission keeps its refusal order; these warnings join the plan before its run scope opens.
|
|
101
|
+
const personas = route === "computer-use" || route === "shared-world"
|
|
102
|
+
? (await resolveCommittedPersonasForCwd(options.cwd, studyPersonaIds(study))).personas
|
|
103
|
+
: new Map();
|
|
104
|
+
const backgroundWarnings = personaBackgroundWarnings(route === "computer-use" &&
|
|
105
|
+
options.count !== undefined &&
|
|
106
|
+
participantList(study) === undefined
|
|
107
|
+
? { ...study, participants: options.count }
|
|
108
|
+
: study, personas);
|
|
109
|
+
runWarnings.splice(0, runWarnings.length, ...(planned.planned.plan.warnings ?? []), ...backgroundWarnings);
|
|
110
|
+
normalized.warnings.push(...backgroundWarnings);
|
|
95
111
|
// The route layers the scorer over the inputs it admitted, and the local study is not
|
|
96
112
|
// rebuilt for it. The admitted runSession, provider and participant desktop belong to this
|
|
97
113
|
// study, so a rebuilt one would leave the participants they start for no finally to close.
|
package/dist/study/discover.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { personaBackgroundWarnings } from "./warnings.js";
|
|
1
2
|
import { studyPersonaIds, resolveCommittedPersonasForCwd } from "./persona-resolve.js";
|
|
2
3
|
import { personaBrief, PersonaConfigError } from "./persona.js";
|
|
3
4
|
import { constants } from "node:fs";
|
|
@@ -193,7 +194,11 @@ export async function inspectStudyManifest(cwd, study) {
|
|
|
193
194
|
config: resolved.config,
|
|
194
195
|
origin: resolved.origin,
|
|
195
196
|
path: resolved.path,
|
|
196
|
-
warnings: [
|
|
197
|
+
warnings: [
|
|
198
|
+
...resolved.warnings,
|
|
199
|
+
...personaResolution.warnings,
|
|
200
|
+
...personaBackgroundWarnings(resolved.config, personaResolution.personas),
|
|
201
|
+
],
|
|
197
202
|
};
|
|
198
203
|
}
|
|
199
204
|
function parseResolvedStudy(args) {
|
package/dist/study/doctor.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ export declare function studySetupChecks(args: StudySetupCheckArgs): Promise<{
|
|
|
65
65
|
keys: string[];
|
|
66
66
|
reads?: ReadonlySet<string>;
|
|
67
67
|
checks: Check[];
|
|
68
|
+
/** The step to take first, which doctor prints before its rows. */
|
|
69
|
+
next?: string;
|
|
68
70
|
}>;
|
|
69
71
|
/**
|
|
70
72
|
* The ids of the project's labs that need each provider key for a live run, from the same plan
|
package/dist/study/doctor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { isLocalBrowserStudy } from "../substrates/local/runtime-config.js";
|
|
3
3
|
import { localRuntimeStatus } from "../substrates/local/runtime.js";
|
|
4
|
+
import { capacityShortfall, describeCapacity, } from "../substrates/local/capacity.js";
|
|
4
5
|
import { keyNamesOf, requiredKeys, requiredSubjectEnv } from "./requirements.js";
|
|
5
6
|
import { localAgentReady, localAgentVersionRefusal, } from "../actors/local-agent/cli.js";
|
|
6
7
|
import { protocolAdditionsWarning, protocolIncompatibilityMessage, } from "../actors/codex/protocol-compat.js";
|
|
@@ -178,8 +179,11 @@ export async function studySetupChecks(args) {
|
|
|
178
179
|
// One account check, shared by the local participant row and the Codex analysis row.
|
|
179
180
|
let accountReadiness;
|
|
180
181
|
const checkAccount = () => (accountReadiness ??= (args.codexAnalysisReadiness ?? codexAccountReadiness)(args.env).catch(() => ({ ready: false, errorCode: "codex_unavailable" })));
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
const plan = planned.planned.plan;
|
|
183
|
+
const desktopsAtOnce = plan.route === "computer-use" ? plan.concurrency : undefined;
|
|
184
|
+
const localChecks = local ? await localBrowserChecks(config, args, desktopsAtOnce) : undefined;
|
|
185
|
+
if (localChecks !== undefined)
|
|
186
|
+
checks.push(...localChecks.checks);
|
|
183
187
|
if (config.comms?.email?.kind === "real")
|
|
184
188
|
checks.push(await realEmailCheck(config.comms.email.connection, keys, args));
|
|
185
189
|
checks.push(...(await participantChecks(config, route, keys, local, args, checkAccount)));
|
|
@@ -193,10 +197,19 @@ export async function studySetupChecks(args) {
|
|
|
193
197
|
const reads = config.review?.scorer === undefined
|
|
194
198
|
? new Set([...keyNamesOf(planned.planned.plan), ...keys])
|
|
195
199
|
: undefined;
|
|
196
|
-
return {
|
|
200
|
+
return {
|
|
201
|
+
desktop,
|
|
202
|
+
keys,
|
|
203
|
+
...(reads === undefined ? {} : { reads }),
|
|
204
|
+
checks,
|
|
205
|
+
...(localChecks?.next === undefined ? {} : { next: localChecks.next }),
|
|
206
|
+
};
|
|
197
207
|
}
|
|
198
|
-
/**
|
|
199
|
-
|
|
208
|
+
/**
|
|
209
|
+
* A local browser study's runtime, how many of its desktops fit and, with an external catch, its
|
|
210
|
+
* captured inbox. `next` is the runtime's message when it is not ready, which doctor leads with.
|
|
211
|
+
*/
|
|
212
|
+
async function localBrowserChecks(config, args, desktopsAtOnce) {
|
|
200
213
|
const checks = [];
|
|
201
214
|
const runtime = await (args.localRuntimeReadiness ??
|
|
202
215
|
(() => localRuntimeStatus({
|
|
@@ -204,7 +217,14 @@ async function localBrowserChecks(config, args) {
|
|
|
204
217
|
media: config.execution?.desktop?.media !== undefined ||
|
|
205
218
|
config.execution?.desktop?.recording !== undefined,
|
|
206
219
|
})))();
|
|
207
|
-
checks.push({
|
|
220
|
+
checks.push({
|
|
221
|
+
name: "local browser runtime",
|
|
222
|
+
ok: runtime.ok,
|
|
223
|
+
message: runtime.message,
|
|
224
|
+
...(runtime.ok && !runtime.installed ? { status: "note" } : {}),
|
|
225
|
+
});
|
|
226
|
+
if (runtime.capacity !== undefined && desktopsAtOnce !== undefined)
|
|
227
|
+
checks.push(capacityCheck(runtime.capacity, desktopsAtOnce));
|
|
208
228
|
const email = config.comms?.email;
|
|
209
229
|
if (email?.kind === "fake" && email.external) {
|
|
210
230
|
const healthy = await externalCatchHealthy(email.external, { timeoutMs: 5000 });
|
|
@@ -216,7 +236,25 @@ async function localBrowserChecks(config, args) {
|
|
|
216
236
|
: `Captured inbox is unavailable or outdated. Start or upgrade and restart ${cli("comms catch")}, then check comms.email.external.catchBaseUrl (and inboxBaseUrl if set). No participant was allocated.`,
|
|
217
237
|
});
|
|
218
238
|
}
|
|
219
|
-
return
|
|
239
|
+
return {
|
|
240
|
+
checks,
|
|
241
|
+
...(runtime.ok && runtime.installed ? {} : { next: runtime.message }),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/** Whether the desktops the study runs at once fit the VM or this machine, and why. */
|
|
245
|
+
function capacityCheck(capacity, desktopsAtOnce) {
|
|
246
|
+
const shortfall = capacityShortfall(capacity, desktopsAtOnce);
|
|
247
|
+
const name = "local desktop capacity";
|
|
248
|
+
if (shortfall === undefined)
|
|
249
|
+
return {
|
|
250
|
+
name,
|
|
251
|
+
ok: true,
|
|
252
|
+
message: `${describeCapacity(capacity)} This study runs ${desktopsAtOnce} at once, so it fits.`,
|
|
253
|
+
};
|
|
254
|
+
// Linux runs a study that is short of memory with a warning, so the row is advice there.
|
|
255
|
+
return "refusal" in shortfall
|
|
256
|
+
? { name, ok: false, message: shortfall.refusal }
|
|
257
|
+
: { name, ok: true, status: "note", message: shortfall.warning };
|
|
220
258
|
}
|
|
221
259
|
/** The saved real email connection. Adds the key it needs to `keys`. */
|
|
222
260
|
async function realEmailCheck(connection, keys, args) {
|
|
@@ -159,9 +159,11 @@ export const starterFiles = [
|
|
|
159
159
|
id: synthetic-new-user
|
|
160
160
|
name: Synthetic New User
|
|
161
161
|
summary: A privacy-safe first-time user evaluating the app with realistic but synthetic needs.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
background: >-
|
|
163
|
+
Alex keeps personal plans in a notebook and uses email and a few everyday apps without trouble.
|
|
164
|
+
They are trying an unfamiliar app to see whether it can make everyday tasks easier. They work
|
|
165
|
+
through some friction but stop when more effort no longer seems worth it, and they take the
|
|
166
|
+
straightforward path the screen offers.
|
|
165
167
|
constraints:
|
|
166
168
|
- Do not use real personal data.
|
|
167
169
|
- Do not use production accounts.
|
|
@@ -175,9 +177,12 @@ constraints:
|
|
|
175
177
|
id: skeptical-power-user
|
|
176
178
|
name: Skeptical Power User
|
|
177
179
|
summary: A privacy-safe experienced user looking for speed, reversibility, and clear proof.
|
|
180
|
+
background: >-
|
|
181
|
+
Robin manages several volunteer projects with spreadsheets and keyboard shortcuts and is at ease
|
|
182
|
+
with advanced options. They want faster tools but have lost work before and care about undoing
|
|
183
|
+
mistakes. They have little patience for repeated friction and stop when a tool keeps getting in
|
|
184
|
+
the way.
|
|
178
185
|
traits:
|
|
179
|
-
patience: low
|
|
180
|
-
technical_confidence: high
|
|
181
186
|
accessibility_needs: keyboard_first
|
|
182
187
|
constraints:
|
|
183
188
|
- Do not use real personal data.
|
package/dist/study/keys.d.ts
CHANGED
package/dist/study/keys.js
CHANGED
|
@@ -108,7 +108,7 @@ export const EXECUTION = {
|
|
|
108
108
|
timeoutMs: true,
|
|
109
109
|
completionTimeoutMs: true,
|
|
110
110
|
egressAllow: true,
|
|
111
|
-
terminal: { stdin: true, transport: true },
|
|
111
|
+
terminal: { stdin: true, transport: true, doNotTrack: true },
|
|
112
112
|
desktop: {
|
|
113
113
|
template: true,
|
|
114
114
|
browser: true,
|
|
@@ -75,9 +75,15 @@ function parseTerminal(raw) {
|
|
|
75
75
|
return { ok: true, value: undefined };
|
|
76
76
|
}
|
|
77
77
|
if (!isRecord(raw)) {
|
|
78
|
-
return invalid("`execution.terminal` must be an object ({ transport?, stdin? }).");
|
|
78
|
+
return invalid("`execution.terminal` must be an object ({ transport?, stdin?, doNotTrack? }).");
|
|
79
79
|
}
|
|
80
80
|
const terminal = {};
|
|
81
|
+
if (raw.doNotTrack !== undefined) {
|
|
82
|
+
if (typeof raw.doNotTrack !== "boolean") {
|
|
83
|
+
return invalid("`execution.terminal.doNotTrack` must be true or false (unquoted).");
|
|
84
|
+
}
|
|
85
|
+
terminal.doNotTrack = raw.doNotTrack;
|
|
86
|
+
}
|
|
81
87
|
if (raw.transport !== undefined) {
|
|
82
88
|
const transport = str(raw.transport);
|
|
83
89
|
if (transport !== "exec-stream") {
|
|
@@ -217,6 +217,7 @@ export type TerminalPlan = PlanBase & {
|
|
|
217
217
|
readonly personaId?: string;
|
|
218
218
|
readonly mission?: string;
|
|
219
219
|
readonly runtime: {
|
|
220
|
+
readonly doNotTrack: boolean;
|
|
220
221
|
readonly version?: string;
|
|
221
222
|
/** Passed to Codex as `--model` on every run, so the bundle can name and price it. */
|
|
222
223
|
readonly model: string;
|