bazilion 0.0.0 → 0.1.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 (265) hide show
  1. package/README.md +53 -152
  2. package/dist/cli.js +2730 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/daemon.js +5888 -0
  5. package/dist/daemon.js.map +1 -0
  6. package/dist/worker.js +1830 -0
  7. package/dist/worker.js.map +1 -0
  8. package/package.json +57 -28
  9. package/.understand-anything/.understandignore +0 -25
  10. package/.understand-anything/fingerprints.json +0 -14267
  11. package/.understand-anything/knowledge-graph.json +0 -18128
  12. package/.understand-anything/meta.json +0 -6
  13. package/CLAUDE.md +0 -164
  14. package/apps/cli/package.json +0 -21
  15. package/apps/cli/src/auth-file.ts +0 -18
  16. package/apps/cli/src/client.ts +0 -37
  17. package/apps/cli/src/columnize.ts +0 -32
  18. package/apps/cli/src/commands/agent.ts +0 -574
  19. package/apps/cli/src/commands/auth.ts +0 -110
  20. package/apps/cli/src/commands/backup.ts +0 -135
  21. package/apps/cli/src/commands/completion.ts +0 -155
  22. package/apps/cli/src/commands/config.ts +0 -95
  23. package/apps/cli/src/commands/doctor.ts +0 -131
  24. package/apps/cli/src/commands/group.ts +0 -132
  25. package/apps/cli/src/commands/inbox.ts +0 -82
  26. package/apps/cli/src/commands/login.ts +0 -73
  27. package/apps/cli/src/commands/memory.ts +0 -106
  28. package/apps/cli/src/commands/profile.ts +0 -259
  29. package/apps/cli/src/commands/provider.ts +0 -170
  30. package/apps/cli/src/commands/send.ts +0 -24
  31. package/apps/cli/src/commands/serve.ts +0 -89
  32. package/apps/cli/src/commands/skill.ts +0 -120
  33. package/apps/cli/src/commands/token.ts +0 -148
  34. package/apps/cli/src/commands/trigger.ts +0 -129
  35. package/apps/cli/src/commands/uninstall.ts +0 -156
  36. package/apps/cli/src/index.ts +0 -196
  37. package/apps/cli/src/paths.ts +0 -12
  38. package/apps/cli/test/agent.test.ts +0 -213
  39. package/apps/cli/test/backup.test.ts +0 -95
  40. package/apps/cli/test/chat.test.ts +0 -539
  41. package/apps/cli/test/columnize.test.ts +0 -29
  42. package/apps/cli/test/completion.test.ts +0 -45
  43. package/apps/cli/test/config-page.test.ts +0 -151
  44. package/apps/cli/test/group.test.ts +0 -74
  45. package/apps/cli/test/helpers.ts +0 -70
  46. package/apps/cli/test/inbox-autodeliver.test.ts +0 -143
  47. package/apps/cli/test/inbox.test.ts +0 -147
  48. package/apps/cli/test/memory.test.ts +0 -69
  49. package/apps/cli/test/profile.test.ts +0 -110
  50. package/apps/cli/test/send.test.ts +0 -30
  51. package/apps/cli/test/server-fixture.ts +0 -212
  52. package/apps/cli/test/session-head.test.ts +0 -45
  53. package/apps/cli/test/skill.test.ts +0 -128
  54. package/apps/cli/test/token.test.ts +0 -109
  55. package/apps/cli/test/trigger.test.ts +0 -245
  56. package/apps/cli/tsconfig.json +0 -4
  57. package/apps/daemon/package.json +0 -31
  58. package/apps/daemon/src/app.ts +0 -41
  59. package/apps/daemon/src/core/agent/archive.ts +0 -8
  60. package/apps/daemon/src/core/agent/delete.ts +0 -30
  61. package/apps/daemon/src/core/agent/resolve.ts +0 -31
  62. package/apps/daemon/src/core/agent/spawn.ts +0 -95
  63. package/apps/daemon/src/core/agent/unarchive.ts +0 -11
  64. package/apps/daemon/src/core/availableModels.ts +0 -58
  65. package/apps/daemon/src/core/db/client.ts +0 -113
  66. package/apps/daemon/src/core/db/migrate.ts +0 -41
  67. package/apps/daemon/src/core/db/migrations/0001_init.sql +0 -179
  68. package/apps/daemon/src/core/group/delete.ts +0 -21
  69. package/apps/daemon/src/core/group/register.ts +0 -68
  70. package/apps/daemon/src/core/index.ts +0 -71
  71. package/apps/daemon/src/core/paths.ts +0 -56
  72. package/apps/daemon/src/core/profile/create.ts +0 -78
  73. package/apps/daemon/src/core/profile/delete.ts +0 -26
  74. package/apps/daemon/src/core/profile/identity.ts +0 -70
  75. package/apps/daemon/src/core/profile/load.ts +0 -45
  76. package/apps/daemon/src/core/profile/seed.ts +0 -74
  77. package/apps/daemon/src/core/profile/templates.ts +0 -60
  78. package/apps/daemon/src/core/profile/update.ts +0 -57
  79. package/apps/daemon/src/core/profile/validate.ts +0 -9
  80. package/apps/daemon/src/core/repos/agents.ts +0 -202
  81. package/apps/daemon/src/core/repos/config.ts +0 -78
  82. package/apps/daemon/src/core/repos/groups.ts +0 -55
  83. package/apps/daemon/src/core/repos/messages.ts +0 -127
  84. package/apps/daemon/src/core/repos/profiles.ts +0 -83
  85. package/apps/daemon/src/core/repos/providerModels.ts +0 -58
  86. package/apps/daemon/src/core/repos/providerState.ts +0 -37
  87. package/apps/daemon/src/core/repos/secrets.ts +0 -145
  88. package/apps/daemon/src/core/repos/skillMeta.ts +0 -49
  89. package/apps/daemon/src/core/repos/triggers.ts +0 -101
  90. package/apps/daemon/src/core/repos/webTokens.ts +0 -87
  91. package/apps/daemon/src/core/secrets.ts +0 -65
  92. package/apps/daemon/src/core/services.ts +0 -264
  93. package/apps/daemon/src/core/skills/discover.ts +0 -28
  94. package/apps/daemon/src/core/skills/import.ts +0 -136
  95. package/apps/daemon/src/core/skills/parse.ts +0 -52
  96. package/apps/daemon/src/core/skills/resolve.ts +0 -50
  97. package/apps/daemon/src/index.ts +0 -45
  98. package/apps/daemon/src/lib/agent-cancel.ts +0 -48
  99. package/apps/daemon/src/lib/agent-id.ts +0 -13
  100. package/apps/daemon/src/lib/agent-turn.ts +0 -56
  101. package/apps/daemon/src/lib/api-key.ts +0 -56
  102. package/apps/daemon/src/lib/auth.ts +0 -41
  103. package/apps/daemon/src/lib/cron.ts +0 -93
  104. package/apps/daemon/src/lib/ctx.ts +0 -80
  105. package/apps/daemon/src/lib/messaging-host.ts +0 -34
  106. package/apps/daemon/src/lib/middleware-auth.ts +0 -52
  107. package/apps/daemon/src/lib/scheduler.ts +0 -294
  108. package/apps/daemon/src/routes/agents.ts +0 -772
  109. package/apps/daemon/src/routes/auth-login.ts +0 -193
  110. package/apps/daemon/src/routes/config.ts +0 -267
  111. package/apps/daemon/src/routes/groups.ts +0 -133
  112. package/apps/daemon/src/routes/messages.ts +0 -29
  113. package/apps/daemon/src/routes/misc.ts +0 -239
  114. package/apps/daemon/src/routes/profiles.ts +0 -197
  115. package/apps/daemon/src/routes/skills.ts +0 -123
  116. package/apps/daemon/src/routes/triggers.ts +0 -29
  117. package/apps/daemon/src/runtime/auth/openai-codex.ts +0 -121
  118. package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +0 -31
  119. package/apps/daemon/src/runtime/index.ts +0 -77
  120. package/apps/daemon/src/runtime/memory/files.ts +0 -103
  121. package/apps/daemon/src/runtime/memory/qmd.ts +0 -152
  122. package/apps/daemon/src/runtime/memory/types.ts +0 -16
  123. package/apps/daemon/src/runtime/pi/events.ts +0 -173
  124. package/apps/daemon/src/runtime/pi/session.ts +0 -536
  125. package/apps/daemon/src/runtime/pi/tools.ts +0 -85
  126. package/apps/daemon/src/runtime/providers/catalog.ts +0 -145
  127. package/apps/daemon/src/runtime/providers/pi-adapter.ts +0 -272
  128. package/apps/daemon/src/runtime/providers/registry.ts +0 -374
  129. package/apps/daemon/src/runtime/providers/retry.ts +0 -176
  130. package/apps/daemon/src/runtime/providers/types.ts +0 -33
  131. package/apps/daemon/src/runtime/session/prompt.ts +0 -83
  132. package/apps/daemon/src/runtime/tools/bootstrap.ts +0 -22
  133. package/apps/daemon/src/runtime/tools/home.ts +0 -114
  134. package/apps/daemon/src/runtime/tools/memory.ts +0 -81
  135. package/apps/daemon/src/runtime/tools/messaging.ts +0 -127
  136. package/apps/daemon/src/runtime/tools/registry.ts +0 -29
  137. package/apps/daemon/src/runtime/tools/types.ts +0 -13
  138. package/apps/daemon/src/runtime/tools/web-extract.ts +0 -110
  139. package/apps/daemon/src/runtime/tools/web-ssrf.ts +0 -245
  140. package/apps/daemon/src/runtime/tools/web.ts +0 -273
  141. package/apps/daemon/src/runtime/worker/entry.ts +0 -221
  142. package/apps/daemon/src/runtime/worker/ipc-protocol.ts +0 -75
  143. package/apps/daemon/src/runtime/worker/spawn.ts +0 -249
  144. package/apps/daemon/test/core/agents.test.ts +0 -319
  145. package/apps/daemon/test/core/available-models.test.ts +0 -63
  146. package/apps/daemon/test/core/config.test.ts +0 -99
  147. package/apps/daemon/test/core/groups.test.ts +0 -63
  148. package/apps/daemon/test/core/helpers.ts +0 -50
  149. package/apps/daemon/test/core/identity.test.ts +0 -85
  150. package/apps/daemon/test/core/migrations.test.ts +0 -83
  151. package/apps/daemon/test/core/profiles.test.ts +0 -182
  152. package/apps/daemon/test/core/provider-models.test.ts +0 -57
  153. package/apps/daemon/test/core/provider-state.test.ts +0 -36
  154. package/apps/daemon/test/core/skill-meta.test.ts +0 -45
  155. package/apps/daemon/test/core/skills.test.ts +0 -271
  156. package/apps/daemon/test/core/triggers.test.ts +0 -182
  157. package/apps/daemon/test/core/web-tokens.test.ts +0 -79
  158. package/apps/daemon/test/cron.test.ts +0 -90
  159. package/apps/daemon/test/runtime/heartbeat.test.ts +0 -34
  160. package/apps/daemon/test/runtime/memory-qmd.test.ts +0 -97
  161. package/apps/daemon/test/runtime/memory.test.ts +0 -78
  162. package/apps/daemon/test/runtime/messaging.test.ts +0 -213
  163. package/apps/daemon/test/runtime/mock-server.ts +0 -65
  164. package/apps/daemon/test/runtime/openai-codex-auth.test.ts +0 -116
  165. package/apps/daemon/test/runtime/providers.test.ts +0 -306
  166. package/apps/daemon/test/runtime/retry.test.ts +0 -191
  167. package/apps/daemon/test/runtime/session-head.test.ts +0 -90
  168. package/apps/daemon/test/runtime/tools-home.test.ts +0 -102
  169. package/apps/daemon/test/runtime/tools-web.test.ts +0 -206
  170. package/apps/daemon/tsconfig.json +0 -4
  171. package/apps/mobile/README.md +0 -60
  172. package/apps/mobile/app/_layout.tsx +0 -58
  173. package/apps/mobile/app/agents/[id]/chat.tsx +0 -486
  174. package/apps/mobile/app/agents/[id]/index.tsx +0 -166
  175. package/apps/mobile/app/agents/index.tsx +0 -212
  176. package/apps/mobile/app/index.tsx +0 -21
  177. package/apps/mobile/app/pair.tsx +0 -226
  178. package/apps/mobile/app/settings.tsx +0 -419
  179. package/apps/mobile/app.json +0 -49
  180. package/apps/mobile/assets/adaptive-icon.png +0 -0
  181. package/apps/mobile/assets/favicon.png +0 -0
  182. package/apps/mobile/assets/icon.png +0 -0
  183. package/apps/mobile/assets/splash-icon.png +0 -0
  184. package/apps/mobile/babel.config.js +0 -6
  185. package/apps/mobile/metro.config.js +0 -28
  186. package/apps/mobile/package.json +0 -44
  187. package/apps/mobile/src/auth.ts +0 -66
  188. package/apps/mobile/src/pair-url.ts +0 -48
  189. package/apps/mobile/src/theme-context.tsx +0 -88
  190. package/apps/mobile/src/theme.ts +0 -135
  191. package/apps/mobile/test/pair-url.test.ts +0 -46
  192. package/apps/mobile/tsconfig.json +0 -23
  193. package/apps/web/components.json +0 -25
  194. package/apps/web/package.json +0 -39
  195. package/apps/web/public/baziu.svg +0 -8
  196. package/apps/web/src/components/AgentTabs.tsx +0 -45
  197. package/apps/web/src/components/BaziuLogo.tsx +0 -21
  198. package/apps/web/src/components/ChatPane.tsx +0 -1033
  199. package/apps/web/src/components/ConfigTabs.tsx +0 -29
  200. package/apps/web/src/components/CopyButton.tsx +0 -68
  201. package/apps/web/src/components/CreateGroupDialog.tsx +0 -127
  202. package/apps/web/src/components/FieldRow.tsx +0 -94
  203. package/apps/web/src/components/Footer.tsx +0 -10
  204. package/apps/web/src/components/PawIcon.tsx +0 -15
  205. package/apps/web/src/components/Sidebar.tsx +0 -287
  206. package/apps/web/src/components/SpawnDialog.tsx +0 -129
  207. package/apps/web/src/components/ThemeToggle.tsx +0 -75
  208. package/apps/web/src/components/TopNav.tsx +0 -34
  209. package/apps/web/src/components/ui/button.tsx +0 -67
  210. package/apps/web/src/components/ui/card.tsx +0 -103
  211. package/apps/web/src/components/ui/checkbox.tsx +0 -31
  212. package/apps/web/src/components/ui/dialog.tsx +0 -168
  213. package/apps/web/src/components/ui/input.tsx +0 -19
  214. package/apps/web/src/components/ui/label.tsx +0 -22
  215. package/apps/web/src/components/ui/radio-group.tsx +0 -44
  216. package/apps/web/src/components/ui/select.tsx +0 -192
  217. package/apps/web/src/components/ui/separator.tsx +0 -26
  218. package/apps/web/src/components/ui/table.tsx +0 -116
  219. package/apps/web/src/components/ui/tabs.tsx +0 -88
  220. package/apps/web/src/components/ui/textarea.tsx +0 -18
  221. package/apps/web/src/lib/auth.ts +0 -50
  222. package/apps/web/src/lib/daemon-client.ts +0 -34
  223. package/apps/web/src/lib/md.ts +0 -45
  224. package/apps/web/src/lib/utils.ts +0 -6
  225. package/apps/web/src/lib/wire-constants.ts +0 -27
  226. package/apps/web/src/routeTree.gen.ts +0 -408
  227. package/apps/web/src/router.tsx +0 -20
  228. package/apps/web/src/routes/__root.tsx +0 -123
  229. package/apps/web/src/routes/agents/$id/inbox.tsx +0 -207
  230. package/apps/web/src/routes/agents/$id/index.tsx +0 -527
  231. package/apps/web/src/routes/agents/$id/triggers.tsx +0 -239
  232. package/apps/web/src/routes/agents/index.tsx +0 -265
  233. package/apps/web/src/routes/api/$.ts +0 -88
  234. package/apps/web/src/routes/config/index.tsx +0 -315
  235. package/apps/web/src/routes/config/services.tsx +0 -49
  236. package/apps/web/src/routes/config/tokens.tsx +0 -192
  237. package/apps/web/src/routes/groups/$id/index.tsx +0 -153
  238. package/apps/web/src/routes/groups/$id/memory.tsx +0 -321
  239. package/apps/web/src/routes/groups/index.tsx +0 -191
  240. package/apps/web/src/routes/index.tsx +0 -133
  241. package/apps/web/src/routes/login.tsx +0 -63
  242. package/apps/web/src/routes/profiles/$id.tsx +0 -549
  243. package/apps/web/src/routes/profiles/index.tsx +0 -458
  244. package/apps/web/src/routes/skills/index.tsx +0 -297
  245. package/apps/web/src/routes/welcome.tsx +0 -61
  246. package/apps/web/src/styles.css +0 -449
  247. package/apps/web/tsconfig.json +0 -25
  248. package/apps/web/vite.config.ts +0 -25
  249. package/biome.json +0 -23
  250. package/docs/agent-engine.md +0 -219
  251. package/docs/architecture.md +0 -627
  252. package/docs/backlog/README.md +0 -42
  253. package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +0 -125
  254. package/docs/openclaw-reference.md +0 -210
  255. package/packages/api-types/package.json +0 -11
  256. package/packages/api-types/src/entities.ts +0 -146
  257. package/packages/api-types/src/events.ts +0 -55
  258. package/packages/api-types/src/index.ts +0 -488
  259. package/packages/api-types/src/memory.ts +0 -15
  260. package/packages/client/package.json +0 -13
  261. package/packages/client/src/index.ts +0 -117
  262. package/pnpm-workspace.yaml +0 -3
  263. package/tsconfig.base.json +0 -23
  264. package/tsconfig.json +0 -11
  265. package/vitest.config.ts +0 -22
