{options.map((option) => (
@@ -160,7 +219,7 @@ export function AgentComposer({ conversation, canSend }: AgentComposerProps) {
className="w-full border-border/70 border-b bg-transparent px-2 py-1 text-foreground outline-none ui-text-sm"
onChange={(event) => setFileQuery(event.target.value)}
onKeyDown={(event) => {
- if (event.key === "Escape") setIsPickingFile(false);
+ if (event.key === "Escape") closePicker();
}}
/>
@@ -174,11 +233,7 @@ export function AgentComposer({ conversation, canSend }: AgentComposerProps) {
key={file.path}
type="button"
className="flex w-full items-center px-2 py-0.5 text-left hover:bg-accent ui-text-sm"
- onClick={() => {
- attach([file.path]);
- setIsPickingFile(false);
- setFileQuery("");
- }}
+ onClick={() => pickFile(file.path)}
>
{file.path}
@@ -193,7 +248,17 @@ export function AgentComposer({ conversation, canSend }: AgentComposerProps) {
rows={3}
value={draft}
placeholder={t("agent.composer.placeholder")}
- onChange={(event) => setDraft(event.target.value)}
+ onChange={(event) => {
+ const value = event.target.value;
+ setDraft(value);
+ // Typing `@` opens the picker on the unfinished reference at the caret.
+ const caret = event.target.selectionStart ?? value.length;
+ const opened = agentMentionQuery(value.slice(0, caret));
+ if (opened === null) return;
+ setMention(opened);
+ setFileQuery(opened.query);
+ setIsPickingFile(true);
+ }}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
event.preventDefault();
@@ -210,7 +275,15 @@ export function AgentComposer({ conversation, canSend }: AgentComposerProps) {
variant="ghost"
tooltip={t("agent.composer.attachFile")}
aria-label={t("agent.composer.attachFile")}
- onClick={() => setIsPickingFile((value) => !value)}
+ onClick={() => {
+ if (isPickingFile) {
+ closePicker();
+ return;
+ }
+ setMention(null);
+ setFileQuery("");
+ setIsPickingFile(true);
+ }}
>
diff --git a/windows/tauri/src/features/agent/services/agent-composer-mentions.test.ts b/windows/tauri/src/features/agent/services/agent-composer-mentions.test.ts
new file mode 100644
index 000000000..a5d4690bc
--- /dev/null
+++ b/windows/tauri/src/features/agent/services/agent-composer-mentions.test.ts
@@ -0,0 +1,26 @@
+import { describe, expect, test } from "bun:test";
+import { agentMentionQuery, removeAgentMention } from "./agent-composer-mentions";
+
+describe("Agent composer file mentions", () => {
+ test("opens on a bare trigger and keeps filtering while the user types", () => {
+ expect(agentMentionQuery("@")).toEqual({ start: 0, query: "" });
+ expect(agentMentionQuery("fix this @src/ma")).toEqual({ start: 9, query: "src/ma" });
+ expect(agentMentionQuery("see\tthen @a")).toEqual({ start: 9, query: "a" });
+ });
+
+ test("stays closed for finished references, addresses and mid-word triggers", () => {
+ expect(agentMentionQuery("mail me at user@example")).toBeNull();
+ expect(agentMentionQuery("done @src/main.ts ")).toBeNull();
+ expect(agentMentionQuery("no trigger here")).toBeNull();
+ expect(agentMentionQuery("")).toBeNull();
+ });
+
+ test("removing the token leaves the rest of the draft intact", () => {
+ const mention = agentMentionQuery("check @src/ma");
+ if (mention === null) throw new Error("the helper must open the picker");
+ expect(removeAgentMention("check @src/ma", mention)).toBe("check ");
+ expect(removeAgentMention("@a", { start: 0, query: "a" })).toBe("");
+ // A shorter draft than the match (user deleted text) is not over-sliced.
+ expect(removeAgentMention("", { start: 0, query: "src" })).toBe("");
+ });
+});
diff --git a/windows/tauri/src/features/agent/services/agent-composer-mentions.ts b/windows/tauri/src/features/agent/services/agent-composer-mentions.ts
new file mode 100644
index 000000000..812de1bef
--- /dev/null
+++ b/windows/tauri/src/features/agent/services/agent-composer-mentions.ts
@@ -0,0 +1,39 @@
+/**
+ * `@` file references in the Agent prompt.
+ *
+ * The composer only uses this to decide which part of the draft opened the file
+ * picker. The draft itself stays plain text and the chosen file travels as an
+ * ACP file reference, so the match is kept pure and testable without a DOM or a
+ * running adapter.
+ */
+
+/** Query of an unfinished `@` reference ending at the caret. */
+export interface AgentMentionQuery {
+ /** Index of the `@` that started the reference. */
+ start: number;
+ /** Text typed after `@`, empty right after the trigger. */
+ query: string;
+}
+
+/**
+ * Read an unfinished `@` reference from the text before the caret. A reference
+ * starts at the beginning of the text or after whitespace and holds no
+ * whitespace or second `@`, so a finished reference or an email address does
+ * not reopen the picker while the user keeps typing.
+ */
+export function agentMentionQuery(textBeforeCaret: string): AgentMentionQuery | null {
+ const match = /(?:^|\s)@([^\s@]*)$/.exec(textBeforeCaret);
+ if (match === null) return null;
+ const query = match[1] ?? "";
+ return { start: textBeforeCaret.length - query.length - 1, query };
+}
+
+/**
+ * Remove the `@query` token that opened the picker, so picking a file leaves no
+ * stray text in the prompt. A draft edited after the picker opened keeps
+ * whatever the user has typed since; only the matched range is dropped.
+ */
+export function removeAgentMention(text: string, mention: AgentMentionQuery): string {
+ const end = Math.min(text.length, mention.start + 1 + mention.query.length);
+ return `${text.slice(0, mention.start)}${text.slice(end)}`;
+}
diff --git a/windows/tauri/src/i18n/locale.ts b/windows/tauri/src/i18n/locale.ts
index 9513fc64a..fa02abd62 100644
--- a/windows/tauri/src/i18n/locale.ts
+++ b/windows/tauri/src/i18n/locale.ts
@@ -366,6 +366,7 @@ const catalogs = {
"agent.composer.preparing": "Preparing the session…",
"agent.composer.queued": "Queued: {text}",
"agent.composer.attachFile": "Attach a project file",
+ "agent.composer.dropFiles": "Drop files to reference them",
"agent.composer.attachActive": "Attach current file",
"agent.composer.removeFile": "Remove {name}",
"agent.composer.searchFiles": "Search project files",
@@ -444,6 +445,13 @@ const catalogs = {
"agent.settings.installing": "Installing…",
"agent.settings.progress": "npm {stage} · {megabytes} MB",
"agent.settings.command": "Executable",
+ "agent.settings.cliDetected": "{name} {version} is installed.",
+ "agent.settings.cliMissing": "{name} {version} or newer was not found.",
+ "agent.settings.updateCli": "Update",
+ "agent.settings.installCli": "Install",
+ "agent.settings.updatingCli": "Updating…",
+ "agent.settings.cliUpdated": "Updated to {version}.",
+ "agent.settings.cliUpdateWarning": "Updated with an installer warning: {warning}",
"agent.settings.arguments": "Arguments",
"agent.settings.authentication": "Sign-in",
"agent.settings.apiKeyMode": "API key",
@@ -459,6 +467,11 @@ const catalogs = {
"agent.settings.allowInsecureHint": "Allow a plain http endpoint",
"agent.settings.reconnectHint": "Provider changes apply on the next connection.",
"agent.settings.reconnect": "Reconnect",
+ "agent.settings.importConfiguration": "Import CLI configuration",
+ "agent.settings.importConfigurationHint": "Reads the endpoint and model only; keys stay with your CLI.",
+ "agent.settings.importConfigurationAction": "Import",
+ "agent.settings.importingConfiguration": "Reading…",
+ "agent.settings.importProtocolMismatch": "This configuration does not match the selected agent's protocol.",
"ai.unknownError": "Unknown error",
"ai.planSummary": "Plan ({count} {label})",
"ai.step": "step",
@@ -4979,6 +4992,7 @@ const catalogs = {
"agent.composer.preparing": "正在准备会话…",
"agent.composer.queued": "已排队:{text}",
"agent.composer.attachFile": "引用项目文件",
+ "agent.composer.dropFiles": "松开即可引用文件",
"agent.composer.attachActive": "引用当前文件",
"agent.composer.removeFile": "移除 {name}",
"agent.composer.searchFiles": "搜索项目文件",
@@ -5056,6 +5070,13 @@ const catalogs = {
"agent.settings.installing": "安装中…",
"agent.settings.progress": "npm {stage} · {megabytes} MB",
"agent.settings.command": "可执行文件",
+ "agent.settings.cliDetected": "已安装 {name} {version}。",
+ "agent.settings.cliMissing": "未找到 {name} {version} 或更高版本。",
+ "agent.settings.updateCli": "升级",
+ "agent.settings.installCli": "安装",
+ "agent.settings.updatingCli": "升级中…",
+ "agent.settings.cliUpdated": "已升级到 {version}。",
+ "agent.settings.cliUpdateWarning": "已升级,但安装器有警告:{warning}",
"agent.settings.arguments": "参数",
"agent.settings.authentication": "登录方式",
"agent.settings.apiKeyMode": "API Key",
@@ -5071,6 +5092,11 @@ const catalogs = {
"agent.settings.allowInsecureHint": "允许明文 http 地址",
"agent.settings.reconnectHint": "服务商改动在下次连接时生效。",
"agent.settings.reconnect": "重新连接",
+ "agent.settings.importConfiguration": "导入 CLI 配置",
+ "agent.settings.importConfigurationHint": "只读取地址与模型,API Key 仍由本机 CLI 管理。",
+ "agent.settings.importConfigurationAction": "导入",
+ "agent.settings.importingConfiguration": "解析中…",
+ "agent.settings.importProtocolMismatch": "该配置的协议与所选 Agent 不匹配。",
"ai.unknownError": "未知错误",
"ai.planSummary": "计划({count} 个{label})",
"ai.step": "步骤",
From 730f3f7575584b193b5fccf14de3db9cc63a2f2a Mon Sep 17 00:00:00 2001
From: puppy_1 <13323021675@163.com>
Date: Thu, 1 Oct 2026 13:37:30 +0800
Subject: [PATCH 12/16] =?UTF-8?q?feat(windows):=20Agent=20=E8=AE=BE?=
=?UTF-8?q?=E7=BD=AE=E6=94=AF=E6=8C=81=20CLI=20=E5=AE=89=E8=A3=85=E5=8D=87?=
=?UTF-8?q?=E7=BA=A7=E4=B8=8E=20CLI=20=E9=85=8D=E7=BD=AE=E5=AF=BC=E5=85=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- CLI 安装/升级入口(agent.installCli):设置页显示 PATH 上 CLI 的版本与安装来源;
只有 host 明确给出 canUpdate,或根本没检测到 CLI 时才提供按钮,升级进度复用
agentInstallProgress;成功后显示已验证版本,安装器带警告时单独一行展示,不把
警告当成失败,也不在无法判断来源时让用户以为会自动覆盖。
- 供应商配置导入(agent.parseProviderConfiguration):粘贴 Codex TOML 或 Claude JSON,
由共享 Rust 解析出 endpoint、model 与协议后填入供应商表单;只读元数据、不搬运
密钥;chatCompletions 以及与所选适配器协议不匹配时给出明确提示,而不是猜一个协议。
- 服务层新增 installAgentCli / parseAgentCliUpdateResult 与 agent-provider-import,
agent.install 与 agent.installCli 共用的进度 channel 抽成 installProgressChannel。
- tauri-core 能力门控加入 agent.parseProviderConfiguration,Agent 面板关闭时与其他
agent.* 一样走统一不可用路径。
---
.../components/agent-settings-section.tsx | 125 +++++++++++++++++-
.../agent/hooks/use-agent-management.ts | 39 +++++-
.../agent/services/agent-management.test.ts | 30 ++++-
.../agent/services/agent-management.ts | 66 +++++++--
.../services/agent-provider-import.test.ts | 60 +++++++++
.../agent/services/agent-provider-import.ts | 78 +++++++++++
.../agent/types/agent-settings.types.ts | 3 +
windows/tauri/src/platform/tauri-core.ts | 3 +-
8 files changed, 392 insertions(+), 12 deletions(-)
create mode 100644 windows/tauri/src/features/agent/services/agent-provider-import.test.ts
create mode 100644 windows/tauri/src/features/agent/services/agent-provider-import.ts
diff --git a/windows/tauri/src/features/agent/components/agent-settings-section.tsx b/windows/tauri/src/features/agent/components/agent-settings-section.tsx
index 5155cdb8c..2e368010b 100644
--- a/windows/tauri/src/features/agent/components/agent-settings-section.tsx
+++ b/windows/tauri/src/features/agent/components/agent-settings-section.tsx
@@ -9,6 +9,12 @@ import Textarea from "@/ui/textarea";
import type { AgentManagement } from "../hooks/use-agent-management";
import { readAgentApiKey, storeAgentApiKey } from "../services/agent-launch";
import {
+ agentProviderConfigSource,
+ importAgentProviderConfiguration,
+ importedProviderMatchesAgent,
+} from "../services/agent-provider-import";
+import {
+ CLAUDE_AGENT_ID,
CODEX_AGENT_ID,
CUSTOM_AGENT_ID,
supportsCodexSubscription,
@@ -17,7 +23,7 @@ import {
const FALLBACK_AGENTS = [
{ id: CODEX_AGENT_ID, name: "Codex" },
- { id: "claude-acp", name: "Claude" },
+ { id: CLAUDE_AGENT_ID, name: "Claude" },
];
interface AgentSettingsSectionProps {
@@ -42,8 +48,12 @@ export function AgentSettingsSection({
const [apiKeyDraft, setApiKeyDraft] = useState("");
const [hasStoredKey, setHasStoredKey] = useState(false);
const [keyError, setKeyError] = useState
(null);
+ const [importText, setImportText] = useState("");
+ const [importError, setImportError] = useState(null);
+ const [isImporting, setIsImporting] = useState(false);
const isCustom = settings.agentId === CUSTOM_AGENT_ID;
const isSubscription = settings.authentication === "codexSubscription";
+ const configSource = agentProviderConfigSource(settings.agentId);
useEffect(() => {
let cancelled = false;
@@ -74,6 +84,11 @@ export function AgentSettingsSection({
const selectedAgent = management.status?.agents.find((agent) => agent.id === settings.agentId);
const canInstall = selectedAgent !== undefined && !isCustom;
const isBusy = management.busyAgentId === settings.agentId;
+ const cli = selectedAgent?.cli ?? null;
+ const cliUpdate = management.cliUpdates[settings.agentId] ?? null;
+ // A missing CLI is installed through its own vendor, and the host only offers
+ // an automatic update when it verified the installation owner.
+ const canInstallCli = canInstall && cli !== null && (cli.detected === null || cli.installation.canUpdate);
const saveKey = async () => {
try {
@@ -87,6 +102,32 @@ export function AgentSettingsSection({
}
};
+ const importConfiguration = async () => {
+ if (configSource === null) return;
+ setIsImporting(true);
+ try {
+ const provider = await importAgentProviderConfiguration(configSource, importText);
+ if (!importedProviderMatchesAgent(settings.agentId, provider)) {
+ setImportError(t("agent.settings.importProtocolMismatch"));
+ return;
+ }
+ onChange({
+ ...settings,
+ provider: {
+ ...settings.provider,
+ protocol: provider.protocol,
+ baseUrl: provider.baseUrl,
+ model: provider.model,
+ },
+ });
+ setImportError(null);
+ } catch (error) {
+ setImportError(error instanceof Error ? error.message : String(error));
+ } finally {
+ setIsImporting(false);
+ }
+ };
+
return (
@@ -151,6 +192,55 @@ export function AgentSettingsSection({
) : null}
+ {canInstall && cli !== null ? (
+
+
+ {cli.detected === null
+ ? t("agent.settings.cliMissing", {
+ name: cli.name,
+ version: cli.minimumVersion,
+ })
+ : t("agent.settings.cliDetected", {
+ name: cli.name,
+ version: cli.detected.version,
+ })}
+
+ {canInstallCli ? (
+
+ ) : null}
+
+ ) : null}
+
+ {canInstallCli || cli === null ? null : (
+
{cli.installation.updateHint}
+ )}
+
+ {cliUpdate === null ? null : (
+ <>
+
+ {t("agent.settings.cliUpdated", { version: cliUpdate.cliVersion })}
+
+ {cliUpdate.updaterWarning === null ? null : (
+
+ {t("agent.settings.cliUpdateWarning", { warning: cliUpdate.updaterWarning })}
+
+ )}
+ >
+ )}
+
{selectedAgent === undefined || selectedAgent.issues.length === 0 ? null : (
{selectedAgent.issues.map((issue) => (
@@ -310,6 +400,39 @@ export function AgentSettingsSection({
{t("agent.settings.reconnect")}
+ {configSource === null ? null : (
+
+
+ {t("agent.settings.importConfiguration")}
+
+
+ )}
>
)}
{keyError === null ? null : {keyError}
}
diff --git a/windows/tauri/src/features/agent/hooks/use-agent-management.ts b/windows/tauri/src/features/agent/hooks/use-agent-management.ts
index a81c958a5..29abcce7f 100644
--- a/windows/tauri/src/features/agent/hooks/use-agent-management.ts
+++ b/windows/tauri/src/features/agent/hooks/use-agent-management.ts
@@ -1,8 +1,10 @@
import { useCallback, useEffect, useState } from "react";
import {
installAgent,
+ installAgentCli,
loadAgentManagementStatus,
uninstallAgent,
+ type AgentCliUpdateResult,
type AgentInstallProgress,
type AgentManagementStatus,
} from "../services/agent-management";
@@ -13,9 +15,12 @@ export interface AgentManagement {
/** Agent id with an install or removal in flight. */
busyAgentId: string | null;
progress: AgentInstallProgress | null;
+ /** Last verified CLI update outcome for each agent id. */
+ cliUpdates: Record;
isRefreshing: boolean;
refresh: () => Promise;
install: (agentId: string) => Promise;
+ installCli: (agentId: string) => Promise;
uninstall: (agentId: string) => Promise;
}
@@ -31,6 +36,7 @@ export function useAgentManagement(dataDirectory: string | null, enabled: boolea
const [error, setError] = useState(null);
const [busyAgentId, setBusyAgentId] = useState(null);
const [progress, setProgress] = useState(null);
+ const [cliUpdates, setCliUpdates] = useState>({});
const [isRefreshing, setIsRefreshing] = useState(false);
const refresh = useCallback(async () => {
@@ -87,5 +93,36 @@ export function useAgentManagement(dataDirectory: string | null, enabled: boolea
[busyAgentId, dataDirectory],
);
- return { status, error, busyAgentId, progress, isRefreshing, refresh, install, uninstall };
+ const installCli = useCallback(
+ async (agentId: string) => {
+ if (dataDirectory === null || busyAgentId !== null) return;
+ setBusyAgentId(agentId);
+ setProgress(null);
+ setError(null);
+ try {
+ const result = await installAgentCli(dataDirectory, agentId, setProgress);
+ setCliUpdates((current) => ({ ...current, [agentId]: result }));
+ setStatus(await loadAgentManagementStatus(dataDirectory));
+ } catch (failure) {
+ setError(failure instanceof Error ? failure.message : String(failure));
+ } finally {
+ setProgress(null);
+ setBusyAgentId(null);
+ }
+ },
+ [busyAgentId, dataDirectory],
+ );
+
+ return {
+ status,
+ error,
+ busyAgentId,
+ progress,
+ cliUpdates,
+ isRefreshing,
+ refresh,
+ install,
+ installCli,
+ uninstall,
+ };
}
diff --git a/windows/tauri/src/features/agent/services/agent-management.test.ts b/windows/tauri/src/features/agent/services/agent-management.test.ts
index 902935843..8bcc1293e 100644
--- a/windows/tauri/src/features/agent/services/agent-management.test.ts
+++ b/windows/tauri/src/features/agent/services/agent-management.test.ts
@@ -1,6 +1,10 @@
import { describe, expect, test } from "bun:test";
import fixture from "../../../../../../shared/fixtures/agent/agent-management-v1.json";
-import { installProgressEvent, parseAgentManagementStatus } from "./agent-management";
+import {
+ installProgressEvent,
+ parseAgentCliUpdateResult,
+ parseAgentManagementStatus,
+} from "./agent-management";
const status = fixture.responses.status;
@@ -58,3 +62,27 @@ describe("Agent install progress", () => {
expect(installProgressEvent(null)).toBeNull();
});
});
+
+describe("Agent CLI update result", () => {
+ test("reads the verified version of a clean update", () => {
+ expect(parseAgentCliUpdateResult(fixture.responses.installCli)).toEqual({
+ cliVersion: "0.156.1",
+ updaterWarning: null,
+ });
+ });
+
+ test("keeps a recovered installer warning separate from the version", () => {
+ expect(parseAgentCliUpdateResult(fixture.responses.installCliRecovered)).toEqual({
+ cliVersion: "0.157.1",
+ updaterWarning: "Download failed; retry completed",
+ });
+ });
+
+ test("an unverified version is rejected instead of shown as success", () => {
+ expect(parseAgentCliUpdateResult({ updaterWarning: "x" })).toBeNull();
+ expect(parseAgentCliUpdateResult({ cliVersion: "" })).toBeNull();
+ expect(parseAgentCliUpdateResult({ cliVersion: 156 })).toBeNull();
+ expect(parseAgentCliUpdateResult("0.156.1")).toBeNull();
+ expect(parseAgentCliUpdateResult(null)).toBeNull();
+ });
+});
diff --git a/windows/tauri/src/features/agent/services/agent-management.ts b/windows/tauri/src/features/agent/services/agent-management.ts
index 96fe0d575..1d336792d 100644
--- a/windows/tauri/src/features/agent/services/agent-management.ts
+++ b/windows/tauri/src/features/agent/services/agent-management.ts
@@ -61,6 +61,16 @@ export interface AgentInstallProgress {
idleMilliseconds: number;
}
+/**
+ * Verified outcome of `agent.installCli`. The warning carries a bounded
+ * installer log tail when the updater reported failure but the CLI was
+ * verifiably updated; it is never error input.
+ */
+export interface AgentCliUpdateResult {
+ cliVersion: string;
+ updaterWarning: string | null;
+}
+
function isRecord(value: unknown): value is Record {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
@@ -96,6 +106,19 @@ export function installProgressEvent(value: unknown): AgentInstallProgress | nul
};
}
+/**
+ * Read the documented `agent.installCli` response, or `null` when it is not
+ * recognised. Responses from hosts that predate `updaterWarning` decode as a
+ * clean result.
+ */
+export function parseAgentCliUpdateResult(value: unknown): AgentCliUpdateResult | null {
+ if (!isRecord(value)) return null;
+ const cliVersion = asString(value.cliVersion);
+ if (cliVersion === null || cliVersion.length === 0) return null;
+ const warning = (asString(value.updaterWarning) ?? "").trim();
+ return { cliVersion, updaterWarning: warning.length > 0 ? warning : null };
+}
+
function cliStatus(value: unknown): AgentCliStatus | null {
if (!isRecord(value)) return null;
const installation = isRecord(value.installation) ? value.installation : {};
@@ -160,20 +183,26 @@ export async function loadAgentManagementStatus(
return status;
}
+/** Channel that forwards one operation's `agentInstallProgress` events. */
+function installProgressChannel(
+ onProgress?: (progress: AgentInstallProgress) => void,
+): Channel | undefined {
+ if (onProgress === undefined) return undefined;
+ const channel = new Channel();
+ channel.onmessage = (event) => {
+ const progress = installProgressEvent(event);
+ if (progress !== null) onProgress(progress);
+ };
+ return channel;
+}
+
/** Install one adapter, reporting npm transfer progress through a channel. */
export async function installAgent(
dataDirectory: string,
agentId: string,
onProgress?: (progress: AgentInstallProgress) => void,
): Promise {
- const channel = onProgress === undefined ? undefined : new Channel();
- if (channel !== undefined && onProgress !== undefined) {
- const report = onProgress;
- channel.onmessage = (event) => {
- const progress = installProgressEvent(event);
- if (progress !== null) report(progress);
- };
- }
+ const channel = installProgressChannel(onProgress);
await invoke(
"agent.install",
{ dataDirectory, agentId },
@@ -181,6 +210,27 @@ export async function installAgent(
);
}
+/**
+ * Install or update the agent's own CLI through the installation owner the
+ * host re-resolves on this call, so no remembered installation facts travel
+ * with the request.
+ */
+export async function installAgentCli(
+ dataDirectory: string,
+ agentId: string,
+ onProgress?: (progress: AgentInstallProgress) => void,
+): Promise {
+ const channel = installProgressChannel(onProgress);
+ const response = await invoke(
+ "agent.installCli",
+ { dataDirectory, agentId },
+ channel === undefined ? undefined : { agentEvents: channel },
+ );
+ const result = parseAgentCliUpdateResult(response);
+ if (result === null) throw new Error("The Agent CLI update response was not recognised.");
+ return result;
+}
+
export async function uninstallAgent(dataDirectory: string, agentId: string): Promise {
await invoke("agent.uninstall", { dataDirectory, agentId });
}
diff --git a/windows/tauri/src/features/agent/services/agent-provider-import.test.ts b/windows/tauri/src/features/agent/services/agent-provider-import.test.ts
new file mode 100644
index 000000000..012cf8e6e
--- /dev/null
+++ b/windows/tauri/src/features/agent/services/agent-provider-import.test.ts
@@ -0,0 +1,60 @@
+import { describe, expect, test } from "bun:test";
+import fixture from "../../../../../../shared/fixtures/agent/provider-configuration-v1.json";
+import {
+ agentProviderConfigSource,
+ importedProviderMatchesAgent,
+ parseImportedAgentProvider,
+} from "./agent-provider-import";
+
+const [codexCase, claudeCase] = fixture.cases;
+
+describe("Imported Agent provider metadata", () => {
+ test("reads the metadata the shared parser reported for each format", () => {
+ expect(parseImportedAgentProvider(codexCase?.expected)).toEqual({
+ protocol: "responses",
+ baseUrl: "https://example.test/v1",
+ model: "fixture-model",
+ });
+ expect(parseImportedAgentProvider(claudeCase?.expected)).toEqual({
+ protocol: "anthropicMessages",
+ baseUrl: "https://example.test/v1",
+ model: "fixture-model",
+ });
+ });
+
+ test("an unsupported or incomplete response is rejected instead of filled in", () => {
+ // `chatCompletions` has no Agent settings equivalent; guessing a protocol
+ // would silently send prompts to the wrong wire format.
+ expect(
+ parseImportedAgentProvider({
+ endpoint: "https://example.test/v1",
+ model: "fixture-model",
+ apiProtocol: "chatCompletions",
+ }),
+ ).toBeNull();
+ expect(parseImportedAgentProvider({ endpoint: "", model: "m", apiProtocol: "responses" })).toBeNull();
+ expect(parseImportedAgentProvider({ endpoint: "https://x.test", model: " " })).toBeNull();
+ expect(parseImportedAgentProvider(null)).toBeNull();
+ expect(parseImportedAgentProvider("responses")).toBeNull();
+ });
+});
+
+describe("CLI configuration source of an agent", () => {
+ test("only the catalog agents that own a CLI format are importable", () => {
+ expect(agentProviderConfigSource("codex-acp")).toBe("codex");
+ expect(agentProviderConfigSource("claude-acp")).toBe("claude");
+ expect(agentProviderConfigSource("custom")).toBeNull();
+ });
+
+ test("imported metadata must speak the selected adapter's protocol", () => {
+ const responses = {
+ protocol: "responses" as const,
+ baseUrl: "https://example.test/v1",
+ model: "fixture-model",
+ };
+ expect(importedProviderMatchesAgent("codex-acp", responses)).toBe(true);
+ expect(importedProviderMatchesAgent("claude-acp", responses)).toBe(false);
+ // A custom adapter declares no protocol here, so the user's choice stands.
+ expect(importedProviderMatchesAgent("custom", responses)).toBe(true);
+ });
+});
diff --git a/windows/tauri/src/features/agent/services/agent-provider-import.ts b/windows/tauri/src/features/agent/services/agent-provider-import.ts
new file mode 100644
index 000000000..eeb55d1fa
--- /dev/null
+++ b/windows/tauri/src/features/agent/services/agent-provider-import.ts
@@ -0,0 +1,78 @@
+/**
+ * Import Agent provider metadata from pasted CLI configuration text.
+ *
+ * The parse itself belongs to the shared Rust core
+ * (`agent.parseProviderConfiguration`), which returns provider metadata only:
+ * endpoint, model and wire protocol, with no credentials or credential
+ * presence. Lithe therefore never copies a key out of the user's CLI files;
+ * the panel asks for one separately when the endpoint needs it.
+ */
+
+import { invoke } from "@/platform/tauri-core";
+import {
+ AGENT_PROTOCOL_BY_ID,
+ CLAUDE_AGENT_ID,
+ CODEX_AGENT_ID,
+ type AgentProviderProtocol,
+} from "../types/agent-settings.types";
+
+/** CLI configuration formats the shared parser understands. */
+export type AgentProviderConfigSource = "codex" | "claude";
+
+/** Provider metadata the shared parser extracted from pasted configuration. */
+export interface ImportedAgentProvider {
+ protocol: AgentProviderProtocol;
+ baseUrl: string;
+ model: string;
+}
+
+/** Which CLI format describes the local configuration of a catalog agent. */
+export function agentProviderConfigSource(agentId: string): AgentProviderConfigSource | null {
+ if (agentId === CODEX_AGENT_ID) return "codex";
+ if (agentId === CLAUDE_AGENT_ID) return "claude";
+ return null;
+}
+
+function asString(value: unknown): string | null {
+ return typeof value === "string" ? value : null;
+}
+
+/**
+ * Read the documented parser response, or `null` when it does not carry the
+ * metadata the panel needs. `chatCompletions` is a valid provider protocol but
+ * has no Agent settings equivalent, so it is rejected instead of being
+ * silently rewritten into another protocol.
+ */
+export function parseImportedAgentProvider(value: unknown): ImportedAgentProvider | null {
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
+ const record = value as Record;
+ const endpoint = (asString(record.endpoint) ?? "").trim();
+ const model = (asString(record.model) ?? "").trim();
+ const protocol = record.apiProtocol;
+ if (endpoint.length === 0 || model.length === 0) return null;
+ if (protocol !== "responses" && protocol !== "anthropicMessages") return null;
+ return { protocol, baseUrl: endpoint, model };
+}
+
+/** Whether imported metadata speaks the protocol the selected adapter expects. */
+export function importedProviderMatchesAgent(
+ agentId: string,
+ provider: ImportedAgentProvider,
+): boolean {
+ const expected = AGENT_PROTOCOL_BY_ID[agentId];
+ return expected === undefined || expected === provider.protocol;
+}
+
+/** Parse pasted CLI configuration into provider metadata through the shared core. */
+export async function importAgentProviderConfiguration(
+ source: AgentProviderConfigSource,
+ configuration: string,
+): Promise {
+ const response = await invoke("agent.parseProviderConfiguration", {
+ source,
+ configuration,
+ });
+ const provider = parseImportedAgentProvider(response);
+ if (provider === null) throw new Error("The provider configuration could not be read.");
+ return provider;
+}
diff --git a/windows/tauri/src/features/agent/types/agent-settings.types.ts b/windows/tauri/src/features/agent/types/agent-settings.types.ts
index 22ca4841e..5ade8f64c 100644
--- a/windows/tauri/src/features/agent/types/agent-settings.types.ts
+++ b/windows/tauri/src/features/agent/types/agent-settings.types.ts
@@ -16,6 +16,9 @@ export const CUSTOM_AGENT_ID = "custom";
export const CODEX_AGENT_ID = "codex-acp";
+/** Built-in adapter id for the Claude Code CLI. */
+export const CLAUDE_AGENT_ID = "claude-acp";
+
export interface AgentProviderSettings {
protocol: AgentProviderProtocol;
/** Endpoint as configured, with or without the protocol's path suffix. */
diff --git a/windows/tauri/src/platform/tauri-core.ts b/windows/tauri/src/platform/tauri-core.ts
index cda56177c..fcf8c9d52 100644
--- a/windows/tauri/src/platform/tauri-core.ts
+++ b/windows/tauri/src/platform/tauri-core.ts
@@ -201,7 +201,8 @@ function capabilityForCommand(command: string): BackendCapability | null {
command === "agent.status" ||
command === "agent.install" ||
command === "agent.uninstall" ||
- command === "agent.installCli"
+ command === "agent.installCli" ||
+ command === "agent.parseProviderConfiguration"
) {
return "agent";
}
From 29dcbdb38a321e6b2c249a79ebcc093df3ab4736 Mon Sep 17 00:00:00 2001
From: puppy_1 <13323021675@163.com>
Date: Thu, 1 Oct 2026 13:47:10 +0800
Subject: [PATCH 13/16] =?UTF-8?q?docs(matrix):=20=E8=AE=B0=E5=BD=95=20Wind?=
=?UTF-8?q?ows=20Agent=20=E7=9A=84=20CLI=20=E5=AE=89=E8=A3=85=E5=8D=87?=
=?UTF-8?q?=E7=BA=A7=E3=80=81=E9=85=8D=E7=BD=AE=E5=AF=BC=E5=85=A5=E4=B8=8E?=
=?UTF-8?q?=E6=8B=96=E6=8B=BD/@=20=E5=BC=95=E7=94=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- agent-management:Windows 说明改为已接入 CLI 安装/升级——只有 host 明确给出可更新来源
(canUpdate)或完全没有检测到 CLI 时才显示按钮,升级后显示 host 验证过的版本,安装器报错
但版本确实提升时单独一行展示警告;仍未接入的是取消安装、预检清单与本机 CLI 配置文件读取。
证据补上 hooks/use-agent-management.ts。
- agent-provider-configuration:说明粘贴配置文本导入已接入(共享 Rust 解析出 endpoint、模型与
协议,只读元数据、不搬运密钥,chatCompletions 或协议不匹配时报错);仍未接入的是一键读取
本机 CLI 配置文件、多供应商列表与取消关联。证据补上 services/agent-provider-import.ts。
- agent-file-references:Windows 由 partial 改为 implemented。补上从系统拖拽文件与在输入框
输入 @ 打开选择器(两者共用同一套去重、数量上限与可移除标签)的说明与证据;真实拖放体验与
Claude 端到端仍待验证,所以 verificationStatus 保持 pending。
- Agent Note 同步以上实现、验证命令与遗留项。
- docs/development/platform-parity-matrix.csv/.md 由 node scripts/generate-platform-feature-matrix.mjs 重新生成。
---
.../2026-09-25-shared-acp-agent-conversation.md | 8 ++++----
docs/development/platform-parity-matrix.csv | 6 +++---
docs/development/platform-parity-matrix.md | 8 ++++----
shared/platform-feature-matrix.json | 16 ++++++++++------
4 files changed, 21 insertions(+), 17 deletions(-)
diff --git a/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md b/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md
index 07b77c260..d2cc5818f 100644
--- a/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md
+++ b/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md
@@ -50,7 +50,7 @@ Agent 对话默认关闭,打开某个项目的 Agent 面板时才启动本机
- **Windows 面板放在编辑器右栏**:Windows 界面在 `windows/tauri/src/features/agent/` 另写 React 面板,落点是编辑器右侧的右栏工具窗,与 macOS 的 `AgentConversationEntryPolicy.rightSidebar` 对齐;不复用也不扩写 `windows/tauri/src/features/ai/` 那套编辑器标签页实现(#440 已判定它不作为参考,#957 负责清理)。入口加在活动栏(`plugin-activity-rail.tsx`),命令是 `workbench.showAgent`。关闭面板只是隐藏:只有功能开关(`settings.agentPanel.enabled`)关掉或项目切换时才停止 Agent,所以隐藏面板不会打断正在运行的一轮。连接和设置按窗口一份,`stores/agent-connection-service.ts` 是模块级单例,面板与供应商设置存在 `settings.agentPanel` 并由 `normalizeAgentPanelSettings` 归一化,服务商 Key 进系统安全存储(键名 `agent-provider-api-key/`),不写设置文件。
- **Windows 启动配置对齐 macOS**:`services/agent-launch.ts` 复刻 `AppModel.agentLaunchConfiguration(agentID:)` 的规则——订阅模式不携带供应商,且只有 `codex-acp` 能用订阅;API Key 模式需要 endpoint 和 Key,缺哪一项就报对应原因;自定义 Agent 必须有可执行文件。失败原因用 `AgentLaunchFailure` 枚举返回,由界面按当前语言翻译,Rust 或 Tauri 的原始错误不会直接给用户看。自定义参数按 macOS 的“一行一个”解析(`agentArguments`),不要按空格切分:带空格的参数会被拆成两个。
- **Windows 的能力门禁**:`config/backend-capabilities.ts` 的 `agent` 改成 `true`,因为 Windows 已经有 Agent 后端。同样被判成 agent 的老命令(`acp_*`、`codex_*`、`ai_provider`、`_chat`、`get_available_agents`)改判给新增的 `aiChat` 能力并保持 `false`:它们在 Windows 上没有后端,直接翻 `agent` 只会把“待开发”提示变成运行期报错。#957 删掉 `features/ai` 后 `aiChat` 可以一起删。
- - **Windows 的安装进度与超时**:`windows/tauri/src-tauri/src/platform.rs` 的 `platform_invoke` 同时接受 `gitEvents` 和 `agentEvents` 两个通道,并按事件的 `kind` 分派:Git 执行事件走前者,`agentInstallProgress` 走后者。适配器安装用用户本机 npm,可能下载几分钟,所以这类请求不写 `timeoutMilliseconds`:信封默认的 30 秒会在下载中途取消它,而共享 host 自己已有 15 分钟的安装上限,macOS 同样不给 `agent.*` 设信封超时。
+ - **Windows 的安装进度与超时**:`windows/tauri/src-tauri/src/platform.rs` 的 `platform_invoke` 同时接受 `gitEvents` 和 `agentEvents` 两个通道,并按事件的 `kind` 分派:Git 执行事件走前者,`agentInstallProgress` 走后者。适配器安装用用户本机 npm,可能下载几分钟,所以这类请求不写 `timeoutMilliseconds`:信封默认的 30 秒会在下载中途取消它,而共享 host 自己已有 15 分钟的安装上限,macOS 同样不给 `agent.*` 设信封超时。设置页的 CLI 安装/升级与配置文本导入调用的是同一组共享命令(`agent.installCli`、`agent.parseProviderConfiguration`),界面只做表单、校验与结果展示:升级按钮只在 host 明确给出可更新来源(`canUpdate`)或完全没有检测到 CLI 时出现,未知来源只显示手动指引;配置导入只读 endpoint、模型与协议,不搬运密钥,协议不支持或与所选适配器不匹配时报错而不是猜一个协议。
- **Windows 的历史标注存在平台 store**:收藏、本地标题和“已移除”是 Lithe 自己的标注,写在平台 store(`agent-history.json`),键为“Agent ID + 工作区路径”,绝不写进发行包或 Agent 的会话文件。macOS 用的是偏好存储(`MacAgentHistoryPersistence` 的 `lithe.agent-history.v1.`),两端都只存标注、不复制转录。每次修改前先重读再合并保存,避免两个窗口互相覆盖对方的标注;筛选、搜索和批量操作只作用于当前可见行。导出前先按需重放未打开的会话(这类加载不打开标签页),全部加载成功后才写用户选定的文件:取消保存对话框不写文件,超过 32 MiB 直接报错,任何一步失败都不产出半份记录。
- **Key 和模型的传法**:API Key 模式的适配器通过 ACP `gateway` 登录,Key 经 stdio 传给 Agent,请求头按协议选择:Responses 协议用 `Authorization: Bearer`,Anthropic 协议用 `x-api-key`。模型按适配器分别传:Codex 用 `CODEX_CONFIG`,Claude 用 `ANTHROPIC_MODEL`。服务商配置里的"模型"必须传给 Agent:实测某个网关禁用了 Codex 的默认模型,不传模型时 Agent 只会回复一条网关报错。
- **设置放在面板里,只有 Agent 管理一页**:Agent 的开关、预检清单(Node、npm、CLI、适配器、本机配置)、适配器和 CLI 的一键安装都在 Agent 面板右上角的设置视图里,不进全局设置窗口。布局仿照 Codeg 和 CC GUI:左侧图标栏,右侧标题加分段切换各个 Agent。
@@ -127,7 +127,7 @@ npm 的进度选项只面向终端,HTTP 日志通常在请求完成后才输
代价:
-- Windows 面板已覆盖会话、消息流、工具证据与权限确认、供应商与订阅切换、上下文用量和额度;历史已支持筛选、搜索、复制会话 ID、收藏、重命名、单条与批量移除及恢复,标注存在平台 store 并按项目与 Agent 隔离,也支持把选中的会话导出成一个 Markdown 文件;独立历史页(独立页面而非面板内列表)仍未实现。共享 host 在 Windows 上的适配器启动和进程树回收还没有真机端到端验收。进程树回收本身已用受控假适配器(`windows/tauri/src-tauri/src/bin/fake_acp_adapter.rs`)验证:它会派生一个继承 stdout 的孙进程,模拟 codex-acp 的 app-server,只杀外层进程会留下它并占住输出管道;普通 Rust 测试由此确认“关闭连接”和“销毁窗口”两条路径都回收了整棵树。退出与窗口销毁并发的时序仍由注册表计数和可控 gate 测试覆盖,不用真实 Agent 复现。
+- Windows 面板已覆盖会话、消息流、工具证据与权限确认、供应商与订阅切换、上下文用量和额度;历史已支持筛选、搜索、复制会话 ID、收藏、重命名、单条与批量移除及恢复,标注存在平台 store 并按项目与 Agent 隔离,也支持把选中的会话导出成一个 Markdown 文件;设置页已支持适配器安装/卸载、按 host 给出的安装来源做 CLI 安装与升级(显示 host 验证过的版本,安装器警告单独展示)、粘贴 Codex TOML / Claude JSON 导入供应商,输入框支持从系统拖拽文件与 `@` 打开项目文件选择器。独立历史页(独立页面而非面板内列表)、本机 CLI 配置文件的一键读取、多供应商列表与取消关联仍未实现。共享 host 在 Windows 上的适配器启动和进程树回收还没有真机端到端验收。进程树回收本身已用受控假适配器(`windows/tauri/src-tauri/src/bin/fake_acp_adapter.rs`)验证:它会派生一个继承 stdout 的孙进程,模拟 codex-acp 的 app-server,只杀外层进程会留下它并占住输出管道;普通 Rust 测试由此确认“关闭连接”和“销毁窗口”两条路径都回收了整棵树。退出与窗口销毁并发的时序仍由注册表计数和可控 gate 测试覆盖,不用真实 Agent 复现。
- Rust C ABI 和 fixture 成为兼容面,两端界面仍要分别维护。
- 用户需要自行安装 Node.js,适配器可以在面板内安装。
- codex-acp 丢失取消时,最多等待十秒后需要用户重连;同一进程的其他会话也会断开。上游未持久化的最后片段可能无法完整回放,界面保留旧记录用于诊断,不能保证 Agent 保存了未完成轮次。
@@ -138,8 +138,8 @@ npm 的进度选项只面向终端,HTTP 日志通常在请求完成后才输
- `./.agents/skills/write-stable-tests/scripts/test-stability-macos.sh -- --filter AgentBrandIconResourceTests`:临时安装包布局覆盖 Codex/Claude 图标加载、资源包和图标缺失时安全回退、缓存隔离,比较读取前后的文件清单与内容,确认不改写发行资源。
- 订阅新增测试覆盖旧配置兼容、显式登录、已有账号、认证通知顺序、登录取消和超时、账号变更、额度真实窗口/缺失值/多 bucket、仅查询不发送 prompt,以及临时失败保留旧值。Linux 已运行 Agent Host 的逐测试计时套件;macOS Swift 编译、真实账号登录及深浅主题/窄宽布局仍须在目标环境验证,不将代码存在等同于运行验证。
-- 真实账号验收:先用 API Key 对话,空闲切换 Codex 订阅,确认请求使用本机账号;未登录时确认打开面板不会启动浏览器,点击登录与取消正确;上下文右侧额度每分钟更新,断网后灰显,换账号后旧值清除,关闭项目不残留探测进程。Claude 不出现订阅入口。Windows 面板已接入连接、供应商与订阅切换、上下文用量和额度,仍需在 Windows 上用真实适配器做一次端到端验收(本机 Node/npm 探测、`.cmd` shim 启动、进程树回收与完整对话)。
-- `bun test src/features/agent/`(Windows 面板的纯函数:用量与额度解析、会话配置、转录归约、权限队列、文件引用、安装进度事件、历史标注与筛选排序、导出用的重放与 Markdown 渲染,以及 `agent.*` 响应解析)与 `bun test src/platform`(`agent_open`/`agent_send`/`agent_close` 的原生命令路由)。
+- 真实账号验收:先用 API Key 对话,空闲切换 Codex 订阅,确认请求使用本机账号;未登录时确认打开面板不会启动浏览器,点击登录与取消正确;上下文右侧额度每分钟更新,断网后灰显,换账号后旧值清除,关闭项目不残留探测进程。Claude 不出现订阅入口。Windows 面板已接入连接、供应商与订阅切换、上下文用量和额度,仍需在 Windows 上用真实适配器做一次端到端验收(本机 Node/npm 探测、`.cmd` shim 启动、进程树回收与完整对话),并在真实窗口里验证 CLI 升级、配置导入、拖拽与 `@` 引用。
+- `bun test src/features/agent/`(Windows 面板的纯函数:用量与额度解析、会话配置、转录归约、权限队列、文件引用、安装进度事件与 `agent.installCli` 结果解析、CLI 配置导入映射与协议校验、`@` 引用匹配与移除、历史标注与筛选排序、导出用的重放与 Markdown 渲染,以及 `agent.*` 响应解析)与 `bun test src/platform`(`agent_open`/`agent_send`/`agent_close` 的原生命令路由)。
- `cargo test --manifest-path windows/tauri/src-tauri/Cargo.toml platform::`(信封期限与事件通道分派:`agent.install`/`agent.installCli` 不带 30 秒期限,`agentInstallProgress` 只发给 agent 通道,Git 执行事件只发给 git 通道)
- `cargo test -p lithe-agent-host --manifest-path rust/Cargo.toml`
diff --git a/docs/development/platform-parity-matrix.csv b/docs/development/platform-parity-matrix.csv
index c5620a292..fde1bc52f 100644
--- a/docs/development/platform-parity-matrix.csv
+++ b/docs/development/platform-parity-matrix.csv
@@ -9,9 +9,9 @@ agent-context-usage,AI,Agent 对话,Agent 上下文用量:输入框圆环百
agent-acp-conversation,AI,Agent 对话,可选 ACP Agent 对话:API Key 网关登录、项目会话与历史恢复、模型/权限/思考配置、不依赖凭据的本机默认模型同步、旧默认模型按上游推荐修复与品牌模型图标、CC GUI 风格可搜索模型与权限弹出面板、工具详情与权限、停止确认和进程清理,已实现,待验证,部分实现,待验证,Agent,macOS + Codex + API Key:验证发消息前的上游配置选项和确认;无自定义服务商或 API Key 时确认仍显示本机顶层默认模型、保留密钥缺失错误;修改本机默认模型后重连,连续新建空会话确认不再使用旧导入模型;配置不在上游目录中的旧模型时确认新会话使用目录内的上游推荐值、拒绝与超时不显示假成功且 Codex 模型菜单显示 OpenAI 标志;验证工具输入/输出/文件/变更、权限拒绝、停止确认及超时重连、历史加载失败保留记录;临时项目跑通读文件→修改→执行测试→追问,复用文档观察和脏缓冲区保存保护。检查模型搜索(名称/ID/分组、空结果)、品牌图标/选中状态、无模型说明、权限说明、思考/速度子菜单、确认/失败与会话切换关闭弹出面板;检查项目切换、退出清理,以及 CC GUI 风格空态、搜索、标签、可调整输入区和配置菜单在窄宽面板及深浅主题下的布局。Windows 面板已覆盖消息流、工具证据与权限确认、会话创建与恢复、Agent 上报的配置选项和停止确认;可搜索模型/权限独立面板与本机默认模型同步尚未实现,Windows 与 Claude 端到端仍待验证。 安装包环境下打开 Agent 面板,确认 Codex/Claude 图标从 Contents/Resources 加载且不依赖构建目录;资源包或图标缺失时显示默认图标而不闪退,读取前后包内文件清单和内容不变。,,macos/Sources/LitheAgentConversationModule; macos/Sources/Lithe/Views/Agent; rust/lithe-agent-host; macos/Tests/LitheTests/AgentBrandIconResourceTests.swift,rust/lithe-agent-host; windows/tauri/src-tauri/src/agent.rs; windows/tauri/src/features/agent/components/agent-panel.tsx; windows/tauri/src/features/agent/components/agent-transcript.tsx; windows/tauri/src/features/agent/components/agent-composer.tsx; windows/tauri/src/features/agent/stores/agent-connection.model.ts
agent-tool-activity-timeline,AI,Agent 对话,连续 Agent 工具调用(执行命令、列文件、读文件与编辑等)合并为默认折叠的时间线卡片,汇总总数与失败/中断/完成进度,并逐条展开输入、输出与文件位置,已实现,待验证,未实现,不适用,Agent,macOS:让真实 Agent 连续列文件、读取文件、执行命令和编辑,确认相邻工具调用只显示一张默认折叠的时间线卡片;展开后每条状态、完整输入、输出和文件位置仍可查看,进行中、全部完成、失败和中断时汇总正确。用 Agent 文字说明和新一轮用户消息隔开工具调用,确认保留文字顺序且不会跨边界合并;搜索工具输入、输出或文件路径时自动展开匹配组;覆盖文件路径仅由 diff 内容上报、未出现在工具标题、输入输出或 locations 中的情况,历史恢复结果与实时流一致。在窄宽面板及深浅主题检查长标题截断、悬停全文、列表滚动和详情可读性。Windows Agent 对话 UI 尚未实现。,,macos/Sources/Lithe/Views/Agent/AgentTranscriptView.swift; macos/Sources/Lithe/Views/Agent/AgentToolGroupView.swift; macos/Tests/LitheTests/AgentConversationPresentationTests.swift,windows/tauri/src/features
agent-turn-statistics,AI,Agent 对话,发送后实时计时、每轮结束固定耗时、Agent 上报的输入/输出 token 与缓存/推理用量详情,已实现,待验证,部分实现,待验证,Agent,macOS:在真实 Agent 会话中确认发送后计时包含建会话、加载历史、工具和权限等待;正常完成、取消确认、请求失败和断连后停止计时,下一轮仍可看到上一轮统计。并发会话切换验证耗时与用量隔离;上报 usage 时显示准确输入/输出并悬停查看总量、缓存和推理值,缺失时只显示耗时。历史回放不编造统计,上下文占用与订阅额度不作为输入/输出 token。检查深浅主题和窄宽面板。Windows 共享 Host 已透传可选 usage,对话 UI 尚未接入。,,macos/Sources/Lithe/Views/Agent/AgentTurnStatisticsView.swift; macos/Sources/Lithe/Views/Agent/AgentTranscriptView.swift; macos/Sources/LitheAgentConversationModule/Application/AgentTurnStatistics.swift; macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift; macos/Tests/LitheTests/AgentConversationPresentationTests.swift,rust/lithe-agent-host/src/prompt.rs; rust/lithe-agent-host/src/tests.rs; shared/fixtures/agent/acp-events-v1.json
-agent-file-references,AI,Agent 对话,从 Finder 或项目树拖入文件到 Agent 输入框,文件选择器、可移除标签、多文件去重和数量限制、文件引用随消息发送及失败保留草稿,已实现,待验证,部分实现,待验证,Agent,macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统或项目树原生拖放尚未接入,Claude 端到端待验证。,,macos/Sources/Lithe/Views/Agent/AgentComposerView.swift; macos/Sources/Lithe/Views/Agent/AgentFileReferenceList.swift; macos/Sources/LitheAgentConversationModule/Application/AgentFileReference.swift; macos/Tests/LitheTests/AgentFileReferenceTests.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift; rust/lithe-agent-host/src/tests.rs,rust/lithe-agent-host/src/prompt.rs; shared/fixtures/agent/acp-events-v1.json; windows/tauri/src/features/agent/components/agent-composer.tsx; windows/tauri/src/features/agent/types/agent-file-uri.ts
-agent-management,AI,Agent 对话,Agent 管理:面板内设置、Node.js/npm 检测、预检清单、ACP 适配器与 Agent CLI 一键安装或升级、一键获取本机 CLI 配置、实时下载数据量/速度/耗时与等待提示、按 CLI 安装来源更新、验证实际升级结果并区分成功警告与失败,已实现,待验证,部分实现,待验证,Agent,在 macOS 打开 Agent 面板右上角的设置:确认显示 Node.js 与 npm 版本,Node 缺失或版本过低时只提示;一键安装、取消安装、更新与卸载 Codex 适配器;CLI 过旧时确认显示 npm/Homebrew/原生来源并通过原安装器升级,npm 环境不匹配或未知来源时只显示手动指引,升级后确认实际 PATH 中的版本达到要求;模拟安装器先下载失败再重试成功但返回非零,确认版本确实提升时显示成功与折叠警告日志,版本未变/仍旧/丢失、取消和超时继续按原语义处理;确认下载数据量、速度、耗时实时更新且未知总量不显示百分比,取消/失败/完成后进度清除;一键获取本机 Codex 配置后在 Agent 面板对话,且提交信息所用服务商不变;Windows 面板已接入 Node.js/npm 检测、预检问题、适配器安装(含下载进度)与卸载;取消安装、CLI 升级与本机 CLI 配置导入尚未接入。,,macos/Sources/Lithe/Views/Agent/AgentPanelSettingsView.swift; macos/Sources/Lithe/Application/Features/AgentManagementFeatureModel.swift; rust/lithe-agent-host/src/install.rs; rust/lithe-core/src/agent/mod.rs; rust/lithe-agent-host/src/npm-progress.mjs; rust/lithe-agent-host/src/cli_update.rs; macos/Tests/LitheTests/AgentManagementFeatureModelTests.swift,rust/lithe-core/src/agent/mod.rs; windows/tauri/src/features/agent/components/agent-settings-section.tsx; windows/tauri/src/features/agent/services/agent-management.ts; windows/tauri/src-tauri/src/platform.rs
-agent-provider-configuration,AI,Agent 对话,Agent 供应商管理:读取本机配置、自定义 Codex/Claude 配置文本、添加与编辑、安全保存密钥、启用与重连、确认删除及取消关联,已实现,待验证,部分实现,待验证,Agent,macOS:验证本机 Codex/Claude 配置读取、刷新与取消关联;添加自定义 TOML/auth JSON 或 Claude settings JSON,检查格式化、输入直引号不被自动替换、弯引号 TOML 错误在保存按钮上方始终可见、非法配置和缺失密钥、取消不保存、编辑保持 ID、删除确认及安全存储失败不丢失配置。在一个窗口保存并等待连接关闭期间,由另一窗口删除或修改同一供应商,确认旧保存失败且不恢复/覆盖供应商、密钥或 Agent 关联。检查窄宽面板、深浅主题、中英文本。空闲切换后确认请求到所选地址和模型;未发送空会话切换或意外退出后重新创建,不向旧 ID 加载,首条消息与文件只发送一次;已有消息、上游列出或成功加载的会话继续恢复,历史加载失败保留记录;正在响应、加载、权限待处理或配置确认时拒绝切换。提交服务商选择不被添加/启用改变。CLI 文件与 bundle 不被写入。Windows 面板当前是单一供应商表单:endpoint、模型、密钥安全存储与重连;导入本机配置文本、多供应商列表与取消关联尚未实现。,,macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift; macos/Sources/Lithe/Platform/MacOS/UI/MacConfigurationTextEditor.swift; macos/Sources/Lithe/Application/Features/AgentProviderConfiguration.swift; macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift; macos/Tests/LitheTests/AgentProviderConfigurationTests.swift; macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift,rust/lithe-core/src/ai/agent_configuration.rs; shared/fixtures/agent/provider-configuration-v1.json; windows/tauri/src/features/agent/components/agent-settings-section.tsx; windows/tauri/src/features/agent/services/agent-launch.ts
+agent-file-references,AI,Agent 对话,从 Finder 或项目树拖入文件到 Agent 输入框,文件选择器、可移除标签、多文件去重和数量限制、文件引用随消息发送及失败保留草稿,已实现,待验证,已实现,待验证,Agent,macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统拖拽文件到输入区与在输入框输入 @ 打开选择器都已接入(两者共用同一套去重、数量上限与可移除标签,选中后把 @query 文本替换为引用标签),真实拖放体验与 Claude 端到端仍待验证。,,macos/Sources/Lithe/Views/Agent/AgentComposerView.swift; macos/Sources/Lithe/Views/Agent/AgentFileReferenceList.swift; macos/Sources/LitheAgentConversationModule/Application/AgentFileReference.swift; macos/Tests/LitheTests/AgentFileReferenceTests.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift; rust/lithe-agent-host/src/tests.rs,rust/lithe-agent-host/src/prompt.rs; shared/fixtures/agent/acp-events-v1.json; windows/tauri/src/features/agent/components/agent-composer.tsx; windows/tauri/src/features/agent/types/agent-file-uri.ts; windows/tauri/src/features/agent/services/agent-composer-mentions.ts; windows/tauri/src/features/file-system/utils/file-system-dropped-paths.ts
+agent-management,AI,Agent 对话,Agent 管理:面板内设置、Node.js/npm 检测、预检清单、ACP 适配器与 Agent CLI 一键安装或升级、一键获取本机 CLI 配置、实时下载数据量/速度/耗时与等待提示、按 CLI 安装来源更新、验证实际升级结果并区分成功警告与失败,已实现,待验证,部分实现,待验证,Agent,在 macOS 打开 Agent 面板右上角的设置:确认显示 Node.js 与 npm 版本,Node 缺失或版本过低时只提示;一键安装、取消安装、更新与卸载 Codex 适配器;CLI 过旧时确认显示 npm/Homebrew/原生来源并通过原安装器升级,npm 环境不匹配或未知来源时只显示手动指引,升级后确认实际 PATH 中的版本达到要求;模拟安装器先下载失败再重试成功但返回非零,确认版本确实提升时显示成功与折叠警告日志,版本未变/仍旧/丢失、取消和超时继续按原语义处理;确认下载数据量、速度、耗时实时更新且未知总量不显示百分比,取消/失败/完成后进度清除;一键获取本机 Codex 配置后在 Agent 面板对话,且提交信息所用服务商不变;Windows 面板已接入 Node.js/npm 检测、预检问题、适配器安装(含下载进度)与卸载;CLI 的安装与升级也已接入:只有 host 明确给出可更新来源(canUpdate)或完全没有检测到 CLI 时才显示按钮,升级后显示 host 验证过的版本,安装器报错但版本确实提升时单独一行展示警告,未知来源只显示手动指引;取消安装、预检清单与本机 CLI 配置文件的一键读取尚未接入。,,macos/Sources/Lithe/Views/Agent/AgentPanelSettingsView.swift; macos/Sources/Lithe/Application/Features/AgentManagementFeatureModel.swift; rust/lithe-agent-host/src/install.rs; rust/lithe-core/src/agent/mod.rs; rust/lithe-agent-host/src/npm-progress.mjs; rust/lithe-agent-host/src/cli_update.rs; macos/Tests/LitheTests/AgentManagementFeatureModelTests.swift,rust/lithe-core/src/agent/mod.rs; windows/tauri/src/features/agent/components/agent-settings-section.tsx; windows/tauri/src/features/agent/services/agent-management.ts; windows/tauri/src/features/agent/hooks/use-agent-management.ts; windows/tauri/src-tauri/src/platform.rs
+agent-provider-configuration,AI,Agent 对话,Agent 供应商管理:读取本机配置、自定义 Codex/Claude 配置文本、添加与编辑、安全保存密钥、启用与重连、确认删除及取消关联,已实现,待验证,部分实现,待验证,Agent,macOS:验证本机 Codex/Claude 配置读取、刷新与取消关联;添加自定义 TOML/auth JSON 或 Claude settings JSON,检查格式化、输入直引号不被自动替换、弯引号 TOML 错误在保存按钮上方始终可见、非法配置和缺失密钥、取消不保存、编辑保持 ID、删除确认及安全存储失败不丢失配置。在一个窗口保存并等待连接关闭期间,由另一窗口删除或修改同一供应商,确认旧保存失败且不恢复/覆盖供应商、密钥或 Agent 关联。检查窄宽面板、深浅主题、中英文本。空闲切换后确认请求到所选地址和模型;未发送空会话切换或意外退出后重新创建,不向旧 ID 加载,首条消息与文件只发送一次;已有消息、上游列出或成功加载的会话继续恢复,历史加载失败保留记录;正在响应、加载、权限待处理或配置确认时拒绝切换。提交服务商选择不被添加/启用改变。CLI 文件与 bundle 不被写入。Windows 面板当前是单一供应商表单:endpoint、模型、密钥安全存储与重连;粘贴配置文本导入已接入:由共享 Rust 解析出 endpoint、模型与协议后填入表单,只读元数据、不搬运密钥,chatCompletions 或与所选适配器协议不匹配时明确报错;一键读取本机 CLI 配置文件、多供应商列表与取消关联尚未实现。,,macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift; macos/Sources/Lithe/Platform/MacOS/UI/MacConfigurationTextEditor.swift; macos/Sources/Lithe/Application/Features/AgentProviderConfiguration.swift; macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift; macos/Tests/LitheTests/AgentProviderConfigurationTests.swift; macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift,rust/lithe-core/src/ai/agent_configuration.rs; shared/fixtures/agent/provider-configuration-v1.json; windows/tauri/src/features/agent/components/agent-settings-section.tsx; windows/tauri/src/features/agent/services/agent-launch.ts; windows/tauri/src/features/agent/services/agent-provider-import.ts
agent-codex-subscription,AI,Agent 对话,Codex 供应商选择官方订阅:复用本机账号、显式浏览器登录与取消、API Key 模式切换及旧配置兼容,已实现,待验证,已实现,待验证,Agent,macOS:已有 Codex ChatGPT 登录直接对话;未登录只显示登录按钮,点击才打开浏览器,取消与超时清理进程;API Key 与订阅空闲切换后验证实际计费来源,不携带旧 key/URL/模型、不改提交服务商;忙碌时拒绝切换,账号变化后断开;老配置仍走 API Key,Claude 不显示订阅选项。真实账号与 macOS UI 待验证;Windows 面板已接入订阅模式切换、登录与取消,订阅启动不携带供应商;真实账号验证待完成。,,macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift; macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift; macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift; macos/Tests/LitheTests/AgentProviderConfigurationTests.swift; rust/lithe-agent-host/src/subscription.rs,rust/lithe-agent-host/src/lib.rs; shared/fixtures/agent/acp-events-v1.json; windows/tauri/src-tauri/src/agent.rs; windows/tauri/src/features/agent/components/agent-settings-section.tsx; windows/tauri/src/features/agent/components/agent-panel.tsx; windows/tauri/src/features/agent/services/agent-launch.ts
agent-subscription-quota,AI,Agent 对话,Codex 订阅额度:上下文右侧紧凑显示、可见时定时刷新、悬停多窗口与重置时间、未知与过期状态及账号隔离,已实现,待验证,已实现,待验证,Agent,macOS:订阅会话上下文右侧显示额度,悬停查看窗口时长/已用百分比/重置和更新时间;真实账号验证可见且活跃时每分钟刷新、隐藏不轮询、单连接不并发探测,查询不创建会话或模型请求;周窗口作为 primary 仍显示 7d,缺失不报 0%;网络失败保留并灰显旧值,账号不匹配清除;API Key 模式不查询。窄宽面板、深浅主题、关闭面板/项目和取消时确认探测进程清理。Windows 面板在上下文用量右侧紧凑显示额度,文档可见时每分钟刷新,悬停列出各窗口与重置时间,过期快照灰显且 API Key 模式不查询;真实账号验证待完成。,,macos/Sources/Lithe/Views/Agent/AgentSubscriptionQuotaView.swift; macos/Sources/LitheAgentConversationModule/Application/AgentSubscriptionQuota.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift; rust/lithe-agent-host/src/subscription/tests.rs,rust/lithe-agent-host/src/subscription.rs; shared/fixtures/agent/acp-events-v1.json; windows/tauri/src-tauri/src/agent.rs; windows/tauri/src/features/agent/components/agent-usage-panel.tsx; windows/tauri/src/features/agent/components/agent-panel.tsx; windows/tauri/src/features/agent/types/agent.types.ts
workspace-open-switch,工作区,工作区生命周期,打开工作区与切换项目,已实现,待验证,已实现,待验证,Workspace,打开多个项目并在项目之间切换,确认当前项目、文件树和编辑器状态正确。,,macos/Sources/Lithe/Views/Workspace; macos/Sources/Lithe/Services/Workspace,windows/tauri/src/features/workspace
diff --git a/docs/development/platform-parity-matrix.md b/docs/development/platform-parity-matrix.md
index 8d8fb6ceb..8f6de2d85 100644
--- a/docs/development/platform-parity-matrix.md
+++ b/docs/development/platform-parity-matrix.md
@@ -6,7 +6,7 @@
- 盘点状态:initial-static-inventory(根据 macOS Views/Application/Services、Windows features/extensions 和共享契约的代码入口进行初版盘点;未替代真实运行验收。)
- 功能项:115
- macOS:实现:✅ 101 已实现,🟡 3 部分实现,❌ 7 未实现,🧩 4 平台专属;验证:✔️ 0 已验证,🔍 104 待验证,— 11 不适用
-- Windows:实现:✅ 102 已实现,🟡 9 部分实现,❌ 3 未实现,🧩 1 平台专属;验证:✔️ 0 已验证,🔍 111 待验证,— 4 不适用
+- Windows:实现:✅ 103 已实现,🟡 8 部分实现,❌ 3 未实现,🧩 1 平台专属;验证:✔️ 0 已验证,🔍 111 待验证,— 4 不适用
## 实现状态定义
@@ -44,9 +44,9 @@
| Agent 对话 | **可选 ACP Agent 对话:API Key 网关登录、项目会话与历史恢复、模型/权限/思考配置、不依赖凭据的本机默认模型同步、旧默认模型按上游推荐修复与品牌模型图标、CC GUI 风格可搜索模型与权限弹出面板、工具详情与权限、停止确认和进程清理**
agent-acp-conversation | ✅ 已实现
🔍 待验证
`macos/Sources/LitheAgentConversationModule`、`macos/Sources/Lithe/Views/Agent`、`rust/lithe-agent-host`、`macos/Tests/LitheTests/AgentBrandIconResourceTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-agent-host`、`windows/tauri/src-tauri/src/agent.rs`、`windows/tauri/src/features/agent/components/agent-panel.tsx`、`windows/tauri/src/features/agent/components/agent-transcript.tsx`、`windows/tauri/src/features/agent/components/agent-composer.tsx`、`windows/tauri/src/features/agent/stores/agent-connection.model.ts` | Agent | macOS + Codex + API Key:验证发消息前的上游配置选项和确认;无自定义服务商或 API Key 时确认仍显示本机顶层默认模型、保留密钥缺失错误;修改本机默认模型后重连,连续新建空会话确认不再使用旧导入模型;配置不在上游目录中的旧模型时确认新会话使用目录内的上游推荐值、拒绝与超时不显示假成功且 Codex 模型菜单显示 OpenAI 标志;验证工具输入/输出/文件/变更、权限拒绝、停止确认及超时重连、历史加载失败保留记录;临时项目跑通读文件→修改→执行测试→追问,复用文档观察和脏缓冲区保存保护。检查模型搜索(名称/ID/分组、空结果)、品牌图标/选中状态、无模型说明、权限说明、思考/速度子菜单、确认/失败与会话切换关闭弹出面板;检查项目切换、退出清理,以及 CC GUI 风格空态、搜索、标签、可调整输入区和配置菜单在窄宽面板及深浅主题下的布局。Windows 面板已覆盖消息流、工具证据与权限确认、会话创建与恢复、Agent 上报的配置选项和停止确认;可搜索模型/权限独立面板与本机默认模型同步尚未实现,Windows 与 Claude 端到端仍待验证。 安装包环境下打开 Agent 面板,确认 Codex/Claude 图标从 Contents/Resources 加载且不依赖构建目录;资源包或图标缺失时显示默认图标而不闪退,读取前后包内文件清单和内容不变。 | |
| Agent 对话 | **连续 Agent 工具调用(执行命令、列文件、读文件与编辑等)合并为默认折叠的时间线卡片,汇总总数与失败/中断/完成进度,并逐条展开输入、输出与文件位置**
agent-tool-activity-timeline | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentTranscriptView.swift`、`macos/Sources/Lithe/Views/Agent/AgentToolGroupView.swift`、`macos/Tests/LitheTests/AgentConversationPresentationTests.swift` | ❌ 未实现
— 不适用
`windows/tauri/src/features` | Agent | macOS:让真实 Agent 连续列文件、读取文件、执行命令和编辑,确认相邻工具调用只显示一张默认折叠的时间线卡片;展开后每条状态、完整输入、输出和文件位置仍可查看,进行中、全部完成、失败和中断时汇总正确。用 Agent 文字说明和新一轮用户消息隔开工具调用,确认保留文字顺序且不会跨边界合并;搜索工具输入、输出或文件路径时自动展开匹配组;覆盖文件路径仅由 diff 内容上报、未出现在工具标题、输入输出或 locations 中的情况,历史恢复结果与实时流一致。在窄宽面板及深浅主题检查长标题截断、悬停全文、列表滚动和详情可读性。Windows Agent 对话 UI 尚未实现。 | |
| Agent 对话 | **发送后实时计时、每轮结束固定耗时、Agent 上报的输入/输出 token 与缓存/推理用量详情**
agent-turn-statistics | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentTurnStatisticsView.swift`、`macos/Sources/Lithe/Views/Agent/AgentTranscriptView.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentTurnStatistics.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift`、`macos/Tests/LitheTests/AgentConversationPresentationTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-agent-host/src/prompt.rs`、`rust/lithe-agent-host/src/tests.rs`、`shared/fixtures/agent/acp-events-v1.json` | Agent | macOS:在真实 Agent 会话中确认发送后计时包含建会话、加载历史、工具和权限等待;正常完成、取消确认、请求失败和断连后停止计时,下一轮仍可看到上一轮统计。并发会话切换验证耗时与用量隔离;上报 usage 时显示准确输入/输出并悬停查看总量、缓存和推理值,缺失时只显示耗时。历史回放不编造统计,上下文占用与订阅额度不作为输入/输出 token。检查深浅主题和窄宽面板。Windows 共享 Host 已透传可选 usage,对话 UI 尚未接入。 | |
-| Agent 对话 | **从 Finder 或项目树拖入文件到 Agent 输入框,文件选择器、可移除标签、多文件去重和数量限制、文件引用随消息发送及失败保留草稿**
agent-file-references | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentComposerView.swift`、`macos/Sources/Lithe/Views/Agent/AgentFileReferenceList.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentFileReference.swift`、`macos/Tests/LitheTests/AgentFileReferenceTests.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift`、`rust/lithe-agent-host/src/tests.rs` | 🟡 部分实现
🔍 待验证
`rust/lithe-agent-host/src/prompt.rs`、`shared/fixtures/agent/acp-events-v1.json`、`windows/tauri/src/features/agent/components/agent-composer.tsx`、`windows/tauri/src/features/agent/types/agent-file-uri.ts` | Agent | macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统或项目树原生拖放尚未接入,Claude 端到端待验证。 | |
-| Agent 对话 | **Agent 管理:面板内设置、Node.js/npm 检测、预检清单、ACP 适配器与 Agent CLI 一键安装或升级、一键获取本机 CLI 配置、实时下载数据量/速度/耗时与等待提示、按 CLI 安装来源更新、验证实际升级结果并区分成功警告与失败**
agent-management | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentPanelSettingsView.swift`、`macos/Sources/Lithe/Application/Features/AgentManagementFeatureModel.swift`、`rust/lithe-agent-host/src/install.rs`、`rust/lithe-core/src/agent/mod.rs`、`rust/lithe-agent-host/src/npm-progress.mjs`、`rust/lithe-agent-host/src/cli_update.rs`、`macos/Tests/LitheTests/AgentManagementFeatureModelTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-core/src/agent/mod.rs`、`windows/tauri/src/features/agent/components/agent-settings-section.tsx`、`windows/tauri/src/features/agent/services/agent-management.ts`、`windows/tauri/src-tauri/src/platform.rs` | Agent | 在 macOS 打开 Agent 面板右上角的设置:确认显示 Node.js 与 npm 版本,Node 缺失或版本过低时只提示;一键安装、取消安装、更新与卸载 Codex 适配器;CLI 过旧时确认显示 npm/Homebrew/原生来源并通过原安装器升级,npm 环境不匹配或未知来源时只显示手动指引,升级后确认实际 PATH 中的版本达到要求;模拟安装器先下载失败再重试成功但返回非零,确认版本确实提升时显示成功与折叠警告日志,版本未变/仍旧/丢失、取消和超时继续按原语义处理;确认下载数据量、速度、耗时实时更新且未知总量不显示百分比,取消/失败/完成后进度清除;一键获取本机 Codex 配置后在 Agent 面板对话,且提交信息所用服务商不变;Windows 面板已接入 Node.js/npm 检测、预检问题、适配器安装(含下载进度)与卸载;取消安装、CLI 升级与本机 CLI 配置导入尚未接入。 | |
-| Agent 对话 | **Agent 供应商管理:读取本机配置、自定义 Codex/Claude 配置文本、添加与编辑、安全保存密钥、启用与重连、确认删除及取消关联**
agent-provider-configuration | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift`、`macos/Sources/Lithe/Platform/MacOS/UI/MacConfigurationTextEditor.swift`、`macos/Sources/Lithe/Application/Features/AgentProviderConfiguration.swift`、`macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift`、`macos/Tests/LitheTests/AgentProviderConfigurationTests.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-core/src/ai/agent_configuration.rs`、`shared/fixtures/agent/provider-configuration-v1.json`、`windows/tauri/src/features/agent/components/agent-settings-section.tsx`、`windows/tauri/src/features/agent/services/agent-launch.ts` | Agent | macOS:验证本机 Codex/Claude 配置读取、刷新与取消关联;添加自定义 TOML/auth JSON 或 Claude settings JSON,检查格式化、输入直引号不被自动替换、弯引号 TOML 错误在保存按钮上方始终可见、非法配置和缺失密钥、取消不保存、编辑保持 ID、删除确认及安全存储失败不丢失配置。在一个窗口保存并等待连接关闭期间,由另一窗口删除或修改同一供应商,确认旧保存失败且不恢复/覆盖供应商、密钥或 Agent 关联。检查窄宽面板、深浅主题、中英文本。空闲切换后确认请求到所选地址和模型;未发送空会话切换或意外退出后重新创建,不向旧 ID 加载,首条消息与文件只发送一次;已有消息、上游列出或成功加载的会话继续恢复,历史加载失败保留记录;正在响应、加载、权限待处理或配置确认时拒绝切换。提交服务商选择不被添加/启用改变。CLI 文件与 bundle 不被写入。Windows 面板当前是单一供应商表单:endpoint、模型、密钥安全存储与重连;导入本机配置文本、多供应商列表与取消关联尚未实现。 | |
+| Agent 对话 | **从 Finder 或项目树拖入文件到 Agent 输入框,文件选择器、可移除标签、多文件去重和数量限制、文件引用随消息发送及失败保留草稿**
agent-file-references | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentComposerView.swift`、`macos/Sources/Lithe/Views/Agent/AgentFileReferenceList.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentFileReference.swift`、`macos/Tests/LitheTests/AgentFileReferenceTests.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift`、`rust/lithe-agent-host/src/tests.rs` | ✅ 已实现
🔍 待验证
`rust/lithe-agent-host/src/prompt.rs`、`shared/fixtures/agent/acp-events-v1.json`、`windows/tauri/src/features/agent/components/agent-composer.tsx`、`windows/tauri/src/features/agent/types/agent-file-uri.ts`、`windows/tauri/src/features/agent/services/agent-composer-mentions.ts`、`windows/tauri/src/features/file-system/utils/file-system-dropped-paths.ts` | Agent | macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统拖拽文件到输入区与在输入框输入 @ 打开选择器都已接入(两者共用同一套去重、数量上限与可移除标签,选中后把 @query 文本替换为引用标签),真实拖放体验与 Claude 端到端仍待验证。 | |
+| Agent 对话 | **Agent 管理:面板内设置、Node.js/npm 检测、预检清单、ACP 适配器与 Agent CLI 一键安装或升级、一键获取本机 CLI 配置、实时下载数据量/速度/耗时与等待提示、按 CLI 安装来源更新、验证实际升级结果并区分成功警告与失败**
agent-management | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentPanelSettingsView.swift`、`macos/Sources/Lithe/Application/Features/AgentManagementFeatureModel.swift`、`rust/lithe-agent-host/src/install.rs`、`rust/lithe-core/src/agent/mod.rs`、`rust/lithe-agent-host/src/npm-progress.mjs`、`rust/lithe-agent-host/src/cli_update.rs`、`macos/Tests/LitheTests/AgentManagementFeatureModelTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-core/src/agent/mod.rs`、`windows/tauri/src/features/agent/components/agent-settings-section.tsx`、`windows/tauri/src/features/agent/services/agent-management.ts`、`windows/tauri/src/features/agent/hooks/use-agent-management.ts`、`windows/tauri/src-tauri/src/platform.rs` | Agent | 在 macOS 打开 Agent 面板右上角的设置:确认显示 Node.js 与 npm 版本,Node 缺失或版本过低时只提示;一键安装、取消安装、更新与卸载 Codex 适配器;CLI 过旧时确认显示 npm/Homebrew/原生来源并通过原安装器升级,npm 环境不匹配或未知来源时只显示手动指引,升级后确认实际 PATH 中的版本达到要求;模拟安装器先下载失败再重试成功但返回非零,确认版本确实提升时显示成功与折叠警告日志,版本未变/仍旧/丢失、取消和超时继续按原语义处理;确认下载数据量、速度、耗时实时更新且未知总量不显示百分比,取消/失败/完成后进度清除;一键获取本机 Codex 配置后在 Agent 面板对话,且提交信息所用服务商不变;Windows 面板已接入 Node.js/npm 检测、预检问题、适配器安装(含下载进度)与卸载;CLI 的安装与升级也已接入:只有 host 明确给出可更新来源(canUpdate)或完全没有检测到 CLI 时才显示按钮,升级后显示 host 验证过的版本,安装器报错但版本确实提升时单独一行展示警告,未知来源只显示手动指引;取消安装、预检清单与本机 CLI 配置文件的一键读取尚未接入。 | |
+| Agent 对话 | **Agent 供应商管理:读取本机配置、自定义 Codex/Claude 配置文本、添加与编辑、安全保存密钥、启用与重连、确认删除及取消关联**
agent-provider-configuration | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift`、`macos/Sources/Lithe/Platform/MacOS/UI/MacConfigurationTextEditor.swift`、`macos/Sources/Lithe/Application/Features/AgentProviderConfiguration.swift`、`macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift`、`macos/Tests/LitheTests/AgentProviderConfigurationTests.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-core/src/ai/agent_configuration.rs`、`shared/fixtures/agent/provider-configuration-v1.json`、`windows/tauri/src/features/agent/components/agent-settings-section.tsx`、`windows/tauri/src/features/agent/services/agent-launch.ts`、`windows/tauri/src/features/agent/services/agent-provider-import.ts` | Agent | macOS:验证本机 Codex/Claude 配置读取、刷新与取消关联;添加自定义 TOML/auth JSON 或 Claude settings JSON,检查格式化、输入直引号不被自动替换、弯引号 TOML 错误在保存按钮上方始终可见、非法配置和缺失密钥、取消不保存、编辑保持 ID、删除确认及安全存储失败不丢失配置。在一个窗口保存并等待连接关闭期间,由另一窗口删除或修改同一供应商,确认旧保存失败且不恢复/覆盖供应商、密钥或 Agent 关联。检查窄宽面板、深浅主题、中英文本。空闲切换后确认请求到所选地址和模型;未发送空会话切换或意外退出后重新创建,不向旧 ID 加载,首条消息与文件只发送一次;已有消息、上游列出或成功加载的会话继续恢复,历史加载失败保留记录;正在响应、加载、权限待处理或配置确认时拒绝切换。提交服务商选择不被添加/启用改变。CLI 文件与 bundle 不被写入。Windows 面板当前是单一供应商表单:endpoint、模型、密钥安全存储与重连;粘贴配置文本导入已接入:由共享 Rust 解析出 endpoint、模型与协议后填入表单,只读元数据、不搬运密钥,chatCompletions 或与所选适配器协议不匹配时明确报错;一键读取本机 CLI 配置文件、多供应商列表与取消关联尚未实现。 | |
| Agent 对话 | **Codex 供应商选择官方订阅:复用本机账号、显式浏览器登录与取消、API Key 模式切换及旧配置兼容**
agent-codex-subscription | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift`、`macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift`、`macos/Tests/LitheTests/AgentProviderConfigurationTests.swift`、`rust/lithe-agent-host/src/subscription.rs` | ✅ 已实现
🔍 待验证
`rust/lithe-agent-host/src/lib.rs`、`shared/fixtures/agent/acp-events-v1.json`、`windows/tauri/src-tauri/src/agent.rs`、`windows/tauri/src/features/agent/components/agent-settings-section.tsx`、`windows/tauri/src/features/agent/components/agent-panel.tsx`、`windows/tauri/src/features/agent/services/agent-launch.ts` | Agent | macOS:已有 Codex ChatGPT 登录直接对话;未登录只显示登录按钮,点击才打开浏览器,取消与超时清理进程;API Key 与订阅空闲切换后验证实际计费来源,不携带旧 key/URL/模型、不改提交服务商;忙碌时拒绝切换,账号变化后断开;老配置仍走 API Key,Claude 不显示订阅选项。真实账号与 macOS UI 待验证;Windows 面板已接入订阅模式切换、登录与取消,订阅启动不携带供应商;真实账号验证待完成。 | |
| Agent 对话 | **Codex 订阅额度:上下文右侧紧凑显示、可见时定时刷新、悬停多窗口与重置时间、未知与过期状态及账号隔离**
agent-subscription-quota | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentSubscriptionQuotaView.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentSubscriptionQuota.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift`、`rust/lithe-agent-host/src/subscription/tests.rs` | ✅ 已实现
🔍 待验证
`rust/lithe-agent-host/src/subscription.rs`、`shared/fixtures/agent/acp-events-v1.json`、`windows/tauri/src-tauri/src/agent.rs`、`windows/tauri/src/features/agent/components/agent-usage-panel.tsx`、`windows/tauri/src/features/agent/components/agent-panel.tsx`、`windows/tauri/src/features/agent/types/agent.types.ts` | Agent | macOS:订阅会话上下文右侧显示额度,悬停查看窗口时长/已用百分比/重置和更新时间;真实账号验证可见且活跃时每分钟刷新、隐藏不轮询、单连接不并发探测,查询不创建会话或模型请求;周窗口作为 primary 仍显示 7d,缺失不报 0%;网络失败保留并灰显旧值,账号不匹配清除;API Key 模式不查询。窄宽面板、深浅主题、关闭面板/项目和取消时确认探测进程清理。Windows 面板在上下文用量右侧紧凑显示额度,文档可见时每分钟刷新,悬停列出各窗口与重置时间,过期快照灰显且 API Key 模式不查询;真实账号验证待完成。 | |
| AI 提交信息 | **Provider 配置与提交信息生成**
ai-commit-generation | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Application/Features/CommitWorkflowCoordinator.swift`、`macos/Sources/Lithe/Platform/MacOS/AI` | ✅ 已实现
🔍 待验证
`windows/tauri/src/features/git/services/ai-commit-service.ts`、`shared/contracts/ai-commit.md` | Git / AI | 用相同 diff、规则和 Provider 配置比较请求计划、取消、错误和生成文本。 | |
diff --git a/shared/platform-feature-matrix.json b/shared/platform-feature-matrix.json
index 951cd44ad..0d683d775 100644
--- a/shared/platform-feature-matrix.json
+++ b/shared/platform-feature-matrix.json
@@ -361,13 +361,15 @@
"rust/lithe-agent-host/src/prompt.rs",
"shared/fixtures/agent/acp-events-v1.json",
"windows/tauri/src/features/agent/components/agent-composer.tsx",
- "windows/tauri/src/features/agent/types/agent-file-uri.ts"
+ "windows/tauri/src/features/agent/types/agent-file-uri.ts",
+ "windows/tauri/src/features/agent/services/agent-composer-mentions.ts",
+ "windows/tauri/src/features/file-system/utils/file-system-dropped-paths.ts"
],
- "implementationStatus": "partial",
+ "implementationStatus": "implemented",
"verificationStatus": "pending"
},
"owner": "Agent",
- "verification": "macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统或项目树原生拖放尚未接入,Claude 端到端待验证。"
+ "verification": "macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统拖拽文件到输入区与在输入框输入 @ 打开选择器都已接入(两者共用同一套去重、数量上限与可移除标签,选中后把 @query 文本替换为引用标签),真实拖放体验与 Claude 端到端仍待验证。"
},
{
"id": "agent-management",
@@ -392,13 +394,14 @@
"rust/lithe-core/src/agent/mod.rs",
"windows/tauri/src/features/agent/components/agent-settings-section.tsx",
"windows/tauri/src/features/agent/services/agent-management.ts",
+ "windows/tauri/src/features/agent/hooks/use-agent-management.ts",
"windows/tauri/src-tauri/src/platform.rs"
],
"implementationStatus": "partial",
"verificationStatus": "pending"
},
"owner": "Agent",
- "verification": "在 macOS 打开 Agent 面板右上角的设置:确认显示 Node.js 与 npm 版本,Node 缺失或版本过低时只提示;一键安装、取消安装、更新与卸载 Codex 适配器;CLI 过旧时确认显示 npm/Homebrew/原生来源并通过原安装器升级,npm 环境不匹配或未知来源时只显示手动指引,升级后确认实际 PATH 中的版本达到要求;模拟安装器先下载失败再重试成功但返回非零,确认版本确实提升时显示成功与折叠警告日志,版本未变/仍旧/丢失、取消和超时继续按原语义处理;确认下载数据量、速度、耗时实时更新且未知总量不显示百分比,取消/失败/完成后进度清除;一键获取本机 Codex 配置后在 Agent 面板对话,且提交信息所用服务商不变;Windows 面板已接入 Node.js/npm 检测、预检问题、适配器安装(含下载进度)与卸载;取消安装、CLI 升级与本机 CLI 配置导入尚未接入。"
+ "verification": "在 macOS 打开 Agent 面板右上角的设置:确认显示 Node.js 与 npm 版本,Node 缺失或版本过低时只提示;一键安装、取消安装、更新与卸载 Codex 适配器;CLI 过旧时确认显示 npm/Homebrew/原生来源并通过原安装器升级,npm 环境不匹配或未知来源时只显示手动指引,升级后确认实际 PATH 中的版本达到要求;模拟安装器先下载失败再重试成功但返回非零,确认版本确实提升时显示成功与折叠警告日志,版本未变/仍旧/丢失、取消和超时继续按原语义处理;确认下载数据量、速度、耗时实时更新且未知总量不显示百分比,取消/失败/完成后进度清除;一键获取本机 Codex 配置后在 Agent 面板对话,且提交信息所用服务商不变;Windows 面板已接入 Node.js/npm 检测、预检问题、适配器安装(含下载进度)与卸载;CLI 的安装与升级也已接入:只有 host 明确给出可更新来源(canUpdate)或完全没有检测到 CLI 时才显示按钮,升级后显示 host 验证过的版本,安装器报错但版本确实提升时单独一行展示警告,未知来源只显示手动指引;取消安装、预检清单与本机 CLI 配置文件的一键读取尚未接入。"
},
{
"id": "agent-provider-configuration",
@@ -425,11 +428,12 @@
"rust/lithe-core/src/ai/agent_configuration.rs",
"shared/fixtures/agent/provider-configuration-v1.json",
"windows/tauri/src/features/agent/components/agent-settings-section.tsx",
- "windows/tauri/src/features/agent/services/agent-launch.ts"
+ "windows/tauri/src/features/agent/services/agent-launch.ts",
+ "windows/tauri/src/features/agent/services/agent-provider-import.ts"
]
},
"owner": "Agent",
- "verification": "macOS:验证本机 Codex/Claude 配置读取、刷新与取消关联;添加自定义 TOML/auth JSON 或 Claude settings JSON,检查格式化、输入直引号不被自动替换、弯引号 TOML 错误在保存按钮上方始终可见、非法配置和缺失密钥、取消不保存、编辑保持 ID、删除确认及安全存储失败不丢失配置。在一个窗口保存并等待连接关闭期间,由另一窗口删除或修改同一供应商,确认旧保存失败且不恢复/覆盖供应商、密钥或 Agent 关联。检查窄宽面板、深浅主题、中英文本。空闲切换后确认请求到所选地址和模型;未发送空会话切换或意外退出后重新创建,不向旧 ID 加载,首条消息与文件只发送一次;已有消息、上游列出或成功加载的会话继续恢复,历史加载失败保留记录;正在响应、加载、权限待处理或配置确认时拒绝切换。提交服务商选择不被添加/启用改变。CLI 文件与 bundle 不被写入。Windows 面板当前是单一供应商表单:endpoint、模型、密钥安全存储与重连;导入本机配置文本、多供应商列表与取消关联尚未实现。"
+ "verification": "macOS:验证本机 Codex/Claude 配置读取、刷新与取消关联;添加自定义 TOML/auth JSON 或 Claude settings JSON,检查格式化、输入直引号不被自动替换、弯引号 TOML 错误在保存按钮上方始终可见、非法配置和缺失密钥、取消不保存、编辑保持 ID、删除确认及安全存储失败不丢失配置。在一个窗口保存并等待连接关闭期间,由另一窗口删除或修改同一供应商,确认旧保存失败且不恢复/覆盖供应商、密钥或 Agent 关联。检查窄宽面板、深浅主题、中英文本。空闲切换后确认请求到所选地址和模型;未发送空会话切换或意外退出后重新创建,不向旧 ID 加载,首条消息与文件只发送一次;已有消息、上游列出或成功加载的会话继续恢复,历史加载失败保留记录;正在响应、加载、权限待处理或配置确认时拒绝切换。提交服务商选择不被添加/启用改变。CLI 文件与 bundle 不被写入。Windows 面板当前是单一供应商表单:endpoint、模型、密钥安全存储与重连;粘贴配置文本导入已接入:由共享 Rust 解析出 endpoint、模型与协议后填入表单,只读元数据、不搬运密钥,chatCompletions 或与所选适配器协议不匹配时明确报错;一键读取本机 CLI 配置文件、多供应商列表与取消关联尚未实现。"
},
{
"id": "agent-codex-subscription",
From 20614882d033c987611bc23b86391fa81706d39d Mon Sep 17 00:00:00 2001
From: puppy_1 <13323021675@163.com>
Date: Thu, 1 Oct 2026 14:47:53 +0800
Subject: [PATCH 14/16] =?UTF-8?q?fix(windows):=20Agent=20=E8=BE=93?=
=?UTF-8?q?=E5=85=A5=E6=A1=86=E6=8E=A5=E6=94=B6=E7=B3=BB=E7=BB=9F=E4=B8=8E?=
=?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=A0=91=E7=9A=84=E7=9C=9F=E5=AE=9E=E6=8B=96?=
=?UTF-8?q?=E6=94=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Tauri 默认开启原生拖放(dragDropEnabled),WebView2 收到系统文件时只发原生事件,
DOM 的 HTML5 drop 拿不到文件;项目树用鼠标事件模拟拖动,也不会触发 drop。
此前输入区只挂了 HTML5 onDrop,真实窗口里两条路径都不会添加引用。
- 拖放路由新增 agent 目标:落点在 data-agent-file-drop-target 内时,原生拖放与
项目树松手都派发 lithe-agent-file-drop 事件,交给输入区同一个 attach 入口
(复用去重与 32 个上限);文件夹不作为引用
- 新增路由与派发单测;矩阵 agent-file-references 证据与说明、Agent Note 同步
---
...026-09-25-shared-acp-agent-conversation.md | 2 +-
docs/development/platform-parity-matrix.csv | 2 +-
docs/development/platform-parity-matrix.md | 2 +-
shared/platform-feature-matrix.json | 4 +-
.../agent/components/agent-composer.tsx | 32 +++++++++-
.../hooks/use-file-explorer-drag-drop.ts | 16 +++++
.../hooks/use-file-system-folder-drop.ts | 9 +++
.../utils/file-system-drop-controller.test.ts | 61 +++++++++++++++++++
.../utils/file-system-drop-controller.ts | 29 ++++++++-
9 files changed, 149 insertions(+), 8 deletions(-)
create mode 100644 windows/tauri/src/features/file-system/utils/file-system-drop-controller.test.ts
diff --git a/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md b/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md
index d2cc5818f..3efd0a86e 100644
--- a/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md
+++ b/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md
@@ -127,7 +127,7 @@ npm 的进度选项只面向终端,HTTP 日志通常在请求完成后才输
代价:
-- Windows 面板已覆盖会话、消息流、工具证据与权限确认、供应商与订阅切换、上下文用量和额度;历史已支持筛选、搜索、复制会话 ID、收藏、重命名、单条与批量移除及恢复,标注存在平台 store 并按项目与 Agent 隔离,也支持把选中的会话导出成一个 Markdown 文件;设置页已支持适配器安装/卸载、按 host 给出的安装来源做 CLI 安装与升级(显示 host 验证过的版本,安装器警告单独展示)、粘贴 Codex TOML / Claude JSON 导入供应商,输入框支持从系统拖拽文件与 `@` 打开项目文件选择器。独立历史页(独立页面而非面板内列表)、本机 CLI 配置文件的一键读取、多供应商列表与取消关联仍未实现。共享 host 在 Windows 上的适配器启动和进程树回收还没有真机端到端验收。进程树回收本身已用受控假适配器(`windows/tauri/src-tauri/src/bin/fake_acp_adapter.rs`)验证:它会派生一个继承 stdout 的孙进程,模拟 codex-acp 的 app-server,只杀外层进程会留下它并占住输出管道;普通 Rust 测试由此确认“关闭连接”和“销毁窗口”两条路径都回收了整棵树。退出与窗口销毁并发的时序仍由注册表计数和可控 gate 测试覆盖,不用真实 Agent 复现。
+- Windows 面板已覆盖会话、消息流、工具证据与权限确认、供应商与订阅切换、上下文用量和额度;历史已支持筛选、搜索、复制会话 ID、收藏、重命名、单条与批量移除及恢复,标注存在平台 store 并按项目与 Agent 隔离,也支持把选中的会话导出成一个 Markdown 文件;设置页已支持适配器安装/卸载、按 host 给出的安装来源做 CLI 安装与升级(显示 host 验证过的版本,安装器警告单独展示)、粘贴 Codex TOML / Claude JSON 导入供应商,输入框支持从系统、项目树拖入文件与 `@` 打开项目文件选择器。Windows 的拖入不能只靠 HTML5 `drop`:Tauri 默认开启原生拖放(`dragDropEnabled`),WebView2 收到系统文件时只发原生事件、DOM 拿不到文件;项目树又是用鼠标事件模拟拖动。所以两条路径都按落点找到带 `data-agent-file-drop-target` 的输入区,再派发 `lithe-agent-file-drop` 事件,输入区用同一个 `attach` 入口处理,去重与 32 个上限只有一份。正确做法是给新的拖入目标加落点标记并接这个事件;不要为了拿到 HTML5 拖放而关闭 `dragDropEnabled`,那会让文件夹打开项目和终端拖入失效。独立历史页(独立页面而非面板内列表)、本机 CLI 配置文件的一键读取、多供应商列表与取消关联仍未实现。共享 host 在 Windows 上的适配器启动和进程树回收还没有真机端到端验收。进程树回收本身已用受控假适配器(`windows/tauri/src-tauri/src/bin/fake_acp_adapter.rs`)验证:它会派生一个继承 stdout 的孙进程,模拟 codex-acp 的 app-server,只杀外层进程会留下它并占住输出管道;普通 Rust 测试由此确认“关闭连接”和“销毁窗口”两条路径都回收了整棵树。退出与窗口销毁并发的时序仍由注册表计数和可控 gate 测试覆盖,不用真实 Agent 复现。
- Rust C ABI 和 fixture 成为兼容面,两端界面仍要分别维护。
- 用户需要自行安装 Node.js,适配器可以在面板内安装。
- codex-acp 丢失取消时,最多等待十秒后需要用户重连;同一进程的其他会话也会断开。上游未持久化的最后片段可能无法完整回放,界面保留旧记录用于诊断,不能保证 Agent 保存了未完成轮次。
diff --git a/docs/development/platform-parity-matrix.csv b/docs/development/platform-parity-matrix.csv
index fcde2631d..519fa2709 100644
--- a/docs/development/platform-parity-matrix.csv
+++ b/docs/development/platform-parity-matrix.csv
@@ -9,7 +9,7 @@ agent-context-usage,AI,Agent 对话,Agent 上下文用量:输入框圆环百
agent-acp-conversation,AI,Agent 对话,可选 ACP Agent 对话:API Key 网关登录、项目会话与历史恢复、模型/权限/思考配置、不依赖凭据的本机默认模型同步、旧默认模型按上游推荐修复与品牌模型图标、CC GUI 风格可搜索模型与权限弹出面板、工具详情与权限、停止确认和进程清理,已实现,待验证,部分实现,待验证,Agent,macOS + Codex + API Key:验证发消息前的上游配置选项和确认;无自定义服务商或 API Key 时确认仍显示本机顶层默认模型、保留密钥缺失错误;修改本机默认模型后重连,连续新建空会话确认不再使用旧导入模型;配置不在上游目录中的旧模型时确认新会话使用目录内的上游推荐值、拒绝与超时不显示假成功且 Codex 模型菜单显示 OpenAI 标志;验证工具输入/输出/文件/变更、权限拒绝、停止确认及超时重连、历史加载失败保留记录;临时项目跑通读文件→修改→执行测试→追问,复用文档观察和脏缓冲区保存保护。检查模型搜索(名称/ID/分组、空结果)、品牌图标/选中状态、无模型说明、权限说明、思考/速度子菜单、确认/失败与会话切换关闭弹出面板;检查项目切换、退出清理,以及 CC GUI 风格空态、搜索、标签、可调整输入区和配置菜单在窄宽面板及深浅主题下的布局。Windows 面板已覆盖消息流、工具证据与权限确认、会话创建与恢复、Agent 上报的配置选项和停止确认;可搜索模型/权限独立面板与本机默认模型同步尚未实现,Windows 与 Claude 端到端仍待验证。 安装包环境下打开 Agent 面板,确认 Codex/Claude 图标从 Contents/Resources 加载且不依赖构建目录;资源包或图标缺失时显示默认图标而不闪退,读取前后包内文件清单和内容不变。,,macos/Sources/LitheAgentConversationModule; macos/Sources/Lithe/Views/Agent; rust/lithe-agent-host; macos/Tests/LitheTests/AgentBrandIconResourceTests.swift,rust/lithe-agent-host; windows/tauri/src-tauri/src/agent.rs; windows/tauri/src/features/agent/components/agent-panel.tsx; windows/tauri/src/features/agent/components/agent-transcript.tsx; windows/tauri/src/features/agent/components/agent-composer.tsx; windows/tauri/src/features/agent/stores/agent-connection.model.ts
agent-tool-activity-timeline,AI,Agent 对话,连续 Agent 工具调用(执行命令、列文件、读文件与编辑等)合并为默认折叠的时间线卡片,汇总总数与失败/中断/完成进度,并逐条展开输入、输出与文件位置,已实现,待验证,未实现,不适用,Agent,macOS:让真实 Agent 连续列文件、读取文件、执行命令和编辑,确认相邻工具调用只显示一张默认折叠的时间线卡片;展开后每条状态、完整输入、输出和文件位置仍可查看,进行中、全部完成、失败和中断时汇总正确。用 Agent 文字说明和新一轮用户消息隔开工具调用,确认保留文字顺序且不会跨边界合并;搜索工具输入、输出或文件路径时自动展开匹配组;覆盖文件路径仅由 diff 内容上报、未出现在工具标题、输入输出或 locations 中的情况,历史恢复结果与实时流一致。在窄宽面板及深浅主题检查长标题截断、悬停全文、列表滚动和详情可读性。Windows Agent 对话 UI 尚未实现。,,macos/Sources/Lithe/Views/Agent/AgentTranscriptView.swift; macos/Sources/Lithe/Views/Agent/AgentToolGroupView.swift; macos/Tests/LitheTests/AgentConversationPresentationTests.swift,windows/tauri/src/features
agent-turn-statistics,AI,Agent 对话,发送后实时计时、每轮结束固定耗时、Agent 上报的输入/输出 token 与缓存/推理用量详情,已实现,待验证,部分实现,待验证,Agent,macOS:在真实 Agent 会话中确认发送后计时包含建会话、加载历史、工具和权限等待;正常完成、取消确认、请求失败和断连后停止计时,下一轮仍可看到上一轮统计。并发会话切换验证耗时与用量隔离;上报 usage 时显示准确输入/输出并悬停查看总量、缓存和推理值,缺失时只显示耗时。历史回放不编造统计,上下文占用与订阅额度不作为输入/输出 token。检查深浅主题和窄宽面板。Windows 共享 Host 已透传可选 usage,对话 UI 尚未接入。,,macos/Sources/Lithe/Views/Agent/AgentTurnStatisticsView.swift; macos/Sources/Lithe/Views/Agent/AgentTranscriptView.swift; macos/Sources/LitheAgentConversationModule/Application/AgentTurnStatistics.swift; macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift; macos/Tests/LitheTests/AgentConversationPresentationTests.swift,rust/lithe-agent-host/src/prompt.rs; rust/lithe-agent-host/src/tests.rs; shared/fixtures/agent/acp-events-v1.json
-agent-file-references,AI,Agent 对话,从 Finder 或项目树拖入文件到 Agent 输入框,文件选择器、可移除标签、多文件去重和数量限制、文件引用随消息发送及失败保留草稿,已实现,待验证,已实现,待验证,Agent,macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统拖拽文件到输入区与在输入框输入 @ 打开选择器都已接入(两者共用同一套去重、数量上限与可移除标签,选中后把 @query 文本替换为引用标签),真实拖放体验与 Claude 端到端仍待验证。,,macos/Sources/Lithe/Views/Agent/AgentComposerView.swift; macos/Sources/Lithe/Views/Agent/AgentFileReferenceList.swift; macos/Sources/LitheAgentConversationModule/Application/AgentFileReference.swift; macos/Tests/LitheTests/AgentFileReferenceTests.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift; rust/lithe-agent-host/src/tests.rs,rust/lithe-agent-host/src/prompt.rs; shared/fixtures/agent/acp-events-v1.json; windows/tauri/src/features/agent/components/agent-composer.tsx; windows/tauri/src/features/agent/types/agent-file-uri.ts; windows/tauri/src/features/agent/services/agent-composer-mentions.ts; windows/tauri/src/features/file-system/utils/file-system-dropped-paths.ts
+agent-file-references,AI,Agent 对话,从 Finder 或项目树拖入文件到 Agent 输入框,文件选择器、可移除标签、多文件去重和数量限制、文件引用随消息发送及失败保留草稿,已实现,待验证,已实现,待验证,Agent,macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统、项目树拖入文件到输入区与在输入框输入 @ 打开选择器都已接入(系统拖放走 Tauri 原生拖放事件、项目树走鼠标拖动,都按落点路由到输入区;两者共用同一套去重、数量上限与可移除标签,选中后把 @query 文本替换为引用标签),真实拖放体验与 Claude 端到端仍待验证。,,macos/Sources/Lithe/Views/Agent/AgentComposerView.swift; macos/Sources/Lithe/Views/Agent/AgentFileReferenceList.swift; macos/Sources/LitheAgentConversationModule/Application/AgentFileReference.swift; macos/Tests/LitheTests/AgentFileReferenceTests.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift; rust/lithe-agent-host/src/tests.rs,rust/lithe-agent-host/src/prompt.rs; shared/fixtures/agent/acp-events-v1.json; windows/tauri/src/features/agent/components/agent-composer.tsx; windows/tauri/src/features/agent/types/agent-file-uri.ts; windows/tauri/src/features/agent/services/agent-composer-mentions.ts; windows/tauri/src/features/file-system/utils/file-system-drop-controller.ts; windows/tauri/src/features/file-system/utils/file-system-drop-controller.test.ts; windows/tauri/src/features/file-system/utils/file-system-dropped-paths.ts
agent-management,AI,Agent 对话,Agent 管理:面板内设置、Node.js/npm 检测、预检清单、ACP 适配器与 Agent CLI 一键安装或升级、一键获取本机 CLI 配置、实时下载数据量/速度/耗时与等待提示、按 CLI 安装来源更新、验证实际升级结果并区分成功警告与失败,已实现,待验证,部分实现,待验证,Agent,在 macOS 打开 Agent 面板右上角的设置:确认显示 Node.js 与 npm 版本,Node 缺失或版本过低时只提示;一键安装、取消安装、更新与卸载 Codex 适配器;CLI 过旧时确认显示 npm/Homebrew/原生来源并通过原安装器升级,npm 环境不匹配或未知来源时只显示手动指引,升级后确认实际 PATH 中的版本达到要求;模拟安装器先下载失败再重试成功但返回非零,确认版本确实提升时显示成功与折叠警告日志,版本未变/仍旧/丢失、取消和超时继续按原语义处理;确认下载数据量、速度、耗时实时更新且未知总量不显示百分比,取消/失败/完成后进度清除;一键获取本机 Codex 配置后在 Agent 面板对话,且提交信息所用服务商不变;Windows 面板已接入 Node.js/npm 检测、预检问题、适配器安装(含下载进度)与卸载;CLI 的安装与升级也已接入:只有 host 明确给出可更新来源(canUpdate)或完全没有检测到 CLI 时才显示按钮,升级后显示 host 验证过的版本,安装器报错但版本确实提升时单独一行展示警告,未知来源只显示手动指引;取消安装、预检清单与本机 CLI 配置文件的一键读取尚未接入。,,macos/Sources/Lithe/Views/Agent/AgentPanelSettingsView.swift; macos/Sources/Lithe/Application/Features/AgentManagementFeatureModel.swift; rust/lithe-agent-host/src/install.rs; rust/lithe-core/src/agent/mod.rs; rust/lithe-agent-host/src/npm-progress.mjs; rust/lithe-agent-host/src/cli_update.rs; macos/Tests/LitheTests/AgentManagementFeatureModelTests.swift,rust/lithe-core/src/agent/mod.rs; windows/tauri/src/features/agent/components/agent-settings-section.tsx; windows/tauri/src/features/agent/services/agent-management.ts; windows/tauri/src/features/agent/hooks/use-agent-management.ts; windows/tauri/src-tauri/src/platform.rs
agent-provider-configuration,AI,Agent 对话,Agent 供应商管理:读取本机配置、自定义 Codex/Claude 配置文本、添加与编辑、安全保存密钥、启用与重连、确认删除及取消关联,已实现,待验证,部分实现,待验证,Agent,macOS:验证本机 Codex/Claude 配置读取、刷新与取消关联;添加自定义 TOML/auth JSON 或 Claude settings JSON,检查格式化、输入直引号不被自动替换、弯引号 TOML 错误在保存按钮上方始终可见、非法配置和缺失密钥、取消不保存、编辑保持 ID、删除确认及安全存储失败不丢失配置。在一个窗口保存并等待连接关闭期间,由另一窗口删除或修改同一供应商,确认旧保存失败且不恢复/覆盖供应商、密钥或 Agent 关联。检查窄宽面板、深浅主题、中英文本。空闲切换后确认请求到所选地址和模型;未发送空会话切换或意外退出后重新创建,不向旧 ID 加载,首条消息与文件只发送一次;已有消息、上游列出或成功加载的会话继续恢复,历史加载失败保留记录;正在响应、加载、权限待处理或配置确认时拒绝切换。提交服务商选择不被添加/启用改变。CLI 文件与 bundle 不被写入。Windows 面板当前是单一供应商表单:endpoint、模型、密钥安全存储与重连;粘贴配置文本导入已接入:由共享 Rust 解析出 endpoint、模型与协议后填入表单,只读元数据、不搬运密钥,chatCompletions 或与所选适配器协议不匹配时明确报错;一键读取本机 CLI 配置文件、多供应商列表与取消关联尚未实现。,,macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift; macos/Sources/Lithe/Platform/MacOS/UI/MacConfigurationTextEditor.swift; macos/Sources/Lithe/Application/Features/AgentProviderConfiguration.swift; macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift; macos/Tests/LitheTests/AgentProviderConfigurationTests.swift; macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift,rust/lithe-core/src/ai/agent_configuration.rs; shared/fixtures/agent/provider-configuration-v1.json; windows/tauri/src/features/agent/components/agent-settings-section.tsx; windows/tauri/src/features/agent/services/agent-launch.ts; windows/tauri/src/features/agent/services/agent-provider-import.ts
agent-codex-subscription,AI,Agent 对话,Codex 供应商选择官方订阅:复用本机账号、显式浏览器登录与取消、API Key 模式切换及旧配置兼容,已实现,待验证,已实现,待验证,Agent,macOS:已有 Codex ChatGPT 登录直接对话;未登录只显示登录按钮,点击才打开浏览器,取消与超时清理进程;API Key 与订阅空闲切换后验证实际计费来源,不携带旧 key/URL/模型、不改提交服务商;忙碌时拒绝切换,账号变化后断开;老配置仍走 API Key,Claude 不显示订阅选项。真实账号与 macOS UI 待验证;Windows 面板已接入订阅模式切换、登录与取消,订阅启动不携带供应商;真实账号验证待完成。,,macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift; macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift; macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift; macos/Tests/LitheTests/AgentProviderConfigurationTests.swift; rust/lithe-agent-host/src/subscription.rs,rust/lithe-agent-host/src/lib.rs; shared/fixtures/agent/acp-events-v1.json; windows/tauri/src-tauri/src/agent.rs; windows/tauri/src/features/agent/components/agent-settings-section.tsx; windows/tauri/src/features/agent/components/agent-panel.tsx; windows/tauri/src/features/agent/services/agent-launch.ts
diff --git a/docs/development/platform-parity-matrix.md b/docs/development/platform-parity-matrix.md
index ddda84444..6f2b63533 100644
--- a/docs/development/platform-parity-matrix.md
+++ b/docs/development/platform-parity-matrix.md
@@ -44,7 +44,7 @@
| Agent 对话 | **可选 ACP Agent 对话:API Key 网关登录、项目会话与历史恢复、模型/权限/思考配置、不依赖凭据的本机默认模型同步、旧默认模型按上游推荐修复与品牌模型图标、CC GUI 风格可搜索模型与权限弹出面板、工具详情与权限、停止确认和进程清理**
agent-acp-conversation | ✅ 已实现
🔍 待验证
`macos/Sources/LitheAgentConversationModule`、`macos/Sources/Lithe/Views/Agent`、`rust/lithe-agent-host`、`macos/Tests/LitheTests/AgentBrandIconResourceTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-agent-host`、`windows/tauri/src-tauri/src/agent.rs`、`windows/tauri/src/features/agent/components/agent-panel.tsx`、`windows/tauri/src/features/agent/components/agent-transcript.tsx`、`windows/tauri/src/features/agent/components/agent-composer.tsx`、`windows/tauri/src/features/agent/stores/agent-connection.model.ts` | Agent | macOS + Codex + API Key:验证发消息前的上游配置选项和确认;无自定义服务商或 API Key 时确认仍显示本机顶层默认模型、保留密钥缺失错误;修改本机默认模型后重连,连续新建空会话确认不再使用旧导入模型;配置不在上游目录中的旧模型时确认新会话使用目录内的上游推荐值、拒绝与超时不显示假成功且 Codex 模型菜单显示 OpenAI 标志;验证工具输入/输出/文件/变更、权限拒绝、停止确认及超时重连、历史加载失败保留记录;临时项目跑通读文件→修改→执行测试→追问,复用文档观察和脏缓冲区保存保护。检查模型搜索(名称/ID/分组、空结果)、品牌图标/选中状态、无模型说明、权限说明、思考/速度子菜单、确认/失败与会话切换关闭弹出面板;检查项目切换、退出清理,以及 CC GUI 风格空态、搜索、标签、可调整输入区和配置菜单在窄宽面板及深浅主题下的布局。Windows 面板已覆盖消息流、工具证据与权限确认、会话创建与恢复、Agent 上报的配置选项和停止确认;可搜索模型/权限独立面板与本机默认模型同步尚未实现,Windows 与 Claude 端到端仍待验证。 安装包环境下打开 Agent 面板,确认 Codex/Claude 图标从 Contents/Resources 加载且不依赖构建目录;资源包或图标缺失时显示默认图标而不闪退,读取前后包内文件清单和内容不变。 | |
| Agent 对话 | **连续 Agent 工具调用(执行命令、列文件、读文件与编辑等)合并为默认折叠的时间线卡片,汇总总数与失败/中断/完成进度,并逐条展开输入、输出与文件位置**
agent-tool-activity-timeline | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentTranscriptView.swift`、`macos/Sources/Lithe/Views/Agent/AgentToolGroupView.swift`、`macos/Tests/LitheTests/AgentConversationPresentationTests.swift` | ❌ 未实现
— 不适用
`windows/tauri/src/features` | Agent | macOS:让真实 Agent 连续列文件、读取文件、执行命令和编辑,确认相邻工具调用只显示一张默认折叠的时间线卡片;展开后每条状态、完整输入、输出和文件位置仍可查看,进行中、全部完成、失败和中断时汇总正确。用 Agent 文字说明和新一轮用户消息隔开工具调用,确认保留文字顺序且不会跨边界合并;搜索工具输入、输出或文件路径时自动展开匹配组;覆盖文件路径仅由 diff 内容上报、未出现在工具标题、输入输出或 locations 中的情况,历史恢复结果与实时流一致。在窄宽面板及深浅主题检查长标题截断、悬停全文、列表滚动和详情可读性。Windows Agent 对话 UI 尚未实现。 | |
| Agent 对话 | **发送后实时计时、每轮结束固定耗时、Agent 上报的输入/输出 token 与缓存/推理用量详情**
agent-turn-statistics | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentTurnStatisticsView.swift`、`macos/Sources/Lithe/Views/Agent/AgentTranscriptView.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentTurnStatistics.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift`、`macos/Tests/LitheTests/AgentConversationPresentationTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-agent-host/src/prompt.rs`、`rust/lithe-agent-host/src/tests.rs`、`shared/fixtures/agent/acp-events-v1.json` | Agent | macOS:在真实 Agent 会话中确认发送后计时包含建会话、加载历史、工具和权限等待;正常完成、取消确认、请求失败和断连后停止计时,下一轮仍可看到上一轮统计。并发会话切换验证耗时与用量隔离;上报 usage 时显示准确输入/输出并悬停查看总量、缓存和推理值,缺失时只显示耗时。历史回放不编造统计,上下文占用与订阅额度不作为输入/输出 token。检查深浅主题和窄宽面板。Windows 共享 Host 已透传可选 usage,对话 UI 尚未接入。 | |
-| Agent 对话 | **从 Finder 或项目树拖入文件到 Agent 输入框,文件选择器、可移除标签、多文件去重和数量限制、文件引用随消息发送及失败保留草稿**
agent-file-references | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentComposerView.swift`、`macos/Sources/Lithe/Views/Agent/AgentFileReferenceList.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentFileReference.swift`、`macos/Tests/LitheTests/AgentFileReferenceTests.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift`、`rust/lithe-agent-host/src/tests.rs` | ✅ 已实现
🔍 待验证
`rust/lithe-agent-host/src/prompt.rs`、`shared/fixtures/agent/acp-events-v1.json`、`windows/tauri/src/features/agent/components/agent-composer.tsx`、`windows/tauri/src/features/agent/types/agent-file-uri.ts`、`windows/tauri/src/features/agent/services/agent-composer-mentions.ts`、`windows/tauri/src/features/file-system/utils/file-system-dropped-paths.ts` | Agent | macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统拖拽文件到输入区与在输入框输入 @ 打开选择器都已接入(两者共用同一套去重、数量上限与可移除标签,选中后把 @query 文本替换为引用标签),真实拖放体验与 Claude 端到端仍待验证。 | |
+| Agent 对话 | **从 Finder 或项目树拖入文件到 Agent 输入框,文件选择器、可移除标签、多文件去重和数量限制、文件引用随消息发送及失败保留草稿**
agent-file-references | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentComposerView.swift`、`macos/Sources/Lithe/Views/Agent/AgentFileReferenceList.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentFileReference.swift`、`macos/Tests/LitheTests/AgentFileReferenceTests.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift`、`rust/lithe-agent-host/src/tests.rs` | ✅ 已实现
🔍 待验证
`rust/lithe-agent-host/src/prompt.rs`、`shared/fixtures/agent/acp-events-v1.json`、`windows/tauri/src/features/agent/components/agent-composer.tsx`、`windows/tauri/src/features/agent/types/agent-file-uri.ts`、`windows/tauri/src/features/agent/services/agent-composer-mentions.ts`、`windows/tauri/src/features/file-system/utils/file-system-drop-controller.ts`、`windows/tauri/src/features/file-system/utils/file-system-drop-controller.test.ts`、`windows/tauri/src/features/file-system/utils/file-system-dropped-paths.ts` | Agent | macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统、项目树拖入文件到输入区与在输入框输入 @ 打开选择器都已接入(系统拖放走 Tauri 原生拖放事件、项目树走鼠标拖动,都按落点路由到输入区;两者共用同一套去重、数量上限与可移除标签,选中后把 @query 文本替换为引用标签),真实拖放体验与 Claude 端到端仍待验证。 | |
| Agent 对话 | **Agent 管理:面板内设置、Node.js/npm 检测、预检清单、ACP 适配器与 Agent CLI 一键安装或升级、一键获取本机 CLI 配置、实时下载数据量/速度/耗时与等待提示、按 CLI 安装来源更新、验证实际升级结果并区分成功警告与失败**
agent-management | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentPanelSettingsView.swift`、`macos/Sources/Lithe/Application/Features/AgentManagementFeatureModel.swift`、`rust/lithe-agent-host/src/install.rs`、`rust/lithe-core/src/agent/mod.rs`、`rust/lithe-agent-host/src/npm-progress.mjs`、`rust/lithe-agent-host/src/cli_update.rs`、`macos/Tests/LitheTests/AgentManagementFeatureModelTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-core/src/agent/mod.rs`、`windows/tauri/src/features/agent/components/agent-settings-section.tsx`、`windows/tauri/src/features/agent/services/agent-management.ts`、`windows/tauri/src/features/agent/hooks/use-agent-management.ts`、`windows/tauri/src-tauri/src/platform.rs` | Agent | 在 macOS 打开 Agent 面板右上角的设置:确认显示 Node.js 与 npm 版本,Node 缺失或版本过低时只提示;一键安装、取消安装、更新与卸载 Codex 适配器;CLI 过旧时确认显示 npm/Homebrew/原生来源并通过原安装器升级,npm 环境不匹配或未知来源时只显示手动指引,升级后确认实际 PATH 中的版本达到要求;模拟安装器先下载失败再重试成功但返回非零,确认版本确实提升时显示成功与折叠警告日志,版本未变/仍旧/丢失、取消和超时继续按原语义处理;确认下载数据量、速度、耗时实时更新且未知总量不显示百分比,取消/失败/完成后进度清除;一键获取本机 Codex 配置后在 Agent 面板对话,且提交信息所用服务商不变;Windows 面板已接入 Node.js/npm 检测、预检问题、适配器安装(含下载进度)与卸载;CLI 的安装与升级也已接入:只有 host 明确给出可更新来源(canUpdate)或完全没有检测到 CLI 时才显示按钮,升级后显示 host 验证过的版本,安装器报错但版本确实提升时单独一行展示警告,未知来源只显示手动指引;取消安装、预检清单与本机 CLI 配置文件的一键读取尚未接入。 | |
| Agent 对话 | **Agent 供应商管理:读取本机配置、自定义 Codex/Claude 配置文本、添加与编辑、安全保存密钥、启用与重连、确认删除及取消关联**
agent-provider-configuration | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift`、`macos/Sources/Lithe/Platform/MacOS/UI/MacConfigurationTextEditor.swift`、`macos/Sources/Lithe/Application/Features/AgentProviderConfiguration.swift`、`macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift`、`macos/Tests/LitheTests/AgentProviderConfigurationTests.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-core/src/ai/agent_configuration.rs`、`shared/fixtures/agent/provider-configuration-v1.json`、`windows/tauri/src/features/agent/components/agent-settings-section.tsx`、`windows/tauri/src/features/agent/services/agent-launch.ts`、`windows/tauri/src/features/agent/services/agent-provider-import.ts` | Agent | macOS:验证本机 Codex/Claude 配置读取、刷新与取消关联;添加自定义 TOML/auth JSON 或 Claude settings JSON,检查格式化、输入直引号不被自动替换、弯引号 TOML 错误在保存按钮上方始终可见、非法配置和缺失密钥、取消不保存、编辑保持 ID、删除确认及安全存储失败不丢失配置。在一个窗口保存并等待连接关闭期间,由另一窗口删除或修改同一供应商,确认旧保存失败且不恢复/覆盖供应商、密钥或 Agent 关联。检查窄宽面板、深浅主题、中英文本。空闲切换后确认请求到所选地址和模型;未发送空会话切换或意外退出后重新创建,不向旧 ID 加载,首条消息与文件只发送一次;已有消息、上游列出或成功加载的会话继续恢复,历史加载失败保留记录;正在响应、加载、权限待处理或配置确认时拒绝切换。提交服务商选择不被添加/启用改变。CLI 文件与 bundle 不被写入。Windows 面板当前是单一供应商表单:endpoint、模型、密钥安全存储与重连;粘贴配置文本导入已接入:由共享 Rust 解析出 endpoint、模型与协议后填入表单,只读元数据、不搬运密钥,chatCompletions 或与所选适配器协议不匹配时明确报错;一键读取本机 CLI 配置文件、多供应商列表与取消关联尚未实现。 | |
| Agent 对话 | **Codex 供应商选择官方订阅:复用本机账号、显式浏览器登录与取消、API Key 模式切换及旧配置兼容**
agent-codex-subscription | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift`、`macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift`、`macos/Tests/LitheTests/AgentProviderConfigurationTests.swift`、`rust/lithe-agent-host/src/subscription.rs` | ✅ 已实现
🔍 待验证
`rust/lithe-agent-host/src/lib.rs`、`shared/fixtures/agent/acp-events-v1.json`、`windows/tauri/src-tauri/src/agent.rs`、`windows/tauri/src/features/agent/components/agent-settings-section.tsx`、`windows/tauri/src/features/agent/components/agent-panel.tsx`、`windows/tauri/src/features/agent/services/agent-launch.ts` | Agent | macOS:已有 Codex ChatGPT 登录直接对话;未登录只显示登录按钮,点击才打开浏览器,取消与超时清理进程;API Key 与订阅空闲切换后验证实际计费来源,不携带旧 key/URL/模型、不改提交服务商;忙碌时拒绝切换,账号变化后断开;老配置仍走 API Key,Claude 不显示订阅选项。真实账号与 macOS UI 待验证;Windows 面板已接入订阅模式切换、登录与取消,订阅启动不携带供应商;真实账号验证待完成。 | |
diff --git a/shared/platform-feature-matrix.json b/shared/platform-feature-matrix.json
index 12f35dc79..4e9b98eeb 100644
--- a/shared/platform-feature-matrix.json
+++ b/shared/platform-feature-matrix.json
@@ -363,13 +363,15 @@
"windows/tauri/src/features/agent/components/agent-composer.tsx",
"windows/tauri/src/features/agent/types/agent-file-uri.ts",
"windows/tauri/src/features/agent/services/agent-composer-mentions.ts",
+ "windows/tauri/src/features/file-system/utils/file-system-drop-controller.ts",
+ "windows/tauri/src/features/file-system/utils/file-system-drop-controller.test.ts",
"windows/tauri/src/features/file-system/utils/file-system-dropped-paths.ts"
],
"implementationStatus": "implemented",
"verificationStatus": "pending"
},
"owner": "Agent",
- "verification": "macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统拖拽文件到输入区与在输入框输入 @ 打开选择器都已接入(两者共用同一套去重、数量上限与可移除标签,选中后把 @query 文本替换为引用标签),真实拖放体验与 Claude 端到端仍待验证。"
+ "verification": "macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 面板提供项目文件搜索选择器、可移除标签、去重与数量上限;从系统、项目树拖入文件到输入区与在输入框输入 @ 打开选择器都已接入(系统拖放走 Tauri 原生拖放事件、项目树走鼠标拖动,都按落点路由到输入区;两者共用同一套去重、数量上限与可移除标签,选中后把 @query 文本替换为引用标签),真实拖放体验与 Claude 端到端仍待验证。"
},
{
"id": "agent-management",
diff --git a/windows/tauri/src/features/agent/components/agent-composer.tsx b/windows/tauri/src/features/agent/components/agent-composer.tsx
index d23550d46..477010cfa 100644
--- a/windows/tauri/src/features/agent/components/agent-composer.tsx
+++ b/windows/tauri/src/features/agent/components/agent-composer.tsx
@@ -1,5 +1,9 @@
-import { useCallback, useMemo, useState, type DragEvent } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState, type DragEvent } from "react";
import { useBufferStore } from "@/features/editor/stores/buffer.store";
+import {
+ AGENT_FILE_DROP_EVENT,
+ type AgentFileDropDetail,
+} from "@/features/file-system/utils/file-system-drop-controller";
import { extractDroppedFilePaths } from "@/features/file-system/utils/file-system-dropped-paths";
import { useFileSystemStore } from "@/features/file-system/stores/file-system.store";
import type { FileEntry } from "@/features/file-system/types/app.types";
@@ -114,6 +118,18 @@ export function AgentComposer({ conversation, canSend }: AgentComposerProps) {
[attach],
);
+ // OS drops in WebView2 and project-tree drags arrive as this event instead.
+ const rootRef = useRef(null);
+ useEffect(() => {
+ const root = rootRef.current;
+ if (root === null) return;
+ const handleRoutedDrop = (event: Event) => {
+ attach((event as CustomEvent).detail?.paths ?? []);
+ };
+ root.addEventListener(AGENT_FILE_DROP_EVENT, handleRoutedDrop);
+ return () => root.removeEventListener(AGENT_FILE_DROP_EVENT, handleRoutedDrop);
+ }, [attach]);
+
const handleDragEnter = useCallback((event: DragEvent) => {
if (!Array.from(event.dataTransfer.types).includes("Files")) return;
event.preventDefault();
@@ -131,7 +147,10 @@ export function AgentComposer({ conversation, canSend }: AgentComposerProps) {
const text = draft.trim();
if (text.length === 0 && files.length === 0) return;
try {
- agentConnection().send(text, files.map((file) => file.uri));
+ agentConnection().send(
+ text,
+ files.map((file) => file.uri),
+ );
setDraft("");
setFiles([]);
closePicker();
@@ -155,6 +174,8 @@ export function AgentComposer({ conversation, canSend }: AgentComposerProps) {
return (
event.preventDefault()}
@@ -288,7 +309,12 @@ export function AgentComposer({ conversation, canSend }: AgentComposerProps) {
{activeFilePath === null ? null : (
-