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,191 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, vi } from 'vitest'
|
|
2
|
-
import { isRetryableError, withRetry } from '../../src/runtime/providers/retry.ts'
|
|
3
|
-
import type {
|
|
4
|
-
Provider,
|
|
5
|
-
ProviderRequest,
|
|
6
|
-
ProviderResponse,
|
|
7
|
-
} from '../../src/runtime/providers/types.ts'
|
|
8
|
-
|
|
9
|
-
function okResponse(text = 'ok'): ProviderResponse {
|
|
10
|
-
return { content: text, toolCalls: [], stopReason: 'stop' }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function scripted(outcomes: Array<() => Promise<ProviderResponse>>): Provider {
|
|
14
|
-
let i = 0
|
|
15
|
-
return {
|
|
16
|
-
name: 'mock',
|
|
17
|
-
async chat(_req: ProviderRequest) {
|
|
18
|
-
const fn = outcomes[i++]
|
|
19
|
-
if (!fn) throw new Error('scripted provider: out of outcomes')
|
|
20
|
-
return await fn()
|
|
21
|
-
},
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const baseReq: ProviderRequest = { model: 'm', messages: [] }
|
|
26
|
-
|
|
27
|
-
describe('isRetryableError', () => {
|
|
28
|
-
test('allows transient 5xx / rate-limit / socket markers', () => {
|
|
29
|
-
expect(isRetryableError(new Error('Codex error: {"type":"server_error"}'))).toBe(true)
|
|
30
|
-
expect(isRetryableError(new Error('rate_limit_exceeded'))).toBe(true)
|
|
31
|
-
expect(isRetryableError(new Error('overloaded_error: too many requests'))).toBe(true)
|
|
32
|
-
expect(isRetryableError(new Error('fetch failed'))).toBe(true)
|
|
33
|
-
expect(isRetryableError(new Error('ECONNRESET'))).toBe(true)
|
|
34
|
-
expect(isRetryableError(new Error('status 503 service_unavailable'))).toBe(true)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('blocks auth / invalid-request / context-overflow / 4xx', () => {
|
|
38
|
-
expect(isRetryableError(new Error('invalid_api_key'))).toBe(false)
|
|
39
|
-
expect(isRetryableError(new Error('authentication failed'))).toBe(false)
|
|
40
|
-
expect(isRetryableError(new Error('status 401 unauthorized'))).toBe(false)
|
|
41
|
-
expect(isRetryableError(new Error('context_length_exceeded'))).toBe(false)
|
|
42
|
-
expect(isRetryableError(new Error('model_not_found'))).toBe(false)
|
|
43
|
-
expect(isRetryableError(new Error('insufficient_quota'))).toBe(false)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
test('deny-list wins when both markers appear', () => {
|
|
47
|
-
// A 429 that also mentions authentication: don't retry — the auth
|
|
48
|
-
// failure is the load-bearing signal.
|
|
49
|
-
expect(isRetryableError(new Error('status 429 after authentication failure'))).toBe(false)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test('unknown errors are not retried by default', () => {
|
|
53
|
-
expect(isRetryableError(new Error('random client bug'))).toBe(false)
|
|
54
|
-
expect(isRetryableError(new Error(''))).toBe(false)
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
describe('withRetry', () => {
|
|
59
|
-
test('passes through on first-try success', async () => {
|
|
60
|
-
const p = withRetry(scripted([() => Promise.resolve(okResponse('hi'))]))
|
|
61
|
-
const res = await p.chat(baseReq)
|
|
62
|
-
expect(res.content).toBe('hi')
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test('recovers after a transient server_error', async () => {
|
|
66
|
-
const onRetry = vi.fn()
|
|
67
|
-
const p = withRetry(
|
|
68
|
-
scripted([
|
|
69
|
-
() => Promise.reject(new Error('Codex error: server_error')),
|
|
70
|
-
() => Promise.resolve(okResponse('recovered')),
|
|
71
|
-
]),
|
|
72
|
-
{ initialDelayMs: 1, onRetry },
|
|
73
|
-
)
|
|
74
|
-
const res = await p.chat(baseReq)
|
|
75
|
-
expect(res.content).toBe('recovered')
|
|
76
|
-
expect(onRetry).toHaveBeenCalledTimes(1)
|
|
77
|
-
expect(onRetry.mock.calls[0]?.[0]?.attempt).toBe(1)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
test('gives up after maxRetries + 1 attempts', async () => {
|
|
81
|
-
let calls = 0
|
|
82
|
-
const p = withRetry(
|
|
83
|
-
{
|
|
84
|
-
name: 'mock',
|
|
85
|
-
async chat() {
|
|
86
|
-
calls++
|
|
87
|
-
throw new Error('rate_limit')
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
{ maxRetries: 2, initialDelayMs: 1 },
|
|
91
|
-
)
|
|
92
|
-
await expect(p.chat(baseReq)).rejects.toThrow(/rate_limit/)
|
|
93
|
-
expect(calls).toBe(3)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
test('does not retry non-retryable errors', async () => {
|
|
97
|
-
let calls = 0
|
|
98
|
-
const p = withRetry(
|
|
99
|
-
{
|
|
100
|
-
name: 'mock',
|
|
101
|
-
async chat() {
|
|
102
|
-
calls++
|
|
103
|
-
throw new Error('invalid_api_key')
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
{ maxRetries: 5, initialDelayMs: 1 },
|
|
107
|
-
)
|
|
108
|
-
await expect(p.chat(baseReq)).rejects.toThrow(/invalid_api_key/)
|
|
109
|
-
expect(calls).toBe(1)
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
test('does not retry after text has streamed', async () => {
|
|
113
|
-
// First attempt streams a token, then errors — retry would duplicate the
|
|
114
|
-
// token in the UI, so the wrapper must surface the error as-is.
|
|
115
|
-
let calls = 0
|
|
116
|
-
const p = withRetry(
|
|
117
|
-
{
|
|
118
|
-
name: 'mock',
|
|
119
|
-
async chat(req) {
|
|
120
|
-
calls++
|
|
121
|
-
req.onDelta?.('partial...')
|
|
122
|
-
throw new Error('server_error: stream broke mid-way')
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
{ maxRetries: 3, initialDelayMs: 1 },
|
|
126
|
-
)
|
|
127
|
-
const deltas: string[] = []
|
|
128
|
-
await expect(p.chat({ ...baseReq, onDelta: (d) => deltas.push(d) })).rejects.toThrow(
|
|
129
|
-
/server_error/,
|
|
130
|
-
)
|
|
131
|
-
expect(calls).toBe(1)
|
|
132
|
-
expect(deltas).toEqual(['partial...'])
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
test('aborts immediately if the caller signal is already aborted', async () => {
|
|
136
|
-
const ac = new AbortController()
|
|
137
|
-
ac.abort()
|
|
138
|
-
let calls = 0
|
|
139
|
-
const p = withRetry(
|
|
140
|
-
{
|
|
141
|
-
name: 'mock',
|
|
142
|
-
async chat() {
|
|
143
|
-
calls++
|
|
144
|
-
throw new Error('server_error')
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
{ initialDelayMs: 1 },
|
|
148
|
-
)
|
|
149
|
-
await expect(p.chat({ ...baseReq, signal: ac.signal })).rejects.toThrow()
|
|
150
|
-
expect(calls).toBe(1) // one attempt, then abort check skips the retry
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
test('abort during backoff surfaces the original error', async () => {
|
|
154
|
-
const ac = new AbortController()
|
|
155
|
-
const p = withRetry(
|
|
156
|
-
{
|
|
157
|
-
name: 'mock',
|
|
158
|
-
async chat() {
|
|
159
|
-
throw new Error('server_error: upstream blew up')
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
{ maxRetries: 3, initialDelayMs: 100 },
|
|
163
|
-
)
|
|
164
|
-
// Abort 10ms into the 100ms backoff.
|
|
165
|
-
setTimeout(() => ac.abort(), 10)
|
|
166
|
-
await expect(p.chat({ ...baseReq, signal: ac.signal })).rejects.toThrow(
|
|
167
|
-
/server_error: upstream blew up/,
|
|
168
|
-
)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
test('backoff grows exponentially up to maxDelayMs', async () => {
|
|
172
|
-
const delays: number[] = []
|
|
173
|
-
const p = withRetry(
|
|
174
|
-
{
|
|
175
|
-
name: 'mock',
|
|
176
|
-
async chat() {
|
|
177
|
-
throw new Error('server_error')
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
maxRetries: 3,
|
|
182
|
-
initialDelayMs: 100,
|
|
183
|
-
maxDelayMs: 250,
|
|
184
|
-
onRetry: (info) => delays.push(info.delayMs),
|
|
185
|
-
},
|
|
186
|
-
)
|
|
187
|
-
await expect(p.chat(baseReq)).rejects.toThrow()
|
|
188
|
-
// 100, 200, min(400, 250) = 250
|
|
189
|
-
expect(delays).toEqual([100, 200, 250])
|
|
190
|
-
})
|
|
191
|
-
})
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, mkdtempSync, rmSync, utimesSync, 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 { resolvePaths } from '../../src/core/index.ts'
|
|
6
|
-
import { loadSessionHead } from '../../src/runtime/pi/session.ts'
|
|
7
|
-
|
|
8
|
-
// Minimal ResolvedAgent shape loadSessionHead actually reads — we don't need
|
|
9
|
-
// a real spawn + provider wiring for this unit test, only the agent.id (used
|
|
10
|
-
// to resolve the session dir). loadSessionHead itself doesn't touch the
|
|
11
|
-
// group, but the type still requires it.
|
|
12
|
-
function fakeAgent(id: string, dir: string) {
|
|
13
|
-
return {
|
|
14
|
-
agent: { id, dir, name: id, status: 'idle' as const },
|
|
15
|
-
group: { id: 'g', name: 'g', path: dir, userMd: '', createdAt: 0 },
|
|
16
|
-
skills: [],
|
|
17
|
-
} as unknown as Parameters<typeof loadSessionHead>[0]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
let home: string
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
home = mkdtempSync(join(tmpdir(), 'bazilion-session-head-'))
|
|
23
|
-
})
|
|
24
|
-
afterEach(() => rmSync(home, { recursive: true, force: true }))
|
|
25
|
-
|
|
26
|
-
test('returns {file:null, size:0} when no sessions dir exists (fresh agent)', () => {
|
|
27
|
-
const paths = resolvePaths(home)
|
|
28
|
-
mkdirSync(paths.agentsDir, { recursive: true })
|
|
29
|
-
mkdirSync(join(paths.agentsDir, 'agent-a'), { recursive: true })
|
|
30
|
-
|
|
31
|
-
const head = loadSessionHead(fakeAgent('agent-a', join(paths.agentsDir, 'agent-a')), paths)
|
|
32
|
-
expect(head).toEqual({ file: null, size: 0 })
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test('returns {file:null, size:0} when sessions dir is empty', () => {
|
|
36
|
-
const paths = resolvePaths(home)
|
|
37
|
-
mkdirSync(join(paths.agentsDir, 'agent-b', 'sessions'), { recursive: true })
|
|
38
|
-
|
|
39
|
-
const head = loadSessionHead(fakeAgent('agent-b', join(paths.agentsDir, 'agent-b')), paths)
|
|
40
|
-
expect(head).toEqual({ file: null, size: 0 })
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('returns the newest .jsonl basename + its byte size', () => {
|
|
44
|
-
const paths = resolvePaths(home)
|
|
45
|
-
const sessionDir = join(paths.agentsDir, 'agent-c', 'sessions')
|
|
46
|
-
mkdirSync(sessionDir, { recursive: true })
|
|
47
|
-
|
|
48
|
-
writeFileSync(join(sessionDir, 'old.jsonl'), 'old entry\n', 'utf8')
|
|
49
|
-
const now = Date.now() / 1000
|
|
50
|
-
// Backdate 'old' so 'newer' is unambiguously most-recent regardless of the
|
|
51
|
-
// order mkdir stamps mtime.
|
|
52
|
-
utimesSync(join(sessionDir, 'old.jsonl'), now - 60, now - 60)
|
|
53
|
-
|
|
54
|
-
const newerContent = '{"type":"session"}\n{"type":"message"}\n'
|
|
55
|
-
writeFileSync(join(sessionDir, 'newer.jsonl'), newerContent, 'utf8')
|
|
56
|
-
|
|
57
|
-
const head = loadSessionHead(fakeAgent('agent-c', join(paths.agentsDir, 'agent-c')), paths)
|
|
58
|
-
expect(head.file).toBe('newer.jsonl')
|
|
59
|
-
expect(head.size).toBe(Buffer.byteLength(newerContent, 'utf8'))
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test('ignores non-jsonl files in the sessions dir', () => {
|
|
63
|
-
const paths = resolvePaths(home)
|
|
64
|
-
const sessionDir = join(paths.agentsDir, 'agent-d', 'sessions')
|
|
65
|
-
mkdirSync(sessionDir, { recursive: true })
|
|
66
|
-
|
|
67
|
-
writeFileSync(join(sessionDir, 'README.md'), 'not a session\n', 'utf8')
|
|
68
|
-
writeFileSync(join(sessionDir, '.DS_Store'), 'mac junk', 'utf8')
|
|
69
|
-
writeFileSync(join(sessionDir, 'real.jsonl'), 'entry\n', 'utf8')
|
|
70
|
-
|
|
71
|
-
const head = loadSessionHead(fakeAgent('agent-d', join(paths.agentsDir, 'agent-d')), paths)
|
|
72
|
-
expect(head.file).toBe('real.jsonl')
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test('size grows monotonically as entries are appended — matches poll signal', () => {
|
|
76
|
-
const paths = resolvePaths(home)
|
|
77
|
-
const sessionDir = join(paths.agentsDir, 'agent-e', 'sessions')
|
|
78
|
-
mkdirSync(sessionDir, { recursive: true })
|
|
79
|
-
const file = join(sessionDir, 'session.jsonl')
|
|
80
|
-
writeFileSync(file, 'a\n', 'utf8')
|
|
81
|
-
|
|
82
|
-
const head1 = loadSessionHead(fakeAgent('agent-e', join(paths.agentsDir, 'agent-e')), paths)
|
|
83
|
-
// simulate pi appending another entry — ordinary append, no rename
|
|
84
|
-
writeFileSync(file, 'a\nb\n', 'utf8')
|
|
85
|
-
const head2 = loadSessionHead(fakeAgent('agent-e', join(paths.agentsDir, 'agent-e')), paths)
|
|
86
|
-
|
|
87
|
-
expect(head1.file).toBe('session.jsonl')
|
|
88
|
-
expect(head2.file).toBe('session.jsonl')
|
|
89
|
-
expect(head2.size).toBeGreaterThan(head1.size)
|
|
90
|
-
})
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { mkdtempSync, readFileSync, 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 { homeTools } from '../../src/runtime/tools/home.ts'
|
|
6
|
-
import { createToolRegistry } from '../../src/runtime/tools/registry.ts'
|
|
7
|
-
|
|
8
|
-
let agentDir: string
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
agentDir = mkdtempSync(join(tmpdir(), 'bazilion-home-'))
|
|
12
|
-
writeFileSync(join(agentDir, 'IDENTITY.md'), '# IDENTITY\n\n- Name:\n', 'utf8')
|
|
13
|
-
writeFileSync(join(agentDir, 'SOUL.md'), '# SOUL\n\nhelpful and honest.\n', 'utf8')
|
|
14
|
-
writeFileSync(join(agentDir, 'BOOTSTRAP.md'), '# BOOTSTRAP\n\nfirst run.\n', 'utf8')
|
|
15
|
-
})
|
|
16
|
-
afterEach(() => rmSync(agentDir, { recursive: true, force: true }))
|
|
17
|
-
|
|
18
|
-
test('home_read returns the file contents', async () => {
|
|
19
|
-
const tools = createToolRegistry(homeTools(agentDir))
|
|
20
|
-
const out = await tools.invoke('home_read', JSON.stringify({ file: 'IDENTITY.md' }))
|
|
21
|
-
expect(out).toContain('# IDENTITY')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
test('home_read rejects files outside the whitelist', async () => {
|
|
25
|
-
const tools = createToolRegistry(homeTools(agentDir))
|
|
26
|
-
await expect(
|
|
27
|
-
tools.invoke('home_read', JSON.stringify({ file: '../etc/passwd' })),
|
|
28
|
-
).rejects.toThrow(/must be one of/)
|
|
29
|
-
await expect(
|
|
30
|
-
tools.invoke('home_read', JSON.stringify({ file: 'memory/notes.md' })),
|
|
31
|
-
).rejects.toThrow(/must be one of/)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('home_read surfaces a helpful error if the file is missing', async () => {
|
|
35
|
-
const tools = createToolRegistry(homeTools(agentDir))
|
|
36
|
-
// TOOLS.md is whitelisted but not seeded in beforeEach — should raise a read error
|
|
37
|
-
await expect(tools.invoke('home_read', JSON.stringify({ file: 'TOOLS.md' }))).rejects.toThrow(
|
|
38
|
-
/could not read TOOLS\.md/,
|
|
39
|
-
)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('home_write replaces file contents and is readable back', async () => {
|
|
43
|
-
const tools = createToolRegistry(homeTools(agentDir))
|
|
44
|
-
const body = '# IDENTITY\n\n- Name: Molty\n- Emoji: 🦞\n'
|
|
45
|
-
const w = await tools.invoke('home_write', JSON.stringify({ file: 'IDENTITY.md', content: body }))
|
|
46
|
-
expect(w).toMatch(/wrote IDENTITY\.md/)
|
|
47
|
-
expect(readFileSync(join(agentDir, 'IDENTITY.md'), 'utf8')).toBe(body)
|
|
48
|
-
const r = await tools.invoke('home_read', JSON.stringify({ file: 'IDENTITY.md' }))
|
|
49
|
-
expect(r).toBe(body)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test('home_write refuses to touch BOOTSTRAP.md (lifecycle belongs to bootstrap_done)', async () => {
|
|
53
|
-
const tools = createToolRegistry(homeTools(agentDir))
|
|
54
|
-
await expect(
|
|
55
|
-
tools.invoke('home_write', JSON.stringify({ file: 'BOOTSTRAP.md', content: 'nope' })),
|
|
56
|
-
).rejects.toThrow(/must be one of/)
|
|
57
|
-
// Seeded BOOTSTRAP.md content is intact
|
|
58
|
-
expect(readFileSync(join(agentDir, 'BOOTSTRAP.md'), 'utf8')).toContain('first run')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('home_write rejects files outside the whitelist', async () => {
|
|
62
|
-
const tools = createToolRegistry(homeTools(agentDir))
|
|
63
|
-
await expect(
|
|
64
|
-
tools.invoke(
|
|
65
|
-
'home_write',
|
|
66
|
-
JSON.stringify({ file: '../escape.md', content: 'should not leak' }),
|
|
67
|
-
),
|
|
68
|
-
).rejects.toThrow(/must be one of/)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('home_list reports seeded files with byte sizes', async () => {
|
|
72
|
-
const tools = createToolRegistry(homeTools(agentDir))
|
|
73
|
-
const out = await tools.invoke('home_list', '{}')
|
|
74
|
-
expect(out).toMatch(/IDENTITY\.md \(\d+b\)/)
|
|
75
|
-
expect(out).toMatch(/SOUL\.md \(\d+b\)/)
|
|
76
|
-
expect(out).toMatch(/BOOTSTRAP\.md \(\d+b\)/)
|
|
77
|
-
// files we never created should not appear
|
|
78
|
-
expect(out).not.toMatch(/AGENTS\.md/)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
test('home tools are scoped to the provided agentDir — sibling agents do not collide', async () => {
|
|
82
|
-
const other = mkdtempSync(join(tmpdir(), 'bazilion-home-other-'))
|
|
83
|
-
try {
|
|
84
|
-
writeFileSync(join(other, 'IDENTITY.md'), 'sibling original\n', 'utf8')
|
|
85
|
-
|
|
86
|
-
const a = createToolRegistry(homeTools(agentDir))
|
|
87
|
-
const b = createToolRegistry(homeTools(other))
|
|
88
|
-
|
|
89
|
-
await a.invoke(
|
|
90
|
-
'home_write',
|
|
91
|
-
JSON.stringify({ file: 'IDENTITY.md', content: 'agent-a writes its own\n' }),
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
expect(readFileSync(join(agentDir, 'IDENTITY.md'), 'utf8')).toBe('agent-a writes its own\n')
|
|
95
|
-
expect(readFileSync(join(other, 'IDENTITY.md'), 'utf8')).toBe('sibling original\n')
|
|
96
|
-
|
|
97
|
-
const bRead = await b.invoke('home_read', JSON.stringify({ file: 'IDENTITY.md' }))
|
|
98
|
-
expect(bRead).toBe('sibling original\n')
|
|
99
|
-
} finally {
|
|
100
|
-
rmSync(other, { recursive: true, force: true })
|
|
101
|
-
}
|
|
102
|
-
})
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { afterEach, expect, test } from 'vitest'
|
|
2
|
-
import { createToolRegistry } from '../../src/runtime/tools/registry.ts'
|
|
3
|
-
import { webTools } from '../../src/runtime/tools/web.ts'
|
|
4
|
-
import { type MockServer, startMockServer } from './mock-server.ts'
|
|
5
|
-
|
|
6
|
-
let server: MockServer | null = null
|
|
7
|
-
afterEach(async () => {
|
|
8
|
-
await server?.stop()
|
|
9
|
-
server = null
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
async function startMock(handler: (req: Request) => Response | Promise<Response>): Promise<string> {
|
|
13
|
-
server = await startMockServer(handler)
|
|
14
|
-
return server.url
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
test('web_fetch fetches a plain text URL', async () => {
|
|
18
|
-
const base = await startMock(
|
|
19
|
-
() => new Response('hello plain', { headers: { 'content-type': 'text/plain' } }),
|
|
20
|
-
)
|
|
21
|
-
const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
|
|
22
|
-
const result = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/doc` }))
|
|
23
|
-
expect(result).toContain('hello plain')
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test('web_fetch extracts readable content from HTML (strips scripts/nav/footer)', async () => {
|
|
27
|
-
const html = `<html><head><title>Test Doc</title><script>evil()</script><style>.x{}</style></head>
|
|
28
|
-
<body>
|
|
29
|
-
<nav>skip nav</nav>
|
|
30
|
-
<article>
|
|
31
|
-
<h1>The Real Article</h1>
|
|
32
|
-
<p>This is the primary body of the article and it is long enough to give Readability something to score. The fox jumps over the lazy dog. Repeat repeat repeat.</p>
|
|
33
|
-
<p>A second paragraph of genuine content to satisfy any content-length thresholds.</p>
|
|
34
|
-
</article>
|
|
35
|
-
<footer>skip footer</footer>
|
|
36
|
-
</body></html>`
|
|
37
|
-
const base = await startMock(
|
|
38
|
-
() => new Response(html, { headers: { 'content-type': 'text/html' } }),
|
|
39
|
-
)
|
|
40
|
-
const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
|
|
41
|
-
const result = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/page` }))
|
|
42
|
-
expect(result).toContain('primary body of the article')
|
|
43
|
-
expect(result).not.toContain('evil()')
|
|
44
|
-
expect(result).not.toContain('<script')
|
|
45
|
-
expect(result).not.toContain('skip nav')
|
|
46
|
-
expect(result).not.toContain('skip footer')
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('web_fetch includes title as markdown heading', async () => {
|
|
50
|
-
const html = `<html><head><title>My Awesome Post</title></head>
|
|
51
|
-
<body><article><h1>My Awesome Post</h1>
|
|
52
|
-
<p>Body text that is long enough to trigger Readability scoring here, filler filler filler filler.</p>
|
|
53
|
-
<p>Another solid paragraph so Readability has plenty to work with.</p>
|
|
54
|
-
</article></body></html>`
|
|
55
|
-
const base = await startMock(
|
|
56
|
-
() => new Response(html, { headers: { 'content-type': 'text/html' } }),
|
|
57
|
-
)
|
|
58
|
-
const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
|
|
59
|
-
const result = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/post` }))
|
|
60
|
-
expect(result).toMatch(/^#\s+My Awesome Post/)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
test('web_fetch pretty-prints JSON responses', async () => {
|
|
64
|
-
const base = await startMock(
|
|
65
|
-
() => new Response('{"a":1,"b":[2,3]}', { headers: { 'content-type': 'application/json' } }),
|
|
66
|
-
)
|
|
67
|
-
const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
|
|
68
|
-
const result = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/data.json` }))
|
|
69
|
-
expect(result).toContain('"a": 1')
|
|
70
|
-
expect(result).toContain('"b": [')
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test('web_fetch truncates at max_length', async () => {
|
|
74
|
-
const base = await startMock(
|
|
75
|
-
() => new Response('a'.repeat(500), { headers: { 'content-type': 'text/plain' } }),
|
|
76
|
-
)
|
|
77
|
-
const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
|
|
78
|
-
const result = await tools.invoke(
|
|
79
|
-
'web_fetch',
|
|
80
|
-
JSON.stringify({ url: `${base}/big`, max_length: 100 }),
|
|
81
|
-
)
|
|
82
|
-
expect(result).toContain('[truncated at 100 chars]')
|
|
83
|
-
expect(result.length).toBeLessThan(200)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
test('web_fetch throws on non-2xx', async () => {
|
|
87
|
-
const base = await startMock(() => new Response('nope', { status: 404 }))
|
|
88
|
-
const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
|
|
89
|
-
await expect(
|
|
90
|
-
tools.invoke('web_fetch', JSON.stringify({ url: `${base}/missing` })),
|
|
91
|
-
).rejects.toThrow(/404/)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
test('web_fetch caches results per (url, extract_mode)', async () => {
|
|
95
|
-
let hits = 0
|
|
96
|
-
const base = await startMock(() => {
|
|
97
|
-
hits += 1
|
|
98
|
-
return new Response(
|
|
99
|
-
'<html><body><article><p>Cached content here, enough words to pass threshold filler filler.</p></article></body></html>',
|
|
100
|
-
{
|
|
101
|
-
headers: { 'content-type': 'text/html' },
|
|
102
|
-
},
|
|
103
|
-
)
|
|
104
|
-
})
|
|
105
|
-
const tools = createToolRegistry(webTools({ fetchImpl: fetch, allowPrivate: true }))
|
|
106
|
-
const first = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/cached` }))
|
|
107
|
-
const second = await tools.invoke('web_fetch', JSON.stringify({ url: `${base}/cached` }))
|
|
108
|
-
expect(first).toBe(second)
|
|
109
|
-
expect(hits).toBe(1)
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
test('web_fetch blocks private IP literals by default', async () => {
|
|
113
|
-
const tools = createToolRegistry(webTools())
|
|
114
|
-
await expect(
|
|
115
|
-
tools.invoke('web_fetch', JSON.stringify({ url: 'http://127.0.0.1/' })),
|
|
116
|
-
).rejects.toThrow(/private/i)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
test('web_fetch blocks localhost by default', async () => {
|
|
120
|
-
const tools = createToolRegistry(webTools())
|
|
121
|
-
await expect(
|
|
122
|
-
tools.invoke('web_fetch', JSON.stringify({ url: 'http://localhost/' })),
|
|
123
|
-
).rejects.toThrow(/Blocked hostname/i)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
test('web_fetch blocks non-http(s) schemes', async () => {
|
|
127
|
-
const tools = createToolRegistry(webTools({ allowPrivate: true }))
|
|
128
|
-
await expect(
|
|
129
|
-
tools.invoke('web_fetch', JSON.stringify({ url: 'file:///etc/passwd' })),
|
|
130
|
-
).rejects.toThrow(/http or https/i)
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
test('web_search tools are registered', () => {
|
|
134
|
-
const tools = createToolRegistry(webTools())
|
|
135
|
-
expect(tools.has('web_search')).toBe(true)
|
|
136
|
-
expect(tools.has('web_fetch')).toBe(true)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
test('web_search rejects empty query', async () => {
|
|
140
|
-
const tools = createToolRegistry(webTools())
|
|
141
|
-
await expect(tools.invoke('web_search', JSON.stringify({ query: '' }))).rejects.toThrow(
|
|
142
|
-
/query is required/,
|
|
143
|
-
)
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
test('web_search errors when no backend is configured', async () => {
|
|
147
|
-
const tools = createToolRegistry(webTools({ env: {} }))
|
|
148
|
-
await expect(tools.invoke('web_search', JSON.stringify({ query: 'test' }))).rejects.toThrow(
|
|
149
|
-
/no search backend configured/,
|
|
150
|
-
)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
test('web_search uses Brave when BRAVE_API_KEY is set', async () => {
|
|
154
|
-
let capturedHeaders = null as Headers | null
|
|
155
|
-
const base = await startMock((req) => {
|
|
156
|
-
capturedHeaders = req.headers
|
|
157
|
-
return new Response(
|
|
158
|
-
JSON.stringify({
|
|
159
|
-
web: {
|
|
160
|
-
results: [
|
|
161
|
-
{
|
|
162
|
-
title: 'Weather Krakow',
|
|
163
|
-
url: 'https://weather.example.com/krakow',
|
|
164
|
-
description: 'Sunny, 22°C',
|
|
165
|
-
},
|
|
166
|
-
],
|
|
167
|
-
},
|
|
168
|
-
}),
|
|
169
|
-
{ headers: { 'content-type': 'application/json' } },
|
|
170
|
-
)
|
|
171
|
-
})
|
|
172
|
-
const tools = createToolRegistry(
|
|
173
|
-
webTools({
|
|
174
|
-
fetchImpl: async (input, init) => {
|
|
175
|
-
const url =
|
|
176
|
-
typeof input === 'string' ? input : input instanceof URL ? input.href : input.url
|
|
177
|
-
const redirected = url.replace('https://api.search.brave.com', base)
|
|
178
|
-
return fetch(redirected, init)
|
|
179
|
-
},
|
|
180
|
-
env: { BRAVE_API_KEY: 'test-key' },
|
|
181
|
-
}),
|
|
182
|
-
)
|
|
183
|
-
const result = await tools.invoke('web_search', JSON.stringify({ query: 'weather krakow' }))
|
|
184
|
-
expect(result).toContain('Weather Krakow')
|
|
185
|
-
expect(result).toContain('Sunny')
|
|
186
|
-
expect(result).toContain('weather.example.com')
|
|
187
|
-
expect(capturedHeaders?.get('x-subscription-token')).toBe('test-key')
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
test('web_search uses SearXNG when SEARXNG_URL is set', async () => {
|
|
191
|
-
const base = await startMock(
|
|
192
|
-
() =>
|
|
193
|
-
new Response(
|
|
194
|
-
JSON.stringify({
|
|
195
|
-
results: [
|
|
196
|
-
{ title: 'News', url: 'https://news.example.com', content: 'Latest headlines' },
|
|
197
|
-
],
|
|
198
|
-
}),
|
|
199
|
-
{ headers: { 'content-type': 'application/json' } },
|
|
200
|
-
),
|
|
201
|
-
)
|
|
202
|
-
const tools = createToolRegistry(webTools({ env: { SEARXNG_URL: base } }))
|
|
203
|
-
const result = await tools.invoke('web_search', JSON.stringify({ query: 'news' }))
|
|
204
|
-
expect(result).toContain('News')
|
|
205
|
-
expect(result).toContain('Latest headlines')
|
|
206
|
-
})
|
package/apps/mobile/README.md
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# @bazilion/mobile
|
|
2
|
-
|
|
3
|
-
Expo (SDK 54) + Expo Router 6 + React 19 + RN 0.81 + new architecture. Pairs to a running `bazilion` daemon over LAN/Tailscale/VPN via a QR code minted on the server.
|
|
4
|
-
|
|
5
|
-
## First run
|
|
6
|
-
|
|
7
|
-
1. **On the server**: expose the daemon on your LAN.
|
|
8
|
-
```sh
|
|
9
|
-
bazilion serve --host 0.0.0.0
|
|
10
|
-
```
|
|
11
|
-
The daemon binds `127.0.0.1:4321` by default and prints a loud warning when
|
|
12
|
-
you bind it beyond loopback — TLS is your responsibility (Tailscale handles
|
|
13
|
-
it for personal networks; reverse-proxy with TLS for anything else).
|
|
14
|
-
2. **On the server**: mint a pairing token.
|
|
15
|
-
```sh
|
|
16
|
-
bazilion token create phone --qr
|
|
17
|
-
```
|
|
18
|
-
A QR encoding `bazilion://pair?server=<url>&token=<t>` prints in the terminal.
|
|
19
|
-
3. **On the phone**: install [Expo Go](https://expo.dev/go) and make sure it's on the same network as the server (or Tailscale, etc.).
|
|
20
|
-
4. **On your dev machine**:
|
|
21
|
-
```sh
|
|
22
|
-
pnpm --filter @bazilion/mobile start
|
|
23
|
-
```
|
|
24
|
-
Scan the QR the Expo CLI prints with Expo Go → the app loads → grant camera access → point the camera at the **pairing** QR from step 2.
|
|
25
|
-
5. The app verifies the token against `/api/health`, saves `server` + `token` into `expo-secure-store`, and lands on the agents list.
|
|
26
|
-
|
|
27
|
-
## Manual pairing
|
|
28
|
-
|
|
29
|
-
If the camera flow doesn't work (remote testing, wrong Expo Go version, etc.), tap "Paste URL instead" and paste the `bazilion://pair?…` URL the CLI printed. Same verification path.
|
|
30
|
-
|
|
31
|
-
## Commands
|
|
32
|
-
|
|
33
|
-
- `pnpm --filter @bazilion/mobile start` — Expo dev server (reads the Metro/Babel config).
|
|
34
|
-
- `pnpm --filter @bazilion/mobile typecheck` — TypeScript check over the mobile tree only.
|
|
35
|
-
- Root `pnpm test` picks up `apps/mobile/test/**/*.test.ts` — currently the `pair-url` parser suite.
|
|
36
|
-
|
|
37
|
-
## Layout
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
app/
|
|
41
|
-
_layout.tsx root stack + StatusBar
|
|
42
|
-
index.tsx loads SecureStore → redirects /pair or /agents
|
|
43
|
-
pair.tsx camera QR scan + manual-paste fallback + verify + save
|
|
44
|
-
settings.tsx server URL + unpair
|
|
45
|
-
agents/
|
|
46
|
-
index.tsx FlatList of agents (pull-to-refresh, unpair header, 401 → /pair)
|
|
47
|
-
[id]/
|
|
48
|
-
index.tsx detail: name, status, model, profile, group, skills
|
|
49
|
-
chat.tsx NDJSON streaming chat screen
|
|
50
|
-
src/
|
|
51
|
-
auth.ts SecureStore wrapper + verifyCredentials + clientFor()
|
|
52
|
-
pair-url.ts pure TS URL parser (vitest-tested)
|
|
53
|
-
theme.ts Baziu palette + spacing tokens
|
|
54
|
-
test/
|
|
55
|
-
pair-url.test.ts
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Status
|
|
59
|
-
|
|
60
|
-
Pairing, agents list, chat (NDJSON streaming), and settings ship end-to-end. Inbox + triggers screens are the next mobile work — paused for now while focus is on the web app.
|