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.
- package/.understand-anything/.understandignore +25 -0
- package/.understand-anything/fingerprints.json +14267 -0
- package/.understand-anything/knowledge-graph.json +18128 -0
- package/.understand-anything/meta.json +6 -0
- package/CLAUDE.md +164 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/apps/cli/package.json +21 -0
- package/apps/cli/src/auth-file.ts +18 -0
- package/apps/cli/src/client.ts +37 -0
- package/apps/cli/src/columnize.ts +32 -0
- package/apps/cli/src/commands/agent.ts +574 -0
- package/apps/cli/src/commands/auth.ts +110 -0
- package/apps/cli/src/commands/backup.ts +135 -0
- package/apps/cli/src/commands/completion.ts +155 -0
- package/apps/cli/src/commands/config.ts +95 -0
- package/apps/cli/src/commands/doctor.ts +131 -0
- package/apps/cli/src/commands/group.ts +132 -0
- package/apps/cli/src/commands/inbox.ts +82 -0
- package/apps/cli/src/commands/login.ts +73 -0
- package/apps/cli/src/commands/memory.ts +106 -0
- package/apps/cli/src/commands/profile.ts +259 -0
- package/apps/cli/src/commands/provider.ts +170 -0
- package/apps/cli/src/commands/send.ts +24 -0
- package/apps/cli/src/commands/serve.ts +89 -0
- package/apps/cli/src/commands/skill.ts +120 -0
- package/apps/cli/src/commands/token.ts +148 -0
- package/apps/cli/src/commands/trigger.ts +129 -0
- package/apps/cli/src/commands/uninstall.ts +156 -0
- package/apps/cli/src/index.ts +196 -0
- package/apps/cli/src/paths.ts +12 -0
- package/apps/cli/test/agent.test.ts +213 -0
- package/apps/cli/test/backup.test.ts +95 -0
- package/apps/cli/test/chat.test.ts +539 -0
- package/apps/cli/test/columnize.test.ts +29 -0
- package/apps/cli/test/completion.test.ts +45 -0
- package/apps/cli/test/config-page.test.ts +151 -0
- package/apps/cli/test/group.test.ts +74 -0
- package/apps/cli/test/helpers.ts +70 -0
- package/apps/cli/test/inbox-autodeliver.test.ts +143 -0
- package/apps/cli/test/inbox.test.ts +147 -0
- package/apps/cli/test/memory.test.ts +69 -0
- package/apps/cli/test/profile.test.ts +110 -0
- package/apps/cli/test/send.test.ts +30 -0
- package/apps/cli/test/server-fixture.ts +212 -0
- package/apps/cli/test/session-head.test.ts +45 -0
- package/apps/cli/test/skill.test.ts +128 -0
- package/apps/cli/test/token.test.ts +109 -0
- package/apps/cli/test/trigger.test.ts +245 -0
- package/apps/cli/tsconfig.json +4 -0
- package/apps/daemon/package.json +31 -0
- package/apps/daemon/src/app.ts +41 -0
- package/apps/daemon/src/core/agent/archive.ts +8 -0
- package/apps/daemon/src/core/agent/delete.ts +30 -0
- package/apps/daemon/src/core/agent/resolve.ts +31 -0
- package/apps/daemon/src/core/agent/spawn.ts +95 -0
- package/apps/daemon/src/core/agent/unarchive.ts +11 -0
- package/apps/daemon/src/core/availableModels.ts +58 -0
- package/apps/daemon/src/core/db/client.ts +113 -0
- package/apps/daemon/src/core/db/migrate.ts +41 -0
- package/apps/daemon/src/core/db/migrations/0001_init.sql +179 -0
- package/apps/daemon/src/core/group/delete.ts +21 -0
- package/apps/daemon/src/core/group/register.ts +68 -0
- package/apps/daemon/src/core/index.ts +71 -0
- package/apps/daemon/src/core/paths.ts +56 -0
- package/apps/daemon/src/core/profile/create.ts +78 -0
- package/apps/daemon/src/core/profile/delete.ts +26 -0
- package/apps/daemon/src/core/profile/identity.ts +70 -0
- package/apps/daemon/src/core/profile/load.ts +45 -0
- package/apps/daemon/src/core/profile/seed.ts +74 -0
- package/apps/daemon/src/core/profile/templates.ts +60 -0
- package/apps/daemon/src/core/profile/update.ts +57 -0
- package/apps/daemon/src/core/profile/validate.ts +9 -0
- package/apps/daemon/src/core/repos/agents.ts +202 -0
- package/apps/daemon/src/core/repos/config.ts +78 -0
- package/apps/daemon/src/core/repos/groups.ts +55 -0
- package/apps/daemon/src/core/repos/messages.ts +127 -0
- package/apps/daemon/src/core/repos/profiles.ts +83 -0
- package/apps/daemon/src/core/repos/providerModels.ts +58 -0
- package/apps/daemon/src/core/repos/providerState.ts +37 -0
- package/apps/daemon/src/core/repos/secrets.ts +145 -0
- package/apps/daemon/src/core/repos/skillMeta.ts +49 -0
- package/apps/daemon/src/core/repos/triggers.ts +101 -0
- package/apps/daemon/src/core/repos/webTokens.ts +87 -0
- package/apps/daemon/src/core/secrets.ts +65 -0
- package/apps/daemon/src/core/services.ts +264 -0
- package/apps/daemon/src/core/skills/discover.ts +28 -0
- package/apps/daemon/src/core/skills/import.ts +136 -0
- package/apps/daemon/src/core/skills/parse.ts +52 -0
- package/apps/daemon/src/core/skills/resolve.ts +50 -0
- package/apps/daemon/src/index.ts +45 -0
- package/apps/daemon/src/lib/agent-cancel.ts +48 -0
- package/apps/daemon/src/lib/agent-id.ts +13 -0
- package/apps/daemon/src/lib/agent-turn.ts +56 -0
- package/apps/daemon/src/lib/api-key.ts +56 -0
- package/apps/daemon/src/lib/auth.ts +41 -0
- package/apps/daemon/src/lib/cron.ts +93 -0
- package/apps/daemon/src/lib/ctx.ts +80 -0
- package/apps/daemon/src/lib/messaging-host.ts +34 -0
- package/apps/daemon/src/lib/middleware-auth.ts +52 -0
- package/apps/daemon/src/lib/scheduler.ts +294 -0
- package/apps/daemon/src/routes/agents.ts +772 -0
- package/apps/daemon/src/routes/auth-login.ts +193 -0
- package/apps/daemon/src/routes/config.ts +267 -0
- package/apps/daemon/src/routes/groups.ts +133 -0
- package/apps/daemon/src/routes/messages.ts +29 -0
- package/apps/daemon/src/routes/misc.ts +239 -0
- package/apps/daemon/src/routes/profiles.ts +197 -0
- package/apps/daemon/src/routes/skills.ts +123 -0
- package/apps/daemon/src/routes/triggers.ts +29 -0
- package/apps/daemon/src/runtime/auth/openai-codex.ts +121 -0
- package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +31 -0
- package/apps/daemon/src/runtime/index.ts +77 -0
- package/apps/daemon/src/runtime/memory/files.ts +103 -0
- package/apps/daemon/src/runtime/memory/qmd.ts +152 -0
- package/apps/daemon/src/runtime/memory/types.ts +16 -0
- package/apps/daemon/src/runtime/pi/events.ts +173 -0
- package/apps/daemon/src/runtime/pi/session.ts +536 -0
- package/apps/daemon/src/runtime/pi/tools.ts +85 -0
- package/apps/daemon/src/runtime/providers/catalog.ts +145 -0
- package/apps/daemon/src/runtime/providers/pi-adapter.ts +272 -0
- package/apps/daemon/src/runtime/providers/registry.ts +374 -0
- package/apps/daemon/src/runtime/providers/retry.ts +176 -0
- package/apps/daemon/src/runtime/providers/types.ts +33 -0
- package/apps/daemon/src/runtime/session/prompt.ts +83 -0
- package/apps/daemon/src/runtime/tools/bootstrap.ts +22 -0
- package/apps/daemon/src/runtime/tools/home.ts +114 -0
- package/apps/daemon/src/runtime/tools/memory.ts +81 -0
- package/apps/daemon/src/runtime/tools/messaging.ts +127 -0
- package/apps/daemon/src/runtime/tools/registry.ts +29 -0
- package/apps/daemon/src/runtime/tools/types.ts +13 -0
- package/apps/daemon/src/runtime/tools/web-extract.ts +110 -0
- package/apps/daemon/src/runtime/tools/web-ssrf.ts +245 -0
- package/apps/daemon/src/runtime/tools/web.ts +273 -0
- package/apps/daemon/src/runtime/worker/entry.ts +221 -0
- package/apps/daemon/src/runtime/worker/ipc-protocol.ts +75 -0
- package/apps/daemon/src/runtime/worker/spawn.ts +249 -0
- package/apps/daemon/test/core/agents.test.ts +319 -0
- package/apps/daemon/test/core/available-models.test.ts +63 -0
- package/apps/daemon/test/core/config.test.ts +99 -0
- package/apps/daemon/test/core/groups.test.ts +63 -0
- package/apps/daemon/test/core/helpers.ts +50 -0
- package/apps/daemon/test/core/identity.test.ts +85 -0
- package/apps/daemon/test/core/migrations.test.ts +83 -0
- package/apps/daemon/test/core/profiles.test.ts +182 -0
- package/apps/daemon/test/core/provider-models.test.ts +57 -0
- package/apps/daemon/test/core/provider-state.test.ts +36 -0
- package/apps/daemon/test/core/skill-meta.test.ts +45 -0
- package/apps/daemon/test/core/skills.test.ts +271 -0
- package/apps/daemon/test/core/triggers.test.ts +182 -0
- package/apps/daemon/test/core/web-tokens.test.ts +79 -0
- package/apps/daemon/test/cron.test.ts +90 -0
- package/apps/daemon/test/runtime/heartbeat.test.ts +34 -0
- package/apps/daemon/test/runtime/memory-qmd.test.ts +97 -0
- package/apps/daemon/test/runtime/memory.test.ts +78 -0
- package/apps/daemon/test/runtime/messaging.test.ts +213 -0
- package/apps/daemon/test/runtime/mock-server.ts +65 -0
- package/apps/daemon/test/runtime/openai-codex-auth.test.ts +116 -0
- package/apps/daemon/test/runtime/providers.test.ts +306 -0
- package/apps/daemon/test/runtime/retry.test.ts +191 -0
- package/apps/daemon/test/runtime/session-head.test.ts +90 -0
- package/apps/daemon/test/runtime/tools-home.test.ts +102 -0
- package/apps/daemon/test/runtime/tools-web.test.ts +206 -0
- package/apps/daemon/tsconfig.json +4 -0
- package/apps/mobile/README.md +60 -0
- package/apps/mobile/app/_layout.tsx +58 -0
- package/apps/mobile/app/agents/[id]/chat.tsx +486 -0
- package/apps/mobile/app/agents/[id]/index.tsx +166 -0
- package/apps/mobile/app/agents/index.tsx +212 -0
- package/apps/mobile/app/index.tsx +21 -0
- package/apps/mobile/app/pair.tsx +226 -0
- package/apps/mobile/app/settings.tsx +419 -0
- package/apps/mobile/app.json +49 -0
- package/apps/mobile/assets/adaptive-icon.png +0 -0
- package/apps/mobile/assets/favicon.png +0 -0
- package/apps/mobile/assets/icon.png +0 -0
- package/apps/mobile/assets/splash-icon.png +0 -0
- package/apps/mobile/babel.config.js +6 -0
- package/apps/mobile/metro.config.js +28 -0
- package/apps/mobile/package.json +44 -0
- package/apps/mobile/src/auth.ts +66 -0
- package/apps/mobile/src/pair-url.ts +48 -0
- package/apps/mobile/src/theme-context.tsx +88 -0
- package/apps/mobile/src/theme.ts +135 -0
- package/apps/mobile/test/pair-url.test.ts +46 -0
- package/apps/mobile/tsconfig.json +23 -0
- package/apps/web/components.json +25 -0
- package/apps/web/package.json +39 -0
- package/apps/web/public/baziu.svg +8 -0
- package/apps/web/src/components/AgentTabs.tsx +45 -0
- package/apps/web/src/components/BaziuLogo.tsx +21 -0
- package/apps/web/src/components/ChatPane.tsx +1033 -0
- package/apps/web/src/components/ConfigTabs.tsx +29 -0
- package/apps/web/src/components/CopyButton.tsx +68 -0
- package/apps/web/src/components/CreateGroupDialog.tsx +127 -0
- package/apps/web/src/components/FieldRow.tsx +94 -0
- package/apps/web/src/components/Footer.tsx +10 -0
- package/apps/web/src/components/PawIcon.tsx +15 -0
- package/apps/web/src/components/Sidebar.tsx +287 -0
- package/apps/web/src/components/SpawnDialog.tsx +129 -0
- package/apps/web/src/components/ThemeToggle.tsx +75 -0
- package/apps/web/src/components/TopNav.tsx +34 -0
- package/apps/web/src/components/ui/button.tsx +67 -0
- package/apps/web/src/components/ui/card.tsx +103 -0
- package/apps/web/src/components/ui/checkbox.tsx +31 -0
- package/apps/web/src/components/ui/dialog.tsx +168 -0
- package/apps/web/src/components/ui/input.tsx +19 -0
- package/apps/web/src/components/ui/label.tsx +22 -0
- package/apps/web/src/components/ui/radio-group.tsx +44 -0
- package/apps/web/src/components/ui/select.tsx +192 -0
- package/apps/web/src/components/ui/separator.tsx +26 -0
- package/apps/web/src/components/ui/table.tsx +116 -0
- package/apps/web/src/components/ui/tabs.tsx +88 -0
- package/apps/web/src/components/ui/textarea.tsx +18 -0
- package/apps/web/src/lib/auth.ts +50 -0
- package/apps/web/src/lib/daemon-client.ts +34 -0
- package/apps/web/src/lib/md.ts +45 -0
- package/apps/web/src/lib/utils.ts +6 -0
- package/apps/web/src/lib/wire-constants.ts +27 -0
- package/apps/web/src/routeTree.gen.ts +408 -0
- package/apps/web/src/router.tsx +20 -0
- package/apps/web/src/routes/__root.tsx +123 -0
- package/apps/web/src/routes/agents/$id/inbox.tsx +207 -0
- package/apps/web/src/routes/agents/$id/index.tsx +527 -0
- package/apps/web/src/routes/agents/$id/triggers.tsx +239 -0
- package/apps/web/src/routes/agents/index.tsx +265 -0
- package/apps/web/src/routes/api/$.ts +88 -0
- package/apps/web/src/routes/config/index.tsx +315 -0
- package/apps/web/src/routes/config/services.tsx +49 -0
- package/apps/web/src/routes/config/tokens.tsx +192 -0
- package/apps/web/src/routes/groups/$id/index.tsx +153 -0
- package/apps/web/src/routes/groups/$id/memory.tsx +321 -0
- package/apps/web/src/routes/groups/index.tsx +191 -0
- package/apps/web/src/routes/index.tsx +133 -0
- package/apps/web/src/routes/login.tsx +63 -0
- package/apps/web/src/routes/profiles/$id.tsx +549 -0
- package/apps/web/src/routes/profiles/index.tsx +458 -0
- package/apps/web/src/routes/skills/index.tsx +297 -0
- package/apps/web/src/routes/welcome.tsx +61 -0
- package/apps/web/src/styles.css +449 -0
- package/apps/web/tsconfig.json +25 -0
- package/apps/web/vite.config.ts +25 -0
- package/biome.json +23 -0
- package/docs/agent-engine.md +219 -0
- package/docs/architecture.md +627 -0
- package/docs/backlog/README.md +42 -0
- package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +125 -0
- package/docs/openclaw-reference.md +210 -0
- package/package.json +38 -0
- package/packages/api-types/package.json +11 -0
- package/packages/api-types/src/entities.ts +146 -0
- package/packages/api-types/src/events.ts +55 -0
- package/packages/api-types/src/index.ts +488 -0
- package/packages/api-types/src/memory.ts +15 -0
- package/packages/client/package.json +13 -0
- package/packages/client/src/index.ts +117 -0
- package/pnpm-workspace.yaml +3 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.json +11 -0
- 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
|
+
}
|