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.
Files changed (265) hide show
  1. package/README.md +53 -152
  2. package/dist/cli.js +2730 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/daemon.js +5888 -0
  5. package/dist/daemon.js.map +1 -0
  6. package/dist/worker.js +1830 -0
  7. package/dist/worker.js.map +1 -0
  8. package/package.json +57 -28
  9. package/.understand-anything/.understandignore +0 -25
  10. package/.understand-anything/fingerprints.json +0 -14267
  11. package/.understand-anything/knowledge-graph.json +0 -18128
  12. package/.understand-anything/meta.json +0 -6
  13. package/CLAUDE.md +0 -164
  14. package/apps/cli/package.json +0 -21
  15. package/apps/cli/src/auth-file.ts +0 -18
  16. package/apps/cli/src/client.ts +0 -37
  17. package/apps/cli/src/columnize.ts +0 -32
  18. package/apps/cli/src/commands/agent.ts +0 -574
  19. package/apps/cli/src/commands/auth.ts +0 -110
  20. package/apps/cli/src/commands/backup.ts +0 -135
  21. package/apps/cli/src/commands/completion.ts +0 -155
  22. package/apps/cli/src/commands/config.ts +0 -95
  23. package/apps/cli/src/commands/doctor.ts +0 -131
  24. package/apps/cli/src/commands/group.ts +0 -132
  25. package/apps/cli/src/commands/inbox.ts +0 -82
  26. package/apps/cli/src/commands/login.ts +0 -73
  27. package/apps/cli/src/commands/memory.ts +0 -106
  28. package/apps/cli/src/commands/profile.ts +0 -259
  29. package/apps/cli/src/commands/provider.ts +0 -170
  30. package/apps/cli/src/commands/send.ts +0 -24
  31. package/apps/cli/src/commands/serve.ts +0 -89
  32. package/apps/cli/src/commands/skill.ts +0 -120
  33. package/apps/cli/src/commands/token.ts +0 -148
  34. package/apps/cli/src/commands/trigger.ts +0 -129
  35. package/apps/cli/src/commands/uninstall.ts +0 -156
  36. package/apps/cli/src/index.ts +0 -196
  37. package/apps/cli/src/paths.ts +0 -12
  38. package/apps/cli/test/agent.test.ts +0 -213
  39. package/apps/cli/test/backup.test.ts +0 -95
  40. package/apps/cli/test/chat.test.ts +0 -539
  41. package/apps/cli/test/columnize.test.ts +0 -29
  42. package/apps/cli/test/completion.test.ts +0 -45
  43. package/apps/cli/test/config-page.test.ts +0 -151
  44. package/apps/cli/test/group.test.ts +0 -74
  45. package/apps/cli/test/helpers.ts +0 -70
  46. package/apps/cli/test/inbox-autodeliver.test.ts +0 -143
  47. package/apps/cli/test/inbox.test.ts +0 -147
  48. package/apps/cli/test/memory.test.ts +0 -69
  49. package/apps/cli/test/profile.test.ts +0 -110
  50. package/apps/cli/test/send.test.ts +0 -30
  51. package/apps/cli/test/server-fixture.ts +0 -212
  52. package/apps/cli/test/session-head.test.ts +0 -45
  53. package/apps/cli/test/skill.test.ts +0 -128
  54. package/apps/cli/test/token.test.ts +0 -109
  55. package/apps/cli/test/trigger.test.ts +0 -245
  56. package/apps/cli/tsconfig.json +0 -4
  57. package/apps/daemon/package.json +0 -31
  58. package/apps/daemon/src/app.ts +0 -41
  59. package/apps/daemon/src/core/agent/archive.ts +0 -8
  60. package/apps/daemon/src/core/agent/delete.ts +0 -30
  61. package/apps/daemon/src/core/agent/resolve.ts +0 -31
  62. package/apps/daemon/src/core/agent/spawn.ts +0 -95
  63. package/apps/daemon/src/core/agent/unarchive.ts +0 -11
  64. package/apps/daemon/src/core/availableModels.ts +0 -58
  65. package/apps/daemon/src/core/db/client.ts +0 -113
  66. package/apps/daemon/src/core/db/migrate.ts +0 -41
  67. package/apps/daemon/src/core/db/migrations/0001_init.sql +0 -179
  68. package/apps/daemon/src/core/group/delete.ts +0 -21
  69. package/apps/daemon/src/core/group/register.ts +0 -68
  70. package/apps/daemon/src/core/index.ts +0 -71
  71. package/apps/daemon/src/core/paths.ts +0 -56
  72. package/apps/daemon/src/core/profile/create.ts +0 -78
  73. package/apps/daemon/src/core/profile/delete.ts +0 -26
  74. package/apps/daemon/src/core/profile/identity.ts +0 -70
  75. package/apps/daemon/src/core/profile/load.ts +0 -45
  76. package/apps/daemon/src/core/profile/seed.ts +0 -74
  77. package/apps/daemon/src/core/profile/templates.ts +0 -60
  78. package/apps/daemon/src/core/profile/update.ts +0 -57
  79. package/apps/daemon/src/core/profile/validate.ts +0 -9
  80. package/apps/daemon/src/core/repos/agents.ts +0 -202
  81. package/apps/daemon/src/core/repos/config.ts +0 -78
  82. package/apps/daemon/src/core/repos/groups.ts +0 -55
  83. package/apps/daemon/src/core/repos/messages.ts +0 -127
  84. package/apps/daemon/src/core/repos/profiles.ts +0 -83
  85. package/apps/daemon/src/core/repos/providerModels.ts +0 -58
  86. package/apps/daemon/src/core/repos/providerState.ts +0 -37
  87. package/apps/daemon/src/core/repos/secrets.ts +0 -145
  88. package/apps/daemon/src/core/repos/skillMeta.ts +0 -49
  89. package/apps/daemon/src/core/repos/triggers.ts +0 -101
  90. package/apps/daemon/src/core/repos/webTokens.ts +0 -87
  91. package/apps/daemon/src/core/secrets.ts +0 -65
  92. package/apps/daemon/src/core/services.ts +0 -264
  93. package/apps/daemon/src/core/skills/discover.ts +0 -28
  94. package/apps/daemon/src/core/skills/import.ts +0 -136
  95. package/apps/daemon/src/core/skills/parse.ts +0 -52
  96. package/apps/daemon/src/core/skills/resolve.ts +0 -50
  97. package/apps/daemon/src/index.ts +0 -45
  98. package/apps/daemon/src/lib/agent-cancel.ts +0 -48
  99. package/apps/daemon/src/lib/agent-id.ts +0 -13
  100. package/apps/daemon/src/lib/agent-turn.ts +0 -56
  101. package/apps/daemon/src/lib/api-key.ts +0 -56
  102. package/apps/daemon/src/lib/auth.ts +0 -41
  103. package/apps/daemon/src/lib/cron.ts +0 -93
  104. package/apps/daemon/src/lib/ctx.ts +0 -80
  105. package/apps/daemon/src/lib/messaging-host.ts +0 -34
  106. package/apps/daemon/src/lib/middleware-auth.ts +0 -52
  107. package/apps/daemon/src/lib/scheduler.ts +0 -294
  108. package/apps/daemon/src/routes/agents.ts +0 -772
  109. package/apps/daemon/src/routes/auth-login.ts +0 -193
  110. package/apps/daemon/src/routes/config.ts +0 -267
  111. package/apps/daemon/src/routes/groups.ts +0 -133
  112. package/apps/daemon/src/routes/messages.ts +0 -29
  113. package/apps/daemon/src/routes/misc.ts +0 -239
  114. package/apps/daemon/src/routes/profiles.ts +0 -197
  115. package/apps/daemon/src/routes/skills.ts +0 -123
  116. package/apps/daemon/src/routes/triggers.ts +0 -29
  117. package/apps/daemon/src/runtime/auth/openai-codex.ts +0 -121
  118. package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +0 -31
  119. package/apps/daemon/src/runtime/index.ts +0 -77
  120. package/apps/daemon/src/runtime/memory/files.ts +0 -103
  121. package/apps/daemon/src/runtime/memory/qmd.ts +0 -152
  122. package/apps/daemon/src/runtime/memory/types.ts +0 -16
  123. package/apps/daemon/src/runtime/pi/events.ts +0 -173
  124. package/apps/daemon/src/runtime/pi/session.ts +0 -536
  125. package/apps/daemon/src/runtime/pi/tools.ts +0 -85
  126. package/apps/daemon/src/runtime/providers/catalog.ts +0 -145
  127. package/apps/daemon/src/runtime/providers/pi-adapter.ts +0 -272
  128. package/apps/daemon/src/runtime/providers/registry.ts +0 -374
  129. package/apps/daemon/src/runtime/providers/retry.ts +0 -176
  130. package/apps/daemon/src/runtime/providers/types.ts +0 -33
  131. package/apps/daemon/src/runtime/session/prompt.ts +0 -83
  132. package/apps/daemon/src/runtime/tools/bootstrap.ts +0 -22
  133. package/apps/daemon/src/runtime/tools/home.ts +0 -114
  134. package/apps/daemon/src/runtime/tools/memory.ts +0 -81
  135. package/apps/daemon/src/runtime/tools/messaging.ts +0 -127
  136. package/apps/daemon/src/runtime/tools/registry.ts +0 -29
  137. package/apps/daemon/src/runtime/tools/types.ts +0 -13
  138. package/apps/daemon/src/runtime/tools/web-extract.ts +0 -110
  139. package/apps/daemon/src/runtime/tools/web-ssrf.ts +0 -245
  140. package/apps/daemon/src/runtime/tools/web.ts +0 -273
  141. package/apps/daemon/src/runtime/worker/entry.ts +0 -221
  142. package/apps/daemon/src/runtime/worker/ipc-protocol.ts +0 -75
  143. package/apps/daemon/src/runtime/worker/spawn.ts +0 -249
  144. package/apps/daemon/test/core/agents.test.ts +0 -319
  145. package/apps/daemon/test/core/available-models.test.ts +0 -63
  146. package/apps/daemon/test/core/config.test.ts +0 -99
  147. package/apps/daemon/test/core/groups.test.ts +0 -63
  148. package/apps/daemon/test/core/helpers.ts +0 -50
  149. package/apps/daemon/test/core/identity.test.ts +0 -85
  150. package/apps/daemon/test/core/migrations.test.ts +0 -83
  151. package/apps/daemon/test/core/profiles.test.ts +0 -182
  152. package/apps/daemon/test/core/provider-models.test.ts +0 -57
  153. package/apps/daemon/test/core/provider-state.test.ts +0 -36
  154. package/apps/daemon/test/core/skill-meta.test.ts +0 -45
  155. package/apps/daemon/test/core/skills.test.ts +0 -271
  156. package/apps/daemon/test/core/triggers.test.ts +0 -182
  157. package/apps/daemon/test/core/web-tokens.test.ts +0 -79
  158. package/apps/daemon/test/cron.test.ts +0 -90
  159. package/apps/daemon/test/runtime/heartbeat.test.ts +0 -34
  160. package/apps/daemon/test/runtime/memory-qmd.test.ts +0 -97
  161. package/apps/daemon/test/runtime/memory.test.ts +0 -78
  162. package/apps/daemon/test/runtime/messaging.test.ts +0 -213
  163. package/apps/daemon/test/runtime/mock-server.ts +0 -65
  164. package/apps/daemon/test/runtime/openai-codex-auth.test.ts +0 -116
  165. package/apps/daemon/test/runtime/providers.test.ts +0 -306
  166. package/apps/daemon/test/runtime/retry.test.ts +0 -191
  167. package/apps/daemon/test/runtime/session-head.test.ts +0 -90
  168. package/apps/daemon/test/runtime/tools-home.test.ts +0 -102
  169. package/apps/daemon/test/runtime/tools-web.test.ts +0 -206
  170. package/apps/daemon/tsconfig.json +0 -4
  171. package/apps/mobile/README.md +0 -60
  172. package/apps/mobile/app/_layout.tsx +0 -58
  173. package/apps/mobile/app/agents/[id]/chat.tsx +0 -486
  174. package/apps/mobile/app/agents/[id]/index.tsx +0 -166
  175. package/apps/mobile/app/agents/index.tsx +0 -212
  176. package/apps/mobile/app/index.tsx +0 -21
  177. package/apps/mobile/app/pair.tsx +0 -226
  178. package/apps/mobile/app/settings.tsx +0 -419
  179. package/apps/mobile/app.json +0 -49
  180. package/apps/mobile/assets/adaptive-icon.png +0 -0
  181. package/apps/mobile/assets/favicon.png +0 -0
  182. package/apps/mobile/assets/icon.png +0 -0
  183. package/apps/mobile/assets/splash-icon.png +0 -0
  184. package/apps/mobile/babel.config.js +0 -6
  185. package/apps/mobile/metro.config.js +0 -28
  186. package/apps/mobile/package.json +0 -44
  187. package/apps/mobile/src/auth.ts +0 -66
  188. package/apps/mobile/src/pair-url.ts +0 -48
  189. package/apps/mobile/src/theme-context.tsx +0 -88
  190. package/apps/mobile/src/theme.ts +0 -135
  191. package/apps/mobile/test/pair-url.test.ts +0 -46
  192. package/apps/mobile/tsconfig.json +0 -23
  193. package/apps/web/components.json +0 -25
  194. package/apps/web/package.json +0 -39
  195. package/apps/web/public/baziu.svg +0 -8
  196. package/apps/web/src/components/AgentTabs.tsx +0 -45
  197. package/apps/web/src/components/BaziuLogo.tsx +0 -21
  198. package/apps/web/src/components/ChatPane.tsx +0 -1033
  199. package/apps/web/src/components/ConfigTabs.tsx +0 -29
  200. package/apps/web/src/components/CopyButton.tsx +0 -68
  201. package/apps/web/src/components/CreateGroupDialog.tsx +0 -127
  202. package/apps/web/src/components/FieldRow.tsx +0 -94
  203. package/apps/web/src/components/Footer.tsx +0 -10
  204. package/apps/web/src/components/PawIcon.tsx +0 -15
  205. package/apps/web/src/components/Sidebar.tsx +0 -287
  206. package/apps/web/src/components/SpawnDialog.tsx +0 -129
  207. package/apps/web/src/components/ThemeToggle.tsx +0 -75
  208. package/apps/web/src/components/TopNav.tsx +0 -34
  209. package/apps/web/src/components/ui/button.tsx +0 -67
  210. package/apps/web/src/components/ui/card.tsx +0 -103
  211. package/apps/web/src/components/ui/checkbox.tsx +0 -31
  212. package/apps/web/src/components/ui/dialog.tsx +0 -168
  213. package/apps/web/src/components/ui/input.tsx +0 -19
  214. package/apps/web/src/components/ui/label.tsx +0 -22
  215. package/apps/web/src/components/ui/radio-group.tsx +0 -44
  216. package/apps/web/src/components/ui/select.tsx +0 -192
  217. package/apps/web/src/components/ui/separator.tsx +0 -26
  218. package/apps/web/src/components/ui/table.tsx +0 -116
  219. package/apps/web/src/components/ui/tabs.tsx +0 -88
  220. package/apps/web/src/components/ui/textarea.tsx +0 -18
  221. package/apps/web/src/lib/auth.ts +0 -50
  222. package/apps/web/src/lib/daemon-client.ts +0 -34
  223. package/apps/web/src/lib/md.ts +0 -45
  224. package/apps/web/src/lib/utils.ts +0 -6
  225. package/apps/web/src/lib/wire-constants.ts +0 -27
  226. package/apps/web/src/routeTree.gen.ts +0 -408
  227. package/apps/web/src/router.tsx +0 -20
  228. package/apps/web/src/routes/__root.tsx +0 -123
  229. package/apps/web/src/routes/agents/$id/inbox.tsx +0 -207
  230. package/apps/web/src/routes/agents/$id/index.tsx +0 -527
  231. package/apps/web/src/routes/agents/$id/triggers.tsx +0 -239
  232. package/apps/web/src/routes/agents/index.tsx +0 -265
  233. package/apps/web/src/routes/api/$.ts +0 -88
  234. package/apps/web/src/routes/config/index.tsx +0 -315
  235. package/apps/web/src/routes/config/services.tsx +0 -49
  236. package/apps/web/src/routes/config/tokens.tsx +0 -192
  237. package/apps/web/src/routes/groups/$id/index.tsx +0 -153
  238. package/apps/web/src/routes/groups/$id/memory.tsx +0 -321
  239. package/apps/web/src/routes/groups/index.tsx +0 -191
  240. package/apps/web/src/routes/index.tsx +0 -133
  241. package/apps/web/src/routes/login.tsx +0 -63
  242. package/apps/web/src/routes/profiles/$id.tsx +0 -549
  243. package/apps/web/src/routes/profiles/index.tsx +0 -458
  244. package/apps/web/src/routes/skills/index.tsx +0 -297
  245. package/apps/web/src/routes/welcome.tsx +0 -61
  246. package/apps/web/src/styles.css +0 -449
  247. package/apps/web/tsconfig.json +0 -25
  248. package/apps/web/vite.config.ts +0 -25
  249. package/biome.json +0 -23
  250. package/docs/agent-engine.md +0 -219
  251. package/docs/architecture.md +0 -627
  252. package/docs/backlog/README.md +0 -42
  253. package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +0 -125
  254. package/docs/openclaw-reference.md +0 -210
  255. package/packages/api-types/package.json +0 -11
  256. package/packages/api-types/src/entities.ts +0 -146
  257. package/packages/api-types/src/events.ts +0 -55
  258. package/packages/api-types/src/index.ts +0 -488
  259. package/packages/api-types/src/memory.ts +0 -15
  260. package/packages/client/package.json +0 -13
  261. package/packages/client/src/index.ts +0 -117
  262. package/pnpm-workspace.yaml +0 -3
  263. package/tsconfig.base.json +0 -23
  264. package/tsconfig.json +0 -11
  265. package/vitest.config.ts +0 -22
