(
+ message.optionSelected || null
+ )
+ const optionLockedRef = useRef(!!message.optionSelected)
// Show reply for ALL agent messages
const canReply = message.style === 'agent' && onReply
@@ -82,6 +88,27 @@ export const ChatMessageItem = memo(function ChatMessageItem({
+ {message.options && message.options.length > 0 && (
+
+ Please select a response to continue:
+ {message.options.map((opt, index) => (
+
+ ))}
+
+ )}
{/* Reply button - positioned outside the bubble at top-right */}
{canReply && isHovered && (
diff --git a/app/ui_layer/browser/frontend/src/pages/Chat/ChatPage.module.css b/app/ui_layer/browser/frontend/src/pages/Chat/ChatPage.module.css
index 6f34429c..ff81a83c 100644
--- a/app/ui_layer/browser/frontend/src/pages/Chat/ChatPage.module.css
+++ b/app/ui_layer/browser/frontend/src/pages/Chat/ChatPage.module.css
@@ -194,6 +194,57 @@
word-break: break-word;
}
+/* Interactive option buttons inside messages */
+.messageOptions {
+ display: flex;
+ flex-direction: column;
+ margin-top: var(--space-2);
+ border-top: 1px solid var(--border-primary);
+ padding-top: var(--space-2);
+}
+
+.optionsPrompt {
+ font-size: var(--text-xs);
+ color: var(--text-secondary);
+ padding: 0 0 var(--space-2);
+}
+
+.optionButton {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ width: 100%;
+ padding: var(--space-2) var(--space-3);
+ border: none;
+ border-top: 1px solid var(--border-primary);
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ font-size: var(--text-sm);
+ font-weight: var(--font-medium);
+ cursor: pointer;
+ text-align: left;
+ border-radius: var(--radius-sm);
+ transition: background var(--transition-fast);
+}
+
+.optionButton:hover:not(:disabled) {
+ background: var(--bg-primary);
+}
+
+.optionButton--selected {
+ background: var(--bg-primary);
+}
+
+.optionButton--disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.optionIndex {
+ font-weight: var(--font-semibold);
+ color: var(--text-secondary);
+}
+
/* Attachments underneath the bubble */
.messageAttachments {
width: 100%;
diff --git a/app/ui_layer/browser/frontend/src/pages/Chat/ChatPage.tsx b/app/ui_layer/browser/frontend/src/pages/Chat/ChatPage.tsx
index aad99257..756a1ad9 100644
--- a/app/ui_layer/browser/frontend/src/pages/Chat/ChatPage.tsx
+++ b/app/ui_layer/browser/frontend/src/pages/Chat/ChatPage.tsx
@@ -53,7 +53,7 @@ const formatFileSize = (bytes: number): string => {
}
export function ChatPage() {
- const { messages, actions, connected, sendMessage, cancelTask, cancellingTaskId, openFile, openFolder, lastSeenMessageId, markMessagesAsSeen, replyTarget, setReplyTarget, clearReplyTarget, loadOlderMessages, hasMoreMessages, loadingOlderMessages } = useWebSocket()
+ const { messages, actions, connected, sendMessage, cancelTask, cancellingTaskId, openFile, openFolder, lastSeenMessageId, markMessagesAsSeen, replyTarget, setReplyTarget, clearReplyTarget, loadOlderMessages, hasMoreMessages, loadingOlderMessages, sendOptionClick } = useWebSocket()
// Derive agent status from actions and messages
const status = useDerivedAgentStatus({
@@ -556,6 +556,7 @@ export function ChatPage() {
onOpenFile={openFile}
onOpenFolder={openFolder}
onReply={handleChatReply}
+ onOptionClick={sendOptionClick}
/>
)
diff --git a/app/ui_layer/browser/frontend/src/types/index.ts b/app/ui_layer/browser/frontend/src/types/index.ts
index a6d55b27..cc4398c0 100644
--- a/app/ui_layer/browser/frontend/src/types/index.ts
+++ b/app/ui_layer/browser/frontend/src/types/index.ts
@@ -12,6 +12,12 @@ export interface Attachment {
url: string
}
+export interface ChatMessageOption {
+ label: string
+ value: string
+ style?: 'primary' | 'danger' | 'default'
+}
+
export interface ChatMessage {
sender: string
content: string
@@ -20,13 +26,15 @@ export interface ChatMessage {
messageId: string
attachments?: Attachment[]
taskSessionId?: string // Links message to a task session for reply feature
+ options?: ChatMessageOption[]
+ optionSelected?: string // Value of the option that was selected
}
// ─────────────────────────────────────────────────────────────────────
// Action/Task Types
// ─────────────────────────────────────────────────────────────────────
-export type ActionStatus = 'running' | 'completed' | 'error' | 'pending' | 'cancelled' | 'waiting'
+export type ActionStatus = 'running' | 'completed' | 'error' | 'pending' | 'cancelled' | 'waiting' | 'paused'
export type ItemType = 'task' | 'action' | 'reasoning'
export interface ActionItem {
@@ -95,6 +103,8 @@ export type WSMessageType =
// Task control
| 'task_cancel'
| 'task_cancel_response'
+ // Option click (interactive buttons in chat)
+ | 'option_click'
// Onboarding
| 'onboarding_step'
| 'onboarding_step_get'
diff --git a/app/ui_layer/components/types.py b/app/ui_layer/components/types.py
index f0dedf3d..3c3a0437 100644
--- a/app/ui_layer/components/types.py
+++ b/app/ui_layer/components/types.py
@@ -29,6 +29,22 @@ class Attachment:
url: str
+@dataclass
+class ChatMessageOption:
+ """
+ Data structure for an interactive option/button in a chat message.
+
+ Attributes:
+ label: Button text displayed to the user (e.g. "Continue")
+ value: Machine-readable value sent back on click (e.g. "continue_limit")
+ style: Visual style - "primary", "danger", or "default"
+ """
+
+ label: str
+ value: str
+ style: str = "default"
+
+
@dataclass
class ChatMessage:
"""
@@ -44,6 +60,8 @@ class ChatMessage:
message_id: Optional unique identifier for the message
attachments: Optional list of file attachments
task_session_id: Optional task session ID for reply feature
+ options: Optional list of interactive options/buttons
+ option_selected: Value of the option that was selected, if any
"""
sender: str
@@ -53,6 +71,8 @@ class ChatMessage:
message_id: Optional[str] = None
attachments: Optional[List[Attachment]] = None
task_session_id: Optional[str] = None
+ options: Optional[List[ChatMessageOption]] = None
+ option_selected: Optional[str] = None
def __post_init__(self) -> None:
"""Generate message_id if not provided."""
diff --git a/app/ui_layer/controller/ui_controller.py b/app/ui_layer/controller/ui_controller.py
index f65125bf..9ec34ae3 100644
--- a/app/ui_layer/controller/ui_controller.py
+++ b/app/ui_layer/controller/ui_controller.py
@@ -280,6 +280,21 @@ async def submit_message(
await self._agent._handle_chat_message(payload)
+ async def handle_option_click(self, value: str, session_id: str) -> None:
+ """
+ Handle a user clicking an option button in a chat message.
+
+ Routes limit-choice options to the appropriate agent handler.
+
+ Args:
+ value: The option value (e.g. "continue_limit", "abort_limit")
+ session_id: The task session ID associated with the option
+ """
+ if value == "continue_limit":
+ await self._agent.handle_limit_continue(session_id)
+ elif value == "abort_limit":
+ await self._agent.handle_limit_abort(session_id)
+
# ─────────────────────────────────────────────────────────────────────
# Event Processing
# ─────────────────────────────────────────────────────────────────────
diff --git a/app/usage/chat_storage.py b/app/usage/chat_storage.py
index 17de5ffc..da85aa3e 100644
--- a/app/usage/chat_storage.py
+++ b/app/usage/chat_storage.py
@@ -34,6 +34,8 @@ class StoredChatMessage:
timestamp: float
attachments: Optional[List[Dict[str, Any]]] = None
task_session_id: Optional[str] = None
+ options: Optional[List[Dict[str, Any]]] = None
+ option_selected: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
@@ -48,6 +50,10 @@ def to_dict(self) -> Dict[str, Any]:
result["attachments"] = self.attachments
if self.task_session_id:
result["taskSessionId"] = self.task_session_id
+ if self.options:
+ result["options"] = self.options
+ if self.option_selected:
+ result["optionSelected"] = self.option_selected
return result
@@ -104,7 +110,7 @@ def _init_db(self) -> None:
ON chat_messages(message_id)
""")
- # Migration: Add task_session_id column if it doesn't exist
+ # Migration: Add new columns if they don't exist
cursor.execute("PRAGMA table_info(chat_messages)")
columns = [col[1] for col in cursor.fetchall()]
if "task_session_id" not in columns:
@@ -113,6 +119,18 @@ def _init_db(self) -> None:
ADD COLUMN task_session_id TEXT
""")
logger.info("[ChatStorage] Migrated: added task_session_id column")
+ if "options" not in columns:
+ cursor.execute("""
+ ALTER TABLE chat_messages
+ ADD COLUMN options TEXT
+ """)
+ logger.info("[ChatStorage] Migrated: added options column")
+ if "option_selected" not in columns:
+ cursor.execute("""
+ ALTER TABLE chat_messages
+ ADD COLUMN option_selected TEXT
+ """)
+ logger.info("[ChatStorage] Migrated: added option_selected column")
conn.commit()
@@ -130,8 +148,8 @@ def insert_message(self, message: StoredChatMessage) -> int:
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO chat_messages
- (message_id, sender, content, style, timestamp, attachments, task_session_id)
- VALUES (?, ?, ?, ?, ?, ?, ?)
+ (message_id, sender, content, style, timestamp, attachments, task_session_id, options, option_selected)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
message.message_id,
message.sender,
@@ -140,6 +158,8 @@ def insert_message(self, message: StoredChatMessage) -> int:
message.timestamp,
json.dumps(message.attachments) if message.attachments else None,
message.task_session_id,
+ json.dumps(message.options) if message.options else None,
+ message.option_selected,
))
conn.commit()
return cursor.lastrowid
@@ -162,7 +182,7 @@ def get_messages(
with sqlite3.connect(self._db_path) as conn:
cursor = conn.cursor()
cursor.execute("""
- SELECT message_id, sender, content, style, timestamp, attachments, task_session_id
+ SELECT message_id, sender, content, style, timestamp, attachments, task_session_id, options, option_selected
FROM chat_messages
ORDER BY timestamp ASC
LIMIT ? OFFSET ?
@@ -178,6 +198,8 @@ def get_messages(
timestamp=row[4],
attachments=json.loads(row[5]) if row[5] else None,
task_session_id=row[6],
+ options=json.loads(row[7]) if row[7] else None,
+ option_selected=row[8],
)
for row in rows
]
@@ -196,7 +218,7 @@ def get_recent_messages(self, limit: int = 100) -> List[StoredChatMessage]:
cursor = conn.cursor()
# Get last N messages ordered by timestamp DESC, then reverse
cursor.execute("""
- SELECT message_id, sender, content, style, timestamp, attachments, task_session_id
+ SELECT message_id, sender, content, style, timestamp, attachments, task_session_id, options, option_selected
FROM chat_messages
ORDER BY timestamp DESC
LIMIT ?
@@ -212,6 +234,8 @@ def get_recent_messages(self, limit: int = 100) -> List[StoredChatMessage]:
timestamp=row[4],
attachments=json.loads(row[5]) if row[5] else None,
task_session_id=row[6],
+ options=json.loads(row[7]) if row[7] else None,
+ option_selected=row[8],
)
for row in rows
]
@@ -234,6 +258,26 @@ def clear_messages(self) -> int:
conn.commit()
return count
+ def update_option_selected(self, message_id: str, option_value: str) -> bool:
+ """
+ Mark which option was selected on a message.
+
+ Args:
+ message_id: The message ID to update.
+ option_value: The value of the selected option.
+
+ Returns:
+ True if the message was updated, False if not found.
+ """
+ with sqlite3.connect(self._db_path) as conn:
+ cursor = conn.cursor()
+ cursor.execute(
+ "UPDATE chat_messages SET option_selected = ? WHERE message_id = ?",
+ (option_value, message_id),
+ )
+ conn.commit()
+ return cursor.rowcount > 0
+
def delete_message(self, message_id: str) -> bool:
"""
Delete a message by ID.
@@ -271,7 +315,7 @@ def get_messages_before(
with sqlite3.connect(self._db_path) as conn:
cursor = conn.cursor()
cursor.execute("""
- SELECT message_id, sender, content, style, timestamp, attachments, task_session_id
+ SELECT message_id, sender, content, style, timestamp, attachments, task_session_id, options, option_selected
FROM chat_messages
WHERE timestamp < ?
ORDER BY timestamp DESC
@@ -288,6 +332,8 @@ def get_messages_before(
timestamp=row[4],
attachments=json.loads(row[5]) if row[5] else None,
task_session_id=row[6],
+ options=json.loads(row[7]) if row[7] else None,
+ option_selected=row[8],
)
for row in rows
]
diff --git a/craftbot.log b/craftbot.log
deleted file mode 100644
index fe1ee0ea..00000000
--- a/craftbot.log
+++ /dev/null
@@ -1,299 +0,0 @@
-
-============================================================
-CraftBot service started at 2026-04-08 14:51:22
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-Traceback (most recent call last):
- File "C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py", line 1074, in
- print_browser_header()
- ~~~~~~~~~~~~~~~~~~~~^^
- File "C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py", line 610, in print_browser_header
- print("\n\U0001f916 CraftBot")
- ~~~~~^^^^^^^^^^^^^^^^^
- File "C:\Python314\Lib\encodings\cp1252.py", line 19, in encode
- return codecs.charmap_encode(input,self.errors,encoding_table)[0]
- ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-UnicodeEncodeError: 'charmap' codec can't encode character '\U0001f916' in position 2: character maps to
-
-============================================================
-CraftBot service started at 2026-04-08 14:59:15
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-Traceback (most recent call last):
- File "C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py", line 1074, in
- print_browser_header()
- ~~~~~~~~~~~~~~~~~~~~^^
- File "C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py", line 610, in print_browser_header
- print("\n\U0001f916 CraftBot")
- ~~~~~^^^^^^^^^^^^^^^^^
- File "C:\Python314\Lib\encodings\cp1252.py", line 19, in encode
- return codecs.charmap_encode(input,self.errors,encoding_table)[0]
- ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-UnicodeEncodeError: 'charmap' codec can't encode character '\U0001f916' in position 2: character maps to
-
-============================================================
-CraftBot service started at 2026-04-08 15:07:33
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-Traceback (most recent call last):
- File "C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py", line 1074, in
- print_browser_header()
- ~~~~~~~~~~~~~~~~~~~~^^
- File "C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py", line 610, in print_browser_header
- print("\n\U0001f916 CraftBot")
- ~~~~~^^^^^^^^^^^^^^^^^
- File "C:\Python314\Lib\encodings\cp1252.py", line 19, in encode
- return codecs.charmap_encode(input,self.errors,encoding_table)[0]
- ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-UnicodeEncodeError: 'charmap' codec can't encode character '\U0001f916' in position 2: character maps to
-
-============================================================
-CraftBot service started at 2026-04-08 15:18:54
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-
---- Cleanup Initiated (Exit Status: 1073807364) ---
-[*] Skipping Docker cleanup (not started in CLI mode).
-
-============================================================
-CraftBot service started at 2026-04-08 16:27:25
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-08 16:51:37
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-08 17:18:37
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-08 17:38:37
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-08 17:52:20
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-08 17:59:29
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-08 18:05:16
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-08 18:16:07
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-08 20:52:14
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-08 20:57:52
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-08 21:06:10
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
-
-============================================================
-CraftBot service started at 2026-04-09 00:58:46
-Command: C:\Python314\pythonw.exe C:\Users\ganiy\OneDrive\Desktop\OneDrive\Korivi Important Data\Aether\CraftOS\CraftBot\CraftBot\run.py --no-open-browser
-============================================================
-
-🤖 CraftBot
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-
-Mode: Browser
-
- [ 1/8] Starting frontend server... ✓
- [ 2/8] Starting agent backend... ✓
- [ 3/8] Initializing agent... ✓
- [ 4/8] Connecting to MCP servers... ✓
- [ 5/8] Loading skills... ✓
- [ 6/8] Loading libraries... ✓
- [ 7/8] Starting scheduler... ✓
- [ 8/8] Starting communications... ✓
diff --git a/craftbot.pid b/craftbot.pid
deleted file mode 100644
index b86a3065..00000000
--- a/craftbot.pid
+++ /dev/null
@@ -1 +0,0 @@
-10948
\ No newline at end of file
diff --git a/service.py b/service.py
index 725986b8..37689924 100644
--- a/service.py
+++ b/service.py
@@ -547,6 +547,9 @@ def _install_windows(run_args: List[str]) -> None:
def _uninstall_windows() -> None:
+ removed_any = False
+
+ # Remove from Task Scheduler
try:
result = subprocess.run(
["schtasks", "/delete", "/tn", TASK_NAME, "/f"],
@@ -554,11 +557,31 @@ def _uninstall_windows() -> None:
)
if result.returncode == 0:
print(f"Auto-start removed (task '{TASK_NAME}' deleted).")
- else:
- # Task may not exist
- print(f"Could not remove task (it may not be registered): {result.stderr.strip()}")
+ removed_any = True
+ except Exception as e:
+ print(f"Warning: Could not query Task Scheduler — {e}")
+
+ # Remove from Registry (HKCU\...\Run) — the fallback auto-start method
+ try:
+ import winreg
+ key = winreg.OpenKey(
+ winreg.HKEY_CURRENT_USER,
+ r"Software\Microsoft\Windows\CurrentVersion\Run",
+ 0, winreg.KEY_SET_VALUE,
+ )
+ try:
+ winreg.DeleteValue(key, TASK_NAME)
+ print(f"Auto-start removed (registry entry '{TASK_NAME}' deleted).")
+ removed_any = True
+ except FileNotFoundError:
+ pass # Entry didn't exist in registry — that's fine
+ finally:
+ winreg.CloseKey(key)
except Exception as e:
- print(f"Error: {e}")
+ print(f"Warning: Could not clean registry — {e}")
+
+ if not removed_any:
+ print("No auto-start registration found (already uninstalled?).")
# ─── Auto-start: Linux systemd (user service) ─────────────────────────────────
@@ -789,6 +812,23 @@ def cmd_install(extra_args: List[str]) -> None:
_close_console_window()
+def _remove_desktop_shortcut() -> None:
+ """Remove the CraftBot desktop shortcut if it exists."""
+ desktop = _find_desktop()
+ if not desktop:
+ return
+ if _PLATFORM == "win32":
+ shortcut_path = os.path.join(desktop, SHORTCUT_NAME)
+ else:
+ shortcut_path = os.path.join(desktop, "CraftBot.desktop")
+ if os.path.isfile(shortcut_path):
+ try:
+ os.remove(shortcut_path)
+ print(f"Desktop shortcut removed: {shortcut_path}")
+ except Exception as e:
+ print(f"Warning: Could not remove desktop shortcut — {e}")
+
+
def cmd_uninstall() -> None:
"""Remove auto-start registration and uninstall dependencies."""
# Stop the service first if running
@@ -796,6 +836,9 @@ def cmd_uninstall() -> None:
if pid and _is_running(pid):
cmd_stop()
+ # Clean up PID file
+ _remove_pid()
+
# Remove auto-start registration
plat = _PLATFORM
if plat == "win32":
@@ -805,6 +848,9 @@ def cmd_uninstall() -> None:
else:
_uninstall_linux()
+ # Remove desktop shortcut
+ _remove_desktop_shortcut()
+
# Uninstall pip packages
req_file = os.path.join(BASE_DIR, "requirements.txt")
if os.path.isfile(req_file):