@treenity/core 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/README.md +78 -0
  2. package/dist/chain.d.ts +3 -4
  3. package/dist/chain.d.ts.map +1 -1
  4. package/dist/chain.js.map +1 -1
  5. package/dist/client/trpc.d.ts.map +1 -1
  6. package/dist/client/trpc.js +1 -0
  7. package/dist/client/trpc.js.map +1 -1
  8. package/dist/comp/index.d.ts +3 -4
  9. package/dist/comp/index.d.ts.map +1 -1
  10. package/dist/comp/index.js +5 -4
  11. package/dist/comp/index.js.map +1 -1
  12. package/dist/comp/needs.d.ts.map +1 -1
  13. package/dist/comp/needs.js +3 -3
  14. package/dist/comp/needs.js.map +1 -1
  15. package/dist/core/component.d.ts +10 -8
  16. package/dist/core/component.d.ts.map +1 -1
  17. package/dist/core/component.js +4 -8
  18. package/dist/core/component.js.map +1 -1
  19. package/dist/core/context.d.ts +2 -2
  20. package/dist/core/context.d.ts.map +1 -1
  21. package/dist/core/path.d.ts.map +1 -1
  22. package/dist/core/path.js +7 -3
  23. package/dist/core/path.js.map +1 -1
  24. package/dist/core/registry.d.ts +2 -1
  25. package/dist/core/registry.d.ts.map +1 -1
  26. package/dist/core/registry.js.map +1 -1
  27. package/dist/core.d.ts +1 -1
  28. package/dist/core.d.ts.map +1 -1
  29. package/dist/core.js +1 -1
  30. package/dist/core.js.map +1 -1
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +2 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/log.d.ts +30 -0
  36. package/dist/log.d.ts.map +1 -1
  37. package/dist/log.js +119 -0
  38. package/dist/log.js.map +1 -1
  39. package/dist/mod/examples/ticker/service.js +2 -3
  40. package/dist/mod/examples/ticker/service.js.map +1 -1
  41. package/dist/mod/index.d.ts +1 -1
  42. package/dist/mod/index.d.ts.map +1 -1
  43. package/dist/mod/index.js +1 -1
  44. package/dist/mod/index.js.map +1 -1
  45. package/dist/mod/loader.d.ts +1 -0
  46. package/dist/mod/loader.d.ts.map +1 -1
  47. package/dist/mod/loader.js +27 -1
  48. package/dist/mod/loader.js.map +1 -1
  49. package/dist/mods/clients.d.ts +2 -0
  50. package/dist/mods/clients.d.ts.map +1 -0
  51. package/dist/mods/clients.js +3 -0
  52. package/dist/mods/clients.js.map +1 -0
  53. package/dist/mods/llm/index.js +1 -1
  54. package/dist/mods/llm/index.js.map +1 -1
  55. package/dist/mods/mcp/server.d.ts +0 -1
  56. package/dist/mods/mcp/server.js +0 -1
  57. package/dist/mods/mcp/service.d.ts +0 -1
  58. package/dist/mods/mcp/service.js +0 -1
  59. package/dist/mods/mcp/types.d.ts +0 -1
  60. package/dist/mods/mcp/types.js +0 -1
  61. package/dist/mods/servers.d.ts +4 -0
  62. package/dist/mods/servers.d.ts.map +1 -0
  63. package/dist/mods/servers.js +5 -0
  64. package/dist/mods/servers.js.map +1 -0
  65. package/dist/mods/treenity/builtins.d.ts +2 -0
  66. package/dist/mods/treenity/builtins.d.ts.map +1 -0
  67. package/dist/mods/treenity/builtins.js +18 -0
  68. package/dist/mods/treenity/builtins.js.map +1 -0
  69. package/dist/mods/treenity/logs.d.ts +18 -0
  70. package/dist/mods/treenity/logs.d.ts.map +1 -0
  71. package/dist/mods/treenity/logs.js +17 -0
  72. package/dist/mods/treenity/logs.js.map +1 -0
  73. package/dist/mods/treenity/seed.js +29 -27
  74. package/dist/mods/treenity/seed.js.map +1 -1
  75. package/dist/mods/treenity/server.d.ts +2 -0
  76. package/dist/mods/treenity/server.d.ts.map +1 -1
  77. package/dist/mods/treenity/server.js +2 -0
  78. package/dist/mods/treenity/server.js.map +1 -1
  79. package/dist/mods/uix/client.js +4 -4
  80. package/dist/mods/uix/client.js.map +1 -1
  81. package/dist/mods/uix/compile.d.ts.map +1 -1
  82. package/dist/mods/uix/compile.js +4 -2
  83. package/dist/mods/uix/compile.js.map +1 -1
  84. package/dist/schema/_test-fixture.d.ts +11 -0
  85. package/dist/schema/_test-fixture.d.ts.map +1 -0
  86. package/dist/schema/_test-fixture.js +8 -0
  87. package/dist/schema/_test-fixture.js.map +1 -0
  88. package/dist/schema/types.d.ts +1 -0
  89. package/dist/schema/types.d.ts.map +1 -1
  90. package/dist/server/actions.js +1 -1
  91. package/dist/server/auth.d.ts.map +1 -1
  92. package/dist/server/auth.js +4 -3
  93. package/dist/server/auth.js.map +1 -1
  94. package/dist/server/client.d.ts +10 -3
  95. package/dist/server/client.d.ts.map +1 -1
  96. package/dist/server/client.js +1 -1
  97. package/dist/server/client.js.map +1 -1
  98. package/dist/server/doc-index.d.ts.map +1 -1
  99. package/dist/server/doc-index.js +13 -12
  100. package/dist/server/doc-index.js.map +1 -1
  101. package/dist/server/factory.d.ts +6 -0
  102. package/dist/server/factory.d.ts.map +1 -1
  103. package/dist/server/factory.js +44 -25
  104. package/dist/server/factory.js.map +1 -1
  105. package/dist/server/main.d.ts +0 -4
  106. package/dist/server/main.d.ts.map +1 -1
  107. package/dist/server/main.js +4 -30
  108. package/dist/server/main.js.map +1 -1
  109. package/dist/server/mcp.d.ts +0 -1
  110. package/dist/server/mcp.js +0 -1
  111. package/dist/server/migrate.js +3 -3
  112. package/dist/server/migrate.js.map +1 -1
  113. package/dist/server/mods-mount.d.ts +0 -1
  114. package/dist/server/mods-mount.d.ts.map +1 -1
  115. package/dist/server/mods-mount.js +0 -1
  116. package/dist/server/mods-mount.js.map +1 -1
  117. package/dist/server/mount-adapters.js +9 -4
  118. package/dist/server/mount-adapters.js.map +1 -1
  119. package/dist/server/prefab.d.ts +2 -2
  120. package/dist/server/prefab.d.ts.map +1 -1
  121. package/dist/server/prefab.js +4 -2
  122. package/dist/server/prefab.js.map +1 -1
  123. package/dist/server/refs.d.ts +3 -0
  124. package/dist/server/refs.d.ts.map +1 -0
  125. package/dist/server/refs.js +59 -0
  126. package/dist/server/refs.js.map +1 -0
  127. package/dist/server/server.d.ts.map +1 -1
  128. package/dist/server/server.js +13 -3
  129. package/dist/server/server.js.map +1 -1
  130. package/dist/server/sub.js.map +1 -1
  131. package/dist/server/trpc.d.ts +7 -0
  132. package/dist/server/trpc.d.ts.map +1 -1
  133. package/dist/server/trpc.js +6 -2
  134. package/dist/server/trpc.js.map +1 -1
  135. package/dist/tree/fs.d.ts.map +1 -1
  136. package/dist/tree/fs.js +20 -18
  137. package/dist/tree/fs.js.map +1 -1
  138. package/dist/tree/index.d.ts.map +1 -1
  139. package/dist/tree/index.js +2 -1
  140. package/dist/tree/index.js.map +1 -1
  141. package/dist/tree-chain.d.ts +2 -1
  142. package/dist/tree-chain.d.ts.map +1 -1
  143. package/dist/tree-chain.js +5 -3
  144. package/dist/tree-chain.js.map +1 -1
  145. package/dist/uri.d.ts.map +1 -1
  146. package/dist/uri.js +8 -3
  147. package/dist/uri.js.map +1 -1
  148. package/package.json +48 -6
  149. package/src/chain.ts +7 -5
  150. package/src/client/trpc.ts +1 -0
  151. package/src/comp/index.test.ts +45 -9
  152. package/src/comp/index.ts +19 -8
  153. package/src/comp/needs.ts +3 -3
  154. package/src/core/component.ts +16 -14
  155. package/src/core/context.ts +4 -4
  156. package/src/core/index.test.ts +22 -1
  157. package/src/core/path.ts +6 -3
  158. package/src/core/registry.ts +4 -7
  159. package/src/core.ts +1 -1
  160. package/src/index.ts +1 -0
  161. package/src/log.ts +172 -1
  162. package/src/mod/docs/07-realtime.md +19 -11
  163. package/src/mod/docs/08-services.md +10 -8
  164. package/src/mod/docs/10-acl.md +1 -1
  165. package/src/mod/docs/12-conventions.md +43 -0
  166. package/src/mod/docs/13-example.md +36 -26
  167. package/src/mod/docs/14-mod-format.md +81 -8
  168. package/src/mod/examples/ticker/service.ts +2 -3
  169. package/src/mod/index.ts +1 -1
  170. package/src/mod/loader.test.ts +53 -1
  171. package/src/mod/loader.ts +34 -1
  172. package/src/mods/clients.ts +2 -0
  173. package/src/mods/llm/index.ts +1 -1
  174. package/src/mods/servers.ts +4 -0
  175. package/src/mods/treenity/builtins.ts +21 -0
  176. package/src/mods/treenity/logs.ts +26 -0
  177. package/src/mods/treenity/seed.ts +30 -28
  178. package/src/mods/treenity/server.ts +2 -0
  179. package/src/mods/uix/client.ts +4 -4
  180. package/src/mods/uix/compile.ts +4 -2
  181. package/src/schema/_test-fixture.ts +12 -0
  182. package/src/schema/generated/ai.agent.json +133 -0
  183. package/src/schema/generated/ai.approval.json +105 -0
  184. package/src/schema/generated/ai.approvals.json +24 -0
  185. package/src/schema/generated/ai.assignment.json +28 -0
  186. package/src/schema/generated/ai.plan.json +84 -0
  187. package/src/schema/generated/ai.policy.json +105 -0
  188. package/src/schema/generated/ai.pool.json +37 -0
  189. package/src/schema/generated/ai.thread.json +64 -0
  190. package/src/schema/generated/board.kanban.json +7 -0
  191. package/src/schema/generated/canary.item.json +40 -0
  192. package/src/schema/generated/claude-search.json +20 -0
  193. package/src/schema/generated/craft.product.json +47 -0
  194. package/src/schema/generated/craft.shop.json +94 -0
  195. package/src/schema/generated/craft.subscription.json +27 -0
  196. package/src/schema/generated/examples.demo.sensor.reading.json +25 -0
  197. package/src/schema/generated/flow.node.action.json +61 -0
  198. package/src/schema/generated/flow.node.code.json +43 -0
  199. package/src/schema/generated/flow.node.condition.json +37 -0
  200. package/src/schema/generated/flow.node.end.json +35 -0
  201. package/src/schema/generated/flow.node.http.json +65 -0
  202. package/src/schema/generated/flow.node.llm.json +67 -0
  203. package/src/schema/generated/flow.node.loop.json +49 -0
  204. package/src/schema/generated/flow.node.start.json +39 -0
  205. package/src/schema/generated/flow.scenario.json +118 -0
  206. package/src/schema/generated/grove.attempt.json +199 -0
  207. package/src/schema/generated/grove.path.json +93 -0
  208. package/src/schema/generated/grove.review.json +27 -0
  209. package/src/schema/generated/grove.task.json +164 -0
  210. package/src/schema/generated/intel.scenario.json +58 -0
  211. package/src/schema/generated/intel.signal.json +113 -0
  212. package/src/schema/generated/intel.world.json +15 -0
  213. package/src/schema/generated/landing.block.json +201 -0
  214. package/src/schema/generated/landing.page.json +84 -0
  215. package/src/schema/generated/metatron.config.json +119 -0
  216. package/src/schema/generated/metatron.permission.json +36 -0
  217. package/src/schema/generated/metatron.skill.json +36 -0
  218. package/src/schema/generated/metatron.task.json +114 -0
  219. package/src/schema/generated/metatron.template.json +26 -0
  220. package/src/schema/generated/metatron.workspace.json +60 -0
  221. package/src/schema/generated/order.status.json +21 -0
  222. package/src/schema/generated/polyhope.backtest.json +161 -0
  223. package/src/schema/generated/polyhope.feed.json +33 -0
  224. package/src/schema/generated/polyhope.run.json +94 -0
  225. package/src/schema/generated/polyhope.strategy.json +152 -0
  226. package/src/schema/generated/polymax.activity.json +65 -0
  227. package/src/schema/generated/polymax.aggr-feed.json +28 -0
  228. package/src/schema/generated/polymax.aggr.json +20 -0
  229. package/src/schema/generated/polymax.alert.json +56 -0
  230. package/src/schema/generated/polymax.bot-config.json +14 -0
  231. package/src/schema/generated/polymax.bot-status.json +35 -0
  232. package/src/schema/generated/polymax.classification.json +55 -0
  233. package/src/schema/generated/polymax.deposits.json +45 -0
  234. package/src/schema/generated/polymax.holding.json +55 -0
  235. package/src/schema/generated/polymax.identity.json +71 -0
  236. package/src/schema/generated/polymax.lb-entry.json +75 -0
  237. package/src/schema/generated/polymax.leaderboard.json +37 -0
  238. package/src/schema/generated/polymax.market-ref.json +25 -0
  239. package/src/schema/generated/polymax.performance.json +65 -0
  240. package/src/schema/generated/polymax.profile.json +16 -0
  241. package/src/schema/generated/polymax.scan-result.json +50 -0
  242. package/src/schema/generated/polymax.status.json +40 -0
  243. package/src/schema/generated/polymax.tags.json +53 -0
  244. package/src/schema/generated/polymax.trader.json +16 -0
  245. package/src/schema/generated/polymax.wallet-market.json +50 -0
  246. package/src/schema/generated/polymax.wallet-pnl.json +35 -0
  247. package/src/schema/generated/pult.concept.json +53 -0
  248. package/src/schema/generated/pult.config.json +227 -0
  249. package/src/schema/generated/pult.connector.json +72 -0
  250. package/src/schema/generated/pult.market.json +113 -0
  251. package/src/schema/generated/pult.order.json +113 -0
  252. package/src/schema/generated/pult.rete.json +68 -0
  253. package/src/schema/generated/pult.sensor.json +74 -0
  254. package/src/schema/generated/pult.signal.json +54 -0
  255. package/src/schema/generated/pult.synapse.json +93 -0
  256. package/src/schema/generated/pult.trade.json +93 -0
  257. package/src/schema/generated/resim.config.json +34 -0
  258. package/src/schema/generated/resim.function.json +62 -0
  259. package/src/schema/generated/resim.goal.json +22 -0
  260. package/src/schema/generated/resim.resource.json +40 -0
  261. package/src/schema/generated/resim.state.json +48 -0
  262. package/src/schema/generated/resim.world.json +40 -0
  263. package/src/schema/generated/saveme.action.save.json +29 -0
  264. package/src/schema/generated/saveme.message.json +36 -0
  265. package/src/schema/generated/saveme.router.json +31 -0
  266. package/src/schema/generated/t.coolify.json +50 -0
  267. package/src/schema/generated/t.event.json +46 -0
  268. package/src/schema/generated/t.logs.json +155 -0
  269. package/src/schema/generated/t.note.json +31 -0
  270. package/src/schema/generated/t.person.json +36 -0
  271. package/src/schema/generated/t.tenant.json +57 -0
  272. package/src/schema/generated/t.tenant.status.json +42 -0
  273. package/src/schema/generated/tagger.config.json +115 -0
  274. package/src/schema/generated/tagger.result.json +57 -0
  275. package/src/schema/generated/tagger.tree.json +36 -0
  276. package/src/schema/generated/ui.table.json +46 -0
  277. package/src/schema/types.ts +1 -0
  278. package/src/server/actions.test.ts +1 -1
  279. package/src/server/actions.ts +1 -1
  280. package/src/server/api.test.ts +9 -0
  281. package/src/server/auth.ts +4 -3
  282. package/src/server/client.ts +1 -1
  283. package/src/server/coverage.test.ts +1 -1
  284. package/src/server/doc-index.ts +13 -12
  285. package/src/server/e2e.test.ts +4 -3
  286. package/src/server/factory.ts +46 -24
  287. package/src/server/main.ts +4 -36
  288. package/src/server/migrate.ts +4 -4
  289. package/src/server/mods-mount.ts +0 -2
  290. package/src/server/mount-adapters.ts +9 -4
  291. package/src/server/mount.test.ts +73 -5
  292. package/src/server/prefab.ts +3 -2
  293. package/src/server/refs.test.ts +82 -0
  294. package/src/server/refs.ts +64 -0
  295. package/src/server/server.ts +14 -3
  296. package/src/server/sub.ts +2 -2
  297. package/src/server/trpc.ts +9 -3
  298. package/src/tree/fs.ts +21 -15
  299. package/src/tree/index.test.ts +26 -0
  300. package/src/tree/index.ts +2 -1
  301. package/src/tree-chain.test.ts +37 -44
  302. package/src/tree-chain.ts +11 -5
  303. package/src/uri.test.ts +32 -5
  304. package/src/uri.ts +4 -2
  305. package/dist/mods/mcp/server.d.ts.map +0 -1
  306. package/dist/mods/mcp/server.js.map +0 -1
  307. package/dist/mods/mcp/service.d.ts.map +0 -1
  308. package/dist/mods/mcp/service.js.map +0 -1
  309. package/dist/mods/mcp/types.d.ts.map +0 -1
  310. package/dist/mods/mcp/types.js.map +0 -1
  311. package/dist/schema/test-opaque.d.ts +0 -3
  312. package/dist/schema/test-opaque.d.ts.map +0 -1
  313. package/dist/schema/test-opaque.js +0 -43
  314. package/dist/schema/test-opaque.js.map +0 -1
  315. package/dist/server/mcp.d.ts.map +0 -1
  316. package/dist/server/mcp.js.map +0 -1
  317. package/src/mods/mcp/CLAUDE.md +0 -6
  318. package/src/mods/mcp/server.ts +0 -2
  319. package/src/mods/mcp/service.ts +0 -19
  320. package/src/mods/mcp/types.ts +0 -7
  321. package/src/schema/test-opaque.ts +0 -42
  322. package/src/server/mcp.ts +0 -326
