@holoscript/core 6.0.3 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +40 -40
  2. package/dist/codebase/index.d.ts +25 -0
  3. package/dist/compiler/index.d.ts +82 -0
  4. package/dist/constants.d.ts +4 -0
  5. package/dist/coordinators/index.d.ts +189 -0
  6. package/dist/entries/interop.d.ts +9 -0
  7. package/dist/entries/scripting.d.ts +7 -0
  8. package/dist/index.d.ts +520 -19
  9. package/dist/paper-0c-spike/index.d.ts +29 -0
  10. package/dist/reconstruction/index.d.ts +78 -0
  11. package/dist/self-improvement/index.d.ts +0 -48
  12. package/dist/storage/index.d.ts +24 -0
  13. package/dist/tools/index.d.ts +33 -0
  14. package/dist/traitDocs/traitDocs.d.ts +69 -0
  15. package/dist/traits/index.d.ts +61 -0
  16. package/package.json +102 -19
  17. package/dist/GLTFPipeline-3KLWWUQO.cjs +0 -34
  18. package/dist/GLTFPipeline-LYII2ZVQ.js +0 -13
  19. package/dist/HoloScriptPlusParser-BZR5DELQ.js +0 -692
  20. package/dist/HoloScriptPlusParser-SBMYDNXO.cjs +0 -697
  21. package/dist/USDZExporter-MHEHXZB4.cjs +0 -709
  22. package/dist/USDZExporter-YA55ZS35.js +0 -707
  23. package/dist/chunk-26TLYBFD.js +0 -309
  24. package/dist/chunk-27LWW5FU.cjs +0 -410
  25. package/dist/chunk-2UX5LRAP.cjs +0 -833
  26. package/dist/chunk-32TWR3HE.js +0 -70
  27. package/dist/chunk-3FXV5S25.cjs +0 -633
  28. package/dist/chunk-3FYZOR7S.cjs +0 -69
  29. package/dist/chunk-3KEU5QYY.cjs +0 -175
  30. package/dist/chunk-4FCZDTD5.js +0 -610
  31. package/dist/chunk-4KJ2R7VP.cjs +0 -507
  32. package/dist/chunk-4SMUJFHL.js +0 -1213
  33. package/dist/chunk-54FHG2MX.js +0 -655
  34. package/dist/chunk-56IF4NTA.js +0 -56
  35. package/dist/chunk-5GLCTO44.js +0 -161
  36. package/dist/chunk-5TOFIR6L.js +0 -407
  37. package/dist/chunk-65RFOWZI.js +0 -765
  38. package/dist/chunk-6OQBLABR.js +0 -5240
  39. package/dist/chunk-6WNCRE6F.js +0 -126
  40. package/dist/chunk-74YCHHTE.js +0 -1872
  41. package/dist/chunk-7H5UNJZD.cjs +0 -1973
  42. package/dist/chunk-7KPI4EKH.cjs +0 -701
  43. package/dist/chunk-7LCYLPW2.cjs +0 -663
  44. package/dist/chunk-7N3JIJMT.js +0 -1395
  45. package/dist/chunk-7X2IEJIE.cjs +0 -2526
  46. package/dist/chunk-A6GO3DPZ.cjs +0 -1002
  47. package/dist/chunk-AC4BSHFV.js +0 -765
  48. package/dist/chunk-AIUXRS74.cjs +0 -3681
  49. package/dist/chunk-ARNKA274.cjs +0 -130
  50. package/dist/chunk-AW7WAELW.js +0 -975
  51. package/dist/chunk-BKIGPPVM.js +0 -629
  52. package/dist/chunk-BO2OKHIY.js +0 -6055
  53. package/dist/chunk-BU5ZAFMC.js +0 -115
  54. package/dist/chunk-BY3B7ZYV.cjs +0 -457
  55. package/dist/chunk-C2QHVHZF.js +0 -4066
  56. package/dist/chunk-C7BNX4XJ.js +0 -1043
  57. package/dist/chunk-CLYEIWA5.cjs +0 -408
  58. package/dist/chunk-CMYAWUX3.js +0 -4455
  59. package/dist/chunk-CN4NOESF.cjs +0 -416
  60. package/dist/chunk-CNVM7J3M.js +0 -883
  61. package/dist/chunk-CO2VM2DK.js +0 -1965
  62. package/dist/chunk-CQDOF3G7.cjs +0 -1404
  63. package/dist/chunk-DH5G2JUA.cjs +0 -614
  64. package/dist/chunk-DIEDKX5B.cjs +0 -660
  65. package/dist/chunk-DN7UFU63.cjs +0 -172
  66. package/dist/chunk-DXVCEFZB.js +0 -2027
  67. package/dist/chunk-EGIZHYJP.cjs +0 -85
  68. package/dist/chunk-EJA7G2C4.cjs +0 -428
  69. package/dist/chunk-ELLQPFAF.js +0 -830
  70. package/dist/chunk-EMO7HAKJ.cjs +0 -1875
  71. package/dist/chunk-ENV7K6XA.js +0 -282
  72. package/dist/chunk-EPWRXL6S.js +0 -2522
  73. package/dist/chunk-EUFLX2PI.js +0 -56956
  74. package/dist/chunk-F5LVGHNT.js +0 -449
  75. package/dist/chunk-FEFHPUEM.cjs +0 -6122
  76. package/dist/chunk-FF3Q7ADR.js +0 -380
  77. package/dist/chunk-GC3YU46J.js +0 -762
  78. package/dist/chunk-H6WMMLQK.cjs +0 -4492
  79. package/dist/chunk-H7XMORZI.js +0 -2731
  80. package/dist/chunk-HAN4V3PF.cjs +0 -2037
  81. package/dist/chunk-HBZYCASG.js +0 -4208
  82. package/dist/chunk-HF4OFY25.cjs +0 -4097
  83. package/dist/chunk-HHS6FMOU.cjs +0 -3813
  84. package/dist/chunk-HJJLD366.js +0 -1169
  85. package/dist/chunk-HKCVM6OK.cjs +0 -2237
  86. package/dist/chunk-HUFNKFA6.js +0 -698
  87. package/dist/chunk-I3XGTIHM.cjs +0 -1505
  88. package/dist/chunk-I6XZ5DZF.cjs +0 -488
  89. package/dist/chunk-IHOUYQIA.js +0 -406
  90. package/dist/chunk-ILRBGFVD.cjs +0 -72
  91. package/dist/chunk-KDB6BUMB.js +0 -3666
  92. package/dist/chunk-KUJRR4FJ.js +0 -657
  93. package/dist/chunk-KYM4XRFG.js +0 -421
  94. package/dist/chunk-L3Z2HIWJ.cjs +0 -793
  95. package/dist/chunk-LBPEZQAF.js +0 -2232
  96. package/dist/chunk-LER4WXW5.cjs +0 -286
  97. package/dist/chunk-LK5IAVH6.cjs +0 -1173
  98. package/dist/chunk-M6FU6S22.js +0 -2911
  99. package/dist/chunk-MWEFR6YQ.js +0 -1857
  100. package/dist/chunk-N5FTS757.cjs +0 -415
  101. package/dist/chunk-NCUHGRTZ.js +0 -752
  102. package/dist/chunk-NKRKT6V2.js +0 -69
  103. package/dist/chunk-NLPSZT4C.js +0 -630
  104. package/dist/chunk-NRUB55IT.cjs +0 -771
  105. package/dist/chunk-NTMZSDXM.cjs +0 -633
  106. package/dist/chunk-ODGMVILH.js +0 -9662
  107. package/dist/chunk-PBA6NXCT.cjs +0 -1216
  108. package/dist/chunk-PHENW4MQ.js +0 -65
  109. package/dist/chunk-PJMOXFPR.cjs +0 -1861
  110. package/dist/chunk-PLMYCCA4.js +0 -504
  111. package/dist/chunk-PPULB4GG.cjs +0 -9728
  112. package/dist/chunk-PQLGZKMC.cjs +0 -755
  113. package/dist/chunk-Q56TT7IL.js +0 -483
  114. package/dist/chunk-QHVVVN47.cjs +0 -2033
  115. package/dist/chunk-QWKUKVRE.js +0 -2026
  116. package/dist/chunk-RE3OKSYF.cjs +0 -424
  117. package/dist/chunk-RT7LJRSF.cjs +0 -57378
  118. package/dist/chunk-SPDELRRV.cjs +0 -319
  119. package/dist/chunk-SUCBB66F.js +0 -229
  120. package/dist/chunk-TSWTWZ42.cjs +0 -4214
  121. package/dist/chunk-UITWA6DV.cjs +0 -2829
  122. package/dist/chunk-UQW6SLM5.js +0 -172
  123. package/dist/chunk-V2ILLPHK.cjs +0 -237
  124. package/dist/chunk-V42NTCFH.js +0 -3645
  125. package/dist/chunk-VJ3L7CHT.cjs +0 -169
  126. package/dist/chunk-VJVCD5T5.cjs +0 -769
  127. package/dist/chunk-VK5AXKO3.js +0 -746
  128. package/dist/chunk-VSXOIUCF.cjs +0 -473
  129. package/dist/chunk-VZNKJZTT.cjs +0 -749
  130. package/dist/chunk-W76ETJTI.js +0 -678
  131. package/dist/chunk-WFJIDI2N.cjs +0 -424
  132. package/dist/chunk-WN3YF33G.cjs +0 -118
  133. package/dist/chunk-WUXIRGZP.cjs +0 -2915
  134. package/dist/chunk-WYH4GVZ5.js +0 -1488
  135. package/dist/chunk-X4YVN7H3.cjs +0 -888
  136. package/dist/chunk-X67XRI2T.js +0 -410
  137. package/dist/chunk-XDXZM3ZP.cjs +0 -1046
  138. package/dist/chunk-XH7SE4HH.cjs +0 -72
  139. package/dist/chunk-XNMEH2BI.js +0 -126
  140. package/dist/chunk-XSF76QRU.js +0 -468
  141. package/dist/chunk-XSUZMPVQ.cjs +0 -682
  142. package/dist/chunk-XUILI4KB.cjs +0 -430
  143. package/dist/chunk-Y5MUAYTO.js +0 -167
  144. package/dist/chunk-Y7VK5TH3.cjs +0 -129
  145. package/dist/chunk-YCMQQQ5U.cjs +0 -5244
  146. package/dist/chunk-YYXH45E2.js +0 -426
  147. package/dist/chunk-ZGTGVSTZ.js +0 -422
  148. package/dist/chunk-ZL6VJ6SN.js +0 -421
  149. package/dist/cli/holoscript-runner.cjs +0 -2258
  150. package/dist/cli/holoscript-runner.js +0 -2233
  151. package/dist/codebase/index.cjs +0 -336
  152. package/dist/codebase/index.js +0 -331
  153. package/dist/compiler/agent-inference.cjs +0 -29
  154. package/dist/compiler/agent-inference.js +0 -6
  155. package/dist/compiler/android-xr.cjs +0 -20
  156. package/dist/compiler/android-xr.js +0 -10
  157. package/dist/compiler/android.cjs +0 -21
  158. package/dist/compiler/android.js +0 -11
  159. package/dist/compiler/ar.cjs +0 -21
  160. package/dist/compiler/ar.js +0 -9
  161. package/dist/compiler/babylon.cjs +0 -18
  162. package/dist/compiler/babylon.js +0 -12
  163. package/dist/compiler/coco.cjs +0 -27
  164. package/dist/compiler/coco.js +0 -4
  165. package/dist/compiler/domain-block-utils.cjs +0 -595
  166. package/dist/compiler/domain-block-utils.js +0 -9
  167. package/dist/compiler/dtdl.cjs +0 -25
  168. package/dist/compiler/dtdl.js +0 -9
  169. package/dist/compiler/gltf-pipeline.cjs +0 -39
  170. package/dist/compiler/gltf-pipeline.js +0 -4
  171. package/dist/compiler/godot.cjs +0 -16
  172. package/dist/compiler/godot.js +0 -10
  173. package/dist/compiler/incremental.cjs +0 -30
  174. package/dist/compiler/incremental.js +0 -5
  175. package/dist/compiler/index.cjs +0 -1522
  176. package/dist/compiler/index.js +0 -1321
  177. package/dist/compiler/ios.cjs +0 -21
  178. package/dist/compiler/ios.js +0 -11
  179. package/dist/compiler/multi-layer.cjs +0 -27
  180. package/dist/compiler/multi-layer.js +0 -12
  181. package/dist/compiler/nodetoy.cjs +0 -23
  182. package/dist/compiler/nodetoy.js +0 -4
  183. package/dist/compiler/openxr.cjs +0 -16
  184. package/dist/compiler/openxr.js +0 -10
  185. package/dist/compiler/playcanvas.cjs +0 -17
  186. package/dist/compiler/playcanvas.js +0 -11
  187. package/dist/compiler/r3f.cjs +0 -35
  188. package/dist/compiler/r3f.js +0 -13
  189. package/dist/compiler/remotion.cjs +0 -23
  190. package/dist/compiler/remotion.js +0 -4
  191. package/dist/compiler/reproducibility.cjs +0 -39
  192. package/dist/compiler/reproducibility.js +0 -4
  193. package/dist/compiler/sdf.cjs +0 -22
  194. package/dist/compiler/sdf.js +0 -10
  195. package/dist/compiler/semantic-scene.cjs +0 -31
  196. package/dist/compiler/semantic-scene.js +0 -4
  197. package/dist/compiler/state.cjs +0 -18
  198. package/dist/compiler/state.js +0 -8
  199. package/dist/compiler/trait-composition.cjs +0 -27
  200. package/dist/compiler/trait-composition.js +0 -6
  201. package/dist/compiler/unity.cjs +0 -16
  202. package/dist/compiler/unity.js +0 -10
  203. package/dist/compiler/unreal.cjs +0 -20
  204. package/dist/compiler/unreal.js +0 -10
  205. package/dist/compiler/urdf.cjs +0 -46
  206. package/dist/compiler/urdf.js +0 -10
  207. package/dist/compiler/usd-physics.cjs +0 -24
  208. package/dist/compiler/usd-physics.js +0 -7
  209. package/dist/compiler/visionos.cjs +0 -16
  210. package/dist/compiler/visionos.js +0 -10
  211. package/dist/compiler/vrchat.cjs +0 -20
  212. package/dist/compiler/vrchat.js +0 -10
  213. package/dist/compiler/vrr.cjs +0 -22
  214. package/dist/compiler/vrr.js +0 -10
  215. package/dist/compiler/wasm.cjs +0 -34
  216. package/dist/compiler/wasm.js +0 -10
  217. package/dist/compiler/webgpu.cjs +0 -16
  218. package/dist/compiler/webgpu.js +0 -10
  219. package/dist/debugger.cjs +0 -21
  220. package/dist/debugger.js +0 -8
  221. package/dist/entries/interop.cjs +0 -62
  222. package/dist/entries/interop.js +0 -5
  223. package/dist/entries/scripting.cjs +0 -67
  224. package/dist/entries/scripting.js +0 -10
  225. package/dist/index.cjs +0 -61703
  226. package/dist/index.js +0 -58652
  227. package/dist/math/vec3.cjs +0 -57
  228. package/dist/math/vec3.js +0 -4
  229. package/dist/parser.cjs +0 -26
  230. package/dist/parser.js +0 -9
  231. package/dist/playwright-7DTEQCBD.cjs +0 -98673
  232. package/dist/playwright-BIZXMLD2.js +0 -98660
  233. package/dist/post-quantum-JTTAAGO3.cjs +0 -6
  234. package/dist/post-quantum-RVPVDEPI.js +0 -4
  235. package/dist/runtime.cjs +0 -16
  236. package/dist/runtime.js +0 -7
  237. package/dist/storage/index.cjs +0 -37
  238. package/dist/storage/index.js +0 -4
  239. package/dist/traits/index.cjs +0 -14331
  240. package/dist/traits/index.js +0 -12126
  241. package/dist/type-checker.cjs +0 -18
  242. package/dist/type-checker.js +0 -5
  243. package/dist/wot/index.cjs +0 -29
  244. package/dist/wot/index.js +0 -4
