bazilion 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/.understand-anything/.understandignore +25 -0
  2. package/.understand-anything/fingerprints.json +14267 -0
  3. package/.understand-anything/knowledge-graph.json +18128 -0
  4. package/.understand-anything/meta.json +6 -0
  5. package/CLAUDE.md +164 -0
  6. package/LICENSE +21 -0
  7. package/README.md +195 -0
  8. package/apps/cli/package.json +21 -0
  9. package/apps/cli/src/auth-file.ts +18 -0
  10. package/apps/cli/src/client.ts +37 -0
  11. package/apps/cli/src/columnize.ts +32 -0
  12. package/apps/cli/src/commands/agent.ts +574 -0
  13. package/apps/cli/src/commands/auth.ts +110 -0
  14. package/apps/cli/src/commands/backup.ts +135 -0
  15. package/apps/cli/src/commands/completion.ts +155 -0
  16. package/apps/cli/src/commands/config.ts +95 -0
  17. package/apps/cli/src/commands/doctor.ts +131 -0
  18. package/apps/cli/src/commands/group.ts +132 -0
  19. package/apps/cli/src/commands/inbox.ts +82 -0
  20. package/apps/cli/src/commands/login.ts +73 -0
  21. package/apps/cli/src/commands/memory.ts +106 -0
  22. package/apps/cli/src/commands/profile.ts +259 -0
  23. package/apps/cli/src/commands/provider.ts +170 -0
  24. package/apps/cli/src/commands/send.ts +24 -0
  25. package/apps/cli/src/commands/serve.ts +89 -0
  26. package/apps/cli/src/commands/skill.ts +120 -0
  27. package/apps/cli/src/commands/token.ts +148 -0
  28. package/apps/cli/src/commands/trigger.ts +129 -0
  29. package/apps/cli/src/commands/uninstall.ts +156 -0
  30. package/apps/cli/src/index.ts +196 -0
  31. package/apps/cli/src/paths.ts +12 -0
  32. package/apps/cli/test/agent.test.ts +213 -0
  33. package/apps/cli/test/backup.test.ts +95 -0
  34. package/apps/cli/test/chat.test.ts +539 -0
  35. package/apps/cli/test/columnize.test.ts +29 -0
  36. package/apps/cli/test/completion.test.ts +45 -0
  37. package/apps/cli/test/config-page.test.ts +151 -0
  38. package/apps/cli/test/group.test.ts +74 -0
  39. package/apps/cli/test/helpers.ts +70 -0
  40. package/apps/cli/test/inbox-autodeliver.test.ts +143 -0
  41. package/apps/cli/test/inbox.test.ts +147 -0
  42. package/apps/cli/test/memory.test.ts +69 -0
  43. package/apps/cli/test/profile.test.ts +110 -0
  44. package/apps/cli/test/send.test.ts +30 -0
  45. package/apps/cli/test/server-fixture.ts +212 -0
  46. package/apps/cli/test/session-head.test.ts +45 -0
  47. package/apps/cli/test/skill.test.ts +128 -0
  48. package/apps/cli/test/token.test.ts +109 -0
  49. package/apps/cli/test/trigger.test.ts +245 -0
  50. package/apps/cli/tsconfig.json +4 -0
  51. package/apps/daemon/package.json +31 -0
  52. package/apps/daemon/src/app.ts +41 -0
  53. package/apps/daemon/src/core/agent/archive.ts +8 -0
  54. package/apps/daemon/src/core/agent/delete.ts +30 -0
  55. package/apps/daemon/src/core/agent/resolve.ts +31 -0
  56. package/apps/daemon/src/core/agent/spawn.ts +95 -0
  57. package/apps/daemon/src/core/agent/unarchive.ts +11 -0
  58. package/apps/daemon/src/core/availableModels.ts +58 -0
  59. package/apps/daemon/src/core/db/client.ts +113 -0
  60. package/apps/daemon/src/core/db/migrate.ts +41 -0
  61. package/apps/daemon/src/core/db/migrations/0001_init.sql +179 -0
  62. package/apps/daemon/src/core/group/delete.ts +21 -0
  63. package/apps/daemon/src/core/group/register.ts +68 -0
  64. package/apps/daemon/src/core/index.ts +71 -0
  65. package/apps/daemon/src/core/paths.ts +56 -0
  66. package/apps/daemon/src/core/profile/create.ts +78 -0
  67. package/apps/daemon/src/core/profile/delete.ts +26 -0
  68. package/apps/daemon/src/core/profile/identity.ts +70 -0
  69. package/apps/daemon/src/core/profile/load.ts +45 -0
  70. package/apps/daemon/src/core/profile/seed.ts +74 -0
  71. package/apps/daemon/src/core/profile/templates.ts +60 -0
  72. package/apps/daemon/src/core/profile/update.ts +57 -0
  73. package/apps/daemon/src/core/profile/validate.ts +9 -0
  74. package/apps/daemon/src/core/repos/agents.ts +202 -0
  75. package/apps/daemon/src/core/repos/config.ts +78 -0
  76. package/apps/daemon/src/core/repos/groups.ts +55 -0
  77. package/apps/daemon/src/core/repos/messages.ts +127 -0
  78. package/apps/daemon/src/core/repos/profiles.ts +83 -0
  79. package/apps/daemon/src/core/repos/providerModels.ts +58 -0
  80. package/apps/daemon/src/core/repos/providerState.ts +37 -0
  81. package/apps/daemon/src/core/repos/secrets.ts +145 -0
  82. package/apps/daemon/src/core/repos/skillMeta.ts +49 -0
  83. package/apps/daemon/src/core/repos/triggers.ts +101 -0
  84. package/apps/daemon/src/core/repos/webTokens.ts +87 -0
  85. package/apps/daemon/src/core/secrets.ts +65 -0
  86. package/apps/daemon/src/core/services.ts +264 -0
  87. package/apps/daemon/src/core/skills/discover.ts +28 -0
  88. package/apps/daemon/src/core/skills/import.ts +136 -0
  89. package/apps/daemon/src/core/skills/parse.ts +52 -0
  90. package/apps/daemon/src/core/skills/resolve.ts +50 -0
  91. package/apps/daemon/src/index.ts +45 -0
  92. package/apps/daemon/src/lib/agent-cancel.ts +48 -0
  93. package/apps/daemon/src/lib/agent-id.ts +13 -0
  94. package/apps/daemon/src/lib/agent-turn.ts +56 -0
  95. package/apps/daemon/src/lib/api-key.ts +56 -0
  96. package/apps/daemon/src/lib/auth.ts +41 -0
  97. package/apps/daemon/src/lib/cron.ts +93 -0
  98. package/apps/daemon/src/lib/ctx.ts +80 -0
  99. package/apps/daemon/src/lib/messaging-host.ts +34 -0
  100. package/apps/daemon/src/lib/middleware-auth.ts +52 -0
  101. package/apps/daemon/src/lib/scheduler.ts +294 -0
  102. package/apps/daemon/src/routes/agents.ts +772 -0
  103. package/apps/daemon/src/routes/auth-login.ts +193 -0
  104. package/apps/daemon/src/routes/config.ts +267 -0
  105. package/apps/daemon/src/routes/groups.ts +133 -0
  106. package/apps/daemon/src/routes/messages.ts +29 -0
  107. package/apps/daemon/src/routes/misc.ts +239 -0
  108. package/apps/daemon/src/routes/profiles.ts +197 -0
  109. package/apps/daemon/src/routes/skills.ts +123 -0
  110. package/apps/daemon/src/routes/triggers.ts +29 -0
  111. package/apps/daemon/src/runtime/auth/openai-codex.ts +121 -0
  112. package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +31 -0
  113. package/apps/daemon/src/runtime/index.ts +77 -0
  114. package/apps/daemon/src/runtime/memory/files.ts +103 -0
  115. package/apps/daemon/src/runtime/memory/qmd.ts +152 -0
  116. package/apps/daemon/src/runtime/memory/types.ts +16 -0
  117. package/apps/daemon/src/runtime/pi/events.ts +173 -0
  118. package/apps/daemon/src/runtime/pi/session.ts +536 -0
  119. package/apps/daemon/src/runtime/pi/tools.ts +85 -0
  120. package/apps/daemon/src/runtime/providers/catalog.ts +145 -0
  121. package/apps/daemon/src/runtime/providers/pi-adapter.ts +272 -0
  122. package/apps/daemon/src/runtime/providers/registry.ts +374 -0
  123. package/apps/daemon/src/runtime/providers/retry.ts +176 -0
  124. package/apps/daemon/src/runtime/providers/types.ts +33 -0
  125. package/apps/daemon/src/runtime/session/prompt.ts +83 -0
  126. package/apps/daemon/src/runtime/tools/bootstrap.ts +22 -0
  127. package/apps/daemon/src/runtime/tools/home.ts +114 -0
  128. package/apps/daemon/src/runtime/tools/memory.ts +81 -0
  129. package/apps/daemon/src/runtime/tools/messaging.ts +127 -0
  130. package/apps/daemon/src/runtime/tools/registry.ts +29 -0
  131. package/apps/daemon/src/runtime/tools/types.ts +13 -0
  132. package/apps/daemon/src/runtime/tools/web-extract.ts +110 -0
  133. package/apps/daemon/src/runtime/tools/web-ssrf.ts +245 -0
  134. package/apps/daemon/src/runtime/tools/web.ts +273 -0
  135. package/apps/daemon/src/runtime/worker/entry.ts +221 -0
  136. package/apps/daemon/src/runtime/worker/ipc-protocol.ts +75 -0
  137. package/apps/daemon/src/runtime/worker/spawn.ts +249 -0
  138. package/apps/daemon/test/core/agents.test.ts +319 -0
  139. package/apps/daemon/test/core/available-models.test.ts +63 -0
  140. package/apps/daemon/test/core/config.test.ts +99 -0
  141. package/apps/daemon/test/core/groups.test.ts +63 -0
  142. package/apps/daemon/test/core/helpers.ts +50 -0
  143. package/apps/daemon/test/core/identity.test.ts +85 -0
  144. package/apps/daemon/test/core/migrations.test.ts +83 -0
  145. package/apps/daemon/test/core/profiles.test.ts +182 -0
  146. package/apps/daemon/test/core/provider-models.test.ts +57 -0
  147. package/apps/daemon/test/core/provider-state.test.ts +36 -0
  148. package/apps/daemon/test/core/skill-meta.test.ts +45 -0
  149. package/apps/daemon/test/core/skills.test.ts +271 -0
  150. package/apps/daemon/test/core/triggers.test.ts +182 -0
  151. package/apps/daemon/test/core/web-tokens.test.ts +79 -0
  152. package/apps/daemon/test/cron.test.ts +90 -0
  153. package/apps/daemon/test/runtime/heartbeat.test.ts +34 -0
  154. package/apps/daemon/test/runtime/memory-qmd.test.ts +97 -0
  155. package/apps/daemon/test/runtime/memory.test.ts +78 -0
  156. package/apps/daemon/test/runtime/messaging.test.ts +213 -0
  157. package/apps/daemon/test/runtime/mock-server.ts +65 -0
  158. package/apps/daemon/test/runtime/openai-codex-auth.test.ts +116 -0
  159. package/apps/daemon/test/runtime/providers.test.ts +306 -0
  160. package/apps/daemon/test/runtime/retry.test.ts +191 -0
  161. package/apps/daemon/test/runtime/session-head.test.ts +90 -0
  162. package/apps/daemon/test/runtime/tools-home.test.ts +102 -0
  163. package/apps/daemon/test/runtime/tools-web.test.ts +206 -0
  164. package/apps/daemon/tsconfig.json +4 -0
  165. package/apps/mobile/README.md +60 -0
  166. package/apps/mobile/app/_layout.tsx +58 -0
  167. package/apps/mobile/app/agents/[id]/chat.tsx +486 -0
  168. package/apps/mobile/app/agents/[id]/index.tsx +166 -0
  169. package/apps/mobile/app/agents/index.tsx +212 -0
  170. package/apps/mobile/app/index.tsx +21 -0
  171. package/apps/mobile/app/pair.tsx +226 -0
  172. package/apps/mobile/app/settings.tsx +419 -0
  173. package/apps/mobile/app.json +49 -0
  174. package/apps/mobile/assets/adaptive-icon.png +0 -0
  175. package/apps/mobile/assets/favicon.png +0 -0
  176. package/apps/mobile/assets/icon.png +0 -0
  177. package/apps/mobile/assets/splash-icon.png +0 -0
  178. package/apps/mobile/babel.config.js +6 -0
  179. package/apps/mobile/metro.config.js +28 -0
  180. package/apps/mobile/package.json +44 -0
  181. package/apps/mobile/src/auth.ts +66 -0
  182. package/apps/mobile/src/pair-url.ts +48 -0
  183. package/apps/mobile/src/theme-context.tsx +88 -0
  184. package/apps/mobile/src/theme.ts +135 -0
  185. package/apps/mobile/test/pair-url.test.ts +46 -0
  186. package/apps/mobile/tsconfig.json +23 -0
  187. package/apps/web/components.json +25 -0
  188. package/apps/web/package.json +39 -0
  189. package/apps/web/public/baziu.svg +8 -0
  190. package/apps/web/src/components/AgentTabs.tsx +45 -0
  191. package/apps/web/src/components/BaziuLogo.tsx +21 -0
  192. package/apps/web/src/components/ChatPane.tsx +1033 -0
  193. package/apps/web/src/components/ConfigTabs.tsx +29 -0
  194. package/apps/web/src/components/CopyButton.tsx +68 -0
  195. package/apps/web/src/components/CreateGroupDialog.tsx +127 -0
  196. package/apps/web/src/components/FieldRow.tsx +94 -0
  197. package/apps/web/src/components/Footer.tsx +10 -0
  198. package/apps/web/src/components/PawIcon.tsx +15 -0
  199. package/apps/web/src/components/Sidebar.tsx +287 -0
  200. package/apps/web/src/components/SpawnDialog.tsx +129 -0
  201. package/apps/web/src/components/ThemeToggle.tsx +75 -0
  202. package/apps/web/src/components/TopNav.tsx +34 -0
  203. package/apps/web/src/components/ui/button.tsx +67 -0
  204. package/apps/web/src/components/ui/card.tsx +103 -0
  205. package/apps/web/src/components/ui/checkbox.tsx +31 -0
  206. package/apps/web/src/components/ui/dialog.tsx +168 -0
  207. package/apps/web/src/components/ui/input.tsx +19 -0
  208. package/apps/web/src/components/ui/label.tsx +22 -0
  209. package/apps/web/src/components/ui/radio-group.tsx +44 -0
  210. package/apps/web/src/components/ui/select.tsx +192 -0
  211. package/apps/web/src/components/ui/separator.tsx +26 -0
  212. package/apps/web/src/components/ui/table.tsx +116 -0
  213. package/apps/web/src/components/ui/tabs.tsx +88 -0
  214. package/apps/web/src/components/ui/textarea.tsx +18 -0
  215. package/apps/web/src/lib/auth.ts +50 -0
  216. package/apps/web/src/lib/daemon-client.ts +34 -0
  217. package/apps/web/src/lib/md.ts +45 -0
  218. package/apps/web/src/lib/utils.ts +6 -0
  219. package/apps/web/src/lib/wire-constants.ts +27 -0
  220. package/apps/web/src/routeTree.gen.ts +408 -0
  221. package/apps/web/src/router.tsx +20 -0
  222. package/apps/web/src/routes/__root.tsx +123 -0
  223. package/apps/web/src/routes/agents/$id/inbox.tsx +207 -0
  224. package/apps/web/src/routes/agents/$id/index.tsx +527 -0
  225. package/apps/web/src/routes/agents/$id/triggers.tsx +239 -0
  226. package/apps/web/src/routes/agents/index.tsx +265 -0
  227. package/apps/web/src/routes/api/$.ts +88 -0
  228. package/apps/web/src/routes/config/index.tsx +315 -0
  229. package/apps/web/src/routes/config/services.tsx +49 -0
  230. package/apps/web/src/routes/config/tokens.tsx +192 -0
  231. package/apps/web/src/routes/groups/$id/index.tsx +153 -0
  232. package/apps/web/src/routes/groups/$id/memory.tsx +321 -0
  233. package/apps/web/src/routes/groups/index.tsx +191 -0
  234. package/apps/web/src/routes/index.tsx +133 -0
  235. package/apps/web/src/routes/login.tsx +63 -0
  236. package/apps/web/src/routes/profiles/$id.tsx +549 -0
  237. package/apps/web/src/routes/profiles/index.tsx +458 -0
  238. package/apps/web/src/routes/skills/index.tsx +297 -0
  239. package/apps/web/src/routes/welcome.tsx +61 -0
  240. package/apps/web/src/styles.css +449 -0
  241. package/apps/web/tsconfig.json +25 -0
  242. package/apps/web/vite.config.ts +25 -0
  243. package/biome.json +23 -0
  244. package/docs/agent-engine.md +219 -0
  245. package/docs/architecture.md +627 -0
  246. package/docs/backlog/README.md +42 -0
  247. package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +125 -0
  248. package/docs/openclaw-reference.md +210 -0
  249. package/package.json +38 -0
  250. package/packages/api-types/package.json +11 -0
  251. package/packages/api-types/src/entities.ts +146 -0
  252. package/packages/api-types/src/events.ts +55 -0
  253. package/packages/api-types/src/index.ts +488 -0
  254. package/packages/api-types/src/memory.ts +15 -0
  255. package/packages/client/package.json +13 -0
  256. package/packages/client/src/index.ts +117 -0
  257. package/pnpm-workspace.yaml +3 -0
  258. package/tsconfig.base.json +23 -0
  259. package/tsconfig.json +11 -0
  260. package/vitest.config.ts +22 -0
