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,56 @@
1
+ import type { ChatFrame } from '@bazilion/api-types'
2
+ import { mergeSecretsIntoEnv, providerStateRepo, resolveAgent } from '../core/index.ts'
3
+ import { spawnWorkerTurn } from '../runtime/index.ts'
4
+ import { registerAgent, unregisterAgent } from './agent-cancel.ts'
5
+ import { resolveAgentApiKey } from './api-key.ts'
6
+ import { getCtx } from './ctx.ts'
7
+ import { createDbMessagingHost } from './messaging-host.ts'
8
+
9
+ interface RunAgentTurnOpts {
10
+ /** If omitted, a fresh AbortController is created internally. */
11
+ controller?: AbortController
12
+ }
13
+
14
+ /**
15
+ * Runs one full agent turn in an isolated subprocess, streaming `ChatFrame`s
16
+ * in NDJSON-ready order. The heavy lifting (provider calls, tool execution,
17
+ * pi session journal append) happens inside the child; this function is a
18
+ * thin relay that:
19
+ * - resolves the agent + provider gate + secrets envelope here in the
20
+ * daemon (the worker no longer holds a SQLite handle of its own),
21
+ * - spawns the worker with an IPC channel and a `MessagingHost` that
22
+ * services the inter-agent messaging tools the worker calls back into,
23
+ * - forwards stdout frames to the caller and wires cancellation through
24
+ * the agent-cancel registry.
25
+ */
26
+ export async function* runAgentTurn(
27
+ agentId: string,
28
+ message: string,
29
+ opts: RunAgentTurnOpts = {},
30
+ ): AsyncGenerator<ChatFrame> {
31
+ const { db, paths, authToken } = getCtx()
32
+ const agent = resolveAgent(db, paths, agentId)
33
+ const enabledProviders = Array.from(providerStateRepo.listEnabled(db))
34
+ const env = mergeSecretsIntoEnv(db, authToken)
35
+ const messagingHost = createDbMessagingHost(db)
36
+ // Pre-fetch the API key for OAuth providers (`openai-codex`) before the
37
+ // worker spawns — the worker has no DB handle, so it can't reach the
38
+ // secrets table itself. For env-key providers this is a no-op (`{}`).
39
+ // Refresher is intentionally skipped: the worker has no IPC channel for
40
+ // OAuth refresh today, and the initial token comfortably outlives a
41
+ // single turn for ChatGPT-backed sessions.
42
+ const { apiKey } = await resolveAgentApiKey(db, authToken, agent)
43
+
44
+ const controller = opts.controller ?? new AbortController()
45
+ registerAgent(agentId, controller)
46
+ try {
47
+ for await (const frame of spawnWorkerTurn(
48
+ { agent, message, enabledProviders, apiKey },
49
+ { signal: controller.signal, env, messagingHost },
50
+ )) {
51
+ yield frame
52
+ }
53
+ } finally {
54
+ unregisterAgent(agentId)
55
+ }
56
+ }
@@ -0,0 +1,56 @@
1
+ // Resolves the API key (and refresher) for an agent's provider. Centralizes
2
+ // the OAuth special case for `openai-codex` — its access token lives in the
3
+ // daemon-owned `secrets` table, not in env vars, so callers can't pluck it
4
+ // from the merged env the way they can for plain API-key providers.
5
+
6
+ import type { ResolvedAgent } from '@bazilion/api-types'
7
+ import type { BazilionDb } from '../core/index.ts'
8
+ import { hasOpenAICodexCredentials, loadOpenAICodexAccessToken } from '../runtime/index.ts'
9
+
10
+ export interface AgentApiKey {
11
+ /** Initial access token / API key. Undefined when the env layer carries it. */
12
+ apiKey?: string
13
+ /**
14
+ * Optional refresher pi calls during long tool-execution loops to swap an
15
+ * expired JWT for a fresh one. Only set for OAuth providers — daemon-side
16
+ * sessions wire this; worker turns currently rely on the initial token
17
+ * carrying the whole turn (subsecond-to-minutes) since they have no DB
18
+ * handle to refresh against.
19
+ */
20
+ refreshApiKey?: (providerName: string) => Promise<string>
21
+ }
22
+
23
+ /**
24
+ * Pre-fetch the API key for `agent`'s provider. Returns `{}` when the
25
+ * provider is env-key-based (the merged env passed to the session already
26
+ * carries the value). For `openai-codex`, throws a friendly error when the
27
+ * user hasn't connected their ChatGPT account yet — that surfaces in the
28
+ * chat UI as a clear "go to /config" message rather than pi's generic
29
+ * "no API key" complaint.
30
+ */
31
+ export async function resolveAgentApiKey(
32
+ db: BazilionDb,
33
+ authToken: string,
34
+ agent: ResolvedAgent,
35
+ opts: { withRefresher?: boolean } = {},
36
+ ): Promise<AgentApiKey> {
37
+ const providerName = agent.model.split(':', 1)[0] ?? ''
38
+ if (providerName !== 'openai-codex') return {}
39
+
40
+ if (!hasOpenAICodexCredentials(db, authToken)) {
41
+ throw new Error(
42
+ 'openai-codex is not connected — run `bazilion auth openai login` or click Connect on /config',
43
+ )
44
+ }
45
+ const apiKey = await loadOpenAICodexAccessToken(db, authToken)
46
+ if (!opts.withRefresher) return { apiKey }
47
+ return {
48
+ apiKey,
49
+ refreshApiKey: async (requestedProvider) => {
50
+ if (requestedProvider !== 'openai-codex') {
51
+ throw new Error(`unexpected refresh request for ${requestedProvider}`)
52
+ }
53
+ return loadOpenAICodexAccessToken(db, authToken)
54
+ },
55
+ }
56
+ }
@@ -0,0 +1,41 @@
1
+ // Token verification primitives — framework-agnostic. The Hono auth middleware
2
+ // (lib/middleware-auth.ts) wraps these; native clients (CLI, mobile) pass the
3
+ // same token via `Authorization: Bearer …`, and browsers send the httpOnly
4
+ // `bz_token` cookie minted by `POST /api/login`.
5
+ //
6
+ // All tokens — including the bootstrap one minted by the daemon's first-run
7
+ // bootstrap — live as hashed rows in the `web_tokens` table. The bootstrap
8
+ // row's plaintext is exposed in `~/.bazilion/auth.json` so the daemon (PBKDF2
9
+ // seed for the secrets table) and the CLI (loopback bearer) can use it.
10
+ // Validation goes through `findActiveByToken`, no special-case loopback path.
11
+
12
+ import { webTokenRepo } from '../core/index.ts'
13
+ import { getCtx } from './ctx.ts'
14
+
15
+ /**
16
+ * Is this string a currently-valid token? Accepts any active (non-revoked)
17
+ * row in `web_tokens` — including the bootstrap row written by the daemon's
18
+ * first-run bootstrap. Bumps `last_used_at` on a match so operators can see
19
+ * idle vs active tokens in `token list`.
20
+ */
21
+ export function isValidToken(token: string): boolean {
22
+ try {
23
+ const { db } = getCtx()
24
+ const match = webTokenRepo.findActiveByToken(db, token)
25
+ if (match) {
26
+ webTokenRepo.markUsed(db, match.id)
27
+ return true
28
+ }
29
+ } catch {
30
+ // db unavailable — treat as unauthenticated
31
+ }
32
+ return false
33
+ }
34
+
35
+ /** Pull the bearer token out of an `Authorization: Bearer …` header. */
36
+ export function extractBearer(authHeader: string | null | undefined): string | null {
37
+ if (!authHeader) return null
38
+ // RFC 6750 auth scheme is case-insensitive.
39
+ const match = /^\s*Bearer\s+(.+?)\s*$/i.exec(authHeader)
40
+ return match?.[1] ?? null
41
+ }
@@ -0,0 +1,93 @@
1
+ // Minimal 5-field cron matcher: "minute hour day-of-month month day-of-week".
2
+ // Supports *, */N, N, N-M, and comma-separated lists thereof per field.
3
+ // Day-of-week: 0 or 7 = Sunday, 1 = Monday ... 6 = Saturday.
4
+ //
5
+ // Matching semantics match the "standard" cron (non-Vixie) rule: if BOTH
6
+ // day-of-month and day-of-week are restricted (i.e. not `*`), the match is an
7
+ // OR — the trigger fires when either matches. Most common expressions are
8
+ // `*/5 * * * *` or `0 9 * * *` where only one of the two is restricted, so
9
+ // the subtlety rarely matters.
10
+
11
+ function parseField(raw: string, min: number, max: number): Set<number> {
12
+ const values = new Set<number>()
13
+ for (const part of raw.split(',')) {
14
+ const [rangePart, stepPart] = part.split('/')
15
+ const step = stepPart === undefined ? 1 : Number(stepPart)
16
+ if (!Number.isInteger(step) || step < 1) {
17
+ throw new Error(`invalid step "${stepPart}"`)
18
+ }
19
+ let lo: number
20
+ let hi: number
21
+ if (rangePart === '*' || rangePart === undefined) {
22
+ lo = min
23
+ hi = max
24
+ } else if (rangePart.includes('-')) {
25
+ const [a, b] = rangePart.split('-').map((n) => Number(n))
26
+ if (!Number.isInteger(a) || !Number.isInteger(b)) {
27
+ throw new Error(`invalid range "${rangePart}"`)
28
+ }
29
+ lo = a as number
30
+ hi = b as number
31
+ } else {
32
+ const n = Number(rangePart)
33
+ if (!Number.isInteger(n)) {
34
+ throw new Error(`invalid value "${rangePart}"`)
35
+ }
36
+ lo = n
37
+ hi = n
38
+ }
39
+ if (lo < min || hi > max || lo > hi) {
40
+ throw new Error(`value out of range ${min}-${max}: "${part}"`)
41
+ }
42
+ for (let v = lo; v <= hi; v += step) values.add(v)
43
+ }
44
+ return values
45
+ }
46
+
47
+ export interface ParsedCron {
48
+ minute: Set<number>
49
+ hour: Set<number>
50
+ dom: Set<number>
51
+ month: Set<number>
52
+ dow: Set<number>
53
+ domRestricted: boolean
54
+ dowRestricted: boolean
55
+ }
56
+
57
+ export function parseCron(expr: string): ParsedCron {
58
+ const parts = expr.trim().split(/\s+/)
59
+ if (parts.length !== 5) {
60
+ throw new Error(`expected 5 fields, got ${parts.length}: "${expr}"`)
61
+ }
62
+ const [m, h, dom, mon, dow] = parts as [string, string, string, string, string]
63
+ const parsed: ParsedCron = {
64
+ minute: parseField(m, 0, 59),
65
+ hour: parseField(h, 0, 23),
66
+ dom: parseField(dom, 1, 31),
67
+ month: parseField(mon, 1, 12),
68
+ // accept 7 as Sunday alias → normalise to 0
69
+ dow: new Set([...parseField(dow.replace(/7/g, '0'), 0, 6)]),
70
+ domRestricted: dom !== '*',
71
+ dowRestricted: dow !== '*',
72
+ }
73
+ return parsed
74
+ }
75
+
76
+ export function matchesCron(parsed: ParsedCron, date: Date): boolean {
77
+ if (!parsed.minute.has(date.getMinutes())) return false
78
+ if (!parsed.hour.has(date.getHours())) return false
79
+ if (!parsed.month.has(date.getMonth() + 1)) return false
80
+ const domMatch = parsed.dom.has(date.getDate())
81
+ const dowMatch = parsed.dow.has(date.getDay())
82
+ if (parsed.domRestricted && parsed.dowRestricted) {
83
+ return domMatch || dowMatch
84
+ }
85
+ if (parsed.domRestricted) return domMatch
86
+ if (parsed.dowRestricted) return dowMatch
87
+ return true
88
+ }
89
+
90
+ /** Validates an expression — throws on syntax errors. Used by API write paths. */
91
+ export function validateCron(expr: string): void {
92
+ parseCron(expr)
93
+ }
@@ -0,0 +1,80 @@
1
+ import { chmodSync, existsSync, mkdirSync, writeFileSync } from 'node:fs'
2
+ import {
3
+ type BazilionDb,
4
+ openDb,
5
+ type Paths,
6
+ readAuthFile,
7
+ resolvePaths,
8
+ runMigrations,
9
+ webTokenRepo,
10
+ } from '../core/index.ts'
11
+ import { startScheduler } from './scheduler.ts'
12
+
13
+ let _db: BazilionDb | null = null
14
+ let _paths: Paths | null = null
15
+ let _authToken: string | null = null
16
+ let _schedulerStarted = false
17
+
18
+ export interface DaemonCtx {
19
+ db: BazilionDb
20
+ paths: Paths
21
+ /**
22
+ * Plaintext bootstrap token from `auth.json`. Used to derive the encryption
23
+ * key for the `secrets` table — `mergeSecretsIntoEnv(db, ctx.authToken)`.
24
+ * Cached for the process lifetime; if the user rotates the token, the
25
+ * daemon must restart to pick it up.
26
+ */
27
+ authToken: string
28
+ }
29
+
30
+ /**
31
+ * One-shot first-run bootstrap. Idempotent: every step skips itself when its
32
+ * artifact already exists. Mints the bootstrap web_tokens row + writes the
33
+ * plaintext into auth.json the first time we see no auth file.
34
+ */
35
+ function bootstrap(paths: Paths): { db: BazilionDb; authToken: string } {
36
+ for (const d of [
37
+ paths.home,
38
+ paths.profilesDir,
39
+ paths.agentsDir,
40
+ paths.skillsDir,
41
+ paths.groupsDir,
42
+ paths.logsDir,
43
+ ]) {
44
+ mkdirSync(d, { recursive: true })
45
+ }
46
+
47
+ const db = openDb(paths.db)
48
+ runMigrations(db)
49
+
50
+ if (!existsSync(paths.authFile)) {
51
+ const created = webTokenRepo.create(db, 'bootstrap')
52
+ writeFileSync(paths.authFile, `${JSON.stringify({ token: created.token }, null, 2)}\n`, {
53
+ mode: 0o600,
54
+ })
55
+ try {
56
+ chmodSync(paths.authFile, 0o600)
57
+ } catch {
58
+ // Windows: chmod is a no-op
59
+ }
60
+ console.log(`bazilion auto-bootstrapped at ${paths.home}`)
61
+ console.log(`bootstrap token written to ${paths.authFile}`)
62
+ return { db, authToken: created.token }
63
+ }
64
+
65
+ return { db, authToken: readAuthFile(paths.authFile).token }
66
+ }
67
+
68
+ export function getCtx(): DaemonCtx {
69
+ if (!_paths) _paths = resolvePaths()
70
+ if (!_db || _authToken === null) {
71
+ const result = bootstrap(_paths)
72
+ _db = result.db
73
+ _authToken = result.authToken
74
+ }
75
+ if (!_schedulerStarted && process.env.BAZILION_SCHEDULER !== 'off') {
76
+ _schedulerStarted = true
77
+ startScheduler()
78
+ }
79
+ return { db: _db, paths: _paths, authToken: _authToken }
80
+ }
@@ -0,0 +1,34 @@
1
+ // Daemon-side `MessagingHost` implementation backed by the local SQLite handle.
2
+ //
3
+ // Two consumers:
4
+ // 1. In-process callers (compact / context / truncate endpoints) that build
5
+ // a Bazilion session for inspection and want messaging tools enumerated
6
+ // with the same shape the chat path sees.
7
+ // 2. The IPC handler that services messaging requests issued by worker
8
+ // subprocesses. Workers no longer hold a SQLite handle of their own —
9
+ // they call `process.send({type: 'rpc', ...})` and the parent dispatches
10
+ // through this host.
11
+
12
+ import { agentRepo, type BazilionDb, messageRepo } from '../core/index.ts'
13
+ import type { MessagingHost } from '../runtime/index.ts'
14
+
15
+ export function createDbMessagingHost(db: BazilionDb): MessagingHost {
16
+ return {
17
+ agentExists(agentId) {
18
+ return agentRepo.get(db, agentId) !== null
19
+ },
20
+ sendMessage(input) {
21
+ const m = messageRepo.send(db, input)
22
+ return { messageId: m.id }
23
+ },
24
+ listInbox(agentId, opts) {
25
+ return messageRepo.listInbox(db, agentId, opts)
26
+ },
27
+ markRead(messageId) {
28
+ messageRepo.markRead(db, messageId)
29
+ },
30
+ findReplies(agentId, replyTo) {
31
+ return messageRepo.findReplies(db, agentId, replyTo)
32
+ },
33
+ }
34
+ }
@@ -0,0 +1,52 @@
1
+ // Hono auth + first-run gate middleware.
2
+ //
3
+ // Mirrors what apps/web/src/middleware.ts used to do for `/api/*` paths,
4
+ // but framework-typed for Hono. Web SSR auth (login redirect, welcome
5
+ // redirect) stays in the Astro app's own middleware — the daemon only
6
+ // returns JSON status codes; the web app translates those to redirects.
7
+
8
+ import type { Context, Next } from 'hono'
9
+ import { getCookie } from 'hono/cookie'
10
+ import { isSetupComplete } from '../core/index.ts'
11
+ import { extractBearer, isValidToken } from './auth.ts'
12
+ import { getCtx } from './ctx.ts'
13
+
14
+ /** Reachable without a token. The login route mints them; health is a probe. */
15
+ const PUBLIC_PATHS = new Set(['/api/login', '/api/health'])
16
+
17
+ /**
18
+ * Once authenticated, these paths still pass through the first-run gate so
19
+ * users can finish their initial setup. Everything else 409s until the user
20
+ * has at least one enabled provider with ≥1 curated model.
21
+ */
22
+ const SETUP_OPEN_PREFIXES = ['/api/config', '/api/auth', '/api/health']
23
+
24
+ function isSetupOpen(path: string): boolean {
25
+ for (const prefix of SETUP_OPEN_PREFIXES) {
26
+ if (path === prefix || path.startsWith(`${prefix}/`)) return true
27
+ }
28
+ return false
29
+ }
30
+
31
+ // biome-ignore lint/suspicious/noConfusingVoidType: hono's Next() returns Promise<void>; the union is the framework's middleware contract.
32
+ export async function authMiddleware(c: Context, next: Next): Promise<Response | void> {
33
+ const path = c.req.path
34
+ if (PUBLIC_PATHS.has(path)) {
35
+ await next()
36
+ return
37
+ }
38
+
39
+ const bearer = extractBearer(c.req.header('authorization'))
40
+ const cookie = getCookie(c, 'bz_token')
41
+ const token = bearer ?? cookie
42
+
43
+ if (!token || !isValidToken(token)) {
44
+ return c.json({ error: 'unauthorized' }, 401)
45
+ }
46
+
47
+ if (!isSetupOpen(path) && !isSetupComplete(getCtx().db)) {
48
+ return c.json({ error: 'setup incomplete' }, 409)
49
+ }
50
+
51
+ await next()
52
+ }