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.
- package/README.md +53 -152
- package/dist/cli.js +2730 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon.js +5888 -0
- package/dist/daemon.js.map +1 -0
- package/dist/worker.js +1830 -0
- package/dist/worker.js.map +1 -0
- package/package.json +57 -28
- package/.understand-anything/.understandignore +0 -25
- package/.understand-anything/fingerprints.json +0 -14267
- package/.understand-anything/knowledge-graph.json +0 -18128
- package/.understand-anything/meta.json +0 -6
- package/CLAUDE.md +0 -164
- package/apps/cli/package.json +0 -21
- package/apps/cli/src/auth-file.ts +0 -18
- package/apps/cli/src/client.ts +0 -37
- package/apps/cli/src/columnize.ts +0 -32
- package/apps/cli/src/commands/agent.ts +0 -574
- package/apps/cli/src/commands/auth.ts +0 -110
- package/apps/cli/src/commands/backup.ts +0 -135
- package/apps/cli/src/commands/completion.ts +0 -155
- package/apps/cli/src/commands/config.ts +0 -95
- package/apps/cli/src/commands/doctor.ts +0 -131
- package/apps/cli/src/commands/group.ts +0 -132
- package/apps/cli/src/commands/inbox.ts +0 -82
- package/apps/cli/src/commands/login.ts +0 -73
- package/apps/cli/src/commands/memory.ts +0 -106
- package/apps/cli/src/commands/profile.ts +0 -259
- package/apps/cli/src/commands/provider.ts +0 -170
- package/apps/cli/src/commands/send.ts +0 -24
- package/apps/cli/src/commands/serve.ts +0 -89
- package/apps/cli/src/commands/skill.ts +0 -120
- package/apps/cli/src/commands/token.ts +0 -148
- package/apps/cli/src/commands/trigger.ts +0 -129
- package/apps/cli/src/commands/uninstall.ts +0 -156
- package/apps/cli/src/index.ts +0 -196
- package/apps/cli/src/paths.ts +0 -12
- package/apps/cli/test/agent.test.ts +0 -213
- package/apps/cli/test/backup.test.ts +0 -95
- package/apps/cli/test/chat.test.ts +0 -539
- package/apps/cli/test/columnize.test.ts +0 -29
- package/apps/cli/test/completion.test.ts +0 -45
- package/apps/cli/test/config-page.test.ts +0 -151
- package/apps/cli/test/group.test.ts +0 -74
- package/apps/cli/test/helpers.ts +0 -70
- package/apps/cli/test/inbox-autodeliver.test.ts +0 -143
- package/apps/cli/test/inbox.test.ts +0 -147
- package/apps/cli/test/memory.test.ts +0 -69
- package/apps/cli/test/profile.test.ts +0 -110
- package/apps/cli/test/send.test.ts +0 -30
- package/apps/cli/test/server-fixture.ts +0 -212
- package/apps/cli/test/session-head.test.ts +0 -45
- package/apps/cli/test/skill.test.ts +0 -128
- package/apps/cli/test/token.test.ts +0 -109
- package/apps/cli/test/trigger.test.ts +0 -245
- package/apps/cli/tsconfig.json +0 -4
- package/apps/daemon/package.json +0 -31
- package/apps/daemon/src/app.ts +0 -41
- package/apps/daemon/src/core/agent/archive.ts +0 -8
- package/apps/daemon/src/core/agent/delete.ts +0 -30
- package/apps/daemon/src/core/agent/resolve.ts +0 -31
- package/apps/daemon/src/core/agent/spawn.ts +0 -95
- package/apps/daemon/src/core/agent/unarchive.ts +0 -11
- package/apps/daemon/src/core/availableModels.ts +0 -58
- package/apps/daemon/src/core/db/client.ts +0 -113
- package/apps/daemon/src/core/db/migrate.ts +0 -41
- package/apps/daemon/src/core/db/migrations/0001_init.sql +0 -179
- package/apps/daemon/src/core/group/delete.ts +0 -21
- package/apps/daemon/src/core/group/register.ts +0 -68
- package/apps/daemon/src/core/index.ts +0 -71
- package/apps/daemon/src/core/paths.ts +0 -56
- package/apps/daemon/src/core/profile/create.ts +0 -78
- package/apps/daemon/src/core/profile/delete.ts +0 -26
- package/apps/daemon/src/core/profile/identity.ts +0 -70
- package/apps/daemon/src/core/profile/load.ts +0 -45
- package/apps/daemon/src/core/profile/seed.ts +0 -74
- package/apps/daemon/src/core/profile/templates.ts +0 -60
- package/apps/daemon/src/core/profile/update.ts +0 -57
- package/apps/daemon/src/core/profile/validate.ts +0 -9
- package/apps/daemon/src/core/repos/agents.ts +0 -202
- package/apps/daemon/src/core/repos/config.ts +0 -78
- package/apps/daemon/src/core/repos/groups.ts +0 -55
- package/apps/daemon/src/core/repos/messages.ts +0 -127
- package/apps/daemon/src/core/repos/profiles.ts +0 -83
- package/apps/daemon/src/core/repos/providerModels.ts +0 -58
- package/apps/daemon/src/core/repos/providerState.ts +0 -37
- package/apps/daemon/src/core/repos/secrets.ts +0 -145
- package/apps/daemon/src/core/repos/skillMeta.ts +0 -49
- package/apps/daemon/src/core/repos/triggers.ts +0 -101
- package/apps/daemon/src/core/repos/webTokens.ts +0 -87
- package/apps/daemon/src/core/secrets.ts +0 -65
- package/apps/daemon/src/core/services.ts +0 -264
- package/apps/daemon/src/core/skills/discover.ts +0 -28
- package/apps/daemon/src/core/skills/import.ts +0 -136
- package/apps/daemon/src/core/skills/parse.ts +0 -52
- package/apps/daemon/src/core/skills/resolve.ts +0 -50
- package/apps/daemon/src/index.ts +0 -45
- package/apps/daemon/src/lib/agent-cancel.ts +0 -48
- package/apps/daemon/src/lib/agent-id.ts +0 -13
- package/apps/daemon/src/lib/agent-turn.ts +0 -56
- package/apps/daemon/src/lib/api-key.ts +0 -56
- package/apps/daemon/src/lib/auth.ts +0 -41
- package/apps/daemon/src/lib/cron.ts +0 -93
- package/apps/daemon/src/lib/ctx.ts +0 -80
- package/apps/daemon/src/lib/messaging-host.ts +0 -34
- package/apps/daemon/src/lib/middleware-auth.ts +0 -52
- package/apps/daemon/src/lib/scheduler.ts +0 -294
- package/apps/daemon/src/routes/agents.ts +0 -772
- package/apps/daemon/src/routes/auth-login.ts +0 -193
- package/apps/daemon/src/routes/config.ts +0 -267
- package/apps/daemon/src/routes/groups.ts +0 -133
- package/apps/daemon/src/routes/messages.ts +0 -29
- package/apps/daemon/src/routes/misc.ts +0 -239
- package/apps/daemon/src/routes/profiles.ts +0 -197
- package/apps/daemon/src/routes/skills.ts +0 -123
- package/apps/daemon/src/routes/triggers.ts +0 -29
- package/apps/daemon/src/runtime/auth/openai-codex.ts +0 -121
- package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +0 -31
- package/apps/daemon/src/runtime/index.ts +0 -77
- package/apps/daemon/src/runtime/memory/files.ts +0 -103
- package/apps/daemon/src/runtime/memory/qmd.ts +0 -152
- package/apps/daemon/src/runtime/memory/types.ts +0 -16
- package/apps/daemon/src/runtime/pi/events.ts +0 -173
- package/apps/daemon/src/runtime/pi/session.ts +0 -536
- package/apps/daemon/src/runtime/pi/tools.ts +0 -85
- package/apps/daemon/src/runtime/providers/catalog.ts +0 -145
- package/apps/daemon/src/runtime/providers/pi-adapter.ts +0 -272
- package/apps/daemon/src/runtime/providers/registry.ts +0 -374
- package/apps/daemon/src/runtime/providers/retry.ts +0 -176
- package/apps/daemon/src/runtime/providers/types.ts +0 -33
- package/apps/daemon/src/runtime/session/prompt.ts +0 -83
- package/apps/daemon/src/runtime/tools/bootstrap.ts +0 -22
- package/apps/daemon/src/runtime/tools/home.ts +0 -114
- package/apps/daemon/src/runtime/tools/memory.ts +0 -81
- package/apps/daemon/src/runtime/tools/messaging.ts +0 -127
- package/apps/daemon/src/runtime/tools/registry.ts +0 -29
- package/apps/daemon/src/runtime/tools/types.ts +0 -13
- package/apps/daemon/src/runtime/tools/web-extract.ts +0 -110
- package/apps/daemon/src/runtime/tools/web-ssrf.ts +0 -245
- package/apps/daemon/src/runtime/tools/web.ts +0 -273
- package/apps/daemon/src/runtime/worker/entry.ts +0 -221
- package/apps/daemon/src/runtime/worker/ipc-protocol.ts +0 -75
- package/apps/daemon/src/runtime/worker/spawn.ts +0 -249
- package/apps/daemon/test/core/agents.test.ts +0 -319
- package/apps/daemon/test/core/available-models.test.ts +0 -63
- package/apps/daemon/test/core/config.test.ts +0 -99
- package/apps/daemon/test/core/groups.test.ts +0 -63
- package/apps/daemon/test/core/helpers.ts +0 -50
- package/apps/daemon/test/core/identity.test.ts +0 -85
- package/apps/daemon/test/core/migrations.test.ts +0 -83
- package/apps/daemon/test/core/profiles.test.ts +0 -182
- package/apps/daemon/test/core/provider-models.test.ts +0 -57
- package/apps/daemon/test/core/provider-state.test.ts +0 -36
- package/apps/daemon/test/core/skill-meta.test.ts +0 -45
- package/apps/daemon/test/core/skills.test.ts +0 -271
- package/apps/daemon/test/core/triggers.test.ts +0 -182
- package/apps/daemon/test/core/web-tokens.test.ts +0 -79
- package/apps/daemon/test/cron.test.ts +0 -90
- package/apps/daemon/test/runtime/heartbeat.test.ts +0 -34
- package/apps/daemon/test/runtime/memory-qmd.test.ts +0 -97
- package/apps/daemon/test/runtime/memory.test.ts +0 -78
- package/apps/daemon/test/runtime/messaging.test.ts +0 -213
- package/apps/daemon/test/runtime/mock-server.ts +0 -65
- package/apps/daemon/test/runtime/openai-codex-auth.test.ts +0 -116
- package/apps/daemon/test/runtime/providers.test.ts +0 -306
- package/apps/daemon/test/runtime/retry.test.ts +0 -191
- package/apps/daemon/test/runtime/session-head.test.ts +0 -90
- package/apps/daemon/test/runtime/tools-home.test.ts +0 -102
- package/apps/daemon/test/runtime/tools-web.test.ts +0 -206
- package/apps/daemon/tsconfig.json +0 -4
- package/apps/mobile/README.md +0 -60
- package/apps/mobile/app/_layout.tsx +0 -58
- package/apps/mobile/app/agents/[id]/chat.tsx +0 -486
- package/apps/mobile/app/agents/[id]/index.tsx +0 -166
- package/apps/mobile/app/agents/index.tsx +0 -212
- package/apps/mobile/app/index.tsx +0 -21
- package/apps/mobile/app/pair.tsx +0 -226
- package/apps/mobile/app/settings.tsx +0 -419
- package/apps/mobile/app.json +0 -49
- 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 +0 -6
- package/apps/mobile/metro.config.js +0 -28
- package/apps/mobile/package.json +0 -44
- package/apps/mobile/src/auth.ts +0 -66
- package/apps/mobile/src/pair-url.ts +0 -48
- package/apps/mobile/src/theme-context.tsx +0 -88
- package/apps/mobile/src/theme.ts +0 -135
- package/apps/mobile/test/pair-url.test.ts +0 -46
- package/apps/mobile/tsconfig.json +0 -23
- package/apps/web/components.json +0 -25
- package/apps/web/package.json +0 -39
- package/apps/web/public/baziu.svg +0 -8
- package/apps/web/src/components/AgentTabs.tsx +0 -45
- package/apps/web/src/components/BaziuLogo.tsx +0 -21
- package/apps/web/src/components/ChatPane.tsx +0 -1033
- package/apps/web/src/components/ConfigTabs.tsx +0 -29
- package/apps/web/src/components/CopyButton.tsx +0 -68
- package/apps/web/src/components/CreateGroupDialog.tsx +0 -127
- package/apps/web/src/components/FieldRow.tsx +0 -94
- package/apps/web/src/components/Footer.tsx +0 -10
- package/apps/web/src/components/PawIcon.tsx +0 -15
- package/apps/web/src/components/Sidebar.tsx +0 -287
- package/apps/web/src/components/SpawnDialog.tsx +0 -129
- package/apps/web/src/components/ThemeToggle.tsx +0 -75
- package/apps/web/src/components/TopNav.tsx +0 -34
- package/apps/web/src/components/ui/button.tsx +0 -67
- package/apps/web/src/components/ui/card.tsx +0 -103
- package/apps/web/src/components/ui/checkbox.tsx +0 -31
- package/apps/web/src/components/ui/dialog.tsx +0 -168
- package/apps/web/src/components/ui/input.tsx +0 -19
- package/apps/web/src/components/ui/label.tsx +0 -22
- package/apps/web/src/components/ui/radio-group.tsx +0 -44
- package/apps/web/src/components/ui/select.tsx +0 -192
- package/apps/web/src/components/ui/separator.tsx +0 -26
- package/apps/web/src/components/ui/table.tsx +0 -116
- package/apps/web/src/components/ui/tabs.tsx +0 -88
- package/apps/web/src/components/ui/textarea.tsx +0 -18
- package/apps/web/src/lib/auth.ts +0 -50
- package/apps/web/src/lib/daemon-client.ts +0 -34
- package/apps/web/src/lib/md.ts +0 -45
- package/apps/web/src/lib/utils.ts +0 -6
- package/apps/web/src/lib/wire-constants.ts +0 -27
- package/apps/web/src/routeTree.gen.ts +0 -408
- package/apps/web/src/router.tsx +0 -20
- package/apps/web/src/routes/__root.tsx +0 -123
- package/apps/web/src/routes/agents/$id/inbox.tsx +0 -207
- package/apps/web/src/routes/agents/$id/index.tsx +0 -527
- package/apps/web/src/routes/agents/$id/triggers.tsx +0 -239
- package/apps/web/src/routes/agents/index.tsx +0 -265
- package/apps/web/src/routes/api/$.ts +0 -88
- package/apps/web/src/routes/config/index.tsx +0 -315
- package/apps/web/src/routes/config/services.tsx +0 -49
- package/apps/web/src/routes/config/tokens.tsx +0 -192
- package/apps/web/src/routes/groups/$id/index.tsx +0 -153
- package/apps/web/src/routes/groups/$id/memory.tsx +0 -321
- package/apps/web/src/routes/groups/index.tsx +0 -191
- package/apps/web/src/routes/index.tsx +0 -133
- package/apps/web/src/routes/login.tsx +0 -63
- package/apps/web/src/routes/profiles/$id.tsx +0 -549
- package/apps/web/src/routes/profiles/index.tsx +0 -458
- package/apps/web/src/routes/skills/index.tsx +0 -297
- package/apps/web/src/routes/welcome.tsx +0 -61
- package/apps/web/src/styles.css +0 -449
- package/apps/web/tsconfig.json +0 -25
- package/apps/web/vite.config.ts +0 -25
- package/biome.json +0 -23
- package/docs/agent-engine.md +0 -219
- package/docs/architecture.md +0 -627
- package/docs/backlog/README.md +0 -42
- package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +0 -125
- package/docs/openclaw-reference.md +0 -210
- package/packages/api-types/package.json +0 -11
- package/packages/api-types/src/entities.ts +0 -146
- package/packages/api-types/src/events.ts +0 -55
- package/packages/api-types/src/index.ts +0 -488
- package/packages/api-types/src/memory.ts +0 -15
- package/packages/client/package.json +0 -13
- package/packages/client/src/index.ts +0 -117
- package/pnpm-workspace.yaml +0 -3
- package/tsconfig.base.json +0 -23
- package/tsconfig.json +0 -11
- package/vitest.config.ts +0 -22
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { createServer, type ServerResponse } from 'node:http'
|
|
2
|
-
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'
|
|
3
|
-
import { extractAgentId } from './helpers.ts'
|
|
4
|
-
import { startTestServer, type TestServer } from './server-fixture.ts'
|
|
5
|
-
|
|
6
|
-
interface MockLlm {
|
|
7
|
-
url: string
|
|
8
|
-
push(responses: unknown[]): void
|
|
9
|
-
reset(): void
|
|
10
|
-
stop(): Promise<void>
|
|
11
|
-
callCount(): number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface Canned {
|
|
15
|
-
choices: Array<{
|
|
16
|
-
message: { role?: string; content?: string | null }
|
|
17
|
-
finish_reason?: string
|
|
18
|
-
}>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function writeSse(res: ServerResponse, canned: Canned): void {
|
|
22
|
-
res.writeHead(200, {
|
|
23
|
-
'content-type': 'text/event-stream',
|
|
24
|
-
'cache-control': 'no-cache',
|
|
25
|
-
connection: 'keep-alive',
|
|
26
|
-
})
|
|
27
|
-
const msg = canned.choices[0]?.message ?? {}
|
|
28
|
-
const finish = canned.choices[0]?.finish_reason ?? 'stop'
|
|
29
|
-
res.write(
|
|
30
|
-
`data: ${JSON.stringify({
|
|
31
|
-
id: 'mock',
|
|
32
|
-
object: 'chat.completion.chunk',
|
|
33
|
-
choices: [{ index: 0, delta: { role: 'assistant' }, finish_reason: null }],
|
|
34
|
-
})}\n\n`,
|
|
35
|
-
)
|
|
36
|
-
if (typeof msg.content === 'string' && msg.content.length > 0) {
|
|
37
|
-
res.write(
|
|
38
|
-
`data: ${JSON.stringify({
|
|
39
|
-
id: 'mock',
|
|
40
|
-
object: 'chat.completion.chunk',
|
|
41
|
-
choices: [{ index: 0, delta: { content: msg.content }, finish_reason: null }],
|
|
42
|
-
})}\n\n`,
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
res.write(
|
|
46
|
-
`data: ${JSON.stringify({
|
|
47
|
-
id: 'mock',
|
|
48
|
-
object: 'chat.completion.chunk',
|
|
49
|
-
choices: [{ index: 0, delta: {}, finish_reason: finish }],
|
|
50
|
-
})}\n\n`,
|
|
51
|
-
)
|
|
52
|
-
res.write('data: [DONE]\n\n')
|
|
53
|
-
res.end()
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function startLmStudioMock(): Promise<MockLlm> {
|
|
57
|
-
const queue: unknown[] = []
|
|
58
|
-
let calls = 0
|
|
59
|
-
const server = createServer((_req, res) => {
|
|
60
|
-
calls++
|
|
61
|
-
if (queue.length === 0) {
|
|
62
|
-
// Default response so we never block — the scheduler may fire more times
|
|
63
|
-
// than the test pushed responses for, and we don't want to deadlock.
|
|
64
|
-
writeSse(res, {
|
|
65
|
-
choices: [
|
|
66
|
-
{ message: { role: 'assistant', content: 'default-pong' }, finish_reason: 'stop' },
|
|
67
|
-
],
|
|
68
|
-
})
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
const item = queue.shift() as Canned
|
|
72
|
-
writeSse(res, item)
|
|
73
|
-
})
|
|
74
|
-
return new Promise((resolve) => {
|
|
75
|
-
server.listen(0, () => {
|
|
76
|
-
const addr = server.address()
|
|
77
|
-
if (!addr || typeof addr === 'string') throw new Error('unexpected server address')
|
|
78
|
-
resolve({
|
|
79
|
-
url: `http://localhost:${addr.port}/v1`,
|
|
80
|
-
push(items) {
|
|
81
|
-
queue.push(...items)
|
|
82
|
-
},
|
|
83
|
-
reset() {
|
|
84
|
-
queue.length = 0
|
|
85
|
-
calls = 0
|
|
86
|
-
},
|
|
87
|
-
stop: () => new Promise<void>((r) => server.close(() => r())),
|
|
88
|
-
callCount: () => calls,
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let mock: MockLlm
|
|
95
|
-
let server: TestServer
|
|
96
|
-
|
|
97
|
-
beforeAll(async () => {
|
|
98
|
-
mock = await startLmStudioMock()
|
|
99
|
-
server = await startTestServer({
|
|
100
|
-
LMSTUDIO_URL: mock.url,
|
|
101
|
-
// Drive the scheduler fast so the test doesn't wait 5s per tick.
|
|
102
|
-
BAZILION_SCHEDULER_TICK_MS: '200',
|
|
103
|
-
})
|
|
104
|
-
})
|
|
105
|
-
afterAll(async () => {
|
|
106
|
-
await server.stop()
|
|
107
|
-
await mock.stop()
|
|
108
|
-
})
|
|
109
|
-
// Reset order matters: wipe DB (kills any active triggers) *before* zeroing
|
|
110
|
-
// the mock's call counter, so a tick mid-reset can't increment `calls` after
|
|
111
|
-
// we've cleared it. The scheduler has nothing to fire after the DB wipe.
|
|
112
|
-
beforeEach(() => {
|
|
113
|
-
server.reset()
|
|
114
|
-
mock.reset()
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
async function spawnAgent(): Promise<string> {
|
|
118
|
-
await server.cli(['profile', 'create', 'p', '--model', 'lmstudio:test-model'])
|
|
119
|
-
const r = await server.cli(['agent', 'spawn', '--profile', 'p'])
|
|
120
|
-
return extractAgentId(r.stdout)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
test('trigger add/list round-trip', async () => {
|
|
124
|
-
const agentId = await spawnAgent()
|
|
125
|
-
const add = await server.cli([
|
|
126
|
-
'trigger',
|
|
127
|
-
'add',
|
|
128
|
-
agentId,
|
|
129
|
-
'--every',
|
|
130
|
-
'60',
|
|
131
|
-
'--message',
|
|
132
|
-
'tick',
|
|
133
|
-
'--disabled',
|
|
134
|
-
])
|
|
135
|
-
expect(add.exitCode).toBe(0)
|
|
136
|
-
expect(add.stdout).toMatch(/every 60s\tdisabled/)
|
|
137
|
-
|
|
138
|
-
const list = await server.cli(['trigger', 'list', agentId])
|
|
139
|
-
expect(list.exitCode).toBe(0)
|
|
140
|
-
expect(list.stdout).toMatch(/every 60s/)
|
|
141
|
-
expect(list.stdout).toMatch(/last: \(never\)/)
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
test('trigger enable/disable toggles state', async () => {
|
|
145
|
-
const agentId = await spawnAgent()
|
|
146
|
-
const add = await server.cli([
|
|
147
|
-
'trigger',
|
|
148
|
-
'add',
|
|
149
|
-
agentId,
|
|
150
|
-
'--every',
|
|
151
|
-
'60',
|
|
152
|
-
'--message',
|
|
153
|
-
'tick',
|
|
154
|
-
'--disabled',
|
|
155
|
-
])
|
|
156
|
-
const [id] = add.stdout.trim().split('\t') as [string]
|
|
157
|
-
expect(id).toBeTruthy()
|
|
158
|
-
|
|
159
|
-
await server.cli(['trigger', 'enable', id])
|
|
160
|
-
let list = await server.cli(['trigger', 'list', agentId])
|
|
161
|
-
expect(list.stdout).toMatch(/enabled/)
|
|
162
|
-
|
|
163
|
-
await server.cli(['trigger', 'disable', id])
|
|
164
|
-
list = await server.cli(['trigger', 'list', agentId])
|
|
165
|
-
expect(list.stdout).toMatch(/disabled/)
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
test('trigger rm deletes the trigger', async () => {
|
|
169
|
-
const agentId = await spawnAgent()
|
|
170
|
-
const add = await server.cli([
|
|
171
|
-
'trigger',
|
|
172
|
-
'add',
|
|
173
|
-
agentId,
|
|
174
|
-
'--every',
|
|
175
|
-
'60',
|
|
176
|
-
'--message',
|
|
177
|
-
'tick',
|
|
178
|
-
'--disabled',
|
|
179
|
-
])
|
|
180
|
-
const [id] = add.stdout.trim().split('\t') as [string]
|
|
181
|
-
await server.cli(['trigger', 'rm', id])
|
|
182
|
-
const list = await server.cli(['trigger', 'list', agentId])
|
|
183
|
-
expect(list.stdout).toContain('(no triggers)')
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
test('enabled interval trigger fires and updates last_fired_at', async () => {
|
|
187
|
-
const agentId = await spawnAgent()
|
|
188
|
-
|
|
189
|
-
// intervalSec=1 → first fire happens ~1s after creation, scheduler ticks every 200ms.
|
|
190
|
-
const add = await server.cli([
|
|
191
|
-
'trigger',
|
|
192
|
-
'add',
|
|
193
|
-
agentId,
|
|
194
|
-
'--every',
|
|
195
|
-
'1',
|
|
196
|
-
'--message',
|
|
197
|
-
'hello from cron',
|
|
198
|
-
])
|
|
199
|
-
expect(add.exitCode).toBe(0)
|
|
200
|
-
|
|
201
|
-
// Wait up to 15s for `last_fired_at` to flip from never to a timestamp —
|
|
202
|
-
// the scheduler bumps it before it kicks the worker.
|
|
203
|
-
let fired = false
|
|
204
|
-
for (let i = 0; i < 150; i++) {
|
|
205
|
-
const list = await server.cli(['trigger', 'list', agentId])
|
|
206
|
-
if (!/last: \(never\)/.test(list.stdout)) {
|
|
207
|
-
fired = true
|
|
208
|
-
break
|
|
209
|
-
}
|
|
210
|
-
await new Promise((r) => setTimeout(r, 100))
|
|
211
|
-
}
|
|
212
|
-
expect(fired).toBe(true)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
test('cron trigger with invalid expression is rejected', async () => {
|
|
216
|
-
const agentId = await spawnAgent()
|
|
217
|
-
const r = await server.cli([
|
|
218
|
-
'trigger',
|
|
219
|
-
'add',
|
|
220
|
-
agentId,
|
|
221
|
-
'--cron',
|
|
222
|
-
'not-a-cron',
|
|
223
|
-
'--message',
|
|
224
|
-
'nope',
|
|
225
|
-
])
|
|
226
|
-
expect(r.exitCode).not.toBe(0)
|
|
227
|
-
expect(r.stderr + r.stdout).toMatch(/invalid cron/i)
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
test('trigger add rejects both --every and --cron', async () => {
|
|
231
|
-
const agentId = await spawnAgent()
|
|
232
|
-
const r = await server.cli([
|
|
233
|
-
'trigger',
|
|
234
|
-
'add',
|
|
235
|
-
agentId,
|
|
236
|
-
'--every',
|
|
237
|
-
'60',
|
|
238
|
-
'--cron',
|
|
239
|
-
'* * * * *',
|
|
240
|
-
'--message',
|
|
241
|
-
'hi',
|
|
242
|
-
])
|
|
243
|
-
expect(r.exitCode).not.toBe(0)
|
|
244
|
-
expect(r.stderr + r.stdout).toMatch(/exactly one of --every/)
|
|
245
|
-
})
|
package/apps/cli/tsconfig.json
DELETED
package/apps/daemon/package.json
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@bazilion/daemon",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"bazilion-daemon": "./src/index.ts"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"dev": "tsx src/index.ts",
|
|
11
|
-
"start": "node --experimental-strip-types src/index.ts"
|
|
12
|
-
},
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"@bazilion/api-types": "workspace:*",
|
|
15
|
-
"@hono/node-server": "^1.13.0",
|
|
16
|
-
"@mariozechner/pi-agent-core": "^0.69.0",
|
|
17
|
-
"@mariozechner/pi-ai": "^0.69.0",
|
|
18
|
-
"@mariozechner/pi-coding-agent": "^0.69.0",
|
|
19
|
-
"@mozilla/readability": "^0.6.0",
|
|
20
|
-
"@tobilu/qmd": "^2.1.0",
|
|
21
|
-
"adm-zip": "^0.5.17",
|
|
22
|
-
"hono": "^4.6.0",
|
|
23
|
-
"linkedom": "^0.18.12",
|
|
24
|
-
"typebox": "^1.1.32",
|
|
25
|
-
"undici": "^8.1.0",
|
|
26
|
-
"yaml": "^2.6.0"
|
|
27
|
-
},
|
|
28
|
-
"devDependencies": {
|
|
29
|
-
"@types/adm-zip": "^0.5.8"
|
|
30
|
-
}
|
|
31
|
-
}
|
package/apps/daemon/src/app.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// Hono app factory.
|
|
2
|
-
//
|
|
3
|
-
// Returns a fully-wired Hono app: middleware (auth + first-run gate) +
|
|
4
|
-
// routes. Kept as a factory so tests can build one with a dedicated DB
|
|
5
|
-
// without touching the global ctx singleton.
|
|
6
|
-
|
|
7
|
-
import { Hono } from 'hono'
|
|
8
|
-
import { authMiddleware } from './lib/middleware-auth.ts'
|
|
9
|
-
import { agentsRouter } from './routes/agents.ts'
|
|
10
|
-
import { authRouter } from './routes/auth-login.ts'
|
|
11
|
-
import { configRouter } from './routes/config.ts'
|
|
12
|
-
import { groupsRouter } from './routes/groups.ts'
|
|
13
|
-
import { messagesRouter } from './routes/messages.ts'
|
|
14
|
-
import { miscRouter } from './routes/misc.ts'
|
|
15
|
-
import { profilesRouter } from './routes/profiles.ts'
|
|
16
|
-
import { skillsRouter } from './routes/skills.ts'
|
|
17
|
-
import { triggersRouter } from './routes/triggers.ts'
|
|
18
|
-
|
|
19
|
-
export function createApp(): Hono {
|
|
20
|
-
const app = new Hono()
|
|
21
|
-
|
|
22
|
-
// Auth + first-run gate runs before every route. Public paths (/api/login,
|
|
23
|
-
// /api/health) and the setup-open prefixes (/api/config, /api/auth) are
|
|
24
|
-
// whitelisted inside the middleware itself.
|
|
25
|
-
app.use('*', authMiddleware)
|
|
26
|
-
|
|
27
|
-
app.route('/api/agents', agentsRouter)
|
|
28
|
-
app.route('/api/groups', groupsRouter)
|
|
29
|
-
app.route('/api/profiles', profilesRouter)
|
|
30
|
-
app.route('/api/skills', skillsRouter)
|
|
31
|
-
app.route('/api/triggers', triggersRouter)
|
|
32
|
-
app.route('/api/messages', messagesRouter)
|
|
33
|
-
app.route('/api/config', configRouter)
|
|
34
|
-
// miscRouter exposes /backup, /tokens, /tokens/:id directly under /api —
|
|
35
|
-
// mounted at the API root so each handler can use its full path.
|
|
36
|
-
// authRouter likewise: /auth/openai*, /providers/test, /login.
|
|
37
|
-
app.route('/api', miscRouter)
|
|
38
|
-
app.route('/api', authRouter)
|
|
39
|
-
|
|
40
|
-
return app
|
|
41
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { BazilionDb } from '../db/client.ts'
|
|
2
|
-
import * as agentRepo from '../repos/agents.ts'
|
|
3
|
-
|
|
4
|
-
export function archiveAgent(db: BazilionDb, id: string): void {
|
|
5
|
-
const agent = agentRepo.get(db, id)
|
|
6
|
-
if (!agent) throw new Error(`agent not found: ${id}`)
|
|
7
|
-
agentRepo.archive(db, agent.id)
|
|
8
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { existsSync, rmSync } from 'node:fs'
|
|
2
|
-
import type { BazilionDb } from '../db/client.ts'
|
|
3
|
-
import * as agentRepo from '../repos/agents.ts'
|
|
4
|
-
|
|
5
|
-
export function deleteAgent(db: BazilionDb, id: string): void {
|
|
6
|
-
const agent = agentRepo.get(db, id)
|
|
7
|
-
if (!agent) throw new Error(`agent not found: ${id}`)
|
|
8
|
-
const fullId = agent.id
|
|
9
|
-
|
|
10
|
-
db.raw.transaction(() => {
|
|
11
|
-
// messages.from_agent_id and to_agent_id reference agents(id) with no ON
|
|
12
|
-
// DELETE rule, and messages.reply_to references messages(id) the same way,
|
|
13
|
-
// so a naive DELETE of the agent fails if it has any mailbox history.
|
|
14
|
-
// Null out inbound reply pointers to this agent's messages, then purge the
|
|
15
|
-
// messages themselves, then let agentRepo.remove cascade the rest
|
|
16
|
-
// (agent_skills, runs, events). agents.group_id is `ON DELETE RESTRICT`
|
|
17
|
-
// from the group side, but the agent row itself goes away freely.
|
|
18
|
-
db.raw.run(
|
|
19
|
-
`UPDATE messages SET reply_to = NULL
|
|
20
|
-
WHERE reply_to IN (SELECT id FROM messages WHERE from_agent_id = ? OR to_agent_id = ?)`,
|
|
21
|
-
[fullId, fullId],
|
|
22
|
-
)
|
|
23
|
-
db.raw.run('DELETE FROM messages WHERE from_agent_id = ? OR to_agent_id = ?', [fullId, fullId])
|
|
24
|
-
agentRepo.remove(db, fullId)
|
|
25
|
-
})()
|
|
26
|
-
|
|
27
|
-
if (existsSync(agent.dir)) {
|
|
28
|
-
rmSync(agent.dir, { recursive: true, force: true })
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { ResolvedAgent } from '@bazilion/api-types'
|
|
2
|
-
import type { BazilionDb } from '../db/client.ts'
|
|
3
|
-
import type { Paths } from '../paths.ts'
|
|
4
|
-
import * as agentRepo from '../repos/agents.ts'
|
|
5
|
-
import * as groupRepo from '../repos/groups.ts'
|
|
6
|
-
import * as profileRepo from '../repos/profiles.ts'
|
|
7
|
-
|
|
8
|
-
export function resolveAgent(db: BazilionDb, paths: Paths, agentId: string): ResolvedAgent {
|
|
9
|
-
// `agentRepo.get` accepts either a full UUID or an unambiguous prefix.
|
|
10
|
-
const agent = agentRepo.get(db, agentId)
|
|
11
|
-
if (!agent) throw new Error(`agent not found: ${agentId}`)
|
|
12
|
-
|
|
13
|
-
const profile = profileRepo.get(db, agent.profileId)
|
|
14
|
-
if (!profile) {
|
|
15
|
-
throw new Error(`profile not found for agent ${agentId}: ${agent.profileId}`)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const group = groupRepo.get(db, agent.groupId, paths)
|
|
19
|
-
if (!group) {
|
|
20
|
-
throw new Error(`group not found for agent ${agentId}: ${agent.groupId}`)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
agent,
|
|
25
|
-
profile,
|
|
26
|
-
model: agent.modelOverride ?? profile.defaultModel,
|
|
27
|
-
reasoningLevel: agent.reasoningLevel,
|
|
28
|
-
group,
|
|
29
|
-
skills: agentRepo.listAttachedSkills(db, agentId),
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto'
|
|
2
|
-
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import type { Agent, ReasoningLevel } from '@bazilion/api-types'
|
|
5
|
-
import type { BazilionDb } from '../db/client.ts'
|
|
6
|
-
import type { Paths } from '../paths.ts'
|
|
7
|
-
import { loadProfile } from '../profile/load.ts'
|
|
8
|
-
import { DEFAULT_GROUP_ID } from '../profile/seed.ts'
|
|
9
|
-
import * as agentRepo from '../repos/agents.ts'
|
|
10
|
-
import * as groupRepo from '../repos/groups.ts'
|
|
11
|
-
import { discoverSkills } from '../skills/discover.ts'
|
|
12
|
-
|
|
13
|
-
export interface SpawnAgentInput {
|
|
14
|
-
profileId: string
|
|
15
|
-
name?: string
|
|
16
|
-
modelOverride?: string | null
|
|
17
|
-
reasoningLevel?: ReasoningLevel
|
|
18
|
-
/**
|
|
19
|
-
* Group the new agent joins. One agent belongs to exactly one group. When
|
|
20
|
-
* omitted, falls back to the seeded 'default' group — the same fallback
|
|
21
|
-
* used everywhere we need a sensible cwd. Must refer to an existing group.
|
|
22
|
-
*/
|
|
23
|
-
groupId?: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function spawnAgent(db: BazilionDb, paths: Paths, input: SpawnAgentInput): Agent {
|
|
27
|
-
const loaded = loadProfile(db, input.profileId)
|
|
28
|
-
const id = randomUUID()
|
|
29
|
-
const dir = paths.agentDir(id)
|
|
30
|
-
// Agent's private home: identity files + sessions. Memory now lives at
|
|
31
|
-
// the group level (`groups/<slug>/memory/`), shared by all member agents,
|
|
32
|
-
// so we no longer create per-agent memory dirs here.
|
|
33
|
-
mkdirSync(dir, { recursive: true })
|
|
34
|
-
mkdirSync(join(dir, 'sessions'), { recursive: true })
|
|
35
|
-
|
|
36
|
-
// Copy profile templates so the agent can diverge per-instance.
|
|
37
|
-
writeFileSync(join(dir, 'SOUL.md'), loaded.files.soul)
|
|
38
|
-
writeFileSync(join(dir, 'IDENTITY.md'), loaded.files.identity)
|
|
39
|
-
if (loaded.files.bootstrap !== null) {
|
|
40
|
-
writeFileSync(join(dir, 'BOOTSTRAP.md'), loaded.files.bootstrap)
|
|
41
|
-
}
|
|
42
|
-
if (loaded.files.agents !== null) {
|
|
43
|
-
writeFileSync(join(dir, 'AGENTS.md'), loaded.files.agents)
|
|
44
|
-
}
|
|
45
|
-
if (loaded.files.tools !== null) {
|
|
46
|
-
writeFileSync(join(dir, 'TOOLS.md'), loaded.files.tools)
|
|
47
|
-
}
|
|
48
|
-
if (loaded.files.heartbeat !== null) {
|
|
49
|
-
writeFileSync(join(dir, 'HEARTBEAT.md'), loaded.files.heartbeat)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const reasoningLevel: ReasoningLevel = input.reasoningLevel ?? 'medium'
|
|
53
|
-
|
|
54
|
-
// Resolve group: explicit input wins; otherwise fall back to the seeded
|
|
55
|
-
// 'default'. If neither exists, error out — an agent can't live without a
|
|
56
|
-
// group (the FK `agents.group_id REFERENCES groups(id)` enforces it too).
|
|
57
|
-
const groupId = input.groupId ?? DEFAULT_GROUP_ID
|
|
58
|
-
const group = groupRepo.get(db, groupId, paths)
|
|
59
|
-
if (!group) {
|
|
60
|
-
throw new Error(
|
|
61
|
-
`spawnAgent: group "${groupId}" does not exist. Pass an explicit --group or complete first-run setup first.`,
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const agentJson = {
|
|
66
|
-
profileId: input.profileId,
|
|
67
|
-
name: input.name ?? loaded.profile.name,
|
|
68
|
-
modelOverride: input.modelOverride ?? null,
|
|
69
|
-
reasoningLevel,
|
|
70
|
-
groupId: group.id,
|
|
71
|
-
}
|
|
72
|
-
writeFileSync(join(dir, 'agent.json'), `${JSON.stringify(agentJson, null, 2)}\n`)
|
|
73
|
-
|
|
74
|
-
const agent = agentRepo.insert(db, {
|
|
75
|
-
id,
|
|
76
|
-
profileId: input.profileId,
|
|
77
|
-
name: agentJson.name,
|
|
78
|
-
modelOverride: agentJson.modelOverride,
|
|
79
|
-
reasoningLevel,
|
|
80
|
-
status: 'idle',
|
|
81
|
-
dir,
|
|
82
|
-
groupId: group.id,
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
// Skills come from the profile only — `skills_mode='all'` attaches every
|
|
86
|
-
// installed skill, `'selected'` attaches the profile's default list.
|
|
87
|
-
// Per-agent skill changes happen post-spawn via `agent skill add/rm`.
|
|
88
|
-
const skills =
|
|
89
|
-
loaded.profile.skillsMode === 'all'
|
|
90
|
-
? discoverSkills(paths).map((s) => s.name)
|
|
91
|
-
: loaded.defaultSkills
|
|
92
|
-
for (const s of skills) agentRepo.attachSkill(db, id, s)
|
|
93
|
-
|
|
94
|
-
return agent
|
|
95
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { BazilionDb } from '../db/client.ts'
|
|
2
|
-
import * as agentRepo from '../repos/agents.ts'
|
|
3
|
-
|
|
4
|
-
export function unarchiveAgent(db: BazilionDb, id: string): void {
|
|
5
|
-
const agent = agentRepo.get(db, id)
|
|
6
|
-
if (!agent) throw new Error(`agent not found: ${id}`)
|
|
7
|
-
if (agent.status !== 'archived') {
|
|
8
|
-
throw new Error(`agent is not archived (status: ${agent.status})`)
|
|
9
|
-
}
|
|
10
|
-
agentRepo.unarchive(db, agent.id)
|
|
11
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// Enumerates models that agents are actually allowed to use right now:
|
|
2
|
-
// every curated model of every currently-enabled provider. Drives the model
|
|
3
|
-
// dropdowns on the profile and agent forms.
|
|
4
|
-
//
|
|
5
|
-
// A provider must be both (a) toggled on in `provider_state` AND (b) have
|
|
6
|
-
// at least one curated entry in `provider_models` to show up here. Drop a
|
|
7
|
-
// provider off either side and it disappears from the list without a UI
|
|
8
|
-
// edit — the dropdowns are entirely data-driven.
|
|
9
|
-
|
|
10
|
-
import type { BazilionDb } from './db/client.ts'
|
|
11
|
-
import * as providerModelRepo from './repos/providerModels.ts'
|
|
12
|
-
import * as providerStateRepo from './repos/providerState.ts'
|
|
13
|
-
|
|
14
|
-
export interface AvailableModel {
|
|
15
|
-
provider: string
|
|
16
|
-
model: string
|
|
17
|
-
/** The `provider:model` string used as the form value and in model resolution. */
|
|
18
|
-
value: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Flat list of `{provider, model, value}` for every curated model of every
|
|
23
|
-
* enabled provider. Iteration order: providers as they're stored in
|
|
24
|
-
* `provider_state` (by insertion time), models as curated (preserving the
|
|
25
|
-
* admin's ordering from the textarea).
|
|
26
|
-
*/
|
|
27
|
-
export function listAvailableModels(db: BazilionDb): AvailableModel[] {
|
|
28
|
-
const enabled = providerStateRepo.listEnabled(db)
|
|
29
|
-
const out: AvailableModel[] = []
|
|
30
|
-
for (const provider of enabled) {
|
|
31
|
-
for (const model of providerModelRepo.list(db, provider)) {
|
|
32
|
-
out.push({ provider, model, value: `${provider}:${model}` })
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return out
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Same as `listAvailableModels` but grouped by provider — the shape the web
|
|
40
|
-
* dropdowns consume directly via `<optgroup>`.
|
|
41
|
-
*/
|
|
42
|
-
export function groupAvailableModels(db: BazilionDb): { provider: string; models: string[] }[] {
|
|
43
|
-
const enabled = providerStateRepo.listEnabled(db)
|
|
44
|
-
const groups: { provider: string; models: string[] }[] = []
|
|
45
|
-
for (const provider of enabled) {
|
|
46
|
-
const models = providerModelRepo.list(db, provider)
|
|
47
|
-
if (models.length > 0) groups.push({ provider, models })
|
|
48
|
-
}
|
|
49
|
-
return groups
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* True once the user has at least one usable model — i.e. at least one enabled
|
|
54
|
-
* provider with at least one curated model. Gates the first-run flow.
|
|
55
|
-
*/
|
|
56
|
-
export function isSetupComplete(db: BazilionDb): boolean {
|
|
57
|
-
return listAvailableModels(db).length > 0
|
|
58
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { DatabaseSync, type SQLInputValue } from 'node:sqlite'
|
|
2
|
-
|
|
3
|
-
export interface QueryStmt<RowType, ParamsType extends unknown[]> {
|
|
4
|
-
get(...params: ParamsType): RowType | null
|
|
5
|
-
all(...params: ParamsType): RowType[]
|
|
6
|
-
run(...params: ParamsType): { changes: number; lastInsertRowid: number | bigint }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface QueryableDatabase {
|
|
10
|
-
query<RowType, ParamsType extends unknown[]>(sql: string): QueryStmt<RowType, ParamsType>
|
|
11
|
-
run(sql: string, params?: unknown[]): { changes: number; lastInsertRowid: number | bigint }
|
|
12
|
-
exec(sql: string): void
|
|
13
|
-
transaction<T>(fn: () => T): () => T
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface BazilionDb {
|
|
17
|
-
raw: QueryableDatabase
|
|
18
|
-
close(): void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function wrap(rawDb: DatabaseSync): QueryableDatabase {
|
|
22
|
-
const cache = new Map<string, ReturnType<DatabaseSync['prepare']>>()
|
|
23
|
-
function getStmt(sql: string) {
|
|
24
|
-
let s = cache.get(sql)
|
|
25
|
-
if (!s) {
|
|
26
|
-
s = rawDb.prepare(sql)
|
|
27
|
-
cache.set(sql, s)
|
|
28
|
-
}
|
|
29
|
-
return s
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
query<R, P extends unknown[]>(sql: string): QueryStmt<R, P> {
|
|
34
|
-
const stmt = getStmt(sql)
|
|
35
|
-
return {
|
|
36
|
-
get(...params: P): R | null {
|
|
37
|
-
const result = stmt.get(...(params as SQLInputValue[]))
|
|
38
|
-
return (result as R | undefined) ?? null
|
|
39
|
-
},
|
|
40
|
-
all(...params: P): R[] {
|
|
41
|
-
return stmt.all(...(params as SQLInputValue[])) as R[]
|
|
42
|
-
},
|
|
43
|
-
run(...params: P) {
|
|
44
|
-
return stmt.run(...(params as SQLInputValue[])) as {
|
|
45
|
-
changes: number
|
|
46
|
-
lastInsertRowid: number | bigint
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
run(sql, params) {
|
|
52
|
-
const stmt = getStmt(sql)
|
|
53
|
-
return stmt.run(...((params ?? []) as SQLInputValue[])) as {
|
|
54
|
-
changes: number
|
|
55
|
-
lastInsertRowid: number | bigint
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
exec(sql) {
|
|
59
|
-
rawDb.exec(sql)
|
|
60
|
-
},
|
|
61
|
-
// node:sqlite has no callable `transaction` wrapper; use manual BEGIN/COMMIT/ROLLBACK.
|
|
62
|
-
transaction<T>(fn: () => T): () => T {
|
|
63
|
-
return () => {
|
|
64
|
-
rawDb.exec('BEGIN')
|
|
65
|
-
try {
|
|
66
|
-
const result = fn()
|
|
67
|
-
rawDb.exec('COMMIT')
|
|
68
|
-
return result
|
|
69
|
-
} catch (err) {
|
|
70
|
-
rawDb.exec('ROLLBACK')
|
|
71
|
-
throw err
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function applyPragmas(rawDb: DatabaseSync, includeWal: boolean): void {
|
|
79
|
-
if (includeWal) {
|
|
80
|
-
try {
|
|
81
|
-
rawDb.exec('PRAGMA journal_mode = WAL')
|
|
82
|
-
} catch {
|
|
83
|
-
// some sqlite builds reject WAL on :memory: — ignore
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
rawDb.exec('PRAGMA foreign_keys = ON')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function openDb(path: string): BazilionDb {
|
|
90
|
-
const raw = new DatabaseSync(path)
|
|
91
|
-
applyPragmas(raw, true)
|
|
92
|
-
return {
|
|
93
|
-
raw: wrap(raw),
|
|
94
|
-
close() {
|
|
95
|
-
raw.close()
|
|
96
|
-
},
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function openInMemoryDb(): BazilionDb {
|
|
101
|
-
const raw = new DatabaseSync(':memory:')
|
|
102
|
-
applyPragmas(raw, false)
|
|
103
|
-
return {
|
|
104
|
-
raw: wrap(raw),
|
|
105
|
-
close() {
|
|
106
|
-
raw.close()
|
|
107
|
-
},
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function inTx<T>(db: BazilionDb, fn: () => T): T {
|
|
112
|
-
return db.raw.transaction(fn)()
|
|
113
|
-
}
|