@@ -1,191 +0,0 @@
1
- import { describe, expect, test, vi } from 'vitest'
2
- import { isRetryableError, withRetry } from '../../src/runtime/providers/retry.ts'
3
- import type {
4
- Provider,
5
- ProviderRequest,
6
- ProviderResponse,
7
- } from '../../src/runtime/providers/types.ts'
8
-
9
- function okResponse(text = 'ok'): ProviderResponse {
10
- return { content: text, toolCalls: [], stopReason: 'stop' }
11
- }
12
-
13
- function scripted(outcomes: Array<() => Promise<ProviderResponse>>): Provider {
14
- let i = 0
15
- return {
16
- name: 'mock',
17
- async chat(_req: ProviderRequest) {
18
- const fn = outcomes[i++]
19
- if (!fn) throw new Error('scripted provider: out of outcomes')
20
- return await fn()
21
- },
22
- }
23
- }
24
-
25
- const baseReq: ProviderRequest = { model: 'm', messages: [] }
26
-
27
- describe('isRetryableError', () => {
28
- test('allows transient 5xx / rate-limit / socket markers', () => {
29
- expect(isRetryableError(new Error('Codex error: {"type":"server_error"}'))).toBe(true)
30
- expect(isRetryableError(new Error('rate_limit_exceeded'))).toBe(true)
31
- expect(isRetryableError(new Error('overloaded_error: too many requests'))).toBe(true)
32
- expect(isRetryableError(new Error('fetch failed'))).toBe(true)
33
- expect(isRetryableError(new Error('ECONNRESET'))).toBe(true)
34
- expect(isRetryableError(new Error('status 503 service_unavailable'))).toBe(true)
35
- })
36
-
37
- test('blocks auth / invalid-request / context-overflow / 4xx', () => {
38
- expect(isRetryableError(new Error('invalid_api_key'))).toBe(false)
39
- expect(isRetryableError(new Error('authentication failed'))).toBe(false)
40
- expect(isRetryableError(new Error('status 401 unauthorized'))).toBe(false)
41
- expect(isRetryableError(new Error('context_length_exceeded'))).toBe(false)
42
- expect(isRetryableError(new Error('model_not_found'))).toBe(false)
43
- expect(isRetryableError(new Error('insufficient_quota'))).toBe(false)
44
- })
45
-
46
- test('deny-list wins when both markers appear', () => {
47
- // A 429 that also mentions authentication: don't retry — the auth
48
- // failure is the load-bearing signal.
49
- expect(isRetryableError(new Error('status 429 after authentication failure'))).toBe(false)
50
- })
51
-
52
- test('unknown errors are not retried by default', () => {
53
- expect(isRetryableError(new Error('random client bug'))).toBe(false)
54
- expect(isRetryableError(new Error(''))).toBe(false)
55
- })
56
- })
57
-
58
- describe('withRetry', () => {
59
- test('passes through on first-try success', async () => {
60
- const p = withRetry(scripted([() => Promise.resolve(okResponse('hi'))]))
61
- const res = await p.chat(baseReq)
62
- expect(res.content).toBe('hi')
63
- })
64
-
65
- test('recovers after a transient server_error', async () => {
66
- const onRetry = vi.fn()
67
- const p = withRetry(
68
- scripted([
69
- () => Promise.reject(new Error('Codex error: server_error')),
70
- () => Promise.resolve(okResponse('recovered')),
71
- ]),
72
- { initialDelayMs: 1, onRetry },
73
- )
74
- const res = await p.chat(baseReq)
75
- expect(res.content).toBe('recovered')
76
- expect(onRetry).toHaveBeenCalledTimes(1)
77
- expect(onRetry.mock.calls[0]?.[0]?.attempt).toBe(1)
78
- })
79
-
80
- test('gives up after maxRetries + 1 attempts', async () => {
81
- let calls = 0
82
- const p = withRetry(
83
- {
84
- name: 'mock',
85
- async chat() {
86
- calls++
87
- throw new Error('rate_limit')
88
- },
89
- },
90
- { maxRetries: 2, initialDelayMs: 1 },
91
- )
92
- await expect(p.chat(baseReq)).rejects.toThrow(/rate_limit/)
93
- expect(calls).toBe(3)
94
- })
95
-
96
- test('does not retry non-retryable errors', async () => {
97
- let calls = 0
98
- const p = withRetry(
99
- {
100
- name: 'mock',
101
- async chat() {
102
- calls++
103
- throw new Error('invalid_api_key')
104
- },
105
- },
106
- { maxRetries: 5, initialDelayMs: 1 },
107
- )
108
- await expect(p.chat(baseReq)).rejects.toThrow(/invalid_api_key/)
109
- expect(calls).toBe(1)
110
- })
111
-
112
- test('does not retry after text has streamed', async () => {
113
- // First attempt streams a token, then errors — retry would duplicate the
114
- // token in the UI, so the wrapper must surface the error as-is.
115
- let calls = 0
116
- const p = withRetry(
117
- {
118
- name: 'mock',
119
- async chat(req) {
120
- calls++
121
- req.onDelta?.('partial...')
122
- throw new Error('server_error: stream broke mid-way')
123
- },
124
- },
125
- { maxRetries: 3, initialDelayMs: 1 },
126
- )
127
- const deltas: string[] = []
128
- await expect(p.chat({ ...baseReq, onDelta: (d) => deltas.push(d) })).rejects.toThrow(
129
- /server_error/,
130
- )
131
- expect(calls).toBe(1)
132
- expect(deltas).toEqual(['partial...'])
133
- })
134
-
135
- test('aborts immediately if the caller signal is already aborted', async () => {
136
- const ac = new AbortController()
137
- ac.abort()
138
- let calls = 0
139
- const p = withRetry(
140
- {
141
- name: 'mock',
142
- async chat() {
143
- calls++
144
- throw new Error('server_error')
145
- },
146
- },
147
- { initialDelayMs: 1 },
148
- )
149
- await expect(p.chat({ ...baseReq, signal: ac.signal })).rejects.toThrow()
150
- expect(calls).toBe(1) // one attempt, then abort check skips the retry
151
- })
152
-
153
- test('abort during backoff surfaces the original error', async () => {
154
- const ac = new AbortController()
155
- const p = withRetry(
156
- {
157
- name: 'mock',
158
- async chat() {
159
- throw new Error('server_error: upstream blew up')
160
- },
161
- },
162
- { maxRetries: 3, initialDelayMs: 100 },
163
- )
164
- // Abort 10ms into the 100ms backoff.
165
- setTimeout(() => ac.abort(), 10)
166
- await expect(p.chat({ ...baseReq, signal: ac.signal })).rejects.toThrow(
167
- /server_error: upstream blew up/,
168
- )
169
- })
170
-
171
- test('backoff grows exponentially up to maxDelayMs', async () => {
172
- const delays: number[] = []
173
- const p = withRetry(
174
- {
175
- name: 'mock',
176
- async chat() {
177
- throw new Error('server_error')
178
- },
179
- },
180
- {
181
- maxRetries: 3,
182
- initialDelayMs: 100,
183
- maxDelayMs: 250,
184
- onRetry: (info) => delays.push(info.delayMs),
185
- },
186
- )
187
- await expect(p.chat(baseReq)).rejects.toThrow()
188
- // 100, 200, min(400, 250) = 250
189
- expect(delays).toEqual([100, 200, 250])
190
- })
191
- })
@@ -1,90 +0,0 @@
1
- import { mkdirSync, mkdtempSync, rmSync, utimesSync, writeFileSync } from 'node:fs'
2
- import { tmpdir } from 'node:os'
3
- import { join } from 'node:path'
4
- import { afterEach, beforeEach, expect, test } from 'vitest'
5
- import { resolvePaths } from '../../src/core/index.ts'
6
- import { loadSessionHead } from '../../src/runtime/pi/session.ts'
7
-
8
- // Minimal ResolvedAgent shape loadSessionHead actually reads — we don't need
9
- // a real spawn + provider wiring for this unit test, only the agent.id (used
10
- // to resolve the session dir). loadSessionHead itself doesn't touch the
11
- // group, but the type still requires it.
12
- function fakeAgent(id: string, dir: string) {
13
- return {
14
- agent: { id, dir, name: id, status: 'idle' as const },
15
- group: { id: 'g', name: 'g', path: dir, userMd: '', createdAt: 0 },
16
- skills: [],
17
- } as unknown as Parameters<typeof loadSessionHead>[0]
18
- }
19
-
20
- let home: string
21
- beforeEach(() => {
22
- home = mkdtempSync(join(tmpdir(), 'bazilion-session-head-'))
23
- })
24
- afterEach(() => rmSync(home, { recursive: true, force: true }))
25
-
26
- test('returns {file:null, size:0} when no sessions dir exists (fresh agent)', () => {
27
- const paths = resolvePaths(home)
28
- mkdirSync(paths.agentsDir, { recursive: true })
29
- mkdirSync(join(paths.agentsDir, 'agent-a'), { recursive: true })
30
-
31
- const head = loadSessionHead(fakeAgent('agent-a', join(paths.agentsDir, 'agent-a')), paths)
32
- expect(head).toEqual({ file: null, size: 0 })
33
- })
34
-
35
- test('returns {file:null, size:0} when sessions dir is empty', () => {
36
- const paths = resolvePaths(home)
37
- mkdirSync(join(paths.agentsDir, 'agent-b', 'sessions'), { recursive: true })
38
-
39
- const head = loadSessionHead(fakeAgent('agent-b', join(paths.agentsDir, 'agent-b')), paths)
40
- expect(head).toEqual({ file: null, size: 0 })
41
- })
42
-
43
- test('returns the newest .jsonl basename + its byte size', () => {
44
- const paths = resolvePaths(home)
45
- const sessionDir = join(paths.agentsDir, 'agent-c', 'sessions')
46
- mkdirSync(sessionDir, { recursive: true })
47
-
48
- writeFileSync(join(sessionDir, 'old.jsonl'), 'old entry\n', 'utf8')
49
- const now = Date.now() / 1000
50
- // Backdate 'old' so 'newer' is unambiguously most-recent regardless of the
51
- // order mkdir stamps mtime.
52
- utimesSync(join(sessionDir, 'old.jsonl'), now - 60, now - 60)
53
-
54
- const newerContent = '{"type":"session"}\n{"type":"message"}\n'
55
- writeFileSync(join(sessionDir, 'newer.jsonl'), newerContent, 'utf8')
56
-
57
- const head = loadSessionHead(fakeAgent('agent-c', join(paths.agentsDir, 'agent-c')), paths)
58
- expect(head.file).toBe('newer.jsonl')
59
- expect(head.size).toBe(Buffer.byteLength(newerContent, 'utf8'))
60
- })
61
-
62
- test('ignores non-jsonl files in the sessions dir', () => {
63
- const paths = resolvePaths(home)
64
- const sessionDir = join(paths.agentsDir, 'agent-d', 'sessions')
65
- mkdirSync(sessionDir, { recursive: true })
66
-
67
- writeFileSync(join(sessionDir, 'README.md'), 'not a session\n', 'utf8')
68
- writeFileSync(join(sessionDir, '.DS_Store'), 'mac junk', 'utf8')
69
- writeFileSync(join(sessionDir, 'real.jsonl'), 'entry\n', 'utf8')
70
-
71
- const head = loadSessionHead(fakeAgent('agent-d', join(paths.agentsDir, 'agent-d')), paths)
72
- expect(head.file).toBe('real.jsonl')
73
- })
74
-
75
- test('size grows monotonically as entries are appended — matches poll signal', () => {
76
- const paths = resolvePaths(home)
77
- const sessionDir = join(paths.agentsDir, 'agent-e', 'sessions')
78
- mkdirSync(sessionDir, { recursive: true })
79
- const file = join(sessionDir, 'session.jsonl')
80
- writeFileSync(file, 'a\n', 'utf8')
81
-
82
- const head1 = loadSessionHead(fakeAgent('agent-e', join(paths.agentsDir, 'agent-e')), paths)
83
- // simulate pi appending another entry — ordinary append, no rename
84
- writeFileSync(file, 'a\nb\n', 'utf8')
85
- const head2 = loadSessionHead(fakeAgent('agent-e', join(paths.agentsDir, 'agent-e')), paths)
86
-
87
- expect(head1.file).toBe('session.jsonl')
88
- expect(head2.file).toBe('session.jsonl')
89
- expect(head2.size).toBeGreaterThan(head1.size)
90
- })
@@ -1,102 +0,0 @@
1
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
2
- import { tmpdir } from 'node:os'
3
- import { join } from 'node:path'
4
- import { afterEach, beforeEach, expect, test } from 'vitest'
5
- import { homeTools } from '../../src/runtime/tools/home.ts'
6
- import { createToolRegistry } from '../../src/runtime/tools/registry.ts'
7
-
8
- let agentDir: string
9
-
10
- beforeEach(() => {
11
- agentDir = mkdtempSync(join(tmpdir(), 'bazilion-home-'))
12
- writeFileSync(join(agentDir, 'IDENTITY.md'), '# IDENTITY\n\n- Name:\n', 'utf8')
13
- writeFileSync(join(agentDir, 'SOUL.md'), '# SOUL\n\nhelpful and honest.\n', 'utf8')
14
- writeFileSync(join(agentDir, 'BOOTSTRAP.md'), '# BOOTSTRAP\n\nfirst run.\n', 'utf8')
15
- })
16
- afterEach(() => rmSync(agentDir, { recursive: true, force: true }))
17
-
18
- test('home_read returns the file contents', async () => {
19
- const tools = createToolRegistry(homeTools(agentDir))
20
- const out = await tools.invoke('home_read', JSON.stringify({ file: 'IDENTITY.md' }))
21
- expect(out).toContain('# IDENTITY')
22
- })
23
-
24
- test('home_read rejects files outside the whitelist', async () => {
25
- const tools = createToolRegistry(homeTools(agentDir))
26
- await expect(
27
- tools.invoke('home_read', JSON.stringify({ file: '../etc/passwd' })),
28
- ).rejects.toThrow(/must be one of/)
29
- await expect(
30
- tools.invoke('home_read', JSON.stringify({ file: 'memory/notes.md' })),
31
- ).rejects.toThrow(/must be one of/)
32
- })
33
-
34
- test('home_read surfaces a helpful error if the file is missing', async () => {
35
- const tools = createToolRegistry(homeTools(agentDir))
36
- // TOOLS.md is whitelisted but not seeded in beforeEach — should raise a read error
37
- await expect(tools.invoke('home_read', JSON.stringify({ file: 'TOOLS.md' }))).rejects.toThrow(
38
- /could not read TOOLS\.md/,
39
- )
40
- })
41
-
42
- test('home_write replaces file contents and is readable back', async () => {
43
- const tools = createToolRegistry(homeTools(agentDir))
44
- const body = '# IDENTITY\n\n- Name: Molty\n- Emoji: 🦞\n'
45
- const w = await tools.invoke('home_write', JSON.stringify({ file: 'IDENTITY.md', content: body }))
46
- expect(w).toMatch(/wrote IDENTITY\.md/)
47
- expect(readFileSync(join(agentDir, 'IDENTITY.md'), 'utf8')).toBe(body)
48
- const r = await tools.invoke('home_read', JSON.stringify({ file: 'IDENTITY.md' }))
49
- expect(r).toBe(body)
50
- })
51
-
52
- test('home_write refuses to touch BOOTSTRAP.md (lifecycle belongs to bootstrap_done)', async () => {
53
- const tools = createToolRegistry(homeTools(agentDir))
54
- await expect(
55
- tools.invoke('home_write', JSON.stringify({ file: 'BOOTSTRAP.md', content: 'nope' })),
56
- ).rejects.toThrow(/must be one of/)
57
- // Seeded BOOTSTRAP.md content is intact
58
- expect(readFileSync(join(agentDir, 'BOOTSTRAP.md'), 'utf8')).toContain('first run')
59
- })
60
-
61
- test('home_write rejects files outside the whitelist', async () => {
62
- const tools = createToolRegistry(homeTools(agentDir))
63
- await expect(
64
- tools.invoke(
65
- 'home_write',
66
- JSON.stringify({ file: '../escape.md', content: 'should not leak' }),
67
- ),
68
- ).rejects.toThrow(/must be one of/)
69
- })
70
-
71
- test('home_list reports seeded files with byte sizes', async () => {
72
- const tools = createToolRegistry(homeTools(agentDir))
73
- const out = await tools.invoke('home_list', '{}')
74
- expect(out).toMatch(/IDENTITY\.md \(\d+b\)/)
75
- expect(out).toMatch(/SOUL\.md \(\d+b\)/)
76
- expect(out).toMatch(/BOOTSTRAP\.md \(\d+b\)/)
77
- // files we never created should not appear
78
- expect(out).not.toMatch(/AGENTS\.md/)
79
- })
80
-
81
- test('home tools are scoped to the provided agentDir — sibling agents do not collide', async () => {
82
- const other = mkdtempSync(join(tmpdir(), 'bazilion-home-other-'))
83
- try {
84
- writeFileSync(join(other, 'IDENTITY.md'), 'sibling original\n', 'utf8')
85
-
86
- const a = createToolRegistry(homeTools(agentDir))
87
- const b = createToolRegistry(homeTools(other))
88
-
89
- await a.invoke(
90
- 'home_write',
91
- JSON.stringify({ file: 'IDENTITY.md', content: 'agent-a writes its own\n' }),
92
- )
93
-
94
- expect(readFileSync(join(agentDir, 'IDENTITY.md'), 'utf8')).toBe('agent-a writes its own\n')
95
- expect(readFileSync(join(other, 'IDENTITY.md'), 'utf8')).toBe('sibling original\n')
96
-
97
- const bRead = await b.invoke('home_read', JSON.stringify({ file: 'IDENTITY.md' }))
98
- expect(bRead).toBe('sibling original\n')
99
- } finally {
100
- rmSync(other, { recursive: true, force: true })
101
- }
102
- })
@@ -1,206 +0,0 @@
1
- import { afterEach, expect, test } from 'vitest'
2
- import { createToolRegistry } from '../../src/runtime/tools/registry.ts'
3
- import { webTools } from '../../src/runtime/tools/web.ts'
4
- import { type MockServer, startMockServer } from './mock-server.ts'
5
-
6
- let server: MockServer | null = null
7
- afterEach(async () => {
8
- await server?.stop()
9
- server = null
10
- })
11
-
12
- async function startMock(handler: (req: Request) => Response | Promise<Response>): Promise<string> {
13
- server = await startMockServer(handler)
14
- return server.url
15
- }
16
-
17
- test('web_fetch fetches a plain text URL', async () => {
18
- const base = await startMock(
19
- () => new Response('hello plain', { headers: { 'content-type': 'text/plain' } }),
20
- )
21
- const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
22
- const result = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/doc` }))
23
- expect(result).toContain('hello plain')
24
- })
25
-
26
- test('web_fetch extracts readable content from HTML (strips scripts/nav/footer)', async () => {
27
- const html = `<html><head><title>Test Doc</title><script>evil()</script><style>.x{}</style></head>
28
- <body>
29
- <nav>skip nav</nav>
30
- <article>
31
- <h1>The Real Article</h1>
32
- <p>This is the primary body of the article and it is long enough to give Readability something to score. The fox jumps over the lazy dog. Repeat repeat repeat.</p>
33
- <p>A second paragraph of genuine content to satisfy any content-length thresholds.</p>
34
- </article>
35
- <footer>skip footer</footer>
36
- </body></html>`
37
- const base = await startMock(
38
- () => new Response(html, { headers: { 'content-type': 'text/html' } }),
39
- )
40
- const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
41
- const result = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/page` }))
42
- expect(result).toContain('primary body of the article')
43
- expect(result).not.toContain('evil()')
44
- expect(result).not.toContain('<script')
45
- expect(result).not.toContain('skip nav')
46
- expect(result).not.toContain('skip footer')
47
- })
48
-
49
- test('web_fetch includes title as markdown heading', async () => {
50
- const html = `<html><head><title>My Awesome Post</title></head>
51
- <body><article><h1>My Awesome Post</h1>
52
- <p>Body text that is long enough to trigger Readability scoring here, filler filler filler filler.</p>
53
- <p>Another solid paragraph so Readability has plenty to work with.</p>
54
- </article></body></html>`
55
- const base = await startMock(
56
- () => new Response(html, { headers: { 'content-type': 'text/html' } }),
57
- )
58
- const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
59
- const result = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/post` }))
60
- expect(result).toMatch(/^#\s+My Awesome Post/)
61
- })
62
-
63
- test('web_fetch pretty-prints JSON responses', async () => {
64
- const base = await startMock(
65
- () => new Response('{"a":1,"b":[2,3]}', { headers: { 'content-type': 'application/json' } }),
66
- )
67
- const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
68
- const result = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/data.json` }))
69
- expect(result).toContain('"a": 1')
70
- expect(result).toContain('"b": [')
71
- })
72
-
73
- test('web_fetch truncates at max_length', async () => {
74
- const base = await startMock(
75
- () => new Response('a'.repeat(500), { headers: { 'content-type': 'text/plain' } }),
76
- )
77
- const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
78
- const result = await tools.invoke(
79
- 'web_fetch',
80
- JSON.stringify({ url: `${base}/big`, max_length: 100 }),
81
- )
82
- expect(result).toContain('[truncated at 100 chars]')
83
- expect(result.length).toBeLessThan(200)
84
- })
85
-
86
- test('web_fetch throws on non-2xx', async () => {
87
- const base = await startMock(() => new Response('nope', { status: 404 }))
88
- const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
89
- await expect(
90
- tools.invoke('web_fetch', JSON.stringify({ url: `${base}/missing` })),
91
- ).rejects.toThrow(/404/)
92
- })
93
-
94
- test('web_fetch caches results per (url, extract_mode)', async () => {
95
- let hits = 0
96
- const base = await startMock(() => {
97
- hits += 1
98
- return new Response(
99
- '<html><body><article><p>Cached content here, enough words to pass threshold filler filler.</p></article></body></html>',
100
- {
101
- headers: { 'content-type': 'text/html' },
102
- },
103
- )
104
- })
105
- const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
106
- const first = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/cached` }))
107
- const second = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/cached` }))
108
- expect(first).toBe(second)
109
- expect(hits).toBe(1)
110
- })
111
-
112
- test('web_fetch blocks private IP literals by default', async () => {
113
- const tools = createToolRegistry(webTools())
114
- await expect(
115
- tools.invoke('web_fetch', JSON.stringify({ url: 'http://127.0.0.1/' })),
116
- ).rejects.toThrow(/private/i)
117
- })
118
-
119
- test('web_fetch blocks localhost by default', async () => {
120
- const tools = createToolRegistry(webTools())
121
- await expect(
122
- tools.invoke('web_fetch', JSON.stringify({ url: 'http://localhost/' })),
123
- ).rejects.toThrow(/Blocked hostname/i)
124
- })
125
-
126
- test('web_fetch blocks non-http(s) schemes', async () => {
127
- const tools = createToolRegistry(webTools({ allowPrivate: true }))
128
- await expect(
129
- tools.invoke('web_fetch', JSON.stringify({ url: 'file:///etc/passwd' })),
130
- ).rejects.toThrow(/http or https/i)
131
- })
132
-
133
- test('web_search tools are registered', () => {
134
- const tools = createToolRegistry(webTools())
135
- expect(tools.has('web_search')).toBe(true)
136
- expect(tools.has('web_fetch')).toBe(true)
137
- })
138
-
139
- test('web_search rejects empty query', async () => {
140
- const tools = createToolRegistry(webTools())
141
- await expect(tools.invoke('web_search', JSON.stringify({ query: '' }))).rejects.toThrow(
142
- /query is required/,
143
- )
144
- })
145
-
146
- test('web_search errors when no backend is configured', async () => {
147
- const tools = createToolRegistry(webTools({ env: {} }))
148
- await expect(tools.invoke('web_search', JSON.stringify({ query: 'test' }))).rejects.toThrow(
149
- /no search backend configured/,
150
- )
151
- })
152
-
153
- test('web_search uses Brave when BRAVE_API_KEY is set', async () => {
154
- let capturedHeaders = null as Headers | null
155
- const base = await startMock((req) => {
156
- capturedHeaders = req.headers
157
- return new Response(
158
- JSON.stringify({
159
- web: {
160
- results: [
161
- {
162
- title: 'Weather Krakow',
163
- url: 'https://weather.example.com/krakow',
164
- description: 'Sunny, 22°C',
165
- },
166
- ],
167
- },
168
- }),
169
- { headers: { 'content-type': 'application/json' } },
170
- )
171
- })
172
- const tools = createToolRegistry(
173
- webTools({
174
- fetchImpl: async (input, init) => {
175
- const url =
176
- typeof input === 'string' ? input : input instanceof URL ? input.href : input.url
177
- const redirected = url.replace('https://api.search.brave.com', base)
178
- return fetch(redirected, init)
179
- },
180
- env: { BRAVE_API_KEY: 'test-key' },
181
- }),
182
- )
183
- const result = await tools.invoke('web_search', JSON.stringify({ query: 'weather krakow' }))
184
- expect(result).toContain('Weather Krakow')
185
- expect(result).toContain('Sunny')
186
- expect(result).toContain('weather.example.com')
187
- expect(capturedHeaders?.get('x-subscription-token')).toBe('test-key')
188
- })
189
-
190
- test('web_search uses SearXNG when SEARXNG_URL is set', async () => {
191
- const base = await startMock(
192
- () =>
193
- new Response(
194
- JSON.stringify({
195
- results: [
196
- { title: 'News', url: 'https://news.example.com', content: 'Latest headlines' },
197
- ],
198
- }),
199
- { headers: { 'content-type': 'application/json' } },
200
- ),
201
- )
202
- const tools = createToolRegistry(webTools({ env: { SEARXNG_URL: base } }))
203
- const result = await tools.invoke('web_search', JSON.stringify({ query: 'news' }))
204
- expect(result).toContain('News')
205
- expect(result).toContain('Latest headlines')
206
- })
@@ -1,4 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "include": ["src/**/*"]
4
- }
@@ -1,60 +0,0 @@
1
- # @bazilion/mobile
2
-
3
- Expo (SDK 54) + Expo Router 6 + React 19 + RN 0.81 + new architecture. Pairs to a running `bazilion` daemon over LAN/Tailscale/VPN via a QR code minted on the server.
4
-
5
- ## First run
6
-
7
- 1. **On the server**: expose the daemon on your LAN.
8
- ```sh
9
- bazilion serve --host 0.0.0.0
10
- ```
11
- The daemon binds `127.0.0.1:4321` by default and prints a loud warning when
12
- you bind it beyond loopback — TLS is your responsibility (Tailscale handles
13
- it for personal networks; reverse-proxy with TLS for anything else).
14
- 2. **On the server**: mint a pairing token.
15
- ```sh
16
- bazilion token create phone --qr
17
- ```
18
- A QR encoding `bazilion://pair?server=<url>&token=<t>` prints in the terminal.
19
- 3. **On the phone**: install [Expo Go](https://expo.dev/go) and make sure it's on the same network as the server (or Tailscale, etc.).
20
- 4. **On your dev machine**:
21
- ```sh
22
- pnpm --filter @bazilion/mobile start
23
- ```
24
- Scan the QR the Expo CLI prints with Expo Go → the app loads → grant camera access → point the camera at the **pairing** QR from step 2.
25
- 5. The app verifies the token against `/api/health`, saves `server` + `token` into `expo-secure-store`, and lands on the agents list.
26
-
27
- ## Manual pairing
28
-
29
- If the camera flow doesn't work (remote testing, wrong Expo Go version, etc.), tap "Paste URL instead" and paste the `bazilion://pair?…` URL the CLI printed. Same verification path.
30
-
31
- ## Commands
32
-
33
- - `pnpm --filter @bazilion/mobile start` — Expo dev server (reads the Metro/Babel config).
34
- - `pnpm --filter @bazilion/mobile typecheck` — TypeScript check over the mobile tree only.
35
- - Root `pnpm test` picks up `apps/mobile/test/**/*.test.ts` — currently the `pair-url` parser suite.
36
-
37
- ## Layout
38
-
39
- ```
40
- app/
41
- _layout.tsx root stack + StatusBar
42
- index.tsx loads SecureStore → redirects /pair or /agents
43
- pair.tsx camera QR scan + manual-paste fallback + verify + save
44
- settings.tsx server URL + unpair
45
- agents/
46
- index.tsx FlatList of agents (pull-to-refresh, unpair header, 401 → /pair)
47
- [id]/
48
- index.tsx detail: name, status, model, profile, group, skills
49
- chat.tsx NDJSON streaming chat screen
50
- src/
51
- auth.ts SecureStore wrapper + verifyCredentials + clientFor()
52
- pair-url.ts pure TS URL parser (vitest-tested)
53
- theme.ts Baziu palette + spacing tokens
54
- test/
55
- pair-url.test.ts
56
- ```
57
-
58
- ## Status
59
-
60
- Pairing, agents list, chat (NDJSON streaming), and settings ship end-to-end. Inbox + triggers screens are the next mobile work — paused for now while focus is on the web app.