@@ -1,2233 +0,0 @@
1
- #!/usr/bin/env node
2
- import { generateProvenance, checkLicenseCompatibility, ethToWei, calculateRevenueDistribution, formatRevenueDistribution, PROTOCOL_CONSTANTS, CompositionTestRunner, registerStdlib } from '../chunk-AW7WAELW.js';
3
- import { AbsorbProcessor, ScriptTestRunner, HotReloadWatcher } from '../chunk-GC3YU46J.js';
4
- import { InteropContext } from '../chunk-FF3Q7ADR.js';
5
- import { parse } from '../chunk-CMYAWUX3.js';
6
- import '../chunk-DXVCEFZB.js';
7
- import '../chunk-H7XMORZI.js';
8
- import '../chunk-26TLYBFD.js';
9
- import '../chunk-SUCBB66F.js';
10
- import '../chunk-XNMEH2BI.js';
11
- import '../chunk-32TWR3HE.js';
12
- import 'dotenv/config';
13
- import * as fs from 'fs';
14
- import * as path from 'path';
15
- import * as readline from 'readline';
16
- import { spawn } from 'child_process';
17
- import { randomUUID } from 'crypto';
18
- import { createHeadlessRuntime, HEADLESS_PROFILE, getProfile } from '@holoscript/engine/runtime';
19
-
20
- function defaultModelForProvider(provider) {
21
- switch (provider) {
22
- case "xai":
23
- return "grok-3";
24
- case "openai":
25
- return "gpt-4.1";
26
- case "ollama":
27
- return "brittney-qwen-v23:latest";
28
- case "anthropic":
29
- default:
30
- return "claude-sonnet-4-20250514";
31
- }
32
- }
33
- function defaultToolProfileForProvider(provider) {
34
- switch (provider) {
35
- case "xai":
36
- return "grok-hsplus";
37
- case "anthropic":
38
- return "claude-hsplus";
39
- case "openai":
40
- case "ollama":
41
- default:
42
- return "standard";
43
- }
44
- }
45
- function parseProvider(value) {
46
- const normalized = value?.toLowerCase();
47
- if (normalized === "anthropic" || normalized === "xai" || normalized === "openai" || normalized === "ollama") {
48
- return normalized;
49
- }
50
- return void 0;
51
- }
52
- function parseToolProfile(value) {
53
- const normalized = value?.toLowerCase();
54
- if (normalized === "claude-hsplus" || normalized === "grok-hsplus" || normalized === "standard") {
55
- return normalized;
56
- }
57
- return void 0;
58
- }
59
- function daemonEnvDefaults() {
60
- const provider = parseProvider(process.env.HOLODAEMON_PROVIDER) || "anthropic";
61
- const toolProfile = parseToolProfile(process.env.HOLODAEMON_TOOL_PROFILE) || defaultToolProfileForProvider(provider);
62
- const model = process.env.HOLODAEMON_MODEL || defaultModelForProvider(provider);
63
- return { provider, toolProfile, model };
64
- }
65
- function parseArgs(argv) {
66
- const args = argv.slice(2);
67
- const envDefaults = daemonEnvDefaults();
68
- const opts = {
69
- command: "help",
70
- json: false,
71
- target: "headless",
72
- profile: "headless",
73
- debug: false,
74
- watch: false,
75
- daemon: false,
76
- ticks: 100,
77
- cycles: 15,
78
- commit: false,
79
- enforceGotchas: false,
80
- providerRotation: false,
81
- alwaysOn: false,
82
- cycleIntervalSec: 30,
83
- allowShell: false,
84
- allowedShellCommands: [],
85
- allowedHosts: [],
86
- allowedPaths: [],
87
- provider: envDefaults.provider,
88
- toolProfile: envDefaults.toolProfile,
89
- model: envDefaults.model,
90
- timeout: 30,
91
- sessionId: randomUUID(),
92
- // Deploy defaults
93
- share: false,
94
- author: "",
95
- license: "free",
96
- serverUrl: "https://mcp.holoscript.net",
97
- // Protocol publish defaults
98
- publish: false,
99
- mintNft: false,
100
- price: "0",
101
- walletKey: ""
102
- };
103
- let modelExplicit = false;
104
- let toolProfileExplicit = false;
105
- if (args.length === 0) return opts;
106
- const cmd = args[0];
107
- if (cmd === "run" || cmd === "test" || cmd === "compile" || cmd === "deploy" || cmd === "absorb" || cmd === "daemon" || cmd === "holodaemon" || cmd === "moltbook-daemon" || cmd === "holomesh-daemon") {
108
- opts.command = cmd;
109
- }
110
- if (opts.command === "daemon" && args[1] === "status") {
111
- opts.command = "daemon-status";
112
- }
113
- if (args[1] && !args[1].startsWith("--") && opts.command !== "daemon-status") {
114
- opts.file = args[1];
115
- }
116
- for (let i = 0; i < args.length; i++) {
117
- if (args[i] === "--target" && args[i + 1]) opts.target = args[++i];
118
- if (args[i] === "--profile" && args[i + 1]) opts.profile = args[++i];
119
- if (args[i] === "--output" && args[i + 1]) opts.output = args[++i];
120
- if (args[i] === "--tier" && args[i + 1]) {
121
- const tier = args[++i].toLowerCase();
122
- if (["low", "med", "high", "ultra"].includes(tier)) {
123
- opts.qualityTier = tier;
124
- }
125
- }
126
- if (args[i] === "--debug" || args[i] === "--verbose") opts.debug = true;
127
- if (args[i] === "--watch" || args[i] === "-w") opts.watch = true;
128
- if (args[i] === "--daemon") opts.daemon = true;
129
- if (args[i] === "--ticks" && args[i + 1]) {
130
- const parsed = Number(args[++i]);
131
- if (Number.isFinite(parsed) && parsed >= 0) {
132
- opts.ticks = Math.floor(parsed);
133
- }
134
- }
135
- if (args[i] === "--cycles" && args[i + 1]) {
136
- const parsed = Number(args[++i]);
137
- if (Number.isFinite(parsed) && parsed > 0) {
138
- opts.cycles = Math.floor(parsed);
139
- }
140
- }
141
- if (args[i] === "--commit") opts.commit = true;
142
- if (args[i] === "--trial" && args[i + 1]) {
143
- opts.trial = Number(args[++i]);
144
- }
145
- if (args[i] === "--provider" && args[i + 1]) {
146
- const provider = parseProvider(args[++i]);
147
- if (provider) {
148
- opts.provider = provider;
149
- }
150
- }
151
- if (args[i] === "--tool-profile" && args[i + 1]) {
152
- const profile = parseToolProfile(args[++i]);
153
- if (profile) {
154
- opts.toolProfile = profile;
155
- toolProfileExplicit = true;
156
- }
157
- }
158
- if (args[i] === "--model" && args[i + 1]) {
159
- opts.model = args[++i];
160
- modelExplicit = true;
161
- }
162
- if (args[i] === "--timeout" && args[i + 1]) {
163
- const parsed = Number(args[++i]);
164
- if (Number.isFinite(parsed) && parsed > 0) {
165
- opts.timeout = Math.min(120, Math.max(1, Math.floor(parsed)));
166
- }
167
- }
168
- if (args[i] === "--focus" && args[i + 1]) {
169
- opts.focus = args[++i];
170
- }
171
- if (args[i] === "--enforce-gotchas") opts.enforceGotchas = true;
172
- if (args[i] === "--provider-rotation") opts.providerRotation = true;
173
- if (args[i] === "--always-on") opts.alwaysOn = true;
174
- if (args[i] === "--allow-shell") opts.allowShell = true;
175
- if (args[i] === "--skills-dir" && args[i + 1]) opts.skillsDir = args[++i];
176
- if (args[i] === "--cycle-interval-sec" && args[i + 1]) {
177
- const parsed = Number(args[++i]);
178
- if (Number.isFinite(parsed) && parsed >= 1) {
179
- opts.cycleIntervalSec = Math.floor(parsed);
180
- }
181
- }
182
- if (args[i] === "--allow-shell-command" && args[i + 1]) {
183
- opts.allowedShellCommands.push(args[++i]);
184
- }
185
- if (args[i] === "--allow-host" && args[i + 1]) {
186
- opts.allowedHosts.push(args[++i]);
187
- }
188
- if (args[i] === "--allow-path" && args[i + 1]) {
189
- opts.allowedPaths.push(args[++i]);
190
- }
191
- if (args[i] === "--json") opts.json = true;
192
- if (args[i] === "--share") opts.share = true;
193
- if (args[i] === "--author" && args[i + 1]) opts.author = args[++i];
194
- if (args[i] === "--license" && args[i + 1]) opts.license = args[++i];
195
- if (args[i] === "--server" && args[i + 1]) opts.serverUrl = args[++i];
196
- if (args[i] === "--publish") opts.publish = true;
197
- if (args[i] === "--mint-nft") opts.mintNft = true;
198
- if (args[i] === "--price" && args[i + 1]) opts.price = args[++i];
199
- if (args[i] === "--wallet-key" && args[i + 1]) opts.walletKey = args[++i];
200
- }
201
- if (!modelExplicit) {
202
- opts.model = defaultModelForProvider(opts.provider);
203
- }
204
- if (!toolProfileExplicit) {
205
- opts.toolProfile = defaultToolProfileForProvider(opts.provider);
206
- }
207
- return opts;
208
- }
209
- function extractChatText(data) {
210
- const d = data;
211
- return d?.content?.[0]?.text ?? d?.choices?.[0]?.message?.content ?? d?.message?.content ?? d?.response ?? "";
212
- }
213
- function extractTokenUsage(data) {
214
- const d = data;
215
- const inputTokens = d?.usage?.input_tokens ?? d?.usage?.prompt_tokens ?? d?.prompt_eval_count ?? 0;
216
- const outputTokens = d?.usage?.output_tokens ?? d?.usage?.completion_tokens ?? d?.eval_count ?? 0;
217
- return {
218
- inputTokens: Number.isFinite(inputTokens) ? Number(inputTokens) : 0,
219
- outputTokens: Number.isFinite(outputTokens) ? Number(outputTokens) : 0
220
- };
221
- }
222
- function createDaemonLLMProvider(opts) {
223
- if (opts.provider === "anthropic") {
224
- return {
225
- chat: async ({ system, prompt, maxTokens }) => {
226
- const apiKey = process.env.ANTHROPIC_API_KEY;
227
- if (!apiKey) throw new Error("ANTHROPIC_API_KEY not set");
228
- const response = await fetch("https://api.anthropic.com/v1/messages", {
229
- method: "POST",
230
- headers: {
231
- "x-api-key": apiKey,
232
- "anthropic-version": "2023-06-01",
233
- "content-type": "application/json"
234
- },
235
- body: JSON.stringify({
236
- model: opts.model,
237
- max_tokens: maxTokens || 4096,
238
- system,
239
- messages: [{ role: "user", content: prompt }]
240
- })
241
- });
242
- if (!response.ok) {
243
- const body = await response.text().catch(() => "");
244
- throw new Error(`Anthropic API ${response.status}: ${body.slice(0, 200)}`);
245
- }
246
- const data = await response.json();
247
- const usage = extractTokenUsage(data);
248
- return {
249
- text: extractChatText(data),
250
- inputTokens: usage.inputTokens,
251
- outputTokens: usage.outputTokens
252
- };
253
- }
254
- };
255
- }
256
- if (opts.provider === "xai") {
257
- return {
258
- chat: async ({ system, prompt, maxTokens }) => {
259
- const apiKey = process.env.XAI_API_KEY;
260
- if (!apiKey) throw new Error("XAI_API_KEY not set");
261
- const response = await fetch("https://api.x.ai/v1/chat/completions", {
262
- method: "POST",
263
- headers: {
264
- Authorization: `Bearer ${apiKey}`,
265
- "content-type": "application/json"
266
- },
267
- body: JSON.stringify({
268
- model: opts.model,
269
- max_tokens: maxTokens || 4096,
270
- temperature: 0.2,
271
- messages: [
272
- { role: "system", content: system },
273
- { role: "user", content: prompt }
274
- ]
275
- })
276
- });
277
- if (!response.ok) {
278
- const body = await response.text().catch(() => "");
279
- throw new Error(`xAI API ${response.status}: ${body.slice(0, 200)}`);
280
- }
281
- const data = await response.json();
282
- const usage = extractTokenUsage(data);
283
- return {
284
- text: extractChatText(data),
285
- inputTokens: usage.inputTokens,
286
- outputTokens: usage.outputTokens
287
- };
288
- }
289
- };
290
- }
291
- if (opts.provider === "openai") {
292
- return {
293
- chat: async ({ system, prompt, maxTokens }) => {
294
- const apiKey = process.env.OPENAI_API_KEY;
295
- if (!apiKey) throw new Error("OPENAI_API_KEY not set");
296
- const baseUrl = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
297
- const response = await fetch(`${baseUrl}/chat/completions`, {
298
- method: "POST",
299
- headers: {
300
- Authorization: `Bearer ${apiKey}`,
301
- "content-type": "application/json"
302
- },
303
- body: JSON.stringify({
304
- model: opts.model,
305
- max_tokens: maxTokens || 4096,
306
- temperature: 0.2,
307
- messages: [
308
- { role: "system", content: system },
309
- { role: "user", content: prompt }
310
- ]
311
- })
312
- });
313
- if (!response.ok) {
314
- const body = await response.text().catch(() => "");
315
- throw new Error(`OpenAI API ${response.status}: ${body.slice(0, 200)}`);
316
- }
317
- const data = await response.json();
318
- const usage = extractTokenUsage(data);
319
- return {
320
- text: extractChatText(data),
321
- inputTokens: usage.inputTokens,
322
- outputTokens: usage.outputTokens
323
- };
324
- }
325
- };
326
- }
327
- return {
328
- chat: async ({ system, prompt }) => {
329
- const baseUrl = process.env.OLLAMA_BASE_URL || process.env.OLLAMA_URL;
330
- if (!baseUrl) throw new Error("Ollama selected but OLLAMA_BASE_URL/OLLAMA_URL not set. Use --provider anthropic|xai|openai or set OLLAMA_BASE_URL in .env");
331
- const response = await fetch(`${baseUrl}/api/generate`, {
332
- method: "POST",
333
- headers: { "content-type": "application/json" },
334
- body: JSON.stringify({
335
- model: opts.model,
336
- system,
337
- prompt,
338
- stream: false,
339
- options: {
340
- temperature: 0.2
341
- }
342
- })
343
- });
344
- if (!response.ok) {
345
- const body = await response.text().catch(() => "");
346
- throw new Error(`Ollama API ${response.status}: ${body.slice(0, 200)}`);
347
- }
348
- const data = await response.json();
349
- const usage = extractTokenUsage(data);
350
- return {
351
- text: extractChatText(data),
352
- inputTokens: usage.inputTokens,
353
- outputTokens: usage.outputTokens
354
- };
355
- }
356
- };
357
- }
358
- function createNodeHostCapabilities(cwd) {
359
- return {
360
- fileSystem: {
361
- readFile: (filePath) => fs.readFileSync(path.resolve(cwd, filePath), "utf-8"),
362
- writeFile: (filePath, content) => {
363
- fs.writeFileSync(path.resolve(cwd, filePath), content, "utf-8");
364
- },
365
- deleteFile: (filePath) => {
366
- fs.rmSync(path.resolve(cwd, filePath), { force: true });
367
- },
368
- exists: (filePath) => fs.existsSync(path.resolve(cwd, filePath))
369
- },
370
- process: {
371
- exec: (command, args = [], options) => new Promise((resolve2, reject) => {
372
- const child = spawn(command, args, {
373
- cwd: options?.cwd ?? cwd,
374
- env: { ...process.env, ...options?.env ?? {} },
375
- shell: true,
376
- stdio: ["ignore", "pipe", "pipe"]
377
- });
378
- let stdout = "";
379
- let stderr = "";
380
- let timeoutHandle = null;
381
- child.stdout?.on("data", (data) => {
382
- stdout += data.toString("utf-8");
383
- });
384
- child.stderr?.on("data", (data) => {
385
- stderr += data.toString("utf-8");
386
- });
387
- if (options?.timeoutMs && options.timeoutMs > 0) {
388
- timeoutHandle = setTimeout(() => {
389
- try {
390
- child.kill("SIGKILL");
391
- } catch {
392
- }
393
- }, options.timeoutMs);
394
- }
395
- child.on("close", (code, signal) => {
396
- if (timeoutHandle) clearTimeout(timeoutHandle);
397
- resolve2({ code, signal, stdout, stderr });
398
- });
399
- child.on("error", (error) => {
400
- if (timeoutHandle) clearTimeout(timeoutHandle);
401
- reject(error);
402
- });
403
- })
404
- },
405
- network: {
406
- fetch: async (url, options) => {
407
- if (typeof fetch === "undefined") {
408
- throw new Error("fetch is not available in this runtime");
409
- }
410
- const response = await fetch(url, {
411
- method: options?.method,
412
- headers: options?.headers,
413
- body: options?.body,
414
- credentials: options?.credentials
415
- });
416
- const headers = {};
417
- response.headers.forEach((value, key) => {
418
- headers[key.toLowerCase()] = value;
419
- });
420
- const contentType = headers["content-type"] ?? "";
421
- let body;
422
- let text;
423
- let json;
424
- if (contentType.includes("application/json")) {
425
- try {
426
- json = await response.json();
427
- body = json;
428
- } catch {
429
- text = await response.text();
430
- body = text;
431
- }
432
- } else {
433
- text = await response.text();
434
- body = text;
435
- }
436
- return {
437
- status: response.status,
438
- ok: response.ok,
439
- headers,
440
- body,
441
- text,
442
- json
443
- };
444
- }
445
- }
446
- };
447
- }
448
- function runTicks(runtime, count) {
449
- for (let i = 0; i < count; i++) {
450
- runtime.tick();
451
- }
452
- }
453
- async function runDaemon(runtime, opts) {
454
- const supportsEvents = typeof runtime.emit === "function";
455
- const supportsStats = typeof runtime.getStats === "function";
456
- const supportsActionRegistry = typeof runtime.registerAction === "function";
457
- const supportsSubscriptions = typeof runtime.on === "function";
458
- const pendingActionResolutions = /* @__PURE__ */ new Map();
459
- const registeredActions = /* @__PURE__ */ new Set();
460
- let nextActionId = 1;
461
- const send = (message) => {
462
- process.stdout.write(`${JSON.stringify(message)}
463
- `);
464
- };
465
- const unsubscribeActionResult = supportsSubscriptions ? runtime.on("action:result", (payload) => {
466
- send({
467
- type: "daemon:action_result",
468
- payload
469
- });
470
- }) : () => {
471
- };
472
- send({
473
- type: "daemon:ready",
474
- profile: opts.profile,
475
- supportsEvents,
476
- supportsStats,
477
- supportsActionRegistry
478
- });
479
- const rl = readline.createInterface({
480
- input: process.stdin,
481
- output: process.stdout,
482
- terminal: false
483
- });
484
- const close = () => {
485
- for (const [, pending] of pendingActionResolutions) {
486
- clearTimeout(pending.timer);
487
- pending.reject(new Error("Daemon shutting down"));
488
- }
489
- pendingActionResolutions.clear();
490
- try {
491
- unsubscribeActionResult();
492
- } catch {
493
- }
494
- try {
495
- runtime.stop();
496
- } catch {
497
- }
498
- rl.close();
499
- };
500
- process.once("SIGINT", () => {
501
- send({ type: "daemon:stopped", reason: "SIGINT" });
502
- close();
503
- });
504
- process.once("SIGTERM", () => {
505
- send({ type: "daemon:stopped", reason: "SIGTERM" });
506
- close();
507
- });
508
- rl.on("line", (line) => {
509
- const raw = line.trim();
510
- if (!raw) return;
511
- let command;
512
- try {
513
- command = JSON.parse(raw);
514
- } catch {
515
- send({ type: "daemon:error", error: "Invalid JSON command" });
516
- return;
517
- }
518
- const op = command?.op;
519
- switch (op) {
520
- case "tick": {
521
- const count = Number.isFinite(command?.count) ? Math.max(0, Math.floor(command.count)) : 1;
522
- runTicks(runtime, count);
523
- send({ type: "daemon:ok", op, ticked: count, stats: runtime.getStats?.() });
524
- break;
525
- }
526
- case "emit": {
527
- if (!supportsEvents) {
528
- send({ type: "daemon:error", op, error: "Runtime does not expose emit()" });
529
- break;
530
- }
531
- runtime.emit(command.event, command.payload);
532
- send({ type: "daemon:ok", op, event: command.event });
533
- break;
534
- }
535
- case "state:get": {
536
- const key = command?.key;
537
- const value = typeof key === "string" ? runtime.getState(key) : runtime.getAllState?.();
538
- send({ type: "daemon:ok", op, key, value });
539
- break;
540
- }
541
- case "state:set": {
542
- const key = command?.key;
543
- if (typeof key === "string") {
544
- runtime.setState(key, command.value);
545
- send({ type: "daemon:ok", op, key });
546
- } else {
547
- send({ type: "daemon:error", op, error: "state:set requires string key" });
548
- }
549
- break;
550
- }
551
- case "stats": {
552
- send({ type: "daemon:ok", op, stats: runtime.getStats?.() });
553
- break;
554
- }
555
- case "action:register": {
556
- if (!supportsActionRegistry) {
557
- send({ type: "daemon:error", op, error: "Runtime does not expose registerAction()" });
558
- break;
559
- }
560
- const actionName = command?.name;
561
- if (typeof actionName !== "string" || actionName.trim() === "") {
562
- send({ type: "daemon:error", op, error: "action:register requires non-empty name" });
563
- break;
564
- }
565
- runtime.registerAction(
566
- actionName,
567
- (params, blackboard) => {
568
- const actionRequestId = `daemon-action-${nextActionId++}`;
569
- const timeoutMs = Number.isFinite(command?.timeoutMs) ? Math.max(1, Math.floor(command.timeoutMs)) : 3e4;
570
- return new Promise((resolve2, reject) => {
571
- const timer = setTimeout(() => {
572
- pendingActionResolutions.delete(actionRequestId);
573
- resolve2(false);
574
- send({
575
- type: "daemon:action_timeout",
576
- action: actionName,
577
- actionRequestId,
578
- timeoutMs
579
- });
580
- }, timeoutMs);
581
- pendingActionResolutions.set(actionRequestId, {
582
- resolve: resolve2,
583
- reject,
584
- timer,
585
- runtimeRequestId: actionRequestId,
586
- actionName
587
- });
588
- send({
589
- type: "daemon:action_request",
590
- action: actionName,
591
- actionRequestId,
592
- params,
593
- blackboard
594
- });
595
- });
596
- }
597
- );
598
- registeredActions.add(actionName);
599
- send({ type: "daemon:ok", op, name: actionName });
600
- break;
601
- }
602
- case "action:list": {
603
- send({
604
- type: "daemon:ok",
605
- op,
606
- actions: [...registeredActions],
607
- pending: pendingActionResolutions.size
608
- });
609
- break;
610
- }
611
- case "action:reply": {
612
- const actionRequestId = command?.actionRequestId;
613
- if (typeof actionRequestId !== "string") {
614
- send({ type: "daemon:error", op, error: "action:reply requires actionRequestId" });
615
- break;
616
- }
617
- const pending = pendingActionResolutions.get(actionRequestId);
618
- if (!pending) {
619
- send({ type: "daemon:error", op, error: `Unknown actionRequestId: ${actionRequestId}` });
620
- break;
621
- }
622
- clearTimeout(pending.timer);
623
- pendingActionResolutions.delete(actionRequestId);
624
- const status = command?.status;
625
- const success = typeof command?.success === "boolean" ? command.success : status === "success" || status === "true";
626
- pending.resolve(Boolean(success));
627
- send({ type: "daemon:ok", op, actionRequestId, success: Boolean(success) });
628
- break;
629
- }
630
- case "action:unregister": {
631
- if (!supportsActionRegistry) {
632
- send({ type: "daemon:error", op, error: "Runtime does not expose registerAction()" });
633
- break;
634
- }
635
- const actionName = command?.name;
636
- if (typeof actionName !== "string" || actionName.trim() === "") {
637
- send({ type: "daemon:error", op, error: "action:unregister requires non-empty name" });
638
- break;
639
- }
640
- runtime.registerAction(actionName, () => false);
641
- registeredActions.delete(actionName);
642
- send({ type: "daemon:ok", op, name: actionName });
643
- break;
644
- }
645
- case "stop": {
646
- send({ type: "daemon:stopped", reason: "command" });
647
- close();
648
- break;
649
- }
650
- default:
651
- send({ type: "daemon:error", error: `Unknown op: ${String(op)}` });
652
- }
653
- });
654
- await new Promise((resolve2) => {
655
- rl.once("close", () => resolve2());
656
- });
657
- }
658
- async function runScript(opts) {
659
- if (!opts.file) {
660
- console.error("Error: No input file specified");
661
- process.exit(1);
662
- }
663
- const filePath = path.resolve(opts.file);
664
- if (!fs.existsSync(filePath)) {
665
- console.error(`Error: File not found: ${filePath}`);
666
- process.exit(1);
667
- }
668
- const source = fs.readFileSync(filePath, "utf-8");
669
- const ext = path.extname(filePath);
670
- console.log(`[holoscript] Running ${path.basename(filePath)} (target: ${opts.target})`);
671
- const parseResult = parse(source);
672
- const ast = parseResult.ast;
673
- if (opts.debug) {
674
- console.log(`[holoscript] Parsed ${ast.body?.length || 0} top-level nodes`);
675
- console.log(`[holoscript] File type: ${ext}`);
676
- }
677
- const profile = opts.profile === "headless" ? HEADLESS_PROFILE : getProfile(opts.profile);
678
- const runtime = createHeadlessRuntime(ast, {
679
- profile,
680
- tickRate: 10,
681
- debug: opts.debug,
682
- hostCapabilities: createNodeHostCapabilities(path.dirname(filePath))
683
- });
684
- registerStdlib(
685
- runtime,
686
- {
687
- policy: {
688
- rootDir: path.dirname(filePath),
689
- allowedPaths: opts.allowedPaths.length > 0 ? opts.allowedPaths : ["compositions", "data", "src", "packages"],
690
- maxFileBytes: 2 * 1024 * 1024,
691
- allowShell: opts.allowShell,
692
- allowedShellCommands: opts.allowedShellCommands,
693
- maxShellOutputBytes: 100 * 1024,
694
- shellTimeoutMs: 6e4,
695
- allowNetwork: opts.allowedHosts.length > 0,
696
- allowedHosts: opts.allowedHosts
697
- },
698
- hostCapabilities: createNodeHostCapabilities(path.dirname(filePath))
699
- }
700
- );
701
- new InteropContext(path.dirname(filePath));
702
- if (opts.debug) {
703
- console.log(`[holoscript] Profile: ${profile.name}`);
704
- console.log(`[holoscript] Interop context: ${path.dirname(filePath)}`);
705
- }
706
- runtime.start();
707
- if (opts.daemon) {
708
- if (opts.debug) {
709
- console.log("[holoscript] Entering daemon mode");
710
- }
711
- await runDaemon(runtime, opts);
712
- return;
713
- }
714
- runTicks(runtime, opts.ticks);
715
- runtime.stop();
716
- const stats = runtime.getStats();
717
- console.log(
718
- `[holoscript] Complete \u2014 ${stats.tickCount} ticks, ${stats.nodesProcessed} nodes processed`
719
- );
720
- if (opts.watch) {
721
- console.log(`[holoscript] Watching for changes... (Ctrl+C to stop)`);
722
- const watcher = new HotReloadWatcher({
723
- watchPaths: [path.dirname(filePath)],
724
- extensions: [".hs", ".hsplus", ".holo"],
725
- debounceMs: 300,
726
- mode: "soft"
727
- });
728
- watcher.on("reload", async (event) => {
729
- console.log(`
730
- [holoscript] File changed: ${path.basename(event.filePath)}`);
731
- console.log(`[holoscript] Re-running ${path.basename(filePath)}...`);
732
- try {
733
- const newSource = fs.readFileSync(filePath, "utf-8");
734
- const newParseResult = parse(newSource);
735
- const newAst = newParseResult.ast;
736
- const newRuntime = createHeadlessRuntime(newAst, {
737
- profile: opts.profile === "headless" ? HEADLESS_PROFILE : getProfile(opts.profile),
738
- tickRate: 10,
739
- debug: opts.debug,
740
- hostCapabilities: createNodeHostCapabilities(path.dirname(filePath))
741
- });
742
- newRuntime.start();
743
- runTicks(newRuntime, opts.ticks);
744
- newRuntime.stop();
745
- const newStats = newRuntime.getStats();
746
- console.log(
747
- `[holoscript] Complete \u2014 ${newStats.tickCount} ticks, ${newStats.nodesProcessed} nodes`
748
- );
749
- } catch (err) {
750
- console.error(`[holoscript] Error:`, err.message);
751
- }
752
- });
753
- watcher.start();
754
- await new Promise(() => {
755
- });
756
- }
757
- }
758
- async function testScript(opts) {
759
- if (!opts.file) {
760
- console.error("Error: No test file specified");
761
- process.exit(1);
762
- }
763
- const filePath = path.resolve(opts.file);
764
- if (!fs.existsSync(filePath)) {
765
- console.error(`Error: File not found: ${filePath}`);
766
- process.exit(1);
767
- }
768
- const source = fs.readFileSync(filePath, "utf-8");
769
- console.log(`[holoscript test] Running tests in ${path.basename(filePath)}`);
770
- const parseResult = parse(source);
771
- const ast = parseResult.ast;
772
- const profile = HEADLESS_PROFILE;
773
- const runtime = createHeadlessRuntime(ast, { profile, tickRate: 10, debug: opts.debug });
774
- runtime.start();
775
- for (let i = 0; i < 50; i++) runtime.tick();
776
- const runner = new ScriptTestRunner({
777
- debug: opts.debug,
778
- runtimeState: runtime.getState()
779
- });
780
- const scriptResults = runner.runTestsFromSource(source, filePath);
781
- const runtimeState = runtime.getState();
782
- const compositionState = Object.keys(runtimeState).length > 0 ? runtimeState : CompositionTestRunner.extractStateFromSource(source);
783
- const computedDefs = CompositionTestRunner.extractComputedFromSource(source);
784
- const compositionRunner = new CompositionTestRunner(compositionState, computedDefs, {
785
- debug: opts.debug
786
- });
787
- const compositionResults = compositionRunner.runTestsFromSource(source);
788
- const results = [...scriptResults, ...compositionResults];
789
- const passed = results.filter((r) => r.status === "passed").length;
790
- const failed = results.filter((r) => r.status === "failed").length;
791
- const skipped = results.filter((r) => r.status === "skipped").length;
792
- console.log("");
793
- for (const result of results) {
794
- const icon = result.status === "passed" ? "\u2713" : result.status === "failed" ? "\u2717" : "\u25CB";
795
- const color = result.status === "passed" ? "\x1B[32m" : result.status === "failed" ? "\x1B[31m" : "\x1B[33m";
796
- console.log(` ${color}${icon}\x1B[0m ${result.name} (${result.durationMs}ms)`);
797
- if (result.error) {
798
- console.log(` \x1B[31m${result.error}\x1B[0m`);
799
- }
800
- }
801
- console.log("");
802
- console.log(
803
- `Tests: ${passed} passed, ${failed} failed, ${skipped} skipped (${results.length} total)`
804
- );
805
- if (failed > 0) process.exit(1);
806
- }
807
- function compileScript(opts) {
808
- if (!opts.file) {
809
- console.error("Error: No input file specified");
810
- process.exit(1);
811
- }
812
- const filePath = path.resolve(opts.file);
813
- if (!fs.existsSync(filePath)) {
814
- console.error(`Error: File not found: ${filePath}`);
815
- process.exit(1);
816
- }
817
- const source = fs.readFileSync(filePath, "utf-8");
818
- if (opts.enforceGotchas) {
819
- const gotchaRe = /@gotcha\s*\{([^}]+)\}/g;
820
- let gMatch;
821
- const criticals = [];
822
- while ((gMatch = gotchaRe.exec(source)) !== null) {
823
- const block = gMatch[1];
824
- const sevMatch = block.match(/severity:\s*"([^"]*)"/i);
825
- const severity = sevMatch?.[1] || "warning";
826
- if (severity === "critical") {
827
- const warnMatch = block.match(/warning:\s*"([^"]*)"/i);
828
- const mitMatch = block.match(/mitigation:\s*"([^"]*)"/i);
829
- criticals.push({
830
- warning: warnMatch?.[1] || "Unknown critical gotcha",
831
- mitigation: mitMatch?.[1] || "No mitigation specified"
832
- });
833
- }
834
- }
835
- if (criticals.length > 0) {
836
- console.error(
837
- `[holoscript compile] BLOCKED by ${criticals.length} critical @gotcha violation(s):`
838
- );
839
- for (const g of criticals) {
840
- console.error(` \u2717 ${g.warning}`);
841
- console.error(` mitigation: ${g.mitigation}`);
842
- }
843
- process.exit(1);
844
- }
845
- console.log(`[holoscript compile] @gotcha check passed (no critical violations)`);
846
- }
847
- const compileResult = parse(source);
848
- if (!compileResult.success) {
849
- console.error(`[holoscript compile] Failed to parse: ${compileResult.errors?.[0]?.message}`);
850
- process.exit(1);
851
- }
852
- const ast = compileResult.ast;
853
- const outputPath = opts.output || filePath.replace(/\.(hs|hsplus|holo)$/, `.${opts.target === "python" ? "py" : "js"}`);
854
- console.log(
855
- `[holoscript compile] ${path.basename(filePath)} \u2192 ${opts.target} \u2192 ${path.basename(outputPath)}`
856
- );
857
- let output;
858
- switch (opts.target) {
859
- case "node":
860
- output = generateNodeTarget(ast);
861
- break;
862
- case "python":
863
- output = generatePythonTarget(ast);
864
- break;
865
- default:
866
- output = JSON.stringify(ast, null, 2);
867
- }
868
- fs.writeFileSync(outputPath, output, "utf-8");
869
- console.log(`[holoscript compile] Written to ${outputPath}`);
870
- }
871
- function generateNodeTarget(ast) {
872
- const lines = [
873
- "// Auto-generated by holoscript compile --target node",
874
- "// Source: HoloScript composition",
875
- `"use strict";`,
876
- ""
877
- ];
878
- if (ast.root && ast.root.children) {
879
- for (const node of ast.root.children) {
880
- if (node.type === "composition" || node.type === "ObjectDeclaration") {
881
- const name = node.name || "default";
882
- lines.push(`// ${node.type}: ${name}`);
883
- lines.push(`module.exports.${name} = ${JSON.stringify(node, null, 2)};`);
884
- lines.push("");
885
- }
886
- }
887
- }
888
- return lines.join("\n");
889
- }
890
- function generatePythonTarget(ast) {
891
- const lines = [
892
- "# Auto-generated by holoscript compile --target python",
893
- "# Source: HoloScript composition",
894
- "import json",
895
- ""
896
- ];
897
- if (ast.root && ast.root.children) {
898
- for (const node of ast.root.children) {
899
- if (node.type === "composition" || node.type === "ObjectDeclaration") {
900
- const name = node.name || "default_obj";
901
- lines.push(`# ${node.type}: ${name}`);
902
- lines.push(`${name} = json.loads('''${JSON.stringify(node)}''')`);
903
- lines.push("");
904
- }
905
- }
906
- }
907
- return lines.join("\n");
908
- }
909
- async function deployScript(opts) {
910
- if (!opts.file) {
911
- console.error("Error: No source file specified");
912
- console.error(
913
- "Usage: holoscript deploy <file> [--share] [--publish] [--price <eth>] [--mint-nft] [--author <name>] [--license <type>] [--server <url>]"
914
- );
915
- process.exit(1);
916
- }
917
- const filePath = path.resolve(opts.file);
918
- if (!fs.existsSync(filePath)) {
919
- console.error(`Error: File not found: ${filePath}`);
920
- process.exit(1);
921
- }
922
- const source = fs.readFileSync(filePath, "utf-8");
923
- const fileName = path.basename(filePath);
924
- const title = path.basename(filePath, path.extname(filePath));
925
- console.log(`Parsing ${fileName}...`);
926
- const ast = parse(source);
927
- if (ast.errors && ast.errors.length > 0) {
928
- console.error("Parse errors:");
929
- for (const err of ast.errors) {
930
- console.error(` ${err.message || err}`);
931
- }
932
- process.exit(1);
933
- }
934
- const license = opts.license || "free";
935
- const provenance = generateProvenance(source, ast, {
936
- author: opts.author || "anonymous",
937
- license
938
- });
939
- console.log(
940
- `Provenance: ${provenance.publishMode} | hash: ${provenance.hash.slice(0, 12)}... | license: ${provenance.license}`
941
- );
942
- if (provenance.imports.length > 0) {
943
- const importedLicenses = provenance.imports.map((imp) => ({
944
- path: imp.path,
945
- license: "free"
946
- }));
947
- const licenseCheck = checkLicenseCompatibility(license, importedLicenses);
948
- if (licenseCheck.warnings.length > 0) {
949
- for (const w of licenseCheck.warnings) {
950
- console.log(` Warning: ${w}`);
951
- }
952
- }
953
- if (!licenseCheck.compatible) {
954
- console.error("License compatibility errors:");
955
- for (const e of licenseCheck.errors) {
956
- console.error(` ${e}`);
957
- }
958
- process.exit(1);
959
- }
960
- if (licenseCheck.forcedLicense) {
961
- console.log(` License forced to "${licenseCheck.forcedLicense}" by imports`);
962
- }
963
- }
964
- const serverUrl = opts.serverUrl.replace(/\/$/, "");
965
- if (opts.publish) {
966
- const priceWei = ethToWei(opts.price || "0");
967
- const importChain = provenance.imports.map((imp, i) => ({
968
- contentHash: imp.hash || `import-${i}`,
969
- author: imp.author || imp.path,
970
- depth: 1,
971
- children: []
972
- }));
973
- const revenuePreview = calculateRevenueDistribution(
974
- priceWei,
975
- provenance.author || "anonymous",
976
- importChain
977
- );
978
- console.log("\nRevenue distribution:");
979
- for (const line of formatRevenueDistribution(revenuePreview)) {
980
- console.log(` ${line}`);
981
- }
982
- console.log(`
983
- Publishing to protocol at ${serverUrl}...`);
984
- const metadataBody = JSON.stringify({
985
- contentHash: provenance.hash,
986
- provenance: {
987
- hash: provenance.hash,
988
- author: provenance.author,
989
- license: provenance.license,
990
- publishMode: provenance.publishMode,
991
- imports: provenance.imports,
992
- created: provenance.created
993
- }
994
- });
995
- try {
996
- const metaRes = await fetch(`${serverUrl}/api/protocol/metadata`, {
997
- method: "POST",
998
- headers: { "Content-Type": "application/json" },
999
- body: metadataBody
1000
- });
1001
- if (!metaRes.ok) {
1002
- const text = await metaRes.text();
1003
- console.error(`Metadata storage failed (${metaRes.status}): ${text}`);
1004
- process.exit(1);
1005
- }
1006
- } catch (err) {
1007
- const msg = err instanceof Error ? err.message : String(err);
1008
- console.error(`Metadata storage failed: ${msg}`);
1009
- process.exit(1);
1010
- }
1011
- const protocolBody = JSON.stringify({
1012
- contentHash: provenance.hash,
1013
- author: provenance.author || "anonymous",
1014
- importHashes: provenance.imports.map((imp) => imp.hash).filter(Boolean),
1015
- license: provenance.license,
1016
- publishMode: provenance.publishMode,
1017
- price: opts.price || "0",
1018
- referralBps: PROTOCOL_CONSTANTS.DEFAULT_REFERRAL_BPS,
1019
- metadataURI: `${serverUrl}/metadata/${provenance.hash}`,
1020
- mintAsNFT: opts.mintNft,
1021
- // Include scene data for CDN deploy
1022
- code: source,
1023
- title,
1024
- description: `Published from CLI: ${fileName}`
1025
- });
1026
- try {
1027
- const pubRes = await fetch(`${serverUrl}/api/protocol`, {
1028
- method: "POST",
1029
- headers: { "Content-Type": "application/json" },
1030
- body: protocolBody
1031
- });
1032
- if (!pubRes.ok) {
1033
- const text = await pubRes.text();
1034
- console.error(`Protocol publish failed (${pubRes.status}): ${text}`);
1035
- process.exit(1);
1036
- }
1037
- const pubResult = await pubRes.json();
1038
- console.log("\nPublished to HoloScript Protocol!");
1039
- console.log(` Hash: ${pubResult.contentHash}`);
1040
- console.log(` ID: ${pubResult.protocolId}`);
1041
- console.log(` Collect: ${pubResult.collectUrl}`);
1042
- console.log(` Registry: ${pubResult.registryUrl}`);
1043
- if (pubResult.sceneUrl) {
1044
- console.log(` Scene: ${pubResult.sceneUrl}`);
1045
- }
1046
- if (pubResult.embedUrl) {
1047
- console.log(` Embed: ${pubResult.embedUrl}`);
1048
- }
1049
- if (priceWei > 0n) {
1050
- console.log(` Price: ${opts.price} ETH`);
1051
- } else {
1052
- console.log(" Price: Free collect");
1053
- }
1054
- if (opts.mintNft) {
1055
- console.log(" NFT: Zora mint requested");
1056
- }
1057
- } catch (err) {
1058
- const msg = err instanceof Error ? err.message : String(err);
1059
- console.error(`Protocol publish failed: ${msg}`);
1060
- console.error(
1061
- "Is the server running? Try: holoscript deploy <file> --publish --server http://localhost:3000"
1062
- );
1063
- process.exit(1);
1064
- }
1065
- return;
1066
- }
1067
- const endpoint = `${serverUrl}/api/deploy`;
1068
- console.log(`Deploying to ${serverUrl}...`);
1069
- const body = JSON.stringify({
1070
- code: source,
1071
- title,
1072
- description: `Deployed from CLI: ${fileName}`,
1073
- author: provenance.author,
1074
- license: provenance.license,
1075
- provenance: {
1076
- hash: provenance.hash,
1077
- publishMode: provenance.publishMode,
1078
- imports: provenance.imports
1079
- }
1080
- });
1081
- try {
1082
- const res = await fetch(endpoint, {
1083
- method: "POST",
1084
- headers: { "Content-Type": "application/json" },
1085
- body
1086
- });
1087
- if (!res.ok) {
1088
- const text = await res.text();
1089
- console.error(`Deploy failed (${res.status}): ${text}`);
1090
- process.exit(1);
1091
- }
1092
- const result = await res.json();
1093
- console.log("\nDeployed successfully!");
1094
- console.log(` ID: ${result.id}`);
1095
- console.log(` URL: ${result.url}`);
1096
- console.log(` Embed: ${result.embed}`);
1097
- console.log(` API: ${result.api}`);
1098
- if (result.provenance) {
1099
- console.log(` Mode: ${result.provenance.publishMode}`);
1100
- console.log(` Hash: ${result.provenance.hash}`);
1101
- }
1102
- if (opts.share) {
1103
- console.log(`
1104
- Share URL: ${result.url}`);
1105
- }
1106
- } catch (err) {
1107
- const msg = err instanceof Error ? err.message : String(err);
1108
- console.error(`Deploy failed: ${msg}`);
1109
- console.error(
1110
- "Is the server running? Try: holoscript deploy <file> --server http://localhost:3000"
1111
- );
1112
- process.exit(1);
1113
- }
1114
- }
1115
- function absorbScript(opts) {
1116
- if (!opts.file) {
1117
- console.error("Error: No source file specified");
1118
- process.exit(1);
1119
- }
1120
- const filePath = path.resolve(opts.file);
1121
- if (!fs.existsSync(filePath)) {
1122
- console.error(`Error: File not found: ${filePath}`);
1123
- process.exit(1);
1124
- }
1125
- const content = fs.readFileSync(filePath, "utf-8");
1126
- const ext = path.extname(filePath).toLowerCase();
1127
- const langMap = {
1128
- ".py": "python",
1129
- ".ts": "typescript",
1130
- ".tsx": "typescript",
1131
- ".js": "javascript",
1132
- ".jsx": "javascript",
1133
- ".mjs": "javascript"
1134
- };
1135
- const language = langMap[ext];
1136
- if (!language) {
1137
- console.error(
1138
- `Error: Unsupported file type '${ext}'. Supported: .py, .ts, .tsx, .js, .jsx, .mjs`
1139
- );
1140
- process.exit(1);
1141
- }
1142
- const outputPath = opts.output || filePath.replace(/\.\w+$/, ".hsplus");
1143
- console.log(
1144
- `[holoscript absorb] ${path.basename(filePath)} (${language}) \u2192 ${path.basename(outputPath)}`
1145
- );
1146
- const processor = new AbsorbProcessor();
1147
- const result = processor.absorb({ language, filePath, content });
1148
- fs.writeFileSync(outputPath, result.generatedHSPlus, "utf-8");
1149
- console.log(`[holoscript absorb] Extracted:`);
1150
- console.log(` ${result.functions.length} functions`);
1151
- console.log(` ${result.classes.length} classes`);
1152
- console.log(` ${result.imports.length} imports`);
1153
- console.log(` ${result.constants.length} constants`);
1154
- if (result.warnings.length > 0) {
1155
- console.log(`
1156
- Warnings:`);
1157
- for (const w of result.warnings) {
1158
- console.log(` \u26A0 ${w}`);
1159
- }
1160
- }
1161
- console.log(`[holoscript absorb] Written to ${outputPath}`);
1162
- }
1163
- function findGitRoot(startDir) {
1164
- let dir = startDir;
1165
- while (dir !== path.dirname(dir)) {
1166
- if (fs.existsSync(path.join(dir, ".git"))) return dir;
1167
- dir = path.dirname(dir);
1168
- }
1169
- return startDir;
1170
- }
1171
- function sleep(ms) {
1172
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1173
- }
1174
- function extractEconomyConfig(ast) {
1175
- const result = {};
1176
- let found = false;
1177
- function walk(node) {
1178
- if (!node || typeof node !== "object") return;
1179
- const obj = node;
1180
- if (Array.isArray(obj.traits)) {
1181
- for (const trait of obj.traits) {
1182
- const t = trait;
1183
- if (t.name === "economy" && typeof t.config === "object" && t.config) {
1184
- const cfg = t.config;
1185
- if (typeof cfg.budget === "number") result.budget = cfg.budget;
1186
- if (typeof cfg.default_spend_limit === "number")
1187
- result.default_spend_limit = cfg.default_spend_limit;
1188
- if (typeof cfg.initial_balance === "number") result.initial_balance = cfg.initial_balance;
1189
- if (typeof cfg.task_completion_reward === "number")
1190
- result.task_completion_reward = cfg.task_completion_reward;
1191
- found = true;
1192
- }
1193
- }
1194
- }
1195
- if (Array.isArray(obj.directives)) {
1196
- for (const dir of obj.directives) {
1197
- const d = dir;
1198
- if ((d.type === "trait" || d.type === "ObjectTrait") && d.name === "economy" && typeof d.config === "object" && d.config) {
1199
- const cfg = d.config;
1200
- if (typeof cfg.budget === "number") result.budget = cfg.budget;
1201
- if (typeof cfg.default_spend_limit === "number")
1202
- result.default_spend_limit = cfg.default_spend_limit;
1203
- if (typeof cfg.initial_balance === "number") result.initial_balance = cfg.initial_balance;
1204
- if (typeof cfg.task_completion_reward === "number")
1205
- result.task_completion_reward = cfg.task_completion_reward;
1206
- found = true;
1207
- }
1208
- }
1209
- }
1210
- if (Array.isArray(obj.body)) obj.body.forEach(walk);
1211
- if (Array.isArray(obj.children)) obj.children.forEach(walk);
1212
- if (Array.isArray(obj.objects)) obj.objects.forEach(walk);
1213
- }
1214
- walk(ast);
1215
- return found ? result : void 0;
1216
- }
1217
- function loadRuntimeSkillActions(skillsDir, opts, host, repoRoot, debug = false) {
1218
- const actions = {};
1219
- if (!fs.existsSync(skillsDir)) return actions;
1220
- const files = fs.readdirSync(skillsDir).filter((name) => name.endsWith(".hsplus")).map((name) => path.join(skillsDir, name));
1221
- for (const file of files) {
1222
- const content = fs.readFileSync(file, "utf-8");
1223
- const actionRe = /action\s+"([^"]+)"\s*\{([\s\S]*?)\}/g;
1224
- let match;
1225
- while ((match = actionRe.exec(content)) !== null) {
1226
- const actionName = match[1].trim();
1227
- const block = match[2];
1228
- const commandMatch = block.match(/command\s*:\s*["']([^"']+)["']/);
1229
- if (!commandMatch) continue;
1230
- const command = commandMatch[1].trim();
1231
- const argMatch = block.match(/args\s*:\s*\[([^\]]*)\]/);
1232
- const bakedArgs = [];
1233
- if (argMatch) {
1234
- const argValueRe = /["']([^"']+)["']/g;
1235
- let argToken;
1236
- while ((argToken = argValueRe.exec(argMatch[1])) !== null) {
1237
- bakedArgs.push(argToken[1]);
1238
- }
1239
- }
1240
- actions[actionName] = async (params, bb, ctx) => {
1241
- if (!opts.allowShell) {
1242
- bb.skill_error = `Skill ${actionName} blocked: shell disabled`;
1243
- return false;
1244
- }
1245
- if (opts.allowedShellCommands.length > 0) {
1246
- const executable = command.split(/\s+/)[0].toLowerCase();
1247
- const allowed = opts.allowedShellCommands.some((c) => c.toLowerCase() === executable);
1248
- if (!allowed) {
1249
- bb.skill_error = `Skill ${actionName} blocked: command ${executable} not allowlisted`;
1250
- return false;
1251
- }
1252
- }
1253
- const runtimeArgs = Array.isArray(params.args) ? params.args.filter((v) => typeof v === "string") : [];
1254
- const timeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) ? Math.max(1e3, Math.min(3e5, Math.floor(params.timeoutMs))) : 6e4;
1255
- const result = await host.exec(command, [...bakedArgs, ...runtimeArgs], {
1256
- cwd: repoRoot,
1257
- timeoutMs
1258
- });
1259
- bb.skill_last_action = actionName;
1260
- bb.skill_last_code = result.code ?? -1;
1261
- bb.skill_last_stdout = result.stdout.slice(0, 5e4);
1262
- bb.skill_last_stderr = result.stderr.slice(0, 5e4);
1263
- ctx.emit("daemon:skill:executed", { actionName, code: result.code, file });
1264
- return result.code === 0;
1265
- };
1266
- }
1267
- }
1268
- if (debug) {
1269
- console.log(
1270
- `[daemon] Loaded ${Object.keys(actions).length} runtime skill action(s) from ${skillsDir}`
1271
- );
1272
- }
1273
- return actions;
1274
- }
1275
- async function daemonScript(opts) {
1276
- if (!opts.file) {
1277
- console.error("Error: No composition file specified");
1278
- process.exit(1);
1279
- }
1280
- const filePath = path.resolve(opts.file);
1281
- if (!fs.existsSync(filePath)) {
1282
- console.error(`Error: File not found: ${filePath}`);
1283
- process.exit(1);
1284
- }
1285
- const { createDaemonActions, getDaemonFileState } = await import('@holoscript/absorb-service/daemon');
1286
- const repoRoot = findGitRoot(path.dirname(filePath));
1287
- const source = fs.readFileSync(filePath, "utf-8");
1288
- const parseResult = parse(source);
1289
- if (!parseResult.success || !parseResult.ast) {
1290
- const errors = parseResult.errors?.map((e) => e.message).join(", ") || "unknown";
1291
- console.error(`[daemon] Parse failed: ${errors}`);
1292
- process.exit(1);
1293
- }
1294
- const compositionAST = parseResult.ast;
1295
- console.log(`[daemon] Composition: ${path.basename(filePath)}`);
1296
- console.log(`[daemon] Repo root: ${repoRoot}`);
1297
- console.log(
1298
- `[daemon] Cycles: ${opts.cycles} | Commit: ${opts.commit} | Timeout: ${opts.timeout}min | Provider: ${opts.provider} | Model: ${opts.model} | Tool profile: ${opts.toolProfile} | Always-on: ${opts.alwaysOn} | Cycle interval: ${opts.cycleIntervalSec}s`
1299
- );
1300
- const stateDir = path.join(repoRoot, ".holoscript");
1301
- if (!fs.existsSync(stateDir)) fs.mkdirSync(stateDir, { recursive: true });
1302
- const lockFile = path.join(stateDir, "daemon.lock");
1303
- const lockData = { pid: process.pid, time: Date.now(), heartbeat: Date.now() };
1304
- if (fs.existsSync(lockFile)) {
1305
- try {
1306
- const existing = JSON.parse(fs.readFileSync(lockFile, "utf-8"));
1307
- const staleMs = 12e4;
1308
- if (Date.now() - existing.heartbeat < staleMs) {
1309
- console.error(
1310
- `[daemon] Another daemon is running (PID ${existing.pid}). Remove ${lockFile} to force.`
1311
- );
1312
- process.exit(1);
1313
- }
1314
- console.log(`[daemon] Reclaiming stale lock from PID ${existing.pid}`);
1315
- } catch {
1316
- }
1317
- }
1318
- fs.writeFileSync(lockFile, JSON.stringify(lockData), "utf-8");
1319
- const heartbeatTimer = setInterval(() => {
1320
- try {
1321
- fs.writeFileSync(lockFile, JSON.stringify({ ...lockData, heartbeat: Date.now() }), "utf-8");
1322
- } catch {
1323
- }
1324
- }, 3e4);
1325
- const fileWatchers = [];
1326
- const cleanup = () => {
1327
- clearInterval(heartbeatTimer);
1328
- for (const watcher of fileWatchers) {
1329
- try {
1330
- watcher.close();
1331
- } catch {
1332
- }
1333
- }
1334
- try {
1335
- fs.rmSync(lockFile, { force: true });
1336
- } catch {
1337
- }
1338
- };
1339
- process.once("SIGINT", cleanup);
1340
- process.once("SIGTERM", cleanup);
1341
- process.once("uncaughtException", (err) => {
1342
- cleanup();
1343
- console.error("[daemon] Uncaught:", err);
1344
- process.exit(1);
1345
- });
1346
- process.once("unhandledRejection", (err) => {
1347
- cleanup();
1348
- console.error("[daemon] Unhandled:", err);
1349
- process.exit(1);
1350
- });
1351
- const host = {
1352
- readFile: (p) => fs.readFileSync(path.resolve(repoRoot, p), "utf-8"),
1353
- writeFile: (p, c) => {
1354
- const resolved = path.resolve(repoRoot, p);
1355
- fs.mkdirSync(path.dirname(resolved), { recursive: true });
1356
- fs.writeFileSync(resolved, c, "utf-8");
1357
- },
1358
- exists: (p) => fs.existsSync(path.resolve(repoRoot, p)),
1359
- exec: (cmd, args = [], execOpts = {}) => new Promise((resolve2, reject) => {
1360
- const child = spawn(cmd, args, {
1361
- cwd: execOpts.cwd ?? repoRoot,
1362
- shell: true,
1363
- stdio: ["ignore", "pipe", "pipe"]
1364
- });
1365
- let stdout = "";
1366
- let stderr = "";
1367
- let timer = null;
1368
- child.stdout?.on("data", (d) => {
1369
- stdout += d.toString("utf-8");
1370
- });
1371
- child.stderr?.on("data", (d) => {
1372
- stderr += d.toString("utf-8");
1373
- });
1374
- if (execOpts.timeoutMs && execOpts.timeoutMs > 0) {
1375
- timer = setTimeout(() => {
1376
- try {
1377
- child.kill("SIGKILL");
1378
- } catch {
1379
- }
1380
- }, execOpts.timeoutMs);
1381
- }
1382
- child.on("close", (code) => {
1383
- if (timer) clearTimeout(timer);
1384
- resolve2({ code, stdout, stderr });
1385
- });
1386
- child.on("error", reject);
1387
- })
1388
- };
1389
- const skillsDirRel = opts.skillsDir || "compositions/skills";
1390
- const skillsDirAbs = path.resolve(repoRoot, skillsDirRel);
1391
- if (!fs.existsSync(skillsDirAbs)) {
1392
- fs.mkdirSync(skillsDirAbs, { recursive: true });
1393
- }
1394
- let runtimeSkillActions = loadRuntimeSkillActions(skillsDirAbs, opts, host, repoRoot, opts.debug);
1395
- let activeRuntime = null;
1396
- const reloadRuntimeSkills = () => {
1397
- runtimeSkillActions = loadRuntimeSkillActions(skillsDirAbs, opts, host, repoRoot, opts.debug);
1398
- if (activeRuntime) {
1399
- for (const [name, handler] of Object.entries(runtimeSkillActions)) {
1400
- activeRuntime.registerAction(name, handler);
1401
- }
1402
- }
1403
- };
1404
- try {
1405
- const watcher = fs.watch(skillsDirAbs, { persistent: false }, () => {
1406
- reloadRuntimeSkills();
1407
- });
1408
- fileWatchers.push(watcher);
1409
- } catch (error) {
1410
- if (opts.debug) {
1411
- console.warn(
1412
- `[daemon] Skill watcher unavailable for ${skillsDirAbs}: ${error.message}`
1413
- );
1414
- }
1415
- }
1416
- const llm = createDaemonLLMProvider(opts);
1417
- const defaultFocusRotation = [
1418
- "typefix",
1419
- "coverage",
1420
- "typefix",
1421
- "lint",
1422
- "target-sweep",
1423
- "trait-sampling",
1424
- "runtime-matrix",
1425
- "absorb-roundtrip",
1426
- "typefix",
1427
- "all"
1428
- ];
1429
- const focusRotation = getASTBlackboardValue(compositionAST, "focus_rotation") ?? defaultFocusRotation;
1430
- const providerRotationEnabled = getASTBlackboardValue(compositionAST, "provider_rotation_enabled") ?? opts.providerRotation;
1431
- const rotationProviders = getASTBlackboardValue(compositionAST, "provider_rotation") ?? ["anthropic", "xai"];
1432
- const quarantineThreshold = getASTBlackboardValue(compositionAST, "quarantine_threshold") ?? 3;
1433
- const economyConfig = extractEconomyConfig(compositionAST);
1434
- if (economyConfig && opts.debug) {
1435
- console.log(`[daemon] Economy config from composition:`, economyConfig);
1436
- }
1437
- const config = {
1438
- repoRoot,
1439
- commit: opts.commit,
1440
- model: opts.model,
1441
- provider: opts.provider,
1442
- toolProfile: opts.toolProfile,
1443
- verbose: opts.debug,
1444
- trial: opts.trial,
1445
- focusRotation,
1446
- stateDir,
1447
- quarantineThreshold,
1448
- skillsDir: opts.skillsDir,
1449
- toolPolicy: {
1450
- allowShell: opts.allowShell,
1451
- allowedShellCommands: opts.allowedShellCommands,
1452
- allowedHosts: opts.allowedHosts,
1453
- allowedPaths: opts.allowedPaths
1454
- },
1455
- economyConfig,
1456
- sessionId: opts.sessionId
1457
- };
1458
- const stateFile = path.join(stateDir, "daemon-state.json");
1459
- const telemetryFile = path.join(stateDir, "daemon-telemetry.jsonl");
1460
- const daemonStateDefaults = {
1461
- totalCycles: 0,
1462
- focusIndex: 0,
1463
- bestQuality: 0,
1464
- lastQuality: 0,
1465
- totalCostUSD: 0,
1466
- totalInputTokens: 0,
1467
- totalOutputTokens: 0,
1468
- typeErrorBaseline: 0,
1469
- lastFocus: "",
1470
- lastCycleTimeISO: ""
1471
- };
1472
- let daemonState = { ...daemonStateDefaults };
1473
- if (fs.existsSync(stateFile)) {
1474
- try {
1475
- daemonState = { ...daemonState, ...JSON.parse(fs.readFileSync(stateFile, "utf-8")) };
1476
- } catch {
1477
- }
1478
- }
1479
- const fileStateFile = path.join(stateDir, "daemon-file-state.json");
1480
- if (fs.existsSync(fileStateFile)) {
1481
- try {
1482
- const fileState = JSON.parse(fs.readFileSync(fileStateFile, "utf-8"));
1483
- config.committedFiles = fileState.committed || [];
1484
- config.failedFiles = fileState.failures || {};
1485
- } catch {
1486
- }
1487
- }
1488
- if (daemonState.typeErrorBaseline > 0) {
1489
- config.typeErrorBaseline = daemonState.typeErrorBaseline;
1490
- }
1491
- let daemonResult = createDaemonActions(host, llm, config);
1492
- let actions = daemonResult.actions;
1493
- try {
1494
- await llm.chat({ system: "reply ok", prompt: "ok", maxTokens: 1 });
1495
- console.log("[daemon] API key validated");
1496
- } catch (err) {
1497
- console.error(`[daemon] API key validation failed: ${err.message}`);
1498
- cleanup();
1499
- process.exit(1);
1500
- }
1501
- let convergenceStreak = 0;
1502
- const CONVERGENCE_THRESHOLD = 3;
1503
- let cycle = 0;
1504
- while (opts.alwaysOn || cycle < opts.cycles) {
1505
- let focusIdx = daemonState.focusIndex % focusRotation.length;
1506
- let focus = opts.focus || focusRotation[focusIdx];
1507
- if (!opts.focus) {
1508
- const wisdomFile = path.join(stateDir, "accumulated-wisdom.json");
1509
- let wisdomEntries = [];
1510
- try {
1511
- if (fs.existsSync(wisdomFile)) {
1512
- wisdomEntries = JSON.parse(fs.readFileSync(wisdomFile, "utf-8"));
1513
- }
1514
- } catch {
1515
- }
1516
- const STALE_THRESHOLD = 3;
1517
- let attempts = 0;
1518
- while (attempts < focusRotation.length) {
1519
- const focusHistory = wisdomEntries.filter((w) => w.focus === focus);
1520
- const lastN = focusHistory.slice(-STALE_THRESHOLD);
1521
- const isStale = lastN.length >= STALE_THRESHOLD && lastN.every((w) => (w.delta || 0) === 0);
1522
- if (!isStale) break;
1523
- console.log(
1524
- `[daemon] Skipping stale focus "${focus}" (${STALE_THRESHOLD} consecutive zero-delta cycles)`
1525
- );
1526
- focusIdx = (focusIdx + 1) % focusRotation.length;
1527
- focus = focusRotation[focusIdx];
1528
- attempts++;
1529
- }
1530
- }
1531
- config.cycleFocus = focus;
1532
- config.daemonFile = filePath;
1533
- config.qualityBefore = daemonState.lastQuality;
1534
- const cycleStart = Date.now();
1535
- if (providerRotationEnabled) {
1536
- const cycleProvider = rotationProviders[cycle % rotationProviders.length];
1537
- const cycleModel = defaultModelForProvider(cycleProvider);
1538
- const cycleToolProfile = defaultToolProfileForProvider(cycleProvider);
1539
- const cycleOpts = {
1540
- ...opts,
1541
- provider: cycleProvider,
1542
- model: cycleModel};
1543
- const cycleLlm = createDaemonLLMProvider(cycleOpts);
1544
- config.provider = cycleProvider;
1545
- config.model = cycleModel;
1546
- config.toolProfile = cycleToolProfile;
1547
- daemonResult = createDaemonActions(host, cycleLlm, config);
1548
- actions = daemonResult.actions;
1549
- console.log(
1550
- `
1551
- [daemon] === Cycle ${cycle + 1}${opts.alwaysOn ? "" : `/${opts.cycles}`} | Focus: ${focus} | Provider: ${cycleProvider} (${cycleModel}) ===`
1552
- );
1553
- } else {
1554
- console.log(
1555
- `
1556
- [daemon] === Cycle ${cycle + 1}${opts.alwaysOn ? "" : `/${opts.cycles}`} | Focus: ${focus} ===`
1557
- );
1558
- }
1559
- const cycleAST = JSON.parse(JSON.stringify(compositionAST));
1560
- materializeTraits(cycleAST);
1561
- const bbValues = {
1562
- focus,
1563
- cycleNumber: daemonState.totalCycles + cycle,
1564
- daemon_file: filePath,
1565
- quality_before: daemonState.lastQuality
1566
- };
1567
- setASTBlackboard(cycleAST, bbValues);
1568
- if (opts.debug) {
1569
- console.log(`[daemon] Blackboard injection: focus=${focus}, daemon_file=${filePath}`);
1570
- }
1571
- const runtime = createHeadlessRuntime(cycleAST, {
1572
- profile: HEADLESS_PROFILE,
1573
- tickRate: 10,
1574
- debug: opts.debug,
1575
- hostCapabilities: createNodeHostCapabilities(repoRoot)
1576
- });
1577
- activeRuntime = runtime;
1578
- for (const [name, handler] of Object.entries(actions)) {
1579
- runtime.registerAction(name, handler);
1580
- }
1581
- for (const [name, handler] of Object.entries(runtimeSkillActions)) {
1582
- runtime.registerAction(name, handler);
1583
- }
1584
- daemonResult.wireTraitListeners(runtime);
1585
- const maxWaitMs = opts.timeout * 60 * 1e3;
1586
- const btResult = await new Promise(
1587
- (resolve2) => {
1588
- let resolved = false;
1589
- const timeout = setTimeout(() => {
1590
- if (!resolved) {
1591
- resolved = true;
1592
- resolve2({ status: "timeout", blackboard: {} });
1593
- }
1594
- }, maxWaitMs);
1595
- runtime.on("bt_complete", (payload) => {
1596
- if (!resolved) {
1597
- resolved = true;
1598
- clearTimeout(timeout);
1599
- const p = payload;
1600
- resolve2({
1601
- status: p?.status || "unknown",
1602
- blackboard: p?.blackboard || {}
1603
- });
1604
- }
1605
- });
1606
- runtime.start();
1607
- }
1608
- );
1609
- const btStatus = btResult.status;
1610
- const btBlackboard = btResult.blackboard;
1611
- const durationSec = ((Date.now() - cycleStart) / 1e3).toFixed(1);
1612
- const stats = runtime.getStats();
1613
- const inputTokens = btBlackboard.inputTokens || 0;
1614
- const outputTokens = btBlackboard.outputTokens || 0;
1615
- const qualityAfter = btBlackboard.quality_after || daemonState.lastQuality;
1616
- const costUSD = inputTokens * 3 / 1e6 + outputTokens * 15 / 1e6;
1617
- const committed = btBlackboard.committed_count || 0;
1618
- const qualityDelta = qualityAfter - (config.qualityBefore || 0);
1619
- runtime.emit("daemon:cycle_raw_telemetry", {
1620
- focus,
1621
- ticks: stats.updateCount,
1622
- inputTokens,
1623
- outputTokens,
1624
- qualityBefore: daemonState.lastQuality,
1625
- qualityAfter,
1626
- costUSD,
1627
- committed: committed > 0
1628
- });
1629
- runtime.emit("daemon:raw_quality", { value: qualityAfter, focus });
1630
- const totalTokens = inputTokens + outputTokens;
1631
- const costEfficiency = costUSD > 0 ? Math.min(1, qualityAfter * totalTokens / (costUSD * 5e5)) : qualityAfter;
1632
- runtime.emit("feedback:update_metric", { name: "cost_efficiency", value: costEfficiency });
1633
- const candidateCount = Array.isArray(btBlackboard.candidates) ? btBlackboard.candidates.length : 0;
1634
- const emergenceRatio = candidateCount > 0 ? Math.min(1, committed / candidateCount) : 0;
1635
- runtime.emit("feedback:update_metric", { name: "emergence_ratio", value: emergenceRatio });
1636
- const wisdomCount = btBlackboard.wisdomCount || 0;
1637
- const regenerativeHealth = Math.min(1, wisdomCount / 200);
1638
- runtime.emit("feedback:update_metric", {
1639
- name: "regenerative_health",
1640
- value: regenerativeHealth
1641
- });
1642
- runtime.stop();
1643
- activeRuntime = null;
1644
- const telemetryRecord = {
1645
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1646
- cycleNumber: daemonState.totalCycles + 1,
1647
- focus,
1648
- provider: config.provider,
1649
- model: config.model,
1650
- btStatus,
1651
- ticks: stats.updateCount,
1652
- inputTokens,
1653
- outputTokens,
1654
- totalTokens,
1655
- costUSD,
1656
- qualityBefore: config.qualityBefore || 0,
1657
- qualityAfter,
1658
- qualityDelta,
1659
- typeErrors: btBlackboard.quality_typeErrors ?? null,
1660
- testsPassed: btBlackboard.quality_testsPassed ?? null,
1661
- testsTotal: btBlackboard.quality_testsTotal ?? null,
1662
- committedCount: committed,
1663
- candidateCount,
1664
- wisdomCount,
1665
- durationSec: Number(durationSec)
1666
- };
1667
- fs.appendFileSync(telemetryFile, `${JSON.stringify(telemetryRecord)}
1668
- `, "utf-8");
1669
- console.log(
1670
- `[daemon] Cycle ${cycle + 1} done in ${durationSec}s | ${stats.updateCount} ticks | BT: ${btStatus} | quality: ${qualityAfter.toFixed(3)}`
1671
- );
1672
- daemonState.totalCycles++;
1673
- daemonState.focusIndex = (focusIdx + 1) % focusRotation.length;
1674
- daemonState.lastQuality = qualityAfter;
1675
- if (qualityAfter > daemonState.bestQuality) {
1676
- daemonState.bestQuality = qualityAfter;
1677
- }
1678
- daemonState.totalInputTokens += inputTokens;
1679
- daemonState.totalOutputTokens += outputTokens;
1680
- daemonState.totalCostUSD += costUSD;
1681
- daemonState.lastFocus = focus;
1682
- daemonState.lastCycleTimeISO = (/* @__PURE__ */ new Date()).toISOString();
1683
- const cycleTypeErrors = btBlackboard.typeErrorBaseline || btBlackboard.typeErrorCount || 0;
1684
- if (daemonState.typeErrorBaseline === 0 && cycleTypeErrors > 0) {
1685
- daemonState.typeErrorBaseline = cycleTypeErrors;
1686
- config.typeErrorBaseline = cycleTypeErrors;
1687
- console.log(`[daemon] Established type error baseline: ${cycleTypeErrors}`);
1688
- }
1689
- fs.writeFileSync(stateFile, JSON.stringify(daemonState, null, 2), "utf-8");
1690
- const fileState = getDaemonFileState(repoRoot);
1691
- fs.writeFileSync(fileStateFile, JSON.stringify(fileState, null, 2), "utf-8");
1692
- if (qualityDelta <= 0 && committed === 0) {
1693
- convergenceStreak++;
1694
- if (convergenceStreak >= CONVERGENCE_THRESHOLD) {
1695
- console.log(
1696
- `[daemon] Plateau detected \u2014 ${convergenceStreak} consecutive zero-delta cycles. Escalating strategy.`
1697
- );
1698
- convergenceStreak = 0;
1699
- const oldThreshold = config.quarantineThreshold || 3;
1700
- config.quarantineThreshold = oldThreshold + 2;
1701
- console.log(
1702
- `[daemon] Raised quarantine threshold: ${oldThreshold} \u2192 ${config.quarantineThreshold}`
1703
- );
1704
- if (!opts.focus) {
1705
- const typefixIdx = focusRotation.indexOf("typefix");
1706
- if (typefixIdx >= 0) {
1707
- daemonState.focusIndex = typefixIdx;
1708
- console.log(`[daemon] Forcing next cycle to 'typefix' focus`);
1709
- }
1710
- }
1711
- if (config.quarantineThreshold >= quarantineThreshold + 4) {
1712
- console.log(
1713
- `[daemon] Escalated twice with no progress. Stopping to avoid wasting tokens.`
1714
- );
1715
- break;
1716
- }
1717
- }
1718
- } else {
1719
- convergenceStreak = 0;
1720
- }
1721
- cycle++;
1722
- if (opts.alwaysOn && opts.cycleIntervalSec > 0) {
1723
- console.log(`[daemon] Sleeping ${opts.cycleIntervalSec}s before next cycle...`);
1724
- await sleep(opts.cycleIntervalSec * 1e3);
1725
- }
1726
- }
1727
- cleanup();
1728
- console.log(`
1729
- [daemon] Completed ${cycle} cycle(s).`);
1730
- console.log(
1731
- `[daemon] Best quality: ${daemonState.bestQuality.toFixed(3)} | Total cost: $${daemonState.totalCostUSD.toFixed(3)} | Total cycles: ${daemonState.totalCycles}`
1732
- );
1733
- }
1734
- function materializeTraits(ast) {
1735
- const walk = (node) => {
1736
- if (!node || typeof node !== "object") return;
1737
- const n = node;
1738
- const traits = /* @__PURE__ */ new Map();
1739
- if (Array.isArray(n.directives)) {
1740
- for (const directive of n.directives) {
1741
- const d = directive;
1742
- if (d.type === "trait" && typeof d.name === "string") {
1743
- traits.set(d.name, d.config ?? {});
1744
- }
1745
- }
1746
- }
1747
- if (traits.size > 0) {
1748
- n.traits = traits;
1749
- } else if (n.traits && !(n.traits instanceof Map)) {
1750
- delete n.traits;
1751
- }
1752
- for (const key of ["body", "children", "nodes", "members"]) {
1753
- if (Array.isArray(n[key])) {
1754
- n[key].forEach(walk);
1755
- }
1756
- }
1757
- if (n.root && typeof n.root === "object") {
1758
- walk(n.root);
1759
- }
1760
- };
1761
- walk(ast);
1762
- }
1763
- function setASTBlackboard(ast, values) {
1764
- const visited = /* @__PURE__ */ new WeakSet();
1765
- let found = false;
1766
- const walk = (node) => {
1767
- if (found || !node || typeof node !== "object") return;
1768
- const obj = node;
1769
- if (visited.has(obj)) return;
1770
- visited.add(obj);
1771
- if (obj.blackboard && typeof obj.blackboard === "object" && !Array.isArray(obj.blackboard)) {
1772
- Object.assign(obj.blackboard, values);
1773
- found = true;
1774
- return;
1775
- }
1776
- for (const key of Object.keys(obj)) {
1777
- const val = obj[key];
1778
- if (val && typeof val === "object") {
1779
- if (Array.isArray(val)) {
1780
- for (const item of val) walk(item);
1781
- } else if (val instanceof Map) {
1782
- for (const v of val.values()) walk(v);
1783
- } else {
1784
- walk(val);
1785
- }
1786
- }
1787
- if (found) return;
1788
- }
1789
- };
1790
- walk(ast);
1791
- }
1792
- function getASTBlackboardValue(ast, key) {
1793
- const visited = /* @__PURE__ */ new WeakSet();
1794
- let result = void 0;
1795
- const walk = (node) => {
1796
- if (result !== void 0 || !node || typeof node !== "object") return;
1797
- const obj = node;
1798
- if (visited.has(obj)) return;
1799
- visited.add(obj);
1800
- if (obj.blackboard && typeof obj.blackboard === "object" && !Array.isArray(obj.blackboard)) {
1801
- const bb = obj.blackboard;
1802
- if (key in bb) {
1803
- result = bb[key];
1804
- return;
1805
- }
1806
- }
1807
- for (const k of Object.keys(obj)) {
1808
- const val = obj[k];
1809
- if (val && typeof val === "object") {
1810
- if (Array.isArray(val)) {
1811
- for (const item of val) walk(item);
1812
- } else if (!(val instanceof Map)) {
1813
- walk(val);
1814
- }
1815
- }
1816
- if (result !== void 0) return;
1817
- }
1818
- };
1819
- walk(ast);
1820
- return result;
1821
- }
1822
- async function holoMeshDaemonScript(opts) {
1823
- if (!opts.file) {
1824
- console.error("Error: No composition file specified");
1825
- console.error("Usage: holoscript holomesh-daemon compositions/holomesh-agent.hsplus [options]");
1826
- process.exit(1);
1827
- }
1828
- const filePath = path.resolve(opts.file);
1829
- if (!fs.existsSync(filePath)) {
1830
- console.error(`Error: File not found: ${filePath}`);
1831
- process.exit(1);
1832
- }
1833
- if (!process.env.MCP_API_KEY) {
1834
- console.error("[holomesh-daemon] MCP_API_KEY environment variable is required");
1835
- process.exit(1);
1836
- }
1837
- const rawSource = fs.readFileSync(filePath, "utf-8");
1838
- const source = rawSource.replace(/^#(version|target|mode)\s+.*/gm, "");
1839
- const parseResult = parse(source);
1840
- if (!parseResult.success || !parseResult.ast) {
1841
- const errors = parseResult.errors?.map((e) => e.message).join(", ") || "unknown";
1842
- console.error(`[holomesh-daemon] Parse failed: ${errors}`);
1843
- process.exit(1);
1844
- }
1845
- const compositionAST = parseResult.ast;
1846
- const stateDir = path.resolve(path.dirname(filePath), ".holoscript");
1847
- if (!fs.existsSync(stateDir)) fs.mkdirSync(stateDir, { recursive: true });
1848
- const stateFile = path.join(stateDir, "holomesh-state.json");
1849
- console.log(`[holomesh-daemon] Composition: ${path.basename(filePath)}`);
1850
- console.log(`[holomesh-daemon] Cycles: ${opts.cycles} | Always-on: ${opts.alwaysOn}`);
1851
- const lockFile = path.join(stateDir, "holomesh-daemon.lock");
1852
- const lockData = { pid: process.pid, time: Date.now(), heartbeat: Date.now() };
1853
- if (fs.existsSync(lockFile)) {
1854
- try {
1855
- const existing = JSON.parse(fs.readFileSync(lockFile, "utf-8"));
1856
- if (Date.now() - existing.heartbeat < 12e4) {
1857
- console.error(
1858
- `[holomesh-daemon] Another daemon is running (PID ${existing.pid}). Remove ${lockFile} to force.`
1859
- );
1860
- process.exit(1);
1861
- }
1862
- console.log(`[holomesh-daemon] Reclaiming stale lock from PID ${existing.pid}`);
1863
- } catch {
1864
- }
1865
- }
1866
- fs.writeFileSync(lockFile, JSON.stringify(lockData), "utf-8");
1867
- const heartbeatTimer = setInterval(() => {
1868
- try {
1869
- fs.writeFileSync(lockFile, JSON.stringify({ ...lockData, heartbeat: Date.now() }), "utf-8");
1870
- } catch {
1871
- }
1872
- }, 3e4);
1873
- const cleanup = () => {
1874
- clearInterval(heartbeatTimer);
1875
- try {
1876
- fs.rmSync(lockFile, { force: true });
1877
- } catch {
1878
- }
1879
- };
1880
- process.once("SIGINT", cleanup);
1881
- process.once("SIGTERM", cleanup);
1882
- let createHoloMeshDaemonActions;
1883
- let HoloMeshOrchestratorClient;
1884
- try {
1885
- let actionsModule;
1886
- let clientModule;
1887
- try {
1888
- actionsModule = await import('@holoscript/mcp-server/holomesh/agent/holomesh-daemon-actions');
1889
- clientModule = await import('@holoscript/mcp-server/holomesh/orchestrator-client');
1890
- } catch {
1891
- const actionsPath = "../../../mcp-server/src/holomesh/agent/holomesh-daemon-actions";
1892
- const clientPath = "../../../mcp-server/src/holomesh/orchestrator-client";
1893
- actionsModule = await import(actionsPath);
1894
- clientModule = await import(clientPath);
1895
- }
1896
- createHoloMeshDaemonActions = actionsModule.createHoloMeshDaemonActions;
1897
- HoloMeshOrchestratorClient = clientModule.HoloMeshOrchestratorClient;
1898
- } catch (err) {
1899
- console.error(`[holomesh-daemon] Failed to import holomesh modules: ${err.message}`);
1900
- console.error("[holomesh-daemon] Ensure @holoscript/mcp-server is installed");
1901
- cleanup();
1902
- process.exit(1);
1903
- }
1904
- const meshConfig = {
1905
- orchestratorUrl: process.env.MCP_ORCHESTRATOR_URL || "https://mcp-orchestrator-production-45f9.up.railway.app",
1906
- apiKey: process.env.MCP_API_KEY,
1907
- workspace: process.env.HOLOMESH_WORKSPACE || "ai-ecosystem",
1908
- agentName: process.env.HOLOMESH_AGENT_NAME || "holomesh-agent",
1909
- discoveryIntervalMs: 5 * 60 * 1e3,
1910
- inboxIntervalMs: 60 * 1e3,
1911
- maxContributionsPerCycle: 5,
1912
- maxQueriesPerCycle: 3,
1913
- budgetCapUSD: parseFloat(process.env.HOLOMESH_BUDGET_CAP || "5")
1914
- };
1915
- const client = new HoloMeshOrchestratorClient(meshConfig);
1916
- const v2Enabled = process.env.HOLOMESH_V2_ENABLED === "true";
1917
- const localAgentDid = process.env.HOLOMESH_AGENT_DID || (v2Enabled ? `did:holo:${meshConfig.agentName}` : void 0);
1918
- const walletEnabled = process.env.HOLOMESH_WALLET_ENABLED === "true";
1919
- const walletTestnet = process.env.HOLOMESH_WALLET_TESTNET === "true";
1920
- const { actions, wireTraitListeners } = createHoloMeshDaemonActions(client, {
1921
- stateFile,
1922
- verbose: opts.debug,
1923
- v2Enabled,
1924
- localAgentDid,
1925
- localMcpUrl: process.env.HOLOMESH_LOCAL_MCP_URL || "https://mcp.holoscript.net",
1926
- crdtSnapshotPath: v2Enabled ? path.join(path.dirname(stateFile), "holomesh-crdt.bin") : void 0,
1927
- peerStorePath: v2Enabled ? path.join(path.dirname(stateFile), "holomesh-peers.json") : void 0,
1928
- walletEnabled,
1929
- walletTestnet
1930
- });
1931
- console.log(
1932
- `[holomesh-daemon] ${Object.keys(actions).length} action handlers registered${v2Enabled ? ` (V2 P2P enabled, DID: ${localAgentDid})` : ""}${walletEnabled ? " (V3 Wallet enabled)" : ""}`
1933
- );
1934
- let cycle = 0;
1935
- while (opts.alwaysOn || cycle < opts.cycles) {
1936
- console.log(
1937
- `
1938
- [holomesh-daemon] === Cycle ${cycle + 1}${opts.alwaysOn ? "" : `/${opts.cycles}`} ===`
1939
- );
1940
- const cycleAST = JSON.parse(JSON.stringify(compositionAST));
1941
- materializeTraits(cycleAST);
1942
- const runtime = createHeadlessRuntime(cycleAST, {
1943
- profile: HEADLESS_PROFILE,
1944
- tickRate: 1,
1945
- debug: opts.debug,
1946
- hostCapabilities: createNodeHostCapabilities(path.dirname(filePath))
1947
- });
1948
- for (const [name, handler] of Object.entries(actions)) {
1949
- runtime.registerAction(name, handler);
1950
- }
1951
- wireTraitListeners(runtime);
1952
- const maxWaitMs = (opts.timeout ?? 5) * 60 * 1e3;
1953
- const btResult = await new Promise((resolve2) => {
1954
- let resolved = false;
1955
- const timeout = setTimeout(() => {
1956
- if (!resolved) {
1957
- resolved = true;
1958
- resolve2({ status: "timeout" });
1959
- }
1960
- }, maxWaitMs);
1961
- runtime.on("bt_complete", () => {
1962
- if (!resolved) {
1963
- resolved = true;
1964
- clearTimeout(timeout);
1965
- resolve2({ status: "complete" });
1966
- }
1967
- });
1968
- runtime.start();
1969
- });
1970
- runtime.stop();
1971
- console.log(`[holomesh-daemon] Cycle ${cycle + 1} ${btResult.status}`);
1972
- cycle++;
1973
- if (opts.alwaysOn || cycle < opts.cycles) {
1974
- const intervalSec = opts.cycleIntervalSec > 0 ? opts.cycleIntervalSec : 120;
1975
- const jitter = 1 + (Math.random() * 2 - 1) * 0.3;
1976
- const waitMs = Math.round(intervalSec * 1e3 * jitter);
1977
- console.log(`[holomesh-daemon] Waiting ${Math.round(waitMs / 1e3)}s before next cycle...`);
1978
- await new Promise((r) => setTimeout(r, waitMs));
1979
- }
1980
- }
1981
- cleanup();
1982
- console.log("[holomesh-daemon] Done");
1983
- }
1984
- async function daemonStatus(jsonOutput = false) {
1985
- const repoRoot = findGitRoot(process.cwd());
1986
- const stateDir = path.join(repoRoot, ".holoscript");
1987
- if (!fs.existsSync(stateDir)) {
1988
- if (jsonOutput) {
1989
- console.log(
1990
- JSON.stringify({
1991
- status: "missing_state_dir",
1992
- running: false,
1993
- stateDir
1994
- })
1995
- );
1996
- } else {
1997
- console.log("[daemon status] No state directory found. Run a daemon cycle first.");
1998
- }
1999
- return;
2000
- }
2001
- const lockFile = path.join(stateDir, "daemon.lock");
2002
- let isRunning = false;
2003
- let lockPid = 0;
2004
- let lastHeartbeat = 0;
2005
- if (fs.existsSync(lockFile)) {
2006
- try {
2007
- const lock = JSON.parse(fs.readFileSync(lockFile, "utf-8"));
2008
- lockPid = lock.pid;
2009
- lastHeartbeat = lock.heartbeat;
2010
- isRunning = Date.now() - lastHeartbeat < 12e4;
2011
- } catch {
2012
- }
2013
- }
2014
- const stateFile = path.join(stateDir, "daemon-state.json");
2015
- let ds = {};
2016
- if (fs.existsSync(stateFile)) {
2017
- try {
2018
- ds = JSON.parse(fs.readFileSync(stateFile, "utf-8"));
2019
- } catch {
2020
- }
2021
- }
2022
- const fileStateFile = path.join(stateDir, "daemon-file-state.json");
2023
- let fileState = {
2024
- committed: [],
2025
- failures: {}
2026
- };
2027
- if (fs.existsSync(fileStateFile)) {
2028
- try {
2029
- fileState = JSON.parse(fs.readFileSync(fileStateFile, "utf-8"));
2030
- } catch {
2031
- }
2032
- }
2033
- const wisdomFile = path.join(stateDir, "accumulated-wisdom.json");
2034
- let wisdomCount = 0;
2035
- if (fs.existsSync(wisdomFile)) {
2036
- try {
2037
- wisdomCount = JSON.parse(fs.readFileSync(wisdomFile, "utf-8")).length;
2038
- } catch {
2039
- }
2040
- }
2041
- const ledgerFile = path.join(stateDir, "fix-ledger.json");
2042
- let ledger = [];
2043
- if (fs.existsSync(ledgerFile)) {
2044
- try {
2045
- ledger = JSON.parse(fs.readFileSync(ledgerFile, "utf-8"));
2046
- } catch {
2047
- }
2048
- }
2049
- const telemetryFile = path.join(stateDir, "daemon-telemetry.jsonl");
2050
- let telemetryCount = 0;
2051
- let lastTelemetry;
2052
- if (fs.existsSync(telemetryFile)) {
2053
- try {
2054
- const lines = fs.readFileSync(telemetryFile, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
2055
- telemetryCount = lines.length;
2056
- if (lines.length > 0) {
2057
- lastTelemetry = JSON.parse(lines[lines.length - 1]);
2058
- }
2059
- } catch {
2060
- }
2061
- }
2062
- const totalCycles = ds.totalCycles || 0;
2063
- const lastFocus = ds.lastFocus || "n/a";
2064
- const lastCycleTime = ds.lastCycleTimeISO ? new Date(ds.lastCycleTimeISO).toLocaleString() : "never";
2065
- const bestQ = Number(ds.bestQuality || 0).toFixed(3);
2066
- const lastQ = Number(ds.lastQuality || 0).toFixed(3);
2067
- const baseline = ds.typeErrorBaseline ? `${ds.typeErrorBaseline} type errors` : "not yet measured";
2068
- const totalCost = Number(ds.totalCostUSD || 0);
2069
- const tokIn = Number(ds.totalInputTokens || 0);
2070
- const tokOut = Number(ds.totalOutputTokens || 0);
2071
- const quarantined = Object.values(fileState.failures).filter((n) => n >= 3).length;
2072
- if (jsonOutput) {
2073
- console.log(
2074
- JSON.stringify({
2075
- status: "ok",
2076
- running: isRunning,
2077
- pid: lockPid || null,
2078
- heartbeatAgeSec: lastHeartbeat > 0 ? Math.round((Date.now() - lastHeartbeat) / 1e3) : null,
2079
- stateDir,
2080
- session: {
2081
- totalCycles,
2082
- lastFocus,
2083
- lastCycleTimeISO: ds.lastCycleTimeISO || null
2084
- },
2085
- quality: {
2086
- best: Number(bestQ),
2087
- last: Number(lastQ),
2088
- baselineTypeErrors: ds.typeErrorBaseline || 0
2089
- },
2090
- cost: {
2091
- totalUSD: totalCost,
2092
- tokensIn: tokIn,
2093
- tokensOut: tokOut
2094
- },
2095
- files: {
2096
- committed: fileState.committed.length,
2097
- quarantined
2098
- },
2099
- wisdomCount,
2100
- telemetry: {
2101
- file: telemetryFile,
2102
- count: telemetryCount,
2103
- last: lastTelemetry || null
2104
- },
2105
- recentLedger: ledger.slice(-5)
2106
- })
2107
- );
2108
- return;
2109
- }
2110
- const G = "\x1B[32m";
2111
- const Y = "\x1B[33m";
2112
- const R = "\x1B[0m";
2113
- const B = "\x1B[1m";
2114
- const runStatus = isRunning ? `${G}\u25CF RUNNING${R} (PID ${lockPid}, heartbeat ${Math.round((Date.now() - lastHeartbeat) / 1e3)}s ago)` : `${Y}\u25CB IDLE${R}`;
2115
- console.log(`
2116
- ${B}HoloDaemon Status${R} ${runStatus}`);
2117
- console.log(` State dir: ${stateDir}
2118
- `);
2119
- console.log(`${B}Session${R}`);
2120
- console.log(` Total cycles: ${totalCycles}`);
2121
- console.log(` Last focus: ${lastFocus}`);
2122
- console.log(` Last cycle: ${lastCycleTime}`);
2123
- console.log(`
2124
- ${B}Quality${R}`);
2125
- console.log(` Best: ${bestQ}`);
2126
- console.log(` Last: ${lastQ}`);
2127
- console.log(` Baseline: ${baseline}`);
2128
- const totalCostDisplay = totalCost.toFixed(4);
2129
- const tokInDisplay = tokIn.toLocaleString();
2130
- const tokOutDisplay = tokOut.toLocaleString();
2131
- console.log(`
2132
- ${B}Cost${R}`);
2133
- console.log(` Total: $${totalCostDisplay}`);
2134
- console.log(` Tokens in: ${tokInDisplay}`);
2135
- console.log(` Tokens out: ${tokOutDisplay}`);
2136
- console.log(`
2137
- ${B}Files${R}`);
2138
- console.log(` Committed: ${fileState.committed.length}`);
2139
- console.log(` Quarantined: ${quarantined}`);
2140
- console.log(` Wisdom: ${wisdomCount} entries`);
2141
- console.log(` Telemetry: ${telemetryCount} entries`);
2142
- const recent = ledger.slice(-5);
2143
- if (recent.length > 0) {
2144
- console.log(`
2145
- ${B}Recent Ledger Entries${R} (last ${recent.length})`);
2146
- for (const entry of recent) {
2147
- const ts = entry.timestamp ? new Date(entry.timestamp).toLocaleString() : "?";
2148
- const file = entry.candidate ? path.basename(entry.candidate) : "?";
2149
- const result = entry.result === "committed" ? `${G}committed${R}` : entry.result === "rolled_back" ? `${Y}rolled_back${R}` : entry.result || "?";
2150
- const sha = entry.commitSha ? ` (${entry.commitSha.slice(0, 7)})` : "";
2151
- console.log(` ${ts} ${file} ${result}${sha} focus:${entry.focus || "?"}`);
2152
- }
2153
- }
2154
- console.log("");
2155
- }
2156
- function showHelp() {
2157
- console.log(`
2158
- HoloScript CLI \u2014 Headless Runner v5.0
2159
-
2160
- Usage:
2161
- holoscript run <file> [--target node|python|ros2] [--profile headless|minimal|full] [--ticks <n>] [--daemon] [--debug]
2162
- holoscript test <file> [--debug]
2163
- holoscript compile <file> [--target node|python] [--output <path>] [--enforce-gotchas]
2164
- holoscript deploy <file> [--share] [--publish] [--price <eth>] [--mint-nft] [--wallet-key <key>] [--author <name>] [--license <type>] [--server <url>]
2165
- holoscript absorb <file> [--output <path>] [--debug]
2166
- holoscript daemon <file> [--provider anthropic|xai|openai|ollama] [--tool-profile claude-hsplus|grok-hsplus|standard] [--cycles <n>] [--commit] [--model <model>] [--trial <n>] [--provider-rotation] [--always-on] [--cycle-interval-sec <n>] [--skills-dir <path>] [--allow-shell] [--allow-shell-command <cmd>] [--allow-host <host>] [--allow-path <path>] [--debug]
2167
- holoscript daemon status [--json]
2168
-
2169
- Environment defaults (daemon):
2170
- HOLODAEMON_PROVIDER anthropic|xai|openai|ollama
2171
- HOLODAEMON_TOOL_PROFILE claude-hsplus|grok-hsplus|standard
2172
- HOLODAEMON_MODEL model identifier for selected provider
2173
-
2174
- Supported file types:
2175
- .hs Agent templates, behavior trees, event handlers
2176
- .hsplus Full language with modules, types, async/await
2177
- .holo Spatial compositions (optional render target)
2178
-
2179
- Examples:
2180
- holoscript run agent.hs --target node --debug
2181
- holoscript run agent.hs --daemon
2182
- holoscript test tests.hs
2183
- holoscript compile service.hsplus --target python --output service.py
2184
- holoscript compile world.holo --enforce-gotchas
2185
- holoscript absorb legacy.py --output agent.hsplus
2186
- holoscript daemon compositions/self-improve-daemon.hsplus --cycles 15 --commit
2187
- holoscript daemon compositions/self-improve-daemon.hsplus --provider xai --model grok-3
2188
- holoscript daemon compositions/self-improve-daemon.hsplus --provider anthropic --tool-profile claude-hsplus
2189
- holoscript daemon compositions/self-improve-daemon.hsplus --provider-rotation --cycles 10
2190
- holoscript daemon status
2191
- `);
2192
- }
2193
- async function main() {
2194
- const opts = parseArgs(process.argv);
2195
- switch (opts.command) {
2196
- case "run":
2197
- await runScript(opts);
2198
- break;
2199
- case "test":
2200
- await testScript(opts);
2201
- break;
2202
- case "compile":
2203
- compileScript(opts);
2204
- break;
2205
- case "deploy":
2206
- await deployScript(opts);
2207
- break;
2208
- case "absorb":
2209
- absorbScript(opts);
2210
- break;
2211
- case "daemon":
2212
- case "holodaemon":
2213
- await daemonScript(opts);
2214
- break;
2215
- case "holomesh-daemon":
2216
- await holoMeshDaemonScript(opts);
2217
- break;
2218
- case "daemon-status":
2219
- await daemonStatus(opts.json);
2220
- break;
2221
- case "help":
2222
- default:
2223
- showHelp();
2224
- }
2225
- }
2226
- main().catch((err) => {
2227
- console.error("[holoscript] Fatal error:", err.message);
2228
- process.exit(1);
2229
- });
2230
-
2231
- export { daemonScript, holoMeshDaemonScript };
2232
- //# sourceMappingURL=holoscript-runner.js.map
2233
- //# sourceMappingURL=holoscript-runner.js.map