fix: cmux probe hang + UI polish + quit button

Bug fixes:
- fix(#60): probeAutomationPermission passed requestorAddr instead of
  targetAddr to AEDeterminePermissionToAutomateTarget, causing the
  AE permission check to query the wrong app and hang indefinitely,
  freezing the entire settings panel
- fix(#59): discoverClaudeSessionsFromConfig used runShellWithTimeout
  to spawn /bin/ps for pid liveness checks, which fails under certain
  code-signing configurations. Replaced with kill(pid, 0) signal check
  — faster, no subprocess needed, works in all environments

UI improvements:
- Add "Quit Mio Island" button at bottom of Settings → About tab
- Anthropic API Proxy description: improve readability (medium weight,
  gray-white color, wider line spacing), update CodeIsland → MioIsland
- TextField placeholders: change from default black to light gray
  (white 30% opacity) for both proxy URL and plugin install URL fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
徐翔宇 2026-04-15 14:31:26 +08:00
parent 031a80eaf3
commit b7364ee6cd
4 changed files with 39 additions and 17 deletions

View file

@ -108,16 +108,16 @@ enum L10n {
static var anthropicApiProxyDescription: String {
tr(
"""
Applies to: the rate-limit bar (api.anthropic.com) and every subprocess CodeIsland spawns including the Stats plugin's claude CLI and any future plugin's shell-outs. We set HTTPS_PROXY / HTTP_PROXY / ALL_PROXY on CodeIsland's own process once at startup, so children inherit it automatically. No launchctl pollution, no per-plugin opt-in.
Applies to: the rate-limit bar (api.anthropic.com) and every subprocess MioIsland spawns including the Stats plugin's claude CLI and any future plugin's shell-outs. HTTPS_PROXY / HTTP_PROXY / ALL_PROXY are set once at startup, all children inherit automatically.
Does NOT apply to: CodeLight sync (our own server, stays direct), or third-party plugins that use their own URLSession to reach external APIs those honor system proxy settings instead.
Does NOT apply to: CodeLight sync (always direct) or third-party plugin URLSession calls (those use system proxy).
Leave empty for direct connection.
""",
"""
:(api.anthropic.com) CodeIsland Stats claude CLI shell-out CodeIsland `setenv` HTTPS_PROXY / HTTP_PROXY / ALL_PROXY,,
(api.anthropic.com) MioIsland Stats claude CLI HTTPS_PROXY / HTTP_PROXY / ALL_PROXY
:CodeLight (,); URLSession API ()
CodeLight URLSession
"""
@ -218,6 +218,7 @@ enum L10n {
static var starOnGitHub: String { tr("Star on GitHub", "GitHub 点星") }
static var wechatLabel: String { tr("WeChat", "微信") }
static var maintainedTagline: String { tr("Actively maintained · Your star keeps us going!", "持续更新中 · Star 是我们最大的动力!") }
static var quitApp: String { tr("Quit Mio Island", "退出 Mio Island") }
// MARK: - Plugin marketplace
static var pluginMarketplaceTitle: String { tr("Plugin Marketplace", "插件市场") }

View file

@ -182,9 +182,9 @@ final class TerminalWriter {
}
defer { AEDisposeDesc(&targetAddr) }
// Now the question is correctly: "Can Code Island automate this terminal?"
// Check: "Can this app automate the target terminal?"
let status = AEDeterminePermissionToAutomateTarget(
&requestorAddr,
&targetAddr,
AEEventClass(typeWildCard),
AEEventID(typeWildCard),
false
@ -911,13 +911,9 @@ final class TerminalWriter {
let sessionId = json["sessionId"] as? String,
Self.isUuidLike(sessionId) else { continue }
// Verify the process is still alive
let (out, ok) = await runShellWithTimeout(
"/bin/ps", ["-p", "\(pid)", "-o", "pid="], timeout: 1.0
)
guard ok,
let line = out?.trimmingCharacters(in: .whitespacesAndNewlines),
line == "\(pid)" else { continue }
// Verify the process is still alive using kill(0) signal check.
// Faster and more reliable than spawning /bin/ps subprocess.
guard kill(Int32(pid), 0) == 0 else { continue }
// Prefer cwd from the JSON; fall back to lsof if absent
let cwd: String

View file

@ -160,7 +160,7 @@ struct NativePluginStoreView: View {
.foregroundColor(.white.opacity(0.45))
HStack(spacing: 8) {
TextField("https://api.miomio.chat/api/i/...", text: $installURLText)
TextField("", text: $installURLText, prompt: Text("https://api.miomio.chat/api/i/...").foregroundColor(.white.opacity(0.3)))
.textFieldStyle(.plain)
.font(.system(size: 12, design: .monospaced))
.foregroundColor(.white.opacity(0.9))

View file

@ -456,7 +456,7 @@ private struct AnthropicProxyRow: View {
var body: some View {
VStack(alignment: .leading, spacing: 6) {
TextField(L10n.anthropicApiProxyPlaceholder, text: $proxyURL)
TextField("", text: $proxyURL, prompt: Text(L10n.anthropicApiProxyPlaceholder).foregroundColor(.white.opacity(0.3)))
.textFieldStyle(.plain)
.font(.system(size: 12, design: .monospaced))
.foregroundColor(.white.opacity(0.95))
@ -472,8 +472,9 @@ private struct AnthropicProxyRow: View {
)
Text(L10n.anthropicApiProxyDescription)
.font(.system(size: 10))
.foregroundColor(.white.opacity(0.5))
.font(.system(size: 10, weight: .medium))
.foregroundColor(Color(white: 0.75))
.lineSpacing(4)
.fixedSize(horizontal: false, vertical: true)
}
}
@ -746,6 +747,30 @@ private struct AboutTab: View {
.foregroundColor(Theme.detailText.opacity(0.5))
.frame(maxWidth: .infinity, alignment: .center)
.padding(.top, 4)
Button {
NSApplication.shared.terminate(nil)
} label: {
HStack(spacing: 6) {
Image(systemName: "power")
.font(.system(size: 11))
Text(L10n.quitApp)
.font(.system(size: 12, weight: .medium))
}
.foregroundColor(.red.opacity(0.8))
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.red.opacity(0.08))
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(Color.red.opacity(0.15), lineWidth: 0.5)
)
}
.buttonStyle(.plain)
.padding(.top, 4)
}
}