diff --git a/agents/01KP22WA8NVMYWZ85XHAVP0RG4.json b/agents/builder.json similarity index 95% rename from agents/01KP22WA8NVMYWZ85XHAVP0RG4.json rename to agents/builder.json index a7a7328..db6b18d 100644 --- a/agents/01KP22WA8NVMYWZ85XHAVP0RG4.json +++ b/agents/builder.json @@ -32,15 +32,15 @@ "type": "emoji" }, "variables": { - "zWQheH5BoVPriUiE": { - "id": "zWQheH5BoVPriUiE", + "company-context": { + "id": "company-context", "rank": "V", "type": "string" } }, "addToKnowledgeProjectVariables": [ { - "id": "zWQheH5BoVPriUiE", + "id": "company-context", "type": "agent-template-variable-input-type" } ] diff --git a/agents/01KP22WA8N4VKKY6WB2XX4CB0A.json b/agents/critic.json similarity index 95% rename from agents/01KP22WA8N4VKKY6WB2XX4CB0A.json rename to agents/critic.json index 02ce446..a19a154 100644 --- a/agents/01KP22WA8N4VKKY6WB2XX4CB0A.json +++ b/agents/critic.json @@ -32,15 +32,15 @@ "type": "emoji" }, "variables": { - "zWQheH5BoVPriUiE": { - "id": "zWQheH5BoVPriUiE", + "company-context": { + "id": "company-context", "rank": "V", "type": "string" } }, "addToKnowledgeProjectVariables": [ { - "id": "zWQheH5BoVPriUiE", + "id": "company-context", "type": "agent-template-variable-input-type" } ] diff --git a/agents/01KP22WA8N1S8JDRDP55D87NX5.json b/agents/editor.json similarity index 94% rename from agents/01KP22WA8N1S8JDRDP55D87NX5.json rename to agents/editor.json index b907c64..256bcca 100644 --- a/agents/01KP22WA8N1S8JDRDP55D87NX5.json +++ b/agents/editor.json @@ -32,15 +32,15 @@ "type": "emoji" }, "variables": { - "zWQheH5BoVPriUiE": { - "id": "zWQheH5BoVPriUiE", + "company-context": { + "id": "company-context", "rank": "V", "type": "string" } }, "addToKnowledgeProjectVariables": [ { - "id": "zWQheH5BoVPriUiE", + "id": "company-context", "type": "agent-template-variable-input-type" } ] diff --git a/agents/01KP22WA8NDTKXX5K4NFZE32YC.json b/agents/researcher.json similarity index 94% rename from agents/01KP22WA8NDTKXX5K4NFZE32YC.json rename to agents/researcher.json index 1e46f95..fb55c23 100644 --- a/agents/01KP22WA8NDTKXX5K4NFZE32YC.json +++ b/agents/researcher.json @@ -32,15 +32,15 @@ "type": "emoji" }, "variables": { - "zWQheH5BoVPriUiE": { - "id": "zWQheH5BoVPriUiE", + "company-context": { + "id": "company-context", "rank": "V", "type": "string" } }, "addToKnowledgeProjectVariables": [ { - "id": "zWQheH5BoVPriUiE", + "id": "company-context", "type": "agent-template-variable-input-type" } ] diff --git a/agents/01KP22WA8NPMP922SAHDKHQS8Q.json b/agents/strategist.json similarity index 95% rename from agents/01KP22WA8NPMP922SAHDKHQS8Q.json rename to agents/strategist.json index 2f7cc42..684d721 100644 --- a/agents/01KP22WA8NPMP922SAHDKHQS8Q.json +++ b/agents/strategist.json @@ -32,15 +32,15 @@ "type": "emoji" }, "variables": { - "zWQheH5BoVPriUiE": { - "id": "zWQheH5BoVPriUiE", + "company-context": { + "id": "company-context", "rank": "V", "type": "string" } }, "addToKnowledgeProjectVariables": [ { - "id": "zWQheH5BoVPriUiE", + "id": "company-context", "type": "agent-template-variable-input-type" } ] diff --git a/apps/default.json b/apps/default.json index 9a4e426..5162a6d 100644 --- a/apps/default.json +++ b/apps/default.json @@ -107,7 +107,7 @@ "directory": { "workspace.ts": { "file": { - "contents": "// Cortex — Workspace DNA constants\n// All agents, projects, and automations in the space\n\nexport const AGENTS = [\n {\n id: '01KP22WA8NPMP922SAHDKHQS8Q',\n name: 'Strategist',\n emoji: '🧭',\n color: 'from-violet-500/20 to-purple-600/20',\n border: 'border-violet-500/30',\n badge: 'bg-violet-500/10 text-violet-400',\n description: 'Surfaces tradeoffs you haven\\'t named. Turns false binaries into real options.',\n role: 'Options · Tradeoffs · Conditions',\n taskadeUrl: 'https://staging.taskade.dev',\n },\n {\n id: '01KP22WA8N4VKKY6WB2XX4CB0A',\n name: 'Critic',\n emoji: '⚔️',\n color: 'from-red-500/20 to-rose-600/20',\n border: 'border-red-500/30',\n badge: 'bg-red-500/10 text-red-400',\n description: 'Argues against every plan. Finds the hole before the market does.',\n role: 'Pressure Test · Steel Man · Gaps',\n taskadeUrl: 'https://staging.taskade.dev',\n },\n {\n id: '01KP22WA8NDTKXX5K4NFZE32YC',\n name: 'Researcher',\n emoji: '🔬',\n color: 'from-cyan-500/20 to-blue-600/20',\n border: 'border-cyan-500/30',\n badge: 'bg-cyan-500/10 text-cyan-400',\n description: 'Verifies claims with evidence. Never confuses confidence with correctness.',\n role: 'Evidence · Claims · Sources',\n taskadeUrl: 'https://staging.taskade.dev',\n },\n {\n id: '01KP22WA8N1S8JDRDP55D87NX5',\n name: 'Editor',\n emoji: '✍️',\n color: 'from-amber-500/20 to-orange-600/20',\n border: 'border-amber-500/30',\n badge: 'bg-amber-500/10 text-amber-400',\n description: 'Cuts until only signal remains. Makes every word earn its place.',\n role: 'Clarity · Precision · Voice',\n taskadeUrl: 'https://staging.taskade.dev',\n },\n {\n id: '01KP22WA8NVMYWZ85XHAVP0RG4',\n name: 'Builder',\n emoji: '🔧',\n color: 'from-emerald-500/20 to-green-600/20',\n border: 'border-emerald-500/30',\n badge: 'bg-emerald-500/10 text-emerald-400',\n description: 'Turns vague direction into executable specs. Scoped, sequenced, ready to ship.',\n role: 'Spec · Scope · Sequence',\n taskadeUrl: 'https://staging.taskade.dev',\n },\n] as const;\n\nexport const PROJECTS = [\n {\n id: 'nNzQx9qpAitsKEMa',\n name: 'Welcome to Cortex',\n category: 'Core',\n emoji: '🏠',\n description: 'The front door. Start here.',\n taskadeUrl: 'https://staging.taskade.dev/d/nNzQx9qpAitsKEMa',\n lastModified: '2026-04-12',\n },\n {\n id: 'zWQheH5BoVPriUiE',\n name: 'Company Context',\n category: 'Core',\n emoji: '🏛️',\n description: 'Operating principles, weekly priorities, and institutional memory.',\n taskadeUrl: 'https://staging.taskade.dev/d/zWQheH5BoVPriUiE',\n lastModified: '2026-04-12',\n },\n {\n id: 'aSs5trhjYStYSydc',\n name: 'Decision Log',\n category: 'Core',\n emoji: '📋',\n description: 'Every major decision, its reasoning, and outcome.',\n taskadeUrl: 'https://staging.taskade.dev/d/aSs5trhjYStYSydc',\n lastModified: '2026-04-12',\n },\n {\n id: '7GBCCnibQz8RQWuJ',\n name: 'Library — Frameworks',\n category: 'Library',\n emoji: '🧠',\n description: 'Mental models and decision-making frameworks.',\n taskadeUrl: 'https://staging.taskade.dev/d/7GBCCnibQz8RQWuJ',\n lastModified: '2026-04-10',\n },\n {\n id: 'BFsoG1yRn72cNtBr',\n name: 'Library — References',\n category: 'Library',\n emoji: '📚',\n description: 'Essential reading, blog posts, tools, and key metric definitions.',\n taskadeUrl: 'https://staging.taskade.dev/d/BFsoG1yRn72cNtBr',\n lastModified: '2026-04-10',\n },\n {\n id: 'YoDL7iQavPKCJNJ6',\n name: 'Playbook — Launch',\n category: 'Playbooks',\n emoji: '🚀',\n description: 'How to ship. The complete launch sequence.',\n taskadeUrl: 'https://staging.taskade.dev/d/YoDL7iQavPKCJNJ6',\n lastModified: '2026-04-08',\n },\n {\n id: 'P4vqDyabfwFLYwHY',\n name: 'Playbook — Fundraise',\n category: 'Playbooks',\n emoji: '💰',\n description: 'Investor prep, pitch narrative, and term sheet playbook.',\n taskadeUrl: 'https://staging.taskade.dev/d/P4vqDyabfwFLYwHY',\n lastModified: '2026-04-08',\n },\n {\n id: 'Lz5wgBknjP4GhuFG',\n name: 'Playbook — Pricing',\n category: 'Playbooks',\n emoji: '💳',\n description: 'Pricing strategy, packaging, and model decisions.',\n taskadeUrl: 'https://staging.taskade.dev/d/Lz5wgBknjP4GhuFG',\n lastModified: '2026-04-07',\n },\n {\n id: 'opHBiT9XYgNKD1Dz',\n name: 'Playbook — Hiring',\n category: 'Playbooks',\n emoji: '🧑‍💼',\n description: 'Who to hire, when, and how to run a great interview process.',\n taskadeUrl: 'https://staging.taskade.dev/d/opHBiT9XYgNKD1Dz',\n lastModified: '2026-04-06',\n },\n {\n id: 'u5uSqqqNQFFP8SR1',\n name: 'Playbook — Support',\n category: 'Playbooks',\n emoji: '🎧',\n description: 'Customer support escalation paths and response templates.',\n taskadeUrl: 'https://staging.taskade.dev/d/u5uSqqqNQFFP8SR1',\n lastModified: '2026-04-05',\n },\n] as const;\n\nexport const AUTOMATIONS = [\n {\n id: '01KP22WA8NMWA2MCDQT43A2K1Q',\n name: 'Decision Council',\n emoji: '⚖️',\n trigger: 'Webhook',\n description: '4 agents pressure-test every major decision before you commit.',\n agents: ['Strategist', 'Critic', 'Researcher', 'Builder'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP22WA8NMWA2MCDQT43A2K1Q',\n color: 'from-violet-500/10 to-purple-500/10',\n border: 'border-violet-500/20',\n },\n {\n id: '01KP22WA8N4GFKFJKTR2CXMSJQ',\n name: 'Inbox Triage',\n emoji: '📥',\n trigger: 'Webhook',\n description: 'Researcher classifies incoming signals and appends to Company Context.',\n agents: ['Researcher'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP22WA8N4GFKFJKTR2CXMSJQ',\n color: 'from-cyan-500/10 to-blue-500/10',\n border: 'border-cyan-500/20',\n },\n {\n id: '01KP22WA8N2MNZ830GAAYZ8G9R',\n name: 'Incident Response',\n emoji: '🚨',\n trigger: 'Webhook',\n description: 'Builder specs the next 4 hours of response when an incident fires.',\n agents: ['Builder'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP22WA8N2MNZ830GAAYZ8G9R',\n color: 'from-red-500/10 to-rose-500/10',\n border: 'border-red-500/20',\n },\n {\n id: '01KP22WA8NHPV626F46CM2WP36',\n name: 'Weekly Review',\n emoji: '📊',\n trigger: 'Friday 6 PM',\n description: 'Editor and Strategist close the week with a retrospective.',\n agents: ['Editor', 'Strategist'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP22WA8NHPV626F46CM2WP36',\n color: 'from-amber-500/10 to-orange-500/10',\n border: 'border-amber-500/20',\n },\n {\n id: '01KP22WA8N1NF18NFENB9AWMFR',\n name: 'Monday Planning',\n emoji: '🗓️',\n trigger: 'Monday 8 AM',\n description: 'Strategist maps the one thing that matters most this week.',\n agents: ['Strategist'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP22WA8N1NF18NFENB9AWMFR',\n color: 'from-emerald-500/10 to-green-500/10',\n border: 'border-emerald-500/20',\n },\n {\n id: '01KP22WA8NVFMDTGDSPPQBS5XZ',\n name: 'Daily Standup',\n emoji: '☀️',\n trigger: 'Daily 9 AM',\n description: 'Morning briefing from the Builder on today\\'s priorities.',\n agents: ['Builder'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP22WA8NVFMDTGDSPPQBS5XZ',\n color: 'from-sky-500/10 to-blue-500/10',\n border: 'border-sky-500/20',\n },\n] as const;\n\nexport type Agent = typeof AGENTS[number];\nexport type Project = typeof PROJECTS[number];\nexport type Automation = typeof AUTOMATIONS[number];\n" + "contents": "// Cortex — Workspace DNA constants\n// All agents, projects, and automations in the space\n\nexport const AGENTS = [\n {\n id: '01KP2MV4A064B82EF4Q2QS2RCD',\n name: 'Strategist',\n emoji: '🧭',\n color: 'from-violet-500/20 to-purple-600/20',\n border: 'border-violet-500/30',\n badge: 'bg-violet-500/10 text-violet-400',\n description: 'Surfaces tradeoffs you haven\\'t named. Turns false binaries into real options.',\n role: 'Options · Tradeoffs · Conditions',\n taskadeUrl: 'https://staging.taskade.dev',\n },\n {\n id: '01KP2MV4A0YKPJ7EFBJ6CDB1Z8',\n name: 'Critic',\n emoji: '⚔️',\n color: 'from-red-500/20 to-rose-600/20',\n border: 'border-red-500/30',\n badge: 'bg-red-500/10 text-red-400',\n description: 'Argues against every plan. Finds the hole before the market does.',\n role: 'Pressure Test · Steel Man · Gaps',\n taskadeUrl: 'https://staging.taskade.dev',\n },\n {\n id: '01KP2MV4A02PX80CXR34F0DX7V',\n name: 'Researcher',\n emoji: '🔬',\n color: 'from-cyan-500/20 to-blue-600/20',\n border: 'border-cyan-500/30',\n badge: 'bg-cyan-500/10 text-cyan-400',\n description: 'Verifies claims with evidence. Never confuses confidence with correctness.',\n role: 'Evidence · Claims · Sources',\n taskadeUrl: 'https://staging.taskade.dev',\n },\n {\n id: '01KP2MV4A08B7NCXR1HBFS2VXN',\n name: 'Editor',\n emoji: '✍️',\n color: 'from-amber-500/20 to-orange-600/20',\n border: 'border-amber-500/30',\n badge: 'bg-amber-500/10 text-amber-400',\n description: 'Cuts until only signal remains. Makes every word earn its place.',\n role: 'Clarity · Precision · Voice',\n taskadeUrl: 'https://staging.taskade.dev',\n },\n {\n id: '01KP2MV4A0283A4MZMMMJ8WC61',\n name: 'Builder',\n emoji: '🔧',\n color: 'from-emerald-500/20 to-green-600/20',\n border: 'border-emerald-500/30',\n badge: 'bg-emerald-500/10 text-emerald-400',\n description: 'Turns vague direction into executable specs. Scoped, sequenced, ready to ship.',\n role: 'Spec · Scope · Sequence',\n taskadeUrl: 'https://staging.taskade.dev',\n },\n] as const;\n\nexport const PROJECTS = [\n {\n id: 'CynAWKJy9xLvh84b',\n name: 'Welcome to Cortex',\n category: 'Core',\n emoji: '🏠',\n description: 'The front door. Start here.',\n taskadeUrl: 'https://staging.taskade.dev/d/CynAWKJy9xLvh84b',\n lastModified: '2026-04-12',\n },\n {\n id: '8VNTPiqG9XGrbRhY',\n name: 'Company Context',\n category: 'Core',\n emoji: '🏛️',\n description: 'Operating principles, weekly priorities, and institutional memory.',\n taskadeUrl: 'https://staging.taskade.dev/d/8VNTPiqG9XGrbRhY',\n lastModified: '2026-04-12',\n },\n {\n id: 'HihBURwSkvDn1KiC',\n name: 'Decision Log',\n category: 'Core',\n emoji: '📋',\n description: 'Every major decision, its reasoning, and outcome.',\n taskadeUrl: 'https://staging.taskade.dev/d/HihBURwSkvDn1KiC',\n lastModified: '2026-04-12',\n },\n {\n id: 'yfR8rTzdecdMRdVA',\n name: 'Library — Frameworks',\n category: 'Library',\n emoji: '🧠',\n description: 'Mental models and decision-making frameworks.',\n taskadeUrl: 'https://staging.taskade.dev/d/yfR8rTzdecdMRdVA',\n lastModified: '2026-04-10',\n },\n {\n id: '2V5EdjJFqtX1P47q',\n name: 'Library — References',\n category: 'Library',\n emoji: '📚',\n description: 'Essential reading, blog posts, tools, and key metric definitions.',\n taskadeUrl: 'https://staging.taskade.dev/d/2V5EdjJFqtX1P47q',\n lastModified: '2026-04-10',\n },\n {\n id: '94bWqKa2NBNsHaPF',\n name: 'Playbook — Launch',\n category: 'Playbooks',\n emoji: '🚀',\n description: 'How to ship. The complete launch sequence.',\n taskadeUrl: 'https://staging.taskade.dev/d/94bWqKa2NBNsHaPF',\n lastModified: '2026-04-08',\n },\n {\n id: 'bUnrneNL99FkZB37',\n name: 'Playbook — Fundraise',\n category: 'Playbooks',\n emoji: '💰',\n description: 'Investor prep, pitch narrative, and term sheet playbook.',\n taskadeUrl: 'https://staging.taskade.dev/d/bUnrneNL99FkZB37',\n lastModified: '2026-04-08',\n },\n {\n id: 'wkLBPVihrkDz2QJm',\n name: 'Playbook — Pricing',\n category: 'Playbooks',\n emoji: '💳',\n description: 'Pricing strategy, packaging, and model decisions.',\n taskadeUrl: 'https://staging.taskade.dev/d/wkLBPVihrkDz2QJm',\n lastModified: '2026-04-07',\n },\n {\n id: '9Woi4ZMFXg7sqMCu',\n name: 'Playbook — Hiring',\n category: 'Playbooks',\n emoji: '🧑‍💼',\n description: 'Who to hire, when, and how to run a great interview process.',\n taskadeUrl: 'https://staging.taskade.dev/d/9Woi4ZMFXg7sqMCu',\n lastModified: '2026-04-06',\n },\n {\n id: 'kLAUAQzSC23h8c9Z',\n name: 'Playbook — Support',\n category: 'Playbooks',\n emoji: '🎧',\n description: 'Customer support escalation paths and response templates.',\n taskadeUrl: 'https://staging.taskade.dev/d/kLAUAQzSC23h8c9Z',\n lastModified: '2026-04-05',\n },\n] as const;\n\nexport const AUTOMATIONS = [\n {\n id: '01KP2MV4A14EDTENW2ANRBS2CB',\n name: 'Decision Council',\n emoji: '⚖️',\n trigger: 'Webhook',\n description: '4 agents pressure-test every major decision before you commit.',\n agents: ['Strategist', 'Critic', 'Researcher', 'Builder'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP2MV4A14EDTENW2ANRBS2CB',\n color: 'from-violet-500/10 to-purple-500/10',\n border: 'border-violet-500/20',\n },\n {\n id: '01KP2MV4A19D2VMBBBM3T1NFC1',\n name: 'Inbox Triage',\n emoji: '📥',\n trigger: 'Webhook',\n description: 'Researcher classifies incoming signals and appends to Company Context.',\n agents: ['Researcher'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP2MV4A19D2VMBBBM3T1NFC1',\n color: 'from-cyan-500/10 to-blue-500/10',\n border: 'border-cyan-500/20',\n },\n {\n id: '01KP2MV4A1G0W4BRBRS0CGWFG2',\n name: 'Incident Response',\n emoji: '🚨',\n trigger: 'Webhook',\n description: 'Builder specs the next 4 hours of response when an incident fires.',\n agents: ['Builder'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP2MV4A1G0W4BRBRS0CGWFG2',\n color: 'from-red-500/10 to-rose-500/10',\n border: 'border-red-500/20',\n },\n {\n id: '01KP2MV4A1GA1PY340WRP616PJ',\n name: 'Weekly Review',\n emoji: '📊',\n trigger: 'Friday 6 PM',\n description: 'Editor and Strategist close the week with a retrospective.',\n agents: ['Editor', 'Strategist'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP2MV4A1GA1PY340WRP616PJ',\n color: 'from-amber-500/10 to-orange-500/10',\n border: 'border-amber-500/20',\n },\n {\n id: '01KP2MV4A0GA4F26Y9Z85TV6HS',\n name: 'Monday Planning',\n emoji: '🗓️',\n trigger: 'Monday 8 AM',\n description: 'Strategist maps the one thing that matters most this week.',\n agents: ['Strategist'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP2MV4A0GA4F26Y9Z85TV6HS',\n color: 'from-emerald-500/10 to-green-500/10',\n border: 'border-emerald-500/20',\n },\n {\n id: '01KP2MV4A1SEGKJXV2BP77X9J3',\n name: 'Daily Standup',\n emoji: '☀️',\n trigger: 'Daily 9 AM',\n description: 'Morning briefing from the Builder on today\\'s priorities.',\n agents: ['Builder'],\n taskadeUrl: 'https://staging.taskade.dev/flows/01KP2MV4A1SEGKJXV2BP77X9J3',\n color: 'from-sky-500/10 to-blue-500/10',\n border: 'border-sky-500/20',\n },\n] as const;\n\nexport type Agent = typeof AGENTS[number];\nexport type Project = typeof PROJECTS[number];\nexport type Automation = typeof AUTOMATIONS[number];\n" } } } @@ -135,22 +135,22 @@ "directory": { "Council.tsx": { "file": { - "contents": "import * as React from 'react';\nimport { useChat } from '@ai-sdk/react';\nimport { createConversation, createAgentChat } from '@/lib/agent-chat/v2';\nimport { isToolUIPart } from 'ai';\nimport type { UIMessage } from 'ai';\nimport { ulid } from 'ulidx';\nimport { cn } from '@/lib/utils';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport { Scale, ChevronRight, Loader2, AlertCircle, CheckCircle2, Send } from 'lucide-react';\n\n// The 4 council agents that respond to the webhook\nconst COUNCIL_MEMBERS = [\n {\n id: '01KP22WA8NPMP922SAHDKHQS8Q',\n name: 'Strategist',\n emoji: '🧭',\n role: 'Counsel on a Decision',\n color: 'from-violet-500/10 to-purple-500/10',\n border: 'border-violet-500/30',\n headerBg: 'bg-violet-500/10',\n accent: 'text-violet-400',\n dot: 'bg-violet-400',\n },\n {\n id: '01KP22WA8N4VKKY6WB2XX4CB0A',\n name: 'Critic',\n emoji: '⚔️',\n role: 'Argue Against This',\n color: 'from-red-500/10 to-rose-500/10',\n border: 'border-red-500/30',\n headerBg: 'bg-red-500/10',\n accent: 'text-red-400',\n dot: 'bg-red-400',\n },\n {\n id: '01KP22WA8NDTKXX5K4NFZE32YC',\n name: 'Researcher',\n emoji: '🔬',\n role: 'Verify the Claim',\n color: 'from-cyan-500/10 to-blue-500/10',\n border: 'border-cyan-500/30',\n headerBg: 'bg-cyan-500/10',\n accent: 'text-cyan-400',\n dot: 'bg-cyan-400',\n },\n {\n id: '01KP22WA8NVMYWZ85XHAVP0RG4',\n name: 'Builder',\n emoji: '🔧',\n role: 'Spec It',\n color: 'from-emerald-500/10 to-green-500/10',\n border: 'border-emerald-500/30',\n headerBg: 'bg-emerald-500/10',\n accent: 'text-emerald-400',\n dot: 'bg-emerald-400',\n },\n] as const;\n\ntype AgentState =\n | { status: 'idle' }\n | { status: 'loading' }\n | { status: 'streaming'; chat: ReturnType }\n | { status: 'done'; text: string }\n | { status: 'error'; message: string };\n\n// Individual streaming panel per agent\nfunction AgentPanel({\n member,\n prompt,\n active,\n}: {\n member: typeof COUNCIL_MEMBERS[number];\n prompt: string;\n active: boolean;\n}) {\n const [chat, setChat] = React.useState | null>(null);\n const [agentState, setAgentState] = React.useState({ status: 'idle' });\n\n React.useEffect(() => {\n if (!active || !prompt) return;\n setAgentState({ status: 'loading' });\n setChat(null);\n\n let cancelled = false;\n (async () => {\n try {\n const { conversationId } = await createConversation(member.id);\n if (cancelled) return;\n const newChat = createAgentChat(member.id, conversationId);\n if (cancelled) return;\n setChat(newChat);\n setAgentState({ status: 'streaming', chat: newChat });\n } catch (e) {\n if (!cancelled) {\n setAgentState({ status: 'error', message: String(e) });\n }\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [active, prompt, member.id]);\n\n if (!active || agentState.status === 'idle') {\n return (\n \n \n
Awaiting the question…
\n \n );\n }\n\n if (agentState.status === 'loading') {\n return (\n
\n \n
\n \n Assembling…\n
\n
\n );\n }\n\n if (agentState.status === 'error') {\n return (\n
\n \n
\n \n {agentState.message}\n
\n
\n );\n }\n\n if (!chat) return null;\n\n return (\n
\n \n \n
\n );\n}\n\nfunction AgentPanelHeader({\n member,\n status,\n}: {\n member: typeof COUNCIL_MEMBERS[number];\n status: 'idle' | 'loading' | 'streaming' | 'done' | 'error';\n}) {\n return (\n
\n
\n {member.emoji}\n
\n
{member.name}
\n
{member.role}
\n
\n
\n
\n {status === 'idle' && (\n
\n )}\n {status === 'loading' && (\n \n )}\n {status === 'streaming' && (\n
\n )}\n {status === 'done' && (\n \n )}\n {status === 'error' && (\n \n )}\n
\n
\n );\n}\n\nfunction ActiveAgentChat({\n chat,\n member,\n prompt,\n}: {\n chat: ReturnType;\n member: typeof COUNCIL_MEMBERS[number];\n prompt: string;\n}) {\n const { messages, status, addToolApprovalResponse } = useChat({ chat, id: chat.id });\n const [sent, setSent] = React.useState(false);\n\n React.useEffect(() => {\n if (sent || !prompt) return;\n setSent(true);\n chat.sendMessage({\n id: ulid(),\n role: 'user',\n parts: [{ type: 'text', text: prompt }],\n });\n }, [chat, prompt, sent]);\n\n const isStreaming = status === 'submitted' || status === 'streaming';\n const assistantMessages = messages.filter((m) => m.role === 'assistant');\n\n return (\n
\n {isStreaming && assistantMessages.length === 0 && (\n
\n
\n
\n
\n
\n
\n Thinking…\n
\n )}\n {assistantMessages.map((msg) => (\n \n ))}\n
\n );\n}\n\nfunction MessageContent({\n message,\n onApprove,\n}: {\n message: UIMessage;\n onApprove: ReturnType['addToolApprovalResponse'];\n}) {\n return (\n
\n {message.parts.map((part, i) => {\n const key = `${message.id}-${i}`;\n if (part.type === 'text') {\n return (\n
\n {part.text}\n
\n );\n }\n if (isToolUIPart(part)) {\n return (\n
\n Tool: {part.toolName}\n [{part.state}]\n {part.state === 'approval-requested' && part.approval != null && (\n \n part.approval != null && onApprove({ id: part.approval.id, approved: true })}\n className=\"px-2 py-0.5 rounded bg-emerald-500/20 text-emerald-400 hover:bg-emerald-500/30 transition-colors\"\n >\n Approve\n \n part.approval != null && onApprove({ id: part.approval.id, approved: false })}\n className=\"px-2 py-0.5 rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 transition-colors\"\n >\n Deny\n \n \n )}\n
\n );\n }\n return null;\n })}\n
\n );\n}\n\nexport function Council() {\n const [decision, setDecision] = React.useState('');\n const [context, setContext] = React.useState('');\n const [convened, setConvened] = React.useState(false);\n const [activePrompt, setActivePrompt] = React.useState('');\n const [webhookFired, setWebhookFired] = React.useState(false);\n const [webhookError, setWebhookError] = React.useState(null);\n const resultsRef = React.useRef(null);\n\n const canConvene = decision.trim().length > 5;\n\n async function handleConvene() {\n if (!canConvene) return;\n\n const prompt = `Decision: ${decision.trim()}\\n\\nContext: ${context.trim() || 'No additional context provided.'}`;\n setActivePrompt(prompt);\n setConvened(true);\n setWebhookFired(false);\n setWebhookError(null);\n\n // Fire the Decision Council webhook (for Decision Log persistence)\n try {\n await fetch('/api/taskade/webhooks/01KP22WA8NMWA2MCDQT43A2K1Q/run', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ decision: decision.trim(), context: context.trim() }),\n });\n setWebhookFired(true);\n } catch {\n setWebhookError('Decision Log sync failed — live responses still active.');\n }\n\n setTimeout(() => {\n resultsRef.current?.scrollIntoView({ behavior: 'smooth' });\n }, 300);\n }\n\n function handleReset() {\n setConvened(false);\n setActivePrompt('');\n setDecision('');\n setContext('');\n setWebhookFired(false);\n setWebhookError(null);\n }\n\n return (\n
\n {/* Header */}\n
\n
\n
\n \n
\n

Decision Council

\n
\n

\n Type a decision. The council assembles — 4 agents pressure-test it from every angle before you commit.\n

\n
\n\n {/* Input form */}\n
\n
\n
\n \n setDecision(e.target.value)}\n placeholder=\"e.g. We should raise a $3M seed round now instead of staying default-alive for another 18 months.\"\n rows={3}\n disabled={convened}\n className=\"w-full px-4 py-3 rounded-xl bg-background border border-border text-sm text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-violet-500/40 focus:border-violet-500/40 resize-none disabled:opacity-60 transition-colors\"\n />\n
\n
\n \n setContext(e.target.value)}\n placeholder=\"e.g. We're at $45k MRR, growing 15% MoM, 18 months runway. Main competitor just raised $10M.\"\n rows={2}\n disabled={convened}\n className=\"w-full px-4 py-3 rounded-xl bg-background border border-border text-sm text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-violet-500/40 focus:border-violet-500/40 resize-none disabled:opacity-60 transition-colors\"\n />\n
\n
\n
\n {webhookFired && (\n <>\n \n Logged to Decision Log\n \n )}\n {webhookError && (\n <>\n \n {webhookError}\n \n )}\n
\n
\n {convened && (\n \n New Decision\n \n )}\n \n {convened ? (\n <>\n \n Council in session\n \n ) : (\n <>\n \n Convene Council\n \n \n )}\n \n
\n
\n
\n
\n\n {/* Council members (always visible, activate on convene) */}\n
\n {convened && (\n
\n
\n {COUNCIL_MEMBERS.map((m) => (\n
\n ))}\n
\n The council is deliberating…\n
\n )}\n
\n {COUNCIL_MEMBERS.map((member) => (\n \n ))}\n
\n
\n
\n );\n}\n" + "contents": "import * as React from 'react';\nimport { useChat } from '@ai-sdk/react';\nimport { createConversation, createAgentChat } from '@/lib/agent-chat/v2';\nimport { isToolUIPart } from 'ai';\nimport type { UIMessage } from 'ai';\nimport { ulid } from 'ulidx';\nimport { cn } from '@/lib/utils';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport { Scale, ChevronRight, Loader2, AlertCircle, CheckCircle2, Send } from 'lucide-react';\n\n// The 4 council agents that respond to the webhook\nconst COUNCIL_MEMBERS = [\n {\n id: '01KP2MV4A064B82EF4Q2QS2RCD',\n name: 'Strategist',\n emoji: '🧭',\n role: 'Counsel on a Decision',\n color: 'from-violet-500/10 to-purple-500/10',\n border: 'border-violet-500/30',\n headerBg: 'bg-violet-500/10',\n accent: 'text-violet-400',\n dot: 'bg-violet-400',\n },\n {\n id: '01KP2MV4A0YKPJ7EFBJ6CDB1Z8',\n name: 'Critic',\n emoji: '⚔️',\n role: 'Argue Against This',\n color: 'from-red-500/10 to-rose-500/10',\n border: 'border-red-500/30',\n headerBg: 'bg-red-500/10',\n accent: 'text-red-400',\n dot: 'bg-red-400',\n },\n {\n id: '01KP2MV4A02PX80CXR34F0DX7V',\n name: 'Researcher',\n emoji: '🔬',\n role: 'Verify the Claim',\n color: 'from-cyan-500/10 to-blue-500/10',\n border: 'border-cyan-500/30',\n headerBg: 'bg-cyan-500/10',\n accent: 'text-cyan-400',\n dot: 'bg-cyan-400',\n },\n {\n id: '01KP2MV4A0283A4MZMMMJ8WC61',\n name: 'Builder',\n emoji: '🔧',\n role: 'Spec It',\n color: 'from-emerald-500/10 to-green-500/10',\n border: 'border-emerald-500/30',\n headerBg: 'bg-emerald-500/10',\n accent: 'text-emerald-400',\n dot: 'bg-emerald-400',\n },\n] as const;\n\ntype AgentState =\n | { status: 'idle' }\n | { status: 'loading' }\n | { status: 'streaming'; chat: ReturnType }\n | { status: 'done'; text: string }\n | { status: 'error'; message: string };\n\n// Individual streaming panel per agent\nfunction AgentPanel({\n member,\n prompt,\n active,\n}: {\n member: typeof COUNCIL_MEMBERS[number];\n prompt: string;\n active: boolean;\n}) {\n const [chat, setChat] = React.useState | null>(null);\n const [agentState, setAgentState] = React.useState({ status: 'idle' });\n\n React.useEffect(() => {\n if (!active || !prompt) return;\n setAgentState({ status: 'loading' });\n setChat(null);\n\n let cancelled = false;\n (async () => {\n try {\n const { conversationId } = await createConversation(member.id);\n if (cancelled) return;\n const newChat = createAgentChat(member.id, conversationId);\n if (cancelled) return;\n setChat(newChat);\n setAgentState({ status: 'streaming', chat: newChat });\n } catch (e) {\n if (!cancelled) {\n setAgentState({ status: 'error', message: String(e) });\n }\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [active, prompt, member.id]);\n\n if (!active || agentState.status === 'idle') {\n return (\n \n \n
Awaiting the question…
\n
\n );\n }\n\n if (agentState.status === 'loading') {\n return (\n
\n \n
\n \n Assembling…\n
\n
\n );\n }\n\n if (agentState.status === 'error') {\n return (\n
\n \n
\n \n {agentState.message}\n
\n
\n );\n }\n\n if (!chat) return null;\n\n return (\n
\n \n \n
\n );\n}\n\nfunction AgentPanelHeader({\n member,\n status,\n}: {\n member: typeof COUNCIL_MEMBERS[number];\n status: 'idle' | 'loading' | 'streaming' | 'done' | 'error';\n}) {\n return (\n
\n
\n {member.emoji}\n
\n
{member.name}
\n
{member.role}
\n
\n
\n
\n {status === 'idle' && (\n
\n )}\n {status === 'loading' && (\n \n )}\n {status === 'streaming' && (\n
\n )}\n {status === 'done' && (\n \n )}\n {status === 'error' && (\n \n )}\n
\n
\n );\n}\n\nfunction ActiveAgentChat({\n chat,\n member,\n prompt,\n}: {\n chat: ReturnType;\n member: typeof COUNCIL_MEMBERS[number];\n prompt: string;\n}) {\n const { messages, status, addToolApprovalResponse } = useChat({ chat, id: chat.id });\n const [sent, setSent] = React.useState(false);\n\n React.useEffect(() => {\n if (sent || !prompt) return;\n setSent(true);\n chat.sendMessage({\n id: ulid(),\n role: 'user',\n parts: [{ type: 'text', text: prompt }],\n });\n }, [chat, prompt, sent]);\n\n const isStreaming = status === 'submitted' || status === 'streaming';\n const assistantMessages = messages.filter((m) => m.role === 'assistant');\n\n return (\n
\n {isStreaming && assistantMessages.length === 0 && (\n
\n
\n
\n
\n
\n
\n Thinking…\n
\n )}\n {assistantMessages.map((msg) => (\n \n ))}\n
\n );\n}\n\nfunction MessageContent({\n message,\n onApprove,\n}: {\n message: UIMessage;\n onApprove: ReturnType['addToolApprovalResponse'];\n}) {\n return (\n
\n {message.parts.map((part, i) => {\n const key = `${message.id}-${i}`;\n if (part.type === 'text') {\n return (\n
\n {part.text}\n
\n );\n }\n if (isToolUIPart(part)) {\n return (\n
\n Tool: {part.toolName}\n [{part.state}]\n {part.state === 'approval-requested' && part.approval != null && (\n \n part.approval != null && onApprove({ id: part.approval.id, approved: true })}\n className=\"px-2 py-0.5 rounded bg-emerald-500/20 text-emerald-400 hover:bg-emerald-500/30 transition-colors\"\n >\n Approve\n \n part.approval != null && onApprove({ id: part.approval.id, approved: false })}\n className=\"px-2 py-0.5 rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 transition-colors\"\n >\n Deny\n \n \n )}\n
\n );\n }\n return null;\n })}\n
\n );\n}\n\nexport function Council() {\n const [decision, setDecision] = React.useState('');\n const [context, setContext] = React.useState('');\n const [convened, setConvened] = React.useState(false);\n const [activePrompt, setActivePrompt] = React.useState('');\n const [webhookFired, setWebhookFired] = React.useState(false);\n const [webhookError, setWebhookError] = React.useState(null);\n const resultsRef = React.useRef(null);\n\n const canConvene = decision.trim().length > 5;\n\n async function handleConvene() {\n if (!canConvene) return;\n\n const prompt = `Decision: ${decision.trim()}\\n\\nContext: ${context.trim() || 'No additional context provided.'}`;\n setActivePrompt(prompt);\n setConvened(true);\n setWebhookFired(false);\n setWebhookError(null);\n\n // Fire the Decision Council webhook (for Decision Log persistence)\n try {\n await fetch('/api/taskade/webhooks/01KP2MV4A14EDTENW2ANRBS2CB/run', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ decision: decision.trim(), context: context.trim() }),\n });\n setWebhookFired(true);\n } catch {\n setWebhookError('Decision Log sync failed — live responses still active.');\n }\n\n setTimeout(() => {\n resultsRef.current?.scrollIntoView({ behavior: 'smooth' });\n }, 300);\n }\n\n function handleReset() {\n setConvened(false);\n setActivePrompt('');\n setDecision('');\n setContext('');\n setWebhookFired(false);\n setWebhookError(null);\n }\n\n return (\n
\n {/* Header */}\n
\n
\n
\n \n
\n

Decision Council

\n
\n

\n Type a decision. The council assembles — 4 agents pressure-test it from every angle before you commit.\n

\n
\n\n {/* Input form */}\n
\n
\n
\n \n setDecision(e.target.value)}\n placeholder=\"e.g. We should raise a $3M seed round now instead of staying default-alive for another 18 months.\"\n rows={3}\n disabled={convened}\n className=\"w-full px-4 py-3 rounded-xl bg-background border border-border text-sm text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-violet-500/40 focus:border-violet-500/40 resize-none disabled:opacity-60 transition-colors\"\n />\n
\n
\n \n setContext(e.target.value)}\n placeholder=\"e.g. We're at $45k MRR, growing 15% MoM, 18 months runway. Main competitor just raised $10M.\"\n rows={2}\n disabled={convened}\n className=\"w-full px-4 py-3 rounded-xl bg-background border border-border text-sm text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-violet-500/40 focus:border-violet-500/40 resize-none disabled:opacity-60 transition-colors\"\n />\n
\n
\n
\n {webhookFired && (\n <>\n \n Logged to Decision Log\n \n )}\n {webhookError && (\n <>\n \n {webhookError}\n \n )}\n
\n
\n {convened && (\n \n New Decision\n \n )}\n \n {convened ? (\n <>\n \n Council in session\n \n ) : (\n <>\n \n Convene Council\n \n \n )}\n \n
\n
\n
\n
\n\n {/* Council members (always visible, activate on convene) */}\n
\n {convened && (\n
\n
\n {COUNCIL_MEMBERS.map((m) => (\n
\n ))}\n
\n The council is deliberating…\n
\n )}\n
\n {COUNCIL_MEMBERS.map((member) => (\n \n ))}\n
\n
\n
\n );\n}\n" } }, "Journal.tsx": { "file": { - "contents": "import * as React from 'react';\nimport { cn } from '@/lib/utils';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport {\n BookOpen,\n Search,\n ExternalLink,\n Loader2,\n AlertCircle,\n ChevronDown,\n ChevronRight,\n FileText,\n} from 'lucide-react';\n\ninterface ProjectNode {\n id: string;\n parentId: string | null;\n fieldValues: {\n '/text'?: string;\n '/attributes/note'?: string;\n };\n}\n\ninterface DecisionEntry {\n id: string;\n title: string;\n children: ProjectNode[];\n}\n\nfunction buildTree(nodes: ProjectNode[]): { sections: DecisionEntry[] } {\n // Find top-level h2 nodes (section headings) — parentId is null or root\n const rootIds = new Set(nodes.filter((n) => n.parentId === null).map((n) => n.id));\n const childrenOf = (parentId: string) => nodes.filter((n) => n.parentId === parentId);\n\n const sections: DecisionEntry[] = [];\n\n // Find all nodes that could be decision entries (those under the \"Decisions\" heading)\n for (const rootNode of nodes.filter((n) => n.parentId === null)) {\n const title = rootNode.fieldValues['/text'] ?? '';\n if (!title) continue;\n sections.push({\n id: rootNode.id,\n title,\n children: childrenOf(rootNode.id),\n });\n }\n\n return { sections };\n}\n\nfunction nodeText(node: ProjectNode): string {\n return node.fieldValues['/text'] ?? '';\n}\n\nfunction EntryCard({ entry, query }: { entry: DecisionEntry; query: string }) {\n const [expanded, setExpanded] = React.useState(false);\n\n // Highlight match\n const highlight = (text: string) => {\n if (!query) return text;\n const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`, 'gi');\n const parts = text.split(regex);\n return parts.map((part, i) =>\n regex.test(part) ? (\n \n {part}\n \n ) : (\n part\n ),\n );\n };\n\n const isWide = entry.title.includes('Decision Council') || entry.title.includes('War Room');\n const contentText = entry.children.map((c) => nodeText(c)).join(' ');\n\n return (\n
\n setExpanded((e) => !e)}\n className=\"w-full flex items-start gap-3 p-4 text-left hover:bg-accent/30 transition-colors\"\n >\n
\n {expanded ? (\n \n ) : (\n \n )}\n
\n
\n
\n {highlight(entry.title)}\n
\n {!expanded && contentText && (\n
\n {contentText.substring(0, 160)}…\n
\n )}\n {isWide && (\n
\n \n Council\n \n
\n )}\n
\n \n\n {expanded && (\n
\n
\n {entry.children.map((child) => {\n const text = nodeText(child);\n if (!text) return null;\n return (\n
\n {highlight(text)}\n
\n );\n })}\n
\n
\n )}\n
\n );\n}\n\nfunction SkeletonCard() {\n return (\n
\n
\n
\n
\n
\n );\n}\n\nexport function Journal() {\n const [nodes, setNodes] = React.useState([]);\n const [loading, setLoading] = React.useState(true);\n const [error, setError] = React.useState(null);\n const [query, setQuery] = React.useState('');\n\n React.useEffect(() => {\n setLoading(true);\n fetch('/api/taskade/projects/aSs5trhjYStYSydc/nodes')\n .then((r) => r.json())\n .then((data) => {\n if (data.ok) {\n setNodes(data.payload.nodes);\n } else {\n setError('Failed to load Decision Log.');\n }\n })\n .catch(() => setError('Network error loading Decision Log.'))\n .finally(() => setLoading(false));\n }, []);\n\n // Build flat list of all non-root entries that have content\n const entries: DecisionEntry[] = React.useMemo(() => {\n if (!nodes.length) return [];\n const topLevel = nodes.filter((n) => n.parentId === null);\n const childrenOf = (id: string) => nodes.filter((n) => n.parentId === id);\n\n const result: DecisionEntry[] = [];\n for (const top of topLevel) {\n const title = top.fieldValues['/text'] ?? '';\n if (!title) continue;\n // Each top-level node + its children as one entry\n result.push({ id: top.id, title, children: childrenOf(top.id) });\n }\n return result;\n }, [nodes]);\n\n const filtered = React.useMemo(() => {\n if (!query.trim()) return entries;\n const q = query.toLowerCase();\n return entries.filter((e) => {\n const titleMatch = e.title.toLowerCase().includes(q);\n const childMatch = e.children.some((c) =>\n (c.fieldValues['/text'] ?? '').toLowerCase().includes(q),\n );\n return titleMatch || childMatch;\n });\n }, [entries, query]);\n\n return (\n
\n {/* Header */}\n
\n
\n
\n \n
\n

Decision Journal

\n \n \n Open in Taskade\n \n
\n

\n Every major decision, its reasoning, and outcome — read-only, searchable.\n

\n
\n\n {/* Search */}\n
\n \n setQuery(e.target.value)}\n placeholder=\"Search decisions, context, outcomes…\"\n className=\"w-full pl-10 pr-4 py-2.5 rounded-xl bg-card border border-border text-sm text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-amber-500/40 focus:border-amber-500/40 transition-colors\"\n />\n
\n\n {/* Results count */}\n {!loading && !error && query && (\n
\n {filtered.length} result{filtered.length !== 1 ? 's' : ''} for "{query}"\n
\n )}\n\n {/* Content */}\n {loading && (\n
\n {Array.from({ length: 5 }).map((_, i) => (\n \n ))}\n
\n )}\n\n {error && (\n
\n \n {error}\n
\n )}\n\n {!loading && !error && filtered.length === 0 && (\n
\n \n

\n {query ? 'No decisions match that search.' : 'No decisions logged yet.'}\n

\n {query && (\n setQuery('')}\n className=\"mt-2 text-xs text-violet-400 hover:text-violet-300 transition-colors\"\n >\n Clear search\n \n )}\n
\n )}\n\n {!loading && !error && filtered.length > 0 && (\n
\n {filtered.map((entry) => (\n \n ))}\n
\n )}\n
\n );\n}\n" + "contents": "import * as React from 'react';\nimport { cn } from '@/lib/utils';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport {\n BookOpen,\n Search,\n ExternalLink,\n Loader2,\n AlertCircle,\n ChevronDown,\n ChevronRight,\n FileText,\n} from 'lucide-react';\n\ninterface ProjectNode {\n id: string;\n parentId: string | null;\n fieldValues: {\n '/text'?: string;\n '/attributes/note'?: string;\n };\n}\n\ninterface DecisionEntry {\n id: string;\n title: string;\n children: ProjectNode[];\n}\n\nfunction buildTree(nodes: ProjectNode[]): { sections: DecisionEntry[] } {\n // Find top-level h2 nodes (section headings) — parentId is null or root\n const rootIds = new Set(nodes.filter((n) => n.parentId === null).map((n) => n.id));\n const childrenOf = (parentId: string) => nodes.filter((n) => n.parentId === parentId);\n\n const sections: DecisionEntry[] = [];\n\n // Find all nodes that could be decision entries (those under the \"Decisions\" heading)\n for (const rootNode of nodes.filter((n) => n.parentId === null)) {\n const title = rootNode.fieldValues['/text'] ?? '';\n if (!title) continue;\n sections.push({\n id: rootNode.id,\n title,\n children: childrenOf(rootNode.id),\n });\n }\n\n return { sections };\n}\n\nfunction nodeText(node: ProjectNode): string {\n return node.fieldValues['/text'] ?? '';\n}\n\nfunction EntryCard({ entry, query }: { entry: DecisionEntry; query: string }) {\n const [expanded, setExpanded] = React.useState(false);\n\n // Highlight match\n const highlight = (text: string) => {\n if (!query) return text;\n const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`, 'gi');\n const parts = text.split(regex);\n return parts.map((part, i) =>\n regex.test(part) ? (\n \n {part}\n \n ) : (\n part\n ),\n );\n };\n\n const isWide = entry.title.includes('Decision Council') || entry.title.includes('War Room');\n const contentText = entry.children.map((c) => nodeText(c)).join(' ');\n\n return (\n
\n setExpanded((e) => !e)}\n className=\"w-full flex items-start gap-3 p-4 text-left hover:bg-accent/30 transition-colors\"\n >\n
\n {expanded ? (\n \n ) : (\n \n )}\n
\n
\n
\n {highlight(entry.title)}\n
\n {!expanded && contentText && (\n
\n {contentText.substring(0, 160)}…\n
\n )}\n {isWide && (\n
\n \n Council\n \n
\n )}\n
\n \n\n {expanded && (\n
\n
\n {entry.children.map((child) => {\n const text = nodeText(child);\n if (!text) return null;\n return (\n
\n {highlight(text)}\n
\n );\n })}\n
\n
\n )}\n
\n );\n}\n\nfunction SkeletonCard() {\n return (\n
\n
\n
\n
\n
\n );\n}\n\nexport function Journal() {\n const [nodes, setNodes] = React.useState([]);\n const [loading, setLoading] = React.useState(true);\n const [error, setError] = React.useState(null);\n const [query, setQuery] = React.useState('');\n\n React.useEffect(() => {\n setLoading(true);\n fetch('/api/taskade/projects/HihBURwSkvDn1KiC/nodes')\n .then((r) => r.json())\n .then((data) => {\n if (data.ok) {\n setNodes(data.payload.nodes);\n } else {\n setError('Failed to load Decision Log.');\n }\n })\n .catch(() => setError('Network error loading Decision Log.'))\n .finally(() => setLoading(false));\n }, []);\n\n // Build flat list of all non-root entries that have content\n const entries: DecisionEntry[] = React.useMemo(() => {\n if (!nodes.length) return [];\n const topLevel = nodes.filter((n) => n.parentId === null);\n const childrenOf = (id: string) => nodes.filter((n) => n.parentId === id);\n\n const result: DecisionEntry[] = [];\n for (const top of topLevel) {\n const title = top.fieldValues['/text'] ?? '';\n if (!title) continue;\n // Each top-level node + its children as one entry\n result.push({ id: top.id, title, children: childrenOf(top.id) });\n }\n return result;\n }, [nodes]);\n\n const filtered = React.useMemo(() => {\n if (!query.trim()) return entries;\n const q = query.toLowerCase();\n return entries.filter((e) => {\n const titleMatch = e.title.toLowerCase().includes(q);\n const childMatch = e.children.some((c) =>\n (c.fieldValues['/text'] ?? '').toLowerCase().includes(q),\n );\n return titleMatch || childMatch;\n });\n }, [entries, query]);\n\n return (\n
\n {/* Header */}\n
\n
\n
\n \n
\n

Decision Journal

\n \n \n Open in Taskade\n \n
\n

\n Every major decision, its reasoning, and outcome — read-only, searchable.\n

\n
\n\n {/* Search */}\n
\n \n setQuery(e.target.value)}\n placeholder=\"Search decisions, context, outcomes…\"\n className=\"w-full pl-10 pr-4 py-2.5 rounded-xl bg-card border border-border text-sm text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-amber-500/40 focus:border-amber-500/40 transition-colors\"\n />\n
\n\n {/* Results count */}\n {!loading && !error && query && (\n
\n {filtered.length} result{filtered.length !== 1 ? 's' : ''} for "{query}"\n
\n )}\n\n {/* Content */}\n {loading && (\n
\n {Array.from({ length: 5 }).map((_, i) => (\n \n ))}\n
\n )}\n\n {error && (\n
\n \n {error}\n
\n )}\n\n {!loading && !error && filtered.length === 0 && (\n
\n \n

\n {query ? 'No decisions match that search.' : 'No decisions logged yet.'}\n

\n {query && (\n setQuery('')}\n className=\"mt-2 text-xs text-violet-400 hover:text-violet-300 transition-colors\"\n >\n Clear search\n \n )}\n
\n )}\n\n {!loading && !error && filtered.length > 0 && (\n
\n {filtered.map((entry) => (\n \n ))}\n
\n )}\n
\n );\n}\n" } }, "Library.tsx": { "file": { - "contents": "import * as React from 'react';\nimport { cn } from '@/lib/utils';\nimport {\n Library as LibraryIcon,\n Brain,\n BookMarked,\n Search,\n Copy,\n Check,\n ExternalLink,\n Loader2,\n AlertCircle,\n ChevronRight,\n ChevronDown,\n} from 'lucide-react';\n\ninterface ProjectNode {\n id: string;\n parentId: string | null;\n fieldValues: {\n '/text'?: string;\n '/attributes/note'?: string;\n };\n}\n\ninterface TreeNode {\n id: string;\n text: string;\n note?: string;\n children: TreeNode[];\n}\n\nfunction buildTree(nodes: ProjectNode[]): TreeNode[] {\n const map = new Map();\n const roots: TreeNode[] = [];\n\n for (const n of nodes) {\n map.set(n.id, {\n id: n.id,\n text: n.fieldValues['/text'] ?? '',\n note: n.fieldValues['/attributes/note'],\n children: [],\n });\n }\n\n for (const n of nodes) {\n const node = map.get(n.id)!;\n if (n.parentId === null) {\n roots.push(node);\n } else {\n map.get(n.parentId)?.children.push(node);\n }\n }\n\n return roots;\n}\n\nfunction collectText(node: TreeNode): string {\n const lines: string[] = [node.text];\n for (const child of node.children) {\n lines.push(collectText(child));\n }\n return lines.join('\\n');\n}\n\nfunction CopyButton({ text }: { text: string }) {\n const [copied, setCopied] = React.useState(false);\n\n async function handleCopy() {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch {\n // silent\n }\n }\n\n return (\n \n {copied ? (\n <>\n \n Copied\n \n ) : (\n <>\n \n Copy\n \n )}\n \n );\n}\n\nfunction TreeNodeRow({\n node,\n depth,\n query,\n}: {\n node: TreeNode;\n depth: number;\n query: string;\n}) {\n const [expanded, setExpanded] = React.useState(depth < 1);\n const isSection = depth === 0;\n const hasChildren = node.children.length > 0;\n const copyText = collectText(node);\n\n const highlight = (text: string) => {\n if (!query) return <>{text};\n const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`, 'gi');\n const parts = text.split(regex);\n return (\n <>\n {parts.map((part, i) =>\n regex.test(part) ? (\n \n {part}\n \n ) : (\n {part}\n ),\n )}\n \n );\n };\n\n if (!node.text) return null;\n\n if (isSection) {\n return (\n
\n setExpanded((e) => !e)}\n className=\"w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-accent/30 transition-colors\"\n >\n {expanded ? (\n \n ) : (\n \n )}\n {highlight(node.text)}\n {node.children.length} items\n \n {expanded && node.children.length > 0 && (\n
\n {node.children.map((child) => (\n \n ))}\n
\n )}\n
\n );\n }\n\n // Leaf / framework entry\n return (\n
\n
\n
\n
{highlight(node.text)}
\n {node.note && (\n
{highlight(node.note)}
\n )}\n {hasChildren && (\n
\n {node.children.map((child) => (\n
\n {highlight(child.text)}\n {child.children.map((gc) => (\n
\n {highlight(gc.text)}\n
\n ))}\n
\n ))}\n
\n )}\n
\n
\n \n
\n
\n
\n );\n}\n\nfunction ProjectSection({\n projectId,\n title,\n icon: Icon,\n externalUrl,\n accentClass,\n query,\n}: {\n projectId: string;\n title: string;\n icon: React.ElementType;\n externalUrl: string;\n accentClass: string;\n query: string;\n}) {\n const [nodes, setNodes] = React.useState([]);\n const [loading, setLoading] = React.useState(true);\n const [error, setError] = React.useState(null);\n\n React.useEffect(() => {\n setLoading(true);\n fetch(`/api/taskade/projects/${projectId}/nodes`)\n .then((r) => r.json())\n .then((data) => {\n if (data.ok) setNodes(data.payload.nodes);\n else setError('Failed to load.');\n })\n .catch(() => setError('Network error.'))\n .finally(() => setLoading(false));\n }, [projectId]);\n\n const tree = React.useMemo(() => buildTree(nodes), [nodes]);\n\n const filteredTree = React.useMemo(() => {\n if (!query.trim()) return tree;\n const q = query.toLowerCase();\n function nodeMatches(n: TreeNode): boolean {\n if (n.text.toLowerCase().includes(q)) return true;\n if ((n.note ?? '').toLowerCase().includes(q)) return true;\n return n.children.some(nodeMatches);\n }\n return tree.filter(nodeMatches);\n }, [tree, query]);\n\n return (\n
\n
\n
\n
\n \n
\n

{title}

\n
\n \n \n Open\n \n
\n\n {loading && (\n
\n {Array.from({ length: 3 }).map((_, i) => (\n
\n ))}\n
\n )}\n\n {error && (\n
\n \n {error}\n
\n )}\n\n {!loading && !error && filteredTree.length === 0 && (\n
\n {query ? 'No results match.' : 'No content found.'}\n
\n )}\n\n {!loading && !error && filteredTree.length > 0 && (\n
\n {filteredTree.map((node) => (\n \n ))}\n
\n )}\n
\n );\n}\n\nexport function Library() {\n const [query, setQuery] = React.useState('');\n const [tab, setTab] = React.useState<'frameworks' | 'references'>('frameworks');\n\n return (\n
\n {/* Header */}\n
\n
\n
\n \n
\n

Library

\n
\n

\n Mental models, frameworks, essential reading, and key references — with quick-copy.\n

\n
\n\n {/* Search */}\n
\n \n setQuery(e.target.value)}\n placeholder=\"Search frameworks, books, tools, metrics…\"\n className=\"w-full pl-10 pr-4 py-2.5 rounded-xl bg-card border border-border text-sm text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-cyan-500/40 focus:border-cyan-500/40 transition-colors\"\n />\n
\n\n {/* Tabs */}\n
\n {(\n [\n { key: 'frameworks', label: 'Mental Models', icon: Brain },\n { key: 'references', label: 'References', icon: BookMarked },\n ] as const\n ).map(({ key, label, icon: Icon }) => (\n setTab(key)}\n className={cn(\n 'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-150',\n tab === key\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground',\n )}\n >\n \n {label}\n \n ))}\n
\n\n {/* Content */}\n {tab === 'frameworks' && (\n \n )}\n {tab === 'references' && (\n \n )}\n
\n );\n}\n" + "contents": "import * as React from 'react';\nimport { cn } from '@/lib/utils';\nimport {\n Library as LibraryIcon,\n Brain,\n BookMarked,\n Search,\n Copy,\n Check,\n ExternalLink,\n Loader2,\n AlertCircle,\n ChevronRight,\n ChevronDown,\n} from 'lucide-react';\n\ninterface ProjectNode {\n id: string;\n parentId: string | null;\n fieldValues: {\n '/text'?: string;\n '/attributes/note'?: string;\n };\n}\n\ninterface TreeNode {\n id: string;\n text: string;\n note?: string;\n children: TreeNode[];\n}\n\nfunction buildTree(nodes: ProjectNode[]): TreeNode[] {\n const map = new Map();\n const roots: TreeNode[] = [];\n\n for (const n of nodes) {\n map.set(n.id, {\n id: n.id,\n text: n.fieldValues['/text'] ?? '',\n note: n.fieldValues['/attributes/note'],\n children: [],\n });\n }\n\n for (const n of nodes) {\n const node = map.get(n.id)!;\n if (n.parentId === null) {\n roots.push(node);\n } else {\n map.get(n.parentId)?.children.push(node);\n }\n }\n\n return roots;\n}\n\nfunction collectText(node: TreeNode): string {\n const lines: string[] = [node.text];\n for (const child of node.children) {\n lines.push(collectText(child));\n }\n return lines.join('\\n');\n}\n\nfunction CopyButton({ text }: { text: string }) {\n const [copied, setCopied] = React.useState(false);\n\n async function handleCopy() {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch {\n // silent\n }\n }\n\n return (\n \n {copied ? (\n <>\n \n Copied\n \n ) : (\n <>\n \n Copy\n \n )}\n \n );\n}\n\nfunction TreeNodeRow({\n node,\n depth,\n query,\n}: {\n node: TreeNode;\n depth: number;\n query: string;\n}) {\n const [expanded, setExpanded] = React.useState(depth < 1);\n const isSection = depth === 0;\n const hasChildren = node.children.length > 0;\n const copyText = collectText(node);\n\n const highlight = (text: string) => {\n if (!query) return <>{text};\n const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`, 'gi');\n const parts = text.split(regex);\n return (\n <>\n {parts.map((part, i) =>\n regex.test(part) ? (\n \n {part}\n \n ) : (\n {part}\n ),\n )}\n \n );\n };\n\n if (!node.text) return null;\n\n if (isSection) {\n return (\n
\n setExpanded((e) => !e)}\n className=\"w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-accent/30 transition-colors\"\n >\n {expanded ? (\n \n ) : (\n \n )}\n {highlight(node.text)}\n {node.children.length} items\n \n {expanded && node.children.length > 0 && (\n
\n {node.children.map((child) => (\n \n ))}\n
\n )}\n
\n );\n }\n\n // Leaf / framework entry\n return (\n
\n
\n
\n
{highlight(node.text)}
\n {node.note && (\n
{highlight(node.note)}
\n )}\n {hasChildren && (\n
\n {node.children.map((child) => (\n
\n {highlight(child.text)}\n {child.children.map((gc) => (\n
\n {highlight(gc.text)}\n
\n ))}\n
\n ))}\n
\n )}\n
\n
\n \n
\n
\n
\n );\n}\n\nfunction ProjectSection({\n projectId,\n title,\n icon: Icon,\n externalUrl,\n accentClass,\n query,\n}: {\n projectId: string;\n title: string;\n icon: React.ElementType;\n externalUrl: string;\n accentClass: string;\n query: string;\n}) {\n const [nodes, setNodes] = React.useState([]);\n const [loading, setLoading] = React.useState(true);\n const [error, setError] = React.useState(null);\n\n React.useEffect(() => {\n setLoading(true);\n fetch(`/api/taskade/projects/${projectId}/nodes`)\n .then((r) => r.json())\n .then((data) => {\n if (data.ok) setNodes(data.payload.nodes);\n else setError('Failed to load.');\n })\n .catch(() => setError('Network error.'))\n .finally(() => setLoading(false));\n }, [projectId]);\n\n const tree = React.useMemo(() => buildTree(nodes), [nodes]);\n\n const filteredTree = React.useMemo(() => {\n if (!query.trim()) return tree;\n const q = query.toLowerCase();\n function nodeMatches(n: TreeNode): boolean {\n if (n.text.toLowerCase().includes(q)) return true;\n if ((n.note ?? '').toLowerCase().includes(q)) return true;\n return n.children.some(nodeMatches);\n }\n return tree.filter(nodeMatches);\n }, [tree, query]);\n\n return (\n
\n
\n
\n
\n \n
\n

{title}

\n
\n \n \n Open\n \n
\n\n {loading && (\n
\n {Array.from({ length: 3 }).map((_, i) => (\n
\n ))}\n
\n )}\n\n {error && (\n
\n \n {error}\n
\n )}\n\n {!loading && !error && filteredTree.length === 0 && (\n
\n {query ? 'No results match.' : 'No content found.'}\n
\n )}\n\n {!loading && !error && filteredTree.length > 0 && (\n
\n {filteredTree.map((node) => (\n \n ))}\n
\n )}\n
\n );\n}\n\nexport function Library() {\n const [query, setQuery] = React.useState('');\n const [tab, setTab] = React.useState<'frameworks' | 'references'>('frameworks');\n\n return (\n
\n {/* Header */}\n
\n
\n
\n \n
\n

Library

\n
\n

\n Mental models, frameworks, essential reading, and key references — with quick-copy.\n

\n
\n\n {/* Search */}\n
\n \n setQuery(e.target.value)}\n placeholder=\"Search frameworks, books, tools, metrics…\"\n className=\"w-full pl-10 pr-4 py-2.5 rounded-xl bg-card border border-border text-sm text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-cyan-500/40 focus:border-cyan-500/40 transition-colors\"\n />\n
\n\n {/* Tabs */}\n
\n {(\n [\n { key: 'frameworks', label: 'Mental Models', icon: Brain },\n { key: 'references', label: 'References', icon: BookMarked },\n ] as const\n ).map(({ key, label, icon: Icon }) => (\n setTab(key)}\n className={cn(\n 'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-150',\n tab === key\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground',\n )}\n >\n \n {label}\n \n ))}\n
\n\n {/* Content */}\n {tab === 'frameworks' && (\n \n )}\n {tab === 'references' && (\n \n )}\n
\n );\n}\n" } }, "Dashboard.tsx": { "file": { - "contents": "import * as React from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { cn } from '@/lib/utils';\nimport { AGENTS, PROJECTS, AUTOMATIONS } from '@/data/workspace';\nimport {\n ExternalLink,\n Clock,\n Webhook,\n CalendarClock,\n ArrowRight,\n Layers,\n Bot,\n Workflow,\n} from 'lucide-react';\n\nfunction SectionHeader({\n icon: Icon,\n title,\n count,\n action,\n onAction,\n}: {\n icon: React.ElementType;\n title: string;\n count: number;\n action?: string;\n onAction?: () => void;\n}) {\n return (\n
\n
\n
\n \n
\n

{title}

\n \n {count}\n \n
\n {action && onAction && (\n \n {action} \n \n )}\n
\n );\n}\n\nfunction AgentCard({ agent }: { agent: typeof AGENTS[number] }) {\n const navigate = useNavigate();\n const isCouncilAgent =\n agent.name === 'Strategist' ||\n agent.name === 'Critic' ||\n agent.name === 'Researcher' ||\n agent.name === 'Builder';\n\n return (\n isCouncilAgent && navigate('/council')}\n className={cn(\n 'group relative text-left p-4 rounded-xl border bg-gradient-to-br transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5',\n agent.color,\n agent.border,\n isCouncilAgent ? 'cursor-pointer' : 'cursor-default',\n )}\n >\n
\n
{agent.emoji}
\n \n Agent\n \n
\n
{agent.name}
\n
\n {agent.description}\n
\n
{agent.role}
\n {isCouncilAgent && (\n
\n \n Open Council \n \n
\n )}\n \n );\n}\n\nconst CATEGORY_ORDER = ['Core', 'Library', 'Playbooks'] as const;\nconst CATEGORY_COLORS: Record = {\n Core: 'bg-violet-500/10 text-violet-400 border-violet-500/20',\n Library: 'bg-cyan-500/10 text-cyan-400 border-cyan-500/20',\n Playbooks: 'bg-amber-500/10 text-amber-400 border-amber-500/20',\n};\n\nfunction ProjectCard({ project }: { project: typeof PROJECTS[number] }) {\n const catColor = CATEGORY_COLORS[project.category] ?? 'bg-muted text-muted-foreground';\n\n return (\n \n
{project.emoji}
\n
\n
\n {project.name}\n \n {project.category}\n \n
\n
{project.description}
\n
\n
\n \n {project.lastModified}\n \n
\n \n );\n}\n\nfunction AutomationCard({ automation }: { automation: typeof AUTOMATIONS[number] }) {\n const isWebhook = automation.trigger === 'Webhook';\n const navigate = useNavigate();\n const isCouncil = automation.id === '01KP22WA8NMWA2MCDQT43A2K1Q';\n\n return (\n (isCouncil ? navigate('/council') : window.open(automation.taskadeUrl, '_blank'))}\n className={cn(\n 'group text-left w-full p-4 rounded-xl border bg-gradient-to-br transition-all duration-200 hover:shadow-md hover:-translate-y-0.5',\n automation.color,\n automation.border,\n )}\n >\n
\n {automation.emoji}\n
\n {isWebhook ? (\n \n ) : (\n \n )}\n {automation.trigger}\n
\n
\n
{automation.name}
\n
\n {automation.description}\n
\n
\n {automation.agents.map((a) => (\n \n {a}\n \n ))}\n
\n \n );\n}\n\nexport function Dashboard() {\n const navigate = useNavigate();\n\n const projectsByCategory = CATEGORY_ORDER.map((cat) => ({\n category: cat,\n projects: PROJECTS.filter((p) => p.category === cat),\n }));\n\n return (\n
\n {/* Hero */}\n
\n

\n Good morning, Cortex.\n

\n

\n {AGENTS.length} agents · {PROJECTS.length} projects · {AUTOMATIONS.length} automations — all alive.\n

\n
\n\n {/* Stats row */}\n
\n {[\n { icon: Bot, label: 'Agents', value: AGENTS.length, color: 'text-violet-400' },\n { icon: Layers, label: 'Projects', value: PROJECTS.length, color: 'text-cyan-400' },\n { icon: Workflow, label: 'Automations', value: AUTOMATIONS.length, color: 'text-emerald-400' },\n ].map(({ icon: Icon, label, value, color }) => (\n
\n
\n \n
\n
\n
{value}
\n
{label}
\n
\n
\n ))}\n
\n\n {/* Agents */}\n
\n navigate('/council')}\n />\n
\n {AGENTS.map((agent) => (\n \n ))}\n
\n
\n\n {/* Projects */}\n
\n \n
\n {projectsByCategory.map(({ category, projects }) => (\n
\n
\n {category}\n
\n
\n {projects.map((p) => (\n \n ))}\n
\n
\n ))}\n
\n
\n\n {/* Automations */}\n
\n \n
\n {AUTOMATIONS.map((a) => (\n \n ))}\n
\n
\n
\n );\n}\n" + "contents": "import * as React from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { cn } from '@/lib/utils';\nimport { AGENTS, PROJECTS, AUTOMATIONS } from '@/data/workspace';\nimport {\n ExternalLink,\n Clock,\n Webhook,\n CalendarClock,\n ArrowRight,\n Layers,\n Bot,\n Workflow,\n} from 'lucide-react';\n\nfunction SectionHeader({\n icon: Icon,\n title,\n count,\n action,\n onAction,\n}: {\n icon: React.ElementType;\n title: string;\n count: number;\n action?: string;\n onAction?: () => void;\n}) {\n return (\n
\n
\n
\n \n
\n

{title}

\n \n {count}\n \n
\n {action && onAction && (\n \n {action} \n \n )}\n
\n );\n}\n\nfunction AgentCard({ agent }: { agent: typeof AGENTS[number] }) {\n const navigate = useNavigate();\n const isCouncilAgent =\n agent.name === 'Strategist' ||\n agent.name === 'Critic' ||\n agent.name === 'Researcher' ||\n agent.name === 'Builder';\n\n return (\n isCouncilAgent && navigate('/council')}\n className={cn(\n 'group relative text-left p-4 rounded-xl border bg-gradient-to-br transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5',\n agent.color,\n agent.border,\n isCouncilAgent ? 'cursor-pointer' : 'cursor-default',\n )}\n >\n
\n
{agent.emoji}
\n \n Agent\n \n
\n
{agent.name}
\n
\n {agent.description}\n
\n
{agent.role}
\n {isCouncilAgent && (\n
\n \n Open Council \n \n
\n )}\n \n );\n}\n\nconst CATEGORY_ORDER = ['Core', 'Library', 'Playbooks'] as const;\nconst CATEGORY_COLORS: Record = {\n Core: 'bg-violet-500/10 text-violet-400 border-violet-500/20',\n Library: 'bg-cyan-500/10 text-cyan-400 border-cyan-500/20',\n Playbooks: 'bg-amber-500/10 text-amber-400 border-amber-500/20',\n};\n\nfunction ProjectCard({ project }: { project: typeof PROJECTS[number] }) {\n const catColor = CATEGORY_COLORS[project.category] ?? 'bg-muted text-muted-foreground';\n\n return (\n \n
{project.emoji}
\n
\n
\n {project.name}\n \n {project.category}\n \n
\n
{project.description}
\n
\n
\n \n {project.lastModified}\n \n
\n \n );\n}\n\nfunction AutomationCard({ automation }: { automation: typeof AUTOMATIONS[number] }) {\n const isWebhook = automation.trigger === 'Webhook';\n const navigate = useNavigate();\n const isCouncil = automation.id === '01KP2MV4A14EDTENW2ANRBS2CB';\n\n return (\n (isCouncil ? navigate('/council') : window.open(automation.taskadeUrl, '_blank'))}\n className={cn(\n 'group text-left w-full p-4 rounded-xl border bg-gradient-to-br transition-all duration-200 hover:shadow-md hover:-translate-y-0.5',\n automation.color,\n automation.border,\n )}\n >\n
\n {automation.emoji}\n
\n {isWebhook ? (\n \n ) : (\n \n )}\n {automation.trigger}\n
\n
\n
{automation.name}
\n
\n {automation.description}\n
\n
\n {automation.agents.map((a) => (\n \n {a}\n \n ))}\n
\n \n );\n}\n\nexport function Dashboard() {\n const navigate = useNavigate();\n\n const projectsByCategory = CATEGORY_ORDER.map((cat) => ({\n category: cat,\n projects: PROJECTS.filter((p) => p.category === cat),\n }));\n\n return (\n
\n {/* Hero */}\n
\n

\n Good morning, Cortex.\n

\n

\n {AGENTS.length} agents · {PROJECTS.length} projects · {AUTOMATIONS.length} automations — all alive.\n

\n
\n\n {/* Stats row */}\n
\n {[\n { icon: Bot, label: 'Agents', value: AGENTS.length, color: 'text-violet-400' },\n { icon: Layers, label: 'Projects', value: PROJECTS.length, color: 'text-cyan-400' },\n { icon: Workflow, label: 'Automations', value: AUTOMATIONS.length, color: 'text-emerald-400' },\n ].map(({ icon: Icon, label, value, color }) => (\n
\n
\n \n
\n
\n
{value}
\n
{label}
\n
\n
\n ))}\n
\n\n {/* Agents */}\n
\n navigate('/council')}\n />\n
\n {AGENTS.map((agent) => (\n \n ))}\n
\n
\n\n {/* Projects */}\n
\n \n
\n {projectsByCategory.map(({ category, projects }) => (\n
\n
\n {category}\n
\n
\n {projects.map((p) => (\n \n ))}\n
\n
\n ))}\n
\n
\n\n {/* Automations */}\n
\n \n
\n {AUTOMATIONS.map((a) => (\n \n ))}\n
\n
\n
\n );\n}\n" } } } @@ -443,7 +443,7 @@ }, "Layout.tsx": { "file": { - "contents": "import * as React from 'react';\nimport { NavLink, useLocation } from 'react-router-dom';\nimport { cn } from '@/lib/utils';\nimport {\n LayoutDashboard,\n Scale,\n BookOpen,\n Library,\n Zap,\n Menu,\n X,\n ExternalLink,\n} from 'lucide-react';\n\nconst NAV = [\n { to: '/', label: 'Dashboard', icon: LayoutDashboard, exact: true },\n { to: '/council', label: 'Decision Council', icon: Scale, exact: false },\n { to: '/journal', label: 'Decision Journal', icon: BookOpen, exact: false },\n { to: '/library', label: 'Library', icon: Library, exact: false },\n];\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n const [mobileOpen, setMobileOpen] = React.useState(false);\n const location = useLocation();\n\n React.useEffect(() => {\n setMobileOpen(false);\n }, [location.pathname]);\n\n return (\n
\n {/* Mobile overlay */}\n {mobileOpen && (\n setMobileOpen(false)}\n />\n )}\n\n {/* Sidebar */}\n \n {/* Logo */}\n
\n
\n
\n \n
\n CORTEX\n
\n setMobileOpen(false)}\n className=\"lg:hidden p-1 rounded text-muted-foreground hover:text-foreground\"\n >\n \n \n
\n\n {/* Nav */}\n \n\n {/* Footer */}\n
\n \n \n Open in Taskade\n \n
\n \n\n {/* Main content */}\n
\n {/* Mobile top bar */}\n
\n setMobileOpen(true)}\n className=\"p-2 rounded-lg text-muted-foreground hover:text-foreground hover:bg-accent\"\n >\n \n \n
\n
\n \n
\n CORTEX\n
\n
\n\n
\n {children}\n
\n
\n
\n );\n}\n" + "contents": "import * as React from 'react';\nimport { NavLink, useLocation } from 'react-router-dom';\nimport { cn } from '@/lib/utils';\nimport {\n LayoutDashboard,\n Scale,\n BookOpen,\n Library,\n Brain,\n Menu,\n X,\n ExternalLink,\n} from 'lucide-react';\n\nconst NAV = [\n { to: '/', label: 'Dashboard', icon: LayoutDashboard, exact: true },\n { to: '/council', label: 'Decision Council', icon: Scale, exact: false },\n { to: '/journal', label: 'Decision Journal', icon: BookOpen, exact: false },\n { to: '/library', label: 'Library', icon: Library, exact: false },\n];\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n const [mobileOpen, setMobileOpen] = React.useState(false);\n const location = useLocation();\n\n React.useEffect(() => {\n setMobileOpen(false);\n }, [location.pathname]);\n\n return (\n
\n {/* Mobile overlay */}\n {mobileOpen && (\n setMobileOpen(false)}\n />\n )}\n\n {/* Sidebar */}\n \n {/* Logo */}\n
\n
\n
\n \n
\n CORTEX\n
\n setMobileOpen(false)}\n className=\"lg:hidden p-1 rounded text-muted-foreground hover:text-foreground\"\n >\n \n \n
\n\n {/* Nav */}\n \n\n {/* Footer */}\n
\n \n \n Open in Taskade\n \n
\n \n\n {/* Main content */}\n
\n {/* Mobile top bar */}\n
\n setMobileOpen(true)}\n className=\"p-2 rounded-lg text-muted-foreground hover:text-foreground hover:bg-accent\"\n >\n \n \n
\n
\n \n
\n CORTEX\n
\n
\n\n
\n {children}\n
\n
\n
\n );\n}\n" } }, "ai-elements": { diff --git a/automations/01KP22WA8NVFMDTGDSPPQBS5XZ.json b/automations/daily-standup.json similarity index 92% rename from automations/01KP22WA8NVFMDTGDSPPQBS5XZ.json rename to automations/daily-standup.json index 38a316a..d5d624b 100644 --- a/automations/01KP22WA8NVFMDTGDSPPQBS5XZ.json +++ b/automations/daily-standup.json @@ -63,7 +63,7 @@ "input": "Review open tasks across the workspace and suggest what should ship today. Prioritize by urgency, dependencies, and impact. Format as a clear, actionable standup list.", "agentId": { "type": "flow-template-variable-input-type", - "id": "01KP22WA8NVMYWZ85XHAVP0RG4" + "id": "01KP2MV4A0283A4MZMMMJ8WC61" } } } @@ -82,21 +82,21 @@ "position": "beforeend", "projectId": { "type": "flow-template-variable-input-type", - "id": "aSs5trhjYStYSydc" + "id": "HihBURwSkvDn1KiC" } } } } ], "variables": { - "01KP22WA8NVMYWZ85XHAVP0RG4": { - "id": "01KP22WA8NVMYWZ85XHAVP0RG4", + "builder": { + "id": "builder", "rank": "V", "type": "string", "format": "taskade-space-agent-id" }, - "aSs5trhjYStYSydc": { - "id": "aSs5trhjYStYSydc", + "decision-log": { + "id": "decision-log", "rank": "k", "type": "string", "format": "taskade-project-id" diff --git a/automations/01KP22WA8NMWA2MCDQT43A2K1Q.json b/automations/decision-council.json similarity index 87% rename from automations/01KP22WA8NMWA2MCDQT43A2K1Q.json rename to automations/decision-council.json index 3b3ee27..93b9b1c 100644 --- a/automations/01KP22WA8NMWA2MCDQT43A2K1Q.json +++ b/automations/decision-council.json @@ -35,7 +35,7 @@ "input": "Decision: {{ trigger[\"body\"][\"decision\"] }}\n\nContext: {{ trigger[\"body\"][\"context\"] }}", "agentId": { "type": "flow-template-variable-input-type", - "id": "01KP22WA8NPMP922SAHDKHQS8Q" + "id": "01KP2MV4A064B82EF4Q2QS2RCD" } } } @@ -53,7 +53,7 @@ "input": "Decision: {{ trigger[\"body\"][\"decision\"] }}\n\nContext: {{ trigger[\"body\"][\"context\"] }}", "agentId": { "type": "flow-template-variable-input-type", - "id": "01KP22WA8N4VKKY6WB2XX4CB0A" + "id": "01KP2MV4A0YKPJ7EFBJ6CDB1Z8" } } } @@ -71,7 +71,7 @@ "input": "Decision: {{ trigger[\"body\"][\"decision\"] }}\n\nContext: {{ trigger[\"body\"][\"context\"] }}", "agentId": { "type": "flow-template-variable-input-type", - "id": "01KP22WA8NDTKXX5K4NFZE32YC" + "id": "01KP2MV4A02PX80CXR34F0DX7V" } } } @@ -89,7 +89,7 @@ "input": "Decision: {{ trigger[\"body\"][\"decision\"] }}\n\nContext: {{ trigger[\"body\"][\"context\"] }}", "agentId": { "type": "flow-template-variable-input-type", - "id": "01KP22WA8NVMYWZ85XHAVP0RG4" + "id": "01KP2MV4A0283A4MZMMMJ8WC61" } } } @@ -108,39 +108,39 @@ "position": "beforeend", "projectId": { "type": "flow-template-variable-input-type", - "id": "aSs5trhjYStYSydc" + "id": "HihBURwSkvDn1KiC" } } } } ], "variables": { - "01KP22WA8NPMP922SAHDKHQS8Q": { - "id": "01KP22WA8NPMP922SAHDKHQS8Q", + "strategist": { + "id": "strategist", "rank": "V", "type": "string", "format": "taskade-space-agent-id" }, - "01KP22WA8N4VKKY6WB2XX4CB0A": { - "id": "01KP22WA8N4VKKY6WB2XX4CB0A", + "critic": { + "id": "critic", "rank": "k", "type": "string", "format": "taskade-space-agent-id" }, - "01KP22WA8NDTKXX5K4NFZE32YC": { - "id": "01KP22WA8NDTKXX5K4NFZE32YC", + "researcher": { + "id": "researcher", "rank": "s", "type": "string", "format": "taskade-space-agent-id" }, - "01KP22WA8NVMYWZ85XHAVP0RG4": { - "id": "01KP22WA8NVMYWZ85XHAVP0RG4", + "builder": { + "id": "builder", "rank": "w", "type": "string", "format": "taskade-space-agent-id" }, - "aSs5trhjYStYSydc": { - "id": "aSs5trhjYStYSydc", + "decision-log": { + "id": "decision-log", "rank": "y", "type": "string", "format": "taskade-project-id" diff --git a/automations/01KP22WA8N4GFKFJKTR2CXMSJQ.json b/automations/inbox-triage.json similarity index 92% rename from automations/01KP22WA8N4GFKFJKTR2CXMSJQ.json rename to automations/inbox-triage.json index 8d24839..234e689 100644 --- a/automations/01KP22WA8N4GFKFJKTR2CXMSJQ.json +++ b/automations/inbox-triage.json @@ -39,7 +39,7 @@ "input": "Classify the following incoming message into exactly ONE of these four categories:\n\n- SUPPORT TICKET — a user reporting a bug, issue, or needing help\n- SALES LEAD — a potential customer asking about pricing, features, or partnerships\n- INVESTOR INTRO — someone expressing interest in investing or requesting a meeting for funding\n- NOISE — spam, irrelevant, automated, or low-signal message\n\nRespond with:\nCATEGORY: [one of the four above]\nCONFIDENCE: [High / Medium / Low]\nREASON: [one sentence max]\n\nMessage:\n{{ trigger[\"body\"][\"message\"] }}\n\nSender: {{ trigger[\"body\"][\"sender\"] }}\nSource: {{ trigger[\"body\"][\"source\"] }}", "agentId": { "type": "flow-template-variable-input-type", - "id": "01KP22WA8NDTKXX5K4NFZE32YC" + "id": "01KP2MV4A02PX80CXR34F0DX7V" } } } @@ -58,21 +58,21 @@ "position": "beforeend", "projectId": { "type": "flow-template-variable-input-type", - "id": "zWQheH5BoVPriUiE" + "id": "8VNTPiqG9XGrbRhY" } } } } ], "variables": { - "01KP22WA8NDTKXX5K4NFZE32YC": { - "id": "01KP22WA8NDTKXX5K4NFZE32YC", + "researcher": { + "id": "researcher", "rank": "V", "type": "string", "format": "taskade-space-agent-id" }, - "zWQheH5BoVPriUiE": { - "id": "zWQheH5BoVPriUiE", + "company-context": { + "id": "company-context", "rank": "k", "type": "string", "format": "taskade-project-id" diff --git a/automations/01KP22WA8N2MNZ830GAAYZ8G9R.json b/automations/incident-response.json similarity index 93% rename from automations/01KP22WA8N2MNZ830GAAYZ8G9R.json rename to automations/incident-response.json index 7b2d202..3ef0ce0 100644 --- a/automations/01KP22WA8N2MNZ830GAAYZ8G9R.json +++ b/automations/incident-response.json @@ -60,7 +60,7 @@ "input": "We have an active incident. Spec the next 4 hours of response.\n\nSEVERITY: {{ trigger[\"body\"][\"severity\"] }}\nSUMMARY: {{ trigger[\"body\"][\"summary\"] }}\nAFFECTED SYSTEMS: {{ trigger[\"body\"][\"affected_systems\"] }}\nOWNER: {{ trigger[\"body\"][\"owner\"] }}\n\nDeliver:\n1. Immediate actions (first 15 min)\n2. Investigation sequence (next 45 min)\n3. Mitigation steps (next 60 min)\n4. Communication cadence (who, what, when)\n5. Definition of resolved\n6. Top 2 risks that could make this worse\n\nBe precise. Use verbs. No hedging.", "agentId": { "type": "flow-template-variable-input-type", - "id": "01KP22WA8NVMYWZ85XHAVP0RG4" + "id": "01KP2MV4A0283A4MZMMMJ8WC61" } } } @@ -79,21 +79,21 @@ "position": "beforeend", "projectId": { "type": "flow-template-variable-input-type", - "id": "aSs5trhjYStYSydc" + "id": "HihBURwSkvDn1KiC" } } } } ], "variables": { - "01KP22WA8NVMYWZ85XHAVP0RG4": { - "id": "01KP22WA8NVMYWZ85XHAVP0RG4", + "builder": { + "id": "builder", "rank": "V", "type": "string", "format": "taskade-space-agent-id" }, - "aSs5trhjYStYSydc": { - "id": "aSs5trhjYStYSydc", + "decision-log": { + "id": "decision-log", "rank": "k", "type": "string", "format": "taskade-project-id" diff --git a/automations/01KP22WA8N1NF18NFENB9AWMFR.json b/automations/monday-planning.json similarity index 90% rename from automations/01KP22WA8N1NF18NFENB9AWMFR.json rename to automations/monday-planning.json index 0f55c68..17a9820 100644 --- a/automations/01KP22WA8N1NF18NFENB9AWMFR.json +++ b/automations/monday-planning.json @@ -26,7 +26,7 @@ "input": "It's Monday morning. Identify the ONE thing that matters most this week. Surface the tradeoffs in choosing it over everything else on the table. Structure your output:\n\n1. THE ONE THING — One sentence, no hedging\n2. WHY THIS WEEK — What makes right now the right moment\n3. WHAT WE'RE NOT DOING — The top 3 things we're explicitly deferring and why\n4. THE TRADEOFF — The real cost of this focus\n5. THE SIGNAL TO WATCH — One metric or signal that tells us if we're right\n6. RISK — What could invalidate this by Friday", "agentId": { "type": "flow-template-variable-input-type", - "id": "01KP22WA8NPMP922SAHDKHQS8Q" + "id": "01KP2MV4A064B82EF4Q2QS2RCD" } } } @@ -45,21 +45,21 @@ "position": "beforeend", "projectId": { "type": "flow-template-variable-input-type", - "id": "zWQheH5BoVPriUiE" + "id": "8VNTPiqG9XGrbRhY" } } } } ], "variables": { - "01KP22WA8NPMP922SAHDKHQS8Q": { - "id": "01KP22WA8NPMP922SAHDKHQS8Q", + "strategist": { + "id": "strategist", "rank": "V", "type": "string", "format": "taskade-space-agent-id" }, - "zWQheH5BoVPriUiE": { - "id": "zWQheH5BoVPriUiE", + "company-context": { + "id": "company-context", "rank": "k", "type": "string", "format": "taskade-project-id" diff --git a/automations/01KP22WA8NHPV626F46CM2WP36.json b/automations/weekly-review.json similarity index 88% rename from automations/01KP22WA8NHPV626F46CM2WP36.json rename to automations/weekly-review.json index 1fbd20e..21484a5 100644 --- a/automations/01KP22WA8NHPV626F46CM2WP36.json +++ b/automations/weekly-review.json @@ -26,7 +26,7 @@ "input": "Summarize this week's decisions from the Decision Log in exactly 10 bullets. Cut everything that isn't a concrete decision, number, name, or outcome. Be ruthless.", "agentId": { "type": "flow-template-variable-input-type", - "id": "01KP22WA8N1S8JDRDP55D87NX5" + "id": "01KP2MV4A08B7NCXR1HBFS2VXN" } } } @@ -44,7 +44,7 @@ "input": "Stress test next week's plan. Identify the three most likely points of failure, the hidden assumptions, and the conditions under which the plan breaks down completely.", "agentId": { "type": "flow-template-variable-input-type", - "id": "01KP22WA8NPMP922SAHDKHQS8Q" + "id": "01KP2MV4A064B82EF4Q2QS2RCD" } } } @@ -63,27 +63,27 @@ "position": "beforeend", "projectId": { "type": "flow-template-variable-input-type", - "id": "aSs5trhjYStYSydc" + "id": "HihBURwSkvDn1KiC" } } } } ], "variables": { - "01KP22WA8N1S8JDRDP55D87NX5": { - "id": "01KP22WA8N1S8JDRDP55D87NX5", + "editor": { + "id": "editor", "rank": "V", "type": "string", "format": "taskade-space-agent-id" }, - "01KP22WA8NPMP922SAHDKHQS8Q": { - "id": "01KP22WA8NPMP922SAHDKHQS8Q", + "strategist": { + "id": "strategist", "rank": "k", "type": "string", "format": "taskade-space-agent-id" }, - "aSs5trhjYStYSydc": { - "id": "aSs5trhjYStYSydc", + "decision-log": { + "id": "decision-log", "rank": "s", "type": "string", "format": "taskade-project-id" diff --git a/manifest.json b/manifest.json index aa7cd20..5f7fbe3 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1 @@ -{"version":"1.0","sourceEnvironment":"development","exportedAt":"2026-04-13T00:53:26.716Z","spaceId":"zn8t049t1cyr6yrh","spaceMetadata":{"name":"Cortex","color":"#4b0082","emoji":"","visibility":"collaborator"},"name":"Cortex"} \ No newline at end of file +{"version":"1.0","sourceEnvironment":"staging","exportedAt":"2026-04-13T05:29:36.886Z","spaceId":"l16vxjc2s9jw2ag9","spaceMetadata":{"name":"Cortex","color":"#54c4fa","emoji":"","visibility":null},"name":"Cortex"} \ No newline at end of file diff --git a/projects/zWQheH5BoVPriUiE.json b/projects/company-context.json similarity index 99% rename from projects/zWQheH5BoVPriUiE.json rename to projects/company-context.json index 89e3b42..8c2c52a 100644 --- a/projects/zWQheH5BoVPriUiE.json +++ b/projects/company-context.json @@ -725,7 +725,7 @@ "preferences": { "avatar": { "type": "emoji", - "value": "📋" + "value": "✔️" } } } \ No newline at end of file diff --git a/projects/aSs5trhjYStYSydc.json b/projects/decision-log.json similarity index 99% rename from projects/aSs5trhjYStYSydc.json rename to projects/decision-log.json index e0f575a..e9598dd 100644 --- a/projects/aSs5trhjYStYSydc.json +++ b/projects/decision-log.json @@ -586,7 +586,7 @@ "preferences": { "avatar": { "type": "emoji", - "value": "💭" + "value": "⚗️" } } } \ No newline at end of file diff --git a/projects/7GBCCnibQz8RQWuJ.json b/projects/library-mental-models-frameworks.json similarity index 99% rename from projects/7GBCCnibQz8RQWuJ.json rename to projects/library-mental-models-frameworks.json index f16c2b8..f15deb4 100644 --- a/projects/7GBCCnibQz8RQWuJ.json +++ b/projects/library-mental-models-frameworks.json @@ -755,7 +755,7 @@ "preferences": { "avatar": { "type": "emoji", - "value": "✒️" + "value": "💾" } } } \ No newline at end of file diff --git a/projects/BFsoG1yRn72cNtBr.json b/projects/library-references.json similarity index 99% rename from projects/BFsoG1yRn72cNtBr.json rename to projects/library-references.json index 23486df..69096c7 100644 --- a/projects/BFsoG1yRn72cNtBr.json +++ b/projects/library-references.json @@ -624,7 +624,7 @@ "preferences": { "avatar": { "type": "emoji", - "value": "📚" + "value": "🖥️" } } } \ No newline at end of file diff --git a/projects/P4vqDyabfwFLYwHY.json b/projects/playbook-fundraise.json similarity index 99% rename from projects/P4vqDyabfwFLYwHY.json rename to projects/playbook-fundraise.json index 05e8ef7..f306a6a 100644 --- a/projects/P4vqDyabfwFLYwHY.json +++ b/projects/playbook-fundraise.json @@ -611,7 +611,7 @@ "preferences": { "avatar": { "type": "emoji", - "value": "💌" + "value": "📋" } } } \ No newline at end of file diff --git a/projects/opHBiT9XYgNKD1Dz.json b/projects/playbook-hiring.json similarity index 99% rename from projects/opHBiT9XYgNKD1Dz.json rename to projects/playbook-hiring.json index 3c21847..3cc6a05 100644 --- a/projects/opHBiT9XYgNKD1Dz.json +++ b/projects/playbook-hiring.json @@ -576,7 +576,7 @@ "preferences": { "avatar": { "type": "emoji", - "value": "📓" + "value": "✏️" } } } \ No newline at end of file diff --git a/projects/YoDL7iQavPKCJNJ6.json b/projects/playbook-launch.json similarity index 99% rename from projects/YoDL7iQavPKCJNJ6.json rename to projects/playbook-launch.json index d286590..04402e3 100644 --- a/projects/YoDL7iQavPKCJNJ6.json +++ b/projects/playbook-launch.json @@ -563,7 +563,7 @@ "preferences": { "avatar": { "type": "emoji", - "value": "💭" + "value": "🌐" } } } \ No newline at end of file diff --git a/projects/Lz5wgBknjP4GhuFG.json b/projects/playbook-pricing.json similarity index 99% rename from projects/Lz5wgBknjP4GhuFG.json rename to projects/playbook-pricing.json index 587bd7c..3f27526 100644 --- a/projects/Lz5wgBknjP4GhuFG.json +++ b/projects/playbook-pricing.json @@ -513,7 +513,7 @@ "preferences": { "avatar": { "type": "emoji", - "value": "📕" + "value": "🖇️" } } } \ No newline at end of file diff --git a/projects/u5uSqqqNQFFP8SR1.json b/projects/playbook-support.json similarity index 99% rename from projects/u5uSqqqNQFFP8SR1.json rename to projects/playbook-support.json index 7a26ff9..702fd03 100644 --- a/projects/u5uSqqqNQFFP8SR1.json +++ b/projects/playbook-support.json @@ -497,7 +497,7 @@ "preferences": { "avatar": { "type": "emoji", - "value": "⚕️" + "value": "📒" } } } \ No newline at end of file diff --git a/projects/nNzQx9qpAitsKEMa.json b/projects/welcome-to-cortex.json similarity index 99% rename from projects/nNzQx9qpAitsKEMa.json rename to projects/welcome-to-cortex.json index ee55b7f..e655151 100644 --- a/projects/nNzQx9qpAitsKEMa.json +++ b/projects/welcome-to-cortex.json @@ -859,7 +859,7 @@ "preferences": { "avatar": { "type": "emoji", - "value": "🧩" + "value": "🔎" } } } \ No newline at end of file