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.
Files changed (249) hide show
  1. package/README.md +143 -0
  2. package/dist/cli/index.d.ts +3 -0
  3. package/dist/cli/index.d.ts.map +1 -0
  4. package/dist/cli/index.js +348 -0
  5. package/dist/coverage/index.d.ts +2 -0
  6. package/dist/coverage/index.d.ts.map +1 -0
  7. package/dist/coverage/index.js +1 -0
  8. package/dist/coverage/matrix.d.ts +21 -0
  9. package/dist/coverage/matrix.d.ts.map +1 -0
  10. package/dist/coverage/matrix.js +173 -0
  11. package/dist/coverage/render.d.ts +3 -0
  12. package/dist/coverage/render.d.ts.map +1 -0
  13. package/dist/coverage/render.js +26 -0
  14. package/dist/index.d.ts +8 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +7 -0
  17. package/dist/kernel/duration.d.ts +9 -0
  18. package/dist/kernel/duration.d.ts.map +1 -0
  19. package/dist/kernel/duration.js +44 -0
  20. package/dist/kernel/index.d.ts +4 -0
  21. package/dist/kernel/index.d.ts.map +1 -0
  22. package/dist/kernel/index.js +3 -0
  23. package/dist/kernel/range.d.ts +6 -0
  24. package/dist/kernel/range.d.ts.map +1 -0
  25. package/dist/kernel/range.js +16 -0
  26. package/dist/kernel/stochastic.d.ts +10 -0
  27. package/dist/kernel/stochastic.d.ts.map +1 -0
  28. package/dist/kernel/stochastic.js +25 -0
  29. package/dist/ledger/index.d.ts +4 -0
  30. package/dist/ledger/index.d.ts.map +1 -0
  31. package/dist/ledger/index.js +3 -0
  32. package/dist/ledger/markers.d.ts +46 -0
  33. package/dist/ledger/markers.d.ts.map +1 -0
  34. package/dist/ledger/markers.js +108 -0
  35. package/dist/ledger/stable.d.ts +65 -0
  36. package/dist/ledger/stable.d.ts.map +1 -0
  37. package/dist/ledger/stable.js +78 -0
  38. package/dist/ledger/validation.d.ts +16 -0
  39. package/dist/ledger/validation.d.ts.map +1 -0
  40. package/dist/ledger/validation.js +127 -0
  41. package/dist/moltnet/index.d.ts +3 -0
  42. package/dist/moltnet/index.d.ts.map +1 -0
  43. package/dist/moltnet/index.js +2 -0
  44. package/dist/moltnet/transcript-export.d.ts +34 -0
  45. package/dist/moltnet/transcript-export.d.ts.map +1 -0
  46. package/dist/moltnet/transcript-export.js +68 -0
  47. package/dist/moltnet/world-participant.d.ts +77 -0
  48. package/dist/moltnet/world-participant.d.ts.map +1 -0
  49. package/dist/moltnet/world-participant.js +221 -0
  50. package/dist/observe/artifacts.d.ts +16 -0
  51. package/dist/observe/artifacts.d.ts.map +1 -0
  52. package/dist/observe/artifacts.js +31 -0
  53. package/dist/observe/causalStreams.d.ts +19 -0
  54. package/dist/observe/causalStreams.d.ts.map +1 -0
  55. package/dist/observe/causalStreams.js +38 -0
  56. package/dist/observe/compute.d.ts +17 -0
  57. package/dist/observe/compute.d.ts.map +1 -0
  58. package/dist/observe/compute.js +97 -0
  59. package/dist/observe/index.d.ts +11 -0
  60. package/dist/observe/index.d.ts.map +1 -0
  61. package/dist/observe/index.js +10 -0
  62. package/dist/observe/manifest.d.ts +79 -0
  63. package/dist/observe/manifest.d.ts.map +1 -0
  64. package/dist/observe/manifest.js +87 -0
  65. package/dist/observe/memoryBanks.d.ts +38 -0
  66. package/dist/observe/memoryBanks.d.ts.map +1 -0
  67. package/dist/observe/memoryBanks.js +86 -0
  68. package/dist/observe/observe.d.ts +32 -0
  69. package/dist/observe/observe.d.ts.map +1 -0
  70. package/dist/observe/observe.js +73 -0
  71. package/dist/observe/report.d.ts +165 -0
  72. package/dist/observe/report.d.ts.map +1 -0
  73. package/dist/observe/report.js +138 -0
  74. package/dist/observe/seedSpread.d.ts +49 -0
  75. package/dist/observe/seedSpread.d.ts.map +1 -0
  76. package/dist/observe/seedSpread.js +262 -0
  77. package/dist/observe/seedSpreadArtifacts.d.ts +49 -0
  78. package/dist/observe/seedSpreadArtifacts.d.ts.map +1 -0
  79. package/dist/observe/seedSpreadArtifacts.js +118 -0
  80. package/dist/observe/spreadMatcher.d.ts +45 -0
  81. package/dist/observe/spreadMatcher.d.ts.map +1 -0
  82. package/dist/observe/spreadMatcher.js +124 -0
  83. package/dist/report/index.d.ts +4 -0
  84. package/dist/report/index.d.ts.map +1 -0
  85. package/dist/report/index.js +3 -0
  86. package/dist/report/probeArtifact.d.ts +26 -0
  87. package/dist/report/probeArtifact.d.ts.map +1 -0
  88. package/dist/report/probeArtifact.js +119 -0
  89. package/dist/report/probes.d.ts +52 -0
  90. package/dist/report/probes.d.ts.map +1 -0
  91. package/dist/report/probes.js +164 -0
  92. package/dist/report/score.d.ts +27 -0
  93. package/dist/report/score.d.ts.map +1 -0
  94. package/dist/report/score.js +14 -0
  95. package/dist/report/transcripts.d.ts +12 -0
  96. package/dist/report/transcripts.d.ts.map +1 -0
  97. package/dist/report/transcripts.js +17 -0
  98. package/dist/runtime/causal-fixture.d.ts +26 -0
  99. package/dist/runtime/causal-fixture.d.ts.map +1 -0
  100. package/dist/runtime/causal-fixture.js +36 -0
  101. package/dist/runtime/clock.d.ts +25 -0
  102. package/dist/runtime/clock.d.ts.map +1 -0
  103. package/dist/runtime/clock.js +63 -0
  104. package/dist/runtime/condition.d.ts +55 -0
  105. package/dist/runtime/condition.d.ts.map +1 -0
  106. package/dist/runtime/condition.js +122 -0
  107. package/dist/runtime/emit-causal-fixture.d.ts +2 -0
  108. package/dist/runtime/emit-causal-fixture.d.ts.map +1 -0
  109. package/dist/runtime/emit-causal-fixture.js +45 -0
  110. package/dist/runtime/expression.d.ts +11 -0
  111. package/dist/runtime/expression.d.ts.map +1 -0
  112. package/dist/runtime/expression.js +305 -0
  113. package/dist/runtime/numeric.d.ts +7 -0
  114. package/dist/runtime/numeric.d.ts.map +1 -0
  115. package/dist/runtime/numeric.js +11 -0
  116. package/dist/runtime/rule-actions.d.ts +5 -0
  117. package/dist/runtime/rule-actions.d.ts.map +1 -0
  118. package/dist/runtime/rule-actions.js +82 -0
  119. package/dist/runtime/run-record.d.ts +73 -0
  120. package/dist/runtime/run-record.d.ts.map +1 -0
  121. package/dist/runtime/run-record.js +179 -0
  122. package/dist/runtime/step-tick.d.ts +43 -0
  123. package/dist/runtime/step-tick.d.ts.map +1 -0
  124. package/dist/runtime/step-tick.js +177 -0
  125. package/dist/runtime/trace-compile.d.ts +66 -0
  126. package/dist/runtime/trace-compile.d.ts.map +1 -0
  127. package/dist/runtime/trace-compile.js +157 -0
  128. package/dist/runtime/trace-export.d.ts +5 -0
  129. package/dist/runtime/trace-export.d.ts.map +1 -0
  130. package/dist/runtime/trace-export.js +4 -0
  131. package/dist/runtime/trace-run.d.ts +15 -0
  132. package/dist/runtime/trace-run.d.ts.map +1 -0
  133. package/dist/runtime/trace-run.js +92 -0
  134. package/dist/runtime/trace.d.ts +10 -0
  135. package/dist/runtime/trace.d.ts.map +1 -0
  136. package/dist/runtime/trace.js +9 -0
  137. package/dist/runtime/types.d.ts +61 -0
  138. package/dist/runtime/types.d.ts.map +1 -0
  139. package/dist/runtime/types.js +1 -0
  140. package/dist/runtime/viewer-trace.d.ts +64 -0
  141. package/dist/runtime/viewer-trace.d.ts.map +1 -0
  142. package/dist/runtime/viewer-trace.js +164 -0
  143. package/dist/runtime/world-act.d.ts +55 -0
  144. package/dist/runtime/world-act.d.ts.map +1 -0
  145. package/dist/runtime/world-act.js +156 -0
  146. package/dist/schema/binding.d.ts +23 -0
  147. package/dist/schema/binding.d.ts.map +1 -0
  148. package/dist/schema/binding.js +249 -0
  149. package/dist/schema/index.d.ts +5 -0
  150. package/dist/schema/index.d.ts.map +1 -0
  151. package/dist/schema/index.js +4 -0
  152. package/dist/schema/model.d.ts +244 -0
  153. package/dist/schema/model.d.ts.map +1 -0
  154. package/dist/schema/model.js +222 -0
  155. package/dist/schema/parse.d.ts +10 -0
  156. package/dist/schema/parse.d.ts.map +1 -0
  157. package/dist/schema/parse.js +15 -0
  158. package/dist/schema/semantic.d.ts +3 -0
  159. package/dist/schema/semantic.d.ts.map +1 -0
  160. package/dist/schema/semantic.js +135 -0
  161. package/dist/sims/composeRunManifest.d.ts +54 -0
  162. package/dist/sims/composeRunManifest.d.ts.map +1 -0
  163. package/dist/sims/composeRunManifest.js +40 -0
  164. package/dist/sims/composedJungianSimDriver.d.ts +50 -0
  165. package/dist/sims/composedJungianSimDriver.d.ts.map +1 -0
  166. package/dist/sims/composedJungianSimDriver.js +199 -0
  167. package/dist/sims/composedOfficeSimDriver.d.ts +53 -0
  168. package/dist/sims/composedOfficeSimDriver.d.ts.map +1 -0
  169. package/dist/sims/composedOfficeSimDriver.js +162 -0
  170. package/dist/sims/exchangeWait.d.ts +56 -0
  171. package/dist/sims/exchangeWait.d.ts.map +1 -0
  172. package/dist/sims/exchangeWait.js +66 -0
  173. package/dist/sims/index.d.ts +14 -0
  174. package/dist/sims/index.d.ts.map +1 -0
  175. package/dist/sims/index.js +13 -0
  176. package/dist/sims/moltnetRoomClient.d.ts +47 -0
  177. package/dist/sims/moltnetRoomClient.d.ts.map +1 -0
  178. package/dist/sims/moltnetRoomClient.js +41 -0
  179. package/dist/sims/poll.d.ts +19 -0
  180. package/dist/sims/poll.d.ts.map +1 -0
  181. package/dist/sims/poll.js +23 -0
  182. package/dist/sims/spawnfileCli.d.ts +49 -0
  183. package/dist/sims/spawnfileCli.d.ts.map +1 -0
  184. package/dist/sims/spawnfileCli.js +90 -0
  185. package/dist/sims/spawnfileReceipts.d.ts +76 -0
  186. package/dist/sims/spawnfileReceipts.d.ts.map +1 -0
  187. package/dist/sims/spawnfileReceipts.js +107 -0
  188. package/dist/sims/worldDrivenOfficeSimDriver.d.ts +59 -0
  189. package/dist/sims/worldDrivenOfficeSimDriver.d.ts.map +1 -0
  190. package/dist/sims/worldDrivenOfficeSimDriver.js +182 -0
  191. package/dist/sims/worldLedgerWriter.d.ts +32 -0
  192. package/dist/sims/worldLedgerWriter.d.ts.map +1 -0
  193. package/dist/sims/worldLedgerWriter.js +39 -0
  194. package/dist/sims/worldSeedLint.d.ts +42 -0
  195. package/dist/sims/worldSeedLint.d.ts.map +1 -0
  196. package/dist/sims/worldSeedLint.js +73 -0
  197. package/dist/sims/worldTickIngest.d.ts +27 -0
  198. package/dist/sims/worldTickIngest.d.ts.map +1 -0
  199. package/dist/sims/worldTickIngest.js +35 -0
  200. package/dist/sims/worldTickLoop.d.ts +64 -0
  201. package/dist/sims/worldTickLoop.d.ts.map +1 -0
  202. package/dist/sims/worldTickLoop.js +122 -0
  203. package/dist/view/engineProvenance.d.ts +46 -0
  204. package/dist/view/engineProvenance.d.ts.map +1 -0
  205. package/dist/view/engineProvenance.js +76 -0
  206. package/dist/view/index.d.ts +14 -0
  207. package/dist/view/index.d.ts.map +1 -0
  208. package/dist/view/index.js +107 -0
  209. package/dist/view/membranes.d.ts +14 -0
  210. package/dist/view/membranes.d.ts.map +1 -0
  211. package/dist/view/membranes.js +127 -0
  212. package/dist/view/runDetect.d.ts +2 -0
  213. package/dist/view/runDetect.d.ts.map +1 -0
  214. package/dist/view/runDetect.js +47 -0
  215. package/dist/view/runRawArtifacts.d.ts +48 -0
  216. package/dist/view/runRawArtifacts.d.ts.map +1 -0
  217. package/dist/view/runRawArtifacts.js +143 -0
  218. package/dist/view/runTimeline.d.ts +24 -0
  219. package/dist/view/runTimeline.d.ts.map +1 -0
  220. package/dist/view/runTimeline.js +204 -0
  221. package/dist/view/runTimelineRecords.d.ts +42 -0
  222. package/dist/view/runTimelineRecords.d.ts.map +1 -0
  223. package/dist/view/runTimelineRecords.js +244 -0
  224. package/dist/view/runTimelineRefs.d.ts +27 -0
  225. package/dist/view/runTimelineRefs.d.ts.map +1 -0
  226. package/dist/view/runTimelineRefs.js +31 -0
  227. package/dist/view/runTimelineTypes.d.ts +99 -0
  228. package/dist/view/runTimelineTypes.d.ts.map +1 -0
  229. package/dist/view/runTimelineTypes.js +1 -0
  230. package/dist/view/runViewModel.d.ts +21 -0
  231. package/dist/view/runViewModel.d.ts.map +1 -0
  232. package/dist/view/runViewModel.js +66 -0
  233. package/dist/view/runViewModelCompute.d.ts +24 -0
  234. package/dist/view/runViewModelCompute.d.ts.map +1 -0
  235. package/dist/view/runViewModelCompute.js +157 -0
  236. package/dist/view/runViewModelTypes.d.ts +177 -0
  237. package/dist/view/runViewModelTypes.d.ts.map +1 -0
  238. package/dist/view/runViewModelTypes.js +1 -0
  239. package/dist/view/runWorldTrace.d.ts +97 -0
  240. package/dist/view/runWorldTrace.d.ts.map +1 -0
  241. package/dist/view/runWorldTrace.js +88 -0
  242. package/dist/view/server.d.ts +12 -0
  243. package/dist/view/server.d.ts.map +1 -0
  244. package/dist/view/server.js +306 -0
  245. package/package.json +65 -0
  246. package/web/dist/assets/index-CJHLjfsG.js +49 -0
  247. package/web/dist/assets/index-DJOsPsU8.css +1 -0
  248. package/web/dist/index.html +13 -0
  249. package/web/dist/models/AnimatedMushnub.glb +0 -0
