bazilion 0.0.0 → 0.1.1
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 +6460 -0
- package/dist/daemon.js.map +1 -0
- package/dist/worker.js +2176 -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,182 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from 'vitest'
|
|
2
|
-
import * as agentRepo from '../../src/core/repos/agents.ts'
|
|
3
|
-
import * as profileRepo from '../../src/core/repos/profiles.ts'
|
|
4
|
-
import * as triggerRepo from '../../src/core/repos/triggers.ts'
|
|
5
|
-
import { makeTestEnv, type TestEnv } from './helpers.ts'
|
|
6
|
-
|
|
7
|
-
let env: TestEnv
|
|
8
|
-
let agentId: string
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
env = makeTestEnv()
|
|
12
|
-
profileRepo.insert(env.db, {
|
|
13
|
-
id: 'p',
|
|
14
|
-
name: 'p',
|
|
15
|
-
dir: env.paths.profileDir('p'),
|
|
16
|
-
defaultModel: 'lmstudio:x',
|
|
17
|
-
skillsMode: 'selected',
|
|
18
|
-
})
|
|
19
|
-
const a = agentRepo.insert(env.db, {
|
|
20
|
-
id: 'a1',
|
|
21
|
-
profileId: 'p',
|
|
22
|
-
name: 'A',
|
|
23
|
-
modelOverride: null,
|
|
24
|
-
reasoningLevel: 'medium',
|
|
25
|
-
status: 'idle',
|
|
26
|
-
dir: env.paths.agentDir('a1'),
|
|
27
|
-
groupId: env.groupId,
|
|
28
|
-
})
|
|
29
|
-
agentId = a.id
|
|
30
|
-
})
|
|
31
|
-
afterEach(() => env.cleanup())
|
|
32
|
-
|
|
33
|
-
test('insert + get + listForAgent round-trip for interval trigger', () => {
|
|
34
|
-
const t = triggerRepo.insert(env.db, {
|
|
35
|
-
agentId,
|
|
36
|
-
kind: 'interval',
|
|
37
|
-
intervalSec: 60,
|
|
38
|
-
cronExpr: null,
|
|
39
|
-
message: 'tick',
|
|
40
|
-
})
|
|
41
|
-
expect(t.kind).toBe('interval')
|
|
42
|
-
expect(t.intervalSec).toBe(60)
|
|
43
|
-
expect(t.enabled).toBe(true)
|
|
44
|
-
expect(t.lastFiredAt).toBeNull()
|
|
45
|
-
|
|
46
|
-
const fetched = triggerRepo.get(env.db, t.id)
|
|
47
|
-
expect(fetched?.message).toBe('tick')
|
|
48
|
-
|
|
49
|
-
const list = triggerRepo.listForAgent(env.db, agentId)
|
|
50
|
-
expect(list).toHaveLength(1)
|
|
51
|
-
expect(list[0]?.id).toBe(t.id)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('cron triggers store cronExpr and null intervalSec', () => {
|
|
55
|
-
const t = triggerRepo.insert(env.db, {
|
|
56
|
-
agentId,
|
|
57
|
-
kind: 'cron',
|
|
58
|
-
intervalSec: null,
|
|
59
|
-
cronExpr: '*/5 * * * *',
|
|
60
|
-
message: 'tick',
|
|
61
|
-
})
|
|
62
|
-
expect(t.cronExpr).toBe('*/5 * * * *')
|
|
63
|
-
expect(t.intervalSec).toBeNull()
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('enabled=false disables at insert', () => {
|
|
67
|
-
const t = triggerRepo.insert(env.db, {
|
|
68
|
-
agentId,
|
|
69
|
-
kind: 'interval',
|
|
70
|
-
intervalSec: 30,
|
|
71
|
-
cronExpr: null,
|
|
72
|
-
message: 'tick',
|
|
73
|
-
enabled: false,
|
|
74
|
-
})
|
|
75
|
-
expect(t.enabled).toBe(false)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test('setEnabled toggles the enabled flag', () => {
|
|
79
|
-
const t = triggerRepo.insert(env.db, {
|
|
80
|
-
agentId,
|
|
81
|
-
kind: 'interval',
|
|
82
|
-
intervalSec: 30,
|
|
83
|
-
cronExpr: null,
|
|
84
|
-
message: 'tick',
|
|
85
|
-
})
|
|
86
|
-
triggerRepo.setEnabled(env.db, t.id, false)
|
|
87
|
-
expect(triggerRepo.get(env.db, t.id)?.enabled).toBe(false)
|
|
88
|
-
triggerRepo.setEnabled(env.db, t.id, true)
|
|
89
|
-
expect(triggerRepo.get(env.db, t.id)?.enabled).toBe(true)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
test('markFired populates lastFiredAt', () => {
|
|
93
|
-
const t = triggerRepo.insert(env.db, {
|
|
94
|
-
agentId,
|
|
95
|
-
kind: 'interval',
|
|
96
|
-
intervalSec: 30,
|
|
97
|
-
cronExpr: null,
|
|
98
|
-
message: 'tick',
|
|
99
|
-
})
|
|
100
|
-
const when = Date.now() + 10_000
|
|
101
|
-
triggerRepo.markFired(env.db, t.id, when)
|
|
102
|
-
expect(triggerRepo.get(env.db, t.id)?.lastFiredAt).toBe(when)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
test('remove deletes the trigger', () => {
|
|
106
|
-
const t = triggerRepo.insert(env.db, {
|
|
107
|
-
agentId,
|
|
108
|
-
kind: 'interval',
|
|
109
|
-
intervalSec: 30,
|
|
110
|
-
cronExpr: null,
|
|
111
|
-
message: 'tick',
|
|
112
|
-
})
|
|
113
|
-
triggerRepo.remove(env.db, t.id)
|
|
114
|
-
expect(triggerRepo.get(env.db, t.id)).toBeNull()
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
test('listEnabled filters out disabled rows', () => {
|
|
118
|
-
triggerRepo.insert(env.db, {
|
|
119
|
-
agentId,
|
|
120
|
-
kind: 'interval',
|
|
121
|
-
intervalSec: 30,
|
|
122
|
-
cronExpr: null,
|
|
123
|
-
message: 'on',
|
|
124
|
-
})
|
|
125
|
-
triggerRepo.insert(env.db, {
|
|
126
|
-
agentId,
|
|
127
|
-
kind: 'interval',
|
|
128
|
-
intervalSec: 60,
|
|
129
|
-
cronExpr: null,
|
|
130
|
-
message: 'off',
|
|
131
|
-
enabled: false,
|
|
132
|
-
})
|
|
133
|
-
const enabled = triggerRepo.listEnabled(env.db)
|
|
134
|
-
expect(enabled).toHaveLength(1)
|
|
135
|
-
expect(enabled[0]?.message).toBe('on')
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test('listEnabled excludes triggers on archived agents', () => {
|
|
139
|
-
const other = agentRepo.insert(env.db, {
|
|
140
|
-
id: 'a2',
|
|
141
|
-
profileId: 'p',
|
|
142
|
-
name: 'B',
|
|
143
|
-
modelOverride: null,
|
|
144
|
-
reasoningLevel: 'medium',
|
|
145
|
-
status: 'idle',
|
|
146
|
-
dir: env.paths.agentDir('a2'),
|
|
147
|
-
groupId: env.groupId,
|
|
148
|
-
})
|
|
149
|
-
triggerRepo.insert(env.db, {
|
|
150
|
-
agentId,
|
|
151
|
-
kind: 'interval',
|
|
152
|
-
intervalSec: 30,
|
|
153
|
-
cronExpr: null,
|
|
154
|
-
message: 'live',
|
|
155
|
-
})
|
|
156
|
-
triggerRepo.insert(env.db, {
|
|
157
|
-
agentId: other.id,
|
|
158
|
-
kind: 'interval',
|
|
159
|
-
intervalSec: 30,
|
|
160
|
-
cronExpr: null,
|
|
161
|
-
message: 'archived',
|
|
162
|
-
})
|
|
163
|
-
agentRepo.archive(env.db, other.id)
|
|
164
|
-
const enabled = triggerRepo.listEnabled(env.db)
|
|
165
|
-
expect(enabled).toHaveLength(1)
|
|
166
|
-
expect(enabled[0]?.message).toBe('live')
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
describe('ON DELETE CASCADE', () => {
|
|
170
|
-
test('deleting the agent removes its triggers', () => {
|
|
171
|
-
triggerRepo.insert(env.db, {
|
|
172
|
-
agentId,
|
|
173
|
-
kind: 'interval',
|
|
174
|
-
intervalSec: 30,
|
|
175
|
-
cronExpr: null,
|
|
176
|
-
message: 'tick',
|
|
177
|
-
})
|
|
178
|
-
expect(triggerRepo.listForAgent(env.db, agentId)).toHaveLength(1)
|
|
179
|
-
agentRepo.remove(env.db, agentId)
|
|
180
|
-
expect(triggerRepo.listForAgent(env.db, agentId)).toHaveLength(0)
|
|
181
|
-
})
|
|
182
|
-
})
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, test } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
type BazilionDb,
|
|
4
|
-
openInMemoryDb,
|
|
5
|
-
runMigrations,
|
|
6
|
-
webTokenRepo,
|
|
7
|
-
} from '../../src/core/index.ts'
|
|
8
|
-
|
|
9
|
-
let db: BazilionDb
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
db = openInMemoryDb()
|
|
12
|
-
runMigrations(db)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
describe('webTokenRepo', () => {
|
|
16
|
-
test('create returns plaintext + metadata; plaintext is never re-queryable', () => {
|
|
17
|
-
const out = webTokenRepo.create(db, 'laptop')
|
|
18
|
-
expect(out.token).toMatch(/^[0-9a-f]{48}$/)
|
|
19
|
-
expect(out.meta.label).toBe('laptop')
|
|
20
|
-
expect(out.meta.revokedAt).toBe(null)
|
|
21
|
-
expect(out.meta.lastUsedAt).toBe(null)
|
|
22
|
-
|
|
23
|
-
// list() returns meta only — no plaintext
|
|
24
|
-
const listed = webTokenRepo.list(db)
|
|
25
|
-
expect(listed).toHaveLength(1)
|
|
26
|
-
expect((listed[0] as unknown as { token?: unknown }).token).toBeUndefined()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
test('findActiveByToken matches the plaintext and excludes revoked rows', () => {
|
|
30
|
-
const { token, meta } = webTokenRepo.create(db, 'laptop')
|
|
31
|
-
const match = webTokenRepo.findActiveByToken(db, token)
|
|
32
|
-
expect(match?.id).toBe(meta.id)
|
|
33
|
-
|
|
34
|
-
webTokenRepo.revoke(db, meta.id)
|
|
35
|
-
expect(webTokenRepo.findActiveByToken(db, token)).toBe(null)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('findActiveByToken returns null for unknown tokens', () => {
|
|
39
|
-
webTokenRepo.create(db, 'laptop')
|
|
40
|
-
expect(webTokenRepo.findActiveByToken(db, 'wrong')).toBe(null)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('markUsed updates last_used_at', () => {
|
|
44
|
-
const { meta } = webTokenRepo.create(db, 'laptop')
|
|
45
|
-
const when = Date.now() + 10_000
|
|
46
|
-
webTokenRepo.markUsed(db, meta.id, when)
|
|
47
|
-
expect(webTokenRepo.get(db, meta.id)?.lastUsedAt).toBe(when)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
test('revoke is idempotent — second revoke reports no change', () => {
|
|
51
|
-
const { meta } = webTokenRepo.create(db, 'laptop')
|
|
52
|
-
expect(webTokenRepo.revoke(db, meta.id)).toBe(true)
|
|
53
|
-
expect(webTokenRepo.revoke(db, meta.id)).toBe(false)
|
|
54
|
-
expect(webTokenRepo.get(db, meta.id)?.revokedAt).toBeTruthy()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
test('list hides revoked tokens by default but reveals them with includeRevoked', () => {
|
|
58
|
-
webTokenRepo.create(db, 'a')
|
|
59
|
-
const b = webTokenRepo.create(db, 'b')
|
|
60
|
-
webTokenRepo.revoke(db, b.meta.id)
|
|
61
|
-
expect(webTokenRepo.list(db).map((t) => t.label)).toEqual(['a'])
|
|
62
|
-
expect(webTokenRepo.list(db, { includeRevoked: true }).map((t) => t.label)).toEqual(['a', 'b'])
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test('plaintext token is stored as a hash, not in the clear', () => {
|
|
66
|
-
const { token } = webTokenRepo.create(db, 'laptop')
|
|
67
|
-
const row = db.raw
|
|
68
|
-
.query<{ token_hash: string }, []>('SELECT token_hash FROM web_tokens LIMIT 1')
|
|
69
|
-
.get()
|
|
70
|
-
expect(row?.token_hash).not.toBe(token)
|
|
71
|
-
expect(row?.token_hash).toBe(webTokenRepo.hashToken(token))
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test('two tokens with the same label coexist (label is not a primary key)', () => {
|
|
75
|
-
webTokenRepo.create(db, 'laptop')
|
|
76
|
-
expect(() => webTokenRepo.create(db, 'laptop')).not.toThrow()
|
|
77
|
-
expect(webTokenRepo.list(db)).toHaveLength(2)
|
|
78
|
-
})
|
|
79
|
-
})
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest'
|
|
2
|
-
import { matchesCron, parseCron, validateCron } from '../src/lib/cron.ts'
|
|
3
|
-
|
|
4
|
-
function at(y: number, mo: number, d: number, h: number, mi: number, dow?: number): Date {
|
|
5
|
-
// Local time — matches what matchesCron reads via getMinutes / getHours / etc.
|
|
6
|
-
// dow is ignored; JS computes it from the date itself.
|
|
7
|
-
void dow
|
|
8
|
-
return new Date(y, mo - 1, d, h, mi, 0, 0)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe('parseCron syntax', () => {
|
|
12
|
-
test('star-only', () => {
|
|
13
|
-
expect(() => parseCron('* * * * *')).not.toThrow()
|
|
14
|
-
})
|
|
15
|
-
test('step', () => {
|
|
16
|
-
const p = parseCron('*/15 * * * *')
|
|
17
|
-
expect(p.minute.has(0)).toBe(true)
|
|
18
|
-
expect(p.minute.has(15)).toBe(true)
|
|
19
|
-
expect(p.minute.has(30)).toBe(true)
|
|
20
|
-
expect(p.minute.has(45)).toBe(true)
|
|
21
|
-
expect(p.minute.has(10)).toBe(false)
|
|
22
|
-
})
|
|
23
|
-
test('list', () => {
|
|
24
|
-
const p = parseCron('0,30 * * * *')
|
|
25
|
-
expect(p.minute.has(0)).toBe(true)
|
|
26
|
-
expect(p.minute.has(30)).toBe(true)
|
|
27
|
-
expect(p.minute.has(15)).toBe(false)
|
|
28
|
-
})
|
|
29
|
-
test('range', () => {
|
|
30
|
-
const p = parseCron('10-15 * * * *')
|
|
31
|
-
expect(p.minute.has(10)).toBe(true)
|
|
32
|
-
expect(p.minute.has(15)).toBe(true)
|
|
33
|
-
expect(p.minute.has(16)).toBe(false)
|
|
34
|
-
})
|
|
35
|
-
test('range with step', () => {
|
|
36
|
-
const p = parseCron('0-30/10 * * * *')
|
|
37
|
-
expect(p.minute.has(0)).toBe(true)
|
|
38
|
-
expect(p.minute.has(10)).toBe(true)
|
|
39
|
-
expect(p.minute.has(20)).toBe(true)
|
|
40
|
-
expect(p.minute.has(30)).toBe(true)
|
|
41
|
-
expect(p.minute.has(40)).toBe(false)
|
|
42
|
-
})
|
|
43
|
-
test('Sunday normalization (7 → 0)', () => {
|
|
44
|
-
const p = parseCron('0 0 * * 7')
|
|
45
|
-
expect(p.dow.has(0)).toBe(true)
|
|
46
|
-
})
|
|
47
|
-
test('rejects 4 fields', () => {
|
|
48
|
-
expect(() => parseCron('* * * *')).toThrow(/5 fields/)
|
|
49
|
-
})
|
|
50
|
-
test('rejects out-of-range minute', () => {
|
|
51
|
-
expect(() => parseCron('60 * * * *')).toThrow(/out of range/)
|
|
52
|
-
})
|
|
53
|
-
test('rejects garbage', () => {
|
|
54
|
-
expect(() => parseCron('foo * * * *')).toThrow()
|
|
55
|
-
})
|
|
56
|
-
test('validateCron throws on bad input', () => {
|
|
57
|
-
expect(() => validateCron('* * * *')).toThrow()
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
describe('matchesCron semantics', () => {
|
|
62
|
-
test('every minute matches any minute', () => {
|
|
63
|
-
const p = parseCron('* * * * *')
|
|
64
|
-
expect(matchesCron(p, at(2026, 4, 17, 14, 37))).toBe(true)
|
|
65
|
-
})
|
|
66
|
-
test('every 5 minutes matches minute 25', () => {
|
|
67
|
-
const p = parseCron('*/5 * * * *')
|
|
68
|
-
expect(matchesCron(p, at(2026, 4, 17, 14, 25))).toBe(true)
|
|
69
|
-
expect(matchesCron(p, at(2026, 4, 17, 14, 26))).toBe(false)
|
|
70
|
-
})
|
|
71
|
-
test('hour-restricted fires only in that hour', () => {
|
|
72
|
-
const p = parseCron('0 9 * * *')
|
|
73
|
-
expect(matchesCron(p, at(2026, 4, 17, 9, 0))).toBe(true)
|
|
74
|
-
expect(matchesCron(p, at(2026, 4, 17, 9, 1))).toBe(false)
|
|
75
|
-
expect(matchesCron(p, at(2026, 4, 17, 10, 0))).toBe(false)
|
|
76
|
-
})
|
|
77
|
-
test('dow-only fires on that weekday', () => {
|
|
78
|
-
// 2026-04-17 is a Friday (dow=5).
|
|
79
|
-
const p = parseCron('0 0 * * 5')
|
|
80
|
-
expect(matchesCron(p, at(2026, 4, 17, 0, 0))).toBe(true)
|
|
81
|
-
expect(matchesCron(p, at(2026, 4, 18, 0, 0))).toBe(false)
|
|
82
|
-
})
|
|
83
|
-
test('dom+dow restricted => OR semantics (standard cron)', () => {
|
|
84
|
-
// Every Friday OR the 15th. 2026-04-17 = Friday, 2026-04-15 = 15th (Wed).
|
|
85
|
-
const p = parseCron('0 0 15 * 5')
|
|
86
|
-
expect(matchesCron(p, at(2026, 4, 17, 0, 0))).toBe(true) // Fri
|
|
87
|
-
expect(matchesCron(p, at(2026, 4, 15, 0, 0))).toBe(true) // 15th
|
|
88
|
-
expect(matchesCron(p, at(2026, 4, 16, 0, 0))).toBe(false)
|
|
89
|
-
})
|
|
90
|
-
})
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { expect, test } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
HEARTBEAT_PROMPT,
|
|
4
|
-
isHeartbeatContentEffectivelyEmpty,
|
|
5
|
-
resolveHeartbeatPrompt,
|
|
6
|
-
} from '../../src/runtime/auto-reply/heartbeat.ts'
|
|
7
|
-
|
|
8
|
-
test('resolveHeartbeatPrompt falls back to the canned prompt when raw is empty', () => {
|
|
9
|
-
expect(resolveHeartbeatPrompt()).toBe(HEARTBEAT_PROMPT)
|
|
10
|
-
expect(resolveHeartbeatPrompt('')).toBe(HEARTBEAT_PROMPT)
|
|
11
|
-
expect(resolveHeartbeatPrompt(' \n ')).toBe(HEARTBEAT_PROMPT)
|
|
12
|
-
expect(resolveHeartbeatPrompt(null)).toBe(HEARTBEAT_PROMPT)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
test('resolveHeartbeatPrompt preserves a custom non-empty prompt', () => {
|
|
16
|
-
expect(resolveHeartbeatPrompt('do a thing')).toBe('do a thing')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test('isHeartbeatContentEffectivelyEmpty treats comments/bullets as empty', () => {
|
|
20
|
-
expect(isHeartbeatContentEffectivelyEmpty('')).toBe(true)
|
|
21
|
-
expect(isHeartbeatContentEffectivelyEmpty('# only header\n\n## subheader\n')).toBe(true)
|
|
22
|
-
expect(isHeartbeatContentEffectivelyEmpty('- [ ]\n- [x]\n* [ ]\n+ \n')).toBe(true)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
test('isHeartbeatContentEffectivelyEmpty detects actionable content', () => {
|
|
26
|
-
expect(isHeartbeatContentEffectivelyEmpty('- [ ] check inbox')).toBe(false)
|
|
27
|
-
expect(isHeartbeatContentEffectivelyEmpty('follow up with user')).toBe(false)
|
|
28
|
-
expect(isHeartbeatContentEffectivelyEmpty('#TODO sneaky')).toBe(false) // not an ATX header (no space)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
test('isHeartbeatContentEffectivelyEmpty treats non-strings as non-empty (let the LLM decide)', () => {
|
|
32
|
-
expect(isHeartbeatContentEffectivelyEmpty(null)).toBe(false)
|
|
33
|
-
expect(isHeartbeatContentEffectivelyEmpty(undefined)).toBe(false)
|
|
34
|
-
})
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { existsSync, 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 { qmdBackend } from '../../src/runtime/memory/qmd.ts'
|
|
6
|
-
import type { MemoryBackend } from '../../src/runtime/memory/types.ts'
|
|
7
|
-
|
|
8
|
-
let root: string
|
|
9
|
-
let mem: MemoryBackend
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
root = mkdtempSync(join(tmpdir(), 'bazilion-mem-qmd-'))
|
|
13
|
-
mem = qmdBackend(root)
|
|
14
|
-
await mem.init()
|
|
15
|
-
})
|
|
16
|
-
afterEach(() => rmSync(root, { recursive: true, force: true }))
|
|
17
|
-
|
|
18
|
-
test('write then read returns the content', async () => {
|
|
19
|
-
await mem.write('notes.md', 'hello world')
|
|
20
|
-
const entry = await mem.read('notes.md')
|
|
21
|
-
expect(entry.content).toBe('hello world')
|
|
22
|
-
expect(entry.key).toBe('notes.md')
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
test('write to nested key creates directories', async () => {
|
|
26
|
-
await mem.write('daily/2026-04-11.md', 'today')
|
|
27
|
-
expect(existsSync(join(root, 'daily', '2026-04-11.md'))).toBe(true)
|
|
28
|
-
const entry = await mem.read('daily/2026-04-11.md')
|
|
29
|
-
expect(entry.content).toBe('today')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('read missing key throws', async () => {
|
|
33
|
-
await expect(mem.read('ghost.md')).rejects.toThrow(/not found/)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('list returns all entries sorted by key', async () => {
|
|
37
|
-
await mem.write('b.md', 'two')
|
|
38
|
-
await mem.write('a.md', 'one')
|
|
39
|
-
await mem.write('nested/c.md', 'three')
|
|
40
|
-
const all = await mem.list()
|
|
41
|
-
expect(all.map((e) => e.key)).toEqual(['a.md', 'b.md', 'nested/c.md'])
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
test('list ignores the .qmd-index.sqlite file', async () => {
|
|
45
|
-
await mem.write('a.md', 'one')
|
|
46
|
-
// After init(), the index file exists alongside the markdown
|
|
47
|
-
expect(existsSync(join(root, '.qmd-index.sqlite'))).toBe(true)
|
|
48
|
-
const all = await mem.list()
|
|
49
|
-
expect(all.map((e) => e.key)).toEqual(['a.md'])
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test('search returns matching entries via BM25', async () => {
|
|
53
|
-
await mem.write('colors.md', 'My favorite color is blue.')
|
|
54
|
-
await mem.write('food.md', 'I like pasta and pizza.')
|
|
55
|
-
await mem.write('other.md', 'unrelated content')
|
|
56
|
-
|
|
57
|
-
const hits = await mem.search('color')
|
|
58
|
-
expect(hits.length).toBeGreaterThanOrEqual(1)
|
|
59
|
-
expect(hits[0]?.key).toBe('colors.md')
|
|
60
|
-
expect(hits[0]?.snippet.toLowerCase()).toContain('color')
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
test('search scores more relevant documents higher', async () => {
|
|
64
|
-
await mem.write('a.md', 'pizza pizza pizza')
|
|
65
|
-
await mem.write('b.md', 'I once ate pizza years ago')
|
|
66
|
-
const hits = await mem.search('pizza')
|
|
67
|
-
expect(hits.length).toBeGreaterThanOrEqual(2)
|
|
68
|
-
// BM25 should rank the term-dense doc first.
|
|
69
|
-
expect(hits[0]?.key).toBe('a.md')
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test('remove deletes the entry and drops it from search', async () => {
|
|
73
|
-
await mem.write('temp.md', 'tmp content with pizza')
|
|
74
|
-
let hits = await mem.search('pizza')
|
|
75
|
-
expect(hits.length).toBe(1)
|
|
76
|
-
|
|
77
|
-
await mem.remove('temp.md')
|
|
78
|
-
expect(existsSync(join(root, 'temp.md'))).toBe(false)
|
|
79
|
-
|
|
80
|
-
hits = await mem.search('pizza')
|
|
81
|
-
expect(hits.length).toBe(0)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
test('rejects unsafe keys with path traversal', async () => {
|
|
85
|
-
await expect(mem.write('../../../etc/passwd', 'x')).rejects.toThrow(/unsafe/)
|
|
86
|
-
await expect(mem.write('/abs/path', 'x')).rejects.toThrow(/unsafe/)
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
test('persistence: a new backend instance sees previously written entries', async () => {
|
|
90
|
-
await mem.write('persist.md', 'survives restart')
|
|
91
|
-
const fresh = qmdBackend(root)
|
|
92
|
-
await fresh.init()
|
|
93
|
-
const entry = await fresh.read('persist.md')
|
|
94
|
-
expect(entry.content).toBe('survives restart')
|
|
95
|
-
const hits = await fresh.search('survives')
|
|
96
|
-
expect(hits.length).toBeGreaterThanOrEqual(1)
|
|
97
|
-
})
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { existsSync, 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 { filesBackend } from '../../src/runtime/memory/files.ts'
|
|
6
|
-
import type { MemoryBackend } from '../../src/runtime/memory/types.ts'
|
|
7
|
-
|
|
8
|
-
let root: string
|
|
9
|
-
let mem: MemoryBackend
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
root = mkdtempSync(join(tmpdir(), 'bazilion-mem-'))
|
|
13
|
-
mem = filesBackend(root)
|
|
14
|
-
await mem.init()
|
|
15
|
-
})
|
|
16
|
-
afterEach(() => rmSync(root, { recursive: true, force: true }))
|
|
17
|
-
|
|
18
|
-
test('write then read returns the content', async () => {
|
|
19
|
-
await mem.write('notes.md', 'hello world')
|
|
20
|
-
const entry = await mem.read('notes.md')
|
|
21
|
-
expect(entry.content).toBe('hello world')
|
|
22
|
-
expect(entry.key).toBe('notes.md')
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
test('write to nested key creates directories', async () => {
|
|
26
|
-
await mem.write('daily/2026-04-11.md', 'today')
|
|
27
|
-
expect(existsSync(join(root, 'daily', '2026-04-11.md'))).toBe(true)
|
|
28
|
-
const entry = await mem.read('daily/2026-04-11.md')
|
|
29
|
-
expect(entry.content).toBe('today')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('read missing key throws', async () => {
|
|
33
|
-
await expect(mem.read('ghost.md')).rejects.toThrow(/not found/)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('list returns all entries sorted by key', async () => {
|
|
37
|
-
await mem.write('b.md', 'two')
|
|
38
|
-
await mem.write('a.md', 'one')
|
|
39
|
-
await mem.write('nested/c.md', 'three')
|
|
40
|
-
const all = await mem.list()
|
|
41
|
-
expect(all.map((e) => e.key)).toEqual(['a.md', 'b.md', 'nested/c.md'])
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
test('search returns matching entries with snippets', async () => {
|
|
45
|
-
await mem.write('colors.md', 'My favorite color is blue.')
|
|
46
|
-
await mem.write('food.md', 'I like pasta and pizza.')
|
|
47
|
-
await mem.write('other.md', 'unrelated content')
|
|
48
|
-
|
|
49
|
-
const hits = await mem.search('color')
|
|
50
|
-
expect(hits.length).toBe(1)
|
|
51
|
-
expect(hits[0]?.key).toBe('colors.md')
|
|
52
|
-
expect(hits[0]?.snippet).toContain('color')
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test('search is case-insensitive', async () => {
|
|
56
|
-
await mem.write('x.md', 'BLUE is a color')
|
|
57
|
-
const hits = await mem.search('blue')
|
|
58
|
-
expect(hits.length).toBe(1)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('remove deletes the entry', async () => {
|
|
62
|
-
await mem.write('temp.md', 'tmp')
|
|
63
|
-
await mem.remove('temp.md')
|
|
64
|
-
expect(existsSync(join(root, 'temp.md'))).toBe(false)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('rejects unsafe keys with path traversal', async () => {
|
|
68
|
-
await expect(mem.write('../../../etc/passwd', 'x')).rejects.toThrow(/unsafe/)
|
|
69
|
-
await expect(mem.write('/abs/path', 'x')).rejects.toThrow(/unsafe/)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test('persistence: a new backend instance sees previously written entries', async () => {
|
|
73
|
-
await mem.write('persist.md', 'survives')
|
|
74
|
-
const fresh = filesBackend(root)
|
|
75
|
-
await fresh.init()
|
|
76
|
-
const entry = await fresh.read('persist.md')
|
|
77
|
-
expect(entry.content).toBe('survives')
|
|
78
|
-
})
|