@@ -71,17 +71,13 @@ export default defineMod({
71
71
  const existing = await store.get('/sensors/weather');
72
72
  if (existing) return; // идемпотентно
73
73
 
74
- await store.set({
75
- $path: '/sensors/weather',
76
- $type: 'weather.sensor',
74
+ await store.set(createNode('/sensors/weather', 'weather.sensor', {
77
75
  config: { $type: 'weather.config', location: 'Moscow', interval: 60 },
78
- } as any);
76
+ }));
79
77
 
80
- await store.set({
81
- $path: '/sys/autostart/weather',
82
- $type: 'ref',
78
+ await store.set(createNode('/sys/autostart/weather', 'ref', {
83
79
  $ref: '/sensors/weather',
84
- } as any);
80
+ }));
85
81
  },
86
82
 
87
83
  onLoad: () => console.log('Weather mod loaded'),
@@ -302,3 +298,80 @@ buf.clear(); // сбросить всё
302
298
  4. `expire()` — таймаут для зависших мутаций (сервер не ответил)
303
299
 
304
300
  **Почему хранится method + data:** `structuredClone()` стирает прототипы. Без сохранённой ссылки на метод rebase не может переиграть мутации.
301
+
302
+ ---
303
+
304
+ ## Views — register / Render / RenderContext
305
+
306
+ Вьюхи НИКОГДА не рендерят дочерние компоненты напрямую. Всё через реестр:
307
+
308
+ ### Регистрация — типобезопасная
309
+
310
+ ```tsx
311
+ import { register } from '@treenity/core/core';
312
+ import type { View } from '@treenity/react/context';
313
+ import { Render, RenderContext } from '@treenity/react/context';
314
+
315
+ // View<T> — типизированный React-компонент: { value: T, ctx?, onChange? }
316
+ const SensorView: View<WeatherSensor> = ({ value, ctx }) => {
317
+ const path = ctx!.node.$path; // путь ноды — через ctx, НЕ из value
318
+ return <div>{value.location} — {value.temperature}°C</div>;
319
+ };
320
+
321
+ const SensorRow: View<WeatherSensor> = ({ value, ctx }) => { ... };
322
+
323
+ // register принимает Class<T> — T пробрасывается в handler автоматически
324
+ register(WeatherSensor, 'react', SensorView); // detail view
325
+ register(WeatherSensor, 'react:list', SensorRow); // compact list view
326
+ ```
327
+
328
+ **ЗАПРЕЩЕНО:**
329
+ ```tsx
330
+ // WRONG — as any убивает типизацию, прячет ошибки:
331
+ register('weather.sensor', 'react', SensorView as any);
332
+
333
+ // WRONG — NodeData не смешивать с типами компонентов:
334
+ function SensorRow({ value }: { value: NodeData & WeatherSensor }) { ... }
335
+
336
+ // WRONG — путь из value (value — данные компонента, не нода):
337
+ const path = value.$path; // может не существовать!
338
+ ```
339
+
340
+ ### Рендер дочерних — ТОЛЬКО через `<Render>`
341
+
342
+ ```tsx
343
+ // WRONG — хардкод, обходит реестр:
344
+ {sensors.map(s => <SensorRow key={s.$path} sensor={s} />)}
345
+
346
+ // RIGHT — через реестр, композабельно:
347
+ <RenderContext name="react:list">
348
+ {sensors.map(s => <Render key={s.$path} value={s} />)}
349
+ </RenderContext>
350
+ ```
351
+
352
+ ### Контексты
353
+
354
+ | Контекст | Назначение |
355
+ |----------|-----------|
356
+ | `react` | default/detail view |
357
+ | `react:list` | компактная карточка для списков |
358
+ | `react:edit` | форма редактирования |
359
+
360
+ Fallback автоматический: `react:list` → `react` → `default`.
361
+
362
+ ### Сигнатура view-функции
363
+
364
+ ```tsx
365
+ // View<T> даёт: value: T, ctx: ViewCtx (node, path, execute), onChange
366
+ const SensorView: View<WeatherSensor> = ({ value, ctx }) => {
367
+ // value — данные компонента (WeatherSensor fields)
368
+ // ctx!.node — полная NodeData (с $path, $type, $acl и т.д.)
369
+ // ctx!.execute(action, data) — вызов экшена
370
+ };
371
+ ```
372
+
373
+ ### Почему это важно
374
+
375
+ - **Композиция:** любой тип рендерит чужих детей, не зная их вьюх
376
+ - **Переопределение:** зарегистрируй новый `react:list` для типа → все списки обновятся
377
+ - **AI visibility:** реестр интроспектируемый, AI находит доступные вьюхи
@@ -1,9 +1,8 @@
1
- import { getComp } from '#comp';
2
- import { type NodeData, register } from '#core';
1
+ import { getComponent, type NodeData, register } from '#core';
3
2
  import { TickerConfig } from './types';
4
3
 
5
4
  register('ticker', 'service', async (node: NodeData, ctx) => {
6
- const config = getComp(node, TickerConfig);
5
+ const config = getComponent(node, TickerConfig);
7
6
  const interval = (config?.intervalSec ?? 10) * 1000;
8
7
 
9
8
  const timer = setInterval(async () => {
package/src/mod/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  export { defineMod } from './types';
4
4
  export type { TreenityMod, ModManifest, ModState, LoadedMod } from './types';
5
5
  export { discoverMods } from './discover';
6
- export { sortByDependencies, loadMods, loadLocalMods, getLoadedMods, getMod, isModLoaded, clearModRegistry } from './loader';
6
+ export { sortByDependencies, loadMods, loadLocalMods, loadAllMods, getLoadedMods, getMod, isModLoaded, clearModRegistry } from './loader';
7
7
  export type { LoadTarget, LoadResult } from './loader';
8
8
  export { OptimisticBuffer } from './optimistic';
9
9
  export type { PendingMutation } from './optimistic';
@@ -1,6 +1,9 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { beforeEach, describe, it } from 'node:test';
3
- import { clearModRegistry, getLoadedMods, getMod, isModLoaded, loadMods, sortByDependencies } from './loader';
3
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { clearModRegistry, getLoadedMods, getMod, isModLoaded, loadLocalMods, loadMods, sortByDependencies } from './loader';
4
7
  import type { ModManifest } from './types';
5
8
 
6
9
  function m(name: string, deps?: string[]): ModManifest {
@@ -166,3 +169,52 @@ describe('loadMods', () => {
166
169
  assert.equal(isModLoaded('fail-mod'), false);
167
170
  });
168
171
  });
172
+
173
+ describe('loadLocalMods', () => {
174
+ let tmpDir: string;
175
+
176
+ beforeEach(() => {
177
+ clearModRegistry();
178
+ tmpDir = mkdtempSync(join(tmpdir(), 'treenity-mods-'));
179
+ });
180
+
181
+ it('discovers mod with server.ts in a directory', async () => {
182
+ const modDir = join(tmpDir, 'my-mod');
183
+ mkdirSync(modDir);
184
+ writeFileSync(join(modDir, 'server.ts'), 'globalThis.__canary_server_loaded = true;');
185
+
186
+ const result = await loadLocalMods(tmpDir, 'server');
187
+ assert.deepEqual(result.loaded, ['my-mod']);
188
+ assert.equal(result.failed.length, 0);
189
+
190
+ rmSync(tmpDir, { recursive: true });
191
+ });
192
+
193
+ it('skips mod without matching entry file', async () => {
194
+ const modDir = join(tmpDir, 'server-only');
195
+ mkdirSync(modDir);
196
+ writeFileSync(join(modDir, 'server.ts'), '');
197
+
198
+ const result = await loadLocalMods(tmpDir, 'client');
199
+ assert.deepEqual(result.loaded, []);
200
+
201
+ rmSync(tmpDir, { recursive: true });
202
+ });
203
+
204
+ it('returns empty for nonexistent directory', async () => {
205
+ const result = await loadLocalMods('/tmp/no-such-dir-xyz-42', 'server');
206
+ assert.deepEqual(result.loaded, []);
207
+ assert.deepEqual(result.failed, []);
208
+ });
209
+
210
+ it('skips hidden directories', async () => {
211
+ const modDir = join(tmpDir, '.hidden-mod');
212
+ mkdirSync(modDir);
213
+ writeFileSync(join(modDir, 'server.ts'), '');
214
+
215
+ const result = await loadLocalMods(tmpDir, 'server');
216
+ assert.deepEqual(result.loaded, []);
217
+
218
+ rmSync(tmpDir, { recursive: true });
219
+ });
220
+ });
package/src/mod/loader.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import type { Tree } from '#tree';
4
4
  import { readdir, stat } from 'node:fs/promises';
5
- import { join } from 'node:path';
5
+ import { join, resolve } from 'node:path';
6
6
  import { setCurrentMod } from './tracking';
7
7
  import type { LoadedMod, ModManifest, TreenityMod } from './types';
8
8
 
@@ -172,3 +172,36 @@ export async function loadLocalMods(modsDir: string, target: LoadTarget): Promis
172
172
 
173
173
  return result;
174
174
  }
175
+
176
+ // ── Load all mods: internal + engine + project (CWD) ──
177
+
178
+ export async function loadAllMods(target: LoadTarget, ...extraDirs: string[]): Promise<LoadResult> {
179
+ const internalDir = new URL('../mods', import.meta.url).pathname;
180
+ const engineDir = new URL('../../../mods', import.meta.url).pathname;
181
+
182
+ const dirs = [internalDir, engineDir];
183
+
184
+ // CWD/mods/ if different from engine mods
185
+ const projectDir = resolve('mods');
186
+ if (resolve(projectDir) !== resolve(engineDir)) dirs.push(projectDir);
187
+
188
+ dirs.push(...extraDirs);
189
+
190
+ const seen = new Set<string>();
191
+ const result: LoadResult = { loaded: [], failed: [] };
192
+
193
+ for (const dir of dirs) {
194
+ const abs = resolve(dir);
195
+ if (seen.has(abs)) continue;
196
+ seen.add(abs);
197
+
198
+ const r = await loadLocalMods(dir, target);
199
+ result.loaded.push(...r.loaded);
200
+ result.failed.push(...r.failed);
201
+ }
202
+
203
+ if (result.failed.length) console.error('failed mods:', result.failed.map(f => `${f.name}: ${f.error.message}`).join(', '));
204
+ console.log(`mods: ${result.loaded.join(', ')}`);
205
+
206
+ return result;
207
+ }
@@ -0,0 +1,2 @@
1
+ // Barrel: all core mod client registrations
2
+ import './uix/client';
@@ -32,7 +32,7 @@ export function exportSchemaForLLM(): SchemaExport {
32
32
  }
33
33
 
34
34
  export async function describeTree(ctx: ActionCtx, data: unknown): Promise<string> {
35
- const { depth = 3 } = (data as { depth?: number }) ?? {};
35
+ const depth = Number((data as Record<string, unknown>)?.depth) || 3;
36
36
  const path = ctx.node.$path;
37
37
  const lines: string[] = [];
38
38
 
@@ -0,0 +1,4 @@
1
+ // Barrel: all core mod server registrations
2
+ import './autostart/server';
3
+ import './llm/server';
4
+ import './treenity/server';
@@ -0,0 +1,21 @@
1
+ // Core built-in types — registered so validation (Write-Barrier) accepts them.
2
+ // Convention: no dot = core built-in (see Type Naming Convention in CLAUDE.md)
3
+
4
+ import { normalizeType, register } from '#core';
5
+
6
+ const builtins = [
7
+ 'dir', 'root', 'ref', 'user', 'type', 'mount-point', 'session',
8
+ ];
9
+
10
+ export function registerBuiltins() {
11
+ for (const type of builtins) {
12
+ register(type, 'schema', () => ({
13
+ $id: normalizeType(type),
14
+ type: 'object' as const,
15
+ title: type,
16
+ properties: {},
17
+ }));
18
+ }
19
+ }
20
+
21
+ registerBuiltins();
@@ -0,0 +1,26 @@
1
+ // t.logs — unified log buffer, queryable via actions + MCP
2
+
3
+ import { registerType } from '#comp';
4
+ import { interceptConsole, logStats, queryLogs, type LogLevel } from '#log';
5
+
6
+ interceptConsole();
7
+
8
+ /** @description Server log buffer — query, grep, filter by level */
9
+ export class Logs {
10
+ /** @description Query log buffer with grep, level filter, head/tail */
11
+ async query(data: {
12
+ /** Regex pattern to filter messages */ grep?: string;
13
+ /** Log level(s) to include */ level?: LogLevel | LogLevel[];
14
+ /** Return first N entries */ head?: number;
15
+ /** Return last N entries */ tail?: number;
16
+ }) {
17
+ return queryLogs(data);
18
+ }
19
+
20
+ /** @description Buffer stats: buffered count, total ever, max capacity */
21
+ async stats() {
22
+ return logStats();
23
+ }
24
+ }
25
+
26
+ registerType('t.logs', Logs);
@@ -1,14 +1,9 @@
1
- import { type NodeData, R, S } from '@treenity/core/core';
1
+ import { type NodeData, R, S } from '@treenity/core';
2
2
  import { registerPrefab } from '@treenity/core/mod';
3
3
 
4
+ // Universal infra — works with any storage backend (FS, memory, Mongo)
4
5
  registerPrefab('core', 'seed', [
5
6
  { $path: 'sys', $type: 'treenity.system' },
6
- { $path: 'auth', $type: 'dir' },
7
- { $path: 'auth/users', $type: 'mount-point',
8
- connection: { $type: 'connection', db: 'treenity', collection: 'users' },
9
- mount: { $type: 't.mount.mongo' },
10
- $acl: [{ g: 'authenticated', p: R | S }, { g: 'public', p: 0 }],
11
- },
12
7
  { $path: 'sys/types', $type: 'mount-point',
13
8
  mount: { $type: 't.mount.types' },
14
9
  $acl: [{ g: 'public', p: R }],
@@ -17,40 +12,47 @@ registerPrefab('core', 'seed', [
17
12
  mount: { $type: 't.mount.mods' },
18
13
  $acl: [{ g: 'public', p: R }],
19
14
  },
20
- { $path: 'auth/sessions', $type: 'mount-point',
21
- connection: { $type: 'connection', db: 'treenity', collection: 'sessions' },
22
- mount: { $type: 't.mount.mongo' },
23
- },
24
- { $path: 'mnt', $type: 'dir' },
25
- { $path: 'mnt/orders', $type: 't.mount.mongo',
26
- connection: { $type: 'connection', db: 'treenity', collection: 'orders' },
15
+ { $path: 'sys/mcp', $type: 'mcp.server', port: 0 },
16
+ { $path: 'sys/autostart', $type: 'autostart' },
17
+ { $path: '/sys/autostart/mcp', $type: 'ref', $ref: '/sys/mcp' },
18
+ { $path: 'proc', $type: 'mount-point',
19
+ mount: { $type: 't.mount.memory' },
20
+ $acl: [{ g: 'public', p: R }],
27
21
  },
28
- { $path: 'entities', $type: 'dir' },
22
+ { $path: 'llm', $type: 't.llm' },
29
23
  { $path: 'demo', $type: 'dir',
30
24
  metadata: { $type: 'metadata', title: 'Demo Node', description: 'Try calling actions' },
31
25
  status: { $type: 'status', value: 'draft' },
32
26
  counter: { $type: 'counter', count: 0 },
33
27
  },
34
- { $path: 'llm', $type: 't.llm' },
35
- { $path: 'sys/mcp', $type: 'mcp.server', port: 0 },
36
28
  { $path: 'demo/sensors', $type: 'examples.demo.sensor',
37
29
  mount: { $type: 't.mount.memory' },
38
30
  },
31
+ { $path: '/sys/autostart/sensors', $type: 'ref', $ref: '/demo/sensors' },
39
32
  { $path: 'sys/claude-search', $type: 'claude-search' },
40
- { $path: 'proc', $type: 'mount-point',
41
- mount: { $type: 't.mount.memory' },
42
- $acl: [{ g: 'public', p: R }],
43
- },
44
- { $path: 'sys/autostart', $type: 'autostart' },
45
- { $path: '/sys/autostart/mcp', $type: 'ref', $ref: '/sys/mcp' },
46
33
  { $path: '/sys/autostart/claude-search', $type: 'ref', $ref: '/sys/claude-search' },
47
- { $path: '/sys/autostart/sensors', $type: 'ref', $ref: '/demo/sensors' },
48
34
  ] as NodeData[], (nodes) => {
49
- // MCP port from env
50
35
  const mcpPort = Number(process.env.MCP_PORT) || 3212;
51
- let result = nodes.map(n =>
36
+ return nodes.map(n =>
52
37
  n.$path === 'sys/mcp' ? { ...n, port: mcpPort } : n,
53
38
  );
54
-
55
- return result;
56
39
  }, { tier: 'core' });
40
+
41
+ // Mongo-dependent infra — auth, orders, entities
42
+ registerPrefab('mongo', 'seed', [
43
+ { $path: 'auth', $type: 'dir' },
44
+ { $path: 'auth/users', $type: 'mount-point',
45
+ connection: { $type: 'connection', db: 'treenity', collection: 'users' },
46
+ mount: { $type: 't.mount.mongo' },
47
+ $acl: [{ g: 'authenticated', p: R | S }, { g: 'public', p: 0 }],
48
+ },
49
+ { $path: 'auth/sessions', $type: 'mount-point',
50
+ connection: { $type: 'connection', db: 'treenity', collection: 'sessions' },
51
+ mount: { $type: 't.mount.mongo' },
52
+ },
53
+ { $path: 'mnt', $type: 'dir' },
54
+ { $path: 'mnt/orders', $type: 't.mount.mongo',
55
+ connection: { $type: 'connection', db: 'treenity', collection: 'orders' },
56
+ },
57
+ { $path: 'entities', $type: 'dir' },
58
+ ] as NodeData[]);
@@ -1,4 +1,6 @@
1
+ import './builtins';
1
2
  import './system';
2
3
  import './mod-type';
3
4
  import './agent-port';
5
+ import './logs';
4
6
  import './seed';
@@ -5,7 +5,7 @@ import { onResolveMiss, register, unregister } from '#core';
5
5
  import { createInflight } from '#tree/inflight';
6
6
  import * as cache from '@treenity/react/cache';
7
7
  import { tree } from '@treenity/react/client';
8
- import { SystemFallbackView } from '@treenity/react/context';
8
+ import { UixNoView } from '@treenity/react/context';
9
9
  import React from 'react';
10
10
  import { compileComponent, invalidateCache } from './compile';
11
11
 
@@ -55,7 +55,7 @@ register('uix.view', 'react', UixView);
55
55
  const dedup = createInflight<void>();
56
56
 
57
57
  // Watch a type node for future view creation (e.g. AI agent saves view.source via MCP).
58
- // When the node changes and gains view.source, swap SystemFallbackView for the real view.
58
+ // When the node changes and gains view.source, swap UixNoView for the real view.
59
59
  const watched = new Set<string>();
60
60
 
61
61
  function watchTypeNode(type: string, typePath: string) {
@@ -86,7 +86,7 @@ function watchTypeNode(type: string, typePath: string) {
86
86
  onResolveMiss('react', (type) => {
87
87
  // Built-in types (no dot) never have UIX views — avoid contaminating tRPC batches
88
88
  if (!type.includes('.')) {
89
- register(type, 'react', SystemFallbackView);
89
+ register(type, 'react', UixNoView);
90
90
  return;
91
91
  }
92
92
 
@@ -98,7 +98,7 @@ onResolveMiss('react', (type) => {
98
98
  const source = (typeNode as any)?.view?.source;
99
99
 
100
100
  if (!source || typeof source !== 'string') {
101
- register(type, 'react', SystemFallbackView);
101
+ register(type, 'react', UixNoView);
102
102
  // Watch for future view creation (e.g. AI agent saves view.source later)
103
103
  watchTypeNode(type, typePath);
104
104
  return;
@@ -2,8 +2,8 @@
2
2
  // Turns raw JSX/TSX code string into a React component
3
3
 
4
4
  import { register } from '#core';
5
- import { Render, RenderContext, RenderField } from '@treenity/react/context';
6
- import { useChildren, usePath } from '@treenity/react/hooks';
5
+ import { Render, RenderContext, RenderField, viewCtx } from '@treenity/react/context';
6
+ import { execute, useChildren, usePath } from '@treenity/react/hooks';
7
7
  import React from 'react';
8
8
  import { compileJSX } from './jsx-parser';
9
9
 
@@ -22,6 +22,8 @@ const SCOPE: Record<string, unknown> = {
22
22
  RenderField,
23
23
  usePath,
24
24
  useChildren,
25
+ execute,
26
+ viewCtx,
25
27
  };
26
28
 
27
29
  const cache = new Map<string, React.FC<any>>();
@@ -0,0 +1,12 @@
1
+ // Test fixture for extract-schemas.test.ts — provides Autostart class after monorepo split.
2
+ // The real Autostart moved to mods/autostart/ (outside core/src/), so tests use this stub.
3
+
4
+ export class Autostart {
5
+ /** Start a service at given path */
6
+ start(data: {
7
+ /** service to start */
8
+ path: string;
9
+ }) {}
10
+
11
+ stop(data: { path: string }) {}
12
+ }
@@ -0,0 +1,133 @@
1
+ {
2
+ "$id": "ai.agent",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "type": "object",
5
+ "title": "AI Agent — autonomous worker node at /agents/{name}\nLLM config (model, systemPrompt, sessionId) lives in named metatron.config component.\nWork creates metatron.task nodes under /agents/{name}/tasks/.",
6
+ "properties": {
7
+ "role": {
8
+ "type": "string",
9
+ "title": "Open-ended role string. Guardian policies keyed by role.",
10
+ "default": "qa"
11
+ },
12
+ "status": {
13
+ "type": "string",
14
+ "enum": [
15
+ "idle",
16
+ "working",
17
+ "blocked",
18
+ "error",
19
+ "offline"
20
+ ],
21
+ "default": "offline"
22
+ },
23
+ "currentTask": {
24
+ "type": "string",
25
+ "title": "Path to current board.task being worked on",
26
+ "default": ""
27
+ },
28
+ "taskRef": {
29
+ "type": "string",
30
+ "title": "Path to current metatron.task (live log)",
31
+ "default": ""
32
+ },
33
+ "lastRunAt": {
34
+ "type": "number",
35
+ "default": 0
36
+ },
37
+ "totalTokens": {
38
+ "type": "number",
39
+ "title": "Total tokens used across all tasks",
40
+ "default": 0
41
+ }
42
+ },
43
+ "required": [
44
+ "role",
45
+ "status",
46
+ "currentTask",
47
+ "taskRef",
48
+ "lastRunAt",
49
+ "totalTokens"
50
+ ],
51
+ "methods": {
52
+ "online": {
53
+ "description": "Bring agent online",
54
+ "arguments": []
55
+ },
56
+ "offline": {
57
+ "description": "Take agent offline",
58
+ "arguments": []
59
+ },
60
+ "assign": {
61
+ "description": "Assign a board task to this agent",
62
+ "arguments": [
63
+ {
64
+ "name": "data",
65
+ "type": "object",
66
+ "properties": {
67
+ "task": {
68
+ "type": "string",
69
+ "title": "Path to board.task"
70
+ }
71
+ },
72
+ "required": [
73
+ "task"
74
+ ]
75
+ }
76
+ ]
77
+ },
78
+ "complete": {
79
+ "description": "Agent finished current task",
80
+ "arguments": []
81
+ },
82
+ "block": {
83
+ "description": "Agent is blocked",
84
+ "arguments": [
85
+ {
86
+ "name": "data",
87
+ "anyOf": [
88
+ {},
89
+ {
90
+ "type": "object",
91
+ "properties": {
92
+ "reason": {
93
+ "anyOf": [
94
+ {},
95
+ {
96
+ "type": "string"
97
+ }
98
+ ],
99
+ "title": "Reason"
100
+ }
101
+ }
102
+ }
103
+ ]
104
+ }
105
+ ]
106
+ },
107
+ "fail": {
108
+ "description": "Agent hit an error",
109
+ "arguments": [
110
+ {
111
+ "name": "data",
112
+ "anyOf": [
113
+ {},
114
+ {
115
+ "type": "object",
116
+ "properties": {
117
+ "error": {
118
+ "anyOf": [
119
+ {},
120
+ {
121
+ "type": "string"
122
+ }
123
+ ],
124
+ "title": "Error message"
125
+ }
126
+ }
127
+ }
128
+ ]
129
+ }
130
+ ]
131
+ }
132
+ }
133
+ }