@@ -0,0 +1,527 @@
1
+ import { ApiClientError } from '@bazilion/client'
2
+ import type {
3
+ Group,
4
+ ProviderMessage,
5
+ ReasoningLevel,
6
+ ResolvedAgent,
7
+ SessionHeadResponse,
8
+ SkillInfo,
9
+ } from '@bazilion/api-types'
10
+ import { createFileRoute, redirect, useRouter } from '@tanstack/react-router'
11
+ import { createServerFn } from '@tanstack/react-start'
12
+ import { useState } from 'react'
13
+ import { AgentTabs } from '../../../components/AgentTabs'
14
+ import { ChatPane } from '../../../components/ChatPane'
15
+ import { CopyButton } from '../../../components/CopyButton'
16
+ import { daemonClient } from '../../../lib/daemon-client'
17
+ import { REASONING_LEVELS } from '../../../lib/wire-constants'
18
+
19
+ interface ModelGroup {
20
+ provider: string
21
+ models: string[]
22
+ }
23
+
24
+ interface AvailableModelsResponse {
25
+ groups: ModelGroup[]
26
+ }
27
+
28
+ interface AgentView {
29
+ resolved: ResolvedAgent
30
+ initialMessages: ProviderMessage[]
31
+ sessionHead: SessionHeadResponse
32
+ groups: Group[]
33
+ skills: SkillInfo[]
34
+ modelGroups: ModelGroup[]
35
+ }
36
+
37
+ const fetchAgent = createServerFn({ method: 'POST' })
38
+ .inputValidator((d: { id: string }) => d)
39
+ .handler(async ({ data }): Promise<AgentView | null> => {
40
+ const c = daemonClient()
41
+ let resolved: ResolvedAgent
42
+ try {
43
+ resolved = await c.get<ResolvedAgent>(`/api/agents/${encodeURIComponent(data.id)}`)
44
+ } catch (err) {
45
+ if (err instanceof ApiClientError && err.status === 404) return null
46
+ throw err
47
+ }
48
+ const [msgs, head, groups, skills, models] = await Promise.all([
49
+ c.get<{ messages: ProviderMessage[] }>(
50
+ `/api/agents/${encodeURIComponent(resolved.agent.id)}/sessions/messages`,
51
+ ),
52
+ c.get<SessionHeadResponse>(
53
+ `/api/agents/${encodeURIComponent(resolved.agent.id)}/sessions/head`,
54
+ ),
55
+ c.get<Group[]>('/api/groups'),
56
+ c.get<SkillInfo[]>('/api/skills'),
57
+ c.get<AvailableModelsResponse>('/api/config/available-models'),
58
+ ])
59
+ return {
60
+ resolved,
61
+ initialMessages: msgs.messages,
62
+ sessionHead: head,
63
+ groups,
64
+ skills,
65
+ modelGroups: models.groups,
66
+ }
67
+ })
68
+
69
+ export const Route = createFileRoute('/agents/$id/')({
70
+ loader: async ({ params }) => {
71
+ const data = await fetchAgent({ data: { id: params.id } })
72
+ if (!data) throw redirect({ to: '/agents' })
73
+ return data
74
+ },
75
+ component: AgentDetailPage,
76
+ })
77
+
78
+ function AgentDetailPage() {
79
+ const { resolved, initialMessages, sessionHead, groups, skills, modelGroups } =
80
+ Route.useLoaderData()
81
+ const router = useRouter()
82
+
83
+ async function archive() {
84
+ if (!confirm(`archive "${resolved.agent.name}"? (reversible)`)) return
85
+ await fetch(`/api/agents/${resolved.agent.id}/archive`, { method: 'POST' })
86
+ await router.invalidate()
87
+ }
88
+ async function unarchive() {
89
+ await fetch(`/api/agents/${resolved.agent.id}/unarchive`, { method: 'POST' })
90
+ await router.invalidate()
91
+ }
92
+ async function del() {
93
+ const msg =
94
+ `Permanently delete agent "${resolved.agent.name}"?\n\n` +
95
+ `This removes the DB rows (messages, triggers, skill attachments) AND the agent's on-disk directory (memory, templates, state, sessions). This cannot be undone.`
96
+ if (!confirm(msg)) return
97
+ const res = await fetch(`/api/agents/${resolved.agent.id}`, { method: 'DELETE' })
98
+ if (res.ok || res.status === 204) {
99
+ window.location.assign('/agents')
100
+ } else {
101
+ alert(`failed: ${res.statusText}`)
102
+ }
103
+ }
104
+
105
+ return (
106
+ <div>
107
+ <header className="mb-8">
108
+ <h1>{resolved.agent.name}</h1>
109
+ <div className="mt-2 flex flex-wrap items-center gap-x-5 gap-y-2">
110
+ <Tag>
111
+ <span className="px-1.5 font-mono text-[0.95em] text-mocha select-all">
112
+ {resolved.agent.id}
113
+ </span>
114
+ <CopyButton
115
+ value={resolved.agent.id}
116
+ ariaLabel="Copy agent UUID"
117
+ title="Copy UUID to clipboard"
118
+ />
119
+ </Tag>
120
+ <Tag>
121
+ status: <span className="ml-1">{resolved.agent.status}</span>
122
+ </Tag>
123
+ <Tag>
124
+ profile: <code className="ml-1 bg-transparent px-0">{resolved.profile.id}</code>
125
+ </Tag>
126
+ <Tag>
127
+ group:{' '}
128
+ <a
129
+ href={`/groups/${encodeURIComponent(resolved.group.id)}`}
130
+ className="ml-1 bg-transparent px-0 font-mono underline"
131
+ >
132
+ {resolved.group.id}
133
+ </a>{' '}
134
+ ·{' '}
135
+ <a
136
+ href={`/groups/${encodeURIComponent(resolved.group.id)}/memory`}
137
+ className="ml-0.5 underline"
138
+ >
139
+ shared memory →
140
+ </a>
141
+ </Tag>
142
+ <Tag>
143
+ model: <code className="ml-1 bg-transparent px-0">{resolved.model}</code>
144
+ </Tag>
145
+ <Tag>
146
+ reasoning:{' '}
147
+ <code className="ml-1 bg-transparent px-0">{resolved.reasoningLevel}</code>
148
+ </Tag>
149
+ </div>
150
+
151
+ <div className="mt-3 flex flex-wrap gap-2">
152
+ {resolved.agent.status === 'archived' ? (
153
+ <button type="button" onClick={unarchive} className="ghost-btn">
154
+ unarchive
155
+ </button>
156
+ ) : (
157
+ <button type="button" onClick={archive} className="ghost-btn">
158
+ archive
159
+ </button>
160
+ )}
161
+ <button
162
+ type="button"
163
+ onClick={del}
164
+ className="ghost-btn border-[rgba(196,135,138,0.4)] text-[#9B3D3D] hover:bg-[rgba(196,135,138,0.08)]"
165
+ >
166
+ delete permanently
167
+ </button>
168
+ </div>
169
+
170
+ <SettingsDetails
171
+ agentId={resolved.agent.id}
172
+ currentModelOverride={resolved.agent.modelOverride ?? null}
173
+ profileDefaultModel={resolved.profile.defaultModel}
174
+ currentReasoning={resolved.reasoningLevel}
175
+ modelGroups={modelGroups}
176
+ />
177
+ </header>
178
+
179
+ {resolved.agent.status === 'archived' && (
180
+ <div className="mb-4 rounded-md border border-frost bg-fawn/35 px-4 py-2 text-[0.9em] text-chocolate">
181
+ <strong className="text-[#7a4e2a]">This agent is archived.</strong> It's hidden from
182
+ the default agent list; use <em>unarchive</em> above to bring it back, or{' '}
183
+ <em>delete permanently</em> to remove its data entirely.
184
+ </div>
185
+ )}
186
+
187
+ <AgentTabs
188
+ agentId={resolved.agent.id}
189
+ active="chat"
190
+ archived={resolved.agent.status === 'archived'}
191
+ />
192
+
193
+ <SectionTitle>Chat</SectionTitle>
194
+ <div className="h-[70vh]">
195
+ <ChatPane
196
+ agentId={resolved.agent.id}
197
+ agentName={resolved.agent.name}
198
+ initialMessages={initialMessages}
199
+ initialSessionHead={sessionHead}
200
+ />
201
+ </div>
202
+ <p className="mt-2 text-[0.82em] text-mocha-light">
203
+ chat history persists in bazilion across navigation and restarts.
204
+ </p>
205
+
206
+ <section className="mt-10">
207
+ <SectionTitle>Group</SectionTitle>
208
+ <p className="text-[0.9em] text-mocha-light">
209
+ Current group: <code>{resolved.group.id}</code> ({resolved.group.name}) —{' '}
210
+ <code>{resolved.group.path}</code>.{' '}
211
+ <a href={`/groups/${resolved.group.id}`}>view / edit USER.md</a>
212
+ </p>
213
+ <MoveGroupForm
214
+ agentId={resolved.agent.id}
215
+ currentGroupId={resolved.group.id}
216
+ groups={groups}
217
+ />
218
+ </section>
219
+
220
+ <section className="mt-10">
221
+ <SectionTitle>Skills</SectionTitle>
222
+ <SkillsTable
223
+ agentId={resolved.agent.id}
224
+ attached={new Set(resolved.skills)}
225
+ all={skills}
226
+ />
227
+ </section>
228
+ </div>
229
+ )
230
+ }
231
+
232
+ function SectionTitle({ children }: { children: React.ReactNode }) {
233
+ return (
234
+ <h3 className="mb-3 font-body text-[0.85em] font-semibold uppercase tracking-wider text-mocha-light">
235
+ {children}
236
+ </h3>
237
+ )
238
+ }
239
+
240
+ function Tag({ children }: { children: React.ReactNode }) {
241
+ return (
242
+ <span className="inline-flex items-center gap-1 rounded-full border border-frost bg-ivory px-3 py-0.5 text-[0.84em] text-mocha-light">
243
+ {children}
244
+ </span>
245
+ )
246
+ }
247
+
248
+ function SettingsDetails({
249
+ agentId,
250
+ currentModelOverride,
251
+ profileDefaultModel,
252
+ currentReasoning,
253
+ modelGroups,
254
+ }: {
255
+ agentId: string
256
+ currentModelOverride: string | null
257
+ profileDefaultModel: string
258
+ currentReasoning: ReasoningLevel
259
+ modelGroups: ModelGroup[]
260
+ }) {
261
+ const router = useRouter()
262
+ const [modelOverride, setModelOverride] = useState<string>(currentModelOverride ?? '')
263
+ const [reasoning, setReasoning] = useState<ReasoningLevel>(currentReasoning)
264
+ const [saving, setSaving] = useState(false)
265
+ const [savedFlash, setSavedFlash] = useState(false)
266
+ const [error, setError] = useState<string | null>(null)
267
+
268
+ const currentInGroups =
269
+ currentModelOverride !== null &&
270
+ modelGroups.some((g) =>
271
+ g.models.some((m) => `${g.provider}:${m}` === currentModelOverride),
272
+ )
273
+
274
+ async function save(e: React.FormEvent<HTMLFormElement>) {
275
+ e.preventDefault()
276
+ setSaving(true)
277
+ setError(null)
278
+ try {
279
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}`, {
280
+ method: 'PATCH',
281
+ headers: { 'content-type': 'application/json' },
282
+ body: JSON.stringify({
283
+ modelOverride: modelOverride === '' ? null : modelOverride,
284
+ reasoningLevel: reasoning,
285
+ }),
286
+ })
287
+ if (!res.ok) {
288
+ const body = (await res.json().catch(() => null)) as { error?: string } | null
289
+ throw new Error(body?.error ?? res.statusText)
290
+ }
291
+ setSavedFlash(true)
292
+ setTimeout(() => setSavedFlash(false), 1500)
293
+ await router.invalidate()
294
+ } catch (err) {
295
+ setError((err as Error).message)
296
+ } finally {
297
+ setSaving(false)
298
+ }
299
+ }
300
+
301
+ return (
302
+ <details className="mt-3 text-[0.9em]">
303
+ <summary className="cursor-pointer text-mocha-light">settings</summary>
304
+ <form
305
+ onSubmit={save}
306
+ className="mt-2 flex flex-wrap items-end gap-3 border-t border-dashed border-frost py-3"
307
+ >
308
+ <label className="flex flex-col gap-1 text-[0.85em] text-mocha-light">
309
+ model override
310
+ <select
311
+ name="modelOverride"
312
+ value={modelOverride}
313
+ onChange={(e) => setModelOverride(e.target.value)}
314
+ className="min-w-[16rem] rounded-sm border border-frost bg-snow px-2 py-1.5"
315
+ >
316
+ <option value="">inherit from profile ({profileDefaultModel})</option>
317
+ {currentModelOverride !== null && !currentInGroups && (
318
+ <option value={currentModelOverride}>
319
+ {currentModelOverride} (not in curated list)
320
+ </option>
321
+ )}
322
+ {modelGroups.map((g) => (
323
+ <optgroup key={g.provider} label={g.provider}>
324
+ {g.models.map((m) => {
325
+ const value = `${g.provider}:${m}`
326
+ return (
327
+ <option key={value} value={value}>
328
+ {value}
329
+ </option>
330
+ )
331
+ })}
332
+ </optgroup>
333
+ ))}
334
+ </select>
335
+ </label>
336
+ <label className="flex flex-col gap-1 text-[0.85em] text-mocha-light">
337
+ reasoning level
338
+ <select
339
+ name="reasoningLevel"
340
+ value={reasoning}
341
+ onChange={(e) => setReasoning(e.target.value as ReasoningLevel)}
342
+ className="min-w-[16rem] rounded-sm border border-frost bg-snow px-2 py-1.5"
343
+ >
344
+ {REASONING_LEVELS.map((lvl) => (
345
+ <option key={lvl} value={lvl}>
346
+ {lvl}
347
+ </option>
348
+ ))}
349
+ </select>
350
+ </label>
351
+ <button type="submit" disabled={saving}>
352
+ {saving ? 'saving…' : 'save'}
353
+ </button>
354
+ {savedFlash && <span className="text-[0.85em] text-[#3b7a3b]">saved ✓</span>}
355
+ {error && <span className="text-[0.85em] text-[#9B3D3D]">{error}</span>}
356
+ </form>
357
+ </details>
358
+ )
359
+ }
360
+
361
+ function MoveGroupForm({
362
+ agentId,
363
+ currentGroupId,
364
+ groups,
365
+ }: {
366
+ agentId: string
367
+ currentGroupId: string
368
+ groups: Group[]
369
+ }) {
370
+ const router = useRouter()
371
+ const [groupId, setGroupId] = useState(currentGroupId)
372
+ const [moving, setMoving] = useState(false)
373
+ const [err, setErr] = useState<string | null>(null)
374
+
375
+ async function move(e: React.FormEvent<HTMLFormElement>) {
376
+ e.preventDefault()
377
+ if (groupId === currentGroupId) return
378
+ setMoving(true)
379
+ setErr(null)
380
+ try {
381
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/group`, {
382
+ method: 'PATCH',
383
+ headers: { 'content-type': 'application/json' },
384
+ body: JSON.stringify({ groupId }),
385
+ })
386
+ if (!res.ok) {
387
+ const body = (await res.json().catch(() => null)) as { error?: string } | null
388
+ throw new Error(body?.error ?? res.statusText)
389
+ }
390
+ await router.invalidate()
391
+ } catch (e2) {
392
+ setErr((e2 as Error).message)
393
+ } finally {
394
+ setMoving(false)
395
+ }
396
+ }
397
+
398
+ return (
399
+ <form onSubmit={move} className="mt-2 flex flex-wrap items-end gap-2">
400
+ <label className="flex flex-col gap-1 text-[0.85em] text-mocha-light">
401
+ move to:
402
+ <select
403
+ value={groupId}
404
+ onChange={(e) => setGroupId(e.target.value)}
405
+ className="min-w-[18rem] rounded-sm border border-frost bg-snow px-2 py-1.5"
406
+ >
407
+ {groups.map((g) => (
408
+ <option key={g.id} value={g.id}>
409
+ {g.id} — {g.path}
410
+ </option>
411
+ ))}
412
+ </select>
413
+ </label>
414
+ <button type="submit" disabled={moving || groupId === currentGroupId}>
415
+ {moving ? 'moving…' : 'move'}
416
+ </button>
417
+ {err && <span className="text-[0.85em] text-[#9B3D3D]">{err}</span>}
418
+ </form>
419
+ )
420
+ }
421
+
422
+ function SkillsTable({
423
+ agentId,
424
+ attached,
425
+ all,
426
+ }: {
427
+ agentId: string
428
+ attached: Set<string>
429
+ all: SkillInfo[]
430
+ }) {
431
+ const router = useRouter()
432
+ const [pending, setPending] = useState<string | null>(null)
433
+ const [err, setErr] = useState<string | null>(null)
434
+
435
+ async function attach(name: string) {
436
+ setPending(name)
437
+ setErr(null)
438
+ try {
439
+ const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/skills`, {
440
+ method: 'POST',
441
+ headers: { 'content-type': 'application/json' },
442
+ body: JSON.stringify({ skill: name }),
443
+ })
444
+ if (!res.ok) {
445
+ const body = (await res.json().catch(() => null)) as { error?: string } | null
446
+ throw new Error(body?.error ?? res.statusText)
447
+ }
448
+ await router.invalidate()
449
+ } catch (e) {
450
+ setErr((e as Error).message)
451
+ } finally {
452
+ setPending(null)
453
+ }
454
+ }
455
+ async function detach(name: string) {
456
+ setPending(name)
457
+ setErr(null)
458
+ try {
459
+ const res = await fetch(
460
+ `/api/agents/${encodeURIComponent(agentId)}/skills/${encodeURIComponent(name)}`,
461
+ { method: 'DELETE' },
462
+ )
463
+ if (!res.ok && res.status !== 204) {
464
+ const body = (await res.json().catch(() => null)) as { error?: string } | null
465
+ throw new Error(body?.error ?? res.statusText)
466
+ }
467
+ await router.invalidate()
468
+ } catch (e) {
469
+ setErr((e as Error).message)
470
+ } finally {
471
+ setPending(null)
472
+ }
473
+ }
474
+
475
+ if (all.length === 0) {
476
+ return <p className="text-[0.9em] text-mocha-light">no skills installed</p>
477
+ }
478
+ return (
479
+ <>
480
+ {err && <p className="mb-2 text-[0.85em] text-[#9B3D3D]">{err}</p>}
481
+ <table>
482
+ <thead>
483
+ <tr>
484
+ <th>name</th>
485
+ <th>description</th>
486
+ <th />
487
+ </tr>
488
+ </thead>
489
+ <tbody>
490
+ {all.map((s) => {
491
+ const isAttached = attached.has(s.name)
492
+ const busy = pending === s.name
493
+ return (
494
+ <tr key={s.name}>
495
+ <td>
496
+ <code>{s.name}</code>
497
+ </td>
498
+ <td>{s.description || <span className="text-mocha-light">—</span>}</td>
499
+ <td>
500
+ {isAttached ? (
501
+ <button
502
+ type="button"
503
+ className="ghost-btn"
504
+ disabled={busy}
505
+ onClick={() => detach(s.name)}
506
+ >
507
+ {busy ? '…' : 'detach'}
508
+ </button>
509
+ ) : (
510
+ <button
511
+ type="button"
512
+ className="ghost-btn"
513
+ disabled={busy}
514
+ onClick={() => attach(s.name)}
515
+ >
516
+ {busy ? '…' : 'attach'}
517
+ </button>
518
+ )}
519
+ </td>
520
+ </tr>
521
+ )
522
+ })}
523
+ </tbody>
524
+ </table>
525
+ </>
526
+ )
527
+ }