@@ -67,7 +67,7 @@ final class ChatViewModel {
6767 private var turnHasMutations = false
6868 private var pendingForkContext : String ?
6969 private var apiClient : SpritesAPIClient ?
70- private var mcpSetupTask : Task < Bool , Never > ?
70+
7171 /// UUIDs of Claude NDJSON events already processed.
7272 /// Used by reconnect to skip already-handled events instead of clearing content.
7373 private var processedEventUUIDs : Set < String > = [ ]
@@ -173,12 +173,6 @@ final class ChatViewModel {
173173
174174 func loadSession( apiClient: SpritesAPIClient , modelContext: ModelContext ) {
175175 self . apiClient = apiClient
176- if UserDefaults . standard. bool ( forKey: " claudeQuestionTool " ) {
177- mcpSetupTask = Task { [ weak self] in
178- guard let self else { return false }
179- return await self . installClaudeQuestionToolIfNeeded ( apiClient: apiClient)
180- }
181- }
182176 guard let chat = fetchChat ( modelContext: modelContext) else { return }
183177
184178 sessionId = chat. claudeSessionId
@@ -595,20 +589,20 @@ final class ChatViewModel {
595589 ) async {
596590 status = . connecting
597591
598- // Wait for MCP setup to finish (no-op if setup task not running or already done)
592+ // Delete old service, then use a fresh name so logs start clean
593+ let oldServiceName = serviceName
594+ serviceName = " wisp-claude- \( UUID ( ) . uuidString. prefix ( 8 ) . lowercased ( ) ) "
595+ try ? await apiClient. deleteService ( spriteName: spriteName, serviceName: oldServiceName)
596+
597+ // Install question tool after service cleanup (sprite is awake at this point)
599598 if UserDefaults . standard. bool ( forKey: " claudeQuestionTool " ) {
600- let toolReady = await mcpSetupTask ? . value ?? false
599+ let toolReady = await installClaudeQuestionToolIfNeeded ( apiClient : apiClient )
601600 if !toolReady {
602601 status = . error( " Claude question tool failed to install — disable it in Settings or try again " )
603602 return
604603 }
605604 }
606605
607- // Delete old service, then use a fresh name so logs start clean
608- let oldServiceName = serviceName
609- serviceName = " wisp-claude- \( UUID ( ) . uuidString. prefix ( 8 ) . lowercased ( ) ) "
610- try ? await apiClient. deleteService ( spriteName: spriteName, serviceName: oldServiceName)
611-
612606 // Persist the new service name immediately for reconnect
613607 saveSession ( modelContext: modelContext)
614608
@@ -1457,22 +1451,38 @@ final class ChatViewModel {
14571451 remotePath: ClaudeQuestionTool . serverPyPath,
14581452 data: Data ( ClaudeQuestionTool . serverScript. utf8)
14591453 )
1460- try await apiClient. uploadFile (
1461- spriteName: spriteName,
1462- remotePath: ClaudeQuestionTool . versionPath,
1463- data: Data ( ClaudeQuestionTool . version. utf8)
1464- )
14651454 } catch {
14661455 logger. error ( " Claude question tool installation failed: \( error) " )
14671456 return false
14681457 }
1469- // Make server.py executable
1470- _ = await apiClient. runExec (
1458+ // Make server.py executable and write version file via exec
1459+ // (the fs/write API corrupts very small payloads to null bytes)
1460+ let installCommand = " \( ClaudeQuestionTool . chmodCommand) && mkdir -p ~/.wisp/claude-question && echo -n ' \( ClaudeQuestionTool . version) ' > \( ClaudeQuestionTool . versionPath) "
1461+ let ( installOutput, installSuccess) = await apiClient. runExec (
14711462 spriteName: spriteName,
1472- command: ClaudeQuestionTool . chmodCommand ,
1463+ command: installCommand ,
14731464 timeout: 10
14741465 )
1466+ guard installSuccess else {
1467+ let trimmedOutput = installOutput. trimmingCharacters ( in: . whitespacesAndNewlines)
1468+ logger. error ( " Claude question tool install command failed: \( trimmedOutput) " )
1469+ return false
1470+ }
1471+
1472+ let verificationCommand =
1473+ " if test -x \( ClaudeQuestionTool . serverPyPath) && [ \" $(cat \( ClaudeQuestionTool . versionPath) 2>/dev/null) \" = ' \( ClaudeQuestionTool . version) ' ]; then printf ' \( ClaudeQuestionTool . version) '; else exit 1; fi "
1474+ let ( verificationOutput, verificationSuccess) = await apiClient. runExec (
1475+ spriteName: spriteName,
1476+ command: verificationCommand,
1477+ timeout: 10
1478+ )
1479+ guard verificationSuccess,
1480+ verificationOutput. trimmingCharacters ( in: . whitespacesAndNewlines) == ClaudeQuestionTool . version
1481+ else {
1482+ logger. error ( " Claude question tool verification failed: \( verificationOutput) " )
1483+ return false
1484+ }
1485+
14751486 return true
14761487 }
14771488}
1478-
0 commit comments