humanish 0.110.0 → 0.110.2
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 +15 -12
- package/CHANGELOG.md +26 -0
- package/CONTEXT.md +8 -6
- package/README.md +103 -53
- package/dist/actors/computer-use/loop/ending.js +4 -4
- package/dist/actors/local-agent/claude-participant.d.ts +74 -0
- package/dist/actors/local-agent/claude-participant.js +243 -0
- package/dist/actors/local-agent/claude-session.d.ts +16 -3
- package/dist/actors/local-agent/claude-session.js +64 -23
- package/dist/actors/local-agent/cli.d.ts +17 -0
- package/dist/actors/local-agent/cli.js +111 -24
- package/dist/actors/local-agent/readiness.d.ts +2 -2
- package/dist/actors/local-agent/readiness.js +20 -12
- package/dist/cli/commands/analyze.js +21 -5
- package/dist/cli/commands/observe.js +15 -2
- package/dist/cli/commands/run-signals.d.ts +2 -0
- package/dist/cli/commands/run-signals.js +106 -45
- package/dist/cli/commands/runs.d.ts +5 -0
- package/dist/cli/commands/runs.js +113 -74
- package/dist/cli/commands/setup.js +23 -14
- package/dist/cli/commands/study-route-computer-use.js +9 -3
- package/dist/cli/commands/study-route-open.js +1 -0
- package/dist/cli/commands/study-route-preview.js +1 -0
- package/dist/cli/commands/study-route-scripted.js +2 -0
- package/dist/cli/commands/study-route-shared-world.js +6 -2
- package/dist/cli/commands/study-route-terminal.js +2 -0
- package/dist/cli/commands/watch.js +14 -4
- package/dist/cli/doctor.d.ts +4 -1
- package/dist/cli/doctor.js +51 -10
- package/dist/cli/findings.d.ts +103 -0
- package/dist/cli/findings.js +317 -0
- package/dist/cli/first-run-path.d.ts +32 -11
- package/dist/cli/first-run-path.js +113 -69
- package/dist/cli/invocation.d.ts +7 -0
- package/dist/cli/invocation.js +32 -0
- package/dist/cli/io.d.ts +3 -1
- package/dist/cli/io.js +8 -15
- package/dist/cli/observer-follow.d.ts +20 -0
- package/dist/cli/observer-follow.js +36 -1
- package/dist/cli/orientation.js +16 -14
- package/dist/cli/product-sentence.d.ts +3 -3
- package/dist/cli/product-sentence.js +3 -3
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +11 -0
- package/dist/evidence/encoded-text.d.ts +9 -6
- package/dist/evidence/encoded-text.js +84 -19
- package/dist/evidence/redaction.d.ts +2 -0
- package/dist/evidence/redaction.js +123 -21
- package/dist/feedback/export-bundle.js +2 -2
- package/dist/keys/env-file.d.ts +4 -0
- package/dist/keys/env-file.js +17 -0
- package/dist/keys/key-resolution.js +6 -5
- package/dist/observer/data.d.ts +14 -1
- package/dist/observer/data.js +19 -9
- package/dist/observer/exposure.js +1 -1
- package/dist/observer/library.d.ts +4 -0
- package/dist/observer/library.js +21 -17
- package/dist/observer/render.js +10 -3
- package/dist/observer/run-routes.d.ts +2 -0
- package/dist/observer/run-routes.js +51 -35
- package/dist/observer/serve.js +2 -1
- package/dist/observer-app.html +3 -3
- package/dist/routes/computer-use/bundle.js +3 -1
- package/dist/routes/computer-use/fanout-records.js +1 -1
- package/dist/routes/computer-use/participant-execution.js +3 -1
- package/dist/routes/computer-use/participant-model.js +5 -2
- package/dist/routes/computer-use/plan.js +9 -4
- package/dist/routes/computer-use/preflight.js +1 -1
- package/dist/routes/computer-use/result.js +17 -28
- package/dist/routes/computer-use/single-bundle.js +7 -1
- package/dist/routes/scripted/bundle.js +4 -2
- package/dist/routes/scripted/plan.js +2 -2
- package/dist/routes/scripted/result.d.ts +3 -5
- package/dist/routes/scripted/result.js +16 -22
- package/dist/routes/scripted/scenario.js +4 -4
- package/dist/routes/shared-world/bundle-records.js +6 -1
- package/dist/routes/shared-world/bundle.js +2 -0
- package/dist/routes/shared-world/plan.js +6 -2
- package/dist/routes/shared-world/result.js +22 -22
- package/dist/routes/terminal/bundle.js +6 -3
- package/dist/routes/terminal/credentials.d.ts +5 -5
- package/dist/routes/terminal/credentials.js +14 -6
- package/dist/routes/terminal/dry-run.js +8 -12
- package/dist/routes/terminal/ledger.d.ts +3 -3
- package/dist/routes/terminal/ledger.js +6 -6
- package/dist/routes/terminal/live-finish.js +15 -13
- package/dist/routes/terminal/plan.js +12 -4
- package/dist/routes/terminal/result.d.ts +6 -7
- package/dist/routes/terminal/result.js +6 -22
- package/dist/routes/terminal/route.js +5 -4
- package/dist/routes/terminal/runtime-auth.d.ts +6 -0
- package/dist/routes/terminal/runtime-auth.js +5 -0
- package/dist/routes/terminal/session.js +1 -1
- package/dist/run/bundle-shape.d.ts +3 -1
- package/dist/run/bundle-shape.js +25 -1
- package/dist/run/bundle.d.ts +24 -0
- package/dist/run/display.d.ts +59 -0
- package/dist/run/display.js +106 -0
- package/dist/run/dry-run.js +13 -8
- package/dist/run/judge.d.ts +3 -2
- package/dist/run/judge.js +12 -1
- package/dist/run/local-only-files.d.ts +13 -0
- package/dist/run/local-only-files.js +35 -0
- package/dist/run/pricing.js +1 -1
- package/dist/run/projection.d.ts +0 -2
- package/dist/run/projection.js +0 -11
- package/dist/run/reclaim-outcomes.d.ts +89 -0
- package/dist/run/reclaim-outcomes.js +213 -0
- package/dist/run/reclaim.d.ts +23 -17
- package/dist/run/reclaim.js +213 -114
- package/dist/run/results.d.ts +10 -20
- package/dist/run/results.js +2 -2
- package/dist/run/run-index.d.ts +11 -1
- package/dist/run/run-index.js +11 -0
- package/dist/run/run-not-found.js +3 -2
- package/dist/run/run.d.ts +34 -18
- package/dist/run/run.js +166 -67
- package/dist/run/sandbox-creates.d.ts +59 -0
- package/dist/run/sandbox-creates.js +140 -0
- package/dist/run/sandbox-receipts.d.ts +20 -5
- package/dist/run/sandbox-receipts.js +56 -7
- package/dist/run/stats.d.ts +5 -0
- package/dist/run/stats.js +8 -4
- package/dist/run/status.d.ts +6 -9
- package/dist/run/status.js +7 -23
- package/dist/run/stored-runs.d.ts +5 -14
- package/dist/run/stored-runs.js +31 -146
- package/dist/run/streams.d.ts +3 -2
- package/dist/study/agents-section.d.ts +21 -0
- package/dist/study/agents-section.js +86 -0
- package/dist/study/doctor.d.ts +1 -1
- package/dist/study/doctor.js +8 -4
- package/dist/study/init-templates.js +0 -95
- package/dist/study/init.js +45 -37
- package/dist/study/keys.d.ts +12 -0
- package/dist/study/keys.js +32 -0
- package/dist/study/local-browser-host.d.ts +11 -0
- package/dist/study/local-browser-host.js +42 -0
- package/dist/study/parse/actors.js +9 -1
- package/dist/study/parse/subject.js +12 -0
- package/dist/study/parse/url-credentials.d.ts +9 -0
- package/dist/study/parse/url-credentials.js +50 -0
- package/dist/study/persona.d.ts +4 -0
- package/dist/study/persona.js +4 -2
- package/dist/study/preflight-probes.js +2 -1
- package/dist/study/types.d.ts +8 -6
- package/dist/study/url-credentials.d.ts +6 -0
- package/dist/study/url-credentials.js +34 -0
- package/dist/substrates/e2b/peer-install.d.ts +51 -0
- package/dist/substrates/e2b/peer-install.js +161 -0
- package/dist/substrates/e2b/sandbox.d.ts +34 -6
- package/dist/substrates/e2b/sandbox.js +141 -30
- package/dist/substrates/e2b/sdk.d.ts +33 -0
- package/dist/substrates/e2b/sdk.js +21 -21
- package/dist/substrates/local/firecracker-desktop.js +3 -2
- package/dist/substrates/local/runtime-host.js +5 -4
- package/dist/substrates/local/runtime.js +5 -4
- package/dist/substrates/shell.d.ts +2 -0
- package/dist/substrates/shell.js +4 -0
- package/dist/tui/contract.js +5 -4
- package/dist/tui-app.js +133 -133
- package/dist/verify/liveness.d.ts +19 -3
- package/dist/verify/liveness.js +47 -25
- package/dist/verify/verify.d.ts +3 -0
- package/dist/verify/verify.js +8 -3
- package/docs/architecture/actor-contract.md +2 -1
- package/docs/architecture/observer.md +22 -7
- package/docs/architecture/task-protocol-support.md +13 -13
- package/docs/architecture/terminal-product-route.md +15 -13
- package/docs/contracts/run-bundle.md +95 -10
- package/docs/contracts/schemas.md +44 -26
- package/docs/contracts/study-analysis.md +45 -0
- package/docs/contracts/study-costs.md +8 -0
- package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +3 -0
- package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +9 -3
- package/docs/principles/invariants-and-defaults.md +11 -8
- package/package.json +6 -3
- package/skills/humanish/SKILL.md +38 -20
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
// Every created sandbox id is journaled to the run dir
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Every created sandbox id is journaled to the run dir as soon as it reaches this process: when the
|
|
2
|
+
// desktop startup guard reports it, or when create returns, before any work happens in it. The
|
|
3
|
+
// participant loop lives in the caller's local process, so a sleeping laptop, crash or kill loses
|
|
4
|
+
// every in-memory id; this append-only file keeps them for `humanish reclaim`, and tells verify and
|
|
5
|
+
// the sweep which ids to look for.
|
|
4
6
|
import { appendFile } from "node:fs/promises";
|
|
5
7
|
import { prepareContainedOutputFile } from "./contained-output.js";
|
|
6
8
|
export const SANDBOX_RECEIPTS_ARTIFACT = "sandbox-receipts.ndjson";
|
|
@@ -22,10 +24,11 @@ export function appendedSandboxIds(root) {
|
|
|
22
24
|
return [...(appended.get(rootKey(root)) ?? [])];
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
|
-
* Append one receipt. Best-effort by design: the receipt exists to
|
|
26
|
-
* receipt write must never fail the participant. The only cost of a
|
|
27
|
-
* this id and the TTL backstop covers it instead. Containment is
|
|
28
|
-
* artifact write uses; append (not atomic-replace) keeps racing
|
|
27
|
+
* Append one receipt; false when the write failed. Best-effort by design: the receipt exists to
|
|
28
|
+
* protect the run, so a failed receipt write must never fail the participant. The only cost of a
|
|
29
|
+
* miss is that `reclaim` cannot see this id and the TTL backstop covers it instead. Containment is
|
|
30
|
+
* the same prepare step every artifact write uses; append (not atomic-replace) keeps racing
|
|
31
|
+
* participants' receipts intact.
|
|
29
32
|
*/
|
|
30
33
|
export async function appendSandboxReceipt(root, receipt) {
|
|
31
34
|
const key = rootKey(root);
|
|
@@ -40,11 +43,57 @@ export async function appendSandboxReceipt(root, receipt) {
|
|
|
40
43
|
try {
|
|
41
44
|
const filePath = await prepareContainedOutputFile(root, SANDBOX_RECEIPTS_ARTIFACT);
|
|
42
45
|
await appendFile(filePath, `${JSON.stringify(receipt)}\n`, "utf8");
|
|
46
|
+
return true;
|
|
43
47
|
}
|
|
44
48
|
catch {
|
|
45
49
|
// Swallowed on purpose; see the contract above.
|
|
50
|
+
return false;
|
|
46
51
|
}
|
|
47
52
|
}
|
|
53
|
+
/** Append one owner line; false when the write failed, which never fails the create. */
|
|
54
|
+
export async function appendSandboxOwner(root, owner) {
|
|
55
|
+
try {
|
|
56
|
+
const line = { at: new Date().toISOString(), provider: "e2b", owner };
|
|
57
|
+
const filePath = await prepareContainedOutputFile(root, SANDBOX_RECEIPTS_ARTIFACT);
|
|
58
|
+
// The leading newline ends a line an earlier append left torn, so this record parses.
|
|
59
|
+
await appendFile(filePath, `\n${JSON.stringify(line)}\n`, "utf8");
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Never fails the create. Without the line, reclaim reports the run's sandboxes as unknown,
|
|
64
|
+
// never clean, so the next create tries again.
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const RUN_KEY = /^[0-9a-f]{16}$/;
|
|
69
|
+
/**
|
|
70
|
+
* The owner tag sets a journal's owner lines record for the run named `runId`. Only a complete
|
|
71
|
+
* tuple counts: provider e2b, `tool: humanish`, this run's id and a well-formed key, and nothing
|
|
72
|
+
* else. A partial or foreign line would widen reclaim's search past this run, so it is skipped.
|
|
73
|
+
*/
|
|
74
|
+
export function parseSandboxOwners(text, runId) {
|
|
75
|
+
const owners = new Map();
|
|
76
|
+
for (const line of text.split("\n")) {
|
|
77
|
+
const trimmed = line.trim();
|
|
78
|
+
if (!trimmed)
|
|
79
|
+
continue;
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(trimmed);
|
|
82
|
+
const owner = parsed.owner;
|
|
83
|
+
if (parsed.provider !== "e2b" || owner === null || typeof owner !== "object")
|
|
84
|
+
continue;
|
|
85
|
+
if (Object.keys(owner).length !== 3 || owner.tool !== "humanish" || owner.runId !== runId)
|
|
86
|
+
continue;
|
|
87
|
+
if (typeof owner.runKey !== "string" || !RUN_KEY.test(owner.runKey))
|
|
88
|
+
continue;
|
|
89
|
+
owners.set(owner.runKey, { tool: "humanish", runId, runKey: owner.runKey });
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// A torn line: skip it.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return [...owners.values()];
|
|
96
|
+
}
|
|
48
97
|
/** Parse a receipts file leniently: a torn final line (crash mid-append) drops, valid lines keep. */
|
|
49
98
|
export function parseSandboxReceipts(text) {
|
|
50
99
|
const receipts = [];
|
package/dist/run/stats.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RunDisplayState } from "./display.js";
|
|
1
2
|
import { type CostTotals, type CostRow } from "./costs.js";
|
|
2
3
|
declare const STATS_SCHEMA = "humanish.stats.v2";
|
|
3
4
|
interface StatsParticipants {
|
|
@@ -13,6 +14,7 @@ interface StatsStudyRow {
|
|
|
13
14
|
dryRun: number;
|
|
14
15
|
/** Runs whose bundle carries a verdict; the denominator for passRate. */
|
|
15
16
|
judged: number;
|
|
17
|
+
/** Runs that show as passed (runDisplay in src/run/display.ts): a pass verdict and an ok run. */
|
|
16
18
|
passed: number;
|
|
17
19
|
/** passed / judged, absent when judged is 0. */
|
|
18
20
|
passRate?: number;
|
|
@@ -52,7 +54,10 @@ export interface StatsResult {
|
|
|
52
54
|
estimatedSpendUsd: number;
|
|
53
55
|
unpricedRuns: number;
|
|
54
56
|
participants: StatsParticipants;
|
|
57
|
+
/** The participants' verdicts. `outcomes` says how many runs passed. */
|
|
55
58
|
verdicts: Record<string, number>;
|
|
59
|
+
/** Runs by the state every surface shows them in (runDisplay in src/run/display.ts). */
|
|
60
|
+
outcomes: Partial<Record<RunDisplayState, number>>;
|
|
56
61
|
costs: CostTotals;
|
|
57
62
|
};
|
|
58
63
|
studies: StatsStudyRow[];
|
package/dist/run/stats.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// numbers under the per-run rules: an estimate is labelled an estimate, a run whose cost is unknown
|
|
3
3
|
// counts as unknown and never as zero, and every rate has a denominator next to it.
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { runDisplay, runDisplayLabel } from "./display.js";
|
|
5
6
|
import { readRunIndex } from "./run-index.js";
|
|
6
7
|
import { addCostTotals, emptyCostTotals, readCostTotals, } from "./costs.js";
|
|
7
8
|
import { round6 } from "./pricing.js";
|
|
@@ -68,7 +69,7 @@ function addToStudyRow(studies, entry, runUsd, costs) {
|
|
|
68
69
|
row.dryRun += 1;
|
|
69
70
|
if (entry.verdict !== undefined) {
|
|
70
71
|
row.judged += 1;
|
|
71
|
-
if (entry.
|
|
72
|
+
if (runDisplay(entry).state === "passed")
|
|
72
73
|
row.passed += 1;
|
|
73
74
|
}
|
|
74
75
|
if (entry.mode === "live" && entry.durationMs !== undefined)
|
|
@@ -157,6 +158,7 @@ export async function computeStats(cwdInput, options = {}) {
|
|
|
157
158
|
unpricedRuns: 0,
|
|
158
159
|
participants: emptyParticipants(),
|
|
159
160
|
verdicts: {},
|
|
161
|
+
outcomes: {},
|
|
160
162
|
costs: emptyCostTotals(),
|
|
161
163
|
};
|
|
162
164
|
const studies = new Map();
|
|
@@ -181,6 +183,8 @@ export async function computeStats(cwdInput, options = {}) {
|
|
|
181
183
|
addParticipants(totals.participants, entry);
|
|
182
184
|
if (entry.verdict !== undefined)
|
|
183
185
|
totals.verdicts[entry.verdict] = (totals.verdicts[entry.verdict] ?? 0) + 1;
|
|
186
|
+
const { state } = runDisplay(entry);
|
|
187
|
+
totals.outcomes[state] = (totals.outcomes[state] ?? 0) + 1;
|
|
184
188
|
addToStudyRow(studies, entry, runUsd, accounting.costs);
|
|
185
189
|
addToDayRow(days, entry, runUsd, accounting.costs);
|
|
186
190
|
}
|
|
@@ -239,7 +243,7 @@ function runCountLines(t) {
|
|
|
239
243
|
const running = t.running > 0 ? ` (${t.running} still running)` : "";
|
|
240
244
|
return [`live runs: ${t.live}${running}`, ...(t.dryRun > 0 ? [`dry runs: ${t.dryRun}`] : [])];
|
|
241
245
|
}
|
|
242
|
-
/** Spend, analysis, participants and
|
|
246
|
+
/** Spend, analysis, participants and outcomes across the selected live runs. */
|
|
243
247
|
function totalsLines(t) {
|
|
244
248
|
const c = t.costs;
|
|
245
249
|
const incomplete = c.incompleteRunEstimates > 0
|
|
@@ -248,7 +252,7 @@ function totalsLines(t) {
|
|
|
248
252
|
const unpriced = c.analysisUnpricedAttempts > 0 || c.analysisUnresolvedAttempts > 0
|
|
249
253
|
? `; ${c.analysisUnpricedAttempts} unpriced, ${c.analysisUnresolvedAttempts} unresolved`
|
|
250
254
|
: "";
|
|
251
|
-
const
|
|
255
|
+
const outcomes = Object.entries(t.outcomes).map(([state, count]) => `${runDisplayLabel(state)} ${count}`);
|
|
252
256
|
return [
|
|
253
257
|
`known estimated spend: ${knownMoney(c.estimatedTotalUsd)}`,
|
|
254
258
|
`participants and desktops: ${knownMoney(c.runEstimatedUsd)}${incomplete}`,
|
|
@@ -265,7 +269,7 @@ function totalsLines(t) {
|
|
|
265
269
|
`participants: ${t.participants.reachedGoal} of ${t.participants.total} reached the goal, ${t.participants.reportedFriction} reported friction`,
|
|
266
270
|
]
|
|
267
271
|
: []),
|
|
268
|
-
...(
|
|
272
|
+
...(outcomes.length > 0 ? [`outcomes: ${outcomes.join(", ")}`] : []),
|
|
269
273
|
];
|
|
270
274
|
}
|
|
271
275
|
function dayLine(row) {
|
package/dist/run/status.d.ts
CHANGED
|
@@ -20,9 +20,9 @@ export type RunInterruptSignal = "SIGINT" | "SIGTERM" | "SIGHUP";
|
|
|
20
20
|
interface RunStatusOutcome {
|
|
21
21
|
/** `review.verdict` verbatim. */
|
|
22
22
|
verdict?: string;
|
|
23
|
-
/**
|
|
23
|
+
/** run.json's `outcome.ok`: the route result's ok. */
|
|
24
24
|
ok?: boolean;
|
|
25
|
-
/**
|
|
25
|
+
/** run.json's `outcome.execution`: whether the run worked as an execution, apart from its verdict. */
|
|
26
26
|
execution?: ExecutionOutcome;
|
|
27
27
|
/** `review.participants` counts, when the run recorded any. */
|
|
28
28
|
participants?: {
|
|
@@ -72,14 +72,11 @@ export interface RunStatusHandle {
|
|
|
72
72
|
/** Finalize: state `finished`, `completedAt`, and the derived outcome. Stops the cadence.
|
|
73
73
|
* Idempotent: a second call is a no-op, so a route with several exit paths is safe. */
|
|
74
74
|
finish(outcome?: RunStatusOutcome): Promise<void>;
|
|
75
|
-
/** After finish,
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
ok: boolean;
|
|
79
|
-
execution: ExecutionOutcome;
|
|
80
|
-
}): Promise<void>;
|
|
75
|
+
/** After finish, replace the finished record's outcome with one read from a rewritten run.json,
|
|
76
|
+
* so the two agree. Before finish, and after an interrupt, it does nothing. */
|
|
77
|
+
restate(outcome: RunStatusOutcome): Promise<void>;
|
|
81
78
|
/** The process is being stopped by `signal`: state `interrupted`, `completedAt` and the signal.
|
|
82
|
-
* It stops the cadence and makes a later finish or
|
|
79
|
+
* It stops the cadence and makes a later finish or restate a no-op, so the route cannot write
|
|
83
80
|
* `running` or `finished` over it. When the run had finished it writes nothing, waits for the
|
|
84
81
|
* writes already queued and resolves false. */
|
|
85
82
|
interrupt(signal: RunInterruptSignal): Promise<boolean>;
|
package/dist/run/status.js
CHANGED
|
@@ -4,15 +4,14 @@
|
|
|
4
4
|
//
|
|
5
5
|
// run.json remains the evidence of record. This file is a derived index and liveness record:
|
|
6
6
|
// `verify` never gates on it, nothing here is a claim about what a participant did, and when the
|
|
7
|
-
// two disagree run.json wins
|
|
7
|
+
// two disagree run.json wins. A finished record copies run.json's outcome, so it can be rebuilt
|
|
8
|
+
// from it; a record written before run.json carried an outcome holds the only copy of the run's ok.
|
|
8
9
|
//
|
|
9
10
|
// Public safety: it holds only the run id, the study id/path/origin (the strings `humanish study list`
|
|
10
11
|
// prints), the mode, a local pid and timestamps. It holds no hostname or user/path identity: an
|
|
11
12
|
// operator may share the run directory, so a share-safety gate must have nothing to strip here.
|
|
12
13
|
import { isProvenanceField, studyFields } from "./study-provenance.js";
|
|
13
14
|
import { writeContainedOutputFile } from "./contained-output.js";
|
|
14
|
-
import { redactText } from "../evidence/redaction.js";
|
|
15
|
-
import { readRunSandboxIds, scrubSandboxIds } from "./sandbox-ids.js";
|
|
16
15
|
export const RUN_STATUS_SCHEMA = "humanish.run-status.v1";
|
|
17
16
|
/** The file, relative to the run directory. */
|
|
18
17
|
export const RUN_STATUS_FILE = "status.json";
|
|
@@ -46,6 +45,9 @@ export function runStatusOutcome(bundle) {
|
|
|
46
45
|
...(typeof bundle.cost?.fullyEstimated === "boolean"
|
|
47
46
|
? { estimatedCostComplete: bundle.cost.fullyEstimated }
|
|
48
47
|
: {}),
|
|
48
|
+
...(bundle.outcome?.state === "finished"
|
|
49
|
+
? { ok: bundle.outcome.ok, execution: bundle.outcome.execution }
|
|
50
|
+
: {}),
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
@@ -103,28 +105,10 @@ export function beginRunStatus(runPaths, options) {
|
|
|
103
105
|
};
|
|
104
106
|
await write(finishedRecord);
|
|
105
107
|
},
|
|
106
|
-
async
|
|
108
|
+
async restate(outcome) {
|
|
107
109
|
if (finishedRecord === undefined)
|
|
108
110
|
return;
|
|
109
|
-
|
|
110
|
-
// again even though the routes scrubbed it. A route settles with its own result, so an SDK
|
|
111
|
-
// error here can still name a sandbox; the receipts give the ids to replace.
|
|
112
|
-
const ids = await readRunSandboxIds(runPaths);
|
|
113
|
-
const redacted = (failure) => ({
|
|
114
|
-
kind: failure.kind,
|
|
115
|
-
message: scrubSandboxIds(redactText(failure.message), ids),
|
|
116
|
-
});
|
|
117
|
-
const execution = {
|
|
118
|
-
succeeded: result.execution.succeeded,
|
|
119
|
-
failures: result.execution.failures.map(redacted),
|
|
120
|
-
...(result.execution.warnings === undefined
|
|
121
|
-
? {}
|
|
122
|
-
: { warnings: result.execution.warnings.map(redacted) }),
|
|
123
|
-
};
|
|
124
|
-
finishedRecord = {
|
|
125
|
-
...finishedRecord,
|
|
126
|
-
outcome: { ...finishedRecord.outcome, ok: result.ok, execution },
|
|
127
|
-
};
|
|
111
|
+
finishedRecord = { ...finishedRecord, outcome };
|
|
128
112
|
await write(finishedRecord);
|
|
129
113
|
},
|
|
130
114
|
async interrupt(signal) {
|
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { type
|
|
3
|
-
import { type CleanupAdapterResult, type CleanupResult } from "./results.js";
|
|
1
|
+
import { type ReviewSummary } from "./bundle.js";
|
|
2
|
+
import { type RunDisplay } from "./display.js";
|
|
4
3
|
import { type VerifyResult } from "../verify/verify.js";
|
|
5
4
|
declare const RUNS_SCHEMA = "humanish.runs-result.v1";
|
|
6
|
-
export interface RunCleanupHooks {
|
|
7
|
-
/** @deprecated Ignored. Stored provider ids are not authority to load or mutate a provider. */
|
|
8
|
-
loadDesktopModule?: () => Promise<E2BDesktopModule>;
|
|
9
|
-
cleanupAdapterResources?: (ctx: {
|
|
10
|
-
cwd: string;
|
|
11
|
-
runDir: string;
|
|
12
|
-
bundle: RunBundle;
|
|
13
|
-
}) => Promise<CleanupAdapterResult[]>;
|
|
14
|
-
now?: () => Date;
|
|
15
|
-
}
|
|
16
5
|
export interface RunsResult {
|
|
17
6
|
schema: typeof RUNS_SCHEMA;
|
|
18
7
|
ok: boolean;
|
|
@@ -22,6 +11,8 @@ export interface RunsResult {
|
|
|
22
11
|
createdAt: string | null;
|
|
23
12
|
mode: string | null;
|
|
24
13
|
path: string;
|
|
14
|
+
/** How the run reads (runDisplay in src/run/display.ts); absent when it has no readable run.json. */
|
|
15
|
+
display?: RunDisplay;
|
|
25
16
|
}>;
|
|
26
17
|
latest: string | null;
|
|
27
18
|
error?: {
|
|
@@ -29,10 +20,10 @@ export interface RunsResult {
|
|
|
29
20
|
message: string;
|
|
30
21
|
};
|
|
31
22
|
}
|
|
32
|
-
export declare function cleanupRun(cwdInput: string, runInput: string, hooks?: RunCleanupHooks): Promise<CleanupResult>;
|
|
33
23
|
export declare function listRuns(cwdInput: string): Promise<RunsResult>;
|
|
34
24
|
export declare function readReview(cwdInput: string, runInput: string): Promise<VerifyResult | (ReviewSummary & {
|
|
35
25
|
path: string;
|
|
36
26
|
runId: string;
|
|
27
|
+
display?: RunDisplay;
|
|
37
28
|
})>;
|
|
38
29
|
export {};
|
package/dist/run/stored-runs.js
CHANGED
|
@@ -1,157 +1,17 @@
|
|
|
1
|
-
import { REDACTED_SANDBOX_ID, sandboxIdDigest } from "../evidence/redaction.js";
|
|
2
1
|
import { lstat, readdir } from "node:fs/promises";
|
|
3
|
-
import { runNotFoundMessage } from "./run-not-found.js";
|
|
4
2
|
import path from "node:path";
|
|
5
3
|
import { redactText } from "../evidence/redaction.js";
|
|
6
|
-
import { bindExistingRunArtifactPaths, RUNS_RELATIVE_ROOT, isSafeRunIdSegment, resolveRunsRoot,
|
|
7
|
-
import { assertPreparedSelectedOutputDirectory, bindExistingManagedHumanishOutputDirectory,
|
|
8
|
-
import { RUN_BUNDLE_FILE
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
4
|
+
import { bindExistingRunArtifactPaths, RUNS_RELATIVE_ROOT, isSafeRunIdSegment, resolveRunsRoot, } from "./paths.js";
|
|
5
|
+
import { assertPreparedSelectedOutputDirectory, bindExistingManagedHumanishOutputDirectory, } from "./contained-output.js";
|
|
6
|
+
import { RUN_BUNDLE_FILE } from "./bundle.js";
|
|
7
|
+
import { isReviewSummary, isRunOutcome } from "./bundle-shape.js";
|
|
8
|
+
import { bundleDisplayFacts, runDisplay } from "./display.js";
|
|
9
|
+
import { RUN_STATUS_FILE } from "./status.js";
|
|
11
10
|
import { readLatest, readRunJsonIfExists, resolveRunPath } from "./locate.js";
|
|
12
11
|
import { withCuaReviewProvenance } from "./outcomes.js";
|
|
13
12
|
import { isNodeError, isRecord } from "./type-guards.js";
|
|
14
13
|
import { invalidRunStorageVerifyResult, verifyResolvedRun, } from "../verify/verify.js";
|
|
15
14
|
const RUNS_SCHEMA = "humanish.runs-result.v1";
|
|
16
|
-
/** A cleanup result that refused before inspecting any resource. */
|
|
17
|
-
function cleanupRefusal(cwd, runInput, checkedAt, error, bundlePath) {
|
|
18
|
-
return {
|
|
19
|
-
schema: CLEANUP_SCHEMA,
|
|
20
|
-
ok: false,
|
|
21
|
-
cwd,
|
|
22
|
-
run: runInput,
|
|
23
|
-
...(bundlePath === undefined ? {} : { bundlePath }),
|
|
24
|
-
checkedAt,
|
|
25
|
-
summary: { resources: 0, killed: 0, alreadyClean: 0, failed: 0, skipped: 0 },
|
|
26
|
-
resources: [],
|
|
27
|
-
adapterResults: [],
|
|
28
|
-
warnings: [],
|
|
29
|
-
error,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/** What the recorded evidence says about one provider resource; nothing is killed from here. */
|
|
33
|
-
function recordedResourceResult(resource) {
|
|
34
|
-
// Named by digest: a run recorded before 0.110 holds the raw id here, which is digested.
|
|
35
|
-
const raw = resource.id === REDACTED_SANDBOX_ID ? undefined : resource.id;
|
|
36
|
-
const idDigest = resource.idDigest ?? (raw === undefined ? undefined : sandboxIdDigest(raw));
|
|
37
|
-
const base = {
|
|
38
|
-
provider: resource.provider,
|
|
39
|
-
kind: resource.kind,
|
|
40
|
-
id: REDACTED_SANDBOX_ID,
|
|
41
|
-
...(idDigest === undefined ? {} : { idDigest }),
|
|
42
|
-
};
|
|
43
|
-
if (resource.provider !== "e2b-desktop" || resource.kind !== "sandbox") {
|
|
44
|
-
return {
|
|
45
|
-
...base,
|
|
46
|
-
status: "skipped",
|
|
47
|
-
message: "cleanup only supports e2b-desktop sandbox resources",
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
if (resource.status === "killed" || resource.cleanup?.killed === true) {
|
|
51
|
-
return {
|
|
52
|
-
...base,
|
|
53
|
-
status: "already_clean",
|
|
54
|
-
message: "resource was already recorded as killed",
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
...base,
|
|
59
|
-
status: "failed",
|
|
60
|
-
message: "automatic provider cleanup requires a verified resource lease",
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
export async function cleanupRun(cwdInput, runInput, hooks = {}) {
|
|
64
|
-
const cwd = path.resolve(cwdInput);
|
|
65
|
-
const checkedAt = (hooks.now ?? (() => new Date()))().toISOString();
|
|
66
|
-
let resolved;
|
|
67
|
-
try {
|
|
68
|
-
resolved = await resolveRunPath(cwd, runInput);
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return cleanupRefusal(cwd, runInput, checkedAt, {
|
|
72
|
-
code: "HUMANISH_INVALID_RUN_BUNDLE",
|
|
73
|
-
message: "Run storage failed containment validation.",
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
if (!resolved) {
|
|
77
|
-
return cleanupRefusal(cwd, runInput, checkedAt, {
|
|
78
|
-
code: "HUMANISH_RUN_NOT_FOUND",
|
|
79
|
-
message: await runNotFoundMessage(cwd, runInput),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
const runPaths = resolved;
|
|
83
|
-
const bundlePath = path.join(runPaths.absoluteRunRoot, RUN_BUNDLE_FILE);
|
|
84
|
-
const cleanupPath = path.join(runPaths.absoluteRunRoot, "cleanup.json");
|
|
85
|
-
await prepareContainedOutputFile(runPaths, "cleanup.json");
|
|
86
|
-
const bundleBytes = await readContainedRegularFile(runPaths, RUN_BUNDLE_FILE);
|
|
87
|
-
let bundle = null;
|
|
88
|
-
if (bundleBytes) {
|
|
89
|
-
try {
|
|
90
|
-
bundle = JSON.parse(bundleBytes.toString("utf8"));
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
bundle = null;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (!isRunBundle(bundle)) {
|
|
97
|
-
return cleanupRefusal(cwd, runInput, checkedAt, {
|
|
98
|
-
code: "HUMANISH_INVALID_RUN_BUNDLE",
|
|
99
|
-
message: "Run bundle failed cleanup shape validation.",
|
|
100
|
-
}, path.relative(cwd, bundlePath));
|
|
101
|
-
}
|
|
102
|
-
const warnings = [];
|
|
103
|
-
const providerResources = bundle.providerResources ?? [];
|
|
104
|
-
const resources = providerResources.map(recordedResourceResult);
|
|
105
|
-
let adapterResults = [];
|
|
106
|
-
if (hooks.cleanupAdapterResources) {
|
|
107
|
-
try {
|
|
108
|
-
adapterResults = await hooks.cleanupAdapterResources({
|
|
109
|
-
cwd,
|
|
110
|
-
runDir: runPaths.physicalRunRoot,
|
|
111
|
-
bundle,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
adapterResults = [
|
|
116
|
-
{
|
|
117
|
-
id: "adapter-cleanup",
|
|
118
|
-
ok: false,
|
|
119
|
-
message: error instanceof Error ? error.message : String(error),
|
|
120
|
-
},
|
|
121
|
-
];
|
|
122
|
-
}
|
|
123
|
-
await validatePreparedRunArtifactPaths(runPaths);
|
|
124
|
-
}
|
|
125
|
-
if (providerResources.length === 0 && adapterResults.length === 0) {
|
|
126
|
-
warnings.push("Run bundle recorded no provider resource evidence; nothing to inspect.");
|
|
127
|
-
}
|
|
128
|
-
const summary = {
|
|
129
|
-
resources: resources.length,
|
|
130
|
-
killed: 0,
|
|
131
|
-
alreadyClean: resources.filter((resource) => resource.status === "already_clean").length,
|
|
132
|
-
failed: resources.filter((resource) => resource.status === "failed").length +
|
|
133
|
-
adapterResults.filter((result) => !result.ok).length,
|
|
134
|
-
skipped: resources.filter((resource) => resource.status === "skipped").length,
|
|
135
|
-
};
|
|
136
|
-
const ok = summary.failed === 0;
|
|
137
|
-
const result = {
|
|
138
|
-
schema: CLEANUP_SCHEMA,
|
|
139
|
-
ok,
|
|
140
|
-
cwd: PUBLIC_TARGET_CWD,
|
|
141
|
-
run: runInput,
|
|
142
|
-
runId: bundle.runId,
|
|
143
|
-
bundlePath: path.relative(cwd, bundlePath),
|
|
144
|
-
cleanupPath: path.relative(cwd, cleanupPath),
|
|
145
|
-
checkedAt,
|
|
146
|
-
summary,
|
|
147
|
-
resources,
|
|
148
|
-
adapterResults,
|
|
149
|
-
warnings,
|
|
150
|
-
};
|
|
151
|
-
await validatePreparedRunArtifactPaths(runPaths);
|
|
152
|
-
await writeContainedOutputFile(runPaths, "cleanup.json", `${JSON.stringify(result, null, 2)}\n`, "utf8");
|
|
153
|
-
return result;
|
|
154
|
-
}
|
|
155
15
|
export async function listRuns(cwdInput) {
|
|
156
16
|
const cwd = path.resolve(cwdInput);
|
|
157
17
|
const runsRootPath = resolveRunsRoot(cwd);
|
|
@@ -216,6 +76,7 @@ export async function listRuns(cwdInput) {
|
|
|
216
76
|
createdAt: isRecord(bundle) && typeof bundle.createdAt === "string" ? bundle.createdAt : null,
|
|
217
77
|
mode: isRecord(bundle) && typeof bundle.mode === "string" ? bundle.mode : null,
|
|
218
78
|
path: path.join(RUNS_RELATIVE_ROOT, entryName),
|
|
79
|
+
...(await listedRunDisplay(entryRunPaths, bundle)),
|
|
219
80
|
});
|
|
220
81
|
}
|
|
221
82
|
if (runsRoot) {
|
|
@@ -234,6 +95,29 @@ export async function listRuns(cwdInput) {
|
|
|
234
95
|
latest: latest?.runId ?? null,
|
|
235
96
|
};
|
|
236
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* A listed run's display, from its run.json and, for a bundle without an outcome, its status
|
|
100
|
+
* record. Like the listing's other fields it reads the file without verifying it; a run.json
|
|
101
|
+
* that is not an object naming a run gets none.
|
|
102
|
+
*/
|
|
103
|
+
async function listedRunDisplay(runPaths, bundle) {
|
|
104
|
+
if (!isRecord(bundle) || typeof bundle.runId !== "string")
|
|
105
|
+
return {};
|
|
106
|
+
const outcome = isRunOutcome(bundle.outcome) ? bundle.outcome : undefined;
|
|
107
|
+
const status = outcome === undefined ? await readRunJsonIfExists(runPaths, RUN_STATUS_FILE) : undefined;
|
|
108
|
+
const facts = bundleDisplayFacts({
|
|
109
|
+
runId: bundle.runId,
|
|
110
|
+
...(typeof bundle.mode === "string" ? { mode: bundle.mode } : {}),
|
|
111
|
+
...(isRecord(bundle.review) && typeof bundle.review.verdict === "string"
|
|
112
|
+
? { review: { verdict: bundle.review.verdict } }
|
|
113
|
+
: {}),
|
|
114
|
+
...(Array.isArray(bundle.simulations)
|
|
115
|
+
? { simulations: bundle.simulations.filter(isRecord) }
|
|
116
|
+
: {}),
|
|
117
|
+
...(outcome === undefined ? {} : { outcome }),
|
|
118
|
+
}, status);
|
|
119
|
+
return { display: runDisplay(facts) };
|
|
120
|
+
}
|
|
237
121
|
function runsUnavailableResult(cwd, error) {
|
|
238
122
|
return {
|
|
239
123
|
schema: RUNS_SCHEMA,
|
|
@@ -279,5 +163,6 @@ export async function readReview(cwdInput, runInput) {
|
|
|
279
163
|
...projected,
|
|
280
164
|
path: path.relative(cwd, path.join(runPaths.absoluteRunRoot, "review.json")),
|
|
281
165
|
runId: path.basename(runPaths.absoluteRunRoot),
|
|
166
|
+
...(runPaths ? await listedRunDisplay(runPaths, bundle) : {}),
|
|
282
167
|
};
|
|
283
168
|
}
|
package/dist/run/streams.d.ts
CHANGED
|
@@ -171,8 +171,9 @@ export interface RunStream {
|
|
|
171
171
|
/**
|
|
172
172
|
* The judge's status for this participant (`judgedStatus` in src/run/judge.ts). It differs from
|
|
173
173
|
* the trace status when a goal_satisfied session reported a blocker or never engaged. Computer-use
|
|
174
|
-
*
|
|
175
|
-
* and
|
|
174
|
+
* and shared-world runs write it for a participant whose session finished; rerun selection and
|
|
175
|
+
* the Observer read it. Absent on the terminal and scripted routes, whose judge has no blocker
|
|
176
|
+
* rule, and in bundles written before it existed.
|
|
176
177
|
*/
|
|
177
178
|
judgedStatus?: ActorStatus;
|
|
178
179
|
transport: "snapshot" | "polling" | "sse" | "pty" | "app-server";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type AgentsSectionReplacement =
|
|
2
|
+
/** The file has no humanish section. */
|
|
3
|
+
{
|
|
4
|
+
kind: "absent";
|
|
5
|
+
}
|
|
6
|
+
/** A start marker with no end marker, in text no release wrote: possibly hand-edited. */
|
|
7
|
+
| {
|
|
8
|
+
kind: "unknown";
|
|
9
|
+
}
|
|
10
|
+
/** The file with humanish's section replaced; equal to the input when it was already current. */
|
|
11
|
+
| {
|
|
12
|
+
kind: "replaced";
|
|
13
|
+
contents: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* `existing` with humanish's section replaced by `section` (as `agentsSection()` returns it, with
|
|
17
|
+
* its leading blank line). The replaced range runs from the start of the heading line that holds
|
|
18
|
+
* the start marker to the end of the end marker's line, or, without an end marker, through the
|
|
19
|
+
* text of the release that wrote it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function replaceAgentsSection(existing: string, section: string): AgentsSectionReplacement;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Finds humanish's own section in a project's `AGENTS.md` so init can replace it on a rerun and
|
|
2
|
+
// leave every other byte alone. Releases up to 0.110.1 wrote the section with a start marker and
|
|
3
|
+
// no end marker, so for those the section's extent is known only by matching a text init wrote.
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { AGENTS_SECTION_END_MARKER, AGENTS_SECTION_MARKER } from "../cli/first-run-path.js";
|
|
6
|
+
/**
|
|
7
|
+
* Each section text a release wrote before the end marker existed, from its `## humanish` heading
|
|
8
|
+
* to its final newline: the UTF-16 length and the sha256 of its UTF-8 bytes. Comparing digests
|
|
9
|
+
* keeps the retired command and key names those versions taught out of the source.
|
|
10
|
+
*/
|
|
11
|
+
const SECTIONS_WITHOUT_END_MARKER = [
|
|
12
|
+
{
|
|
13
|
+
releases: "0.59.0 to 0.80.0",
|
|
14
|
+
length: 1171,
|
|
15
|
+
sha256: "3739c98204cc5b2df603238398bffc5c4a491782a3ab72475b0ad9d967698a11",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
releases: "0.81.0 to 0.93.1",
|
|
19
|
+
length: 1191,
|
|
20
|
+
sha256: "169d646f329f35af1b5e51eb02cd0454a717cf29a447a230f3aa2d5a7354729c",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
releases: "0.94.0 to 0.99.1",
|
|
24
|
+
length: 1214,
|
|
25
|
+
sha256: "68f987769893f041ab78e0b1cb4cd8db14a438511f15b58f9eaa412ea002b51f",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
releases: "0.100.0 to 0.105.0",
|
|
29
|
+
length: 1548,
|
|
30
|
+
sha256: "f60c37003d21b1d8280d002f203130c60bc3c23454b018b3207407faa01cb76f",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
releases: "0.106.0 to 0.107.0",
|
|
34
|
+
length: 1686,
|
|
35
|
+
sha256: "c2cd9d831e17021ad4477f300dbea13df1613bec8f30ebbabfabcd749b73f7f7",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
releases: "0.108.0",
|
|
39
|
+
length: 1679,
|
|
40
|
+
sha256: "6352ec71d7c5073b2bf4993293753944eaf7cfd21169a311f42d9f52bdacfead",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
releases: "0.109.0 to 0.110.0",
|
|
44
|
+
length: 1681,
|
|
45
|
+
sha256: "2559a93463cf50225aadf6753e84dd6e32b25aa3afdf8066e90d19c5d88d0ef0",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
releases: "0.110.1",
|
|
49
|
+
length: 2014,
|
|
50
|
+
sha256: "9fb4cb94400c7447ab0fece861339e50de8e9be99797cf0763d7502be0fe668d",
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* `existing` with humanish's section replaced by `section` (as `agentsSection()` returns it, with
|
|
55
|
+
* its leading blank line). The replaced range runs from the start of the heading line that holds
|
|
56
|
+
* the start marker to the end of the end marker's line, or, without an end marker, through the
|
|
57
|
+
* text of the release that wrote it.
|
|
58
|
+
*/
|
|
59
|
+
export function replaceAgentsSection(existing, section) {
|
|
60
|
+
const marker = existing.indexOf(AGENTS_SECTION_MARKER);
|
|
61
|
+
if (marker < 0)
|
|
62
|
+
return { kind: "absent" };
|
|
63
|
+
const start = existing.lastIndexOf("\n", marker) + 1;
|
|
64
|
+
const replacement = section.replace(/^\n/, "");
|
|
65
|
+
const endMarker = existing.indexOf(AGENTS_SECTION_END_MARKER, marker);
|
|
66
|
+
if (endMarker >= 0) {
|
|
67
|
+
const lineEnd = existing.indexOf("\n", endMarker);
|
|
68
|
+
const end = lineEnd < 0 ? existing.length : lineEnd + 1;
|
|
69
|
+
return {
|
|
70
|
+
kind: "replaced",
|
|
71
|
+
contents: existing.slice(0, start) + replacement + existing.slice(end),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
for (const known of SECTIONS_WITHOUT_END_MARKER) {
|
|
75
|
+
const candidate = existing.slice(start, start + known.length);
|
|
76
|
+
if (candidate.length !== known.length)
|
|
77
|
+
continue;
|
|
78
|
+
if (createHash("sha256").update(candidate).digest("hex") !== known.sha256)
|
|
79
|
+
continue;
|
|
80
|
+
return {
|
|
81
|
+
kind: "replaced",
|
|
82
|
+
contents: existing.slice(0, start) + replacement + existing.slice(start + known.length),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return { kind: "unknown" };
|
|
86
|
+
}
|
package/dist/study/doctor.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type LocalRuntimeStatus } from "../substrates/local/runtime.js";
|
|
2
2
|
import type { StudyConfig } from "./types.js";
|
|
3
3
|
import type { PlanResult } from "./plan-types.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type DetectedLocalAgent } from "../actors/local-agent/cli.js";
|
|
5
5
|
import type { ReasoningEffort } from "../actors/reasoning-effort.js";
|
|
6
6
|
import type { DoctorCheckDraft } from "../cli/doctor.js";
|
|
7
7
|
import { type CodexInstallation } from "../actors/codex/codex-admission.js";
|