@@ -0,0 +1,199 @@
1
+ import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { exportMoltnetTranscript, roomTranscriptTargetFromScope } from "../moltnet/transcript-export.js";
4
+ import { sendWorldEventToMoltnet } from "../moltnet/world-participant.js";
5
+ import { composeRunManifest, sha256HexOfBuffer } from "./composeRunManifest.js";
6
+ import { allMembersConnected, getMoltnetRoom, listMoltnetAgents, listMoltnetRoomMessages, moltnetHealthy } from "./moltnetRoomClient.js";
7
+ import { defaultSleep, pollUntilReady } from "./poll.js";
8
+ import { runSpawnfileArtifactsExport, runSpawnfileDown, runSpawnfileUp } from "./spawnfileCli.js";
9
+ const DEFAULT_SEED_NETWORK = "psyche-floor";
10
+ const DEFAULT_SEED_ROOM = "commons";
11
+ const DEFAULT_SEED_TARGET = "luna-representative";
12
+ const DEFAULT_SEED_TEXT = "@luna-representative The floor asks: how should Luna meet the coming change?";
13
+ const DEFAULT_SYNTHESIS_MARKER = "council has spoken";
14
+ const DEFAULT_TIMEOUT_MS = 300_000;
15
+ const DEFAULT_POLL_INTERVAL_MS = 2_000;
16
+ const asString = (value) => (typeof value === "string" ? value : undefined);
17
+ /**
18
+ * Reads `spawnfile-report.json` and extracts the managed networks: id,
19
+ * base url (host-published 1:1), and per-room membership. This is the driver's
20
+ * ONE structured read of the compile report — everything else is HTTP polling.
21
+ */
22
+ const readNetworkPlans = async (compiledOutputDirectory) => {
23
+ const raw = await readFile(path.join(compiledOutputDirectory, "spawnfile-report.json"), "utf8");
24
+ const report = JSON.parse(raw);
25
+ const plans = Array.isArray(report.container?.moltnet?.server_plans)
26
+ ? report.container.moltnet.server_plans
27
+ : [];
28
+ const networks = [];
29
+ for (const plan of plans) {
30
+ if (asString(plan.mode) !== "managed")
31
+ continue;
32
+ const networkId = asString(plan.network_id);
33
+ const baseUrl = asString(plan.base_url);
34
+ if (!networkId || !baseUrl)
35
+ continue;
36
+ const rooms = Array.isArray(plan.rooms) ? plan.rooms : [];
37
+ networks.push({
38
+ networkId,
39
+ baseUrl,
40
+ rooms: rooms
41
+ .map((room) => ({
42
+ roomId: asString(room.id),
43
+ members: Array.isArray(room.members) ? room.members.filter((m) => typeof m === "string") : []
44
+ }))
45
+ .filter((room) => room.roomId !== undefined)
46
+ });
47
+ }
48
+ return networks;
49
+ };
50
+ /** Waits until EVERY managed network is healthy and every declared room member has a live, connected bridge — the precondition for a single seed to reach the whole psyche. */
51
+ const waitForPsycheReady = async (networks, poll) => {
52
+ for (const network of networks) {
53
+ await pollUntilReady(`moltnet ${network.networkId} health`, poll, async () => (await moltnetHealthy(network.baseUrl)) ? true : null);
54
+ for (const room of network.rooms) {
55
+ await pollUntilReady(`${network.networkId}:${room.roomId} membership`, poll, async () => {
56
+ const summary = await getMoltnetRoom(network.baseUrl, room.roomId);
57
+ const expected = [...room.members].sort();
58
+ const actual = [...summary.members].sort();
59
+ return expected.length === actual.length && expected.every((id, i) => actual[i] === id) ? summary : null;
60
+ });
61
+ await pollUntilReady(`${network.networkId}:${room.roomId} bridges`, poll, async () => {
62
+ const agents = await listMoltnetAgents(network.baseUrl);
63
+ return allMembersConnected(agents, room.roomId, room.members) ? agents : null;
64
+ });
65
+ }
66
+ }
67
+ };
68
+ const buildSeedEvent = (networkId, roomId, targetAgentId, text) => ({
69
+ event_id: `seed-${Date.now()}`,
70
+ kind: "world.message",
71
+ target: `room:${networkId}:${roomId}`,
72
+ sim_time: 0,
73
+ provenance: "external",
74
+ actor: `operator:${targetAgentId}`,
75
+ scope: "global",
76
+ payload: { content: text }
77
+ });
78
+ const messageText = (message) => message.parts.find((part) => part.kind === "text")?.text ?? "";
79
+ /** Writes one per-network room transcript to `raw/moltnet/<networkId>/transcript.json` and returns its manifest artifact entry. */
80
+ const writeNetworkTranscript = async (runDir, network) => {
81
+ const transcript = await exportMoltnetTranscript({
82
+ baseUrl: network.baseUrl,
83
+ targets: network.rooms.map((room) => roomTranscriptTargetFromScope(`room:${network.networkId}:${room.roomId}`))
84
+ });
85
+ const relativePath = path.posix.join("raw", "moltnet", network.networkId, "transcript.json");
86
+ const absolutePath = path.join(runDir, "raw", "moltnet", network.networkId, "transcript.json");
87
+ await mkdir(path.dirname(absolutePath), { recursive: true });
88
+ const content = `${JSON.stringify(transcript, null, 2)}\n`;
89
+ await writeFile(absolutePath, content, "utf8");
90
+ return { path: relativePath, sha256: sha256HexOfBuffer(Buffer.from(content, "utf8")) };
91
+ };
92
+ /** Copies the compile report into the run-dir (the viewer's named feed #1, source of the derived membranes) and returns its artifact entry. */
93
+ const copyCompileReport = async (runDir, compiledOutputDirectory) => {
94
+ const source = path.join(compiledOutputDirectory, "spawnfile-report.json");
95
+ const destination = path.join(runDir, "spawnfile-report.json");
96
+ await copyFile(source, destination);
97
+ const content = await readFile(destination);
98
+ return { path: "spawnfile-report.json", sha256: sha256HexOfBuffer(content) };
99
+ };
100
+ /**
101
+ * Runs the composed jungian psyche end to end: up -> poll every network ready
102
+ * -> seed the floor ONCE -> wait for the representative's synthesis to reappear
103
+ * on the floor (proof the interior council deliberated and answered across the
104
+ * membrane) -> export per-network transcripts + causal artifacts + the compile
105
+ * report -> down -> write `manifest.json` (with `world.rooms[]`) LAST.
106
+ */
107
+ export const runComposedJungianSim = async (options) => {
108
+ const cli = { spawnfileBin: options.spawnfileBin };
109
+ const seedNetworkId = options.seedNetworkId ?? DEFAULT_SEED_NETWORK;
110
+ const seedRoomId = options.seedRoomId ?? DEFAULT_SEED_ROOM;
111
+ const seedTargetAgentId = options.seedTargetAgentId ?? DEFAULT_SEED_TARGET;
112
+ const seedMessageText = options.seedMessageText ?? DEFAULT_SEED_TEXT;
113
+ const synthesisMarker = options.synthesisMarker ?? DEFAULT_SYNTHESIS_MARKER;
114
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
115
+ const poll = {
116
+ intervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
117
+ sleep: defaultSleep,
118
+ timeoutMs
119
+ };
120
+ const now = options.now ?? (() => new Date());
121
+ const upReceipt = await runSpawnfileUp(cli, {
122
+ orgPath: options.orgPath,
123
+ containerName: options.containerName,
124
+ deploymentName: options.deploymentName,
125
+ compiledOutputDirectory: options.compiledOutputDirectory
126
+ });
127
+ if (!upReceipt.run_id) {
128
+ throw new Error("spawnfile up-receipt had no run_id; cannot assemble a run directory");
129
+ }
130
+ const runId = upReceipt.run_id;
131
+ const runDir = path.join(options.runsRootDirectory, runId);
132
+ const networks = await readNetworkPlans(options.compiledOutputDirectory);
133
+ const seedNetwork = networks.find((network) => network.networkId === seedNetworkId);
134
+ if (!seedNetwork) {
135
+ throw new Error(`seed network ${seedNetworkId} not found in compile report`);
136
+ }
137
+ let synthesis;
138
+ try {
139
+ await waitForPsycheReady(networks, poll);
140
+ // The ONE write this driver performs — everything else is a read.
141
+ await sendWorldEventToMoltnet(buildSeedEvent(seedNetworkId, seedRoomId, seedTargetAgentId, seedMessageText), {
142
+ baseUrl: seedNetwork.baseUrl,
143
+ networkId: seedNetworkId
144
+ });
145
+ // Completion = the representative's floor-facing synthesis appears. It can
146
+ // only exist AFTER the representative was woken by the council's conclusion
147
+ // inside the inner network, which itself required the representative to have
148
+ // first crossed the membrane inward — so this single condition proves the
149
+ // whole wake-across-membranes chain closed.
150
+ const synthesisMessage = await pollUntilReady("representative synthesis on the floor", poll, async () => {
151
+ const messages = await listMoltnetRoomMessages(seedNetwork.baseUrl, seedRoomId, 200);
152
+ return (messages.find((message) => message.from.id === seedTargetAgentId && messageText(message).includes(synthesisMarker)) ?? null);
153
+ });
154
+ synthesis = messageText(synthesisMessage);
155
+ const exportResult = await runSpawnfileArtifactsExport(cli, {
156
+ orgPath: options.orgPath,
157
+ deploymentName: options.deploymentName,
158
+ compiledOutputDirectory: options.compiledOutputDirectory,
159
+ destinationDirectory: runDir
160
+ });
161
+ // Per-network transcripts + compile report, fetched/copied while the
162
+ // deployment is still live (transcripts need the running Moltnet HTTP API).
163
+ const transcriptArtifacts = await Promise.all(networks.map((network) => writeNetworkTranscript(runDir, network)));
164
+ const reportArtifact = await copyCompileReport(runDir, options.compiledOutputDirectory);
165
+ await runSpawnfileDown(cli, {
166
+ orgPath: options.orgPath,
167
+ deploymentName: options.deploymentName,
168
+ compiledOutputDirectory: options.compiledOutputDirectory,
169
+ removeVolumes: options.removeVolumesOnDown ?? true
170
+ });
171
+ const world = {
172
+ rooms: networks.flatMap((network) => network.rooms.map((room) => ({
173
+ network_id: network.networkId,
174
+ room_id: room.roomId,
175
+ members: [...room.members].sort()
176
+ })))
177
+ };
178
+ const manifest = composeRunManifest({
179
+ runId,
180
+ createdAt: now().toISOString(),
181
+ fingerprint: upReceipt.fingerprint,
182
+ engine: [...new Set((upReceipt.engines ?? []).map((entry) => entry.engine))].sort().join("+") || "unknown",
183
+ world,
184
+ exportedArtifacts: exportResult.index.files,
185
+ extraArtifacts: [...transcriptArtifacts, reportArtifact]
186
+ });
187
+ await writeFile(path.join(runDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
188
+ }
189
+ catch (error) {
190
+ await runSpawnfileDown(cli, {
191
+ orgPath: options.orgPath,
192
+ deploymentName: options.deploymentName,
193
+ compiledOutputDirectory: options.compiledOutputDirectory,
194
+ removeVolumes: options.removeVolumesOnDown ?? true
195
+ }).catch(() => undefined);
196
+ throw error;
197
+ }
198
+ return { runDir, runId, synthesis };
199
+ };
@@ -0,0 +1,53 @@
1
+ import { type ConversationExchangeResult } from "./exchangeWait.js";
2
+ /**
3
+ * The composed-run driver (Decision 21 / Piece 5 design's "MINIMAL
4
+ * composed-run driver in simfile"): shells the three documented `spawnfile
5
+ * ... --json` lifecycle subcommands, seeds the room exactly ONCE, polls
6
+ * read-only, and assembles a sealed `runs/<id>/` directory `simfile observe`
7
+ * can reconcile. Zero Docker, zero runtime auth, zero Spawnfile TS imports —
8
+ * this package's charter. Any wait that would otherwise need a coax/retry is
9
+ * a Phase-B platform defect to FILE, never something this driver papers over
10
+ * by re-sending a mention.
11
+ */
12
+ export interface RunComposedOfficeSimOptions {
13
+ /** Path to Spawnfile's built CLI entrypoint (e.g. `dist/cli/index.js`). */
14
+ spawnfileBin: string;
15
+ /** Path to the composed office-sim org (`fixtures/sims/office-sim/org`). */
16
+ orgPath: string;
17
+ containerName: string;
18
+ deploymentName: string;
19
+ /** Scratch directory for spawnfile's own compile output + deployment record — separate from the sealed run-dir. */
20
+ compiledOutputDirectory: string;
21
+ /** Parent directory under which `runs/<run_id>/` is assembled. */
22
+ runsRootDirectory: string;
23
+ networkId?: string;
24
+ roomId?: string;
25
+ agentIds?: readonly string[];
26
+ seedTargetAgentId?: string;
27
+ seedMessageText?: string;
28
+ targetTurns?: number;
29
+ quietGraceMs?: number;
30
+ timeoutMs?: number;
31
+ pollIntervalMs?: number;
32
+ /** Passed straight to `spawnfile down --volumes`; defaults true so a
33
+ * composed run leaves nothing behind once its artifacts are safely
34
+ * exported into the run-dir. */
35
+ removeVolumesOnDown?: boolean;
36
+ now?: () => Date;
37
+ }
38
+ export interface RunComposedOfficeSimResult {
39
+ runDir: string;
40
+ runId: string;
41
+ exchange: ConversationExchangeResult;
42
+ }
43
+ /**
44
+ * Runs the composed office-sim end to end: `spawnfile up` (detached) ->
45
+ * poll-read-only for moltnet health/room/bridge readiness -> seed the room
46
+ * ONCE -> poll-read-only for the exchange to conclude -> `spawnfile
47
+ * artifacts export` -> fetch the moltnet transcript (while the container is
48
+ * still live — export/transcript both need the running deployment) ->
49
+ * `spawnfile down` -> compose and write `manifest.json` LAST. Returns the
50
+ * sealed run-dir path for `simfile observe` to reconcile.
51
+ */
52
+ export declare const runComposedOfficeSim: (options: RunComposedOfficeSimOptions) => Promise<RunComposedOfficeSimResult>;
53
+ //# sourceMappingURL=composedOfficeSimDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composedOfficeSimDriver.d.ts","sourceRoot":"","sources":["../../src/sims/composedOfficeSimDriver.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIL,KAAK,0BAA0B,EAChC,MAAM,mBAAmB,CAAC;AAgB3B;;;;;;;;;GASG;AACH,MAAM,WAAW,2BAA2B;IAC1C,2EAA2E;IAC3E,YAAY,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,mHAAmH;IACnH,uBAAuB,EAAE,MAAM,CAAC;IAChC,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;oCAEgC;IAChC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,0BAA0B,CAAC;CACtC;AAsED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAS,2BAA2B,KACnC,OAAO,CAAC,0BAA0B,CAwGpC,CAAC"}
@@ -0,0 +1,162 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { exportMoltnetTranscript, roomTranscriptTargetFromScope } from "../moltnet/transcript-export.js";
4
+ import { sendWorldEventToMoltnet } from "../moltnet/world-participant.js";
5
+ import { composeRunManifest, sha256HexOfBuffer } from "./composeRunManifest.js";
6
+ import { DEFAULT_QUIET_GRACE_MS, resolveTargetTurns, waitForConversationExchange } from "./exchangeWait.js";
7
+ import { allMembersConnected, getMoltnetRoom, listMoltnetAgents, listMoltnetRoomMessages, moltnetHealthy } from "./moltnetRoomClient.js";
8
+ import { defaultSleep, pollUntilReady } from "./poll.js";
9
+ import { runSpawnfileArtifactsExport, runSpawnfileDown, runSpawnfileUp } from "./spawnfileCli.js";
10
+ const DEFAULT_NETWORK_ID = "office_lab";
11
+ const DEFAULT_ROOM_ID = "office-room";
12
+ const DEFAULT_AGENT_IDS = ["eleanor", "sam"];
13
+ const DEFAULT_SEED_TARGET = "eleanor";
14
+ const DEFAULT_SEED_TEXT = "@eleanor We need to finalize the office pilot rollout with Sam: propose the pilot office and a target date for the file migration, and get Sam's agreement.";
15
+ const DEFAULT_TIMEOUT_MS = 240_000;
16
+ const DEFAULT_POLL_INTERVAL_MS = 2_000;
17
+ /** Collapses `up-receipt.engines[]` to one disclosure string for the run
18
+ * manifest: every agent's declared engine when they all agree (true for this
19
+ * composed office-sim, where both agents run `scripted`), else a sorted
20
+ * comma-joined list so a mixed-engine run is still visibly disclosed. */
21
+ const collapseEngineDisclosure = (engines) => {
22
+ const distinct = [...new Set((engines ?? []).map((entry) => entry.engine))].sort();
23
+ if (distinct.length === 0)
24
+ return "unknown";
25
+ return distinct.join("+");
26
+ };
27
+ const waitForMoltnetReadyForSeed = async (baseUrl, roomId, agentIds, pollOptions) => {
28
+ await pollUntilReady("moltnet health", pollOptions, async () => ((await moltnetHealthy(baseUrl)) ? true : null));
29
+ await pollUntilReady(`moltnet room ${roomId} membership`, pollOptions, async () => {
30
+ const room = await getMoltnetRoom(baseUrl, roomId);
31
+ const expected = [...agentIds].sort();
32
+ const actual = [...room.members].sort();
33
+ return expected.length === actual.length && expected.every((id, index) => actual[index] === id) ? room : null;
34
+ });
35
+ await pollUntilReady(`moltnet bridge attachments for ${roomId}`, pollOptions, async () => {
36
+ const agents = await listMoltnetAgents(baseUrl);
37
+ return allMembersConnected(agents, roomId, agentIds) ? agents : null;
38
+ });
39
+ };
40
+ /** Builds the single seed `world.message` event this driver ever sends —
41
+ * the ONE write in the whole flow. Everything else is a GET. */
42
+ const buildSeedEvent = (networkId, roomId, seedTargetAgentId, seedMessageText) => ({
43
+ event_id: `seed-${Date.now()}`,
44
+ kind: "world.message",
45
+ target: `room:${networkId}:${roomId}`,
46
+ sim_time: 0,
47
+ provenance: "external",
48
+ actor: `operator:${seedTargetAgentId}`,
49
+ scope: "global",
50
+ payload: { content: seedMessageText }
51
+ });
52
+ const writeTranscriptArtifact = async (runDir, transcript) => {
53
+ const relativePath = path.posix.join("raw", "moltnet", "transcript.json");
54
+ const absolutePath = path.join(runDir, "raw", "moltnet", "transcript.json");
55
+ await mkdir(path.dirname(absolutePath), { recursive: true });
56
+ const content = `${JSON.stringify(transcript, null, 2)}\n`;
57
+ await writeFile(absolutePath, content, "utf8");
58
+ return { path: relativePath, sha256: sha256HexOfBuffer(Buffer.from(content, "utf8")) };
59
+ };
60
+ /**
61
+ * Runs the composed office-sim end to end: `spawnfile up` (detached) ->
62
+ * poll-read-only for moltnet health/room/bridge readiness -> seed the room
63
+ * ONCE -> poll-read-only for the exchange to conclude -> `spawnfile
64
+ * artifacts export` -> fetch the moltnet transcript (while the container is
65
+ * still live — export/transcript both need the running deployment) ->
66
+ * `spawnfile down` -> compose and write `manifest.json` LAST. Returns the
67
+ * sealed run-dir path for `simfile observe` to reconcile.
68
+ */
69
+ export const runComposedOfficeSim = async (options) => {
70
+ const cliContext = { spawnfileBin: options.spawnfileBin };
71
+ const networkId = options.networkId ?? DEFAULT_NETWORK_ID;
72
+ const roomId = options.roomId ?? DEFAULT_ROOM_ID;
73
+ const agentIds = options.agentIds ?? DEFAULT_AGENT_IDS;
74
+ const seedTargetAgentId = options.seedTargetAgentId ?? DEFAULT_SEED_TARGET;
75
+ const seedMessageText = options.seedMessageText ?? DEFAULT_SEED_TEXT;
76
+ const targetTurns = resolveTargetTurns(options.targetTurns);
77
+ const quietGraceMs = options.quietGraceMs ?? DEFAULT_QUIET_GRACE_MS;
78
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
79
+ const pollOptions = {
80
+ intervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
81
+ sleep: defaultSleep,
82
+ timeoutMs
83
+ };
84
+ const now = options.now ?? (() => new Date());
85
+ const upReceipt = await runSpawnfileUp(cliContext, {
86
+ orgPath: options.orgPath,
87
+ containerName: options.containerName,
88
+ deploymentName: options.deploymentName,
89
+ compiledOutputDirectory: options.compiledOutputDirectory
90
+ });
91
+ if (!upReceipt.run_id) {
92
+ throw new Error("spawnfile up-receipt had no run_id; cannot assemble a run directory");
93
+ }
94
+ const baseUrl = upReceipt.readiness.moltnet_base_url;
95
+ if (!baseUrl) {
96
+ throw new Error("spawnfile up-receipt had no moltnet_base_url; is human_ingress enabled on the network?");
97
+ }
98
+ const runId = upReceipt.run_id;
99
+ const runDir = path.join(options.runsRootDirectory, runId);
100
+ let exchange;
101
+ try {
102
+ await waitForMoltnetReadyForSeed(baseUrl, roomId, agentIds, pollOptions);
103
+ const baseline = await listMoltnetRoomMessages(baseUrl, roomId, 200);
104
+ const baselineIds = new Set(baseline.map((message) => message.id));
105
+ // The ONE write this driver performs — everything else is a read.
106
+ await sendWorldEventToMoltnet(buildSeedEvent(networkId, roomId, seedTargetAgentId, seedMessageText), {
107
+ baseUrl,
108
+ networkId
109
+ });
110
+ exchange = await waitForConversationExchange(baseUrl, roomId, baselineIds, {
111
+ agentIds,
112
+ intervalMs: pollOptions.intervalMs,
113
+ quietGraceMs,
114
+ sleep: pollOptions.sleep,
115
+ targetTurns,
116
+ timeoutMs
117
+ });
118
+ // Export before teardown, and fetch the transcript while the container
119
+ // (and therefore moltnet's HTTP API) is still live — both need the
120
+ // running deployment, so both happen before `spawnfile down`.
121
+ const exportResult = await runSpawnfileArtifactsExport(cliContext, {
122
+ orgPath: options.orgPath,
123
+ deploymentName: options.deploymentName,
124
+ compiledOutputDirectory: options.compiledOutputDirectory,
125
+ destinationDirectory: runDir
126
+ });
127
+ const transcript = await exportMoltnetTranscript({
128
+ baseUrl,
129
+ targets: [roomTranscriptTargetFromScope(`room:${networkId}:${roomId}`)]
130
+ });
131
+ const transcriptArtifact = await writeTranscriptArtifact(runDir, transcript);
132
+ await runSpawnfileDown(cliContext, {
133
+ orgPath: options.orgPath,
134
+ deploymentName: options.deploymentName,
135
+ compiledOutputDirectory: options.compiledOutputDirectory,
136
+ removeVolumes: options.removeVolumesOnDown ?? true
137
+ });
138
+ const world = { network_id: networkId, room_id: roomId, members: [...agentIds].sort() };
139
+ const manifest = composeRunManifest({
140
+ runId,
141
+ createdAt: now().toISOString(),
142
+ fingerprint: upReceipt.fingerprint,
143
+ engine: collapseEngineDisclosure(upReceipt.engines),
144
+ world,
145
+ exportedArtifacts: exportResult.index.files,
146
+ extraArtifacts: [transcriptArtifact]
147
+ });
148
+ await writeFile(path.join(runDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
149
+ }
150
+ catch (error) {
151
+ // Best-effort teardown on failure so a broken run never leaks a live
152
+ // container/volumes; the original error is what's reported.
153
+ await runSpawnfileDown(cliContext, {
154
+ orgPath: options.orgPath,
155
+ deploymentName: options.deploymentName,
156
+ compiledOutputDirectory: options.compiledOutputDirectory,
157
+ removeVolumes: options.removeVolumesOnDown ?? true
158
+ }).catch(() => undefined);
159
+ throw error;
160
+ }
161
+ return { runDir, runId, exchange };
162
+ };
@@ -0,0 +1,56 @@
1
+ import { type MoltnetRoomMessage } from "./moltnetRoomClient.js";
2
+ /**
3
+ * Pure stop/continue decision + polling loop for a bounded multi-turn Moltnet
4
+ * exchange, ported from `src/e2e/moltnetExchangeWait.ts` (Piece 5's design
5
+ * calls that file out as "pure, scenario-agnostic" — the logic to reuse, not
6
+ * a module to import: this package's charter forbids depending on Spawnfile
7
+ * internals, so the decision function and polling loop are reproduced here
8
+ * verbatim rather than referenced across the package boundary).
9
+ *
10
+ * Derail guard (Decision 21, Piece 5 design): this ONLY polls and decides
11
+ * "done" — it never re-sends a mention or otherwise coaxes a reply. If a
12
+ * real run never completes an exchange, that is a platform gap to FILE
13
+ * against the owning layer (Phase B), never something this loop works around
14
+ * by nudging the room again.
15
+ */
16
+ export declare const MIN_TARGET_TURNS = 3;
17
+ export declare const DEFAULT_TARGET_TURNS = 3;
18
+ export declare const DEFAULT_QUIET_GRACE_MS = 35000;
19
+ export type ExchangeEndReason = "turn-cap" | "quiet-timeout" | "timeout";
20
+ export declare const resolveTargetTurns: (targetTurns?: number) => number;
21
+ export interface ExchangeCompletionInput {
22
+ agentTurnCount: number;
23
+ elapsedSinceLastAgentMessageMs: number;
24
+ elapsedSinceStartMs: number;
25
+ quietGraceMs: number;
26
+ targetTurns: number;
27
+ timeoutMs: number;
28
+ }
29
+ export interface ExchangeCompletionResult {
30
+ done: boolean;
31
+ reason: ExchangeEndReason | null;
32
+ }
33
+ /** Same precedence as the ported original: turn-cap, then quiet-timeout
34
+ * (only once at least one agent turn has landed), then the overall timeout. */
35
+ export declare const evaluateExchangeCompletion: (input: ExchangeCompletionInput) => ExchangeCompletionResult;
36
+ export interface WaitForConversationExchangeOptions {
37
+ agentIds: readonly string[];
38
+ intervalMs: number;
39
+ /** Injectable for tests; defaults to a real `listMoltnetRoomMessages` GET. */
40
+ listMessages?: (baseUrl: string, roomId: string, limit: number) => Promise<MoltnetRoomMessage[]>;
41
+ now?: () => number;
42
+ quietGraceMs: number;
43
+ sleep: (delayMs: number) => Promise<void>;
44
+ targetTurns: number;
45
+ timeoutMs: number;
46
+ }
47
+ export interface ConversationExchangeResult {
48
+ agentTurns: MoltnetRoomMessage[];
49
+ endedReason: ExchangeEndReason;
50
+ transcript: MoltnetRoomMessage[];
51
+ }
52
+ /** Only ever GETs the room's message list and re-evaluates
53
+ * `evaluateExchangeCompletion` — poll-read-only, seed-once discipline. The
54
+ * caller seeds the room exactly once, before calling this. */
55
+ export declare const waitForConversationExchange: (baseUrl: string, roomId: string, baselineIds: ReadonlySet<string>, options: WaitForConversationExchangeOptions) => Promise<ConversationExchangeResult>;
56
+ //# sourceMappingURL=exchangeWait.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exchangeWait.d.ts","sourceRoot":"","sources":["../../src/sims/exchangeWait.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE1F;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,oBAAoB,IAAI,CAAC;AACtC,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,eAAe,GAAG,SAAS,CAAC;AAEzE,eAAO,MAAM,kBAAkB,GAAI,cAAc,MAAM,KAAG,MACO,CAAC;AAElE,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,8BAA8B,EAAE,MAAM,CAAC;IACvC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAClC;AAED;+EAC+E;AAC/E,eAAO,MAAM,0BAA0B,GAAI,OAAO,uBAAuB,KAAG,wBAO3E,CAAC;AAEF,MAAM,WAAW,kCAAkC;IACjD,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACjG,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,WAAW,EAAE,iBAAiB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,EAAE,CAAC;CAClC;AAED;;8DAE8D;AAC9D,eAAO,MAAM,2BAA2B,GACtC,SAAS,MAAM,EACf,QAAQ,MAAM,EACd,aAAa,WAAW,CAAC,MAAM,CAAC,EAChC,SAAS,kCAAkC,KAC1C,OAAO,CAAC,0BAA0B,CAgCpC,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { listMoltnetRoomMessages } from "./moltnetRoomClient.js";
2
+ /**
3
+ * Pure stop/continue decision + polling loop for a bounded multi-turn Moltnet
4
+ * exchange, ported from `src/e2e/moltnetExchangeWait.ts` (Piece 5's design
5
+ * calls that file out as "pure, scenario-agnostic" — the logic to reuse, not
6
+ * a module to import: this package's charter forbids depending on Spawnfile
7
+ * internals, so the decision function and polling loop are reproduced here
8
+ * verbatim rather than referenced across the package boundary).
9
+ *
10
+ * Derail guard (Decision 21, Piece 5 design): this ONLY polls and decides
11
+ * "done" — it never re-sends a mention or otherwise coaxes a reply. If a
12
+ * real run never completes an exchange, that is a platform gap to FILE
13
+ * against the owning layer (Phase B), never something this loop works around
14
+ * by nudging the room again.
15
+ */
16
+ export const MIN_TARGET_TURNS = 3;
17
+ export const DEFAULT_TARGET_TURNS = 3;
18
+ export const DEFAULT_QUIET_GRACE_MS = 35_000;
19
+ export const resolveTargetTurns = (targetTurns) => Math.max(MIN_TARGET_TURNS, targetTurns ?? DEFAULT_TARGET_TURNS);
20
+ /** Same precedence as the ported original: turn-cap, then quiet-timeout
21
+ * (only once at least one agent turn has landed), then the overall timeout. */
22
+ export const evaluateExchangeCompletion = (input) => {
23
+ if (input.agentTurnCount >= input.targetTurns)
24
+ return { done: true, reason: "turn-cap" };
25
+ if (input.agentTurnCount > 0 && input.elapsedSinceLastAgentMessageMs >= input.quietGraceMs) {
26
+ return { done: true, reason: "quiet-timeout" };
27
+ }
28
+ if (input.elapsedSinceStartMs >= input.timeoutMs)
29
+ return { done: true, reason: "timeout" };
30
+ return { done: false, reason: null };
31
+ };
32
+ /** Only ever GETs the room's message list and re-evaluates
33
+ * `evaluateExchangeCompletion` — poll-read-only, seed-once discipline. The
34
+ * caller seeds the room exactly once, before calling this. */
35
+ export const waitForConversationExchange = async (baseUrl, roomId, baselineIds, options) => {
36
+ const now = options.now ?? (() => Date.now());
37
+ const listMessages = options.listMessages ?? listMoltnetRoomMessages;
38
+ const startedAt = now();
39
+ const seenIds = new Set(baselineIds);
40
+ const agentTurns = [];
41
+ let lastAgentMessageAt = startedAt;
42
+ let transcript = [];
43
+ for (;;) {
44
+ transcript = await listMessages(baseUrl, roomId, 200);
45
+ for (const message of transcript) {
46
+ if (seenIds.has(message.id))
47
+ continue;
48
+ seenIds.add(message.id);
49
+ if (options.agentIds.includes(message.from.id)) {
50
+ agentTurns.push(message);
51
+ lastAgentMessageAt = now();
52
+ }
53
+ }
54
+ const evaluation = evaluateExchangeCompletion({
55
+ agentTurnCount: agentTurns.length,
56
+ elapsedSinceLastAgentMessageMs: now() - lastAgentMessageAt,
57
+ elapsedSinceStartMs: now() - startedAt,
58
+ quietGraceMs: options.quietGraceMs,
59
+ targetTurns: options.targetTurns,
60
+ timeoutMs: options.timeoutMs
61
+ });
62
+ if (evaluation.done)
63
+ return { agentTurns, endedReason: evaluation.reason, transcript };
64
+ await options.sleep(options.intervalMs);
65
+ }
66
+ };
@@ -0,0 +1,14 @@
1
+ export * from "./composedJungianSimDriver.js";
2
+ export * from "./composedOfficeSimDriver.js";
3
+ export * from "./composeRunManifest.js";
4
+ export * from "./exchangeWait.js";
5
+ export * from "./moltnetRoomClient.js";
6
+ export * from "./poll.js";
7
+ export * from "./spawnfileCli.js";
8
+ export * from "./spawnfileReceipts.js";
9
+ export * from "./worldDrivenOfficeSimDriver.js";
10
+ export * from "./worldLedgerWriter.js";
11
+ export * from "./worldSeedLint.js";
12
+ export * from "./worldTickIngest.js";
13
+ export * from "./worldTickLoop.js";
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sims/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,13 @@
1
+ export * from "./composedJungianSimDriver.js";
2
+ export * from "./composedOfficeSimDriver.js";
3
+ export * from "./composeRunManifest.js";
4
+ export * from "./exchangeWait.js";
5
+ export * from "./moltnetRoomClient.js";
6
+ export * from "./poll.js";
7
+ export * from "./spawnfileCli.js";
8
+ export * from "./spawnfileReceipts.js";
9
+ export * from "./worldDrivenOfficeSimDriver.js";
10
+ export * from "./worldLedgerWriter.js";
11
+ export * from "./worldSeedLint.js";
12
+ export * from "./worldTickIngest.js";
13
+ export * from "./worldTickLoop.js";
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Read-only Moltnet HTTP polling helpers for the composed-run driver —
3
+ * `/healthz`, `/v1/rooms/:id`, `/v1/agents`, `/v1/rooms/:id/messages` are
4
+ * Moltnet's own public wire API (documented, not a Spawnfile internal), the
5
+ * same endpoints `src/e2e/moltnetE2ESupport.ts`'s `createMoltnetHttpClient`
6
+ * speaks — reimplemented locally here (never imported from Spawnfile's
7
+ * `src/e2e`) so this package's charter ("Do not import Spawnfile internals")
8
+ * holds. Every function here is a plain GET: seeding (the one write this
9
+ * driver ever performs) goes through `world-participant.ts` instead.
10
+ */
11
+ export interface MoltnetRoomSummary {
12
+ id: string;
13
+ members: string[];
14
+ }
15
+ export interface MoltnetAgentSummary {
16
+ id: string;
17
+ rooms: string[];
18
+ connected: boolean;
19
+ }
20
+ export interface MoltnetRoomMessage {
21
+ id: string;
22
+ from: {
23
+ id: string;
24
+ type?: string;
25
+ };
26
+ parts: {
27
+ kind: string;
28
+ text?: string;
29
+ }[];
30
+ created_at?: string;
31
+ }
32
+ export interface MoltnetRoomClientOptions {
33
+ fetchFn?: typeof fetch;
34
+ timeoutMs?: number;
35
+ }
36
+ /** Read-only health probe; never throws — a not-yet-ready server is a
37
+ * negative poll result, not an error. */
38
+ export declare const moltnetHealthy: (baseUrl: string, options?: MoltnetRoomClientOptions) => Promise<boolean>;
39
+ export declare const getMoltnetRoom: (baseUrl: string, roomId: string, options?: MoltnetRoomClientOptions) => Promise<MoltnetRoomSummary>;
40
+ export declare const listMoltnetAgents: (baseUrl: string, options?: MoltnetRoomClientOptions) => Promise<MoltnetAgentSummary[]>;
41
+ export declare const listMoltnetRoomMessages: (baseUrl: string, roomId: string, limit: number, options?: MoltnetRoomClientOptions) => Promise<MoltnetRoomMessage[]>;
42
+ /** True once every expected member both appears in room config membership
43
+ * AND has a live, connected bridge attachment — mirrors
44
+ * `moltnetE2ESupport.ts`'s `waitForAgents` gate (room membership alone can be
45
+ * static config, seeding before the bridge attaches is unrecoverable). */
46
+ export declare const allMembersConnected: (agents: readonly MoltnetAgentSummary[], roomId: string, expectedMembers: readonly string[]) => boolean;
47
+ //# sourceMappingURL=moltnetRoomClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moltnetRoomClient.d.ts","sourceRoot":"","sources":["../../src/sims/moltnetRoomClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiBD;yCACyC;AACzC,eAAO,MAAM,cAAc,GAAU,SAAS,MAAM,EAAE,UAAU,wBAAwB,KAAG,OAAO,CAAC,OAAO,CASzG,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,EACf,QAAQ,MAAM,EACd,UAAU,wBAAwB,KACjC,OAAO,CAAC,kBAAkB,CAA4E,CAAC;AAE1G,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,EACf,UAAU,wBAAwB,KACjC,OAAO,CAAC,mBAAmB,EAAE,CACqE,CAAC;AAEtG,eAAO,MAAM,uBAAuB,GAClC,SAAS,MAAM,EACf,QAAQ,MAAM,EACd,OAAO,MAAM,EACb,UAAU,wBAAwB,KACjC,OAAO,CAAC,kBAAkB,EAAE,CAMb,CAAC;AAEnB;;;0EAG0E;AAC1E,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,SAAS,mBAAmB,EAAE,EACtC,QAAQ,MAAM,EACd,iBAAiB,SAAS,MAAM,EAAE,KACjC,OAGA,CAAC"}