bazilion 0.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 (260) hide show
  1. package/.understand-anything/.understandignore +25 -0
  2. package/.understand-anything/fingerprints.json +14267 -0
  3. package/.understand-anything/knowledge-graph.json +18128 -0
  4. package/.understand-anything/meta.json +6 -0
  5. package/CLAUDE.md +164 -0
  6. package/LICENSE +21 -0
  7. package/README.md +195 -0
  8. package/apps/cli/package.json +21 -0
  9. package/apps/cli/src/auth-file.ts +18 -0
  10. package/apps/cli/src/client.ts +37 -0
  11. package/apps/cli/src/columnize.ts +32 -0
  12. package/apps/cli/src/commands/agent.ts +574 -0
  13. package/apps/cli/src/commands/auth.ts +110 -0
  14. package/apps/cli/src/commands/backup.ts +135 -0
  15. package/apps/cli/src/commands/completion.ts +155 -0
  16. package/apps/cli/src/commands/config.ts +95 -0
  17. package/apps/cli/src/commands/doctor.ts +131 -0
  18. package/apps/cli/src/commands/group.ts +132 -0
  19. package/apps/cli/src/commands/inbox.ts +82 -0
  20. package/apps/cli/src/commands/login.ts +73 -0
  21. package/apps/cli/src/commands/memory.ts +106 -0
  22. package/apps/cli/src/commands/profile.ts +259 -0
  23. package/apps/cli/src/commands/provider.ts +170 -0
  24. package/apps/cli/src/commands/send.ts +24 -0
  25. package/apps/cli/src/commands/serve.ts +89 -0
  26. package/apps/cli/src/commands/skill.ts +120 -0
  27. package/apps/cli/src/commands/token.ts +148 -0
  28. package/apps/cli/src/commands/trigger.ts +129 -0
  29. package/apps/cli/src/commands/uninstall.ts +156 -0
  30. package/apps/cli/src/index.ts +196 -0
  31. package/apps/cli/src/paths.ts +12 -0
  32. package/apps/cli/test/agent.test.ts +213 -0
  33. package/apps/cli/test/backup.test.ts +95 -0
  34. package/apps/cli/test/chat.test.ts +539 -0
  35. package/apps/cli/test/columnize.test.ts +29 -0
  36. package/apps/cli/test/completion.test.ts +45 -0
  37. package/apps/cli/test/config-page.test.ts +151 -0
  38. package/apps/cli/test/group.test.ts +74 -0
  39. package/apps/cli/test/helpers.ts +70 -0
  40. package/apps/cli/test/inbox-autodeliver.test.ts +143 -0
  41. package/apps/cli/test/inbox.test.ts +147 -0
  42. package/apps/cli/test/memory.test.ts +69 -0
  43. package/apps/cli/test/profile.test.ts +110 -0
  44. package/apps/cli/test/send.test.ts +30 -0
  45. package/apps/cli/test/server-fixture.ts +212 -0
  46. package/apps/cli/test/session-head.test.ts +45 -0
  47. package/apps/cli/test/skill.test.ts +128 -0
  48. package/apps/cli/test/token.test.ts +109 -0
  49. package/apps/cli/test/trigger.test.ts +245 -0
  50. package/apps/cli/tsconfig.json +4 -0
  51. package/apps/daemon/package.json +31 -0
  52. package/apps/daemon/src/app.ts +41 -0
  53. package/apps/daemon/src/core/agent/archive.ts +8 -0
  54. package/apps/daemon/src/core/agent/delete.ts +30 -0
  55. package/apps/daemon/src/core/agent/resolve.ts +31 -0
  56. package/apps/daemon/src/core/agent/spawn.ts +95 -0
  57. package/apps/daemon/src/core/agent/unarchive.ts +11 -0
  58. package/apps/daemon/src/core/availableModels.ts +58 -0
  59. package/apps/daemon/src/core/db/client.ts +113 -0
  60. package/apps/daemon/src/core/db/migrate.ts +41 -0
  61. package/apps/daemon/src/core/db/migrations/0001_init.sql +179 -0
  62. package/apps/daemon/src/core/group/delete.ts +21 -0
  63. package/apps/daemon/src/core/group/register.ts +68 -0
  64. package/apps/daemon/src/core/index.ts +71 -0
  65. package/apps/daemon/src/core/paths.ts +56 -0
  66. package/apps/daemon/src/core/profile/create.ts +78 -0
  67. package/apps/daemon/src/core/profile/delete.ts +26 -0
  68. package/apps/daemon/src/core/profile/identity.ts +70 -0
  69. package/apps/daemon/src/core/profile/load.ts +45 -0
  70. package/apps/daemon/src/core/profile/seed.ts +74 -0
  71. package/apps/daemon/src/core/profile/templates.ts +60 -0
  72. package/apps/daemon/src/core/profile/update.ts +57 -0
  73. package/apps/daemon/src/core/profile/validate.ts +9 -0
  74. package/apps/daemon/src/core/repos/agents.ts +202 -0
  75. package/apps/daemon/src/core/repos/config.ts +78 -0
  76. package/apps/daemon/src/core/repos/groups.ts +55 -0
  77. package/apps/daemon/src/core/repos/messages.ts +127 -0
  78. package/apps/daemon/src/core/repos/profiles.ts +83 -0
  79. package/apps/daemon/src/core/repos/providerModels.ts +58 -0
  80. package/apps/daemon/src/core/repos/providerState.ts +37 -0
  81. package/apps/daemon/src/core/repos/secrets.ts +145 -0
  82. package/apps/daemon/src/core/repos/skillMeta.ts +49 -0
  83. package/apps/daemon/src/core/repos/triggers.ts +101 -0
  84. package/apps/daemon/src/core/repos/webTokens.ts +87 -0
  85. package/apps/daemon/src/core/secrets.ts +65 -0
  86. package/apps/daemon/src/core/services.ts +264 -0
  87. package/apps/daemon/src/core/skills/discover.ts +28 -0
  88. package/apps/daemon/src/core/skills/import.ts +136 -0
  89. package/apps/daemon/src/core/skills/parse.ts +52 -0
  90. package/apps/daemon/src/core/skills/resolve.ts +50 -0
  91. package/apps/daemon/src/index.ts +45 -0
  92. package/apps/daemon/src/lib/agent-cancel.ts +48 -0
  93. package/apps/daemon/src/lib/agent-id.ts +13 -0
  94. package/apps/daemon/src/lib/agent-turn.ts +56 -0
  95. package/apps/daemon/src/lib/api-key.ts +56 -0
  96. package/apps/daemon/src/lib/auth.ts +41 -0
  97. package/apps/daemon/src/lib/cron.ts +93 -0
  98. package/apps/daemon/src/lib/ctx.ts +80 -0
  99. package/apps/daemon/src/lib/messaging-host.ts +34 -0
  100. package/apps/daemon/src/lib/middleware-auth.ts +52 -0
  101. package/apps/daemon/src/lib/scheduler.ts +294 -0
  102. package/apps/daemon/src/routes/agents.ts +772 -0
  103. package/apps/daemon/src/routes/auth-login.ts +193 -0
  104. package/apps/daemon/src/routes/config.ts +267 -0
  105. package/apps/daemon/src/routes/groups.ts +133 -0
  106. package/apps/daemon/src/routes/messages.ts +29 -0
  107. package/apps/daemon/src/routes/misc.ts +239 -0
  108. package/apps/daemon/src/routes/profiles.ts +197 -0
  109. package/apps/daemon/src/routes/skills.ts +123 -0
  110. package/apps/daemon/src/routes/triggers.ts +29 -0
  111. package/apps/daemon/src/runtime/auth/openai-codex.ts +121 -0
  112. package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +31 -0
  113. package/apps/daemon/src/runtime/index.ts +77 -0
  114. package/apps/daemon/src/runtime/memory/files.ts +103 -0
  115. package/apps/daemon/src/runtime/memory/qmd.ts +152 -0
  116. package/apps/daemon/src/runtime/memory/types.ts +16 -0
  117. package/apps/daemon/src/runtime/pi/events.ts +173 -0
  118. package/apps/daemon/src/runtime/pi/session.ts +536 -0
  119. package/apps/daemon/src/runtime/pi/tools.ts +85 -0
  120. package/apps/daemon/src/runtime/providers/catalog.ts +145 -0
  121. package/apps/daemon/src/runtime/providers/pi-adapter.ts +272 -0
  122. package/apps/daemon/src/runtime/providers/registry.ts +374 -0
  123. package/apps/daemon/src/runtime/providers/retry.ts +176 -0
  124. package/apps/daemon/src/runtime/providers/types.ts +33 -0
  125. package/apps/daemon/src/runtime/session/prompt.ts +83 -0
  126. package/apps/daemon/src/runtime/tools/bootstrap.ts +22 -0
  127. package/apps/daemon/src/runtime/tools/home.ts +114 -0
  128. package/apps/daemon/src/runtime/tools/memory.ts +81 -0
  129. package/apps/daemon/src/runtime/tools/messaging.ts +127 -0
  130. package/apps/daemon/src/runtime/tools/registry.ts +29 -0
  131. package/apps/daemon/src/runtime/tools/types.ts +13 -0
  132. package/apps/daemon/src/runtime/tools/web-extract.ts +110 -0
  133. package/apps/daemon/src/runtime/tools/web-ssrf.ts +245 -0
  134. package/apps/daemon/src/runtime/tools/web.ts +273 -0
  135. package/apps/daemon/src/runtime/worker/entry.ts +221 -0
  136. package/apps/daemon/src/runtime/worker/ipc-protocol.ts +75 -0
  137. package/apps/daemon/src/runtime/worker/spawn.ts +249 -0
  138. package/apps/daemon/test/core/agents.test.ts +319 -0
  139. package/apps/daemon/test/core/available-models.test.ts +63 -0
  140. package/apps/daemon/test/core/config.test.ts +99 -0
  141. package/apps/daemon/test/core/groups.test.ts +63 -0
  142. package/apps/daemon/test/core/helpers.ts +50 -0
  143. package/apps/daemon/test/core/identity.test.ts +85 -0
  144. package/apps/daemon/test/core/migrations.test.ts +83 -0
  145. package/apps/daemon/test/core/profiles.test.ts +182 -0
  146. package/apps/daemon/test/core/provider-models.test.ts +57 -0
  147. package/apps/daemon/test/core/provider-state.test.ts +36 -0
  148. package/apps/daemon/test/core/skill-meta.test.ts +45 -0
  149. package/apps/daemon/test/core/skills.test.ts +271 -0
  150. package/apps/daemon/test/core/triggers.test.ts +182 -0
  151. package/apps/daemon/test/core/web-tokens.test.ts +79 -0
  152. package/apps/daemon/test/cron.test.ts +90 -0
  153. package/apps/daemon/test/runtime/heartbeat.test.ts +34 -0
  154. package/apps/daemon/test/runtime/memory-qmd.test.ts +97 -0
  155. package/apps/daemon/test/runtime/memory.test.ts +78 -0
  156. package/apps/daemon/test/runtime/messaging.test.ts +213 -0
  157. package/apps/daemon/test/runtime/mock-server.ts +65 -0
  158. package/apps/daemon/test/runtime/openai-codex-auth.test.ts +116 -0
  159. package/apps/daemon/test/runtime/providers.test.ts +306 -0
  160. package/apps/daemon/test/runtime/retry.test.ts +191 -0
  161. package/apps/daemon/test/runtime/session-head.test.ts +90 -0
  162. package/apps/daemon/test/runtime/tools-home.test.ts +102 -0
  163. package/apps/daemon/test/runtime/tools-web.test.ts +206 -0
  164. package/apps/daemon/tsconfig.json +4 -0
  165. package/apps/mobile/README.md +60 -0
  166. package/apps/mobile/app/_layout.tsx +58 -0
  167. package/apps/mobile/app/agents/[id]/chat.tsx +486 -0
  168. package/apps/mobile/app/agents/[id]/index.tsx +166 -0
  169. package/apps/mobile/app/agents/index.tsx +212 -0
  170. package/apps/mobile/app/index.tsx +21 -0
  171. package/apps/mobile/app/pair.tsx +226 -0
  172. package/apps/mobile/app/settings.tsx +419 -0
  173. package/apps/mobile/app.json +49 -0
  174. package/apps/mobile/assets/adaptive-icon.png +0 -0
  175. package/apps/mobile/assets/favicon.png +0 -0
  176. package/apps/mobile/assets/icon.png +0 -0
  177. package/apps/mobile/assets/splash-icon.png +0 -0
  178. package/apps/mobile/babel.config.js +6 -0
  179. package/apps/mobile/metro.config.js +28 -0
  180. package/apps/mobile/package.json +44 -0
  181. package/apps/mobile/src/auth.ts +66 -0
  182. package/apps/mobile/src/pair-url.ts +48 -0
  183. package/apps/mobile/src/theme-context.tsx +88 -0
  184. package/apps/mobile/src/theme.ts +135 -0
  185. package/apps/mobile/test/pair-url.test.ts +46 -0
  186. package/apps/mobile/tsconfig.json +23 -0
  187. package/apps/web/components.json +25 -0
  188. package/apps/web/package.json +39 -0
  189. package/apps/web/public/baziu.svg +8 -0
  190. package/apps/web/src/components/AgentTabs.tsx +45 -0
  191. package/apps/web/src/components/BaziuLogo.tsx +21 -0
  192. package/apps/web/src/components/ChatPane.tsx +1033 -0
  193. package/apps/web/src/components/ConfigTabs.tsx +29 -0
  194. package/apps/web/src/components/CopyButton.tsx +68 -0
  195. package/apps/web/src/components/CreateGroupDialog.tsx +127 -0
  196. package/apps/web/src/components/FieldRow.tsx +94 -0
  197. package/apps/web/src/components/Footer.tsx +10 -0
  198. package/apps/web/src/components/PawIcon.tsx +15 -0
  199. package/apps/web/src/components/Sidebar.tsx +287 -0
  200. package/apps/web/src/components/SpawnDialog.tsx +129 -0
  201. package/apps/web/src/components/ThemeToggle.tsx +75 -0
  202. package/apps/web/src/components/TopNav.tsx +34 -0
  203. package/apps/web/src/components/ui/button.tsx +67 -0
  204. package/apps/web/src/components/ui/card.tsx +103 -0
  205. package/apps/web/src/components/ui/checkbox.tsx +31 -0
  206. package/apps/web/src/components/ui/dialog.tsx +168 -0
  207. package/apps/web/src/components/ui/input.tsx +19 -0
  208. package/apps/web/src/components/ui/label.tsx +22 -0
  209. package/apps/web/src/components/ui/radio-group.tsx +44 -0
  210. package/apps/web/src/components/ui/select.tsx +192 -0
  211. package/apps/web/src/components/ui/separator.tsx +26 -0
  212. package/apps/web/src/components/ui/table.tsx +116 -0
  213. package/apps/web/src/components/ui/tabs.tsx +88 -0
  214. package/apps/web/src/components/ui/textarea.tsx +18 -0
  215. package/apps/web/src/lib/auth.ts +50 -0
  216. package/apps/web/src/lib/daemon-client.ts +34 -0
  217. package/apps/web/src/lib/md.ts +45 -0
  218. package/apps/web/src/lib/utils.ts +6 -0
  219. package/apps/web/src/lib/wire-constants.ts +27 -0
  220. package/apps/web/src/routeTree.gen.ts +408 -0
  221. package/apps/web/src/router.tsx +20 -0
  222. package/apps/web/src/routes/__root.tsx +123 -0
  223. package/apps/web/src/routes/agents/$id/inbox.tsx +207 -0
  224. package/apps/web/src/routes/agents/$id/index.tsx +527 -0
  225. package/apps/web/src/routes/agents/$id/triggers.tsx +239 -0
  226. package/apps/web/src/routes/agents/index.tsx +265 -0
  227. package/apps/web/src/routes/api/$.ts +88 -0
  228. package/apps/web/src/routes/config/index.tsx +315 -0
  229. package/apps/web/src/routes/config/services.tsx +49 -0
  230. package/apps/web/src/routes/config/tokens.tsx +192 -0
  231. package/apps/web/src/routes/groups/$id/index.tsx +153 -0
  232. package/apps/web/src/routes/groups/$id/memory.tsx +321 -0
  233. package/apps/web/src/routes/groups/index.tsx +191 -0
  234. package/apps/web/src/routes/index.tsx +133 -0
  235. package/apps/web/src/routes/login.tsx +63 -0
  236. package/apps/web/src/routes/profiles/$id.tsx +549 -0
  237. package/apps/web/src/routes/profiles/index.tsx +458 -0
  238. package/apps/web/src/routes/skills/index.tsx +297 -0
  239. package/apps/web/src/routes/welcome.tsx +61 -0
  240. package/apps/web/src/styles.css +449 -0
  241. package/apps/web/tsconfig.json +25 -0
  242. package/apps/web/vite.config.ts +25 -0
  243. package/biome.json +23 -0
  244. package/docs/agent-engine.md +219 -0
  245. package/docs/architecture.md +627 -0
  246. package/docs/backlog/README.md +42 -0
  247. package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +125 -0
  248. package/docs/openclaw-reference.md +210 -0
  249. package/package.json +38 -0
  250. package/packages/api-types/package.json +11 -0
  251. package/packages/api-types/src/entities.ts +146 -0
  252. package/packages/api-types/src/events.ts +55 -0
  253. package/packages/api-types/src/index.ts +488 -0
  254. package/packages/api-types/src/memory.ts +15 -0
  255. package/packages/client/package.json +13 -0
  256. package/packages/client/src/index.ts +117 -0
  257. package/pnpm-workspace.yaml +3 -0
  258. package/tsconfig.base.json +23 -0
  259. package/tsconfig.json +11 -0
  260. package/vitest.config.ts +22 -0
