From fbe7ce44c116def4d5b623c20fe33243e13e641b Mon Sep 17 00:00:00 2001 From: zhengru Date: Thu, 23 Jul 2026 14:31:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20integrate=20pi=20as=20a=20first-class?= =?UTF-8?q?=20agent=20/=20pi=20=E4=BD=9C=E4=B8=BA=E5=86=85=E7=BD=AE=20Agen?= =?UTF-8?q?t=20=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add PiHookInstaller: drops a TS extension into ~/.pi/agent/extensions/ that forwards pi lifecycle events (session_start, agent_start, tool_call, tool_result, agent_end, agent_settled, session_before_compact) to Glint's Unix socket, mapping ask_user_question / exit_plan_mode to needsReply. Auto-discovered by pi on every session — no settings merge needed, and only fires when Glint's pane env vars are present. - Add PaneAgentKind.pi case across the enum, iconKind, restore-command, and agentKind(named:) matchers (exact 'pi' string match to avoid false-positives on pipe/pip/copy). - Wire WorkspaceStore: piHooksInstalled state, install/uninstall methods, auto-install spec entry, restorePiSession toggle (pi --session-id ). - Add Pi SettingsCard in SettingsView with install/uninstall controls. - Localize new UI strings (en + zh-Hans). --- Glint/Agent/AgentHookInstaller.swift | 190 ++++ Glint/Agent/AgentPaneSummary.swift | 1 + Glint/Agent/PaneAgentState.swift | 9 + Glint/Chrome/SettingsView.swift | 50 +- Glint/Resources/Localizable.xcstrings | 1273 +++++++++++++------------ Glint/Workspace/WorkspaceStore.swift | 41 +- 6 files changed, 975 insertions(+), 589 deletions(-) diff --git a/Glint/Agent/AgentHookInstaller.swift b/Glint/Agent/AgentHookInstaller.swift index 8550a10..fbd3087 100644 --- a/Glint/Agent/AgentHookInstaller.swift +++ b/Glint/Agent/AgentHookInstaller.swift @@ -1804,3 +1804,193 @@ enum GrokHookInstaller { } } } + +/// Installs a TypeScript extension that forwards pi (pi-coding-agent) +/// lifecycle events to Glint's local agent socket. +/// +/// Unlike Claude/Codex (which fire shell hooks whose stdin JSON we parse) and +/// like OMP, pi exposes its lifecycle through a TypeScript extension API +/// (`pi.on("event", …)`). So instead of registering the shared +/// `glint-report.sh` reporter, we drop a `.ts` module into pi's auto-discovered +/// extensions directory and let it speak the same socket protocol directly. +/// +/// pi auto-loads `*.ts` from `~/.pi/agent/extensions/` (global scope) on every +/// session — no settings-file merge needed, unlike OMP. The module is inert +/// outside a Glint pane: it bails unless both `GLINT_PANE_ID` and +/// `GLINT_AGENT_SOCK` are present in the environment. +enum PiHookInstaller { + private static let extensionFileName = "glint-agent-bridge.ts" + /// Marker string embedded in the generated extension body — `isInstalled` + /// keys off it so a hand-written file in the same path isn't treated as + /// Glint-managed, and reinstalls can rewrite our own copy safely. + static let marker = "Glint pi extension" + + static func defaultExtensionURL() -> URL { + FileManager.default.homeDirectoryForCurrentUser + .appendingPathComponent(".pi/agent/extensions", isDirectory: true) + .appendingPathComponent(extensionFileName) + } + + static func isInstalled(extensionURL: URL = PiHookInstaller.defaultExtensionURL()) -> Bool { + guard let body = try? String(contentsOf: extensionURL), body.contains(marker) else { + return false + } + return true + } + + /// Whether pi itself looks installed on this Mac. Prefer the config dir + /// and the binary over a bare directory probe: pi always creates + /// `~/.pi/agent/` once it has run, but we don't want to offer hooks for a + /// stale dot-dir left after an uninstall. The `pi` binary on PATH is the + /// strongest signal (it's an npm bin shim → `node dist/cli.js`). + static func isAgentPresent() -> Bool { + AgentPresence.commandExists("pi") + || AgentPresence.directoryExists(".pi/agent") + } + + static func installIfNeeded(socketPath: String, + extensionURL: URL = PiHookInstaller.defaultExtensionURL()) { + do { + let dir = extensionURL.deletingLastPathComponent() + try FileManager.default.createDirectory( + at: dir, + withIntermediateDirectories: true, + attributes: [.posixPermissions: 0o700] + ) + let body = extensionBody + let needsWrite = (try? String(contentsOf: extensionURL)) != body + if needsWrite { + try body.write(to: extensionURL, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes( + [.posixPermissions: 0o600], + ofItemAtPath: extensionURL.path + ) + } + NSLog("[glint] pi extension installed at \(extensionURL.path)") + } catch { + NSLog("[glint] pi extension install failed: \(error)") + } + _ = socketPath + } + + static func uninstall(extensionURL: URL = PiHookInstaller.defaultExtensionURL()) { + if let body = try? String(contentsOf: extensionURL), body.contains(marker) { + try? FileManager.default.removeItem(at: extensionURL) + NSLog("[glint] pi extension removed from \(extensionURL.path)") + } + } + + /// TypeScript extension loaded by pi's extension runner. Only arms when + /// the pane env vars are present, so a pi session outside Glint is a + /// no-op. Session id is pulled from `ctx.sessionManager.getSessionId()` + /// and forwarded as `session_b64` for restore-on-launch. + /// + /// pi emits the same event names the official docs document + /// (`session_start`, `before_agent_start`, `tool_call`, `tool_result`, + /// `agent_end`, `session_before_compact`). The ask state is handled in a + /// single `tool_call` branch: `ask_user_question` (pi-subagents' blocking + /// question tool) and `exit_plan_mode` (plan approval) both block the + /// turn waiting for the user, so they're remapped to `NeedsReply` instead + /// of `PreToolUse` — mirroring how Grok's reporter handles the same pair. + static let extensionBody: String = """ + // \(marker). Auto-generated by Glint; remove from Settings → Agents. + // @ts-nocheck + import { createConnection } from "node:net" + import { existsSync } from "node:fs" + + const AGENT = "pi" + const SESSION_ID_RE = /^\(PaneAgentKind.sessionIdCharsetClass){1,\(PaneAgentKind.sessionIdMaxLength)}$/ + + function pickSessionId(ctx) { + try { + const id = ctx?.sessionManager?.getSessionId?.() + if (typeof id === "string" && SESSION_ID_RE.test(id)) return id + } catch {} + return null + } + + function send(hook, sessionId) { + const pane = process.env.GLINT_PANE_ID + const sock = process.env.GLINT_AGENT_SOCK + if (!pane || !sock || !existsSync(sock)) return Promise.resolve() + + const payload = { pane, hook, agent: AGENT } + if (sessionId) { + payload.session_b64 = Buffer.from(sessionId, "utf8").toString("base64") + } + const line = JSON.stringify(payload) + "\\n" + + // Use end(line) so the write is flushed before the socket closes — + // write()+destroy() races the kernel and can drop the report. + return new Promise((resolve) => { + let done = false + const finish = () => { + if (done) return + done = true + resolve() + } + try { + const client = createConnection(sock, () => client.end(line)) + client.on("error", finish) + client.on("close", finish) + const timer = setTimeout(() => { + try { client.destroy() } catch {} + finish() + }, 1000) + timer.unref?.() + } catch { + finish() + } + }) + } + + // Tools that block the turn waiting for the user — same semantics as + // OMP's `ask` tool and Grok's ask_user_question/exit_plan_mode. Sourced + // here so the single tool_call branch below stays readable. + const ASK_TOOLS = new Set(["ask_user_question", "exit_plan_mode"]) + + export default function (pi) { + const pane = process.env.GLINT_PANE_ID + const sock = process.env.GLINT_AGENT_SOCK + if (!pane || !sock) return + + pi.on("session_start", (_event, ctx) => { + void send("SessionStart", pickSessionId(ctx)) + }) + + // before_agent_start fires after the user submits a prompt, before the + // agent loop begins — the closest analogue to Claude's UserPromptSubmit. + pi.on("before_agent_start", (_event, ctx) => { + void send("UserPromptSubmit", pickSessionId(ctx)) + }) + + pi.on("tool_call", (event, ctx) => { + // A single branch for the ask state: tools that block on the user + // surface as NeedsReply ("awaiting reply"), every other tool fires + // PreToolUse. The answer's own tool_result → PostToolUse flips the + // pane back to busy. + if (ASK_TOOLS.has(event?.toolName)) { + void send("NeedsReply", pickSessionId(ctx)) + } else { + void send("PreToolUse", pickSessionId(ctx)) + } + }) + + pi.on("tool_result", (_event, ctx) => { + void send("PostToolUse", pickSessionId(ctx)) + }) + + pi.on("session_before_compact", (_event, ctx) => { + void send("PreCompact", pickSessionId(ctx)) + }) + + // agent_end can fire multiple times per session (retries, follow-ups). + // agent_settled is the true "pi will not continue automatically" + // signal — use it for the Stop badge so the pane doesn't flash green + // then go busy again on an auto-retry. + pi.on("agent_settled", (_event, ctx) => { + void send("Stop", pickSessionId(ctx)) + }) + } + """ +} diff --git a/Glint/Agent/AgentPaneSummary.swift b/Glint/Agent/AgentPaneSummary.swift index 1d4365e..7324731 100644 --- a/Glint/Agent/AgentPaneSummary.swift +++ b/Glint/Agent/AgentPaneSummary.swift @@ -16,6 +16,7 @@ extension PaneAgentKind { case .devin: return .devin case .omp: return .omp case .grok: return .grok + case .pi: return .pi } } } diff --git a/Glint/Agent/PaneAgentState.swift b/Glint/Agent/PaneAgentState.swift index 9e8ffe6..94d8ca2 100644 --- a/Glint/Agent/PaneAgentState.swift +++ b/Glint/Agent/PaneAgentState.swift @@ -8,6 +8,7 @@ enum PaneAgentKind: String, Codable { case devin case omp case grok + case pi /// Human-facing label for the per-pane summary popover. var displayName: String { @@ -18,6 +19,7 @@ enum PaneAgentKind: String, Codable { case .devin: return "Devin" case .omp: return "OMP" case .grok: return "Grok" + case .pi: return "Pi" } } @@ -82,6 +84,13 @@ enum PaneAgentKind: String, Codable { return validated.map { "omp -r \($0)\n" } ?? "omp -c\n" case .grok: return validated.map { "grok --resume \($0)\n" } ?? "grok --continue\n" + case .pi: + // `pi --session-id ` uses an exact project session id, + // creating it if missing — so a restored pane lands back in its + // own session instead of being prompted to pick one (as the + // interactive `--resume` would). Falls back to `pi --continue` + // (resume the most-recent) when no id was captured. + return validated.map { "pi --session-id \($0)\n" } ?? "pi --continue\n" } } } diff --git a/Glint/Chrome/SettingsView.swift b/Glint/Chrome/SettingsView.swift index 604b762..327c052 100644 --- a/Glint/Chrome/SettingsView.swift +++ b/Glint/Chrome/SettingsView.swift @@ -167,7 +167,7 @@ enum SettingsCategory: String, CaseIterable, Identifiable { case .general: return "Startup, layout, updates" case .appearance: return "Theme, accent, glass" case .terminal: return "Font, cursor, scrollback" - case .agents: return "Claude Code, Codex, OMP, Grok, hook routing" + case .agents: return "Claude Code, Codex, OMP, Grok, Pi, hook routing" case .shortcuts: return "Keyboard reference" case .about: return nil } @@ -1169,6 +1169,7 @@ private struct AgentsPane: View { @State private var devinInstallFailed = false @State private var ompInstallFailed = false @State private var grokInstallFailed = false + @State private var piInstallFailed = false @State private var newCodexHomePath = "" @State private var newCodexHomeLabel = "" @State private var codexHomeErrors: [UUID: String] = [:] @@ -1486,6 +1487,53 @@ private struct AgentsPane: View { } } + SettingsCard("Pi", + footer: "Glint installs a TypeScript extension into ~/.pi/agent/extensions/ so pi sessions report thinking, tools, and ask_user_question (awaiting reply). Auto-discovered by pi on every session — no settings merge needed. Only fires when Glint's pane environment variables are present, so a pi session outside Glint is a no-op.") { + SettingsRow("Status", subtitle: piInstallFailed + ? "Install failed — check Console for [glint] logs." + : (store.piHooksInstalled + ? "Extension installed into your pi extensions directory." + : (store.piDetected + ? "Pi detected — install the extension to show its status." + : "Pi not detected on this Mac."))) { + HStack(spacing: 8) { + StatusPill( + label: store.piHooksInstalled ? "Installed" : (store.piDetected ? "Not installed" : "Not detected"), + tone: store.piHooksInstalled ? .ok : .neutral + ) + if store.piHooksInstalled { + Button("Uninstall") { + store.uninstallPiHooks() + piInstallFailed = false + } + .controlSize(.small) + } else { + Button("Install") { + store.installPiHooks() + piInstallFailed = !store.piHooksInstalled + } + .controlSize(.small) + .tint(store.accent) + } + } + } + SettingsDivider() + SettingsRow("Resume session on launch", + subtitle: "When Glint reopens, each pane that was running pi at last quit is resumed via `pi --session-id ` — so multiple pi panes in one workspace land back in their own sessions. Falls back to `pi --continue` for panes whose session id wasn't captured.") { + Toggle("", isOn: $store.restorePiSession) + .toggleStyle(.switch).labelsHidden() + } + SettingsDivider() + SettingsRow("Hook config", + subtitle: "Auto-discovered TypeScript extension under pi's global extensions directory; only reports when Glint's pane environment variables are present.") { + Text("~/.pi/agent/extensions/glint-agent-bridge.ts") + .font(.system(size: 11, design: .monospaced)) + .foregroundStyle(Theme.text3) + .lineLimit(1) + .truncationMode(.head) + } + } + SettingsCard("Notifications", footer: "Dock badges and chimes only update for background workspaces — the one you're looking at stays quiet.") { SettingsRow("Show Dock badge for agent attention", diff --git a/Glint/Resources/Localizable.xcstrings b/Glint/Resources/Localizable.xcstrings index edb5a1a..9703b12 100644 --- a/Glint/Resources/Localizable.xcstrings +++ b/Glint/Resources/Localizable.xcstrings @@ -81,17 +81,6 @@ } } }, - "1 pane still has something running; quitting will terminate it.": { - "extractionState": "stale", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "还有 1 个面板有任务在运行,退出会将其终止。" - } - } - } - }, "1 hour": { "extractionState": "manual", "localizations": { @@ -114,13 +103,13 @@ } } }, - "5 minutes": { - "extractionState": "manual", + "1 pane still has something running; quitting will terminate it.": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "5 分钟" + "value": "还有 1 个面板有任务在运行,退出会将其终止。" } } } @@ -158,122 +147,45 @@ } } }, - "Free idle terminals": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "释放空闲终端" - } - } - } - }, - "Idle terminal released": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "空闲终端已释放" - } - } - } - }, - "Memory": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "内存" - } - } - } - }, - "Off — terminal sessions stay live.": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已关闭——终端会话会保持运行。" - } - } - } - }, - "Only inactive shell prompts are eligible.": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "仅未聚焦的 shell 提示符会被释放。" - } - } - } - }, - "Release after": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "多久后释放" - } - } - } - }, - "Releases inactive shell sessions and recreates them in the same folder when you return. Running commands, SSH sessions, agents, tmux, and the focused terminal are never touched.": { + "5 minutes": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "释放闲置的 shell 会话,并在你返回时于同一目录重新创建。正在运行的命令、SSH 会话、Agent、tmux 和当前聚焦的终端都不会受到影响。" + "value": "5 分钟" } } } }, - "Reopen Terminal": { - "extractionState": "manual", + "A native mac terminal made for AI agents.": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "重新打开终端" + "value": "为 AI 代理打造的原生 Mac 终端。" } } } }, - "Reopens in the same folder.": { - "extractionState": "manual", + "ACTION": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "将在同一目录重新打开。" + "value": "动作" } } } }, - "Time without focus.": { + "AHEAD / BEHIND": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "未聚焦的持续时间。" - } - } - } - }, - "A native mac terminal made for AI agents.": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "为 AI 代理打造的原生 Mac 终端。" + "value": "领先 / 落后" } } } @@ -300,17 +212,6 @@ } } }, - "ACTION": { - "extractionState": "stale", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "动作" - } - } - } - }, "Add": { "extractionState": "manual", "localizations": { @@ -355,6 +256,17 @@ } } }, + "Agent is waiting for your reply": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Agent 等待你的回复" + } + } + } + }, "Agent's turn ended in an error": { "extractionState": "manual", "localizations": { @@ -377,35 +289,46 @@ } } }, - "AHEAD / BEHIND": { + "All installed fonts": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "领先 / 落后" + "value": "所有系统字体" } } } }, - "All installed fonts": { + "All installed monospaced": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "所有系统字体" + "value": "所有等宽字体" } } } }, - "All installed monospaced": { + "Allow": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "所有等宽字体" + "value": "允许" + } + } + } + }, + "Allow Glint to execute \"%@\"?": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "允许 Glint 执行 \"%@\"?" } } } @@ -531,6 +454,17 @@ } } }, + "Auto-discovered TypeScript extension under pi's global extensions directory; only reports when Glint's pane environment variables are present.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "pi 全局扩展目录下自动发现的 TypeScript 扩展;仅当 Glint 的窗格环境变量存在时才上报。" + } + } + } + }, "Auto-name new workspaces": { "extractionState": "stale", "localizations": { @@ -564,6 +498,17 @@ } } }, + "BRANCH": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "分支" + } + } + } + }, "Background blur": { "extractionState": "manual", "localizations": { @@ -652,17 +597,6 @@ } } }, - "BRANCH": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "分支" - } - } - } - }, "Branch vs %@": { "extractionState": "manual", "localizations": { @@ -751,35 +685,35 @@ } } }, - "Allow": { - "extractionState": "manual", + "Bundle ID": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "允许" + "value": "Bundle ID" } } } }, - "Allow Glint to execute \"%@\"?": { + "CHANGES": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "允许 Glint 执行 \"%@\"?" + "value": "变更" } } } }, - "Bundle ID": { - "extractionState": "stale", + "CJK fallback": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Bundle ID" + "value": "中文字体兜底" } } } @@ -817,17 +751,6 @@ } } }, - "CHANGES": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "变更" - } - } - } - }, "Changes Only": { "extractionState": "manual", "localizations": { @@ -905,17 +828,6 @@ } } }, - "CJK fallback": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "中文字体兜底" - } - } - } - }, "Claude Code": { "extractionState": "stale", "localizations": { @@ -947,68 +859,90 @@ } } }, - "Claude Code, Codex, hook routing": { - "extractionState": "stale", + "Claude Code, Codex, OMP, Grok, Pi, hook routing": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Claude Code、Codex、Hook 路由" + "value": "Claude Code、Codex、OMP、Grok、Pi、hook 路由" } } } }, - "clean": { + "Claude Code, Codex, OMP, Grok, hook routing": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "干净" + "value": "Claude Code、Codex、OMP、Grok、hook 路由" } } } }, - "Clear": { - "extractionState": "stale", + "Claude Code, Codex, OMP, hook routing": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "清屏" + "value": "Claude Code、Codex、OMP、hook 路由" } } - } + }, + "extractionState": "manual" }, - "Clear filter": { - "extractionState": "manual", + "Claude Code, Codex, hook routing": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "清除筛选" + "value": "Claude Code、Codex、Hook 路由" } } } }, - "Close (Esc)": { + "Clear": { "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "关闭 (Esc)" + "value": "清屏" } } } }, - "Close and Remove Worktree…": { + "Clear filter": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "关闭并移除工作树…" + "value": "清除筛选" + } + } + } + }, + "Close (Esc)": { + "extractionState": "stale", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭 (Esc)" + } + } + } + }, + "Close Other Tabs": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭其他标签页" } } } @@ -1035,90 +969,90 @@ } } }, - "Close Other Tabs": { + "Close Tab": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "关闭其他标签页" + "value": "关闭标签页" } } } }, - "Close other tabs?": { + "Close Tabs": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "关闭其他标签页?" + "value": "关闭标签页" } } } }, - "Close Tab": { + "Close Workspace": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "关闭标签页" + "value": "关闭工作区" } } } }, - "Close Tabs": { + "Close and Remove Worktree…": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "关闭标签页" + "value": "关闭并移除工作树…" } } } }, - "Close the focused pane": { - "extractionState": "stale", + "Close other tabs?": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "关闭当前面板" + "value": "关闭其他标签页?" } } } }, - "Close this pane?": { + "Close the focused pane": { "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "关闭这个面板?" + "value": "关闭当前面板" } } } }, - "Close this tab?": { - "extractionState": "manual", + "Close this pane?": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "关闭这个标签页?" + "value": "关闭这个面板?" } } } }, - "Close Workspace": { + "Close this tab?": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "关闭工作区" + "value": "关闭这个标签页?" } } } @@ -1134,34 +1068,34 @@ } } }, - "Codex detected — install the reporter to show its status.": { + "Codex Home Directories": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "已检测到 Codex —— 安装上报脚本即可显示其状态。" + "value": "Codex Home 目录" } } } }, - "Codex Home Directories": { + "Codex Home stores local auth, config, sessions, and hooks. Glint only installs its own hooks and reads status; Codex continues to manage authentication and configuration. The checkbox only controls monitoring and sidebar display — unchecking a Home keeps its installed hooks; use Remove Hook to uninstall.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Codex Home 目录" + "value": "Codex Home 存放本地认证、配置、会话和钩子。Glint 只安装自己的钩子并读取状态;认证与配置仍由 Codex 管理。复选框只控制监控和侧栏显示——取消勾选会保留已安装的钩子,如需卸载请用 Remove Hook。" } } } }, - "Codex Home stores local auth, config, sessions, and hooks. Glint only installs its own hooks and reads status; Codex continues to manage authentication and configuration. The checkbox only controls monitoring and sidebar display — unchecking a Home keeps its installed hooks; use Remove Hook to uninstall.": { - "extractionState": "manual", + "Codex detected — install the reporter to show its status.": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Codex Home 存放本地认证、配置、会话和钩子。Glint 只安装自己的钩子并读取状态;认证与配置仍由 Codex 管理。复选框只控制监控和侧栏显示——取消勾选会保留已安装的钩子,如需卸载请用 Remove Hook。" + "value": "已检测到 Codex —— 安装上报脚本即可显示其状态。" } } } @@ -1264,17 +1198,6 @@ } } }, - "compacting…": { - "extractionState": "stale", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "压缩中…" - } - } - } - }, "Copied in; your original checkout keeps them too.": { "extractionState": "manual", "localizations": { @@ -1385,35 +1308,35 @@ } } }, - "Create a fresh workspace": { - "extractionState": "stale", + "Create Workspace": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "创建一个新工作区" + "value": "创建工作区" } } } }, - "Create Workspace": { + "Create Worktree": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "创建工作区" + "value": "创建工作树" } } } }, - "Create Worktree": { - "extractionState": "manual", + "Create a fresh workspace": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "创建工作树" + "value": "创建一个新工作区" } } } @@ -1506,35 +1429,35 @@ } } }, - "Default": { + "Dedicated Glint hook file under Grok's global hooks directory; only reports when Glint's pane environment variables are present.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "默认" + "value": "Grok 全局 hooks 目录下的独立 Glint hook 文件;仅在存在 Glint 窗格环境变量时上报。" } } } }, - "Default: ~/glint/worktrees// — discoverable in Finder.": { + "Default": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "默认:~/glint/worktrees//——可在访达中找到。" + "value": "默认" } } } }, - "Delete the worktree directory (confirm required)": { + "Default: ~/glint/worktrees// — discoverable in Finder.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "删除工作树目录(需确认)" + "value": "默认:~/glint/worktrees//——可在访达中找到。" } } } @@ -1561,35 +1484,46 @@ } } }, - "Delete worktree for %@?": { + "Delete Worktree, Keep Branch": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "删除 %@ 的工作树?" + "value": "删除工作树,保留分支" } } } }, - "Delete Worktree, Keep Branch": { + "Delete Worktree…": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "删除工作树,保留分支" + "value": "删除工作树…" } } } }, - "Delete Worktree…": { + "Delete the worktree directory (confirm required)": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "删除工作树…" + "value": "删除工作树目录(需确认)" + } + } + } + }, + "Delete worktree for %@?": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "删除 %@ 的工作树?" } } } @@ -1691,35 +1625,35 @@ } } }, - "Display available enabled Codex Home usage in the sidebar.": { + "Display Claude's 5-hour and weekly limits in the sidebar. Requires reading the login keychain (macOS asks once).": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在侧栏显示已启用 Codex Home 的可用额度。" + "value": "在侧边栏显示 Claude 的 5 小时和每周额度。需要读取登录钥匙串(macOS 会询问一次)。" } } } }, - "Display Claude's 5-hour and weekly limits in the sidebar. Requires reading the login keychain (macOS asks once).": { + "Display Codex's 5-hour and weekly limits in the sidebar.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在侧边栏显示 Claude 的 5 小时和每周额度。需要读取登录钥匙串(macOS 会询问一次)。" + "value": "在侧边栏显示 Codex 的 5 小时和每周额度。" } } } }, - "Display Codex's 5-hour and weekly limits in the sidebar.": { + "Display available enabled Codex Home usage in the sidebar.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在侧边栏显示 Codex 的 5 小时和每周额度。" + "value": "在侧栏显示已启用 Codex Home 的可用额度。" } } } @@ -1757,13 +1691,12 @@ } } }, - "done": { - "extractionState": "stale", + "ESC": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "完成" + "value": "ESC" } } } @@ -1801,45 +1734,57 @@ } } }, - "error": { + "Events": { "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "出错" + "value": "事件" } } } }, - "ESC": { + "Exposes a local socket under ~/.glint/run/ so other apps on this Mac can focus panes and inject text/keys. Any process that can read the 0600 token file may drive your terminals — off by default. Toggling takes effect immediately.": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "ESC" + "value": "在 ~/.glint/run/ 下开放一个本地 socket,让本机其它程序可以聚焦 pane、注入文本/按键。任何能读取 0600 token 文件的进程都能操控你的终端 —— 默认关闭。开关即时生效。" } } } }, - "Events": { - "extractionState": "stale", + "Extension file": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "事件" + "value": "扩展文件" } } - } + }, + "extractionState": "manual" }, - "Exposes a local socket under ~/.glint/run/ so other apps on this Mac can focus panes and inject text/keys. Any process that can read the 0600 token file may drive your terminals — off by default. Toggling takes effect immediately.": { + "Extension installed and registered with OMP.": { + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "扩展已安装并注册到 OMP。" + } + } + }, + "extractionState": "manual" + }, + "Extension installed into your pi extensions directory.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在 ~/.glint/run/ 下开放一个本地 socket,让本机其它程序可以聚焦 pane、注入文本/按键。任何能读取 0600 token 文件的进程都能操控你的终端 —— 默认关闭。开关即时生效。" + "value": "扩展已安装到你的 pi 扩展目录。" } } } @@ -2009,6 +1954,17 @@ } } }, + "Free idle terminals": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "释放空闲终端" + } + } + } + }, "General": { "extractionState": "stale", "localizations": { @@ -2117,6 +2073,17 @@ } } }, + "Glint installs a TypeScript extension into ~/.pi/agent/extensions/ so pi sessions report thinking, tools, and ask_user_question (awaiting reply). Auto-discovered by pi on every session — no settings merge needed. Only fires when Glint's pane environment variables are present, so a pi session outside Glint is a no-op.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Glint 将一个 TypeScript 扩展安装到 ~/.pi/agent/extensions/,使 pi 会话上报思考、工具调用及 ask_user_question(等待回复)状态。pi 会在每次会话时自动发现该扩展 —— 无需合并设置。仅在 Glint 窗格环境变量存在时触发,因此 Glint 之外的 pi 会话不受影响。" + } + } + } + }, "Glint installs a global OpenCode plugin at ~/.config/opencode/plugins/glint-agent-bridge.js so OpenCode sessions can report status without being shown as Claude.": { "localizations": { "zh-Hans": { @@ -2127,6 +2094,17 @@ } } }, + "Glint installs a portable TypeScript extension at ~/.glint/hooks/omp-agent-bridge.ts and registers that path in ~/.omp/agent/settings.json so Oh My Pi sessions report status like Claude. Uses a tilde path (not an absolute home path) so the same install works on any Mac.": { + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Glint 会在 ~/.glint/hooks/omp-agent-bridge.ts 安装可移植的 TypeScript 扩展,并在 ~/.omp/agent/settings.json 中注册该路径,使 Oh My Pi 会话像 Claude 一样上报状态。使用波浪线路径(而非绝对 home 路径),同一套安装在任意 Mac 上都能生效。" + } + } + }, + "extractionState": "manual" + }, "Glint is dark-mode only for now.": { "extractionState": "stale", "localizations": { @@ -2192,6 +2170,17 @@ } } }, + "Glint writes ~/.grok/hooks/glint.json so Grok Build sessions report thinking, tools, and ask_user_question (awaiting reply). Dedicated file — not Claude's settings — so Grok is attributed as Grok. The reporter ignores Claude-compat dual-fires under Grok (GROK_SESSION_ID present).": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Glint 会写入 ~/.grok/hooks/glint.json,使 Grok Build 会话上报思考、工具调用,以及 ask_user_question(待回复)。使用独立文件(而非 Claude 的 settings),因此 Grok 会话会正确归到 Grok。在 Grok 下 reporter 会忽略 Claude 兼容 hooks 的双触发(存在 GROK_SESSION_ID 时)。" + } + } + } + }, "Got it": { "extractionState": "manual", "localizations": { @@ -2203,6 +2192,39 @@ } } }, + "Grok": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Grok" + } + } + } + }, + "Grok detected — install the reporter to show its status.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "已检测到 Grok — 安装 reporter 即可显示其状态。" + } + } + } + }, + "Grok not detected on this Mac.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "本机未检测到 Grok。" + } + } + } + }, "Hide Glint": { "extractionState": "stale", "localizations": { @@ -2291,6 +2313,17 @@ } } }, + "Hooks Glint reacts to from Claude Code.": { + "extractionState": "stale", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Glint 监听的 Claude Code Hook。" + } + } + } + }, "Hooks are stored in Devin's native user config alongside your existing settings.": { "localizations": { "zh-Hans": { @@ -2301,13 +2334,13 @@ } } }, - "Hooks Glint reacts to from Claude Code.": { - "extractionState": "stale", + "Hooks installed into your Grok hooks directory.": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Glint 监听的 Claude Code Hook。" + "value": "Hook 已安装到你的 Grok hooks 目录。" } } } @@ -2366,6 +2399,17 @@ } } }, + "Idle terminal released": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "空闲终端已释放" + } + } + } + }, "Ignore Whitespace": { "extractionState": "manual", "localizations": { @@ -2399,34 +2443,34 @@ } } }, - "Install failed — check Console for [glint] logs.": { + "Install Hook": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "安装失败 —— 请在「控制台」中查看 [glint] 日志。" + "value": "安装钩子" } } } }, - "Install Hook": { + "Install Hooks": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "安装钩子" + "value": "安装 Hook" } } } }, - "Install Hooks": { - "extractionState": "manual", + "Install failed — check Console for [glint] logs.": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "安装 Hook" + "value": "安装失败 —— 请在「控制台」中查看 [glint] 日志。" } } } @@ -2540,35 +2584,35 @@ } } }, - "Label (optional)": { + "LAST COMMIT": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "标签(可选)" + "value": "最近提交" } } } }, - "Language": { - "extractionState": "stale", + "Label (optional)": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "语言" + "value": "标签(可选)" } } } }, - "LAST COMMIT": { - "extractionState": "manual", + "Language": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "最近提交" + "value": "语言" } } } @@ -2693,6 +2737,17 @@ } } }, + "Memory": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "内存" + } + } + } + }, "Memory budget per pane.": { "extractionState": "manual", "localizations": { @@ -2781,28 +2836,6 @@ } } }, - "needs approval": { - "extractionState": "stale", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "等待批准" - } - } - } - }, - "New branch": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "新分支" - } - } - } - }, "New Local Worktree": { "extractionState": "manual", "localizations": { @@ -2847,78 +2880,78 @@ } } }, - "New terminals": { - "extractionState": "manual", + "New Workspace": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "新建终端" + "value": "新建工作区" } } } }, - "New Workspace": { + "New Workspace · %@": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "新建工作区" + "value": "新建工作区 · %@" } } } }, - "New Workspace · %@": { + "New Worktree Workspace": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "新建工作区 · %@" + "value": "新建工作树工作区" } } } }, - "New workspace": { + "New Worktree from Here…": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "新建工作区" + "value": "从此处新建工作树…" } } } }, - "New Worktree from Here…": { + "New branch": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "从此处新建工作树…" + "value": "新分支" } } } }, - "New Worktree Workspace": { + "New terminals": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "新建工作树工作区" + "value": "新建终端" } } } }, - "Next change": { + "New workspace": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "下一个改动" + "value": "新建工作区" } } } @@ -2934,79 +2967,79 @@ } } }, - "No changes": { + "Next change": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "无改动" + "value": "下一个改动" } } } }, - "No changes after filter": { + "No Matches": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "应用过滤后无改动" + "value": "无匹配" } } } }, - "No changes to review": { + "No changes": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "没有可审阅的改动" + "value": "无改动" } } } }, - "no cwd": { - "extractionState": "stale", + "No changes after filter": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "无目录" + "value": "应用过滤后无改动" } } } }, - "No diff for this file": { + "No changes to review": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "该文件无差异" + "value": "没有可审阅的改动" } } } }, - "No matches": { + "No diff for this file": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "没有匹配的字体" + "value": "该文件无差异" } } } }, - "No Matches": { + "No matches": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "无匹配" + "value": "没有匹配的字体" } } } @@ -3033,13 +3066,13 @@ } } }, - "not a git repo": { + "Not Now": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "不是 git 仓库" + "value": "暂不" } } } @@ -3075,17 +3108,6 @@ } } }, - "Not Now": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "暂不" - } - } - } - }, "Not wired": { "extractionState": "stale", "localizations": { @@ -3108,220 +3130,253 @@ } } }, - "Off — no socket bound, no external access.": { + "OK": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "已关闭 —— 未绑定 socket,外部无法访问。" + "value": "好" } } } }, - "OK": { - "extractionState": "manual", + "OMP": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "好" + "value": "OMP" } } - } + }, + "extractionState": "manual" }, - "Only fires while Glint is in the background. Pops a banner in Notification Center when an agent needs approval, finishes, or fails. Silent — the chime stays the audio cue.": { - "extractionState": "manual", + "OMP detected — install the extension to show its status.": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "仅在 Glint 处于后台时触发。agent 需要批准、完成或出错时在通知中心弹出横幅。静默——声音仍由提示音负责。" + "value": "已检测到 OMP — 安装扩展以显示其状态。" } } - } + }, + "extractionState": "manual" }, - "Opacity & blur": { - "extractionState": "manual", + "OMP not detected on this Mac.": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "透明度与模糊" + "value": "此 Mac 上未检测到 OMP。" } } - } + }, + "extractionState": "manual" }, - "Open": { + "Off — no socket bound, no external access.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "打开" + "value": "已关闭 —— 未绑定 socket,外部无法访问。" } } } }, - "Open a checkout": { + "Off — terminal sessions stay live.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "打开一个检出" + "value": "已关闭——终端会话会保持运行。" } } } }, - "Open a new pane on the right": { - "extractionState": "stale", + "Only fires while Glint is in the background. Pops a banner in Notification Center when an agent needs approval, finishes, or fails. Silent — the chime stays the audio cue.": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在右侧打开一个新面板" + "value": "仅在 Glint 处于后台时触发。agent 需要批准、完成或出错时在通知中心弹出横幅。静默——声音仍由提示音负责。" } } } }, - "Open a pane running %@": { + "Only inactive shell prompts are eligible.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "打开一个运行 %@ 的窗格" + "value": "仅未聚焦的 shell 提示符会被释放。" } } } }, - "Open a tab running %@": { + "Opacity & blur": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "打开一个运行 %@ 的标签页" + "value": "透明度与模糊" } } } }, - "Open a workspace on an existing checkout (not isolated).": { + "Open": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在已有检出上打开工作区(不隔离)。" + "value": "打开" } } } }, - "Open a workspace running %@": { + "Open Glint settings": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "打开一个运行 %@ 的工作区" + "value": "打开 Glint 设置" } } } }, - "Open Glint settings": { + "Open Workspace": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "打开 Glint 设置" + "value": "打开工作区" } } } }, - "Open the focused pane's directory in Finder": { + "Open a checkout": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在访达中打开当前面板所在目录" + "value": "打开一个检出" } } } }, - "Open with": { - "extractionState": "manual", + "Open a new pane on the right": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "打开方式" + "value": "在右侧打开一个新面板" } } } }, - "Open Workspace": { + "Open a pane running %@": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "打开工作区" + "value": "打开一个运行 %@ 的窗格" } } } }, - "OpenCode detected — install the plugin to show its status.": { + "Open a tab running %@": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "已检测到 OpenCode —— 安装插件即可显示其状态。" + "value": "打开一个运行 %@ 的标签页" } } } }, - "OpenCode not detected on this Mac.": { + "Open a workspace on an existing checkout (not isolated).": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "本机未检测到 OpenCode。" + "value": "在已有检出上打开工作区(不隔离)。" } } } }, - "Opens directly on the current checkout. For isolation across parallel agents, choose Local Worktree.": { + "Open a workspace running %@": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "直接在当前检出上打开。若要在并行 agent 间隔离,请选择本地工作树。" + "value": "打开一个运行 %@ 的工作区" } } } }, - "pane": { - "extractionState": "stale", + "Open the focused pane's directory in Finder": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "面板" + "value": "在访达中打开当前面板所在目录" } } } }, - "panes": { - "extractionState": "stale", + "Open with": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "面板" + "value": "打开方式" + } + } + } + }, + "OpenCode detected — install the plugin to show its status.": { + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "已检测到 OpenCode —— 安装插件即可显示其状态。" + } + } + } + }, + "OpenCode not detected on this Mac.": { + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "本机未检测到 OpenCode。" + } + } + } + }, + "Opens directly on the current checkout. For isolation across parallel agents, choose Local Worktree.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "直接在当前检出上打开。若要在并行 agent 间隔离,请选择本地工作树。" } } } @@ -3392,6 +3447,39 @@ } } }, + "Pi": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Pi" + } + } + } + }, + "Pi detected — install the extension to show its status.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "已检测到 Pi — 安装扩展即可显示其状态。" + } + } + } + }, + "Pi not detected on this Mac.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "本机未检测到 Pi。" + } + } + } + }, "Pick a git repository first.": { "extractionState": "manual", "localizations": { @@ -3500,24 +3588,24 @@ } } }, - "Previous change": { - "extractionState": "manual", + "Previous Workspace": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "上一个改动" + "value": "上一工作区" } } } }, - "Previous Workspace": { - "extractionState": "stale", + "Previous change": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "上一工作区" + "value": "上一个改动" } } } @@ -3632,6 +3720,39 @@ } } }, + "Registered via ~/.omp/agent/settings.json; only reports when Glint's pane environment variables are present.": { + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "通过 ~/.omp/agent/settings.json 注册;仅在存在 Glint 的 pane 环境变量时上报。" + } + } + }, + "extractionState": "manual" + }, + "Release after": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "多久后释放" + } + } + } + }, + "Releases inactive shell sessions and recreates them in the same folder when you return. Running commands, SSH sessions, agents, tmux, and the focused terminal are never touched.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "释放闲置的 shell 会话,并在你返回时于同一目录重新创建。正在运行的命令、SSH 会话、Agent、tmux 和当前聚焦的终端都不会受到影响。" + } + } + } + }, "Remove Hook": { "extractionState": "manual", "localizations": { @@ -3698,13 +3819,24 @@ } } }, - "repo detected": { + "Reopen Terminal": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "检测到仓库" + "value": "重新打开终端" + } + } + } + }, + "Reopens in the same folder.": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将在同一目录重新打开。" } } } @@ -3786,35 +3918,35 @@ } } }, - "Reveal in Finder": { + "Reveal Worktree in Finder": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在访达中显示" + "value": "在访达中显示工作树" } } } }, - "Reveal in Finder at repository root": { + "Reveal in Finder": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在访达中显示仓库根目录" + "value": "在访达中显示" } } } }, - "Reveal Worktree in Finder": { + "Reveal in Finder at repository root": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在访达中显示工作树" + "value": "在访达中显示仓库根目录" } } } @@ -3874,13 +4006,13 @@ } } }, - "running…": { + "SSH Project": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "运行中…" + "value": "SSH 项目" } } } @@ -4048,112 +4180,112 @@ } } }, - "Show agent status in the sidebar?": { + "Show All": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在侧栏显示 agent 状态?" + "value": "显示全部" } } } }, - "Show All": { + "Show Dock badge for agent attention": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "显示全部" + "value": "Agent 需要关注时显示 Dock 角标" } } } }, - "Show Dock badge for agent attention": { - "extractionState": "manual", + "Show Sidebar": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Agent 需要关注时显示 Dock 角标" + "value": "显示侧栏" } } } }, - "Show macOS notification for agent attention": { + "Show Whitespace": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "后台 agent 需要关注时弹出 macOS 通知" + "value": "显示空白" } } } }, - "Show or hide the workspace sidebar": { - "extractionState": "stale", + "Show agent status in the sidebar?": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "显示或隐藏工作区侧栏" + "value": "在侧栏显示 agent 状态?" } } } }, - "Show Sidebar": { - "extractionState": "stale", + "Show macOS notification for agent attention": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "显示侧栏" + "value": "后台 agent 需要关注时弹出 macOS 通知" } } } }, - "Show the compacting state on workspace cards when context overflows.": { + "Show or hide the workspace sidebar": { "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "上下文溢出时,在工作区卡片上显示压缩状态。" + "value": "显示或隐藏工作区侧栏" } } } }, - "Show the worktree directory": { - "extractionState": "manual", + "Show the compacting state on workspace cards when context overflows.": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "显示工作树目录" + "value": "上下文溢出时,在工作区卡片上显示压缩状态。" } } } }, - "Show usage in sidebar": { + "Show the worktree directory": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "在侧栏显示额度" + "value": "显示工作树目录" } } } }, - "Show Whitespace": { + "Show usage in sidebar": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "显示空白" + "value": "在侧栏显示额度" } } } @@ -4345,17 +4477,6 @@ } } }, - "Split pane": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "分屏" - } - } - } - }, "Split Right · %@": { "extractionState": "manual", "localizations": { @@ -4378,13 +4499,13 @@ } } }, - "SSH Project": { + "Split pane": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "SSH 项目" + "value": "分屏" } } } @@ -4521,17 +4642,6 @@ } } }, - "tabs": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "标签页" - } - } - } - }, "TABS": {}, "Terminal": { "extractionState": "stale", @@ -4665,13 +4775,13 @@ } } }, - "this branch": { + "Time without focus.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "该分支" + "value": "未聚焦的持续时间。" } } } @@ -4731,6 +4841,17 @@ } } }, + "UPSTREAM": { + "extractionState": "manual", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "上游" + } + } + } + }, "Unarchive": { "extractionState": "manual", "localizations": { @@ -4830,17 +4951,6 @@ } } }, - "UPSTREAM": { - "extractionState": "manual", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "上游" - } - } - } - }, "Usage off": { "extractionState": "manual", "localizations": { @@ -4962,6 +5072,27 @@ } } }, + "WORKSPACE": { + "extractionState": "stale", + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区" + } + } + } + }, + "WORKSPACES": { + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区" + } + } + } + }, "Waiting for your approval": { "extractionState": "manual", "localizations": { @@ -5028,62 +5159,73 @@ } } }, - "When Glint reopens, each pane that was running OpenCode at last quit is resumed via `opencode --session ` — so multiple OpenCode panes in one workspace land back in their own sessions. Falls back to `opencode --continue` for panes whose session id wasn't captured.": { + "When Glint reopens, each pane that was running Grok at last quit is resumed via `grok --resume ` — so multiple Grok panes in one workspace land back in their own sessions. Falls back to `grok --continue` for panes whose session id wasn't captured.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Glint 重新打开时,上次退出前在跑 OpenCode 的窗格会用 `opencode --session ` 各自恢复——同一工作区里多个 OpenCode 窗格不会再被合并到同一个会话。没抓到 session id 的窗格回退到 `opencode --continue`。" + "value": "Glint 重新打开时,上次退出时正在运行 Grok 的每个窗格会通过 `grok --resume ` 恢复,因此同一工作区里的多个 Grok 窗格会回到各自会话。未捕获到 session id 的窗格会回退到 `grok --continue`。" } } } }, - "When on, ⌘T / ⌘D / ⌘N and the + button pop a chooser so a new tab, pane, or workspace can start in an agent. Off opens a plain shell.": { + "When Glint reopens, each pane that was running OMP at last quit is resumed via `omp -r ` — so multiple OMP panes in one workspace land back in their own sessions. Falls back to `omp -c` for panes whose session id wasn't captured.": { + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Glint 重新打开时,上次退出时在跑 OMP 的每个 pane 会通过 `omp -r ` 恢复会话,多个 OMP pane 各自回到自己的会话。未捕获 session id 时回退为 `omp -c`。" + } + } + }, + "extractionState": "manual" + }, + "When Glint reopens, each pane that was running OpenCode at last quit is resumed via `opencode --session ` — so multiple OpenCode panes in one workspace land back in their own sessions. Falls back to `opencode --continue` for panes whose session id wasn't captured.": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "开启后,⌘T / ⌘D / ⌘N 和 + 按钮会弹出选择器,让新建的标签页、窗格或工作区直接在某个 Agent 中启动。关闭则打开普通终端。" + "value": "Glint 重新打开时,上次退出前在跑 OpenCode 的窗格会用 `opencode --session ` 各自恢复——同一工作区里多个 OpenCode 窗格不会再被合并到同一个会话。没抓到 session id 的窗格回退到 `opencode --continue`。" } } } }, - "Window": { - "extractionState": "stale", + "When Glint reopens, each pane that was running pi at last quit is resumed via `pi --session-id ` — so multiple pi panes in one workspace land back in their own sessions. Falls back to `pi --continue` for panes whose session id wasn't captured.": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "窗口" + "value": "Glint 重新打开时,上次退出前正在运行 pi 的每个窗格会通过 `pi --session-id ` 恢复 —— 这样同一工作区里的多个 pi 窗格会回到各自的会话。未捕获到 session id 的窗格回退到 `pi --continue`。" } } } }, - "Workspace": { - "extractionState": "stale", + "When on, ⌘T / ⌘D / ⌘N and the + button pop a chooser so a new tab, pane, or workspace can start in an agent. Off opens a plain shell.": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "工作区" + "value": "开启后,⌘T / ⌘D / ⌘N 和 + 按钮会弹出选择器,让新建的标签页、窗格或工作区直接在某个 Agent 中启动。关闭则打开普通终端。" } } } }, - "WORKSPACE": { + "Window": { "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "工作区" + "value": "窗口" } } } }, - "Workspaces": { + "Workspace": { "extractionState": "stale", "localizations": { "zh-Hans": { @@ -5094,7 +5236,8 @@ } } }, - "WORKSPACES": { + "Workspaces": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { @@ -5147,315 +5290,271 @@ } } }, - "~/.glint/hooks/glint-report.sh": {}, - "· %@": {}, - "—": {}, - "⌘N": {}, - "⌘T": {}, - "── session restored ──": { + "awaiting reply": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "── 已恢复会话 ──" + "value": "待回复" } } } }, - "✓ available": { + "clean": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "✓ 可用" + "value": "干净" } } } }, - "✓ done": { + "compacting…": { "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "✓ 已完成" + "value": "压缩中…" } } } }, - "✗ exists": { - "extractionState": "manual", + "done": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "✗ 已存在" + "value": "完成" } } } }, - "✗ invalid name": { - "extractionState": "manual", + "error": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "✗ 名称非法" + "value": "出错" } } } }, - "Claude Code, Codex, OMP, hook routing": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Claude Code、Codex、OMP、hook 路由" - } - } - }, - "extractionState": "manual" - }, - "OMP": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "OMP" - } - } - }, - "extractionState": "manual" - }, - "OMP detected — install the extension to show its status.": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已检测到 OMP — 安装扩展以显示其状态。" - } - } - }, - "extractionState": "manual" - }, - "OMP not detected on this Mac.": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "此 Mac 上未检测到 OMP。" - } - } - }, - "extractionState": "manual" - }, - "When Glint reopens, each pane that was running OMP at last quit is resumed via `omp -r ` — so multiple OMP panes in one workspace land back in their own sessions. Falls back to `omp -c` for panes whose session id wasn't captured.": { + "needs approval": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Glint 重新打开时,上次退出时在跑 OMP 的每个 pane 会通过 `omp -r ` 恢复会话,多个 OMP pane 各自回到自己的会话。未捕获 session id 时回退为 `omp -c`。" + "value": "等待批准" } } - }, - "extractionState": "manual" + } }, - "Extension file": { + "no cwd": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "扩展文件" + "value": "无目录" } } - }, - "extractionState": "manual" + } }, - "Extension installed and registered with OMP.": { + "not a git repo": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "扩展已安装并注册到 OMP。" + "value": "不是 git 仓库" } } - }, - "extractionState": "manual" + } }, - "Registered via ~/.omp/agent/settings.json; only reports when Glint's pane environment variables are present.": { + "pane": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "通过 ~/.omp/agent/settings.json 注册;仅在存在 Glint 的 pane 环境变量时上报。" + "value": "面板" } } - }, - "extractionState": "manual" + } }, - "~/.glint/hooks/omp-agent-bridge.ts": { + "panes": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "~/.glint/hooks/omp-agent-bridge.ts" + "value": "面板" } } - }, - "extractionState": "manual" + } }, - "Glint installs a portable TypeScript extension at ~/.glint/hooks/omp-agent-bridge.ts and registers that path in ~/.omp/agent/settings.json so Oh My Pi sessions report status like Claude. Uses a tilde path (not an absolute home path) so the same install works on any Mac.": { + "reply": { + "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Glint 会在 ~/.glint/hooks/omp-agent-bridge.ts 安装可移植的 TypeScript 扩展,并在 ~/.omp/agent/settings.json 中注册该路径,使 Oh My Pi 会话像 Claude 一样上报状态。使用波浪线路径(而非绝对 home 路径),同一套安装在任意 Mac 上都能生效。" + "value": "待回复" } } - }, - "extractionState": "manual" + } }, - "awaiting reply": { + "repo detected": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "待回复" + "value": "检测到仓库" } } } }, - "thinking…": { + "running…": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "思考中…" + "value": "运行中…" } } } }, - "reply": { + "tabs": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "待回复" + "value": "标签页" } } } }, - "Agent is waiting for your reply": { + "thinking…": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Agent 等待你的回复" + "value": "思考中…" } } } }, - "Claude Code, Codex, OMP, Grok, hook routing": { + "this branch": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Claude Code、Codex、OMP、Grok、hook 路由" + "value": "该分支" } } } }, - "Grok": { - "extractionState": "manual", + "~/.glint/hooks/glint-report.sh": {}, + "~/.glint/hooks/omp-agent-bridge.ts": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Grok" + "value": "~/.glint/hooks/omp-agent-bridge.ts" } } - } + }, + "extractionState": "manual" }, - "Hooks installed into your Grok hooks directory.": { + "~/.grok/hooks/glint.json": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hook 已安装到你的 Grok hooks 目录。" + "value": "~/.grok/hooks/glint.json" } } } }, - "Grok detected — install the reporter to show its status.": { + "~/.pi/agent/extensions/glint-agent-bridge.ts": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "已检测到 Grok — 安装 reporter 即可显示其状态。" + "value": "~/.pi/agent/extensions/glint-agent-bridge.ts" } } } }, - "Grok not detected on this Mac.": { + "· %@": {}, + "—": {}, + "⌘N": {}, + "⌘T": {}, + "── session restored ──": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "本机未检测到 Grok。" + "value": "── 已恢复会话 ──" } } } }, - "When Glint reopens, each pane that was running Grok at last quit is resumed via `grok --resume ` — so multiple Grok panes in one workspace land back in their own sessions. Falls back to `grok --continue` for panes whose session id wasn't captured.": { + "✓ available": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Glint 重新打开时,上次退出时正在运行 Grok 的每个窗格会通过 `grok --resume ` 恢复,因此同一工作区里的多个 Grok 窗格会回到各自会话。未捕获到 session id 的窗格会回退到 `grok --continue`。" + "value": "✓ 可用" } } } }, - "Dedicated Glint hook file under Grok's global hooks directory; only reports when Glint's pane environment variables are present.": { - "extractionState": "manual", + "✓ done": { + "extractionState": "stale", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Grok 全局 hooks 目录下的独立 Glint hook 文件;仅在存在 Glint 窗格环境变量时上报。" + "value": "✓ 已完成" } } } }, - "~/.grok/hooks/glint.json": { + "✗ exists": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "~/.grok/hooks/glint.json" + "value": "✗ 已存在" } } } }, - "Glint writes ~/.grok/hooks/glint.json so Grok Build sessions report thinking, tools, and ask_user_question (awaiting reply). Dedicated file — not Claude's settings — so Grok is attributed as Grok. The reporter ignores Claude-compat dual-fires under Grok (GROK_SESSION_ID present).": { + "✗ invalid name": { "extractionState": "manual", "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Glint 会写入 ~/.grok/hooks/glint.json,使 Grok Build 会话上报思考、工具调用,以及 ask_user_question(待回复)。使用独立文件(而非 Claude 的 settings),因此 Grok 会话会正确归到 Grok。在 Grok 下 reporter 会忽略 Claude 兼容 hooks 的双触发(存在 GROK_SESSION_ID 时)。" + "value": "✗ 名称非法" } } } diff --git a/Glint/Workspace/WorkspaceStore.swift b/Glint/Workspace/WorkspaceStore.swift index dd7301f..70170c8 100644 --- a/Glint/Workspace/WorkspaceStore.swift +++ b/Glint/Workspace/WorkspaceStore.swift @@ -910,6 +910,9 @@ final class WorkspaceStore: ObservableObject { /// Whether Glint's Grok Build hooks are registered in `~/.grok/hooks/glint.json`. @Published var grokHooksInstalled: Bool = false + /// Whether Glint's pi extension is installed in `~/.pi/agent/extensions`. + @Published var piHooksInstalled: Bool = false + /// Whether Glint's modified-Enter shell keybindings are present in the /// user's shell rc (~/.zshrc / ~/.bashrc). Opt-in, default off. @Published var shellKeybindsInstalled: Bool = false @@ -1087,6 +1090,11 @@ final class WorkspaceStore: ObservableObject { didSet { UserDefaults.standard.set(restoreGrokSession, forKey: "glint.restoreGrokSession") } } + /// Same as `restoreClaudeSession` but for pi — feeds `pi --continue` / `pi --session-id `. + @Published var restorePiSession: Bool = (UserDefaults.standard.object(forKey: "glint.restorePiSession") as? Bool) ?? false { + didSet { UserDefaults.standard.set(restorePiSession, forKey: "glint.restorePiSession") } + } + /// Maps each agent kind to the @Published toggle that gates its /// session-restore-on-launch. Single source of truth: adding a new /// agent means adding ONE entry here, not editing two parallel switches @@ -1098,6 +1106,7 @@ final class WorkspaceStore: ObservableObject { .devin: \.restoreDevinSession, .omp: \.restoreOmpSession, .grok: \.restoreGrokSession, + .pi: \.restorePiSession, ] /// Whether session-restore-on-launch is enabled for `kind`. Used by @@ -1322,6 +1331,13 @@ final class WorkspaceStore: ObservableObject { isInstalled: { GrokHookInstaller.isInstalled() }, install: { GrokHookInstaller.installIfNeeded(socketPath: socketPath) } ), + AgentHookSpec( + handledKey: "glint.piHooksAutoInstalled", + displayName: "Pi", + isPresent: PiHookInstaller.isAgentPresent, + isInstalled: { PiHookInstaller.isInstalled() }, + install: { PiHookInstaller.installIfNeeded(socketPath: socketPath) } + ), ] } @@ -1371,6 +1387,7 @@ final class WorkspaceStore: ObservableObject { WorkspaceStore.current?.devinHooksInstalled = DevinHookInstaller.isInstalled() WorkspaceStore.current?.ompHooksInstalled = OmpHookInstaller.isInstalled() WorkspaceStore.current?.grokHooksInstalled = GrokHookInstaller.isInstalled() + WorkspaceStore.current?.piHooksInstalled = PiHookInstaller.isInstalled() } } @@ -1437,6 +1454,16 @@ final class WorkspaceStore: ObservableObject { self.grokHooksInstalled = GrokHookInstaller.isInstalled() } + func installPiHooks() { + PiHookInstaller.installIfNeeded(socketPath: AgentBridge.shared.socketPath) + self.piHooksInstalled = PiHookInstaller.isInstalled() + } + + func uninstallPiHooks() { + PiHookInstaller.uninstall() + self.piHooksInstalled = PiHookInstaller.isInstalled() + } + func installShellKeybinds() { ShellKeybindInstaller.install() self.shellKeybindsInstalled = ShellKeybindInstaller.isInstalled() @@ -1456,6 +1483,7 @@ final class WorkspaceStore: ObservableObject { var devinDetected: Bool { DevinHookInstaller.isAgentPresent() } var ompDetected: Bool { OmpHookInstaller.isAgentPresent() } var grokDetected: Bool { GrokHookInstaller.isAgentPresent() } + var piDetected: Bool { PiHookInstaller.isAgentPresent() } /// Locale to inject into the SwiftUI environment. Driven by /// `preferredLanguage`. On macOS 14+, SwiftUI re-resolves @@ -2483,6 +2511,13 @@ final class WorkspaceStore: ObservableObject { // check would false-positive on process names like "compiz". if lower == "omp" || lower.hasSuffix("/omp") { return .omp } if lower.contains("grok") { return .grok } + // Exact match for "pi" — it's only two letters, so a substring check + // would false-positive on process names like "pipe", "pip", "copy", + // "spiped". pi is an npm bin shim (node dist/cli.js), but + // GhosttySurfaceView.scriptBasenameFromArgv already resolves the + // shim's argv to its basename "pi", so the comm/argv we see here is + // the clean short name. + if lower == "pi" || lower.hasSuffix("/pi") { return .pi } return nil } @@ -3845,6 +3880,7 @@ enum WorkspaceIconKind { case devin case omp case grok + case pi case ssh case vim case python @@ -3861,7 +3897,7 @@ enum WorkspaceIconKind { case .python: return "chevron.left.forwardslash.chevron.right" case .node: return "hexagon.fill" case .git: return "arrow.triangle.branch" - case .claude, .codex, .opencode, .devin, .omp, .grok, .other: + case .claude, .codex, .opencode, .devin, .omp, .grok, .pi, .other: return nil } } @@ -3875,6 +3911,7 @@ enum WorkspaceIconKind { case .devin: return "D" case .omp: return "π" case .grok: return "G" + case .pi: return "π" case .other(let s): return s.first.map { String($0).uppercased() } ?? "?" default: @@ -4132,6 +4169,7 @@ extension WorkspaceStore { case .devin: return .devin case .omp: return .omp case .grok: return .grok + case .pi: return .pi } } @@ -4147,6 +4185,7 @@ extension WorkspaceStore { if names.contains(where: { $0 == "devin" || $0.contains("devin") }) { return .devin } if names.contains(where: { $0 == "omp" || $0.hasSuffix("/omp") }) { return .omp } if names.contains(where: { $0 == "grok" || $0.contains("grok") }) { return .grok } + if names.contains(where: { $0 == "pi" || $0.hasSuffix("/pi") }) { return .pi } if names.contains(where: { $0 == "vim" || $0 == "nvim" || $0 == "vi" }) { return .vim } if names.contains(where: { $0 == "python" || $0 == "python3" || $0 == "ipython" }) { return .python } if names.contains(where: { $0 == "node" || $0 == "deno" || $0 == "bun" }) { return .node }