@@ -1,294 +0,0 @@
1
- // In-process scheduler for agent triggers (heartbeats + cron) and inbox
2
- // auto-delivery.
3
- //
4
- // Each tick does two jobs:
5
- //
6
- // 1. Trigger firing. Loads enabled `agent_triggers` rows and fires whichever
7
- // are due via `runAgentTurn`. A trigger is "due" when:
8
- // - interval: last_fired_at + intervalSec*1000 ≤ now (never-fired
9
- // uses created_at as the baseline)
10
- // - cron: current minute's wall-clock matches expression AND
11
- // last_fired_at's minute < current minute
12
- //
13
- // 2. Inbox auto-delivery (always on). Scans `messages` for recipients
14
- // with unread mail. For each idle recipient (not already running /
15
- // firing), drains all their unread messages in one transaction and
16
- // fires a turn whose prompt embeds the messages. Marking read happens
17
- // *inside* the drain transaction so two concurrent ticks can't
18
- // double-dispatch. To disable both triggers AND auto-delivery, set
19
- // `BAZILION_SCHEDULER=off` — there is no separate inbox-only knob
20
- // because free inter-agent messaging is a baseline Bazilion promise.
21
- //
22
- // Concurrency: each trigger gets an in-memory "firing" guard so a slow turn
23
- // can't pile up overlapping runs for the same trigger. Auto-delivery shares
24
- // the same mechanism keyed on `msg-wake:<agentId>`. The DB is still the
25
- // source of truth for last_fired_at / read_at — we mark *before* kicking the
26
- // run, so a server restart won't immediately re-fire.
27
-
28
- import type { AgentTrigger, Message } from '@bazilion/api-types'
29
- import { agentRepo, messageRepo, triggerRepo } from '../core/index.ts'
30
- import { isActiveAgent } from './agent-cancel.ts'
31
- import { runAgentTurn } from './agent-turn.ts'
32
- import { matchesCron, type ParsedCron, parseCron } from './cron.ts'
33
- import { getCtx } from './ctx.ts'
34
-
35
- const SCHEDULER_KEY = Symbol.for('bazilion.scheduler')
36
- const TICK_MS = Number(process.env.BAZILION_SCHEDULER_TICK_MS ?? 5_000)
37
-
38
- interface SchedulerState {
39
- timer: NodeJS.Timeout | null
40
- firing: Set<string>
41
- cronCache: Map<string, ParsedCron>
42
- /** pinned onStop for graceful shutdown (tests) */
43
- stopped: boolean
44
- }
45
-
46
- function state(): SchedulerState {
47
- const g = globalThis as unknown as Record<symbol, SchedulerState | undefined>
48
- let s = g[SCHEDULER_KEY]
49
- if (!s) {
50
- s = { timer: null, firing: new Set(), cronCache: new Map(), stopped: false }
51
- g[SCHEDULER_KEY] = s
52
- }
53
- return s
54
- }
55
-
56
- function floorToMinute(ms: number): number {
57
- return Math.floor(ms / 60_000) * 60_000
58
- }
59
-
60
- function isDue(t: AgentTrigger, now: number, cronCache: Map<string, ParsedCron>): boolean {
61
- if (!t.enabled) return false
62
- if (t.kind === 'interval') {
63
- const every = (t.intervalSec ?? 0) * 1000
64
- if (every <= 0) return false
65
- const baseline = t.lastFiredAt ?? t.createdAt
66
- return now - baseline >= every
67
- }
68
- if (t.kind === 'cron') {
69
- if (!t.cronExpr) return false
70
- let parsed = cronCache.get(t.cronExpr)
71
- if (!parsed) {
72
- try {
73
- parsed = parseCron(t.cronExpr)
74
- } catch {
75
- return false
76
- }
77
- cronCache.set(t.cronExpr, parsed)
78
- }
79
- const nowFloor = floorToMinute(now)
80
- if (t.lastFiredAt && floorToMinute(t.lastFiredAt) === nowFloor) return false
81
- return matchesCron(parsed, new Date(nowFloor))
82
- }
83
- return false
84
- }
85
-
86
- async function fireTrigger(t: AgentTrigger): Promise<void> {
87
- const s = state()
88
- if (s.firing.has(t.id)) return
89
- s.firing.add(t.id)
90
- const ctx = getCtx()
91
- // Mark fired first — if the agent turn fails, we still don't want to loop
92
- // on the same trigger every tick. The user will see it in `trigger list`
93
- // (last_fired_at updated) and the run will be marked failed.
94
- try {
95
- triggerRepo.markFired(ctx.db, t.id)
96
- } catch (err) {
97
- console.error(`[scheduler] markFired failed for ${t.id}:`, err)
98
- s.firing.delete(t.id)
99
- return
100
- }
101
- try {
102
- // Drain the turn; we don't stream to anyone. Errors surface as `fatal`
103
- // frames which we log but don't throw — the run row in the DB carries
104
- // the real status.
105
- for await (const frame of runAgentTurn(t.agentId, t.message)) {
106
- if (frame.kind === 'fatal') {
107
- console.error(`[scheduler] trigger ${t.id} fatal:`, frame.error)
108
- }
109
- }
110
- } catch (err) {
111
- console.error(`[scheduler] trigger ${t.id} unexpected throw:`, err)
112
- } finally {
113
- s.firing.delete(t.id)
114
- }
115
- }
116
-
117
- function decodeText(payload: string): string {
118
- try {
119
- const obj = JSON.parse(payload) as { text?: unknown }
120
- if (typeof obj.text === 'string') return obj.text
121
- } catch {}
122
- return payload
123
- }
124
-
125
- // Sentinel prepended to every inbox-wake prompt. The web chat UI detects it
126
- // to render the bubble with a distinct "inter-agent" style instead of the
127
- // default user-message styling. Kept in sync by copy with `chat.ts`'s
128
- // INBOX_WAKE_PREFIX constant.
129
- const INBOX_WAKE_PREFIX = '[[bazilion:inbox-wake]]\n'
130
-
131
- /**
132
- * Build a wake-up prompt for an agent that has unread mail. The prompt is
133
- * framed as a user-side system notice — not an agent-style message — so the
134
- * recipient's LLM understands this is the runtime telling it "here's what
135
- * arrived for you".
136
- *
137
- * Loop prevention: we differentiate by `replyTo`. A NEW message (no
138
- * `replyTo`) opens a thread, and the sender is waiting for an answer — the
139
- * agent must respond at least once. A REPLY (`replyTo` set) is already
140
- * closing a round-trip, so the agent may acknowledge silently. This single
141
- * asymmetry is enough to terminate conversations after one exchange instead
142
- * of ping-ponging forever ("you have to answer me" → "no you have to
143
- * answer me" → …).
144
- */
145
- function buildInboxPrompt(
146
- agentId: string,
147
- messages: Message[],
148
- fromNames: Map<string, string>,
149
- ): string {
150
- const lines: string[] = [INBOX_WAKE_PREFIX.trimEnd()]
151
- lines.push(
152
- `You have ${messages.length} new message${messages.length === 1 ? '' : 's'} in your inbox:`,
153
- )
154
- lines.push('')
155
- const newThread: Message[] = []
156
- for (const m of messages) {
157
- const name = fromNames.get(m.fromAgentId)
158
- const fromLabel = name ? `${name} (${m.fromAgentId})` : m.fromAgentId
159
- const text = decodeText(m.payload)
160
- const header = m.replyTo
161
- ? `--- from ${fromLabel} (message ${m.id}, reply to ${m.replyTo}) ---`
162
- : `--- from ${fromLabel} (message ${m.id}) ---`
163
- lines.push(header)
164
- lines.push(text)
165
- lines.push('')
166
- if (!m.replyTo) newThread.push(m)
167
- }
168
- if (newThread.length > 0) {
169
- lines.push(
170
- `You MUST reply to the ${newThread.length} new message${newThread.length === 1 ? '' : 's'} above ` +
171
- '(the ones that are NOT marked as a reply). For each, call ' +
172
- '`send_message(to=<sender agent id>, text=<your reply>, reply_to=<the message id shown in the header>)`. ' +
173
- 'Give a concrete answer if you can; a brief acknowledgement is fine if the message is purely informational. ' +
174
- 'Avoid asking follow-up questions — answer with what you already know. ' +
175
- 'IMPORTANT: do NOT demand a response from the sender in your reply — they already got what they asked for, ' +
176
- 'and asking them back to reply just creates infinite loops.',
177
- )
178
- }
179
- const replies = messages.filter((m) => m.replyTo)
180
- if (replies.length > 0) {
181
- lines.push(
182
- `The other message${replies.length === 1 ? ' is a reply' : 's are replies'} to something you previously sent. ` +
183
- 'Read, absorb, and move on — no response is required. Only send a follow-up if there is a ' +
184
- 'genuinely new question or action that requires it; otherwise this thread is closed.',
185
- )
186
- }
187
- // reference the recipient id so the agent knows which mailbox this is for
188
- // in case it was spawned without persona context
189
- lines.push(`(recipient: ${agentId})`)
190
- return lines.join('\n')
191
- }
192
-
193
- async function fireInboxWake(agentId: string): Promise<void> {
194
- const s = state()
195
- const key = `msg-wake:${agentId}`
196
- if (s.firing.has(key)) return
197
- const ctx = getCtx()
198
- // Skip agents with an active turn — they'll pick up the messages on their
199
- // next natural turn or via the next tick after the current one ends.
200
- if (isActiveAgent(agentId)) return
201
-
202
- s.firing.add(key)
203
- try {
204
- const msgs = messageRepo.drainUnreadForAgent(ctx.db, agentId)
205
- if (msgs.length === 0) {
206
- // Raced with another consumer; nothing to do.
207
- return
208
- }
209
- const fromIds = new Set(msgs.map((m) => m.fromAgentId))
210
- const fromNames = new Map<string, string>()
211
- for (const fid of fromIds) {
212
- const sender = agentRepo.get(ctx.db, fid)
213
- if (sender) fromNames.set(fid, sender.name)
214
- }
215
- const prompt = buildInboxPrompt(agentId, msgs, fromNames)
216
-
217
- try {
218
- for await (const frame of runAgentTurn(agentId, prompt)) {
219
- if (frame.kind === 'fatal') {
220
- console.error(`[scheduler] inbox wake ${agentId} fatal:`, frame.error)
221
- }
222
- }
223
- } catch (err) {
224
- console.error(`[scheduler] inbox wake ${agentId} unexpected throw:`, err)
225
- }
226
- } catch (err) {
227
- console.error(`[scheduler] inbox drain failed for ${agentId}:`, err)
228
- } finally {
229
- s.firing.delete(key)
230
- }
231
- }
232
-
233
- async function tick(): Promise<void> {
234
- const s = state()
235
- if (s.stopped) return
236
- const now = Date.now()
237
- let triggers: AgentTrigger[]
238
- let recipients: string[] = []
239
- try {
240
- const ctx = getCtx()
241
- triggers = triggerRepo.listEnabled(ctx.db)
242
- recipients = messageRepo.listRecipientsWithUnread(ctx.db)
243
- } catch (err) {
244
- console.error('[scheduler] tick read failed:', err)
245
- return
246
- }
247
- for (const t of triggers) {
248
- if (isDue(t, now, s.cronCache)) {
249
- // Fire async, don't await — the tick itself must stay fast.
250
- void fireTrigger(t)
251
- }
252
- }
253
- for (const agentId of recipients) {
254
- // Same fire-and-forget shape as triggers; `fireInboxWake` internally
255
- // dedup-gates and bails if the agent already has an active run.
256
- void fireInboxWake(agentId)
257
- }
258
- }
259
-
260
- export function startScheduler(): void {
261
- const s = state()
262
- // Replace any existing timer unconditionally so a duplicate startScheduler
263
- // call (e.g. test harness calling getCtx twice) doesn't leave stale loops.
264
- if (s.timer) clearInterval(s.timer)
265
- s.stopped = false
266
- s.timer = setInterval(() => {
267
- void tick()
268
- }, TICK_MS)
269
- // Unref so the scheduler never blocks process exit in tests.
270
- if (typeof s.timer.unref === 'function') s.timer.unref()
271
- }
272
-
273
- export function stopScheduler(): void {
274
- const s = state()
275
- s.stopped = true
276
- if (s.timer) {
277
- clearInterval(s.timer)
278
- s.timer = null
279
- }
280
- }
281
-
282
- // Exposed for tests that want to drive the loop manually rather than wait
283
- // on wall-clock intervals.
284
- export async function _tickOnce(): Promise<void> {
285
- await tick()
286
- }
287
-
288
- export function _isDueForTest(
289
- t: AgentTrigger,
290
- now: number,
291
- cronCache: Map<string, ParsedCron> = new Map(),
292
- ): boolean {
293
- return isDue(t, now, cronCache)
294
- }