tryassay 0.21.1 → 0.22.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 (37) hide show
  1. package/README.md +4 -4
  2. package/demo/.claude/.truth_last_prompt +1 -0
  3. package/demo/.claude/truth_status +1 -0
  4. package/demo/css/style.css +1181 -0
  5. package/demo/data/demo-events.json +103 -0
  6. package/demo/index.html +222 -0
  7. package/demo/js/chat.js +292 -0
  8. package/demo/js/code-panel.js +206 -0
  9. package/demo/js/demo-mode.js +107 -0
  10. package/demo/js/orb.js +634 -0
  11. package/demo/js/question-cards.js +207 -0
  12. package/demo/js/sse-client.js +473 -0
  13. package/demo/js/state.js +162 -0
  14. package/demo/js/timeline.js +394 -0
  15. package/demo/js/voice.js +154 -0
  16. package/dist/api/server.d.ts +1 -0
  17. package/dist/api/server.js +65 -2
  18. package/dist/api/server.js.map +1 -1
  19. package/dist/cli.js +13 -0
  20. package/dist/cli.js.map +1 -1
  21. package/dist/commands/demo.d.ts +5 -0
  22. package/dist/commands/demo.js +107 -0
  23. package/dist/commands/demo.js.map +1 -0
  24. package/dist/commands/runtime.d.ts +4 -0
  25. package/dist/commands/runtime.js +50 -3
  26. package/dist/commands/runtime.js.map +1 -1
  27. package/dist/runtime/agents/planner-agent.d.ts +5 -2
  28. package/dist/runtime/agents/planner-agent.js +232 -1
  29. package/dist/runtime/agents/planner-agent.js.map +1 -1
  30. package/dist/runtime/app-create-orchestrator.d.ts +4 -0
  31. package/dist/runtime/app-create-orchestrator.js +151 -48
  32. package/dist/runtime/app-create-orchestrator.js.map +1 -1
  33. package/dist/runtime/dashboard-sync.d.ts +25 -0
  34. package/dist/runtime/dashboard-sync.js +169 -0
  35. package/dist/runtime/dashboard-sync.js.map +1 -0
  36. package/dist/runtime/types.d.ts +28 -0
  37. package/package.json +3 -2
