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
package/dist/observer/library.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { cli } from "../cli/invocation.js";
|
|
2
|
+
// The pip color for each RunDisplay tone; a run with no display gets the neutral gray.
|
|
3
|
+
const DISPLAY_TONES = {
|
|
4
|
+
pass: "#3fb970",
|
|
5
|
+
live: "#4f8ff7",
|
|
6
|
+
fail: "#e5534b",
|
|
7
|
+
warn: "#d29922",
|
|
8
|
+
neutral: "#57606f",
|
|
7
9
|
};
|
|
8
10
|
export function renderLibraryHtml(history, opts) {
|
|
9
11
|
const emptyState = opts.safe
|
|
10
|
-
?
|
|
11
|
-
:
|
|
12
|
+
? `No share_ready runs yet. Run \`${cli("verify")}\` to see why.`
|
|
13
|
+
: `No runs yet. Run \`${cli("run first-run")}\` to create one.`;
|
|
12
14
|
return `<!doctype html>
|
|
13
15
|
<html lang="en" data-theme="dark">
|
|
14
16
|
<head>
|
|
@@ -83,7 +85,7 @@ main { padding: 1rem 1.1rem 3rem; max-width: 44rem; margin: 0 auto; }
|
|
|
83
85
|
}
|
|
84
86
|
// prose-check: script (the library page's JavaScript)
|
|
85
87
|
function libraryClientJs() {
|
|
86
|
-
const tones = JSON.stringify(
|
|
88
|
+
const tones = JSON.stringify(DISPLAY_TONES);
|
|
87
89
|
return `
|
|
88
90
|
(function () {
|
|
89
91
|
var TONES = ${tones};
|
|
@@ -103,9 +105,11 @@ function libraryClientJs() {
|
|
|
103
105
|
link.href = run.href;
|
|
104
106
|
var top = document.createElement("div");
|
|
105
107
|
top.className = "run-top";
|
|
108
|
+
var display = run.display || null;
|
|
109
|
+
var live = display !== null && display.state === "running";
|
|
106
110
|
var pip = document.createElement("span");
|
|
107
|
-
pip.className =
|
|
108
|
-
pip.style.background = TONES[
|
|
111
|
+
pip.className = live ? "pip running" : "pip";
|
|
112
|
+
pip.style.background = (display && TONES[display.tone]) || TONES.neutral;
|
|
109
113
|
var id = document.createElement("span");
|
|
110
114
|
id.className = "run-id";
|
|
111
115
|
id.textContent = run.runId;
|
|
@@ -116,15 +120,15 @@ function libraryClientJs() {
|
|
|
116
120
|
latest.className = "badge latest";
|
|
117
121
|
latest.textContent = "latest";
|
|
118
122
|
top.appendChild(latest);
|
|
119
|
-
} else if (
|
|
120
|
-
var
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
top.appendChild(
|
|
123
|
+
} else if (live) {
|
|
124
|
+
var badge = document.createElement("span");
|
|
125
|
+
badge.className = "badge";
|
|
126
|
+
badge.textContent = "live";
|
|
127
|
+
top.appendChild(badge);
|
|
124
128
|
}
|
|
125
129
|
var meta = document.createElement("div");
|
|
126
130
|
meta.className = "run-meta";
|
|
127
|
-
var metaBits = [run.mode || "unknown", run.streamCount + " participants", run.createdAt || ""];
|
|
131
|
+
var metaBits = [display ? display.label : "", run.mode || "unknown", run.streamCount + " participants", run.createdAt || ""];
|
|
128
132
|
// Labeled cost token: ALWAYS "~$X est." (never a bare "$X"), so the library never implies an
|
|
129
133
|
// authoritative charge. Null = omitted (advisory, fail-open on display).
|
|
130
134
|
if (run.costLabel) {
|
package/dist/observer/render.js
CHANGED
|
@@ -11,7 +11,8 @@ import { projectShareCheckedAnalysis } from "../analysis/sharing.js";
|
|
|
11
11
|
import { loadAnalysis } from "../analysis/load.js";
|
|
12
12
|
import { physicalCwdOf, runIdOf, bindExistingRunArtifactPaths, isSafeRunIdSegment, resolveLatestRunDirectory, validatePreparedRunArtifactPaths, } from "../run/paths.js";
|
|
13
13
|
import { writeContainedOutputFile } from "../run/contained-output.js";
|
|
14
|
-
import { loadRunBundlePrepared } from "../run/locate.js";
|
|
14
|
+
import { loadRunBundlePrepared, readRunJsonIfExists } from "../run/locate.js";
|
|
15
|
+
import { RUN_STATUS_FILE } from "../run/status.js";
|
|
15
16
|
import { verifyRunPrepared } from "../verify/verify.js";
|
|
16
17
|
import { renderObserverHtml } from "./artifact.js";
|
|
17
18
|
import { buildObserverData } from "./data.js";
|
|
@@ -19,6 +20,7 @@ import { buildServeSecurityHeaders, hostAllowed, parsePublicOrigin } from "./htt
|
|
|
19
20
|
import { listenOnLoopback } from "./listen.js";
|
|
20
21
|
import { pinDirectChildDirectory, pinDirectory, readContainedFile, } from "./pinned-files.js";
|
|
21
22
|
import { buildHistoryIndex, matchRunRoute, serveRunPath, writeResponse, } from "./run-routes.js";
|
|
23
|
+
import { cli } from "../cli/invocation.js";
|
|
22
24
|
const OBSERVER_SCHEMA = "humanish.observer-result.v1";
|
|
23
25
|
const observerRuntimeStreamUrls = new WeakMap();
|
|
24
26
|
const observerPreparedRunPaths = new WeakMap();
|
|
@@ -99,7 +101,12 @@ export async function renderObserver(cwdInput, runInput, options = {}) {
|
|
|
99
101
|
...projectShareCheckedAnalysis(await loadAnalysis(preparedRunPaths)),
|
|
100
102
|
...(spend === null ? {} : { spend }),
|
|
101
103
|
};
|
|
102
|
-
|
|
104
|
+
// A run.json without an outcome is in progress, interrupted, or older than the field: its
|
|
105
|
+
// status.json says which, and holds an older run's ok.
|
|
106
|
+
const statusRecord = loaded.bundle.outcome === undefined
|
|
107
|
+
? await readRunJsonIfExists(preparedRunPaths, RUN_STATUS_FILE)
|
|
108
|
+
: undefined;
|
|
109
|
+
const observerData = buildObserverData(loaded.bundle, undefined, statusRecord);
|
|
103
110
|
observerData.publicSafety.share = {
|
|
104
111
|
status: verified.shareSafety.status,
|
|
105
112
|
verifiedAt: new Date().toISOString(),
|
|
@@ -130,7 +137,7 @@ export async function renderObserver(cwdInput, runInput, options = {}) {
|
|
|
130
137
|
loaded.bundle.mode === "live"
|
|
131
138
|
? "Observer renders verified local evidence artifacts; runtime stream auth URLs are not persisted."
|
|
132
139
|
: "This is a dry run: its participants are synthetic and no product behavior was tested.",
|
|
133
|
-
|
|
140
|
+
`Before filing public feedback, use \`${cli("feedback issue")}\` so redaction and public-safety checks gate the payload.`,
|
|
134
141
|
...(openResult.warning ? [openResult.warning] : []),
|
|
135
142
|
],
|
|
136
143
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { RunDisplay } from "../run/display.js";
|
|
2
3
|
import { type ObserverData } from "./data.js";
|
|
3
4
|
import { type PinnedDirectory } from "./pinned-files.js";
|
|
4
5
|
export interface ObserverRuntimeStreamUrl {
|
|
@@ -22,6 +23,7 @@ export declare function buildHistoryIndex(proofRoot: PinnedDirectory, admitRun?:
|
|
|
22
23
|
mode: string | null;
|
|
23
24
|
href: string;
|
|
24
25
|
status: string;
|
|
26
|
+
display?: RunDisplay;
|
|
25
27
|
runtimeState?: NonNullable<ObserverData["runtime"]>["state"];
|
|
26
28
|
streamCount: number;
|
|
27
29
|
estimatedCostUsd: number | null;
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { isAnalysisRecordPath, projectShareCheckedAnalysis } from "../analysis/sharing.js";
|
|
6
6
|
import { loadAnalysis } from "../analysis/load.js";
|
|
7
|
+
import { isLocalOnlyRunFile, isLocalOnlyRunFileIdentity } from "../run/local-only-files.js";
|
|
7
8
|
import { listRuns } from "../run/stored-runs.js";
|
|
8
9
|
import { readAnalysisAccounting } from "../run/costs.js";
|
|
9
10
|
import { bindExistingRunArtifactPaths, isPathInside, isSafeRunIdSegment, } from "../run/paths.js";
|
|
10
11
|
import { analysisCostOf, runCost, runCostLabel } from "../run/run-cost.js";
|
|
11
|
-
import { RUN_STATUS_FILE, RUN_STATUS_STALE_MS, isRunStatusRecord } from "../run/status.js";
|
|
12
|
+
import { RUN_STATUS_FILE, RUN_STATUS_STALE_MS, isRunStatusRecord, } from "../run/status.js";
|
|
12
13
|
import { renderObserverHtml } from "./artifact.js";
|
|
13
14
|
import { buildObserverData, recordedStreamEmbed, withObserverEndings, } from "./data.js";
|
|
14
15
|
import { buildArtifactSecurityHeaders } from "./http.js";
|
|
@@ -70,9 +71,11 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
|
|
|
70
71
|
// file serving bypass its current-content checks or a warmed source-only admission cache.
|
|
71
72
|
const derivedRoot = cleanedRelativePath.split("/")[0];
|
|
72
73
|
const derivedLeaf = path.posix.basename(cleanedRelativePath);
|
|
74
|
+
// Local-only files (raw sandbox ids, the recording pid) are read in process, never served.
|
|
73
75
|
if (derivedRoot === ".analysis-lock" ||
|
|
74
76
|
derivedLeaf.startsWith(".humanish-write-") ||
|
|
75
|
-
isAnalysisRecordPath(cleanedRelativePath)
|
|
77
|
+
isAnalysisRecordPath(cleanedRelativePath) ||
|
|
78
|
+
isLocalOnlyRunFile(cleanedRelativePath)) {
|
|
76
79
|
writeResponse(response, 404, "Not found", "text/plain; charset=utf-8");
|
|
77
80
|
return;
|
|
78
81
|
}
|
|
@@ -103,6 +106,10 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
|
|
|
103
106
|
await serveContainedMedia(runRoot, filePath, response, request);
|
|
104
107
|
return;
|
|
105
108
|
}
|
|
109
|
+
if (await isLocalOnlyRunFileIdentity(root, filePath)) {
|
|
110
|
+
writeResponse(response, 404, "Not found", "text/plain; charset=utf-8");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
106
113
|
try {
|
|
107
114
|
const body = await readContainedFile(runRoot, filePath);
|
|
108
115
|
if (!body) {
|
|
@@ -120,6 +127,7 @@ export async function serveRunPath(runRoot, relativePath, response, runtimeStrea
|
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
129
|
async function readObserverData(runRoot, runtimeStreamUrls = []) {
|
|
130
|
+
const record = await readLocalRunStatus(runRoot);
|
|
123
131
|
// Best-effort load from either source. Both reads swallow all errors on
|
|
124
132
|
// purpose: this runs on every browser poll of a live run, where run.json may
|
|
125
133
|
// be absent, still being written (a partial-JSON parse error), or superseded
|
|
@@ -130,57 +138,64 @@ async function readObserverData(runRoot, runtimeStreamUrls = []) {
|
|
|
130
138
|
if (!bundleBytes)
|
|
131
139
|
throw new Error("run.json unavailable");
|
|
132
140
|
const bundle = JSON.parse(bundleBytes.toString("utf8"));
|
|
133
|
-
return withRuntimeStreamUrls(
|
|
141
|
+
return withRuntimeStreamUrls(withLocalRunStatus(record, buildObserverData(bundle, undefined, record)), runtimeStreamUrls);
|
|
134
142
|
}
|
|
135
143
|
catch { }
|
|
136
144
|
try {
|
|
137
145
|
const observerBytes = await readContainedFile(runRoot, path.join(runRoot.physicalPath, "observer", "observer-data.json"));
|
|
138
146
|
if (!observerBytes)
|
|
139
147
|
throw new Error("observer-data.json unavailable");
|
|
140
|
-
return withRuntimeStreamUrls(
|
|
148
|
+
return withRuntimeStreamUrls(withLocalRunStatus(record, withObserverEndings(JSON.parse(observerBytes.toString("utf8")))), runtimeStreamUrls);
|
|
141
149
|
}
|
|
142
150
|
catch { }
|
|
143
151
|
return null;
|
|
144
152
|
}
|
|
145
|
-
/**
|
|
146
|
-
|
|
147
|
-
* A stale heartbeat means unknown: neither an old timestamp nor a persisted PID proves that a
|
|
148
|
-
* process died (the evidence may have been copied from another machine). No PID is served/probed.
|
|
149
|
-
*/
|
|
150
|
-
async function withLocalRunStatus(runRoot, input) {
|
|
151
|
-
// A served observation must never be inherited from a persisted projection or export.
|
|
152
|
-
const { runtime: _persistedRuntime, ...data } = input;
|
|
153
|
+
/** The run directory's own status record, when it is well formed and names that directory. */
|
|
154
|
+
async function readLocalRunStatus(runRoot) {
|
|
153
155
|
try {
|
|
154
156
|
const bytes = await readContainedFile(runRoot, path.join(runRoot.physicalPath, RUN_STATUS_FILE));
|
|
155
157
|
if (!bytes)
|
|
156
|
-
return
|
|
158
|
+
return undefined;
|
|
157
159
|
const record = JSON.parse(bytes.toString("utf8"));
|
|
158
|
-
|
|
159
|
-
record
|
|
160
|
-
|
|
161
|
-
record.runId !== path.basename(runRoot.physicalPath))
|
|
162
|
-
return data;
|
|
163
|
-
const now = Date.now();
|
|
164
|
-
const started = Date.parse(record.startedAt);
|
|
165
|
-
const updated = Date.parse(record.updatedAt);
|
|
166
|
-
const timestampsValid = Number.isFinite(started) && Number.isFinite(updated) && started <= updated && updated <= now;
|
|
167
|
-
let state = "unknown";
|
|
168
|
-
if (timestampsValid) {
|
|
169
|
-
if (record.state === "finished") {
|
|
170
|
-
state = "finished";
|
|
171
|
-
}
|
|
172
|
-
else if (record.state === "running" && now - updated <= RUN_STATUS_STALE_MS) {
|
|
173
|
-
state = "running";
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
...data,
|
|
178
|
-
runtime: { state, observedAt: new Date(now).toISOString(), source: "local-run-status" },
|
|
179
|
-
};
|
|
160
|
+
return isRunStatusRecord(record) && record.runId === path.basename(runRoot.physicalPath)
|
|
161
|
+
? record
|
|
162
|
+
: undefined;
|
|
180
163
|
}
|
|
181
164
|
catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Liveness is a current read of a contained local status record, separate from run evidence.
|
|
170
|
+
* A stale heartbeat means unknown: neither an old timestamp nor a persisted PID proves that a
|
|
171
|
+
* process died (the evidence may have been copied from another machine). A record its stopped
|
|
172
|
+
* process wrote is interrupted. No PID is served/probed.
|
|
173
|
+
*/
|
|
174
|
+
function withLocalRunStatus(record, input) {
|
|
175
|
+
// A served observation must never be inherited from a persisted projection or export.
|
|
176
|
+
const { runtime: _persistedRuntime, ...data } = input;
|
|
177
|
+
if (record === undefined || record.runId !== data.run.runId || record.mode !== data.run.mode)
|
|
182
178
|
return data;
|
|
179
|
+
const now = Date.now();
|
|
180
|
+
const started = Date.parse(record.startedAt);
|
|
181
|
+
const updated = Date.parse(record.updatedAt);
|
|
182
|
+
const timestampsValid = Number.isFinite(started) && Number.isFinite(updated) && started <= updated && updated <= now;
|
|
183
|
+
let state = "unknown";
|
|
184
|
+
if (timestampsValid) {
|
|
185
|
+
if (record.state === "finished") {
|
|
186
|
+
state = "finished";
|
|
187
|
+
}
|
|
188
|
+
else if (record.state === "interrupted") {
|
|
189
|
+
state = "interrupted";
|
|
190
|
+
}
|
|
191
|
+
else if (record.state === "running" && now - updated <= RUN_STATUS_STALE_MS) {
|
|
192
|
+
state = "running";
|
|
193
|
+
}
|
|
183
194
|
}
|
|
195
|
+
return {
|
|
196
|
+
...data,
|
|
197
|
+
runtime: { state, observedAt: new Date(now).toISOString(), source: "local-run-status" },
|
|
198
|
+
};
|
|
184
199
|
}
|
|
185
200
|
/** internal: exported for the stream lifecycle tests. */
|
|
186
201
|
export function withRuntimeStreamUrls(data, runtimeStreamUrls) {
|
|
@@ -249,6 +264,7 @@ admitRun) {
|
|
|
249
264
|
mode: admitRun ? (data?.run.mode ?? null) : run.mode,
|
|
250
265
|
href: `/_humanish/runs/${encodeURIComponent(run.runId)}/observer/index.html`,
|
|
251
266
|
status: data?.run.status ?? "unknown",
|
|
267
|
+
...(data?.run.display === undefined ? {} : { display: data.run.display }),
|
|
252
268
|
...(data?.runtime ? { runtimeState: data.runtime.state } : {}),
|
|
253
269
|
streamCount: data?.streams.length ?? 0,
|
|
254
270
|
// Labeled run-total cost estimate (advisory; null when the run carries no cost summary).
|
package/dist/observer/serve.js
CHANGED
|
@@ -10,6 +10,7 @@ import { buildServeSecurityHeaders, hostAllowed, parsePublicOrigin, } from "./ht
|
|
|
10
10
|
import { isSafeRunIdSegment } from "../run/paths.js";
|
|
11
11
|
import { listRuns } from "../run/stored-runs.js";
|
|
12
12
|
import { verifyRun } from "../verify/verify.js";
|
|
13
|
+
import { cli } from "../cli/invocation.js";
|
|
13
14
|
export const SERVE_SCHEMA = "humanish.serve-result.v1";
|
|
14
15
|
export function createShareSafetyAdmission(cwd, options = {}) {
|
|
15
16
|
const verifyImpl = options.verifyImpl ?? verifyRun;
|
|
@@ -196,7 +197,7 @@ export async function serveObserverLibrary(cwdInput, options) {
|
|
|
196
197
|
ok: false,
|
|
197
198
|
error: {
|
|
198
199
|
code: "HUMANISH_RUN_NOT_FOUND",
|
|
199
|
-
message:
|
|
200
|
+
message: `No run library found under .humanish/runs. Run \`${cli("run first-run")}\` to create the first run.`,
|
|
200
201
|
},
|
|
201
202
|
};
|
|
202
203
|
}
|