Add personal data tools, 2nd Brain trust toggle, and research agents

Personal Data Tools: native Calendar, Reminders, Contacts (hidden pending
Apple TCC fix in beta), and Location & Maps access via EventKit, Contacts
framework, and MapKit. Write actions (create event/reminder, complete
reminder) gate through an approval sheet. Four hardened-runtime entitlements
added to oAI.entitlements; Info.plist usage strings added for all services.
Personal Data section shows a β badge while Contacts is hidden.

2nd Brain always-trust: inline toggle on the Agent Skills row for the skill
named "2nd Brain" skips the bash approval dialog when the command contains
.brain_helper.py, gated by three runtime checks in MCPService.

Research agents: spawn_research_agents tool runs up to 5 concurrent read-only
sub-agents (read_file, list_directory, search_files, web_search — no write,
no bash, no nesting). Bounded by maxConcurrentAgents setting (default 3) and
a hard ceiling of 8 tasks. Added items field to Tool.Function.Parameters.Property
for JSON Schema array support; wired into AnthropicProvider.convertParametersToDict.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 14:04:47 +02:00
parent 454cef4193
commit 40c5f25517
17 changed files with 11041 additions and 8443 deletions
+14 -4
View File
@@ -799,8 +799,10 @@ Don't narrate future actions ("Let me...") - just use the tools.
let mcpActive = mcpEnabled || settings.mcpEnabled
let anytypeActive = settings.anytypeMcpEnabled && settings.anytypeMcpConfigured
let bashActive = settings.bashEnabled
let personalDataActive = settings.calendarEnabled || settings.remindersEnabled || settings.contactsEnabled || settings.locationMapsEnabled
let researchAgentsActive = settings.agentsEnabled
let modelSupportTools = selectedModel?.capabilities.tools ?? false
if modelSupportTools && (anytypeActive || bashActive || (mcpActive && !mcp.allowedFolders.isEmpty)) {
if modelSupportTools && (anytypeActive || bashActive || personalDataActive || researchAgentsActive || (mcpActive && !mcp.allowedFolders.isEmpty)) {
generateAIResponseWithTools(provider: provider, modelId: modelId)
return
}
@@ -1354,6 +1356,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
var didContinueAfterImages = false // Only inject temp-file continuation once
var totalUsage: ChatResponse.Usage?
var hitIterationLimit = false // Track if we exited due to hitting the limit
var finishedWithEmptyContent = false // Model stopped calling tools but said nothing
for iteration in 0..<maxIterations {
if Task.isCancelled {
@@ -1406,6 +1409,13 @@ Don't narrate future actions ("Let me...") - just use the tools.
continue
}
}
if finalContent.isEmpty {
// Some models (observed with Qwen via OpenRouter) stop calling tools
// after a long tool-call chain but return no summarizing text at all.
// Surface a placeholder and nudge a follow-up turn instead of a silent blank bubble.
finishedWithEmptyContent = true
finalContent = "[No response from the model — retrying]"
}
break
}
@@ -1452,7 +1462,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
break
}
let result = await mcp.executeTool(name: tc.functionName, arguments: tc.arguments)
let result = await mcp.executeTool(name: tc.functionName, arguments: tc.arguments, agentProvider: provider, agentModelId: effectiveModelId)
let resultJSON: String
if let data = try? JSONSerialization.data(withJSONObject: result),
let str = String(data: data, encoding: .utf8) {
@@ -1537,8 +1547,8 @@ Don't narrate future actions ("Let me...") - just use the tools.
isGenerating = false
streamingTask = nil
// If we hit the iteration limit and weren't cancelled, start auto-continue
if hitIterationLimit && !wasCancelled {
// If we hit the iteration limit, or the model returned no text at all, nudge a follow-up turn
if (hitIterationLimit || finishedWithEmptyContent) && !wasCancelled {
startAutoContinue()
}