@electric-ax/agents 0.2.1 → 0.2.2

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 (49) hide show
  1. package/dist/entrypoint.js +5 -3
  2. package/dist/index.cjs +5 -3
  3. package/dist/index.js +5 -3
  4. package/docs/entities/agents/horton.md +89 -0
  5. package/docs/entities/agents/worker.md +102 -0
  6. package/docs/entities/patterns/blackboard.md +111 -0
  7. package/docs/entities/patterns/dispatcher.md +77 -0
  8. package/docs/entities/patterns/manager-worker.md +127 -0
  9. package/docs/entities/patterns/map-reduce.md +81 -0
  10. package/docs/entities/patterns/pipeline.md +101 -0
  11. package/docs/entities/patterns/reactive-observers.md +125 -0
  12. package/docs/examples/mega-draw.md +106 -0
  13. package/docs/examples/playground.md +46 -0
  14. package/docs/index.md +208 -0
  15. package/docs/quickstart.md +201 -0
  16. package/docs/reference/agent-config.md +82 -0
  17. package/docs/reference/agent-tool.md +58 -0
  18. package/docs/reference/built-in-collections.md +334 -0
  19. package/docs/reference/cli.md +238 -0
  20. package/docs/reference/entity-definition.md +57 -0
  21. package/docs/reference/entity-handle.md +63 -0
  22. package/docs/reference/entity-registry.md +73 -0
  23. package/docs/reference/handler-context.md +108 -0
  24. package/docs/reference/runtime-handler.md +136 -0
  25. package/docs/reference/shared-state-handle.md +74 -0
  26. package/docs/reference/state-collection-proxy.md +41 -0
  27. package/docs/reference/wake-event.md +132 -0
  28. package/docs/usage/app-setup.md +165 -0
  29. package/docs/usage/clients-and-react.md +191 -0
  30. package/docs/usage/configuring-the-agent.md +136 -0
  31. package/docs/usage/context-composition.md +204 -0
  32. package/docs/usage/defining-entities.md +181 -0
  33. package/docs/usage/defining-tools.md +229 -0
  34. package/docs/usage/embedded-builtins.md +180 -0
  35. package/docs/usage/managing-state.md +93 -0
  36. package/docs/usage/overview.md +284 -0
  37. package/docs/usage/programmatic-runtime-client.md +216 -0
  38. package/docs/usage/shared-state.md +169 -0
  39. package/docs/usage/spawning-and-coordinating.md +165 -0
  40. package/docs/usage/testing.md +76 -0
  41. package/docs/usage/waking-entities.md +148 -0
  42. package/docs/usage/writing-handlers.md +267 -0
  43. package/package.json +2 -1
  44. package/skills/quickstart/scaffold/package.json +16 -3
  45. package/skills/quickstart/scaffold/tsconfig.json +8 -3
  46. package/skills/quickstart/scaffold/vite.config.ts +21 -0
  47. package/skills/quickstart/scaffold-ui/index.html +12 -0
  48. package/skills/quickstart/scaffold-ui/main.tsx +235 -0
  49. package/skills/quickstart.md +244 -334
@@ -4,14 +4,27 @@
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "start": "tsx server.ts",
7
- "dev": "tsx --watch server.ts"
7
+ "dev": "tsx --watch server.ts",
8
+ "dev:server": "tsx --watch server.ts",
9
+ "dev:ui": "vite",
10
+ "dev:all": "npm run dev:server & npm run dev:ui"
8
11
  },
9
12
  "dependencies": {
10
13
  "@electric-ax/agents-runtime": "latest",
11
- "@sinclair/typebox": "^0.34.49"
14
+ "@radix-ui/themes": "^3.3.0",
15
+ "@sinclair/typebox": "^0.34.49",
16
+ "@tanstack/db": "^0.6.0",
17
+ "@tanstack/react-db": "^0.1.78",
18
+ "react": "^19.2.4",
19
+ "react-dom": "^19.2.4",
20
+ "streamdown": "^2.5.0"
12
21
  },
13
22
  "devDependencies": {
23
+ "@types/react": "^19.2.14",
24
+ "@types/react-dom": "^19.2.3",
25
+ "@vitejs/plugin-react": "^5.2.0",
14
26
  "tsx": "^4.19.0",
15
- "typescript": "^5.7.0"
27
+ "typescript": "^5.7.0",
28
+ "vite": "^7.2.4"
16
29
  }
17
30
  }
@@ -3,13 +3,18 @@
3
3
  "target": "ES2022",
4
4
  "module": "ESNext",
5
5
  "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
6
7
  "strict": true,
7
8
  "esModuleInterop": true,
8
9
  "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
9
11
  "resolveJsonModule": true,
