simfile 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +348 -0
- package/dist/coverage/index.d.ts +2 -0
- package/dist/coverage/index.d.ts.map +1 -0
- package/dist/coverage/index.js +1 -0
- package/dist/coverage/matrix.d.ts +21 -0
- package/dist/coverage/matrix.d.ts.map +1 -0
- package/dist/coverage/matrix.js +173 -0
- package/dist/coverage/render.d.ts +3 -0
- package/dist/coverage/render.d.ts.map +1 -0
- package/dist/coverage/render.js +26 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/kernel/duration.d.ts +9 -0
- package/dist/kernel/duration.d.ts.map +1 -0
- package/dist/kernel/duration.js +44 -0
- package/dist/kernel/index.d.ts +4 -0
- package/dist/kernel/index.d.ts.map +1 -0
- package/dist/kernel/index.js +3 -0
- package/dist/kernel/range.d.ts +6 -0
- package/dist/kernel/range.d.ts.map +1 -0
- package/dist/kernel/range.js +16 -0
- package/dist/kernel/stochastic.d.ts +10 -0
- package/dist/kernel/stochastic.d.ts.map +1 -0
- package/dist/kernel/stochastic.js +25 -0
- package/dist/ledger/index.d.ts +4 -0
- package/dist/ledger/index.d.ts.map +1 -0
- package/dist/ledger/index.js +3 -0
- package/dist/ledger/markers.d.ts +46 -0
- package/dist/ledger/markers.d.ts.map +1 -0
- package/dist/ledger/markers.js +108 -0
- package/dist/ledger/stable.d.ts +65 -0
- package/dist/ledger/stable.d.ts.map +1 -0
- package/dist/ledger/stable.js +78 -0
- package/dist/ledger/validation.d.ts +16 -0
- package/dist/ledger/validation.d.ts.map +1 -0
- package/dist/ledger/validation.js +127 -0
- package/dist/moltnet/index.d.ts +3 -0
- package/dist/moltnet/index.d.ts.map +1 -0
- package/dist/moltnet/index.js +2 -0
- package/dist/moltnet/transcript-export.d.ts +34 -0
- package/dist/moltnet/transcript-export.d.ts.map +1 -0
- package/dist/moltnet/transcript-export.js +68 -0
- package/dist/moltnet/world-participant.d.ts +77 -0
- package/dist/moltnet/world-participant.d.ts.map +1 -0
- package/dist/moltnet/world-participant.js +221 -0
- package/dist/observe/artifacts.d.ts +16 -0
- package/dist/observe/artifacts.d.ts.map +1 -0
- package/dist/observe/artifacts.js +31 -0
- package/dist/observe/causalStreams.d.ts +19 -0
- package/dist/observe/causalStreams.d.ts.map +1 -0
- package/dist/observe/causalStreams.js +38 -0
- package/dist/observe/compute.d.ts +17 -0
- package/dist/observe/compute.d.ts.map +1 -0
- package/dist/observe/compute.js +97 -0
- package/dist/observe/index.d.ts +11 -0
- package/dist/observe/index.d.ts.map +1 -0
- package/dist/observe/index.js +10 -0
- package/dist/observe/manifest.d.ts +79 -0
- package/dist/observe/manifest.d.ts.map +1 -0
- package/dist/observe/manifest.js +87 -0
- package/dist/observe/memoryBanks.d.ts +38 -0
- package/dist/observe/memoryBanks.d.ts.map +1 -0
- package/dist/observe/memoryBanks.js +86 -0
- package/dist/observe/observe.d.ts +32 -0
- package/dist/observe/observe.d.ts.map +1 -0
- package/dist/observe/observe.js +73 -0
- package/dist/observe/report.d.ts +165 -0
- package/dist/observe/report.d.ts.map +1 -0
- package/dist/observe/report.js +138 -0
- package/dist/observe/seedSpread.d.ts +49 -0
- package/dist/observe/seedSpread.d.ts.map +1 -0
- package/dist/observe/seedSpread.js +262 -0
- package/dist/observe/seedSpreadArtifacts.d.ts +49 -0
- package/dist/observe/seedSpreadArtifacts.d.ts.map +1 -0
- package/dist/observe/seedSpreadArtifacts.js +118 -0
- package/dist/observe/spreadMatcher.d.ts +45 -0
- package/dist/observe/spreadMatcher.d.ts.map +1 -0
- package/dist/observe/spreadMatcher.js +124 -0
- package/dist/report/index.d.ts +4 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +3 -0
- package/dist/report/probeArtifact.d.ts +26 -0
- package/dist/report/probeArtifact.d.ts.map +1 -0
- package/dist/report/probeArtifact.js +119 -0
- package/dist/report/probes.d.ts +52 -0
- package/dist/report/probes.d.ts.map +1 -0
- package/dist/report/probes.js +164 -0
- package/dist/report/score.d.ts +27 -0
- package/dist/report/score.d.ts.map +1 -0
- package/dist/report/score.js +14 -0
- package/dist/report/transcripts.d.ts +12 -0
- package/dist/report/transcripts.d.ts.map +1 -0
- package/dist/report/transcripts.js +17 -0
- package/dist/runtime/causal-fixture.d.ts +26 -0
- package/dist/runtime/causal-fixture.d.ts.map +1 -0
- package/dist/runtime/causal-fixture.js +36 -0
- package/dist/runtime/clock.d.ts +25 -0
- package/dist/runtime/clock.d.ts.map +1 -0
- package/dist/runtime/clock.js +63 -0
- package/dist/runtime/condition.d.ts +55 -0
- package/dist/runtime/condition.d.ts.map +1 -0
- package/dist/runtime/condition.js +122 -0
- package/dist/runtime/emit-causal-fixture.d.ts +2 -0
- package/dist/runtime/emit-causal-fixture.d.ts.map +1 -0
- package/dist/runtime/emit-causal-fixture.js +45 -0
- package/dist/runtime/expression.d.ts +11 -0
- package/dist/runtime/expression.d.ts.map +1 -0
- package/dist/runtime/expression.js +305 -0
- package/dist/runtime/numeric.d.ts +7 -0
- package/dist/runtime/numeric.d.ts.map +1 -0
- package/dist/runtime/numeric.js +11 -0
- package/dist/runtime/rule-actions.d.ts +5 -0
- package/dist/runtime/rule-actions.d.ts.map +1 -0
- package/dist/runtime/rule-actions.js +82 -0
- package/dist/runtime/run-record.d.ts +73 -0
- package/dist/runtime/run-record.d.ts.map +1 -0
- package/dist/runtime/run-record.js +179 -0
- package/dist/runtime/step-tick.d.ts +43 -0
- package/dist/runtime/step-tick.d.ts.map +1 -0
- package/dist/runtime/step-tick.js +177 -0
- package/dist/runtime/trace-compile.d.ts +66 -0
- package/dist/runtime/trace-compile.d.ts.map +1 -0
- package/dist/runtime/trace-compile.js +157 -0
- package/dist/runtime/trace-export.d.ts +5 -0
- package/dist/runtime/trace-export.d.ts.map +1 -0
- package/dist/runtime/trace-export.js +4 -0
- package/dist/runtime/trace-run.d.ts +15 -0
- package/dist/runtime/trace-run.d.ts.map +1 -0
- package/dist/runtime/trace-run.js +92 -0
- package/dist/runtime/trace.d.ts +10 -0
- package/dist/runtime/trace.d.ts.map +1 -0
- package/dist/runtime/trace.js +9 -0
- package/dist/runtime/types.d.ts +61 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/viewer-trace.d.ts +64 -0
- package/dist/runtime/viewer-trace.d.ts.map +1 -0
- package/dist/runtime/viewer-trace.js +164 -0
- package/dist/runtime/world-act.d.ts +55 -0
- package/dist/runtime/world-act.d.ts.map +1 -0
- package/dist/runtime/world-act.js +156 -0
- package/dist/schema/binding.d.ts +23 -0
- package/dist/schema/binding.d.ts.map +1 -0
- package/dist/schema/binding.js +249 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/model.d.ts +244 -0
- package/dist/schema/model.d.ts.map +1 -0
- package/dist/schema/model.js +222 -0
- package/dist/schema/parse.d.ts +10 -0
- package/dist/schema/parse.d.ts.map +1 -0
- package/dist/schema/parse.js +15 -0
- package/dist/schema/semantic.d.ts +3 -0
- package/dist/schema/semantic.d.ts.map +1 -0
- package/dist/schema/semantic.js +135 -0
- package/dist/sims/composeRunManifest.d.ts +54 -0
- package/dist/sims/composeRunManifest.d.ts.map +1 -0
- package/dist/sims/composeRunManifest.js +40 -0
- package/dist/sims/composedJungianSimDriver.d.ts +50 -0
- package/dist/sims/composedJungianSimDriver.d.ts.map +1 -0
- package/dist/sims/composedJungianSimDriver.js +199 -0
- package/dist/sims/composedOfficeSimDriver.d.ts +53 -0
- package/dist/sims/composedOfficeSimDriver.d.ts.map +1 -0
- package/dist/sims/composedOfficeSimDriver.js +162 -0
- package/dist/sims/exchangeWait.d.ts +56 -0
- package/dist/sims/exchangeWait.d.ts.map +1 -0
- package/dist/sims/exchangeWait.js +66 -0
- package/dist/sims/index.d.ts +14 -0
- package/dist/sims/index.d.ts.map +1 -0
- package/dist/sims/index.js +13 -0
- package/dist/sims/moltnetRoomClient.d.ts +47 -0
- package/dist/sims/moltnetRoomClient.d.ts.map +1 -0
- package/dist/sims/moltnetRoomClient.js +41 -0
- package/dist/sims/poll.d.ts +19 -0
- package/dist/sims/poll.d.ts.map +1 -0
- package/dist/sims/poll.js +23 -0
- package/dist/sims/spawnfileCli.d.ts +49 -0
- package/dist/sims/spawnfileCli.d.ts.map +1 -0
- package/dist/sims/spawnfileCli.js +90 -0
- package/dist/sims/spawnfileReceipts.d.ts +76 -0
- package/dist/sims/spawnfileReceipts.d.ts.map +1 -0
- package/dist/sims/spawnfileReceipts.js +107 -0
- package/dist/sims/worldDrivenOfficeSimDriver.d.ts +59 -0
- package/dist/sims/worldDrivenOfficeSimDriver.d.ts.map +1 -0
- package/dist/sims/worldDrivenOfficeSimDriver.js +182 -0
- package/dist/sims/worldLedgerWriter.d.ts +32 -0
- package/dist/sims/worldLedgerWriter.d.ts.map +1 -0
- package/dist/sims/worldLedgerWriter.js +39 -0
- package/dist/sims/worldSeedLint.d.ts +42 -0
- package/dist/sims/worldSeedLint.d.ts.map +1 -0
- package/dist/sims/worldSeedLint.js +73 -0
- package/dist/sims/worldTickIngest.d.ts +27 -0
- package/dist/sims/worldTickIngest.d.ts.map +1 -0
- package/dist/sims/worldTickIngest.js +35 -0
- package/dist/sims/worldTickLoop.d.ts +64 -0
- package/dist/sims/worldTickLoop.d.ts.map +1 -0
- package/dist/sims/worldTickLoop.js +122 -0
- package/dist/view/engineProvenance.d.ts +46 -0
- package/dist/view/engineProvenance.d.ts.map +1 -0
- package/dist/view/engineProvenance.js +76 -0
- package/dist/view/index.d.ts +14 -0
- package/dist/view/index.d.ts.map +1 -0
- package/dist/view/index.js +107 -0
- package/dist/view/membranes.d.ts +14 -0
- package/dist/view/membranes.d.ts.map +1 -0
- package/dist/view/membranes.js +127 -0
- package/dist/view/runDetect.d.ts +2 -0
- package/dist/view/runDetect.d.ts.map +1 -0
- package/dist/view/runDetect.js +47 -0
- package/dist/view/runRawArtifacts.d.ts +48 -0
- package/dist/view/runRawArtifacts.d.ts.map +1 -0
- package/dist/view/runRawArtifacts.js +143 -0
- package/dist/view/runTimeline.d.ts +24 -0
- package/dist/view/runTimeline.d.ts.map +1 -0
- package/dist/view/runTimeline.js +204 -0
- package/dist/view/runTimelineRecords.d.ts +42 -0
- package/dist/view/runTimelineRecords.d.ts.map +1 -0
- package/dist/view/runTimelineRecords.js +244 -0
- package/dist/view/runTimelineRefs.d.ts +27 -0
- package/dist/view/runTimelineRefs.d.ts.map +1 -0
- package/dist/view/runTimelineRefs.js +31 -0
- package/dist/view/runTimelineTypes.d.ts +99 -0
- package/dist/view/runTimelineTypes.d.ts.map +1 -0
- package/dist/view/runTimelineTypes.js +1 -0
- package/dist/view/runViewModel.d.ts +21 -0
- package/dist/view/runViewModel.d.ts.map +1 -0
- package/dist/view/runViewModel.js +66 -0
- package/dist/view/runViewModelCompute.d.ts +24 -0
- package/dist/view/runViewModelCompute.d.ts.map +1 -0
- package/dist/view/runViewModelCompute.js +157 -0
- package/dist/view/runViewModelTypes.d.ts +177 -0
- package/dist/view/runViewModelTypes.d.ts.map +1 -0
- package/dist/view/runViewModelTypes.js +1 -0
- package/dist/view/runWorldTrace.d.ts +97 -0
- package/dist/view/runWorldTrace.d.ts.map +1 -0
- package/dist/view/runWorldTrace.js +88 -0
- package/dist/view/server.d.ts +12 -0
- package/dist/view/server.d.ts.map +1 -0
- package/dist/view/server.js +306 -0
- package/package.json +65 -0
- package/web/dist/assets/index-CJHLjfsG.js +49 -0
- package/web/dist/assets/index-DJOsPsU8.css +1 -0
- package/web/dist/index.html +13 -0
- package/web/dist/models/AnimatedMushnub.glb +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine provenance disclosure (honesty gap fix): a run-replay viewer must
|
|
3
|
+
* never let a deterministic scripted screenplay be mistaken for live
|
|
4
|
+
* agents. This module classifies each engine name a run declares — either
|
|
5
|
+
* per-agent (from `spawnfile/up-receipt.json`'s `engines[]`, when the
|
|
6
|
+
* run-dir carries one) or the single collapsed `manifest.engine` string —
|
|
7
|
+
* into one of three per-entry buckets (`scripted` / `real-engine` /
|
|
8
|
+
* `unknown`), then folds those into one overall `EngineProvenance.mode` the
|
|
9
|
+
* viewer badges unmissably.
|
|
10
|
+
*
|
|
11
|
+
* Classification rule (never guess):
|
|
12
|
+
* - `fake-*` or exactly `scripted` -> `scripted` (a canned, authored
|
|
13
|
+
* screenplay — deterministic, not emergent dialogue).
|
|
14
|
+
* - A recognized real model engine name (`grok`, `codex`, `agy`, `claude`,
|
|
15
|
+
* matched as a whole word/hyphenated-prefix so `claude-sonnet-4.5` or
|
|
16
|
+
* `grok-4` still match) -> `real-engine`.
|
|
17
|
+
* - Anything else, including an entirely absent engine field -> `unknown`.
|
|
18
|
+
* `unknown` NEVER collapses to `real-engine` — absence of disclosure is
|
|
19
|
+
* never treated as evidence of a real engine.
|
|
20
|
+
*
|
|
21
|
+
* Overall `mode` is the single per-entry classification when every entry
|
|
22
|
+
* agrees, or `"mixed"` when entries disagree (e.g. one agent scripted,
|
|
23
|
+
* another real — or a real name alongside an undetermined one). `"mixed"`
|
|
24
|
+
* is itself a disclosure, not a suppressed detail.
|
|
25
|
+
*/
|
|
26
|
+
export type EngineClassification = "scripted" | "real-engine" | "unknown";
|
|
27
|
+
export interface EngineEntry {
|
|
28
|
+
/** Present when derived from a per-agent up-receipt `engines[]` row; absent for a single collapsed `manifest.engine` string. */
|
|
29
|
+
agent?: string;
|
|
30
|
+
engine: string;
|
|
31
|
+
}
|
|
32
|
+
export interface EngineProvenance {
|
|
33
|
+
mode: EngineClassification | "mixed";
|
|
34
|
+
engines: EngineEntry[];
|
|
35
|
+
label: string;
|
|
36
|
+
}
|
|
37
|
+
/** Classifies one engine name string. Exported for direct testing of the rule in isolation from the aggregation below. */
|
|
38
|
+
export declare const classifyEngineName: (engine: string) => EngineClassification;
|
|
39
|
+
/**
|
|
40
|
+
* Folds a list of engine entries (per-agent or a single collapsed entry)
|
|
41
|
+
* into the one `EngineProvenance` the viewer badges. An empty list (no
|
|
42
|
+
* engine field disclosed anywhere) is `"unknown"` — never defaulted to
|
|
43
|
+
* real or scripted.
|
|
44
|
+
*/
|
|
45
|
+
export declare const computeEngineProvenance: (engines: readonly EngineEntry[]) => EngineProvenance;
|
|
46
|
+
//# sourceMappingURL=engineProvenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engineProvenance.d.ts","sourceRoot":"","sources":["../../src/view/engineProvenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,gIAAgI;IAChI,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC;IACrC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AAaD,0HAA0H;AAC1H,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,KAAG,oBAKnD,CAAC;AAmBF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,SAAS,WAAW,EAAE,KAAG,gBAezE,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine provenance disclosure (honesty gap fix): a run-replay viewer must
|
|
3
|
+
* never let a deterministic scripted screenplay be mistaken for live
|
|
4
|
+
* agents. This module classifies each engine name a run declares — either
|
|
5
|
+
* per-agent (from `spawnfile/up-receipt.json`'s `engines[]`, when the
|
|
6
|
+
* run-dir carries one) or the single collapsed `manifest.engine` string —
|
|
7
|
+
* into one of three per-entry buckets (`scripted` / `real-engine` /
|
|
8
|
+
* `unknown`), then folds those into one overall `EngineProvenance.mode` the
|
|
9
|
+
* viewer badges unmissably.
|
|
10
|
+
*
|
|
11
|
+
* Classification rule (never guess):
|
|
12
|
+
* - `fake-*` or exactly `scripted` -> `scripted` (a canned, authored
|
|
13
|
+
* screenplay — deterministic, not emergent dialogue).
|
|
14
|
+
* - A recognized real model engine name (`grok`, `codex`, `agy`, `claude`,
|
|
15
|
+
* matched as a whole word/hyphenated-prefix so `claude-sonnet-4.5` or
|
|
16
|
+
* `grok-4` still match) -> `real-engine`.
|
|
17
|
+
* - Anything else, including an entirely absent engine field -> `unknown`.
|
|
18
|
+
* `unknown` NEVER collapses to `real-engine` — absence of disclosure is
|
|
19
|
+
* never treated as evidence of a real engine.
|
|
20
|
+
*
|
|
21
|
+
* Overall `mode` is the single per-entry classification when every entry
|
|
22
|
+
* agrees, or `"mixed"` when entries disagree (e.g. one agent scripted,
|
|
23
|
+
* another real — or a real name alongside an undetermined one). `"mixed"`
|
|
24
|
+
* is itself a disclosure, not a suppressed detail.
|
|
25
|
+
*/
|
|
26
|
+
const SCRIPTED_PATTERN = /^fake-/iu;
|
|
27
|
+
const REAL_ENGINE_NAMES = ["grok", "codex", "agy", "claude"];
|
|
28
|
+
/** Matches a real engine name as a whole segment (`grok`, `grok-4`, `claude-sonnet-4.5`) so a substring of an unrelated word never false-matches. */
|
|
29
|
+
const realEngineNameFrom = (engine) => {
|
|
30
|
+
const lowered = engine.toLowerCase();
|
|
31
|
+
return REAL_ENGINE_NAMES.find((name) => lowered === name || lowered.startsWith(`${name}-`) || lowered.startsWith(`${name}_`));
|
|
32
|
+
};
|
|
33
|
+
/** Classifies one engine name string. Exported for direct testing of the rule in isolation from the aggregation below. */
|
|
34
|
+
export const classifyEngineName = (engine) => {
|
|
35
|
+
const lowered = engine.trim().toLowerCase();
|
|
36
|
+
if (lowered === "scripted" || SCRIPTED_PATTERN.test(lowered))
|
|
37
|
+
return "scripted";
|
|
38
|
+
if (realEngineNameFrom(lowered))
|
|
39
|
+
return "real-engine";
|
|
40
|
+
return "unknown";
|
|
41
|
+
};
|
|
42
|
+
const SCRIPTED_LABEL = "⚙ SCRIPTED RUN — authored screenplay, not emergent dialogue (deterministic, not live agents)";
|
|
43
|
+
const UNKNOWN_LABEL = "❓ ENGINE UNKNOWN — provenance not disclosed, do not assume real agents";
|
|
44
|
+
const realEngineLabel = (engines) => {
|
|
45
|
+
const names = [...new Set(engines.map((entry) => entry.engine))];
|
|
46
|
+
return `🧠 REAL ENGINE · ${names.join(", ")}`;
|
|
47
|
+
};
|
|
48
|
+
const mixedLabel = (engines) => {
|
|
49
|
+
const parts = engines.map((entry) => {
|
|
50
|
+
const classification = classifyEngineName(entry.engine);
|
|
51
|
+
const tag = classification === "scripted" ? "scripted" : classification === "real-engine" ? `real: ${entry.engine}` : "unknown";
|
|
52
|
+
return entry.agent ? `${entry.agent}: ${tag}` : tag;
|
|
53
|
+
});
|
|
54
|
+
return `◐ MIXED ENGINES — ${parts.join(", ")} (not uniformly real or scripted)`;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Folds a list of engine entries (per-agent or a single collapsed entry)
|
|
58
|
+
* into the one `EngineProvenance` the viewer badges. An empty list (no
|
|
59
|
+
* engine field disclosed anywhere) is `"unknown"` — never defaulted to
|
|
60
|
+
* real or scripted.
|
|
61
|
+
*/
|
|
62
|
+
export const computeEngineProvenance = (engines) => {
|
|
63
|
+
if (engines.length === 0) {
|
|
64
|
+
return { mode: "unknown", engines: [], label: UNKNOWN_LABEL };
|
|
65
|
+
}
|
|
66
|
+
const classifications = new Set(engines.map((entry) => classifyEngineName(entry.engine)));
|
|
67
|
+
if (classifications.size === 1) {
|
|
68
|
+
const [mode] = classifications;
|
|
69
|
+
if (mode === "scripted")
|
|
70
|
+
return { mode, engines: [...engines], label: SCRIPTED_LABEL };
|
|
71
|
+
if (mode === "real-engine")
|
|
72
|
+
return { mode, engines: [...engines], label: realEngineLabel(engines) };
|
|
73
|
+
return { mode: "unknown", engines: [...engines], label: UNKNOWN_LABEL };
|
|
74
|
+
}
|
|
75
|
+
return { mode: "mixed", engines: [...engines], label: mixedLabel(engines) };
|
|
76
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ViewCommandOptions {
|
|
2
|
+
port: number;
|
|
3
|
+
sourcePath: string;
|
|
4
|
+
statePath?: string;
|
|
5
|
+
openBrowser: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ParsedViewCommand {
|
|
8
|
+
options?: ViewCommandOptions;
|
|
9
|
+
error?: string;
|
|
10
|
+
help?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const parseViewArguments: (argv: readonly string[]) => ParsedViewCommand;
|
|
13
|
+
export declare const runViewCommand: (argv: readonly string[]) => Promise<number>;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/view/index.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,eAAO,MAAM,kBAAkB,GAAI,MAAM,SAAS,MAAM,EAAE,KAAG,iBAyE5D,CAAA;AAED,eAAO,MAAM,cAAc,GAAU,MAAM,SAAS,MAAM,EAAE,KAAG,OAAO,CAAC,MAAM,CAiC5E,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createViewerServer } from "./server.js";
|
|
3
|
+
export const parseViewArguments = (argv) => {
|
|
4
|
+
const parsed = {
|
|
5
|
+
port: 4400,
|
|
6
|
+
sourcePath: ".",
|
|
7
|
+
openBrowser: true,
|
|
8
|
+
};
|
|
9
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
10
|
+
const arg = argv[index];
|
|
11
|
+
if (arg === "--help" || arg === "-h") {
|
|
12
|
+
return { help: true };
|
|
13
|
+
}
|
|
14
|
+
if (arg === "--port") {
|
|
15
|
+
const value = argv[index + 1];
|
|
16
|
+
if (!value) {
|
|
17
|
+
return { error: "Missing value for --port" };
|
|
18
|
+
}
|
|
19
|
+
const port = Number(value);
|
|
20
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
21
|
+
return { error: "Invalid value for --port" };
|
|
22
|
+
}
|
|
23
|
+
parsed.port = port;
|
|
24
|
+
index += 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (arg.startsWith("--port=")) {
|
|
28
|
+
const port = Number(arg.slice("--port=".length));
|
|
29
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
30
|
+
return { error: "Invalid value for --port" };
|
|
31
|
+
}
|
|
32
|
+
parsed.port = port;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg === "--no-open") {
|
|
36
|
+
parsed.openBrowser = false;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg === "--state") {
|
|
40
|
+
const value = argv[index + 1];
|
|
41
|
+
if (!value) {
|
|
42
|
+
return { error: "Missing value for --state" };
|
|
43
|
+
}
|
|
44
|
+
parsed.statePath = value;
|
|
45
|
+
index += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (arg.startsWith("--state=")) {
|
|
49
|
+
parsed.statePath = arg.slice("--state=".length);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (arg.startsWith("-")) {
|
|
53
|
+
return { error: `Unknown flag ${arg}` };
|
|
54
|
+
}
|
|
55
|
+
if (parsed.sourcePath !== ".") {
|
|
56
|
+
return { error: `Unexpected positional argument ${arg}` };
|
|
57
|
+
}
|
|
58
|
+
parsed.sourcePath = arg;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
options: {
|
|
62
|
+
...parsed,
|
|
63
|
+
mode: parsed.statePath ? "live" : "replay",
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
export const runViewCommand = async (argv) => {
|
|
68
|
+
const parsed = parseViewArguments(argv);
|
|
69
|
+
if (parsed.help) {
|
|
70
|
+
process.stdout.write(viewUsage());
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
if (parsed.error || !parsed.options) {
|
|
74
|
+
if (parsed.error)
|
|
75
|
+
process.stderr.write(`${parsed.error}\n`);
|
|
76
|
+
process.stderr.write(viewUsage());
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
const handle = await createViewerServer({
|
|
80
|
+
port: parsed.options.port,
|
|
81
|
+
sourcePath: parsed.options.sourcePath,
|
|
82
|
+
statePath: parsed.options.statePath,
|
|
83
|
+
mode: parsed.options.statePath ? "live" : "replay",
|
|
84
|
+
});
|
|
85
|
+
const modeLabel = parsed.options.statePath ? "live" : "replay";
|
|
86
|
+
process.stdout.write(`Simfile viewer running at ${handle.url} (mode: ${modeLabel})\n`);
|
|
87
|
+
process.stdout.write(`Press Ctrl+C to stop.\n`);
|
|
88
|
+
if (parsed.options.openBrowser) {
|
|
89
|
+
openBrowser(handle.url);
|
|
90
|
+
}
|
|
91
|
+
await new Promise(() => { });
|
|
92
|
+
return 0;
|
|
93
|
+
};
|
|
94
|
+
const openBrowser = (url) => {
|
|
95
|
+
const command = process.platform === "darwin" ? "open"
|
|
96
|
+
: process.platform === "win32" ? "start \"\""
|
|
97
|
+
: "xdg-open";
|
|
98
|
+
spawn(command, [url], { detached: true, stdio: "ignore", shell: true });
|
|
99
|
+
};
|
|
100
|
+
const viewUsage = () => [
|
|
101
|
+
"Usage:",
|
|
102
|
+
" simfile view --state <path>",
|
|
103
|
+
" simfile view <run-record-dir>",
|
|
104
|
+
" simfile view --port 4400 --no-open --state .sim/",
|
|
105
|
+
" simfile view --help",
|
|
106
|
+
"",
|
|
107
|
+
].join("\n");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RunTimelineMembrane } from "./runTimelineTypes.js";
|
|
2
|
+
/**
|
|
3
|
+
* Builds the membranes array. Empty (never throwing) when the report is
|
|
4
|
+
* absent/unparseable or declares no interior self-teams — the office-sim
|
|
5
|
+
* golden (no teams, no report in its run-dir) legitimately yields `[]`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const deriveMembranes: (report: unknown) => RunTimelineMembrane[];
|
|
8
|
+
/**
|
|
9
|
+
* Reads `spawnfile-report.json` from a run-dir and derives its membranes.
|
|
10
|
+
* Returns `[]` when the file is missing (a non-composed or single-team run
|
|
11
|
+
* that never copied a report in) — never throws for a run without one.
|
|
12
|
+
*/
|
|
13
|
+
export declare const readRunMembranes: (runDir: string) => Promise<RunTimelineMembrane[]>;
|
|
14
|
+
//# sourceMappingURL=membranes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"membranes.d.ts","sourceRoot":"","sources":["../../src/view/membranes.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAwHjE;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,OAAO,KAAG,mBAAmB,EAwCpE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,mBAAmB,EAAE,CAQpF,CAAC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { agentRef, roomRef } from "./runTimelineRefs.js";
|
|
4
|
+
const asRecord = (value) => typeof value === "object" && value !== null ? value : undefined;
|
|
5
|
+
const asString = (value) => (typeof value === "string" ? value : undefined);
|
|
6
|
+
const stripPrefix = (id, prefix) => (id.startsWith(prefix) ? id.slice(prefix.length) : id);
|
|
7
|
+
/** Team-slug set: every `nodes[]` entry with `kind: "team"`, stripped of the `team:` id prefix. */
|
|
8
|
+
const collectTeamSlugs = (report) => {
|
|
9
|
+
const teams = new Set();
|
|
10
|
+
const nodes = Array.isArray(report.nodes) ? report.nodes : [];
|
|
11
|
+
for (const node of nodes) {
|
|
12
|
+
if (asString(node.kind) === "team") {
|
|
13
|
+
const id = asString(node.id);
|
|
14
|
+
if (id)
|
|
15
|
+
teams.add(stripPrefix(id, "team:"));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return teams;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* team-slug -> representative `agent:<id>` ref, read from every agent node's
|
|
22
|
+
* `active_environments.moltnet[network][room].member_slot`: when that slot is
|
|
23
|
+
* a known team slug OTHER than the agent's own id, this agent is that team's
|
|
24
|
+
* representative on a parent floor.
|
|
25
|
+
*/
|
|
26
|
+
const collectRepresentatives = (report, teamSlugs) => {
|
|
27
|
+
const byTeam = new Map();
|
|
28
|
+
const nodes = Array.isArray(report.nodes) ? report.nodes : [];
|
|
29
|
+
for (const node of nodes) {
|
|
30
|
+
if (asString(node.kind) !== "agent")
|
|
31
|
+
continue;
|
|
32
|
+
const nodeId = asString(node.id);
|
|
33
|
+
if (!nodeId)
|
|
34
|
+
continue;
|
|
35
|
+
const agentSlug = stripPrefix(nodeId, "agent:");
|
|
36
|
+
const active = asRecord(node.active_environments);
|
|
37
|
+
const moltnet = asRecord(active?.moltnet);
|
|
38
|
+
if (!moltnet)
|
|
39
|
+
continue;
|
|
40
|
+
for (const networkBinding of Object.values(moltnet)) {
|
|
41
|
+
const rooms = asRecord(asRecord(networkBinding)?.rooms);
|
|
42
|
+
if (!rooms)
|
|
43
|
+
continue;
|
|
44
|
+
for (const roomBinding of Object.values(rooms)) {
|
|
45
|
+
const slot = asString(asRecord(roomBinding)?.member_slot);
|
|
46
|
+
if (slot && slot !== agentSlug && teamSlugs.has(slot) && !byTeam.has(slot)) {
|
|
47
|
+
byTeam.set(slot, agentRef(agentSlug));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return byTeam;
|
|
53
|
+
};
|
|
54
|
+
/** The `server_plans[]` a team owns, matched by the compiler's `id === `${teamSlug}-${networkId}`` convention. */
|
|
55
|
+
const ownedServerPlans = (report, teamSlug) => {
|
|
56
|
+
const moltnet = asRecord(asRecord(report.container)?.moltnet);
|
|
57
|
+
const plans = Array.isArray(moltnet?.server_plans)
|
|
58
|
+
? moltnet.server_plans
|
|
59
|
+
: [];
|
|
60
|
+
return plans.filter((plan) => {
|
|
61
|
+
const id = asString(plan.id);
|
|
62
|
+
const networkId = asString(plan.network_id);
|
|
63
|
+
return Boolean(id && networkId && id === `${teamSlug}-${networkId}`);
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Builds the membranes array. Empty (never throwing) when the report is
|
|
68
|
+
* absent/unparseable or declares no interior self-teams — the office-sim
|
|
69
|
+
* golden (no teams, no report in its run-dir) legitimately yields `[]`.
|
|
70
|
+
*/
|
|
71
|
+
export const deriveMembranes = (report) => {
|
|
72
|
+
const shape = asRecord(report);
|
|
73
|
+
if (!shape)
|
|
74
|
+
return [];
|
|
75
|
+
const teamSlugs = collectTeamSlugs(shape);
|
|
76
|
+
const representatives = collectRepresentatives(shape, teamSlugs);
|
|
77
|
+
const membranes = [];
|
|
78
|
+
for (const [teamSlug, representative] of representatives) {
|
|
79
|
+
const plans = ownedServerPlans(shape, teamSlug);
|
|
80
|
+
if (plans.length === 0)
|
|
81
|
+
continue;
|
|
82
|
+
const interiorRooms = [];
|
|
83
|
+
const members = new Set();
|
|
84
|
+
for (const plan of plans) {
|
|
85
|
+
const networkId = asString(plan.network_id);
|
|
86
|
+
const rooms = Array.isArray(plan.rooms) ? plan.rooms : [];
|
|
87
|
+
for (const room of rooms) {
|
|
88
|
+
const roomId = asString(room.id);
|
|
89
|
+
if (!networkId || !roomId)
|
|
90
|
+
continue;
|
|
91
|
+
interiorRooms.push(roomRef(networkId, roomId));
|
|
92
|
+
const roomMembers = Array.isArray(room.members) ? room.members : [];
|
|
93
|
+
for (const member of roomMembers) {
|
|
94
|
+
const memberId = asString(member);
|
|
95
|
+
if (memberId)
|
|
96
|
+
members.add(agentRef(memberId));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (interiorRooms.length === 0)
|
|
101
|
+
continue;
|
|
102
|
+
membranes.push({
|
|
103
|
+
ref: `team:${teamSlug}`,
|
|
104
|
+
label: teamSlug,
|
|
105
|
+
representative,
|
|
106
|
+
interiorRooms: interiorRooms.sort(),
|
|
107
|
+
members: [...members].sort()
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return membranes.sort((left, right) => left.ref.localeCompare(right.ref));
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Reads `spawnfile-report.json` from a run-dir and derives its membranes.
|
|
114
|
+
* Returns `[]` when the file is missing (a non-composed or single-team run
|
|
115
|
+
* that never copied a report in) — never throws for a run without one.
|
|
116
|
+
*/
|
|
117
|
+
export const readRunMembranes = async (runDir) => {
|
|
118
|
+
const raw = await readFile(path.join(runDir, "spawnfile-report.json"), "utf8").catch(() => null);
|
|
119
|
+
if (raw === null)
|
|
120
|
+
return [];
|
|
121
|
+
try {
|
|
122
|
+
return deriveMembranes(JSON.parse(raw));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runDetect.d.ts","sourceRoot":"","sources":["../../src/view/runDetect.ts"],"names":[],"mappings":"AAmCA,eAAO,MAAM,eAAe,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,OAAO,CAStE,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { access, readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Detects a compose-and-observe run directory (`simfile.run-manifest.v1` + at
|
|
5
|
+
* least one moltnet transcript) so `simfile view <dir>` can pick the
|
|
6
|
+
* run-reader replay mode instead of the world/3D replay mode. Deliberately
|
|
7
|
+
* narrow: it only checks the two artifacts that make the golden fixtures
|
|
8
|
+
* (`fixtures/observe/office-sim-golden/`, `fixtures/observe/jungian-daimon-org-golden/`)
|
|
9
|
+
* and any future real run directory renderable, never a full manifest parse —
|
|
10
|
+
* `runObserve` does the real validation once this returns true. A transcript
|
|
11
|
+
* may be the single-network flat `raw/moltnet/transcript.json` OR any
|
|
12
|
+
* multi-network per-network `raw/moltnet/<network_id>/transcript.json`.
|
|
13
|
+
*/
|
|
14
|
+
const hasMoltnetTranscript = async (dirPath) => {
|
|
15
|
+
const moltnetDir = path.join(dirPath, "raw", "moltnet");
|
|
16
|
+
try {
|
|
17
|
+
await access(path.join(moltnetDir, "transcript.json"));
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// fall through to the per-network scan
|
|
22
|
+
}
|
|
23
|
+
const entries = await readdir(moltnetDir, { withFileTypes: true }).catch(() => []);
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
if (!entry.isDirectory())
|
|
26
|
+
continue;
|
|
27
|
+
try {
|
|
28
|
+
await access(path.join(moltnetDir, entry.name, "transcript.json"));
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// keep scanning
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
};
|
|
37
|
+
export const isObserveRunDir = async (dirPath) => {
|
|
38
|
+
if (!(await hasMoltnetTranscript(dirPath)))
|
|
39
|
+
return false;
|
|
40
|
+
try {
|
|
41
|
+
const raw = JSON.parse(await readFile(path.join(dirPath, "manifest.json"), "utf8"));
|
|
42
|
+
return raw.version === "simfile.run-manifest.v1";
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { EngineEntry } from "./engineProvenance.js";
|
|
2
|
+
import type { RawMnemeEvent, RawTranscript, RunTelemetrySample } from "./runViewModelTypes.js";
|
|
3
|
+
export declare const readMnemeEventsByBank: (runDir: string) => Promise<Map<string, RawMnemeEvent[]>>;
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes both `raw/moltnet/transcript.json` shapes into one internal
|
|
6
|
+
* message list: the golden fixture's `{seedMessageText, transcript: [...]}`
|
|
7
|
+
* and a real composed run's export shape `{conversations: [{messages:
|
|
8
|
+
* [...]}] }` (`moltnet/transcript-export.ts`'s `MoltnetExportedTranscript`).
|
|
9
|
+
* Every conversation's messages are flattened and re-sorted by
|
|
10
|
+
* `created_at` so multi-conversation exports still read chronologically;
|
|
11
|
+
* single-conversation runs (the only shape produced today) are a no-op
|
|
12
|
+
* re-sort of an already-ordered list.
|
|
13
|
+
*/
|
|
14
|
+
export declare const normalizeRawTranscript: (raw: unknown) => RawTranscript;
|
|
15
|
+
/**
|
|
16
|
+
* Reads every moltnet transcript in a run-dir and merges them into one
|
|
17
|
+
* chronologically-sorted message list. A single-network run writes the flat
|
|
18
|
+
* `raw/moltnet/transcript.json` (the golden/office-sim shape); a multi-network
|
|
19
|
+
* composed run (the jungian psyche) writes one per network under
|
|
20
|
+
* `raw/moltnet/<network_id>/transcript.json`. Both are read and unioned so the
|
|
21
|
+
* timeline's `messagesById` spans every room across every network — the join
|
|
22
|
+
* an interior-council membrane needs. Message ids are globally-unique UUIDs, so
|
|
23
|
+
* a flat re-sort of the union is a correct chronological interleave.
|
|
24
|
+
*/
|
|
25
|
+
export declare const readTranscript: (runDir: string) => Promise<RawTranscript>;
|
|
26
|
+
/**
|
|
27
|
+
* Reads `world/telemetry.json` when present (world-driven runs only —
|
|
28
|
+
* `office-secret-v0-golden` ships one, `office-sim-golden` does not).
|
|
29
|
+
* Returns `null` on any missing/malformed file rather than throwing: this
|
|
30
|
+
* artifact is optional, never a requirement for a run to render
|
|
31
|
+
* (`runDetect.ts`'s shape check does not require it).
|
|
32
|
+
*/
|
|
33
|
+
export declare const readWorldTelemetry: (runDir: string) => Promise<RunTelemetrySample[] | null>;
|
|
34
|
+
/** `true` when at least one sample carries at least one variable — the gate that decides whether a run has real gauge data to render (`RunViewModel.variableSamples`'s own doc comment: never a fabricated empty gauge). */
|
|
35
|
+
export declare const hasVariableSamples: (samples: readonly RunTelemetrySample[] | null) => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Reads `spawnfile/up-receipt.json`'s `engines[]` (per-agent engine
|
|
38
|
+
* breakdown) when a composed run-dir carries one. Loosely shaped, not
|
|
39
|
+
* schema-validated — this is an optional, best-effort finer-grained signal
|
|
40
|
+
* layered on top of the manifest's own single `engine` field, so a
|
|
41
|
+
* malformed/partial file degrades to `undefined` (the caller falls back to
|
|
42
|
+
* `manifest.engine`) rather than throwing. No run-dir currently ships this
|
|
43
|
+
* file, but a composed driver's `spawnfileReceipts.ts` up-receipt schema
|
|
44
|
+
* already carries `engines` when a real deployment reports mixed per-agent
|
|
45
|
+
* engines, so this reads it the moment one does.
|
|
46
|
+
*/
|
|
47
|
+
export declare const readUpReceiptEngines: (runDir: string) => Promise<EngineEntry[] | undefined>;
|
|
48
|
+
//# sourceMappingURL=runRawArtifacts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runRawArtifacts.d.ts","sourceRoot":"","sources":["../../src/view/runRawArtifacts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAwB,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAmBrH,eAAO,MAAM,qBAAqB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAYhG,CAAC;AAkBF;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,GAAI,KAAK,OAAO,KAAG,aAWrD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,aAAa,CAsB1E,CAAC;AASF;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,kBAAkB,EAAE,GAAG,IAAI,CAe5F,CAAC;AAEF,4NAA4N;AAC5N,eAAO,MAAM,kBAAkB,GAAI,SAAS,SAAS,kBAAkB,EAAE,GAAG,IAAI,KAAG,OACK,CAAC;AAEzF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,WAAW,EAAE,GAAG,SAAS,CAkB5F,CAAC"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Shared, dependency-free reads of the raw run-directory artifacts every
|
|
5
|
+
* run-reader/run-replay data path needs: the moltnet transcript, the
|
|
6
|
+
* per-bank mneme event logs, and (increment 3) the world telemetry
|
|
7
|
+
* snapshot. Extracted out of `runViewModel.ts` so `runTimeline.ts`
|
|
8
|
+
* (`buildRunTimeline`) can read the exact same records without
|
|
9
|
+
* re-implementing or duplicating file I/O (`AGENTS.md`: small composable
|
|
10
|
+
* modules, no invented state).
|
|
11
|
+
*/
|
|
12
|
+
const parseJsonlLines = (text) => text
|
|
13
|
+
.split(/\r?\n/u)
|
|
14
|
+
.map((line) => line.trim())
|
|
15
|
+
.filter((line) => line.length > 0)
|
|
16
|
+
.map((line) => JSON.parse(line));
|
|
17
|
+
export const readMnemeEventsByBank = async (runDir) => {
|
|
18
|
+
const mnemeDir = path.join(runDir, "raw", "mneme");
|
|
19
|
+
const bankDirs = await readdir(mnemeDir, { withFileTypes: true }).catch(() => []);
|
|
20
|
+
const byBank = new Map();
|
|
21
|
+
for (const entry of bankDirs) {
|
|
22
|
+
if (!entry.isDirectory())
|
|
23
|
+
continue;
|
|
24
|
+
const text = await readFile(path.join(mnemeDir, entry.name, "events.jsonl"), "utf8").catch(() => null);
|
|
25
|
+
if (text === null)
|
|
26
|
+
continue;
|
|
27
|
+
byBank.set(entry.name, parseJsonlLines(text));
|
|
28
|
+
}
|
|
29
|
+
return byBank;
|
|
30
|
+
};
|
|
31
|
+
const isExportShape = (value) => typeof value === "object" && value !== null && Array.isArray(value.conversations);
|
|
32
|
+
/**
|
|
33
|
+
* Normalizes both `raw/moltnet/transcript.json` shapes into one internal
|
|
34
|
+
* message list: the golden fixture's `{seedMessageText, transcript: [...]}`
|
|
35
|
+
* and a real composed run's export shape `{conversations: [{messages:
|
|
36
|
+
* [...]}] }` (`moltnet/transcript-export.ts`'s `MoltnetExportedTranscript`).
|
|
37
|
+
* Every conversation's messages are flattened and re-sorted by
|
|
38
|
+
* `created_at` so multi-conversation exports still read chronologically;
|
|
39
|
+
* single-conversation runs (the only shape produced today) are a no-op
|
|
40
|
+
* re-sort of an already-ordered list.
|
|
41
|
+
*/
|
|
42
|
+
export const normalizeRawTranscript = (raw) => {
|
|
43
|
+
if (isExportShape(raw)) {
|
|
44
|
+
const transcript = raw.conversations
|
|
45
|
+
.flatMap((conversation) => conversation.messages ?? [])
|
|
46
|
+
.slice()
|
|
47
|
+
.sort((left, right) => Date.parse(left.created_at) - Date.parse(right.created_at));
|
|
48
|
+
return { transcript };
|
|
49
|
+
}
|
|
50
|
+
const golden = raw;
|
|
51
|
+
return { seedMessageText: golden.seedMessageText, transcript: golden.transcript ?? [] };
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Reads every moltnet transcript in a run-dir and merges them into one
|
|
55
|
+
* chronologically-sorted message list. A single-network run writes the flat
|
|
56
|
+
* `raw/moltnet/transcript.json` (the golden/office-sim shape); a multi-network
|
|
57
|
+
* composed run (the jungian psyche) writes one per network under
|
|
58
|
+
* `raw/moltnet/<network_id>/transcript.json`. Both are read and unioned so the
|
|
59
|
+
* timeline's `messagesById` spans every room across every network — the join
|
|
60
|
+
* an interior-council membrane needs. Message ids are globally-unique UUIDs, so
|
|
61
|
+
* a flat re-sort of the union is a correct chronological interleave.
|
|
62
|
+
*/
|
|
63
|
+
export const readTranscript = async (runDir) => {
|
|
64
|
+
const moltnetDir = path.join(runDir, "raw", "moltnet");
|
|
65
|
+
const files = [path.join(moltnetDir, "transcript.json")];
|
|
66
|
+
const entries = await readdir(moltnetDir, { withFileTypes: true }).catch(() => []);
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
if (entry.isDirectory())
|
|
69
|
+
files.push(path.join(moltnetDir, entry.name, "transcript.json"));
|
|
70
|
+
}
|
|
71
|
+
let seedMessageText;
|
|
72
|
+
const merged = [];
|
|
73
|
+
for (const file of files) {
|
|
74
|
+
const text = await readFile(file, "utf8").catch(() => null);
|
|
75
|
+
if (text === null)
|
|
76
|
+
continue;
|
|
77
|
+
const normalized = normalizeRawTranscript(JSON.parse(text));
|
|
78
|
+
if (seedMessageText === undefined && normalized.seedMessageText !== undefined) {
|
|
79
|
+
seedMessageText = normalized.seedMessageText;
|
|
80
|
+
}
|
|
81
|
+
merged.push(...normalized.transcript);
|
|
82
|
+
}
|
|
83
|
+
merged.sort((left, right) => Date.parse(left.created_at) - Date.parse(right.created_at));
|
|
84
|
+
return { seedMessageText, transcript: merged };
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Reads `world/telemetry.json` when present (world-driven runs only —
|
|
88
|
+
* `office-secret-v0-golden` ships one, `office-sim-golden` does not).
|
|
89
|
+
* Returns `null` on any missing/malformed file rather than throwing: this
|
|
90
|
+
* artifact is optional, never a requirement for a run to render
|
|
91
|
+
* (`runDetect.ts`'s shape check does not require it).
|
|
92
|
+
*/
|
|
93
|
+
export const readWorldTelemetry = async (runDir) => {
|
|
94
|
+
const raw = await readFile(path.join(runDir, "world", "telemetry.json"), "utf8").catch(() => null);
|
|
95
|
+
if (raw === null)
|
|
96
|
+
return null;
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(raw);
|
|
99
|
+
if (!Array.isArray(parsed.samples))
|
|
100
|
+
return null;
|
|
101
|
+
return parsed.samples.map((sample) => ({
|
|
102
|
+
tick: sample.tick,
|
|
103
|
+
simTime: sample.sim_time,
|
|
104
|
+
phase: sample.phase,
|
|
105
|
+
variables: sample.variables ?? {},
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
/** `true` when at least one sample carries at least one variable — the gate that decides whether a run has real gauge data to render (`RunViewModel.variableSamples`'s own doc comment: never a fabricated empty gauge). */
|
|
113
|
+
export const hasVariableSamples = (samples) => samples !== null && samples.some((sample) => Object.keys(sample.variables).length > 0);
|
|
114
|
+
/**
|
|
115
|
+
* Reads `spawnfile/up-receipt.json`'s `engines[]` (per-agent engine
|
|
116
|
+
* breakdown) when a composed run-dir carries one. Loosely shaped, not
|
|
117
|
+
* schema-validated — this is an optional, best-effort finer-grained signal
|
|
118
|
+
* layered on top of the manifest's own single `engine` field, so a
|
|
119
|
+
* malformed/partial file degrades to `undefined` (the caller falls back to
|
|
120
|
+
* `manifest.engine`) rather than throwing. No run-dir currently ships this
|
|
121
|
+
* file, but a composed driver's `spawnfileReceipts.ts` up-receipt schema
|
|
122
|
+
* already carries `engines` when a real deployment reports mixed per-agent
|
|
123
|
+
* engines, so this reads it the moment one does.
|
|
124
|
+
*/
|
|
125
|
+
export const readUpReceiptEngines = async (runDir) => {
|
|
126
|
+
const raw = await readFile(path.join(runDir, "spawnfile", "up-receipt.json"), "utf8").catch(() => null);
|
|
127
|
+
if (raw === null)
|
|
128
|
+
return undefined;
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(raw);
|
|
131
|
+
if (!Array.isArray(parsed.engines))
|
|
132
|
+
return undefined;
|
|
133
|
+
const entries = parsed.engines.filter((entry) => typeof entry === "object" &&
|
|
134
|
+
entry !== null &&
|
|
135
|
+
typeof entry.engine === "string" &&
|
|
136
|
+
entry.engine.length > 0 &&
|
|
137
|
+
(typeof entry.agent === "string" || entry.agent === undefined));
|
|
138
|
+
return entries.length > 0 ? entries : undefined;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
};
|