@@ -0,0 +1,151 @@
1
+ import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'
2
+ import { startTestServer, type TestServer } from './server-fixture.ts'
3
+
4
+ // End-to-end coverage for the /config page's HTTP surface — provider
5
+ // enable/disable toggle and per-field storage dispatch.
6
+ //
7
+ // The server fixture enables `lmstudio` + `ollama` by default (see
8
+ // `initHome` in server-fixture.ts). These tests explicitly toggle them to
9
+ // verify the switch flows through the API, the DB, and back out via the
10
+ // `/api/config/providers` endpoint the web UI + CLI both consume.
11
+
12
+ let server: TestServer
13
+
14
+ beforeAll(async () => {
15
+ server = await startTestServer()
16
+ })
17
+ afterAll(async () => {
18
+ await server.stop()
19
+ })
20
+ beforeEach(() => server.reset())
21
+
22
+ interface ProviderEntry {
23
+ id: string
24
+ enabled: boolean
25
+ curated: string[]
26
+ fields: { envVar: string; kind: 'secret' | 'config'; set: boolean }[]
27
+ }
28
+
29
+ async function fetchProviders(): Promise<ProviderEntry[]> {
30
+ const res = await fetch(`${server.url}/api/config/providers`, {
31
+ headers: { cookie: `bz_token=${server.token}` },
32
+ })
33
+ expect(res.ok).toBe(true)
34
+ const body = (await res.json()) as { providers: ProviderEntry[] }
35
+ return body.providers
36
+ }
37
+
38
+ async function findProvider(id: string): Promise<ProviderEntry> {
39
+ const providers = await fetchProviders()
40
+ const p = providers.find((x) => x.id === id)
41
+ if (!p) throw new Error(`provider ${id} missing from /api/config/providers response`)
42
+ return p
43
+ }
44
+
45
+ test('provider toggle defaults respect the test fixture seed (lmstudio enabled)', async () => {
46
+ const lmstudio = await findProvider('lmstudio')
47
+ expect(lmstudio.enabled).toBe(true)
48
+ const anthropic = await findProvider('anthropic')
49
+ expect(anthropic.enabled).toBe(false)
50
+ })
51
+
52
+ test('bazilion provider disable + enable round-trip via CLI', async () => {
53
+ const before = await findProvider('lmstudio')
54
+ expect(before.enabled).toBe(true)
55
+
56
+ const off = await server.cli(['provider', 'disable', 'lmstudio'])
57
+ expect(off.exitCode).toBe(0)
58
+ expect(off.stdout).toContain('disabled')
59
+
60
+ const mid = await findProvider('lmstudio')
61
+ expect(mid.enabled).toBe(false)
62
+
63
+ const on = await server.cli(['provider', 'enable', 'lmstudio'])
64
+ expect(on.exitCode).toBe(0)
65
+ expect(on.stdout).toContain('enabled')
66
+
67
+ const after = await findProvider('lmstudio')
68
+ expect(after.enabled).toBe(true)
69
+ })
70
+
71
+ test('provider enable via API accepts {enabled: boolean} and round-trips state', async () => {
72
+ const res = await fetch(`${server.url}/api/config/providers/anthropic/enabled`, {
73
+ method: 'PUT',
74
+ headers: {
75
+ cookie: `bz_token=${server.token}`,
76
+ origin: server.url,
77
+ 'content-type': 'application/json',
78
+ },
79
+ body: JSON.stringify({ enabled: true }),
80
+ })
81
+ expect(res.status).toBe(200)
82
+ const body = (await res.json()) as { name: string; enabled: boolean }
83
+ expect(body).toEqual({ name: 'anthropic', enabled: true })
84
+ const anthropic = await findProvider('anthropic')
85
+ expect(anthropic.enabled).toBe(true)
86
+ })
87
+
88
+ test('toggling an unknown provider returns 404', async () => {
89
+ const res = await fetch(`${server.url}/api/config/providers/martian/enabled`, {
90
+ method: 'PUT',
91
+ headers: {
92
+ cookie: `bz_token=${server.token}`,
93
+ origin: server.url,
94
+ 'content-type': 'application/json',
95
+ },
96
+ body: JSON.stringify({ enabled: true }),
97
+ })
98
+ expect(res.status).toBe(404)
99
+ })
100
+
101
+ test('bazilion config set routes config-kind fields to plaintext store', async () => {
102
+ const set = await server.cli(['config', 'set', 'LMSTUDIO_URL', 'http://127.0.0.1:9999/v1'])
103
+ expect(set.exitCode).toBe(0)
104
+ // CLI echoes "(config)" for the kind.
105
+ expect(set.stdout).toMatch(/LMSTUDIO_URL.*\(config\)/)
106
+ expect(set.stdout).toContain('http://127.0.0.1:9999/v1')
107
+
108
+ // Verify it lands in the provider fields state.
109
+ const lmstudio = await findProvider('lmstudio')
110
+ const url = lmstudio.fields.find((f) => f.envVar === 'LMSTUDIO_URL')
111
+ expect(url?.set).toBe(true)
112
+ })
113
+
114
+ test('bazilion config set routes secret-kind fields to encrypted store', async () => {
115
+ const set = await server.cli(['config', 'set', 'ANTHROPIC_API_KEY', 'sk-ant-testing-1234567890'])
116
+ expect(set.exitCode).toBe(0)
117
+ expect(set.stdout).toMatch(/ANTHROPIC_API_KEY.*\(secret\)/)
118
+ // Secret should be displayed as a masked preview, not the full value.
119
+ expect(set.stdout).not.toContain('sk-ant-testing-1234567890')
120
+
121
+ const anthropic = await findProvider('anthropic')
122
+ const key = anthropic.fields.find((f) => f.envVar === 'ANTHROPIC_API_KEY')
123
+ expect(key?.set).toBe(true)
124
+ })
125
+
126
+ test('bazilion config rm clears both kinds', async () => {
127
+ await server.cli(['config', 'set', 'LMSTUDIO_URL', 'http://example.com/v1'])
128
+ await server.cli(['config', 'set', 'OPENAI_API_KEY', 'sk-test-key-abcdefgh'])
129
+
130
+ const rmUrl = await server.cli(['config', 'rm', 'LMSTUDIO_URL'])
131
+ expect(rmUrl.exitCode).toBe(0)
132
+ const rmKey = await server.cli(['config', 'rm', 'OPENAI_API_KEY'])
133
+ expect(rmKey.exitCode).toBe(0)
134
+
135
+ const lmstudio = await findProvider('lmstudio')
136
+ expect(lmstudio.fields.find((f) => f.envVar === 'LMSTUDIO_URL')?.set).toBe(false)
137
+ const openai = await findProvider('openai')
138
+ expect(openai.fields.find((f) => f.envVar === 'OPENAI_API_KEY')?.set).toBe(false)
139
+ })
140
+
141
+ test('bazilion config set rejects unknown envVars', async () => {
142
+ const r = await server.cli(['config', 'set', 'TOTALLY_MADE_UP_KEY', 'x'])
143
+ expect(r.exitCode).not.toBe(0)
144
+ expect(r.stderr + r.stdout).toMatch(/unknown envVar/i)
145
+ })
146
+
147
+ // SSR-regression coverage that previously asserted the rendered /config HTML
148
+ // matched provider_state lived here. After the daemon split, the test fixture
149
+ // boots only the daemon (the canonical /api/* surface). The SSR page now
150
+ // renders exclusively from the same `/api/config/providers` response we
151
+ // already exercise above — those API checks are the load-bearing assertions.
@@ -0,0 +1,74 @@
1
+ import { existsSync, lstatSync, mkdtempSync } from 'node:fs'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+ import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'
5
+ import { startTestServer, type TestServer } from './server-fixture.ts'
6
+
7
+ let server: TestServer
8
+ beforeAll(async () => {
9
+ server = await startTestServer()
10
+ })
11
+ afterAll(() => server.stop())
12
+ beforeEach(() => server.reset())
13
+
14
+ test('group add + list + rm (slug-only, real dir under groups/)', async () => {
15
+ let r = await server.cli(['group', 'add', 'shared1'])
16
+ expect(r.exitCode).toBe(0)
17
+ expect(r.stdout).toContain('registered group shared1')
18
+
19
+ // Real dir created at ~/.bazilion/groups/shared1/
20
+ const slot = join(server.home, 'groups', 'shared1')
21
+ expect(existsSync(slot)).toBe(true)
22
+ expect(lstatSync(slot).isSymbolicLink()).toBe(false)
23
+ expect(existsSync(join(slot, 'memory'))).toBe(true)
24
+
25
+ r = await server.cli(['group', 'list'])
26
+ expect(r.stdout).toContain('shared1')
27
+ expect(r.stdout).toContain(slot)
28
+
29
+ r = await server.cli(['group', 'rm', 'shared1'])
30
+ expect(r.exitCode).toBe(0)
31
+
32
+ r = await server.cli(['group', 'list'])
33
+ // Auto-seeded 'default' group from the fixture stays; only the removed one is gone.
34
+ expect(r.stdout).not.toContain('shared1')
35
+ expect(r.stdout).toContain('default')
36
+ })
37
+
38
+ test('group add --link materializes the slot as a symlink', async () => {
39
+ const target = mkdtempSync(join(tmpdir(), 'bazilion-cli-link-'))
40
+ const r = await server.cli(['group', 'add', 'linked', '--link', target])
41
+ expect(r.exitCode).toBe(0)
42
+ const slot = join(server.home, 'groups', 'linked')
43
+ expect(lstatSync(slot).isSymbolicLink()).toBe(true)
44
+ })
45
+
46
+ test('group add --link fails when target does not exist', async () => {
47
+ const r = await server.cli([
48
+ 'group',
49
+ 'add',
50
+ 'ghost',
51
+ '--link',
52
+ '/tmp/bazilion-test-no-such-path-xyz',
53
+ ])
54
+ expect(r.exitCode).not.toBe(0)
55
+ expect(r.stderr + r.stdout).toContain('does not exist')
56
+ })
57
+
58
+ test('group user-md set + show + clear round-trip', async () => {
59
+ let r = await server.cli(['group', 'user-md', 'show', 'default'])
60
+ expect(r.exitCode).toBe(0)
61
+ expect(r.stdout.trim()).toBe('')
62
+
63
+ r = await server.cli(['group', 'user-md', 'set', 'default', '--text', 'call me Pat'])
64
+ expect(r.exitCode).toBe(0)
65
+
66
+ r = await server.cli(['group', 'user-md', 'show', 'default'])
67
+ expect(r.stdout).toContain('call me Pat')
68
+
69
+ r = await server.cli(['group', 'user-md', 'clear', 'default'])
70
+ expect(r.exitCode).toBe(0)
71
+
72
+ r = await server.cli(['group', 'user-md', 'show', 'default'])
73
+ expect(r.stdout.trim()).toBe('')
74
+ })
@@ -0,0 +1,70 @@
1
+ import { spawn } from 'node:child_process'
2
+ import { mkdtempSync, rmSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+
6
+ const cliEntry = join(import.meta.dirname, '..', 'src', 'index.ts')
7
+
8
+ export interface CliResult {
9
+ stdout: string
10
+ stderr: string
11
+ exitCode: number
12
+ }
13
+
14
+ // Test CLI spawns use Node 22.12+'s built-in `--experimental-strip-types`
15
+ // instead of `tsx`. Startup drops from ~1.5s (tsx's transform cost) to
16
+ // ~140ms (node loading .ts via native strip). Across a full suite with
17
+ // hundreds of CLI invocations that's ~60s saved wall-clock. The project
18
+ // already requires Node 22.12 (engines in root package.json), and no code
19
+ // uses enums/namespaces — the only TS forms strip-types can't handle.
20
+ //
21
+ // `--no-warnings` silences the "ExperimentalWarning: Type Stripping is an
22
+ // experimental feature" banner that would otherwise land on stderr and
23
+ // upset tests that assert clean stderr. On Node 25+ the flag is
24
+ // effectively stable but the warning still fires.
25
+ const STRIP_TYPES_ARGS = ['--experimental-strip-types', '--no-warnings']
26
+
27
+ export function runCli(
28
+ args: string[],
29
+ home: string,
30
+ extraEnv: NodeJS.ProcessEnv = {},
31
+ ): Promise<CliResult> {
32
+ return new Promise((resolve, reject) => {
33
+ const proc = spawn(process.execPath, [...STRIP_TYPES_ARGS, cliEntry, ...args], {
34
+ env: { ...process.env, BAZILION_HOME: home, ...extraEnv },
35
+ })
36
+ const stdoutChunks: Buffer[] = []
37
+ const stderrChunks: Buffer[] = []
38
+ proc.stdout.on('data', (c: Buffer) => stdoutChunks.push(c))
39
+ proc.stderr.on('data', (c: Buffer) => stderrChunks.push(c))
40
+ proc.on('error', reject)
41
+ proc.on('close', (code) => {
42
+ resolve({
43
+ stdout: Buffer.concat(stdoutChunks).toString('utf8'),
44
+ stderr: Buffer.concat(stderrChunks).toString('utf8'),
45
+ exitCode: code ?? 0,
46
+ })
47
+ })
48
+ })
49
+ }
50
+
51
+ export interface TestHome {
52
+ home: string
53
+ cleanup: () => void
54
+ }
55
+
56
+ export function makeHome(): TestHome {
57
+ const home = mkdtempSync(join(tmpdir(), 'bazilion-cli-test-'))
58
+ return {
59
+ home,
60
+ cleanup() {
61
+ rmSync(home, { recursive: true, force: true })
62
+ },
63
+ }
64
+ }
65
+
66
+ export function extractAgentId(stdout: string): string {
67
+ const m = stdout.match(/spawned agent ([\w-]+)/)
68
+ if (!m?.[1]) throw new Error(`could not extract agent id from: ${stdout}`)
69
+ return m[1]
70
+ }
@@ -0,0 +1,143 @@
1
+ // Integration test for scheduler-driven inbox auto-delivery.
2
+ //
3
+ // Exercises the end-to-end flow: a message is inserted into agent B's inbox
4
+ // (via the `/api/agents/:id/messages` POST, same path the CLI's `bazilion
5
+ // send` uses), the scheduler tick should drain B's unread mail within one
6
+ // tick interval and fire a turn for B with the messages embedded in the
7
+ // prompt. We verify that by watching for a run row against B and matching
8
+ // the mock LLM's received system prompt would be overkill — the presence
9
+ // of a fresh run is sufficient signal the wake loop fired.
10
+
11
+ import { createServer, type ServerResponse } from 'node:http'
12
+ import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'
13
+ import { extractAgentId } from './helpers.ts'
14
+ import { startTestServer, type TestServer } from './server-fixture.ts'
15
+
16
+ interface MockLlm {
17
+ url: string
18
+ push(responses: unknown[]): void
19
+ reset(): void
20
+ stop(): Promise<void>
21
+ callCount(): number
22
+ }
23
+
24
+ interface Canned {
25
+ choices: Array<{
26
+ message: { role?: string; content?: string | null }
27
+ finish_reason?: string
28
+ }>
29
+ }
30
+
31
+ function writeSse(res: ServerResponse, canned: Canned): void {
32
+ res.writeHead(200, {
33
+ 'content-type': 'text/event-stream',
34
+ 'cache-control': 'no-cache',
35
+ connection: 'keep-alive',
36
+ })
37
+ const msg = canned.choices[0]?.message ?? {}
38
+ const finish = canned.choices[0]?.finish_reason ?? 'stop'
39
+ res.write(
40
+ `data: ${JSON.stringify({
41
+ id: 'mock',
42
+ object: 'chat.completion.chunk',
43
+ choices: [{ index: 0, delta: { role: 'assistant' }, finish_reason: null }],
44
+ })}\n\n`,
45
+ )
46
+ if (typeof msg.content === 'string' && msg.content.length > 0) {
47
+ res.write(
48
+ `data: ${JSON.stringify({
49
+ id: 'mock',
50
+ object: 'chat.completion.chunk',
51
+ choices: [{ index: 0, delta: { content: msg.content }, finish_reason: null }],
52
+ })}\n\n`,
53
+ )
54
+ }
55
+ res.write(
56
+ `data: ${JSON.stringify({
57
+ id: 'mock',
58
+ object: 'chat.completion.chunk',
59
+ choices: [{ index: 0, delta: {}, finish_reason: finish }],
60
+ })}\n\n`,
61
+ )
62
+ res.write('data: [DONE]\n\n')
63
+ res.end()
64
+ }
65
+
66
+ function startMock(): Promise<MockLlm> {
67
+ const queue: unknown[] = []
68
+ let calls = 0
69
+ const server = createServer((_req, res) => {
70
+ calls++
71
+ const item = queue.shift() ?? {
72
+ choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],
73
+ }
74
+ writeSse(res, item as Canned)
75
+ })
76
+ return new Promise((resolve) => {
77
+ server.listen(0, () => {
78
+ const addr = server.address()
79
+ if (!addr || typeof addr === 'string') throw new Error('unexpected address')
80
+ resolve({
81
+ url: `http://localhost:${addr.port}/v1`,
82
+ push(items) {
83
+ queue.push(...items)
84
+ },
85
+ reset() {
86
+ queue.length = 0
87
+ calls = 0
88
+ },
89
+ stop: () => new Promise((r) => server.close(() => r())),
90
+ callCount() {
91
+ return calls
92
+ },
93
+ })
94
+ })
95
+ })
96
+ }
97
+
98
+ let mock: MockLlm
99
+ let server: TestServer
100
+
101
+ beforeAll(async () => {
102
+ mock = await startMock()
103
+ server = await startTestServer({
104
+ LMSTUDIO_URL: mock.url,
105
+ // Fast scheduler ticks so the test doesn't wait 5s per auto-deliver.
106
+ BAZILION_SCHEDULER_TICK_MS: '200',
107
+ })
108
+ })
109
+ afterAll(async () => {
110
+ await server.stop()
111
+ await mock.stop()
112
+ })
113
+ beforeEach(() => {
114
+ server.reset()
115
+ mock.reset()
116
+ })
117
+
118
+ async function spawnAgent(name: string): Promise<string> {
119
+ const r = await server.cli(['agent', 'spawn', '--profile', 'p', '--name', name])
120
+ return extractAgentId(r.stdout)
121
+ }
122
+
123
+ test('unread message to an idle agent triggers an auto-delivered turn', async () => {
124
+ await server.cli(['profile', 'create', 'p', '--model', 'lmstudio:test-model'])
125
+ const a = await spawnAgent('alice')
126
+ const b = await spawnAgent('bob')
127
+
128
+ // Post a message from A to B through the HTTP API (same code path `bazilion
129
+ // send` uses).
130
+ const send = await server.cli(['send', a, b, 'hello bob, please reply'])
131
+ expect(send.exitCode).toBe(0)
132
+
133
+ // Wait up to 10s for B's auto-wake turn to hit the mock LLM.
134
+ let llmHit = false
135
+ for (let i = 0; i < 100; i++) {
136
+ if (mock.callCount() > 0) {
137
+ llmHit = true
138
+ break
139
+ }
140
+ await new Promise((r) => setTimeout(r, 100))
141
+ }
142
+ expect(llmHit).toBe(true)
143
+ })
@@ -0,0 +1,147 @@
1
+ import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'
2
+ import { extractAgentId } from './helpers.ts'
3
+ import { startTestServer, type TestServer } from './server-fixture.ts'
4
+
5
+ // This suite tests the inbox CLI / HTTP surface (list / show / read / replyTo)
6
+ // in isolation. Bazilion's scheduler runs inbox auto-delivery every tick,
7
+ // which would drain these tests' seeded messages and mark them read before
8
+ // the assertions run. We disable the scheduler for this fixture; the
9
+ // auto-delivery flow is covered end-to-end by `inbox-autodeliver.test.ts`.
10
+ let server: TestServer
11
+ beforeAll(async () => {
12
+ server = await startTestServer({ BAZILION_SCHEDULER: 'off' })
13
+ })
14
+ afterAll(() => server.stop())
15
+ beforeEach(() => server.reset())
16
+
17
+ async function spawnTwoAgents(): Promise<{ a: string; b: string }> {
18
+ await server.cli(['profile', 'create', 'p', '--model', 'm'])
19
+ let r = await server.cli(['agent', 'spawn', '--profile', 'p'])
20
+ const a = extractAgentId(r.stdout)
21
+ r = await server.cli(['agent', 'spawn', '--profile', 'p'])
22
+ const b = extractAgentId(r.stdout)
23
+ return { a, b }
24
+ }
25
+
26
+ function firstIdFromSend(stdout: string): string {
27
+ const m = stdout.match(/sent message ([\w-]+)/)
28
+ if (!m?.[1]) throw new Error(`no message id in: ${stdout}`)
29
+ return m[1]
30
+ }
31
+
32
+ function parseInboxIds(stdout: string): string[] {
33
+ return stdout
34
+ .split('\n')
35
+ .map((l) => l.trim())
36
+ .filter((l) => l.length > 0 && !l.startsWith('('))
37
+ .map((l) => l.split(/\s+/)[0] ?? '')
38
+ .filter((x) => x.length > 0)
39
+ }
40
+
41
+ test('inbox list shows sent messages', async () => {
42
+ const { a, b } = await spawnTwoAgents()
43
+ const send = await server.cli(['send', a, b, 'first'])
44
+ const id = firstIdFromSend(send.stdout)
45
+
46
+ const r = await server.cli(['inbox', 'list', b])
47
+ expect(r.exitCode).toBe(0)
48
+ expect(parseInboxIds(r.stdout)).toContain(id)
49
+ expect(r.stdout).toContain('first')
50
+ expect(r.stdout).toContain('NEW')
51
+ })
52
+
53
+ test('inbox list --unread filters out read messages', async () => {
54
+ const { a, b } = await spawnTwoAgents()
55
+ const s1 = await server.cli(['send', a, b, 'one'])
56
+ const id1 = firstIdFromSend(s1.stdout)
57
+ await server.cli(['send', a, b, 'two'])
58
+
59
+ await server.cli(['inbox', 'read', id1])
60
+
61
+ const all = await server.cli(['inbox', 'list', b])
62
+ expect(parseInboxIds(all.stdout).length).toBe(2)
63
+
64
+ const unread = await server.cli(['inbox', 'list', b, '--unread'])
65
+ const unreadIds = parseInboxIds(unread.stdout)
66
+ expect(unreadIds).not.toContain(id1)
67
+ expect(unreadIds.length).toBe(1)
68
+ })
69
+
70
+ test('inbox show prints message body', async () => {
71
+ const { a, b } = await spawnTwoAgents()
72
+ const s = await server.cli(['send', a, b, 'hello body'])
73
+ const id = firstIdFromSend(s.stdout)
74
+
75
+ const r = await server.cli(['inbox', 'show', id])
76
+ expect(r.exitCode).toBe(0)
77
+ expect(r.stdout).toContain('hello body')
78
+ expect(r.stdout).toContain(`id: ${id}`)
79
+ expect(r.stdout).toContain(`from: ${a}`)
80
+ })
81
+
82
+ test('inbox read marks a message read', async () => {
83
+ const { a, b } = await spawnTwoAgents()
84
+ const s = await server.cli(['send', a, b, 'mark me'])
85
+ const id = firstIdFromSend(s.stdout)
86
+
87
+ const before = await server.cli(['inbox', 'show', id])
88
+ expect(before.stdout).toContain('read: (unread)')
89
+
90
+ const r = await server.cli(['inbox', 'read', id])
91
+ expect(r.exitCode).toBe(0)
92
+ expect(r.stdout).toContain(`marked read: ${id}`)
93
+
94
+ const after = await server.cli(['inbox', 'show', id])
95
+ expect(after.stdout).not.toContain('(unread)')
96
+ })
97
+
98
+ test('inbox show fails for unknown message id', async () => {
99
+ await server.cli(['profile', 'create', 'p', '--model', 'm'])
100
+ const r = await server.cli(['inbox', 'show', 'nope-not-real'])
101
+ expect(r.exitCode).not.toBe(0)
102
+ expect(r.stderr + r.stdout).toContain('message not found')
103
+ })
104
+
105
+ test('POST with replyTo links the messages', async () => {
106
+ const { a, b } = await spawnTwoAgents()
107
+ const first = await server.cli(['send', a, b, 'please reply'])
108
+ const firstId = firstIdFromSend(first.stdout)
109
+
110
+ const res = await fetch(`${server.url}/api/agents/${a}/messages`, {
111
+ method: 'POST',
112
+ headers: {
113
+ 'content-type': 'application/json',
114
+ cookie: `bz_token=${server.token}`,
115
+ },
116
+ body: JSON.stringify({
117
+ from: b,
118
+ payload: { text: 'here is a reply' },
119
+ replyTo: firstId,
120
+ }),
121
+ })
122
+ expect(res.status).toBe(201)
123
+ const msg = (await res.json()) as { id: string; replyTo: string | null }
124
+ expect(msg.replyTo).toBe(firstId)
125
+
126
+ const show = await server.cli(['inbox', 'show', msg.id])
127
+ expect(show.stdout).toContain(`reply_to: ${firstId}`)
128
+ })
129
+
130
+ test('POST replyTo to missing message is rejected', async () => {
131
+ const { a, b } = await spawnTwoAgents()
132
+ const res = await fetch(`${server.url}/api/agents/${b}/messages`, {
133
+ method: 'POST',
134
+ headers: {
135
+ 'content-type': 'application/json',
136
+ cookie: `bz_token=${server.token}`,
137
+ },
138
+ body: JSON.stringify({
139
+ from: a,
140
+ payload: { text: 'ghost reply' },
141
+ replyTo: 'not-a-real-id',
142
+ }),
143
+ })
144
+ expect(res.status).toBe(404)
145
+ const err = (await res.json()) as { error: string }
146
+ expect(err.error).toContain('reply target not found')
147
+ })
@@ -0,0 +1,69 @@
1
+ import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'
2
+ import { startTestServer, type TestServer } from './server-fixture.ts'
3
+
4
+ // The fixture seeds a `default` group on every reset — that's our scope.
5
+ const GROUP = 'default'
6
+
7
+ let server: TestServer
8
+ beforeAll(async () => {
9
+ server = await startTestServer()
10
+ })
11
+ afterAll(() => server.stop())
12
+ beforeEach(() => server.reset())
13
+
14
+ test('memory write + read round-trip', async () => {
15
+ let r = await server.cli(['memory', 'write', GROUP, 'colors.md', 'My favorite color is blue.'])
16
+ expect(r.exitCode).toBe(0)
17
+ expect(r.stdout).toContain('wrote colors.md')
18
+
19
+ r = await server.cli(['memory', 'read', GROUP, 'colors.md'])
20
+ expect(r.exitCode).toBe(0)
21
+ expect(r.stdout).toContain('My favorite color is blue')
22
+ })
23
+
24
+ test('memory search finds written entries', async () => {
25
+ await server.cli(['memory', 'write', GROUP, 'a.md', 'I like pasta'])
26
+ await server.cli(['memory', 'write', GROUP, 'b.md', 'I like pizza'])
27
+ await server.cli(['memory', 'write', GROUP, 'c.md', 'I like cars'])
28
+
29
+ const r = await server.cli(['memory', 'search', GROUP, 'pizza'])
30
+ expect(r.exitCode).toBe(0)
31
+ expect(r.stdout).toContain('b.md')
32
+ expect(r.stdout).not.toContain('a.md')
33
+ })
34
+
35
+ test('memory list shows all entries', async () => {
36
+ await server.cli(['memory', 'write', GROUP, 'one.md', 'one'])
37
+ await server.cli(['memory', 'write', GROUP, 'two.md', 'two'])
38
+
39
+ const r = await server.cli(['memory', 'list', GROUP])
40
+ expect(r.stdout).toContain('one.md')
41
+ expect(r.stdout).toContain('two.md')
42
+ })
43
+
44
+ test('memory rm deletes the entry', async () => {
45
+ await server.cli(['memory', 'write', GROUP, 'tmp.md', 'temporary'])
46
+ await server.cli(['memory', 'rm', GROUP, 'tmp.md'])
47
+ const r = await server.cli(['memory', 'read', GROUP, 'tmp.md'])
48
+ expect(r.exitCode).not.toBe(0)
49
+ expect(r.stderr + r.stdout).toContain('not found')
50
+ })
51
+
52
+ test('memory persists across CLI invocations under the group dir', async () => {
53
+ await server.cli(['memory', 'write', GROUP, 'persist.md', 'survives restart'])
54
+ // New CLI invocation = new subprocess; state lives server-side.
55
+ const r = await server.cli(['memory', 'read', GROUP, 'persist.md'])
56
+ expect(r.stdout).toContain('survives restart')
57
+ })
58
+
59
+ test('memory is shared across every agent in the group', async () => {
60
+ // Spawn two agents both implicitly placed in `default`. They must see
61
+ // the same entry — the whole point of moving memory to the group level.
62
+ await server.cli(['profile', 'create', 'p', '--model', 'm'])
63
+ await server.cli(['agent', 'spawn', '--profile', 'p'])
64
+ await server.cli(['agent', 'spawn', '--profile', 'p'])
65
+
66
+ await server.cli(['memory', 'write', GROUP, 'shared.md', 'visible to all'])
67
+ const r = await server.cli(['memory', 'read', GROUP, 'shared.md'])
68
+ expect(r.stdout).toContain('visible to all')
69
+ })