10
- "allowImportingTsExtensions": false,
11
- "noEmit": true
12
+ "isolatedModules": true,
13
+ "noEmit": true,
14
+ "paths": {
15
+ "@tanstack/db": ["./node_modules/@tanstack/db"]
16
+ }
12
17
  },
13
- "include": ["**/*.ts"],
18
+ "include": ["**/*.ts", "**/*.tsx"],
14
19
  "exclude": ["node_modules"]
15
20
  }
@@ -0,0 +1,21 @@
1
+ import path from 'node:path'
2
+ import { defineConfig } from 'vite'
3
+ import react from '@vitejs/plugin-react'
4
+
5
+ export default defineConfig({
6
+ root: `ui`,
7
+ plugins: [react()],
8
+ resolve: {
9
+ alias: {
10
+ '@tanstack/db': path.resolve(
11
+ import.meta.dirname,
12
+ `node_modules/@tanstack/db`
13
+ ),
14
+ },
15
+ },
16
+ server: {
17
+ port: 5175,
18
+ proxy: { '/api': `http://localhost:3000` },
19
+ },
20
+ build: { outDir: `../dist`, emptyOutDir: true },
21
+ })
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Perspectives Analyzer</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,235 @@
1
+ import { useState, useEffect, useRef } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { createAgentsClient, entity } from '@electric-ax/agents-runtime'
4
+ import { useChat } from '@electric-ax/agents-runtime/react'
5
+ import {
6
+ Theme,
7
+ Box,
8
+ Flex,
9
+ Heading,
10
+ Text,
11
+ TextField,
12
+ Button,
13
+ Card,
14
+ } from '@radix-ui/themes'
15
+ import { Streamdown } from 'streamdown'
16
+ import '@radix-ui/themes/styles.css'
17
+ import 'streamdown/styles.css'
18
+ import type { EntityStreamDB } from '@electric-ax/agents-runtime'
19
+
20
+ const AGENTS_URL = `http://localhost:4437`
21
+
22
+ const AGENT_COLORS: Record<string, { bg: string; border: string }> = {
23
+ analyser: { bg: `var(--blue-3)`, border: `var(--blue-6)` },
24
+ optimist: { bg: `var(--green-3)`, border: `var(--green-6)` },
25
+ critic: { bg: `var(--red-3)`, border: `var(--red-6)` },
26
+ }
27
+
28
+ function useEntityDb(url: string | null, retryMs = 0) {
29
+ const [db, setDb] = useState<EntityStreamDB | null>(null)
30
+
31
+ useEffect(() => {
32
+ if (!url) {
33
+ setDb(null)
34
+ return
35
+ }
36
+ let cancelled = false
37
+ let observedDb: EntityStreamDB | null = null
38
+ let timer: ReturnType<typeof setTimeout> | null = null
39
+
40
+ const connect = () => {
41
+ const client = createAgentsClient({ baseUrl: AGENTS_URL })
42
+ client.observe(entity(url)).then(
43
+ (observed) => {
44
+ observedDb = observed as EntityStreamDB
45
+ if (cancelled) {
46
+ observedDb.close()
47
+ return
48
+ }
49
+ setDb(observedDb)
50
+ },
51
+ () => {
52
+ if (!cancelled && retryMs > 0) {
53
+ timer = setTimeout(connect, retryMs)
54
+ }
55
+ }
56
+ )
57
+ }
58
+ connect()
59
+
60
+ return () => {
61
+ cancelled = true
62
+ if (timer) clearTimeout(timer)
63
+ observedDb?.close()
64
+ }
65
+ }, [url, retryMs])
66
+
67
+ return db
68
+ }
69
+
70
+ interface AgentMessage {
71
+ agent: string
72
+ text: string
73
+ isStreaming: boolean
74
+ }
75
+
76
+ function useAgentMessages(
77
+ url: string | null,
78
+ agent: string,
79
+ retryMs = 0
80
+ ): AgentMessage[] {
81
+ const db = useEntityDb(url, retryMs)
82
+ const chat = useChat(db)
83
+
84
+ return chat.runs.flatMap((r, ri) =>
85
+ r.texts
86
+ .filter((t) => t.text.trim().length > 0)
87
+ .map((t, ti) => ({
88
+ agent,
89
+ text: t.text,
90
+ isStreaming:
91
+ chat.state === `working` &&
92
+ ri === chat.runs.length - 1 &&
93
+ ti === r.texts.length - 1,
94
+ }))
95
+ )
96
+ }
97
+
98
+ function MessageBubble({ msg }: { msg: AgentMessage }) {
99
+ const colors = AGENT_COLORS[msg.agent] ?? {
100
+ bg: `var(--gray-3)`,
101
+ border: `var(--gray-6)`,
102
+ }
103
+
104
+ return (
105
+ <Card
106
+ size="1"
107
+ style={{
108
+ background: colors.bg,
109
+ borderLeft: `3px solid ${colors.border}`,
110
+ }}
111
+ >
112
+ <Text size="1" weight="bold" style={{ textTransform: `capitalize` }}>
113
+ {msg.agent}
114
+ </Text>
115
+ <Box mt="1" style={{ fontSize: `var(--font-size-2)` }}>
116
+ <Streamdown isAnimating={msg.isStreaming} controls={false}>
117
+ {msg.text}
118
+ </Streamdown>
119
+ </Box>
120
+ </Card>
121
+ )
122
+ }
123
+
124
+ function App() {
125
+ const [question, setQuestion] = useState(``)
126
+ const [urls, setUrls] = useState<{
127
+ entityUrl: string
128
+ optimistUrl: string
129
+ criticUrl: string
130
+ } | null>(null)
131
+ const [loading, setLoading] = useState(false)
132
+ const bottomRef = useRef<HTMLDivElement>(null)
133
+
134
+ const analyserMessages = useAgentMessages(urls?.entityUrl ?? null, `analyser`)
135
+ const optimistMessages = useAgentMessages(
136
+ urls?.optimistUrl ?? null,
137
+ `optimist`,
138
+ 2000
139
+ )
140
+ const criticMessages = useAgentMessages(
141
+ urls?.criticUrl ?? null,
142
+ `critic`,
143
+ 2000
144
+ )
145
+
146
+ const allMessages = [
147
+ ...analyserMessages,
148
+ ...optimistMessages,
149
+ ...criticMessages,
150
+ ]
151
+
152
+ useEffect(() => {
153
+ const el = bottomRef.current?.parentElement
154
+ if (!el) return
155
+ const observer = new MutationObserver(() => {
156
+ window.scrollTo({ top: document.body.scrollHeight, behavior: `smooth` })
157
+ })
158
+ observer.observe(el, {
159
+ childList: true,
160
+ subtree: true,
161
+ characterData: true,
162
+ })
163
+ return () => observer.disconnect()
164
+ }, [urls])
165
+
166
+ const handleAnalyze = async () => {
167
+ if (!question.trim()) return
168
+ setLoading(true)
169
+ try {
170
+ const res = await fetch(`/api/analyze`, {
171
+ method: `POST`,
172
+ headers: { 'Content-Type': `application/json` },
173
+ body: JSON.stringify({ question }),
174
+ })
175
+ const data = (await res.json()) as {
176
+ entityUrl: string
177
+ optimistUrl: string
178
+ criticUrl: string
179
+ }
180
+ setUrls(data)
181
+ } catch (err) {
182
+ console.error(`Analyze failed:`, err)
183
+ } finally {
184
+ setLoading(false)
185
+ }
186
+ }
187
+
188
+ return (
189
+ <Theme appearance="light" accentColor="blue" radius="medium">
190
+ <style>{`@keyframes blink { 50% { opacity: 0; } }`}</style>
191
+ <Box maxWidth="700px" mx="auto" p="5">
192
+ <Heading size="6" mb="1">
193
+ Perspectives Analyzer
194
+ </Heading>
195
+ <Text size="2" color="gray" mb="5" as="p">
196
+ Ask a question and get two perspectives — an optimist and a critic —
197
+ then a balanced analysis.
198
+ </Text>
199
+
200
+ <Flex gap="2" mb="5">
201
+ <Box flexGrow="1">
202
+ <TextField.Root
203
+ size="3"
204
+ placeholder="Enter a question to analyze..."
205
+ value={question}
206
+ onChange={(e) => setQuestion(e.target.value)}
207
+ onKeyDown={(e) => e.key === `Enter` && handleAnalyze()}
208
+ />
209
+ </Box>
210
+ <Button
211
+ size="3"
212
+ onClick={handleAnalyze}
213
+ disabled={loading || !question.trim()}
214
+ >
215
+ {loading ? `Analyzing...` : `Analyze`}
216
+ </Button>
217
+ </Flex>
218
+
219
+ <Flex direction="column" gap="3">
220
+ {urls && allMessages.length === 0 && (
221
+ <Text color="gray" size="2">
222
+ Waiting for agents to respond...
223
+ </Text>
224
+ )}
225
+ {allMessages.map((msg, i) => (
226
+ <MessageBubble key={`${msg.agent}-${i}`} msg={msg} />
227
+ ))}
228
+ <div ref={bottomRef} />
229
+ </Flex>
230
+ </Box>
231
+ </Theme>
232
+ )
233
+ }
234
+
235
+ createRoot(document.getElementById(`root`)!).render(<App />)