@@ -0,0 +1,103 @@
1
+ [
2
+ { "delay": 200, "type": "phase_change", "data": { "phase": "initializing" } },
3
+ { "delay": 500, "type": "agent_activate", "data": { "name": "Coordinator", "role": "coordinator", "status": "active", "trust": 0.95 } },
4
+ { "delay": 800, "type": "agent_activate", "data": { "name": "CodeGen", "role": "code", "status": "active", "trust": 0.90 } },
5
+ { "delay": 1100, "type": "agent_activate", "data": { "name": "Reviewer", "role": "review", "status": "active", "trust": 0.92 } },
6
+ { "delay": 1400, "type": "agent_activate", "data": { "name": "Tester", "role": "test", "status": "active", "trust": 0.88 } },
7
+
8
+ { "delay": 2000, "type": "phase_change", "data": { "phase": "planning" } },
9
+ { "delay": 2200, "type": "task_add", "data": { "id": "task-1", "title": "Design database schema", "agent": "Coordinator", "status": "in_progress" } },
10
+ { "delay": 2800, "type": "task_update", "data": { "id": "task-1", "status": "completed" } },
11
+ { "delay": 3000, "type": "task_add", "data": { "id": "task-2", "title": "Plan API routes", "agent": "Coordinator", "status": "in_progress" } },
12
+ { "delay": 3500, "type": "task_update", "data": { "id": "task-2", "status": "completed" } },
13
+ { "delay": 3700, "type": "task_add", "data": { "id": "task-3", "title": "Design auth flow", "agent": "Coordinator", "status": "in_progress" } },
14
+ { "delay": 4200, "type": "task_update", "data": { "id": "task-3", "status": "completed" } },
15
+ { "delay": 4400, "type": "task_add", "data": { "id": "task-4", "title": "Plan UI components", "agent": "Coordinator", "status": "in_progress" } },
16
+ { "delay": 4900, "type": "task_update", "data": { "id": "task-4", "status": "completed" } },
17
+ { "delay": 5100, "type": "task_add", "data": { "id": "task-5", "title": "Define page routes", "agent": "Coordinator", "status": "in_progress" } },
18
+ { "delay": 5600, "type": "task_update", "data": { "id": "task-5", "status": "completed" } },
19
+
20
+ { "delay": 6000, "type": "phase_change", "data": { "phase": "verifying_plan" } },
21
+ { "delay": 6300, "type": "verification", "data": { "claim": "Schema defines users table with id, email, password_hash", "result": "pass", "method": "formal" } },
22
+ { "delay": 6700, "type": "verification", "data": { "claim": "Schema defines todos table with user_id foreign key", "result": "pass", "method": "formal" } },
23
+ { "delay": 7100, "type": "verification", "data": { "claim": "Auth flow uses bcrypt for password hashing", "result": "pass", "method": "llm" } },
24
+ { "delay": 7400, "type": "verification", "data": { "claim": "API routes follow RESTful conventions", "result": "pass", "method": "llm" } },
25
+ { "delay": 7800, "type": "verification", "data": { "claim": "JWT tokens have expiration set", "result": "pass", "method": "formal" } },
26
+ { "delay": 8200, "type": "verification", "data": { "claim": "CRUD operations validate ownership", "result": "fail", "method": "llm" } },
27
+ { "delay": 8700, "type": "verification", "data": { "claim": "SQL injection prevented in query builder", "result": "pass", "method": "formal" } },
28
+ { "delay": 9200, "type": "verification", "data": { "claim": "CSRF protection on mutation endpoints", "result": "pass", "method": "llm" } },
29
+
30
+ { "delay": 10000, "type": "phase_change", "data": { "phase": "scaffolding" } },
31
+ { "delay": 10400, "type": "code_generated", "data": { "path": "package.json", "language": "json", "content": "{\n \"name\": \"todo-app\",\n \"version\": \"1.0.0\",\n \"scripts\": {\n \"dev\": \"tsx watch src/index.ts\",\n \"build\": \"tsc && vite build\",\n \"start\": \"node dist/index.js\",\n \"db:push\": \"prisma db push\",\n \"db:generate\": \"prisma generate\"\n },\n \"dependencies\": {\n \"express\": \"^4.18.2\",\n \"@prisma/client\": \"^5.8.0\",\n \"bcryptjs\": \"^2.4.3\",\n \"jsonwebtoken\": \"^9.0.2\",\n \"react\": \"^18.2.0\",\n \"react-dom\": \"^18.2.0\"\n },\n \"devDependencies\": {\n \"prisma\": \"^5.8.0\",\n \"typescript\": \"^5.3.3\",\n \"vite\": \"^5.0.0\"\n }\n}" } },
32
+ { "delay": 11000, "type": "code_generated", "data": { "path": "tsconfig.json", "language": "json", "content": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"jsx\": \"react-jsx\",\n \"outDir\": \"./dist\",\n \"rootDir\": \"./src\",\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true\n },\n \"include\": [\"src/**/*\"]\n}" } },
33
+ { "delay": 11800, "type": "code_generated", "data": { "path": "prisma/schema.prisma", "language": "prisma", "content": "generator client {\n provider = \"prisma-client-js\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel User {\n id String @id @default(cuid())\n email String @unique\n passwordHash String @map(\"password_hash\")\n createdAt DateTime @default(now())\n todos Todo[]\n}\n\nmodel Todo {\n id String @id @default(cuid())\n title String\n completed Boolean @default(false)\n userId String\n user User @relation(fields: [userId], references: [id])\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}" } },
34
+
35
+ { "delay": 13000, "type": "phase_change", "data": { "phase": "building_feature" } },
36
+ { "delay": 13200, "type": "task_add", "data": { "id": "task-6", "title": "Implement auth system", "agent": "CodeGen", "status": "in_progress" } },
37
+ { "delay": 13800, "type": "code_generated", "data": { "path": "src/auth/register.ts", "language": "typescript", "content": "import { PrismaClient } from '@prisma/client';\nimport bcrypt from 'bcryptjs';\nimport { z } from 'zod';\n\nconst prisma = new PrismaClient();\n\nconst RegisterSchema = z.object({\n email: z.string().email(),\n password: z.string().min(8).max(128)\n});\n\nexport async function register(email: string, password: string) {\n const input = RegisterSchema.parse({ email, password });\n \n const existing = await prisma.user.findUnique({\n where: { email: input.email }\n });\n \n if (existing) {\n throw new Error('Email already registered');\n }\n \n const passwordHash = await bcrypt.hash(input.password, 12);\n \n const user = await prisma.user.create({\n data: {\n email: input.email,\n passwordHash\n },\n select: { id: true, email: true, createdAt: true }\n });\n \n return user;\n}" } },
38
+ { "delay": 14500, "type": "verification", "data": { "claim": "Register validates email format", "result": "pass", "method": "formal" } },
39
+ { "delay": 14900, "type": "verification", "data": { "claim": "Password hashed with bcrypt cost 12", "result": "pass", "method": "formal" } },
40
+ { "delay": 15200, "type": "code_generated", "data": { "path": "src/auth/login.ts", "language": "typescript", "content": "import { PrismaClient } from '@prisma/client';\nimport bcrypt from 'bcryptjs';\nimport jwt from 'jsonwebtoken';\nimport { z } from 'zod';\n\nconst prisma = new PrismaClient();\nconst JWT_SECRET = process.env.JWT_SECRET!;\n\nconst LoginSchema = z.object({\n email: z.string().email(),\n password: z.string()\n});\n\nexport async function login(email: string, password: string) {\n const input = LoginSchema.parse({ email, password });\n \n const user = await prisma.user.findUnique({\n where: { email: input.email }\n });\n \n if (!user) {\n throw new Error('Invalid credentials');\n }\n \n const valid = await bcrypt.compare(input.password, user.passwordHash);\n if (!valid) {\n throw new Error('Invalid credentials');\n }\n \n const token = jwt.sign(\n { sub: user.id, email: user.email },\n JWT_SECRET,\n { expiresIn: '24h' }\n );\n \n return { token, user: { id: user.id, email: user.email } };\n}" } },
41
+ { "delay": 15800, "type": "verification", "data": { "claim": "Login returns same error for missing user and wrong password", "result": "pass", "method": "llm" } },
42
+ { "delay": 16200, "type": "code_generated", "data": { "path": "src/middleware/auth.ts", "language": "typescript", "content": "import { Request, Response, NextFunction } from 'express';\nimport jwt from 'jsonwebtoken';\n\nconst JWT_SECRET = process.env.JWT_SECRET!;\n\nexport interface AuthRequest extends Request {\n userId?: string;\n}\n\nexport function requireAuth(req: AuthRequest, res: Response, next: NextFunction) {\n const header = req.headers.authorization;\n \n if (!header || !header.startsWith('Bearer ')) {\n return res.status(401).json({ error: 'Missing authorization header' });\n }\n \n const token = header.slice(7);\n \n try {\n const payload = jwt.verify(token, JWT_SECRET) as { sub: string };\n req.userId = payload.sub;\n next();\n } catch {\n return res.status(401).json({ error: 'Invalid or expired token' });\n }\n}" } },
43
+ { "delay": 16800, "type": "verification", "data": { "claim": "Auth middleware rejects missing Bearer prefix", "result": "pass", "method": "formal" } },
44
+ { "delay": 17200, "type": "verification", "data": { "claim": "JWT verification catches expired tokens", "result": "pass", "method": "llm" } },
45
+ { "delay": 17600, "type": "task_update", "data": { "id": "task-6", "status": "completed" } },
46
+
47
+ { "delay": 18000, "type": "task_add", "data": { "id": "task-7", "title": "Implement todo CRUD", "agent": "CodeGen", "status": "in_progress" } },
48
+ { "delay": 18500, "type": "code_generated", "data": { "path": "src/routes/todos.ts", "language": "typescript", "content": "import { Router } from 'express';\nimport { PrismaClient } from '@prisma/client';\nimport { requireAuth, AuthRequest } from '../middleware/auth';\nimport { z } from 'zod';\n\nconst router = Router();\nconst prisma = new PrismaClient();\n\nconst CreateTodoSchema = z.object({\n title: z.string().min(1).max(500)\n});\n\nrouter.get('/', requireAuth, async (req: AuthRequest, res) => {\n const todos = await prisma.todo.findMany({\n where: { userId: req.userId },\n orderBy: { createdAt: 'desc' },\n take: 50\n });\n res.json(todos);\n});\n\nrouter.post('/', requireAuth, async (req: AuthRequest, res) => {\n const input = CreateTodoSchema.parse(req.body);\n const todo = await prisma.todo.create({\n data: { title: input.title, userId: req.userId! }\n });\n res.status(201).json(todo);\n});\n\nrouter.patch('/:id', requireAuth, async (req: AuthRequest, res) => {\n const todo = await prisma.todo.findFirst({\n where: { id: req.params.id, userId: req.userId }\n });\n if (!todo) return res.status(404).json({ error: 'Not found' });\n \n const updated = await prisma.todo.update({\n where: { id: todo.id },\n data: { completed: !todo.completed }\n });\n res.json(updated);\n});\n\nrouter.delete('/:id', requireAuth, async (req: AuthRequest, res) => {\n const todo = await prisma.todo.findFirst({\n where: { id: req.params.id, userId: req.userId }\n });\n if (!todo) return res.status(404).json({ error: 'Not found' });\n \n await prisma.todo.delete({ where: { id: todo.id } });\n res.status(204).send();\n});\n\nexport default router;" } },
49
+ { "delay": 19200, "type": "verification", "data": { "claim": "GET /todos filters by authenticated userId", "result": "pass", "method": "formal" } },
50
+ { "delay": 19600, "type": "verification", "data": { "claim": "PATCH validates ownership before toggling", "result": "pass", "method": "formal" } },
51
+ { "delay": 20000, "type": "code_generated", "data": { "path": "src/models/todo.ts", "language": "typescript", "content": "import { PrismaClient, Todo } from '@prisma/client';\n\nconst prisma = new PrismaClient();\n\nexport type TodoWithoutUser = Omit<Todo, 'userId'>;\n\nexport async function getUserTodos(\n userId: string,\n limit = 50,\n offset = 0\n): Promise<TodoWithoutUser[]> {\n return prisma.todo.findMany({\n where: { userId },\n orderBy: { createdAt: 'desc' },\n take: limit,\n skip: offset,\n select: {\n id: true,\n title: true,\n completed: true,\n createdAt: true,\n updatedAt: true\n }\n });\n}\n\nexport async function countUserTodos(userId: string): Promise<number> {\n return prisma.todo.count({ where: { userId } });\n}\n\nexport async function toggleTodo(\n id: string,\n userId: string\n): Promise<Todo | null> {\n const todo = await prisma.todo.findFirst({ where: { id, userId } });\n if (!todo) return null;\n return prisma.todo.update({\n where: { id },\n data: { completed: !todo.completed }\n });\n}" } },
52
+ { "delay": 20600, "type": "verification", "data": { "claim": "getUserTodos applies pagination with limit and offset", "result": "pass", "method": "llm" } },
53
+ { "delay": 21000, "type": "verification", "data": { "claim": "toggleTodo checks ownership before update", "result": "pass", "method": "formal" } },
54
+ { "delay": 21400, "type": "verification", "data": { "claim": "Missing pagination on list endpoint", "result": "fail", "method": "llm" } },
55
+ { "delay": 21800, "type": "verification", "data": { "claim": "DELETE validates ownership before removal", "result": "pass", "method": "formal" } },
56
+ { "delay": 22200, "type": "task_update", "data": { "id": "task-7", "status": "completed" } },
57
+
58
+ { "delay": 23000, "type": "task_add", "data": { "id": "task-8", "title": "Build UI pages", "agent": "CodeGen", "status": "in_progress" } },
59
+ { "delay": 23500, "type": "code_generated", "data": { "path": "src/pages/LoginPage.tsx", "language": "tsx", "content": "import { useState, FormEvent } from 'react';\nimport { useNavigate } from 'react-router-dom';\n\nexport function LoginPage() {\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n const navigate = useNavigate();\n\n async function handleSubmit(e: FormEvent) {\n e.preventDefault();\n setError(null);\n setLoading(true);\n try {\n const res = await fetch('/api/auth/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email, password })\n });\n if (!res.ok) {\n const data = await res.json();\n throw new Error(data.error || 'Login failed');\n }\n const { token } = await res.json();\n localStorage.setItem('token', token);\n navigate('/todos');\n } catch (err: any) {\n setError(err.message);\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <form onSubmit={handleSubmit}>\n <h1>Sign In</h1>\n {error && <p className=\"error\">{error}</p>}\n <input type=\"email\" value={email} onChange={e => setEmail(e.target.value)} placeholder=\"Email\" required />\n <input type=\"password\" value={password} onChange={e => setPassword(e.target.value)} placeholder=\"Password\" required />\n <button type=\"submit\" disabled={loading}>{loading ? 'Signing in...' : 'Sign In'}</button>\n </form>\n );\n}" } },
60
+ { "delay": 24200, "type": "verification", "data": { "claim": "Login form disables button during submission", "result": "pass", "method": "llm" } },
61
+ { "delay": 24600, "type": "verification", "data": { "claim": "Token stored in localStorage after login", "result": "pass", "method": "formal" } },
62
+ { "delay": 25200, "type": "code_generated", "data": { "path": "src/pages/TodoList.tsx", "language": "tsx", "content": "import { useState, useEffect } from 'react';\n\ninterface Todo {\n id: string;\n title: string;\n completed: boolean;\n createdAt: string;\n}\n\nexport function TodoList() {\n const [todos, setTodos] = useState<Todo[]>([]);\n const [newTitle, setNewTitle] = useState('');\n const token = localStorage.getItem('token');\n const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };\n\n useEffect(() => {\n fetch('/api/todos', { headers }).then(r => r.json()).then(setTodos);\n }, []);\n\n async function addTodo() {\n if (!newTitle.trim()) return;\n const res = await fetch('/api/todos', {\n method: 'POST', headers,\n body: JSON.stringify({ title: newTitle })\n });\n const todo = await res.json();\n setTodos(prev => [todo, ...prev]);\n setNewTitle('');\n }\n\n async function toggle(id: string) {\n const res = await fetch(`/api/todos/${id}`, { method: 'PATCH', headers });\n const updated = await res.json();\n setTodos(prev => prev.map(t => t.id === id ? updated : t));\n }\n\n async function remove(id: string) {\n await fetch(`/api/todos/${id}`, { method: 'DELETE', headers });\n setTodos(prev => prev.filter(t => t.id !== id));\n }\n\n return (\n <div>\n <h1>My Todos</h1>\n <div className=\"add-form\">\n <input value={newTitle} onChange={e => setNewTitle(e.target.value)} placeholder=\"What needs to be done?\" onKeyDown={e => e.key === 'Enter' && addTodo()} />\n <button onClick={addTodo}>Add</button>\n </div>\n <ul className=\"todo-list\">\n {todos.map(todo => (\n <li key={todo.id} className={todo.completed ? 'completed' : ''}>\n <input type=\"checkbox\" checked={todo.completed} onChange={() => toggle(todo.id)} />\n <span>{todo.title}</span>\n <button onClick={() => remove(todo.id)}>Delete</button>\n </li>\n ))}\n </ul>\n </div>\n );\n}" } },
63
+ { "delay": 25800, "type": "verification", "data": { "claim": "TodoList includes Authorization header on all requests", "result": "pass", "method": "formal" } },
64
+ { "delay": 26200, "type": "verification", "data": { "claim": "Optimistic UI update after toggle", "result": "pass", "method": "llm" } },
65
+ { "delay": 26600, "type": "verification", "data": { "claim": "Delete removes item from local state", "result": "pass", "method": "llm" } },
66
+ { "delay": 27200, "type": "task_update", "data": { "id": "task-8", "status": "completed" } },
67
+
68
+ { "delay": 28000, "type": "phase_change", "data": { "phase": "build_verifying" } },
69
+ { "delay": 28300, "type": "verification", "data": { "claim": "All routes use requireAuth middleware", "result": "pass", "method": "formal" } },
70
+ { "delay": 28600, "type": "verification", "data": { "claim": "Prisma schema matches API field names", "result": "pass", "method": "formal" } },
71
+ { "delay": 28900, "type": "verification", "data": { "claim": "Error responses use consistent JSON format", "result": "pass", "method": "llm" } },
72
+ { "delay": 29200, "type": "verification", "data": { "claim": "No raw SQL — all queries via Prisma ORM", "result": "pass", "method": "formal" } },
73
+ { "delay": 29500, "type": "verification", "data": { "claim": "Input validation on all POST/PATCH endpoints", "result": "pass", "method": "formal" } },
74
+ { "delay": 29800, "type": "verification", "data": { "claim": "JWT_SECRET loaded from environment variable", "result": "pass", "method": "formal" } },
75
+ { "delay": 30200, "type": "verification", "data": { "claim": "Password not included in user response objects", "result": "pass", "method": "llm" } },
76
+ { "delay": 30600, "type": "verification", "data": { "claim": "Rate limiting on auth endpoints", "result": "fail", "method": "llm" } },
77
+ { "delay": 31000, "type": "verification", "data": { "claim": "CORS configured for frontend origin", "result": "pass", "method": "llm" } },
78
+ { "delay": 31400, "type": "verification", "data": { "claim": "Prisma client instantiated once per module", "result": "pass", "method": "formal" } },
79
+
80
+ { "delay": 31700, "type": "phase_change", "data": { "phase": "build_repairing" } },
81
+ { "delay": 32000, "type": "task_add", "data": { "id": "task-9", "title": "Fix rate limiting on auth endpoints", "agent": "CodeGen", "status": "in_progress" } },
82
+ { "delay": 32600, "type": "code_generated", "data": { "path": "src/middleware/rate-limiter.ts", "language": "typescript", "content": "import rateLimit from 'express-rate-limit';\n\nexport const authLimiter = rateLimit({\n windowMs: 15 * 60 * 1000,\n max: 5,\n standardHeaders: true,\n legacyHeaders: false,\n message: { error: 'Too many login attempts, please try again later' }\n});\n\nexport const apiLimiter = rateLimit({\n windowMs: 60 * 1000,\n max: 100,\n standardHeaders: true,\n legacyHeaders: false,\n message: { error: 'Rate limit exceeded' }\n});" } },
83
+ { "delay": 33100, "type": "verification", "data": { "claim": "Rate limiting applied to auth endpoints", "result": "pass", "method": "formal" } },
84
+ { "delay": 33400, "type": "task_update", "data": { "id": "task-9", "status": "completed" } },
85
+
86
+ { "delay": 34000, "type": "phase_change", "data": { "phase": "cross_verifying" } },
87
+ { "delay": 34400, "type": "verification", "data": { "claim": "Auth middleware applied to todo routes", "result": "pass", "method": "formal" } },
88
+ { "delay": 34800, "type": "verification", "data": { "claim": "Foreign key constraint enforced in queries", "result": "pass", "method": "formal" } },
89
+ { "delay": 35200, "type": "verification", "data": { "claim": "Login page redirects to todo list on success", "result": "pass", "method": "llm" } },
90
+ { "delay": 35600, "type": "verification", "data": { "claim": "Logout clears JWT from client storage", "result": "pass", "method": "llm" } },
91
+ { "delay": 36000, "type": "verification", "data": { "claim": "Error responses use consistent format across all endpoints", "result": "pass", "method": "llm" } },
92
+
93
+ { "delay": 38000, "type": "phase_change", "data": { "phase": "finalizing" } },
94
+ { "delay": 39000, "type": "agent_activate", "data": { "name": "CodeGen", "role": "code", "status": "idle", "trust": 0.93 } },
95
+ { "delay": 39200, "type": "agent_activate", "data": { "name": "Reviewer", "role": "review", "status": "idle", "trust": 0.94 } },
96
+ { "delay": 39400, "type": "agent_activate", "data": { "name": "Tester", "role": "test", "status": "idle", "trust": 0.91 } },
97
+
98
+ { "delay": 40500, "type": "phase_change", "data": { "phase": "completed" } },
99
+ { "delay": 40700, "type": "agent_activate", "data": { "name": "Coordinator", "role": "coordinator", "status": "idle", "trust": 0.96 } },
100
+
101
+ { "delay": 43000, "type": "phase_change", "data": { "phase": "failed" } },
102
+ { "delay": 45000, "type": "phase_change", "data": { "phase": "completed" } }
103
+ ]
@@ -0,0 +1,222 @@
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>Assay Verified Agent Runtime</title>
7
+ <link rel="stylesheet" href="css/style.css">
8
+ </head>
9
+ <body>
10
+ <div id="app">
11
+ <!-- Header with input bar -->
12
+ <header id="header">
13
+ <div id="input-bar">
14
+ <input
15
+ type="text"
16
+ id="prompt-input"
17
+ placeholder="Describe an application to build..."
18
+ autocomplete="off"
19
+ >
20
+ <button id="submit-btn">Build</button>
21
+ </div>
22
+ <div id="global-clock" class="global-clock hidden">
23
+ <span class="clock-value" id="clock-value">00:00</span>
24
+ <span class="clock-label">elapsed</span>
25
+ </div>
26
+ <button id="mute-toggle" title="Toggle voice narration">&#x1f50a;</button>
27
+ </header>
28
+
29
+ <!-- Plan summary overlay (shown briefly before auto-approve) -->
30
+ <div id="plan-overlay"></div>
31
+
32
+ <!-- Completion overlay with launch button -->
33
+ <div id="completion-overlay"></div>
34
+
35
+ <!-- Three.js orb visualization -->
36
+ <div id="orb-container"></div>
37
+
38
+ <!-- Timeline panel — right side -->
39
+ <div id="timeline-panel">
40
+ <div id="metrics-bar">
41
+ <div class="metric">
42
+ <span class="metric-value" id="metric-tasks">0/0</span>
43
+ <span class="metric-label">Tasks</span>
44
+ </div>
45
+ <div class="metric">
46
+ <span class="metric-value" id="metric-claims">0</span>
47
+ <span class="metric-label">Claims</span>
48
+ </div>
49
+ <div class="metric">
50
+ <span class="metric-value" id="metric-failed">0</span>
51
+ <span class="metric-label">Caught</span>
52
+ </div>
53
+ <div class="metric">
54
+ <span class="metric-value" id="metric-files">0</span>
55
+ <span class="metric-label">Files</span>
56
+ </div>
57
+ </div>
58
+ <div id="timeline-steps"></div>
59
+ </div>
60
+
61
+ <!-- Code streaming panel — bottom -->
62
+ <div id="code-panel">
63
+ <div id="code-tabs"></div>
64
+ <div id="code-content"></div>
65
+ </div>
66
+ </div>
67
+
68
+ <!-- CDN imports — no build step -->
69
+ <script type="importmap">
70
+ {
71
+ "imports": {
72
+ "three": "https://cdn.jsdelivr.net/npm/three@0.162.0/build/three.module.js",
73
+ "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.162.0/examples/jsm/"
74
+ }
75
+ }
76
+ </script>
77
+
78
+ <!-- Load modules -->
79
+ <script type="module" src="js/state.js"></script>
80
+ <script type="module" src="js/orb.js"></script>
81
+ <script type="module" src="js/timeline.js"></script>
82
+ <script type="module" src="js/code-panel.js"></script>
83
+ <script type="module" src="js/voice.js"></script>
84
+ <script type="module" src="js/question-cards.js"></script>
85
+ <script type="module" src="js/chat.js"></script>
86
+ <script type="module" src="js/sse-client.js"></script>
87
+ <script type="module" src="js/demo-mode.js"></script>
88
+
89
+ <!-- Bootstrap -->
90
+ <script type="module">
91
+ import './js/state.js';
92
+ import { initOrb } from './js/orb.js';
93
+ import { initTimeline } from './js/timeline.js';
94
+ import { initCodePanel } from './js/code-panel.js';
95
+ import { initVoice } from './js/voice.js';
96
+ import { initSSEClient } from './js/sse-client.js';
97
+ import { initDemoMode, startDemo } from './js/demo-mode.js';
98
+
99
+ // ── Config ──
100
+ // API server URL — override with ?api=http://host:port
101
+ // API key — override with ?key=xxx or server auto-generates one on startup
102
+ const params = new URLSearchParams(window.location.search);
103
+ const API_BASE = params.get('api') || 'http://localhost:3800';
104
+ const API_KEY = params.get('key') || '';
105
+
106
+ // Initialize all modules
107
+ try { initOrb(); } catch (e) { console.warn('[Boot] Orb init failed (no WebGL?):', e.message); }
108
+ initTimeline();
109
+ initCodePanel();
110
+ initVoice();
111
+ initSSEClient(API_BASE, API_KEY);
112
+ initDemoMode();
113
+
114
+ // Input bar logic
115
+ const input = document.getElementById('prompt-input');
116
+ const btn = document.getElementById('submit-btn');
117
+
118
+ // Derive a sensible AppDescription from a natural-language prompt
119
+ function buildAppDescription(prompt) {
120
+ // Extract a short name from the prompt
121
+ const name = prompt
122
+ .replace(/^(build|create|make|generate)\s+(a|an|me|my)?\s*/i, '')
123
+ .split(/[.,!?\n]/)[0]
124
+ .trim()
125
+ .slice(0, 50)
126
+ .replace(/\s+/g, '-')
127
+ .toLowerCase() || 'my-app';
128
+
129
+ // Detect features from the prompt
130
+ const features = [];
131
+ const lower = prompt.toLowerCase();
132
+ if (/auth|login|signup|sign.?up|register|password/.test(lower)) features.push('User authentication with email/password');
133
+ if (/todo|task|checklist/.test(lower)) features.push('Todo/task CRUD with completion tracking');
134
+ if (/blog|post|article|cms/.test(lower)) features.push('Blog post management with markdown');
135
+ if (/chat|message|real.?time/.test(lower)) features.push('Real-time chat messaging');
136
+ if (/e.?commerce|shop|cart|product|payment/.test(lower)) features.push('Product catalog with shopping cart');
137
+ if (/dashboard|admin|analytics/.test(lower)) features.push('Admin dashboard with analytics');
138
+ if (/api|rest|endpoint/.test(lower)) features.push('REST API endpoints');
139
+ if (/search|filter/.test(lower)) features.push('Search and filtering');
140
+ if (/upload|file|image/.test(lower)) features.push('File upload handling');
141
+ if (/notification|email|alert/.test(lower)) features.push('Notification system');
142
+ // If nothing matched, add the whole prompt as a feature
143
+ if (features.length === 0) features.push(prompt);
144
+
145
+ // Detect tech preferences or default
146
+ let framework = 'next.js';
147
+ let database = 'supabase';
148
+ if (/express|node/.test(lower)) framework = 'express';
149
+ if (/svelte/.test(lower)) framework = 'sveltekit';
150
+ if (/electron|desktop/.test(lower)) framework = 'electron';
151
+ if (/postgres(?!.*supa)/.test(lower)) database = 'postgresql';
152
+ if (/sqlite/.test(lower)) database = 'sqlite';
153
+
154
+ return {
155
+ name,
156
+ description: prompt,
157
+ techStack: {
158
+ language: 'typescript',
159
+ framework,
160
+ database,
161
+ styling: 'tailwind',
162
+ },
163
+ features,
164
+ };
165
+ }
166
+
167
+ btn.addEventListener('click', async () => {
168
+ const prompt = input.value.trim();
169
+ if (!prompt) return;
170
+
171
+ // Reset state for new run
172
+ AppState.reset();
173
+ input.disabled = true;
174
+ btn.disabled = true;
175
+
176
+ // Force demo mode
177
+ if (params.get('demo') === 'true') {
178
+ startDemo(prompt);
179
+ return;
180
+ }
181
+
182
+ // Try live API
183
+ const appDesc = buildAppDescription(prompt);
184
+ const headers = { 'Content-Type': 'application/json' };
185
+ if (API_KEY) headers['Authorization'] = `Bearer ${API_KEY}`;
186
+
187
+ try {
188
+ const res = await fetch(`${API_BASE}/api/v1/app/create`, {
189
+ method: 'POST',
190
+ headers,
191
+ body: JSON.stringify({ ...appDesc, autoApprove: false }),
192
+ });
193
+
194
+ if (!res.ok) {
195
+ const err = await res.text();
196
+ throw new Error(`API ${res.status}: ${err}`);
197
+ }
198
+
199
+ const data = await res.json();
200
+ window.__appId = data.id || data.sessionId;
201
+ // Connect SSE BEFORE updating phase to avoid missing early events
202
+ window.__connectSSE(window.__appId);
203
+ AppState.update({ phase: 'initializing' });
204
+ AppState.addLog({ type: 'system', message: `Session started: ${window.__appId}` });
205
+ } catch (err) {
206
+ console.warn('[Boot] API unavailable, falling back to demo mode:', err.message);
207
+ startDemo(prompt);
208
+ }
209
+ });
210
+
211
+ input.addEventListener('keydown', (e) => {
212
+ if (e.key === 'Enter') btn.click();
213
+ });
214
+
215
+ // Auto-start demo mode if ?demo=true
216
+ if (params.get('demo') === 'true' && !input.value) {
217
+ input.value = 'Build a todo app with authentication';
218
+ setTimeout(() => btn.click(), 1000);
219
+ }
220
+ </script>
221
+ </body>
222
+ </html>
@@ -0,0 +1,292 @@
1
+ // chat.js — Conversational chat for requirements gathering
2
+ // Renders in #plan-overlay, replaces question-cards.js for planning phase
3
+
4
+ const AppState = window.AppState;
5
+
6
+ let chatConfig = { apiBase: '', apiKey: '' };
7
+ let currentSessionId = null;
8
+ let pendingCardAnswers = [];
9
+
10
+ /**
11
+ * Configure API connection (called from sse-client.js)
12
+ */
13
+ export function configureChat(apiBase, apiKey) {
14
+ chatConfig.apiBase = apiBase || '';
15
+ chatConfig.apiKey = apiKey || '';
16
+ }
17
+
18
+ /**
19
+ * Open the chat interface in #plan-overlay.
20
+ * Called once when the first chat_message SSE event arrives.
21
+ */
22
+ export function openChat(sessionId) {
23
+ const overlay = document.getElementById('plan-overlay');
24
+ if (!overlay) return;
25
+
26
+ currentSessionId = sessionId;
27
+ pendingCardAnswers = [];
28
+
29
+ overlay.innerHTML = `
30
+ <div class="chat-container">
31
+ <div class="chat-messages" id="chat-messages"></div>
32
+ <div class="chat-input-bar">
33
+ <input
34
+ type="text"
35
+ id="chat-input"
36
+ placeholder="Type a message..."
37
+ autocomplete="off"
38
+ >
39
+ <button id="chat-send-btn">Send</button>
40
+ </div>
41
+ </div>
42
+ `;
43
+
44
+ overlay.classList.add('visible');
45
+
46
+ // Bind send button
47
+ const sendBtn = document.getElementById('chat-send-btn');
48
+ const chatInput = document.getElementById('chat-input');
49
+
50
+ sendBtn.addEventListener('click', () => sendMessage());
51
+ chatInput.addEventListener('keydown', (e) => {
52
+ if (e.key === 'Enter') sendMessage();
53
+ });
54
+
55
+ // Hide the header input bar during chat
56
+ const header = document.getElementById('header');
57
+ if (header) header.classList.add('chat-active');
58
+ }
59
+
60
+ /**
61
+ * Add an architect message to the chat.
62
+ * Called on each chat_message SSE event.
63
+ */
64
+ export function addArchitectMessage(data) {
65
+ let messagesEl = document.getElementById('chat-messages');
66
+ if (!messagesEl) {
67
+ // Chat not open yet — open it first
68
+ openChat(data.sessionId || window.__appId);
69
+ messagesEl = document.getElementById('chat-messages');
70
+ if (!messagesEl) return; // openChat failed, bail
71
+ }
72
+
73
+ const msgDiv = document.createElement('div');
74
+ msgDiv.className = 'chat-msg chat-msg-architect';
75
+
76
+ // Message text
77
+ const textDiv = document.createElement('div');
78
+ textDiv.className = 'chat-msg-text';
79
+ textDiv.textContent = data.content;
80
+ msgDiv.appendChild(textDiv);
81
+
82
+ // Structured cards (if any)
83
+ if (data.cards && data.cards.length > 0) {
84
+ const cardsDiv = document.createElement('div');
85
+ cardsDiv.className = 'chat-cards';
86
+
87
+ for (const card of data.cards) {
88
+ const cardEl = renderCard(card);
89
+ cardsDiv.appendChild(cardEl);
90
+ }
91
+
92
+ msgDiv.appendChild(cardsDiv);
93
+ }
94
+
95
+ // "Start Building" button (if ready)
96
+ if (data.readiness && data.readiness.ready) {
97
+ const buildBtn = document.createElement('button');
98
+ buildBtn.className = 'chat-build-btn';
99
+ buildBtn.textContent = 'Start Building';
100
+ buildBtn.addEventListener('click', () => startBuilding());
101
+
102
+ const summaryDiv = document.createElement('div');
103
+ summaryDiv.className = 'chat-ready-summary';
104
+ summaryDiv.textContent = data.readiness.summary || '';
105
+
106
+ msgDiv.appendChild(summaryDiv);
107
+ msgDiv.appendChild(buildBtn);
108
+ }
109
+
110
+ messagesEl.appendChild(msgDiv);
111
+ messagesEl.scrollTop = messagesEl.scrollHeight;
112
+ }
113
+
114
+ /**
115
+ * Close the chat and proceed to build phase.
116
+ */
117
+ export function closeChat() {
118
+ const overlay = document.getElementById('plan-overlay');
119
+ if (overlay) {
120
+ overlay.classList.remove('visible');
121
+ setTimeout(() => { overlay.innerHTML = ''; }, 500);
122
+ }
123
+
124
+ // Restore header input bar
125
+ const header = document.getElementById('header');
126
+ if (header) header.classList.remove('chat-active');
127
+
128
+ currentSessionId = null;
129
+ pendingCardAnswers = [];
130
+ }
131
+
132
+ // ── Internal ──
133
+
134
+ function renderCard(card) {
135
+ const cardEl = document.createElement('div');
136
+ cardEl.className = 'chat-card';
137
+ cardEl.dataset.cardId = card.id;
138
+
139
+ const headerEl = document.createElement('div');
140
+ headerEl.className = 'chat-card-header';
141
+ headerEl.textContent = card.header || card.question;
142
+ cardEl.appendChild(headerEl);
143
+
144
+ const questionEl = document.createElement('div');
145
+ questionEl.className = 'chat-card-question';
146
+ questionEl.textContent = card.question;
147
+ cardEl.appendChild(questionEl);
148
+
149
+ const optionsEl = document.createElement('div');
150
+ optionsEl.className = 'chat-card-options';
151
+
152
+ for (const opt of card.options) {
153
+ const optEl = document.createElement('div');
154
+ optEl.className = 'chat-card-option';
155
+ optEl.dataset.cardId = card.id;
156
+ optEl.dataset.label = opt.label;
157
+ optEl.dataset.multi = card.multiSelect ? 'true' : 'false';
158
+
159
+ const labelEl = document.createElement('div');
160
+ labelEl.className = 'chat-card-option-label';
161
+ labelEl.textContent = opt.label;
162
+ optEl.appendChild(labelEl);
163
+
164
+ const descEl = document.createElement('div');
165
+ descEl.className = 'chat-card-option-desc';
166
+ descEl.textContent = opt.description;
167
+ optEl.appendChild(descEl);
168
+
169
+ optEl.addEventListener('click', () => handleCardOptionClick(optEl, card));
170
+ optionsEl.appendChild(optEl);
171
+ }
172
+
173
+ cardEl.appendChild(optionsEl);
174
+ return cardEl;
175
+ }
176
+
177
+ function handleCardOptionClick(optEl, card) {
178
+ const cardId = optEl.dataset.cardId;
179
+ const label = optEl.dataset.label;
180
+ const isMulti = optEl.dataset.multi === 'true';
181
+
182
+ if (isMulti) {
183
+ optEl.classList.toggle('selected');
184
+ } else {
185
+ // Deselect siblings
186
+ const siblings = optEl.parentElement.querySelectorAll('.chat-card-option');
187
+ siblings.forEach(s => s.classList.remove('selected'));
188
+ optEl.classList.add('selected');
189
+ }
190
+
191
+ // Update pending card answers
192
+ const existingIdx = pendingCardAnswers.findIndex(a => a.questionId === cardId);
193
+ if (existingIdx >= 0) {
194
+ pendingCardAnswers.splice(existingIdx, 1);
195
+ }
196
+
197
+ // Collect all selected options for this card
198
+ const cardEl = optEl.closest('.chat-card');
199
+ const selectedOpts = cardEl.querySelectorAll('.chat-card-option.selected');
200
+ const selectedLabels = Array.from(selectedOpts).map(el => el.dataset.label);
201
+
202
+ if (selectedLabels.length > 0) {
203
+ pendingCardAnswers.push({
204
+ questionId: cardId,
205
+ selectedOptions: selectedLabels,
206
+ });
207
+ }
208
+ }
209
+
210
+ async function sendMessage() {
211
+ const input = document.getElementById('chat-input');
212
+ if (!input) return;
213
+
214
+ const message = input.value.trim();
215
+ if (!message && pendingCardAnswers.length === 0) return;
216
+
217
+ const sessionId = currentSessionId || window.__appId;
218
+ if (!sessionId) return;
219
+
220
+ // Add user message to chat
221
+ const messagesEl = document.getElementById('chat-messages');
222
+ if (messagesEl && message) {
223
+ const msgDiv = document.createElement('div');
224
+ msgDiv.className = 'chat-msg chat-msg-user';
225
+ const textDiv = document.createElement('div');
226
+ textDiv.className = 'chat-msg-text';
227
+ textDiv.textContent = message;
228
+ msgDiv.appendChild(textDiv);
229
+ messagesEl.appendChild(msgDiv);
230
+ messagesEl.scrollTop = messagesEl.scrollHeight;
231
+ }
232
+
233
+ // Clear input
234
+ input.value = '';
235
+
236
+ // Build request body
237
+ const body = { message: message || '' };
238
+ if (pendingCardAnswers.length > 0) {
239
+ body.cardAnswers = [...pendingCardAnswers];
240
+ pendingCardAnswers = [];
241
+ }
242
+
243
+ // Disable all card options after submitting
244
+ const allCards = document.querySelectorAll('.chat-card');
245
+ allCards.forEach(card => {
246
+ card.querySelectorAll('.chat-card-option').forEach(opt => {
247
+ opt.style.pointerEvents = 'none';
248
+ opt.style.opacity = '0.6';
249
+ });
250
+ });
251
+
252
+ // Send to API
253
+ const headers = { 'Content-Type': 'application/json' };
254
+ if (chatConfig.apiKey) headers['Authorization'] = `Bearer ${chatConfig.apiKey}`;
255
+
256
+ try {
257
+ const res = await fetch(`${chatConfig.apiBase}/api/v1/app/${sessionId}/chat`, {
258
+ method: 'POST',
259
+ headers,
260
+ body: JSON.stringify(body),
261
+ });
262
+
263
+ if (!res.ok) {
264
+ console.error('[Chat] Send failed:', res.status);
265
+ AppState.addLog({ level: 'error', message: `Chat send failed: ${res.status}` });
266
+ }
267
+ } catch (err) {
268
+ console.error('[Chat] Send error:', err);
269
+ AppState.addLog({ level: 'error', message: `Chat error: ${err.message}` });
270
+ }
271
+ }
272
+
273
+ async function startBuilding() {
274
+ const sessionId = currentSessionId || window.__appId;
275
+ if (!sessionId) return;
276
+
277
+ const headers = { 'Content-Type': 'application/json' };
278
+ if (chatConfig.apiKey) headers['Authorization'] = `Bearer ${chatConfig.apiKey}`;
279
+
280
+ try {
281
+ await fetch(`${chatConfig.apiBase}/api/v1/app/${sessionId}/chat`, {
282
+ method: 'POST',
283
+ headers,
284
+ body: JSON.stringify({ message: '__START_BUILD__' }),
285
+ });
286
+ } catch (err) {
287
+ console.error('[Chat] Start build error:', err);
288
+ }
289
+
290
+ closeChat();
291
+ AppState.addLog({ level: 'success', message: 'Requirements confirmed — starting build...' });
292
+ }