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,319 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import { afterEach, beforeEach, expect, test } from 'vitest'
|
|
4
|
-
import { archiveAgent } from '../../src/core/agent/archive.ts'
|
|
5
|
-
import { deleteAgent } from '../../src/core/agent/delete.ts'
|
|
6
|
-
import { resolveAgent } from '../../src/core/agent/resolve.ts'
|
|
7
|
-
import { spawnAgent } from '../../src/core/agent/spawn.ts'
|
|
8
|
-
import { unarchiveAgent } from '../../src/core/agent/unarchive.ts'
|
|
9
|
-
import { registerGroup } from '../../src/core/group/register.ts'
|
|
10
|
-
import { createProfile } from '../../src/core/profile/create.ts'
|
|
11
|
-
import * as agentRepo from '../../src/core/repos/agents.ts'
|
|
12
|
-
import * as messageRepo from '../../src/core/repos/messages.ts'
|
|
13
|
-
import { makeTestEnv, type TestEnv } from './helpers.ts'
|
|
14
|
-
|
|
15
|
-
let env: TestEnv
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
env = makeTestEnv()
|
|
18
|
-
})
|
|
19
|
-
afterEach(() => env.cleanup())
|
|
20
|
-
|
|
21
|
-
function seedProfile() {
|
|
22
|
-
return createProfile(env.db, env.paths, {
|
|
23
|
-
id: 'base',
|
|
24
|
-
defaultModel: 'anthropic:claude-opus-4-6',
|
|
25
|
-
defaultSkills: ['skill-a'],
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function spawn(input: Partial<Parameters<typeof spawnAgent>[2]> & { profileId?: string } = {}) {
|
|
30
|
-
return spawnAgent(env.db, env.paths, {
|
|
31
|
-
profileId: input.profileId ?? 'base',
|
|
32
|
-
groupId: input.groupId ?? env.groupId,
|
|
33
|
-
...input,
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
test('spawnAgent creates dir, copies templates, inserts row, attaches default skills', () => {
|
|
38
|
-
seedProfile()
|
|
39
|
-
const agent = spawn({ name: 'A1' })
|
|
40
|
-
|
|
41
|
-
expect(agent.profileId).toBe('base')
|
|
42
|
-
expect(agent.status).toBe('idle')
|
|
43
|
-
expect(agent.groupId).toBe(env.groupId)
|
|
44
|
-
expect(existsSync(agent.dir)).toBe(true)
|
|
45
|
-
expect(existsSync(join(agent.dir, 'SOUL.md'))).toBe(true)
|
|
46
|
-
expect(existsSync(join(agent.dir, 'IDENTITY.md'))).toBe(true)
|
|
47
|
-
expect(existsSync(join(agent.dir, 'BOOTSTRAP.md'))).toBe(true)
|
|
48
|
-
expect(existsSync(join(agent.dir, 'agent.json'))).toBe(true)
|
|
49
|
-
// Memory now lives at the group level (`groups/<slug>/memory/`), shared
|
|
50
|
-
// by all member agents. The agent's private home only carries identity
|
|
51
|
-
// files + sessions/.
|
|
52
|
-
expect(existsSync(join(agent.dir, 'sessions'))).toBe(true)
|
|
53
|
-
expect(existsSync(join(agent.dir, 'memory'))).toBe(false)
|
|
54
|
-
|
|
55
|
-
expect(agentRepo.listAttachedSkills(env.db, agent.id)).toEqual(['skill-a'])
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test('spawnAgent copies optional AGENTS/TOOLS/HEARTBEAT files when the profile seeded them', () => {
|
|
59
|
-
createProfile(env.db, env.paths, {
|
|
60
|
-
id: 'felix',
|
|
61
|
-
defaultModel: 'm',
|
|
62
|
-
templates: {
|
|
63
|
-
agents: '# AGENTS\n- peer: hello\n',
|
|
64
|
-
tools: '# TOOLS\n- ripgrep before grep\n',
|
|
65
|
-
heartbeat: '# HEARTBEAT\n- check calendar\n',
|
|
66
|
-
},
|
|
67
|
-
})
|
|
68
|
-
const agent = spawn({ profileId: 'felix' })
|
|
69
|
-
expect(existsSync(join(agent.dir, 'AGENTS.md'))).toBe(true)
|
|
70
|
-
expect(existsSync(join(agent.dir, 'TOOLS.md'))).toBe(true)
|
|
71
|
-
expect(existsSync(join(agent.dir, 'HEARTBEAT.md'))).toBe(true)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test('spawnAgent does not create optional AGENTS/TOOLS/HEARTBEAT files when the profile lacks them', () => {
|
|
75
|
-
seedProfile()
|
|
76
|
-
const agent = spawn()
|
|
77
|
-
expect(existsSync(join(agent.dir, 'AGENTS.md'))).toBe(false)
|
|
78
|
-
expect(existsSync(join(agent.dir, 'TOOLS.md'))).toBe(false)
|
|
79
|
-
expect(existsSync(join(agent.dir, 'HEARTBEAT.md'))).toBe(false)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test('spawning many agents from one profile produces independent instances', () => {
|
|
83
|
-
seedProfile()
|
|
84
|
-
const a = spawn()
|
|
85
|
-
const b = spawn()
|
|
86
|
-
expect(a.id).not.toBe(b.id)
|
|
87
|
-
expect(a.dir).not.toBe(b.dir)
|
|
88
|
-
expect(agentRepo.list(env.db).length).toBe(2)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('spawnAgent places the agent in the requested group', () => {
|
|
92
|
-
seedProfile()
|
|
93
|
-
const g = registerGroup(env.db, { id: 'extra' }, env.paths)
|
|
94
|
-
|
|
95
|
-
const agent = spawn({ groupId: g.id })
|
|
96
|
-
expect(agent.groupId).toBe('extra')
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
test('spawnAgent rejects an unknown group', () => {
|
|
100
|
-
seedProfile()
|
|
101
|
-
expect(() => spawn({ groupId: 'no-such-group' })).toThrow(/group "no-such-group" does not exist/)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
test('agentRepo.setGroup moves an agent to a different group', () => {
|
|
105
|
-
seedProfile()
|
|
106
|
-
const g2 = registerGroup(env.db, { id: 'g2' }, env.paths)
|
|
107
|
-
const agent = spawn()
|
|
108
|
-
expect(agent.groupId).toBe(env.groupId)
|
|
109
|
-
|
|
110
|
-
agentRepo.setGroup(env.db, agent.id, g2.id)
|
|
111
|
-
expect(agentRepo.get(env.db, agent.id)?.groupId).toBe('g2')
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
test('attaching and detaching skills on the fly', () => {
|
|
115
|
-
seedProfile()
|
|
116
|
-
const agent = spawn()
|
|
117
|
-
|
|
118
|
-
agentRepo.attachSkill(env.db, agent.id, 'extra')
|
|
119
|
-
expect(agentRepo.listAttachedSkills(env.db, agent.id).sort()).toEqual(['extra', 'skill-a'])
|
|
120
|
-
|
|
121
|
-
agentRepo.detachSkill(env.db, agent.id, 'skill-a')
|
|
122
|
-
expect(agentRepo.listAttachedSkills(env.db, agent.id)).toEqual(['extra'])
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
test('archiveAgent sets status and timestamp', () => {
|
|
126
|
-
seedProfile()
|
|
127
|
-
const agent = spawn()
|
|
128
|
-
archiveAgent(env.db, agent.id)
|
|
129
|
-
const after = agentRepo.get(env.db, agent.id)
|
|
130
|
-
expect(after?.status).toBe('archived')
|
|
131
|
-
expect(after?.archivedAt).not.toBeNull()
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
test('list excludes archived by default', () => {
|
|
135
|
-
seedProfile()
|
|
136
|
-
const a = spawn()
|
|
137
|
-
spawn()
|
|
138
|
-
archiveAgent(env.db, a.id)
|
|
139
|
-
|
|
140
|
-
expect(agentRepo.list(env.db).length).toBe(1)
|
|
141
|
-
expect(agentRepo.list(env.db, { includeArchived: true }).length).toBe(2)
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
test('unarchiveAgent flips status back to idle and clears archivedAt', () => {
|
|
145
|
-
seedProfile()
|
|
146
|
-
const agent = spawn()
|
|
147
|
-
archiveAgent(env.db, agent.id)
|
|
148
|
-
expect(agentRepo.get(env.db, agent.id)?.status).toBe('archived')
|
|
149
|
-
|
|
150
|
-
unarchiveAgent(env.db, agent.id)
|
|
151
|
-
const after = agentRepo.get(env.db, agent.id)
|
|
152
|
-
expect(after?.status).toBe('idle')
|
|
153
|
-
expect(after?.archivedAt).toBeNull()
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
test('unarchiveAgent throws on a non-archived agent', () => {
|
|
157
|
-
seedProfile()
|
|
158
|
-
const agent = spawn()
|
|
159
|
-
expect(() => unarchiveAgent(env.db, agent.id)).toThrow(/not archived/)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
test('unarchiveAgent throws on an unknown id', () => {
|
|
163
|
-
seedProfile()
|
|
164
|
-
expect(() => unarchiveAgent(env.db, 'ghost-id')).toThrow()
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
test('deleteAgent removes the DB row and the on-disk directory', () => {
|
|
168
|
-
seedProfile()
|
|
169
|
-
const agent = spawn()
|
|
170
|
-
expect(existsSync(agent.dir)).toBe(true)
|
|
171
|
-
|
|
172
|
-
deleteAgent(env.db, agent.id)
|
|
173
|
-
expect(agentRepo.get(env.db, agent.id)).toBeNull()
|
|
174
|
-
expect(existsSync(agent.dir)).toBe(false)
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
test('deleteAgent cascades to attachments, messages, runs', () => {
|
|
178
|
-
seedProfile()
|
|
179
|
-
const a = spawn()
|
|
180
|
-
const b = spawn()
|
|
181
|
-
agentRepo.attachSkill(env.db, a.id, 'extra')
|
|
182
|
-
|
|
183
|
-
// Side table: a message tied to the agent
|
|
184
|
-
messageRepo.send(env.db, {
|
|
185
|
-
from: b.id,
|
|
186
|
-
to: a.id,
|
|
187
|
-
payload: JSON.stringify({ text: 'hi' }),
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
// Sanity: the rows exist before delete
|
|
191
|
-
expect(agentRepo.listAttachedSkills(env.db, a.id).length).toBeGreaterThan(0)
|
|
192
|
-
expect(messageRepo.listInbox(env.db, a.id).length).toBe(1)
|
|
193
|
-
|
|
194
|
-
deleteAgent(env.db, a.id)
|
|
195
|
-
|
|
196
|
-
expect(agentRepo.get(env.db, a.id)).toBeNull()
|
|
197
|
-
expect(agentRepo.listAttachedSkills(env.db, a.id)).toEqual([])
|
|
198
|
-
expect(messageRepo.listInbox(env.db, a.id)).toEqual([])
|
|
199
|
-
|
|
200
|
-
// Agent b is untouched
|
|
201
|
-
expect(agentRepo.get(env.db, b.id)).not.toBeNull()
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
test('deleteAgent throws on an unknown id', () => {
|
|
205
|
-
seedProfile()
|
|
206
|
-
expect(() => deleteAgent(env.db, 'ghost-id')).toThrow()
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
test('agentRepo.get resolves an unambiguous UUID prefix (≥4 chars)', () => {
|
|
210
|
-
seedProfile()
|
|
211
|
-
const agent = spawn()
|
|
212
|
-
// Full UUID still works
|
|
213
|
-
expect(agentRepo.get(env.db, agent.id)?.id).toBe(agent.id)
|
|
214
|
-
// First 8 chars (git-style) resolves
|
|
215
|
-
const prefix = agent.id.slice(0, 8)
|
|
216
|
-
expect(agentRepo.get(env.db, prefix)?.id).toBe(agent.id)
|
|
217
|
-
// <4 chars never resolves
|
|
218
|
-
expect(agentRepo.get(env.db, agent.id.slice(0, 3))).toBeNull()
|
|
219
|
-
// Non-existing prefix still null
|
|
220
|
-
expect(agentRepo.get(env.db, 'zzzzzzzz')).toBeNull()
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
test('agentRepo.get resolves by exact name when unique', () => {
|
|
224
|
-
seedProfile()
|
|
225
|
-
const a = spawn({ name: 'alpha' })
|
|
226
|
-
const b = spawn({ name: 'beta' })
|
|
227
|
-
|
|
228
|
-
expect(agentRepo.get(env.db, 'alpha')?.id).toBe(a.id)
|
|
229
|
-
expect(agentRepo.get(env.db, 'beta')?.id).toBe(b.id)
|
|
230
|
-
expect(agentRepo.get(env.db, 'nobody')).toBeNull()
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
test('agentRepo.get returns null for an ambiguous name', () => {
|
|
234
|
-
seedProfile()
|
|
235
|
-
spawn({ name: 'twin' })
|
|
236
|
-
spawn({ name: 'twin' })
|
|
237
|
-
expect(agentRepo.get(env.db, 'twin')).toBeNull()
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
test('agentRepo.get prefers exact name over prefix collision', () => {
|
|
241
|
-
seedProfile()
|
|
242
|
-
// An agent whose name is a hex string that is ALSO a valid UUID prefix of
|
|
243
|
-
// some other agent's id. The name-match should win so users can rename
|
|
244
|
-
// agents to anything and have lookup behave predictably.
|
|
245
|
-
const id = 'deadbeef-0000-0000-0000-000000000000'
|
|
246
|
-
agentRepo.insert(env.db, {
|
|
247
|
-
id,
|
|
248
|
-
profileId: 'base',
|
|
249
|
-
name: 'A-with-hex-prefix-name',
|
|
250
|
-
modelOverride: null,
|
|
251
|
-
reasoningLevel: 'medium',
|
|
252
|
-
status: 'idle',
|
|
253
|
-
dir: '/tmp/none-hex',
|
|
254
|
-
groupId: env.groupId,
|
|
255
|
-
})
|
|
256
|
-
const b = spawn({ name: 'deadbeef' })
|
|
257
|
-
|
|
258
|
-
// Lookup "deadbeef": exact name on agent b wins, even though it matches a
|
|
259
|
-
// (the id starts with deadbeef).
|
|
260
|
-
expect(agentRepo.get(env.db, 'deadbeef')?.id).toBe(b.id)
|
|
261
|
-
// But the full id for a still resolves
|
|
262
|
-
expect(agentRepo.get(env.db, id)?.id).toBe(id)
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
test('agentRepo.get returns null for ambiguous prefix', () => {
|
|
266
|
-
seedProfile()
|
|
267
|
-
// Insert two agents manually with IDs that share a 4-char prefix; spawnAgent
|
|
268
|
-
// uses random UUIDs so we bypass it.
|
|
269
|
-
agentRepo.insert(env.db, {
|
|
270
|
-
id: 'abcd1111-aaaa-bbbb-cccc-dddddddddddd',
|
|
271
|
-
profileId: 'base',
|
|
272
|
-
name: 'A',
|
|
273
|
-
modelOverride: null,
|
|
274
|
-
reasoningLevel: 'medium',
|
|
275
|
-
status: 'idle',
|
|
276
|
-
dir: '/tmp/none-a',
|
|
277
|
-
groupId: env.groupId,
|
|
278
|
-
})
|
|
279
|
-
agentRepo.insert(env.db, {
|
|
280
|
-
id: 'abcd2222-aaaa-bbbb-cccc-dddddddddddd',
|
|
281
|
-
profileId: 'base',
|
|
282
|
-
name: 'B',
|
|
283
|
-
modelOverride: null,
|
|
284
|
-
reasoningLevel: 'medium',
|
|
285
|
-
status: 'idle',
|
|
286
|
-
dir: '/tmp/none-b',
|
|
287
|
-
groupId: env.groupId,
|
|
288
|
-
})
|
|
289
|
-
expect(agentRepo.get(env.db, 'abcd')).toBeNull() // ambiguous
|
|
290
|
-
expect(agentRepo.get(env.db, 'abcd1')?.name).toBe('A') // unique
|
|
291
|
-
expect(agentRepo.get(env.db, 'abcd2')?.name).toBe('B')
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
test('resolveAgent accepts a UUID prefix', () => {
|
|
295
|
-
seedProfile()
|
|
296
|
-
const agent = spawn()
|
|
297
|
-
const resolved = resolveAgent(env.db, env.paths, agent.id.slice(0, 8))
|
|
298
|
-
expect(resolved.agent.id).toBe(agent.id)
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
test('resolveAgent assembles profile, skills, group, and model', () => {
|
|
302
|
-
seedProfile()
|
|
303
|
-
const g = registerGroup(env.db, { id: 'rg' }, env.paths)
|
|
304
|
-
const agent = spawn({ groupId: g.id, modelOverride: 'openai:gpt-5' })
|
|
305
|
-
|
|
306
|
-
const resolved = resolveAgent(env.db, env.paths, agent.id)
|
|
307
|
-
expect(resolved.model).toBe('openai:gpt-5')
|
|
308
|
-
expect(resolved.profile.id).toBe('base')
|
|
309
|
-
expect(resolved.group.id).toBe('rg')
|
|
310
|
-
expect(resolved.group.userMd).toBe('')
|
|
311
|
-
expect(resolved.skills).toEqual(['skill-a'])
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
test('resolveAgent uses profile default model when no override', () => {
|
|
315
|
-
seedProfile()
|
|
316
|
-
const agent = spawn()
|
|
317
|
-
const resolved = resolveAgent(env.db, env.paths, agent.id)
|
|
318
|
-
expect(resolved.model).toBe('anthropic:claude-opus-4-6')
|
|
319
|
-
})
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, expect, test } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
type BazilionDb,
|
|
4
|
-
groupAvailableModels,
|
|
5
|
-
listAvailableModels,
|
|
6
|
-
openInMemoryDb,
|
|
7
|
-
providerModelRepo,
|
|
8
|
-
providerStateRepo,
|
|
9
|
-
runMigrations,
|
|
10
|
-
} from '../../src/core/index.ts'
|
|
11
|
-
|
|
12
|
-
let db: BazilionDb
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
db = openInMemoryDb()
|
|
16
|
-
runMigrations(db)
|
|
17
|
-
})
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
db.close()
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
test('empty DB yields empty lists', () => {
|
|
23
|
-
expect(listAvailableModels(db)).toEqual([])
|
|
24
|
-
expect(groupAvailableModels(db)).toEqual([])
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('only enabled providers with curated models surface', () => {
|
|
28
|
-
providerStateRepo.setEnabled(db, 'anthropic', true)
|
|
29
|
-
providerStateRepo.setEnabled(db, 'openai', true)
|
|
30
|
-
providerStateRepo.setEnabled(db, 'groq', false)
|
|
31
|
-
providerStateRepo.setEnabled(db, 'lmstudio', true)
|
|
32
|
-
|
|
33
|
-
providerModelRepo.replace(db, 'anthropic', ['claude-opus-4-6', 'claude-sonnet-4-6'])
|
|
34
|
-
providerModelRepo.replace(db, 'openai', ['gpt-4o'])
|
|
35
|
-
// openai but disabled-then-enabled — still has its curated entries
|
|
36
|
-
providerModelRepo.replace(db, 'groq', ['llama-3.3-70b']) // disabled → omitted
|
|
37
|
-
// lmstudio is enabled but has no curated models → omitted from groups
|
|
38
|
-
|
|
39
|
-
const list = listAvailableModels(db)
|
|
40
|
-
expect(list.map((m) => m.value).sort()).toEqual([
|
|
41
|
-
'anthropic:claude-opus-4-6',
|
|
42
|
-
'anthropic:claude-sonnet-4-6',
|
|
43
|
-
'openai:gpt-4o',
|
|
44
|
-
])
|
|
45
|
-
|
|
46
|
-
const groups = groupAvailableModels(db)
|
|
47
|
-
expect(groups).toEqual([
|
|
48
|
-
{ provider: 'anthropic', models: ['claude-opus-4-6', 'claude-sonnet-4-6'] },
|
|
49
|
-
{ provider: 'openai', models: ['gpt-4o'] },
|
|
50
|
-
])
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('disabling a provider makes its curated models disappear from the list', () => {
|
|
54
|
-
providerStateRepo.setEnabled(db, 'anthropic', true)
|
|
55
|
-
providerModelRepo.replace(db, 'anthropic', ['claude-opus-4-6'])
|
|
56
|
-
expect(listAvailableModels(db)).toHaveLength(1)
|
|
57
|
-
|
|
58
|
-
providerStateRepo.setEnabled(db, 'anthropic', false)
|
|
59
|
-
expect(listAvailableModels(db)).toHaveLength(0)
|
|
60
|
-
|
|
61
|
-
providerStateRepo.setEnabled(db, 'anthropic', true)
|
|
62
|
-
expect(listAvailableModels(db)).toHaveLength(1)
|
|
63
|
-
})
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { mkdtempSync, rmSync } from 'node:fs'
|
|
2
|
-
import { tmpdir } from 'node:os'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import { afterEach, beforeEach, expect, test } from 'vitest'
|
|
5
|
-
import { type BazilionDb, openInMemoryDb } from '../../src/core/db/client.ts'
|
|
6
|
-
import { runMigrations } from '../../src/core/db/migrate.ts'
|
|
7
|
-
import { CONFIG_KEYS, isConfigKey, openConfig } from '../../src/core/repos/config.ts'
|
|
8
|
-
import { openSecrets } from '../../src/core/repos/secrets.ts'
|
|
9
|
-
import { mergeSecretsIntoEnv } from '../../src/core/secrets.ts'
|
|
10
|
-
|
|
11
|
-
interface Env {
|
|
12
|
-
home: string
|
|
13
|
-
db: BazilionDb
|
|
14
|
-
authToken: string
|
|
15
|
-
cleanup(): void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function makeEnv(): Env {
|
|
19
|
-
const home = mkdtempSync(join(tmpdir(), 'bazilion-config-'))
|
|
20
|
-
const db = openInMemoryDb()
|
|
21
|
-
runMigrations(db)
|
|
22
|
-
return {
|
|
23
|
-
home,
|
|
24
|
-
db,
|
|
25
|
-
authToken: 'test-token-abc123',
|
|
26
|
-
cleanup() {
|
|
27
|
-
db.close()
|
|
28
|
-
rmSync(home, { recursive: true, force: true })
|
|
29
|
-
},
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let env: Env
|
|
34
|
-
|
|
35
|
-
beforeEach(() => {
|
|
36
|
-
env = makeEnv()
|
|
37
|
-
})
|
|
38
|
-
afterEach(() => {
|
|
39
|
-
env.cleanup()
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('isConfigKey identifies URL-type keys and rejects secret-shaped ones', () => {
|
|
43
|
-
for (const k of CONFIG_KEYS) expect(isConfigKey(k)).toBe(true)
|
|
44
|
-
expect(isConfigKey('ANTHROPIC_API_KEY')).toBe(false)
|
|
45
|
-
expect(isConfigKey('OPENAI_API_KEY')).toBe(false)
|
|
46
|
-
expect(isConfigKey('BRAVE_API_KEY')).toBe(false)
|
|
47
|
-
expect(isConfigKey('random-junk')).toBe(false)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
test('openConfig round-trips LMSTUDIO_URL through the config table', () => {
|
|
51
|
-
const config = openConfig(env.db)
|
|
52
|
-
config.set('LMSTUDIO_URL', 'http://192.168.1.50:1234/v1')
|
|
53
|
-
expect(config.get('LMSTUDIO_URL')).toBe('http://192.168.1.50:1234/v1')
|
|
54
|
-
|
|
55
|
-
// Re-opening (a fresh repo over the same db) yields the same value.
|
|
56
|
-
const reopened = openConfig(env.db)
|
|
57
|
-
expect(reopened.get('LMSTUDIO_URL')).toBe('http://192.168.1.50:1234/v1')
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
test('openConfig rejects unknown keys to prevent accidental secret leaks', () => {
|
|
61
|
-
const config = openConfig(env.db)
|
|
62
|
-
expect(() => config.set('ANTHROPIC_API_KEY', 'sk-abc')).toThrow(/not a known config key/)
|
|
63
|
-
expect(() => config.set('random-thing', 'x')).toThrow(/not a known config key/)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('openConfig.remove clears one key without affecting siblings', () => {
|
|
67
|
-
const config = openConfig(env.db)
|
|
68
|
-
config.set('LMSTUDIO_URL', 'http://x')
|
|
69
|
-
config.set('OLLAMA_URL', 'http://y')
|
|
70
|
-
config.remove('LMSTUDIO_URL')
|
|
71
|
-
expect(config.get('LMSTUDIO_URL')).toBeUndefined()
|
|
72
|
-
expect(config.get('OLLAMA_URL')).toBe('http://y')
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test('openSecrets round-trips an encrypted value with the bootstrap token', () => {
|
|
76
|
-
const secrets = openSecrets(env.db, env.authToken)
|
|
77
|
-
secrets.set('ANTHROPIC_API_KEY', 'sk-from-secrets')
|
|
78
|
-
expect(secrets.get('ANTHROPIC_API_KEY')).toBe('sk-from-secrets')
|
|
79
|
-
|
|
80
|
-
// A different password fails to decrypt — get returns undefined rather
|
|
81
|
-
// than throwing.
|
|
82
|
-
const wrongPass = openSecrets(env.db, 'wrong-token')
|
|
83
|
-
expect(wrongPass.get('ANTHROPIC_API_KEY')).toBeUndefined()
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
test('mergeSecretsIntoEnv layers process env over secrets over config', () => {
|
|
87
|
-
const config = openConfig(env.db)
|
|
88
|
-
config.set('LMSTUDIO_URL', 'http://from-config')
|
|
89
|
-
|
|
90
|
-
const secrets = openSecrets(env.db, env.authToken)
|
|
91
|
-
secrets.set('ANTHROPIC_API_KEY', 'sk-from-secrets')
|
|
92
|
-
|
|
93
|
-
// process env beats both.
|
|
94
|
-
const merged = mergeSecretsIntoEnv(env.db, env.authToken, {
|
|
95
|
-
ANTHROPIC_API_KEY: 'sk-from-env',
|
|
96
|
-
} as NodeJS.ProcessEnv)
|
|
97
|
-
expect(merged.LMSTUDIO_URL).toBe('http://from-config')
|
|
98
|
-
expect(merged.ANTHROPIC_API_KEY).toBe('sk-from-env')
|
|
99
|
-
})
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { existsSync, lstatSync, mkdtempSync, symlinkSync } from 'node:fs'
|
|
2
|
-
import { tmpdir } from 'node:os'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import { afterEach, beforeEach, expect, test } from 'vitest'
|
|
5
|
-
import { registerGroup } from '../../src/core/group/register.ts'
|
|
6
|
-
import * as groupRepo from '../../src/core/repos/groups.ts'
|
|
7
|
-
import { makeTestEnv, type TestEnv } from './helpers.ts'
|
|
8
|
-
|
|
9
|
-
let env: TestEnv
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
env = makeTestEnv()
|
|
12
|
-
})
|
|
13
|
-
afterEach(() => env.cleanup())
|
|
14
|
-
|
|
15
|
-
test('registerGroup creates a real directory under groups/<slug>/', () => {
|
|
16
|
-
const g = registerGroup(env.db, { id: 'g1' }, env.paths)
|
|
17
|
-
expect(g.id).toBe('g1')
|
|
18
|
-
expect(g.path).toBe(env.paths.groupDir('g1'))
|
|
19
|
-
expect(g.userMd).toBe('')
|
|
20
|
-
expect(existsSync(g.path)).toBe(true)
|
|
21
|
-
expect(lstatSync(g.path).isSymbolicLink()).toBe(false)
|
|
22
|
-
expect(existsSync(join(g.path, 'memory'))).toBe(true)
|
|
23
|
-
// +1 over the auto-seeded test-group from makeTestEnv.
|
|
24
|
-
expect(groupRepo.list(env.db, env.paths).length).toBe(2)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('registerGroup with --link materializes the slot as a symlink', () => {
|
|
28
|
-
const target = mkdtempSync(join(tmpdir(), 'bazilion-link-'))
|
|
29
|
-
const g = registerGroup(env.db, { id: 'linked', link: target }, env.paths)
|
|
30
|
-
expect(g.path).toBe(env.paths.groupDir('linked'))
|
|
31
|
-
expect(lstatSync(g.path).isSymbolicLink()).toBe(true)
|
|
32
|
-
// memory subdir gets created via the symlink — ends up inside the target.
|
|
33
|
-
expect(existsSync(join(target, 'memory'))).toBe(true)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('registerGroup --link fails when target does not exist', () => {
|
|
37
|
-
expect(() =>
|
|
38
|
-
registerGroup(env.db, { id: 'broken', link: '/tmp/no-such-bazilion-target-x9' }, env.paths),
|
|
39
|
-
).toThrow(/does not exist/)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('registerGroup refuses duplicate slug', () => {
|
|
43
|
-
registerGroup(env.db, { id: 'first' }, env.paths)
|
|
44
|
-
expect(() => registerGroup(env.db, { id: 'first' }, env.paths)).toThrow(/already registered/)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('registerGroup refuses if the on-disk slot already exists', () => {
|
|
48
|
-
// Materialize the slot before the registry call — should bail out cleanly.
|
|
49
|
-
const slot = env.paths.groupDir('preexisting')
|
|
50
|
-
symlinkSync(env.home, slot, 'dir')
|
|
51
|
-
expect(() => registerGroup(env.db, { id: 'preexisting' }, env.paths)).toThrow(/slot already/)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('registerGroup rejects invalid slug', () => {
|
|
55
|
-
expect(() => registerGroup(env.db, { id: 'Bad Id' }, env.paths)).toThrow(/invalid slug/)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test('setUserMd updates the stored value, read back via get', () => {
|
|
59
|
-
const g = registerGroup(env.db, { id: 'with-user-md' }, env.paths)
|
|
60
|
-
groupRepo.setUserMd(env.db, g.id, 'hi, call me Pat')
|
|
61
|
-
const reloaded = groupRepo.get(env.db, g.id, env.paths)
|
|
62
|
-
expect(reloaded?.userMd).toBe('hi, call me Pat')
|
|
63
|
-
})
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'
|
|
2
|
-
import { tmpdir } from 'node:os'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import { type BazilionDb, openInMemoryDb } from '../../src/core/db/client.ts'
|
|
5
|
-
import { runMigrations } from '../../src/core/db/migrate.ts'
|
|
6
|
-
import { registerGroup } from '../../src/core/group/register.ts'
|
|
7
|
-
import { type Paths, resolvePaths } from '../../src/core/paths.ts'
|
|
8
|
-
|
|
9
|
-
// `process.env.BAZILION_HOME` is consumed by `resolvePaths`. Some tests pass a
|
|
10
|
-
// home explicitly; other code paths reach for the env. Either works.
|
|
11
|
-
|
|
12
|
-
export interface TestEnv {
|
|
13
|
-
home: string
|
|
14
|
-
paths: Paths
|
|
15
|
-
db: BazilionDb
|
|
16
|
-
/**
|
|
17
|
-
* A group registered automatically so spawn/resolve always have a valid
|
|
18
|
-
* id to target. Named `test-group` — tests that need more can register
|
|
19
|
-
* extras.
|
|
20
|
-
*/
|
|
21
|
-
groupId: string
|
|
22
|
-
cleanup: () => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function makeTestEnv(): TestEnv {
|
|
26
|
-
const home = mkdtempSync(join(tmpdir(), 'bazilion-test-'))
|
|
27
|
-
const paths = resolvePaths(home)
|
|
28
|
-
for (const d of [
|
|
29
|
-
paths.profilesDir,
|
|
30
|
-
paths.agentsDir,
|
|
31
|
-
paths.skillsDir,
|
|
32
|
-
paths.logsDir,
|
|
33
|
-
paths.groupsDir,
|
|
34
|
-
]) {
|
|
35
|
-
mkdirSync(d, { recursive: true })
|
|
36
|
-
}
|
|
37
|
-
const db = openInMemoryDb()
|
|
38
|
-
runMigrations(db)
|
|
39
|
-
const group = registerGroup(db, { id: 'test-group', name: 'test' }, paths)
|
|
40
|
-
return {
|
|
41
|
-
home,
|
|
42
|
-
paths,
|
|
43
|
-
db,
|
|
44
|
-
groupId: group.id,
|
|
45
|
-
cleanup() {
|
|
46
|
-
db.close()
|
|
47
|
-
rmSync(home, { recursive: true, force: true })
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import { tmpdir } from 'node:os'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import { afterEach, beforeEach, expect, test } from 'vitest'
|
|
5
|
-
import {
|
|
6
|
-
identityHasValues,
|
|
7
|
-
loadIdentityFromFile,
|
|
8
|
-
parseIdentityMarkdown,
|
|
9
|
-
} from '../../src/core/profile/identity.ts'
|
|
10
|
-
|
|
11
|
-
let dir: string
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
dir = mkdtempSync(join(tmpdir(), 'bazilion-identity-'))
|
|
14
|
-
})
|
|
15
|
-
afterEach(() => rmSync(dir, { recursive: true, force: true }))
|
|
16
|
-
|
|
17
|
-
test('parseIdentityMarkdown extracts name/emoji/vibe from bullet list', () => {
|
|
18
|
-
const md = `# IDENTITY
|
|
19
|
-
|
|
20
|
-
- **Name:** Felix
|
|
21
|
-
- **Vibe:** cat with a clipboard
|
|
22
|
-
- **Emoji:** 🐾
|
|
23
|
-
- **Creature:** familiar
|
|
24
|
-
`
|
|
25
|
-
const parsed = parseIdentityMarkdown(md)
|
|
26
|
-
expect(parsed).toEqual({
|
|
27
|
-
name: 'Felix',
|
|
28
|
-
vibe: 'cat with a clipboard',
|
|
29
|
-
emoji: '🐾',
|
|
30
|
-
creature: 'familiar',
|
|
31
|
-
})
|
|
32
|
-
expect(identityHasValues(parsed)).toBe(true)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test('parseIdentityMarkdown strips placeholder prompts', () => {
|
|
36
|
-
const md = `- Name: pick something you like
|
|
37
|
-
- Emoji: 🦉
|
|
38
|
-
- Theme: your signature - pick one that feels right`
|
|
39
|
-
const parsed = parseIdentityMarkdown(md)
|
|
40
|
-
expect(parsed).toEqual({ emoji: '🦉' })
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('parseIdentityMarkdown ignores unrelated labels and bare text', () => {
|
|
44
|
-
const md = `# Felix
|
|
45
|
-
|
|
46
|
-
Random prose without a colon.
|
|
47
|
-
|
|
48
|
-
- Name: Bazyl
|
|
49
|
-
- Mission: this is not a known label
|
|
50
|
-
- not-even-a-label
|
|
51
|
-
`
|
|
52
|
-
const parsed = parseIdentityMarkdown(md)
|
|
53
|
-
expect(parsed).toEqual({ name: 'Bazyl' })
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test('parseIdentityMarkdown strips markdown emphasis on labels', () => {
|
|
57
|
-
// Label regex drops every *_; value regex only touches edges, after the
|
|
58
|
-
// colon-adjacent space has been preserved.
|
|
59
|
-
const md = `- **Name:** Quill
|
|
60
|
-
- _Vibe_: cheeky`
|
|
61
|
-
const parsed = parseIdentityMarkdown(md)
|
|
62
|
-
expect(parsed.name).toBe('Quill')
|
|
63
|
-
expect(parsed.vibe).toBe('cheeky')
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('identityHasValues is false for empty / placeholder-only identities', () => {
|
|
67
|
-
expect(identityHasValues(parseIdentityMarkdown('# heading\n'))).toBe(false)
|
|
68
|
-
expect(identityHasValues({})).toBe(false)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('loadIdentityFromFile returns null for missing file', () => {
|
|
72
|
-
expect(loadIdentityFromFile(join(dir, 'ghost.md'))).toBeNull()
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test('loadIdentityFromFile returns null when nothing parses', () => {
|
|
76
|
-
const path = join(dir, 'IDENTITY.md')
|
|
77
|
-
writeFileSync(path, '# Just prose, no key:value pairs\n')
|
|
78
|
-
expect(loadIdentityFromFile(path)).toBeNull()
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
test('loadIdentityFromFile returns parsed identity when the file has values', () => {
|
|
82
|
-
const path = join(dir, 'IDENTITY.md')
|
|
83
|
-
writeFileSync(path, '- Name: Ada\n- Emoji: ✨\n')
|
|
84
|
-
expect(loadIdentityFromFile(path)).toEqual({ name: 'Ada', emoji: '✨' })
|
|
85
|
-
})
|