autoforge-ai 0.1.7 → 0.1.8
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.
- package/mcp_server/feature_mcp.py +30 -0
- package/package.json +1 -1
- package/server/routers/assistant_chat.py +30 -0
- package/server/services/assistant_chat_session.py +20 -1
- package/ui/dist/assets/index-iJGA9Z8u.js +94 -0
- package/ui/dist/index.html +1 -1
- package/ui/dist/assets/index-CaW-gQTV.js +0 -94
|
@@ -984,5 +984,35 @@ def feature_set_dependencies(
|
|
|
984
984
|
return json.dumps({"error": f"Failed to set dependencies: {str(e)}"})
|
|
985
985
|
|
|
986
986
|
|
|
987
|
+
@mcp.tool()
|
|
988
|
+
def ask_user(
|
|
989
|
+
questions: Annotated[list[dict], Field(description="List of questions to ask, each with question, header, options (list of {label, description}), and multiSelect (bool)")]
|
|
990
|
+
) -> str:
|
|
991
|
+
"""Ask the user structured questions with selectable options.
|
|
992
|
+
|
|
993
|
+
Use this when you need clarification or want to offer choices to the user.
|
|
994
|
+
Each question has a short header, the question text, and 2-4 clickable options.
|
|
995
|
+
The user's selections will be returned as your next message.
|
|
996
|
+
|
|
997
|
+
Args:
|
|
998
|
+
questions: List of questions, each with:
|
|
999
|
+
- question (str): The question to ask
|
|
1000
|
+
- header (str): Short label (max 12 chars)
|
|
1001
|
+
- options (list): Each with label (str) and description (str)
|
|
1002
|
+
- multiSelect (bool): Allow multiple selections (default false)
|
|
1003
|
+
|
|
1004
|
+
Returns:
|
|
1005
|
+
Acknowledgment that questions were presented to the user
|
|
1006
|
+
"""
|
|
1007
|
+
# Validate input
|
|
1008
|
+
for i, q in enumerate(questions):
|
|
1009
|
+
if not all(key in q for key in ["question", "header", "options"]):
|
|
1010
|
+
return json.dumps({"error": f"Question at index {i} missing required fields"})
|
|
1011
|
+
if len(q["options"]) < 2 or len(q["options"]) > 4:
|
|
1012
|
+
return json.dumps({"error": f"Question at index {i} must have 2-4 options"})
|
|
1013
|
+
|
|
1014
|
+
return "Questions presented to the user. Their response will arrive as your next message."
|
|
1015
|
+
|
|
1016
|
+
|
|
987
1017
|
if __name__ == "__main__":
|
|
988
1018
|
mcp.run()
|
package/package.json
CHANGED
|
@@ -207,12 +207,14 @@ async def assistant_chat_websocket(websocket: WebSocket, project_name: str):
|
|
|
207
207
|
Client -> Server:
|
|
208
208
|
- {"type": "start", "conversation_id": int | null} - Start/resume session
|
|
209
209
|
- {"type": "message", "content": "..."} - Send user message
|
|
210
|
+
- {"type": "answer", "answers": {...}} - Answer to structured questions
|
|
210
211
|
- {"type": "ping"} - Keep-alive ping
|
|
211
212
|
|
|
212
213
|
Server -> Client:
|
|
213
214
|
- {"type": "conversation_created", "conversation_id": int} - New conversation created
|
|
214
215
|
- {"type": "text", "content": "..."} - Text chunk from Claude
|
|
215
216
|
- {"type": "tool_call", "tool": "...", "input": {...}} - Tool being called
|
|
217
|
+
- {"type": "question", "questions": [...]} - Structured questions for user
|
|
216
218
|
- {"type": "response_done"} - Response complete
|
|
217
219
|
- {"type": "error", "content": "..."} - Error message
|
|
218
220
|
- {"type": "pong"} - Keep-alive pong
|
|
@@ -303,6 +305,34 @@ async def assistant_chat_websocket(websocket: WebSocket, project_name: str):
|
|
|
303
305
|
async for chunk in session.send_message(user_content):
|
|
304
306
|
await websocket.send_json(chunk)
|
|
305
307
|
|
|
308
|
+
elif msg_type == "answer":
|
|
309
|
+
# User answered a structured question
|
|
310
|
+
if not session:
|
|
311
|
+
session = get_session(project_name)
|
|
312
|
+
if not session:
|
|
313
|
+
await websocket.send_json({
|
|
314
|
+
"type": "error",
|
|
315
|
+
"content": "No active session. Send 'start' first."
|
|
316
|
+
})
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
# Format the answers as a natural response
|
|
320
|
+
answers = message.get("answers", {})
|
|
321
|
+
if isinstance(answers, dict):
|
|
322
|
+
response_parts = []
|
|
323
|
+
for question_idx, answer_value in answers.items():
|
|
324
|
+
if isinstance(answer_value, list):
|
|
325
|
+
response_parts.append(", ".join(answer_value))
|
|
326
|
+
else:
|
|
327
|
+
response_parts.append(str(answer_value))
|
|
328
|
+
user_response = "; ".join(response_parts) if response_parts else "OK"
|
|
329
|
+
else:
|
|
330
|
+
user_response = str(answers)
|
|
331
|
+
|
|
332
|
+
# Stream Claude's response
|
|
333
|
+
async for chunk in session.send_message(user_response):
|
|
334
|
+
await websocket.send_json(chunk)
|
|
335
|
+
|
|
306
336
|
else:
|
|
307
337
|
await websocket.send_json({
|
|
308
338
|
"type": "error",
|
|
@@ -47,8 +47,13 @@ FEATURE_MANAGEMENT_TOOLS = [
|
|
|
47
47
|
"mcp__features__feature_skip",
|
|
48
48
|
]
|
|
49
49
|
|
|
50
|
+
# Interactive tools
|
|
51
|
+
INTERACTIVE_TOOLS = [
|
|
52
|
+
"mcp__features__ask_user",
|
|
53
|
+
]
|
|
54
|
+
|
|
50
55
|
# Combined list for assistant
|
|
51
|
-
ASSISTANT_FEATURE_TOOLS = READONLY_FEATURE_MCP_TOOLS + FEATURE_MANAGEMENT_TOOLS
|
|
56
|
+
ASSISTANT_FEATURE_TOOLS = READONLY_FEATURE_MCP_TOOLS + FEATURE_MANAGEMENT_TOOLS + INTERACTIVE_TOOLS
|
|
52
57
|
|
|
53
58
|
# Read-only built-in tools (no Write, Edit, Bash)
|
|
54
59
|
READONLY_BUILTIN_TOOLS = [
|
|
@@ -123,6 +128,9 @@ If the user asks you to modify code, explain that you're a project assistant and
|
|
|
123
128
|
- **feature_create_bulk**: Create multiple features at once
|
|
124
129
|
- **feature_skip**: Move a feature to the end of the queue
|
|
125
130
|
|
|
131
|
+
**Interactive:**
|
|
132
|
+
- **ask_user**: Present structured multiple-choice questions to the user. Use this when you need to clarify requirements, offer design choices, or guide a decision. The user sees clickable option buttons and their selection is returned as your next message.
|
|
133
|
+
|
|
126
134
|
## Creating Features
|
|
127
135
|
|
|
128
136
|
When a user asks to add a feature, use the `feature_create` or `feature_create_bulk` MCP tools directly:
|
|
@@ -402,6 +410,17 @@ class AssistantChatSession:
|
|
|
402
410
|
elif block_type == "ToolUseBlock" and hasattr(block, "name"):
|
|
403
411
|
tool_name = block.name
|
|
404
412
|
tool_input = getattr(block, "input", {})
|
|
413
|
+
|
|
414
|
+
# Intercept ask_user tool calls -> yield as question message
|
|
415
|
+
if tool_name == "mcp__features__ask_user":
|
|
416
|
+
questions = tool_input.get("questions", [])
|
|
417
|
+
if questions:
|
|
418
|
+
yield {
|
|
419
|
+
"type": "question",
|
|
420
|
+
"questions": questions,
|
|
421
|
+
}
|
|
422
|
+
continue
|
|
423
|
+
|
|
405
424
|
yield {
|
|
406
425
|
"type": "tool_call",
|
|
407
426
|
"tool": tool_name,
|