@assistkick/create 1.9.0 → 1.11.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 (206) hide show
  1. package/dist/src/scaffolder.d.ts +12 -1
  2. package/dist/src/scaffolder.js +40 -3
  3. package/dist/src/scaffolder.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/assistkick-product-system/package.json +1 -1
  6. package/templates/assistkick-product-system/packages/backend/package.json +1 -0
  7. package/templates/assistkick-product-system/packages/backend/src/mcp/permission_mcp_server.ts +196 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +31 -7
  9. package/templates/assistkick-product-system/packages/backend/src/routes/auth.ts +15 -12
  10. package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.test.ts +95 -0
  11. package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.ts +97 -0
  12. package/templates/assistkick-product-system/packages/backend/src/routes/chat_permission.ts +94 -0
  13. package/templates/assistkick-product-system/packages/backend/src/routes/chat_sessions.ts +189 -0
  14. package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.test.ts +131 -0
  15. package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.ts +94 -0
  16. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +12 -3
  17. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +2 -2
  18. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +391 -23
  19. package/templates/assistkick-product-system/packages/backend/src/routes/git_branches.test.ts +306 -0
  20. package/templates/assistkick-product-system/packages/backend/src/routes/git_connect.test.ts +133 -0
  21. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +66 -9
  22. package/templates/assistkick-product-system/packages/backend/src/routes/preview.ts +204 -0
  23. package/templates/assistkick-product-system/packages/backend/src/routes/projects.test.ts +205 -0
  24. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +37 -9
  25. package/templates/assistkick-product-system/packages/backend/src/routes/skills.test.ts +139 -0
  26. package/templates/assistkick-product-system/packages/backend/src/routes/skills.ts +95 -0
  27. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +5 -4
  28. package/templates/assistkick-product-system/packages/backend/src/routes/users.ts +4 -4
  29. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +8 -8
  30. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +5 -5
  31. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +6 -6
  32. package/templates/assistkick-product-system/packages/backend/src/server.ts +107 -27
  33. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +105 -203
  34. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +76 -266
  35. package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.test.ts +427 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.ts +345 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.test.ts +170 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.ts +106 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.test.ts +217 -0
  40. package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.ts +188 -0
  41. package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.test.ts +1243 -0
  42. package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.ts +894 -0
  43. package/templates/assistkick-product-system/packages/backend/src/services/coherence-review.ts +3 -3
  44. package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.test.ts +85 -0
  45. package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.ts +54 -0
  46. package/templates/assistkick-product-system/packages/backend/src/services/email_service.ts +13 -10
  47. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +11 -3
  48. package/templates/assistkick-product-system/packages/backend/src/services/invitation_service.ts +1 -1
  49. package/templates/assistkick-product-system/packages/backend/src/services/password_reset_service.ts +1 -1
  50. package/templates/assistkick-product-system/packages/backend/src/services/permission_service.test.ts +243 -0
  51. package/templates/assistkick-product-system/packages/backend/src/services/permission_service.ts +259 -0
  52. package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.test.ts +172 -0
  53. package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.ts +225 -0
  54. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +29 -0
  55. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +17 -0
  56. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +255 -0
  57. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +300 -25
  58. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +44 -0
  59. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +62 -7
  60. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +77 -6
  61. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +149 -14
  62. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +2 -1
  63. package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.test.ts +45 -0
  64. package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.ts +157 -0
  65. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +4 -3
  66. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +3 -3
  67. package/templates/assistkick-product-system/packages/frontend/package.json +5 -0
  68. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +2 -0
  69. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +336 -5
  70. package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +192 -12
  71. package/templates/assistkick-product-system/packages/frontend/src/components/AttachmentPreviewList.tsx +98 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/AutocompleteDropdown.tsx +65 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ChatAttachButton.tsx +56 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ChatDropZone.tsx +80 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageBubble.tsx +155 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageContent.tsx +182 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageInput.tsx +233 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ChatSessionSidebar.tsx +218 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ChatStopButton.tsx +32 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ChatTodoSidebar.tsx +113 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ChatView.tsx +842 -0
  82. package/templates/assistkick-product-system/packages/frontend/src/components/CommitMessageModal.tsx +82 -0
  83. package/templates/assistkick-product-system/packages/frontend/src/components/DiagramOverlay.tsx +160 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +5 -5
  85. package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +9 -10
  86. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +5 -5
  87. package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +112 -41
  88. package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +2 -2
  89. package/templates/assistkick-product-system/packages/frontend/src/components/HighlightedText.tsx +87 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/ImageLightbox.tsx +192 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +2 -2
  92. package/templates/assistkick-product-system/packages/frontend/src/components/MentionPill.tsx +33 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/MermaidBlock.tsx +148 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/PermissionDialog.tsx +91 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/PermissionModeSelector.tsx +229 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +249 -83
  97. package/templates/assistkick-product-system/packages/frontend/src/components/QueuedMessageBubble.tsx +38 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +212 -117
  99. package/templates/assistkick-product-system/packages/frontend/src/components/SystemPromptAccordion.tsx +48 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/TaskIcon.tsx +11 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +25 -9
  102. package/templates/assistkick-product-system/packages/frontend/src/components/ToolDiffView.tsx +114 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/ToolResultCard.tsx +87 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/components/ToolUseCard.tsx +149 -0
  105. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +25 -8
  106. package/templates/assistkick-product-system/packages/frontend/src/components/UnifiedGitWidget.tsx +722 -0
  107. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +2 -0
  108. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +2 -1
  109. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/ProgrammableNode.tsx +178 -0
  110. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +3 -0
  111. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +103 -9
  112. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +26 -2
  113. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +42 -1
  114. package/templates/assistkick-product-system/packages/frontend/src/hooks/useDocumentTitle.ts +11 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +1 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_chat_stream.ts +826 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_file_tree_cache.ts +69 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_mention_autocomplete.ts +284 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.test.ts +183 -0
  120. package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.ts +150 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.test.ts +305 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.ts +113 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.test.ts +157 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.ts +95 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.test.ts +65 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.ts +110 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/lib/message_queue.ts +66 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.test.ts +124 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.ts +112 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +2 -0
  131. package/templates/assistkick-product-system/packages/frontend/src/routes/ChatRoute.tsx +8 -0
  132. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +2 -0
  133. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +0 -4
  134. package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +2 -0
  135. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +2 -0
  136. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -0
  137. package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +2 -0
  138. package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +2 -0
  139. package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +2 -0
  140. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +2 -0
  141. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +2 -0
  142. package/templates/assistkick-product-system/packages/frontend/src/routes/accept_invitation.tsx +2 -0
  143. package/templates/assistkick-product-system/packages/frontend/src/routes/forgot_password.tsx +2 -0
  144. package/templates/assistkick-product-system/packages/frontend/src/routes/login.tsx +2 -0
  145. package/templates/assistkick-product-system/packages/frontend/src/routes/register.tsx +2 -0
  146. package/templates/assistkick-product-system/packages/frontend/src/routes/reset_password.tsx +2 -0
  147. package/templates/assistkick-product-system/packages/frontend/src/stores/useAttachmentStore.ts +66 -0
  148. package/templates/assistkick-product-system/packages/frontend/src/stores/useChatSessionStore.ts +107 -0
  149. package/templates/assistkick-product-system/packages/frontend/src/stores/useMessageQueueStore.ts +110 -0
  150. package/templates/assistkick-product-system/packages/frontend/src/stores/usePreviewStore.ts +78 -0
  151. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +7 -0
  152. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +6 -1
  153. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +30 -357
  154. package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.test.ts +115 -0
  155. package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.ts +91 -0
  156. package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.test.ts +30 -0
  157. package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.ts +3 -0
  158. package/templates/assistkick-product-system/packages/shared/db/migrations/0015_magenta_jazinda.sql +1 -0
  159. package/templates/assistkick-product-system/packages/shared/db/migrations/0016_giant_xorn.sql +1 -0
  160. package/templates/assistkick-product-system/packages/shared/db/migrations/0017_sloppy_mentor.sql +6 -0
  161. package/templates/assistkick-product-system/packages/shared/db/migrations/0018_vengeful_kabuki.sql +9 -0
  162. package/templates/assistkick-product-system/packages/shared/db/migrations/0019_careful_sentinels.sql +8 -0
  163. package/templates/assistkick-product-system/packages/shared/db/migrations/0020_clever_spot.sql +27 -0
  164. package/templates/assistkick-product-system/packages/shared/db/migrations/0021_graceful_hex.sql +1 -0
  165. package/templates/assistkick-product-system/packages/shared/db/migrations/0022_short_kingpin.sql +1 -0
  166. package/templates/assistkick-product-system/packages/shared/db/migrations/0023_ambiguous_sharon_carter.sql +1 -0
  167. package/templates/assistkick-product-system/packages/shared/db/migrations/0024_fat_unus.sql +1 -0
  168. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0015_snapshot.json +1552 -0
  169. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0016_snapshot.json +1560 -0
  170. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0017_snapshot.json +1598 -0
  171. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0018_snapshot.json +1657 -0
  172. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0019_snapshot.json +1709 -0
  173. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0020_snapshot.json +1733 -0
  174. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0021_snapshot.json +1740 -0
  175. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0022_snapshot.json +1755 -0
  176. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0023_snapshot.json +1762 -0
  177. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0024_snapshot.json +1769 -0
  178. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  179. package/templates/assistkick-product-system/packages/shared/db/schema.ts +40 -1
  180. package/templates/assistkick-product-system/packages/shared/lib/claude-service.test.ts +236 -0
  181. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +46 -5
  182. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +65 -39
  183. package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.test.ts +173 -0
  184. package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.ts +213 -0
  185. package/templates/assistkick-product-system/packages/shared/lib/validator.test.ts +70 -0
  186. package/templates/assistkick-product-system/packages/shared/lib/validator.ts +17 -1
  187. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +803 -27
  188. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +502 -68
  189. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +4 -4
  190. package/templates/assistkick-product-system/packages/shared/package.json +2 -1
  191. package/templates/assistkick-product-system/packages/shared/test_fixtures/hanging_stream.mjs +46 -0
  192. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +44 -0
  193. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +7 -0
  194. package/templates/assistkick-product-system/packages/shared/tools/remove_node.ts +2 -1
  195. package/templates/assistkick-product-system/packages/shared/tools/resolve_question.ts +2 -1
  196. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -1
  197. package/templates/assistkick-product-system/tests/message_queue.test.ts +178 -0
  198. package/templates/assistkick-product-system/tests/message_queue_per_session.test.ts +143 -0
  199. package/templates/skills/assistkick-bootstrap/SKILL.md +26 -26
  200. package/templates/skills/assistkick-code-reviewer/SKILL.md +45 -46
  201. package/templates/skills/assistkick-db-explorer/SKILL.md +13 -13
  202. package/templates/skills/assistkick-debugger/SKILL.md +23 -23
  203. package/templates/skills/assistkick-developer/SKILL.md +59 -63
  204. package/templates/skills/assistkick-interview/SKILL.md +26 -26
  205. package/templates/skills/assistkick-video-composition-agent/SKILL.md +231 -0
  206. package/templates/skills/assistkick-video-script-writer/SKILL.md +136 -0
@@ -7,18 +7,33 @@ import React, { useState, useEffect, useCallback } from 'react';
7
7
  import { apiClient } from '../api/client';
8
8
  import { useProjectStore } from '../stores/useProjectStore';
9
9
  import { Button } from './ds/Button';
10
- import { Plus, RotateCcw, Trash2, Save, ChevronDown } from 'lucide-react';
10
+ import { Plus, RotateCcw, Trash2, Save, ChevronDown, X } from 'lucide-react';
11
11
 
12
12
  interface Agent {
13
13
  id: string;
14
14
  name: string;
15
- promptTemplate: string;
15
+ model: string;
16
+ skills: string;
17
+ grounding: string;
18
+ defaultGrounding: string | null;
16
19
  projectId: string | null;
17
20
  isDefault: number;
18
21
  createdAt: string;
19
22
  updatedAt: string;
20
23
  }
21
24
 
25
+ interface AvailableSkill {
26
+ id: string;
27
+ name: string;
28
+ preview: string;
29
+ }
30
+
31
+ const AVAILABLE_MODELS = [
32
+ { value: 'claude-opus-4-6', label: 'Claude Opus 4.6' },
33
+ { value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' },
34
+ { value: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5' },
35
+ ];
36
+
22
37
  const PLACEHOLDER_DOCS = [
23
38
  { name: '{{featureId}}', description: 'The feature ID being worked on', stages: 'All' },
24
39
  { name: '{{projectId}}', description: 'The current project ID', stages: 'All' },
@@ -40,7 +55,11 @@ export function AgentsView() {
40
55
  const [agents, setAgents] = useState<Agent[]>([]);
41
56
  const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
42
57
  const [editName, setEditName] = useState('');
43
- const [editPrompt, setEditPrompt] = useState('');
58
+ const [editModel, setEditModel] = useState('claude-opus-4-6');
59
+ const [editSkills, setEditSkills] = useState<string[]>([]);
60
+ const [editGrounding, setEditGrounding] = useState('');
61
+ const [availableSkills, setAvailableSkills] = useState<AvailableSkill[]>([]);
62
+ const [skillPickerOpen, setSkillPickerOpen] = useState(false);
44
63
  const [loading, setLoading] = useState(true);
45
64
  const [saving, setSaving] = useState(false);
46
65
  const [error, setError] = useState('');
@@ -60,14 +79,36 @@ export function AgentsView() {
60
79
  }
61
80
  }, [scope, projectId]);
62
81
 
82
+ const fetchSkills = useCallback(async () => {
83
+ try {
84
+ const resp = await apiClient.fetchAvailableSkills();
85
+ setAvailableSkills(resp.skills);
86
+ } catch {
87
+ // Skills list is non-critical; silently fall back to empty
88
+ }
89
+ }, []);
90
+
63
91
  useEffect(() => {
64
92
  fetchData();
65
93
  }, [fetchData]);
66
94
 
95
+ useEffect(() => {
96
+ fetchSkills();
97
+ }, [fetchSkills]);
98
+
67
99
  useEffect(() => {
68
100
  if (selectedProjectId) setProjectId(selectedProjectId);
69
101
  }, [selectedProjectId]);
70
102
 
103
+ const parseSkills = (skillsJson: string): string[] => {
104
+ try {
105
+ const parsed = JSON.parse(skillsJson);
106
+ return Array.isArray(parsed) ? parsed : [];
107
+ } catch {
108
+ return [];
109
+ }
110
+ };
111
+
71
112
  const showSuccess = (msg: string) => {
72
113
  setSuccessMsg(msg);
73
114
  setTimeout(() => setSuccessMsg(''), 3000);
@@ -76,15 +117,20 @@ export function AgentsView() {
76
117
  const selectAgent = (agent: Agent) => {
77
118
  setSelectedAgent(agent);
78
119
  setEditName(agent.name);
79
- setEditPrompt(agent.promptTemplate);
120
+ setEditModel(agent.model);
121
+ setEditSkills(parseSkills(agent.skills));
122
+ setEditGrounding(agent.grounding);
80
123
  setError('');
124
+ setSkillPickerOpen(false);
81
125
  };
82
126
 
83
127
  const handleCreate = async () => {
84
128
  try {
85
129
  const data: any = {
86
130
  name: 'New Agent',
87
- promptTemplate: 'You are a helpful agent.\n\nFeature: {{featureId}}\nProject: {{projectId}}',
131
+ model: 'claude-opus-4-6',
132
+ skills: '[]',
133
+ grounding: 'You are a helpful agent.\n\nFeature: {{featureId}}\nProject: {{projectId}}',
88
134
  };
89
135
  if (scope === 'project' && projectId) data.projectId = projectId;
90
136
  const resp = await apiClient.createAgent(data);
@@ -96,14 +142,27 @@ export function AgentsView() {
96
142
  }
97
143
  };
98
144
 
145
+ const validateForm = (): string | null => {
146
+ if (!editModel) return 'Model is required';
147
+ if (!editGrounding.trim()) return 'Grounding template cannot be empty';
148
+ return null;
149
+ };
150
+
99
151
  const handleSave = async () => {
100
152
  if (!selectedAgent) return;
153
+ const validationError = validateForm();
154
+ if (validationError) {
155
+ setError(validationError);
156
+ return;
157
+ }
101
158
  setSaving(true);
102
159
  setError('');
103
160
  try {
104
161
  const resp = await apiClient.updateAgent(selectedAgent.id, {
105
162
  name: editName.trim(),
106
- promptTemplate: editPrompt,
163
+ model: editModel,
164
+ skills: JSON.stringify(editSkills),
165
+ grounding: editGrounding,
107
166
  });
108
167
  setSelectedAgent(resp.agent);
109
168
  await fetchData();
@@ -141,7 +200,27 @@ export function AgentsView() {
141
200
  }
142
201
  };
143
202
 
144
- const hasChanges = selectedAgent && (editName !== selectedAgent.name || editPrompt !== selectedAgent.promptTemplate);
203
+ const toggleSkill = (skillId: string) => {
204
+ setEditSkills((prev) =>
205
+ prev.includes(skillId) ? prev.filter((s) => s !== skillId) : [...prev, skillId],
206
+ );
207
+ };
208
+
209
+ const removeSkill = (skillId: string) => {
210
+ setEditSkills((prev) => prev.filter((s) => s !== skillId));
211
+ };
212
+
213
+ const hasChanges =
214
+ selectedAgent &&
215
+ (editName !== selectedAgent.name ||
216
+ editModel !== selectedAgent.model ||
217
+ JSON.stringify(editSkills) !== JSON.stringify(parseSkills(selectedAgent.skills)) ||
218
+ editGrounding !== selectedAgent.grounding);
219
+
220
+ const groundingModified =
221
+ selectedAgent?.isDefault === 1 &&
222
+ selectedAgent.defaultGrounding !== null &&
223
+ editGrounding !== selectedAgent.defaultGrounding;
145
224
 
146
225
  return (
147
226
  <div className="flex h-full">
@@ -272,15 +351,116 @@ export function AgentsView() {
272
351
  </div>
273
352
  </div>
274
353
 
275
- {/* Prompt Template Editor */}
354
+ {/* Model & Skills Row */}
355
+ <div className="px-4 pt-4 pb-2 space-y-3 border-b border-edge">
356
+ {/* Model Selector */}
357
+ <div>
358
+ <div className="text-[11px] font-medium text-content-muted uppercase tracking-wider mb-1.5">Model</div>
359
+ <div className="relative">
360
+ <select
361
+ value={editModel}
362
+ onChange={(e) => setEditModel(e.target.value)}
363
+ className="w-full px-3 py-1.5 rounded-md border border-edge bg-surface text-[12px] text-content appearance-none pr-7"
364
+ >
365
+ {AVAILABLE_MODELS.map(m => (
366
+ <option key={m.value} value={m.value}>{m.label}</option>
367
+ ))}
368
+ </select>
369
+ <ChevronDown size={12} className="absolute right-2 top-1/2 -translate-y-1/2 text-content-muted pointer-events-none" />
370
+ </div>
371
+ </div>
372
+
373
+ {/* Skills Picker */}
374
+ <div>
375
+ <div className="text-[11px] font-medium text-content-muted uppercase tracking-wider mb-1.5">Skills</div>
376
+ {/* Selected skills tags */}
377
+ <div className="flex flex-wrap gap-1.5 mb-2">
378
+ {editSkills.length === 0 && (
379
+ <span className="text-[11px] text-content-muted">No skills selected</span>
380
+ )}
381
+ {editSkills.map(skillId => (
382
+ <span
383
+ key={skillId}
384
+ className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-accent/10 text-accent text-[11px] font-medium"
385
+ >
386
+ {skillId}
387
+ <button
388
+ onClick={() => removeSkill(skillId)}
389
+ className="hover:text-accent/70 transition-colors"
390
+ >
391
+ <X size={10} />
392
+ </button>
393
+ </span>
394
+ ))}
395
+ </div>
396
+ {/* Add skills dropdown */}
397
+ <div className="relative">
398
+ <button
399
+ onClick={() => setSkillPickerOpen(!skillPickerOpen)}
400
+ className="px-3 py-1.5 rounded-md border border-edge bg-surface text-[12px] text-content-muted hover:text-content hover:border-accent/30 transition-all"
401
+ >
402
+ {skillPickerOpen ? 'Close' : 'Add skills...'}
403
+ </button>
404
+ {skillPickerOpen && (
405
+ <div className="absolute z-10 mt-1 w-80 max-h-48 overflow-y-auto rounded-md border border-edge bg-panel shadow-lg">
406
+ {availableSkills.length === 0 ? (
407
+ <div className="p-3 text-[11px] text-content-muted">No skills available</div>
408
+ ) : (
409
+ availableSkills.map(skill => {
410
+ const isSelected = editSkills.includes(skill.id);
411
+ return (
412
+ <button
413
+ key={skill.id}
414
+ onClick={() => toggleSkill(skill.id)}
415
+ className={`w-full text-left px-3 py-2 border-b border-edge/50 transition-all hover:bg-surface-raised ${
416
+ isSelected ? 'bg-accent/5' : ''
417
+ }`}
418
+ >
419
+ <div className="flex items-center gap-2">
420
+ <div
421
+ className={`w-3.5 h-3.5 rounded border flex items-center justify-center shrink-0 ${
422
+ isSelected
423
+ ? 'bg-accent border-accent text-surface'
424
+ : 'border-edge'
425
+ }`}
426
+ >
427
+ {isSelected && (
428
+ <svg width="8" height="8" viewBox="0 0 8 8" fill="none">
429
+ <path d="M1.5 4L3 5.5L6.5 2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
430
+ </svg>
431
+ )}
432
+ </div>
433
+ <div className="min-w-0">
434
+ <div className="text-[12px] font-medium text-content">{skill.id}</div>
435
+ <div className="text-[10px] text-content-muted truncate">{skill.name}</div>
436
+ </div>
437
+ </div>
438
+ </button>
439
+ );
440
+ })
441
+ )}
442
+ </div>
443
+ )}
444
+ </div>
445
+ </div>
446
+ </div>
447
+
448
+ {/* Grounding Template Editor */}
276
449
  <div className="flex-1 flex overflow-hidden">
277
450
  <div className="flex-1 flex flex-col p-4 overflow-hidden">
278
- <div className="text-[11px] font-medium text-content-muted uppercase tracking-wider mb-2">Prompt Template</div>
451
+ <div className="flex items-center justify-between mb-2">
452
+ <div className="text-[11px] font-medium text-content-muted uppercase tracking-wider">Grounding Template</div>
453
+ {groundingModified && (
454
+ <Button variant="ghost" size="sm" icon={<RotateCcw size={10} />} onClick={handleReset}>
455
+ Reset Grounding to Default
456
+ </Button>
457
+ )}
458
+ </div>
279
459
  <textarea
280
- value={editPrompt}
281
- onChange={e => setEditPrompt(e.target.value)}
460
+ value={editGrounding}
461
+ onChange={e => setEditGrounding(e.target.value)}
282
462
  className="flex-1 w-full px-3 py-2 rounded-md border border-edge bg-surface text-[12px] text-content font-mono resize-none focus:border-accent/40 focus:outline-none"
283
- placeholder="Enter agent prompt template..."
463
+ placeholder="Enter agent grounding template..."
284
464
  spellCheck={false}
285
465
  />
286
466
  </div>
@@ -0,0 +1,98 @@
1
+ /**
2
+ * AttachmentPreviewList — displays attached files as preview chips
3
+ * below the message input area before sending.
4
+ *
5
+ * Shows image thumbnails for image files, and a file icon with name
6
+ * for non-image files. Each chip has a remove button.
7
+ */
8
+
9
+ import { X, FileText, FileCode, FileImage, File as FileIcon, Loader2 } from 'lucide-react';
10
+ import { AttachmentManager, type Attachment } from '../lib/attachment_manager';
11
+
12
+ interface Props {
13
+ attachments: readonly Attachment[];
14
+ onRemove: (id: string) => void;
15
+ }
16
+
17
+ const FILE_ICON_MAP: Record<string, typeof FileText> = {
18
+ 'text/': FileText,
19
+ 'application/json': FileCode,
20
+ 'application/javascript': FileCode,
21
+ 'application/typescript': FileCode,
22
+ 'text/html': FileCode,
23
+ 'text/css': FileCode,
24
+ 'text/xml': FileCode,
25
+ };
26
+
27
+ const getFileIcon = (mimeType: string) => {
28
+ if (AttachmentManager.isImage(mimeType)) return FileImage;
29
+ for (const [prefix, Icon] of Object.entries(FILE_ICON_MAP)) {
30
+ if (mimeType.startsWith(prefix)) return Icon;
31
+ }
32
+ return FileIcon;
33
+ };
34
+
35
+ const formatSize = (bytes: number): string => {
36
+ if (bytes < 1024) return `${bytes} B`;
37
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
38
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
39
+ };
40
+
41
+ export function AttachmentPreviewList({ attachments, onRemove }: Props) {
42
+ if (attachments.length === 0) return null;
43
+
44
+ return (
45
+ <div className="flex flex-wrap gap-2 px-3 pb-2">
46
+ {attachments.map((att) => (
47
+ <AttachmentChip key={att.id} attachment={att} onRemove={onRemove} />
48
+ ))}
49
+ </div>
50
+ );
51
+ }
52
+
53
+ interface ChipProps {
54
+ attachment: Attachment;
55
+ onRemove: (id: string) => void;
56
+ }
57
+
58
+ function AttachmentChip({ attachment, onRemove }: ChipProps) {
59
+ const isImage = AttachmentManager.isImage(attachment.mimeType);
60
+ const Icon = getFileIcon(attachment.mimeType);
61
+
62
+ return (
63
+ <div className="relative group flex items-center gap-2 bg-surface-alt border border-edge rounded-lg px-2.5 py-1.5 max-w-[200px]">
64
+ {/* Preview or icon */}
65
+ {isImage && attachment.preview ? (
66
+ <img
67
+ src={attachment.preview}
68
+ alt={attachment.name}
69
+ className="w-8 h-8 rounded object-cover flex-shrink-0"
70
+ />
71
+ ) : (
72
+ <Icon size={16} strokeWidth={1.5} className="text-content-muted flex-shrink-0" />
73
+ )}
74
+
75
+ {/* Name and size */}
76
+ <div className="flex flex-col min-w-0 flex-1">
77
+ <span className="text-[11px] font-mono text-content truncate">{attachment.name}</span>
78
+ <span className="text-[10px] font-mono text-content-muted">
79
+ {attachment.uploading ? 'Uploading...' : formatSize(attachment.size)}
80
+ </span>
81
+ </div>
82
+
83
+ {/* Upload spinner or remove button */}
84
+ {attachment.uploading ? (
85
+ <Loader2 size={12} className="text-accent animate-spin flex-shrink-0" />
86
+ ) : (
87
+ <button
88
+ type="button"
89
+ onClick={() => onRemove(attachment.id)}
90
+ className="absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-surface border border-edge flex items-center justify-center text-content-secondary cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity duration-150 hover:bg-error hover:text-white hover:border-error"
91
+ aria-label={`Remove ${attachment.name}`}
92
+ >
93
+ <X size={8} strokeWidth={2.5} />
94
+ </button>
95
+ )}
96
+ </div>
97
+ );
98
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * AutocompleteDropdown — renders a scrollable list of autocomplete suggestions
3
+ * for file path (@) and skill (/) mentions in the chat input.
4
+ *
5
+ * Supports keyboard navigation (arrow keys highlight items) and mouse selection.
6
+ * Uses onMouseDown with preventDefault to avoid stealing focus from the textarea.
7
+ */
8
+
9
+ import { useRef, useEffect } from 'react';
10
+ import { FileText, Terminal } from 'lucide-react';
11
+ import type { AutocompleteItem } from '../hooks/use_mention_autocomplete';
12
+
13
+ interface AutocompleteDropdownProps {
14
+ items: AutocompleteItem[];
15
+ selectedIndex: number;
16
+ onSelect: (item: AutocompleteItem) => void;
17
+ }
18
+
19
+ export const AutocompleteDropdown = ({ items, selectedIndex, onSelect }: AutocompleteDropdownProps) => {
20
+ const selectedRef = useRef<HTMLDivElement>(null);
21
+
22
+ // Scroll selected item into view
23
+ useEffect(() => {
24
+ selectedRef.current?.scrollIntoView({ block: 'nearest' });
25
+ }, [selectedIndex]);
26
+
27
+ if (items.length === 0) return null;
28
+
29
+ return (
30
+ <div
31
+ className="max-h-48 overflow-y-auto bg-surface border border-edge rounded-lg shadow-lg py-1"
32
+ role="listbox"
33
+ >
34
+ {items.map((item, index) => (
35
+ <div
36
+ key={item.value}
37
+ ref={index === selectedIndex ? selectedRef : undefined}
38
+ className={`flex items-center gap-2 px-3 py-1.5 cursor-pointer text-[13px] font-mono transition-colors duration-75 ${
39
+ index === selectedIndex
40
+ ? 'bg-accent/10 text-accent'
41
+ : 'text-content hover:bg-surface-raised'
42
+ }`}
43
+ role="option"
44
+ aria-selected={index === selectedIndex}
45
+ onMouseDown={(e) => {
46
+ e.preventDefault();
47
+ onSelect(item);
48
+ }}
49
+ >
50
+ {item.type === 'file' ? (
51
+ <FileText size={12} className="shrink-0 text-content-muted" />
52
+ ) : (
53
+ <Terminal size={12} className="shrink-0 text-content-muted" />
54
+ )}
55
+ <span className="truncate">{item.label}</span>
56
+ {item.description && (
57
+ <span className="ml-auto text-[11px] text-content-muted truncate max-w-[200px]">
58
+ {item.description}
59
+ </span>
60
+ )}
61
+ </div>
62
+ ))}
63
+ </div>
64
+ );
65
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * ChatAttachButton — paperclip button to attach files via the native file picker.
3
+ *
4
+ * Opens the browser file dialog when clicked. Accepts any file type.
5
+ * Selected files are passed to the onFilesSelected callback.
6
+ */
7
+
8
+ import { useRef, useCallback } from 'react';
9
+ import { Paperclip } from 'lucide-react';
10
+ import { IconButton } from './ds/IconButton';
11
+
12
+ interface Props {
13
+ onFilesSelected: (files: File[]) => void;
14
+ disabled?: boolean;
15
+ }
16
+
17
+ export function ChatAttachButton({ onFilesSelected, disabled }: Props) {
18
+ const inputRef = useRef<HTMLInputElement>(null);
19
+
20
+ const handleClick = useCallback(() => {
21
+ inputRef.current?.click();
22
+ }, []);
23
+
24
+ const handleChange = useCallback(() => {
25
+ const input = inputRef.current;
26
+ if (!input?.files || input.files.length === 0) return;
27
+
28
+ const files = Array.from(input.files);
29
+ onFilesSelected(files);
30
+
31
+ // Reset so the same file can be selected again
32
+ input.value = '';
33
+ }, [onFilesSelected]);
34
+
35
+ return (
36
+ <>
37
+ <IconButton
38
+ label="Attach files"
39
+ variant="ghost"
40
+ size="sm"
41
+ disabled={disabled}
42
+ onClick={handleClick}
43
+ >
44
+ <Paperclip size={14} strokeWidth={2} />
45
+ </IconButton>
46
+ <input
47
+ ref={inputRef}
48
+ type="file"
49
+ multiple
50
+ className="hidden"
51
+ onChange={handleChange}
52
+ tabIndex={-1}
53
+ />
54
+ </>
55
+ );
56
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * ChatDropZone — wraps the chat area to accept file drag-and-drop.
3
+ *
4
+ * When files are dragged over the zone, a visual overlay appears.
5
+ * On drop, the onFilesDropped callback is invoked with the dropped files.
6
+ */
7
+
8
+ import { useState, useCallback, useRef, type DragEvent, type ReactNode } from 'react';
9
+ import { Upload } from 'lucide-react';
10
+
11
+ interface Props {
12
+ children: ReactNode;
13
+ onFilesDropped: (files: File[]) => void;
14
+ disabled?: boolean;
15
+ }
16
+
17
+ export function ChatDropZone({ children, onFilesDropped, disabled }: Props) {
18
+ const [dragOver, setDragOver] = useState(false);
19
+ const dragCounterRef = useRef(0);
20
+
21
+ const handleDragEnter = useCallback((e: DragEvent) => {
22
+ e.preventDefault();
23
+ e.stopPropagation();
24
+ if (disabled) return;
25
+ dragCounterRef.current++;
26
+ if (e.dataTransfer.types.includes('Files')) {
27
+ setDragOver(true);
28
+ }
29
+ }, [disabled]);
30
+
31
+ const handleDragLeave = useCallback((e: DragEvent) => {
32
+ e.preventDefault();
33
+ e.stopPropagation();
34
+ dragCounterRef.current--;
35
+ if (dragCounterRef.current === 0) {
36
+ setDragOver(false);
37
+ }
38
+ }, []);
39
+
40
+ const handleDragOver = useCallback((e: DragEvent) => {
41
+ e.preventDefault();
42
+ e.stopPropagation();
43
+ }, []);
44
+
45
+ const handleDrop = useCallback((e: DragEvent) => {
46
+ e.preventDefault();
47
+ e.stopPropagation();
48
+ setDragOver(false);
49
+ dragCounterRef.current = 0;
50
+
51
+ if (disabled) return;
52
+
53
+ const files = Array.from(e.dataTransfer.files);
54
+ if (files.length > 0) {
55
+ onFilesDropped(files);
56
+ }
57
+ }, [disabled, onFilesDropped]);
58
+
59
+ return (
60
+ <div
61
+ className="relative flex-1 flex flex-col min-h-0 min-w-0"
62
+ onDragEnter={handleDragEnter}
63
+ onDragLeave={handleDragLeave}
64
+ onDragOver={handleDragOver}
65
+ onDrop={handleDrop}
66
+ >
67
+ {children}
68
+
69
+ {/* Drop overlay */}
70
+ {dragOver && (
71
+ <div className="absolute inset-0 z-50 flex items-center justify-center bg-accent/5 border-2 border-dashed border-accent/40 rounded-xl pointer-events-none">
72
+ <div className="flex flex-col items-center gap-2 text-accent">
73
+ <Upload size={32} strokeWidth={1.5} />
74
+ <span className="text-sm font-mono">Drop files to attach</span>
75
+ </div>
76
+ </div>
77
+ )}
78
+ </div>
79
+ );
80
+ }