Release v1.61.0

This commit is contained in:
coso
2026-06-08 13:24:26 +08:00
parent 2a3b0a1861
commit 7bd374cb1e
306 changed files with 14013 additions and 5463 deletions
+29 -32
View File
@@ -1,53 +1,50 @@
## Lime v1.60.0
## Lime v1.61.0
<sub>The Simplified Chinese release notes are the primary version. This English page is a companion for international readers.</sub>
### New Features
- Made Electron Desktop Host the primary desktop entry point, adding main process, preload, IPC channels, window configuration, DevBridge HTTP bridge, and App Server host bridge foundations.
- Moved release and updater work onto the Electron Forge current path, covering macOS DMG/ZIP, Windows Squirrel, Forge release asset staging, package-resource verification, and local ZIP feed validation.
- Expanded App Server JSON-RPC coverage across Agent sessions, read/list/update, turn cancel, Connect deep links, workspace, model, knowledge, skill, artifact, and evidence surfaces.
- Advanced the Agent Runtime / Claw path onto the App Server current read model, including Electron fixture coverage for session restore, code artifacts, stop generation, history reads, and completion projection.
- Improved Agent App runtime lifecycle support for UI runtime start/status/stop, runtime packages, SDK contracts, native shell configuration, and standalone release helpers.
- Added current GUI smoke coverage for settings, sidebar sessions, Connect deep links, Agent App UI runtime, Claw current fixture, code artifact workbench, and session history fixtures.
- Added App Server JSON-RPC current protocols for automation jobs, covering scheduler config, scheduler status, job list/read/create/update/delete, run now, health, run history, schedule preview, and schedule validation.
- Moved model Provider management onto the App Server current path, covering Provider list, catalog, read, create, update, delete, sort order, config import/export, connection tests, chat tests, model fetching, API key management, key rotation, usage/error records, and UI state persistence.
- Expanded both the TypeScript and Rust App Server clients with typed automation and model Provider methods backed by shared protocol constants and schemas.
- Added an Electron runtime wrapper and continued routing Electron Desktop Host commands into App Server current data surfaces.
### Fixes
- Fixed Electron `safeInvoke` JSON-RPC result-envelope handling so real App Server `result.lines` responses are no longer treated as empty.
- Fixed sends from stale restored sessions by confirming restored ids with `agentSession/read` before submitting a new turn.
- Fixed recent and archived sidebar sessions being filtered by stale remembered workspace ids.
- Fixed Claw first-token waiting state so task cards and input status no longer show completed before a real terminal projection is available.
- Fixed stop-generation behavior so `agentSession/turn/cancel` quickly writes a canceled read model and late completion events cannot overwrite the canceled state.
- Fixed Electron startup and second-instance handling for `lime://connect` deep links, including current resolve, save, and callback paths.
- Fixed automation settings and job APIs so missing required App Server results fail closed instead of falling back to legacy commands.
- Fixed API Key Provider and model Provider flows so reads, writes, tests, imports, and exports no longer depend on legacy `safeInvoke` paths.
- Fixed unregistered desktop-host mock commands silently succeeding; explicit test mocks now fail closed unless a command is registered.
- Fixed diagnostic-only APIs being mixed with production API behavior by introducing a dedicated diagnostic facade.
- Fixed the risk of automation `runNow` falling back to the old Tauri executor before the App Server automation executor is fully migrated.
### Improvements And Refactors
- Consolidated desktop host facts around Electron Desktop Host, App Server JSON-RPC, and `src/lib/desktop-host/` current mock boundaries.
- Split large Agent Chat, workbench, sidebar, Harness, Skill selector, and Agent App tests into focused View Model and fixture layers.
- Tightened App Server client contracts, command catalog, DevBridge policy, and governance catalogs to reduce mock-priority and legacy-command regressions.
- Moved Electron release/updater scripts into `scripts/electron/` and continued organizing App Server, Agent Runtime, Agent App, i18n, Harness, Agent QC, and Knowledge scripts by domain.
- Removed old Tauri naming and legacy updater/builder references from current release paths, making Forge-only release/updater the active source of truth.
- Continued consolidating frontend API gateways for automation, model Providers, file browsing, session files, knowledge, memory, MCP, voice, updates, and workspaces around App Server and Electron current boundaries.
- Removed old automation Rust command and DevBridge dispatcher paths, classifying the legacy automation command family as dead surface.
- Reduced the default `src/lib/desktop-host/*Mocks` surface so production paths cannot fake App Server capabilities through default mocks.
- Removed obsolete `webview-api` exposure and types to reduce overlap between browser runtime helpers and the desktop current path.
- Moved more automation, Provider, and file-browser responsibilities into the App Server local data source, reducing old compatibility wrappers in the services crate.
- Updated command-boundary governance with current automation and Provider method lists plus legacy-command regression guards.
### Tests And Quality
- Added App Server client contracts, command contracts, modality contracts, scripts governance, and Electron release workflow guards to `npm run test:contracts`.
- Added a structured Electron release workflow guard for Forge makers, signing/notarization, Windows Squirrel, R2 updater assets, and legacy release-path rejection.
- Added `npm run governance:scripts` to freeze the root `scripts/` directory and track domain migrations; the root release bucket is now cleared.
- Added App Server, Electron, and Agent Runtime fixture smokes for stdio, sidecar lifecycle, packaged backend failure, package resources, Claw current fixture, cancel fixture, and history restore.
- Strengthened live Provider / WebSearch / WebFetch smoke gates so unauthorized runs fail closed and authorized runs require turn-scoped provider, model, routing, completed tools, and output evidence.
- Added targeted Rust and frontend regressions for App Server cancel/read model/JSON-RPC/external backend, App Server gateway, Agent Runtime clients, Connect, Agent App runtime, and i18n loading.
- Updated the root app, Rust workspace, CLI npm package, Agent App runtime package, App Server client package, and lockfiles to `1.60.0`.
- Expanded App Server client contracts for automation and model Provider protocols, schemas, TypeScript client methods, and Rust client methods.
- Added command-contract guards that block legacy automation and Provider commands from returning through Tauri commands, DevBridge dispatchers, mock priority commands, or runtime surfaces.
- Added targeted regressions for automation APIs, API Key Provider, model Providers, execution runs, session files, hint routes, document export, image search, video generation, and voice models.
- Added desktop-host mock-boundary tests to ensure unregistered mocks fail closed and test fixtures explicitly register commands.
- Expanded Electron host command, IPC channel, update host, App Server host, and current entrypoint coverage.
- Improved Vitest smart-runner and test-file filtering coverage for more stable focused validation.
- Updated the root app, Rust workspace, CLI npm package, Agent App runtime package, App Server client package, and lockfiles to `1.61.0`.
### Documentation
- Updated the App Server implementation plan with Electron migration, Claw current fixture, Connect deep link, Agent App UI runtime, cancel semantics, and release/updater governance progress.
- Updated the quality workflow with Electron Desktop Host, App Server JSON-RPC, GUI smoke, current fixture, live Provider authorization, and localization validation rules.
- Updated scripts governance documentation for Electron, App Server, Agent Runtime, Agent App, i18n, Harness, Agent QC, and Knowledge domain entry points.
- Updated command boundary, governance, Playwright/E2E, App Server release/updater, and frontend migration docs with legacy / compat / dead surface exit criteria.
- Updated Desktop Host / App Server command-boundary documentation for automation, model Providers, file browsing, mocks, DevBridge, and legacy surface classification.
- Updated the App Server frontend integration matrix and implementation plan with automation and Provider migration progress.
- Updated quality workflow and Playwright/E2E guidance to keep GUI validation centered on real Electron Desktop Host plus App Server paths.
- Updated Agent App, Agent UI, Managed Objective, and Skill Forge roadmap notes where current command boundaries changed.
### Other
- Continued the repository structure migration from `src-tauri` to `lime-rs`, aligning Rust workspace and desktop backend fact sources.
- Clarified release runners and asset staging: macOS arm64 uses `macos-15`, x64 uses `macos-15-intel`, and Windows uses `windows-2022`.
- Continued reducing old Tauri, legacy desktop facade, and renderer mock influence on production paths so release facts, command boundaries, and GUI validation stay on the current single path.
**Full changes**: `v1.59.0` -> `v1.60.0`
**Full changes**: `v1.60.0` -> `v1.61.0`
+29 -32
View File
@@ -1,45 +1,42 @@
## Lime v1.60.0
## Lime v1.61.0
### 新功能
- Electron Desktop Host 成为桌面主入口,新增主进程、preload、IPC channel、窗口配置、DevBridge HTTP bridge 与 App Server host bridge
- 发布与更新链路迁移到 Electron Forge current 主路径,覆盖 macOS DMG/ZIP、Windows Squirrel、Forge release asset staging、包资源校验与本地 ZIP feed 验证
- App Server JSON-RPC 能力继续扩展,补齐 Agent session/read/list/update/turn/cancel、Connect deep link、workspace、model、knowledge、skill、artifact 与 evidence 等 current 协议面
- Agent Runtime / Claw 主链进一步接入 App Server current read model,支持真实 Electron fixture 下的会话恢复、代码产物工作台、停止生成、历史读取和完成态投影
- Agent App runtime 增强 UI runtime 生命周期与 Electron fixture 能力,覆盖 start/status/stop、runtime package、SDK contract、native shell 配置与 standalone release 辅助链路。
- 新增多组 current GUI smoke:设置页全量、侧栏会话、Connect deep link、Agent App UI runtime、Claw current fixture、代码产物工作台和 session history fixture。
- App Server JSON-RPC 新增自动化任务 current 协议,覆盖调度器配置、状态读取、任务列表 / 详情 / 创建 / 更新 / 删除、立即运行、健康检查、运行历史、计划预览与计划校验
- 模型 Provider 管理迁移到 App Server current 主,覆盖 Provider 列表、目录、详情、新建、更新、删除、排序、配置导入导出、连接测试、聊天测试、模型拉取、API Key 管理、Key 轮转、使用 / 错误记录与 UI state 持久化
- TypeScript 与 Rust App Server client 补齐自动化和模型 Provider 的类型化客户端方法,前端、Electron 与 Rust 侧共享同一组协议常量和 schema
- Electron Desktop Host 新增 Electron runtime 封装,并继续把 host command 收敛到 App Server current 数据面
### 修复
- 修复 Electron `safeInvoke` JSON-RPC result envelope 解包问题,避免 App Server 返回真实 `result.lines` 时被前端误判为空
- 修复恢复到 App Server 中不存在的历史 session 后继续发送导致无输出的问题,发送前会先用 `agentSession/read` 确认 session 存在并匹配 workspace
- 修复最近对话 / 归档列表被陈旧 remembered workspace 污染过滤条件的问题,侧栏查询不再把未验证 workspace 传给 `agentSession/list`
- 修复 Claw 首 token 等待态中任务卡和输入栏过早显示已完成的问题,只有真实终态和正文投影完成后才展示完成状态
- 修复停止生成语义,`agentSession/turn/cancel` 会先写入 canceled read model 并快速恢复输入框,迟到的完成事件不再覆盖取消态
- 修复 Connect deep link 在 Electron 首启参数和二次实例参数中的分发,覆盖 `lime://connect` 的 current resolve、保存和 callback 路径。
- 修复自动化设置和任务 API 缺少 App Server 必需返回值时可能被旧路径伪装成功的问题,现在会 fail closed,不再回退 legacy 命令
- 修复 API Key Provider / 模型 Provider 读取、写入、测试、导入导出等路径对 legacy `safeInvoke` 的依赖,App Server 不可用时明确暴露错误
- 修复未注册 desktop-host mock 命令可静默通过的风险,测试 mock 遇到未知命令会直接失败
- 修复诊断类 API 与生产 API 混用的问题,新增 diagnostic facade,避免把诊断降级误认为真实业务链路
- 修复自动化 `runNow` 在 App Server 执行器迁移未完成时回退旧 Tauri 执行器的风险,current 协议会拒绝旧执行器回流
### 优化与重构
- 将桌面宿主事实源从 Tauri / legacy facade 收敛到 Electron Desktop Host、App Server JSON-RPC 与 `src/lib/desktop-host/` current mock 边界。
- 大规模拆分 Agent Chat、工作台、侧栏、Harness 面板、Skill 选择器、Agent App 页面等复杂测试和 View Model,降低 React 挂载测试承载业务状态机的比例
- App Server client contract、command catalog、DevBridge policy 与 governance catalog 继续收敛,减少 mock priority command 和 legacy command 回流
- Electron release/updater 脚本迁入 `scripts/electron/`App Server、Agent Runtime、Agent App、i18n、Harness、Agent QC、Knowledge 脚本按领域目录治理
- 清理旧 Tauri 命名与旧 updater/builder 入口,Forge-only release/updater 成为当前发布事实源
- 自动化、模型 Provider、文件浏览、会话文件、知识、记忆、MCP、语音、更新、工作区等前端 API 网关继续收敛到 App Server / Electron current 边界。
- 删除旧自动化 Rust command 与 DevBridge dispatcher,旧自动化命令族进入治理层 dead surface
- 大幅收缩 `src/lib/desktop-host/*Mocks` 默认 mock 面,生产路径不再通过 default mock 伪造 App Server 能力
- 移除旧 `webview-api` 暴露面中的过时类型和入口,减少浏览器运行时与桌面 current 主链的重叠
- App Server 本地数据源吸收自动化、Provider 与文件浏览相关能力,降低 services crate 中旧兼容包装的职责
- 命令边界文档和治理目录补充自动化 / Provider current 方法清单及旧命令禁回流规则。
### 测试与质量
- `npm run test:contracts` 接入 App Server client contract、command contract、modality contract、scripts governance 与 Electron release workflow guard
- 新增 Electron release workflow 结构化守卫,校验 Forge maker、签名/公证、Windows Squirrel、R2 updater asset 计划,并阻止 electron-builder、NSIS、旧 updater metadata 回流
- 新增 `npm run governance:scripts`,冻结 `scripts/` 根目录并跟踪领域迁移,根目录 release bucket 已清零
- 新增 App Server / Electron / Agent Runtime fixture smoke,覆盖 app-server stdio、sidecar lifecycle、packaged backend failure、Electron package resources、Claw current fixture、cancel fixture 与历史恢复
- 增强 live Provider / WebSearch / WebFetch smoke gate,授权前 fail closed,授权后要求 turn-scoped provider/model/routing、工具完成态和输出证据同时满足
- 补充 Rust App Server cancel、read model、JSON-RPC、external backend,以及前端 App Server gateway、Agent Runtime client、Connect、Agent App runtime、i18n loader 等定向回归
- 根应用、Rust workspace、CLI npm package、Agent App runtime package、App Server client package 与锁文件版本统一更新到 `1.60.0`
- App Server client contract 扩展自动化与模型 Provider 覆盖,校验新协议、schema、TypeScript 客户端和 Rust 客户端保持同步
- 命令契约守卫新增自动化 / Provider 旧命令禁回流检查,阻止 legacy Tauri command、DevBridge dispatcher、mock priority 与 runtime surface 重新接入
- 新增自动化 API、API Key Provider、模型 Provider、执行运行、会话文件、提示路由、文档导出、图片搜索、视频生成、语音模型等定向回归
- 新增 desktop-host mock 边界测试,确认未注册 mock fail closed,测试夹具必须显式注册命令
- 扩展 Electron host command、IPC channel、update host、App Server host 与 current entrypoint 测试
- Vitest smart runner 与测试文件过滤逻辑补充回归,提升局部测试选择和守卫稳定性
- 根应用、Rust workspace、CLI npm package、Agent App runtime package、App Server client package 与锁文件版本统一更新到 `1.61.0`
### 文档
- 更新 App Server 实施计划,记录 Electron 迁移、Claw current fixture、Connect deep link、Agent App UI runtime、cancel 语义和 release/updater 治理进展
- 更新工程质量工作流,明确 Electron Desktop Host、App Server JSON-RPC、GUI smoke、current fixture、live Provider 授权和本地化验证口径
- 更新脚本治理 README,沉淀 Electron、App Server、Agent Runtime、Agent App、i18n、Harness、Agent QC、Knowledge 领域脚本入口
- 更新命令边界、治理、Playwright/E2E、App Server release/updater 与前端迁移矩阵,明确 legacy / compat / dead surface 的退出条件
- 更新 Desktop Host / App Server 命令边界,明确自动化、模型 Provider、文件浏览、mock、DevBridge 与 legacy surface 的 current / dead 分类
- 更新 App Server 前端集成矩阵和实施计划,记录自动化与 Provider 迁移到 App Server current 主链的进度
- 更新质量工作流与 Playwright / E2E 指南,继续强调 GUI 桌面产品必须通过真实 Electron Desktop Host + App Server 路径验证
- 更新 Agent App、Agent UI、Managed Objective、Skill Forge 等路线图中与 current 命令边界相关的说明
### 其他
- 继续推进 `src-tauri``lime-rs` 的仓库结构迁移,统一 Rust workspace manifest 与桌面后端事实源
- 发布工作流 runner 和 asset staging 更明确:macOS arm64 固定 `macos-15`x64 固定 `macos-15-intel`Windows 固定 `windows-2022`
- 继续减少旧 Tauri / legacy desktop facade / renderer mock 对生产路径的影响,让发版事实源、命令边界和 GUI 验证保持 current 单主链
**完整变更**: `v1.59.0` -> `v1.60.0`
**完整变更**: `v1.60.0` -> `v1.61.0`
+1 -1
View File
@@ -105,7 +105,7 @@ const {
};
});
vi.mock("electron", () => ({
vi.mock("./electronRuntime", () => ({
app: {
getAppPath: () => process.cwd(),
getVersion: () => "0.0.0-test",
+1 -1
View File
@@ -21,7 +21,7 @@ import {
type RequestId,
type SidecarLaunchConfig,
} from "app-server-client";
import { app } from "electron";
import { app } from "./electronRuntime";
import { readFileSync } from "node:fs";
import path from "node:path";
+214
View File
@@ -0,0 +1,214 @@
import { createServer } from "node:net";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ElectronDevHttpBridge } from "./devHttpBridge";
const activeBridges: ElectronDevHttpBridge[] = [];
type DevHttpBridgeInvoke = ConstructorParameters<
typeof ElectronDevHttpBridge
>[0]["invoke"];
async function createStartedBridge(
invoke: DevHttpBridgeInvoke = vi.fn<DevHttpBridgeInvoke>(
async () => undefined,
),
): Promise<{ bridge: ElectronDevHttpBridge; invoke: DevHttpBridgeInvoke }> {
const port = await reserveLocalPort();
const bridge = new ElectronDevHttpBridge({
host: "127.0.0.1",
port,
invoke,
});
activeBridges.push(bridge);
bridge.start();
await waitForHealth(bridge.url);
return { bridge, invoke };
}
async function reserveLocalPort(): Promise<number> {
const server = createServer();
return new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", () => {
const address = server.address();
if (!address || typeof address === "string") {
server.close(() => reject(new Error("failed to reserve local port")));
return;
}
server.close(() => resolve(address.port));
});
});
}
async function waitForHealth(baseUrl: string): Promise<void> {
const deadline = Date.now() + 2_000;
let lastError: unknown;
while (Date.now() < deadline) {
try {
const response = await fetch(`${baseUrl}/health`);
if (response.ok) {
return;
}
} catch (error) {
lastError = error;
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
throw lastError instanceof Error
? lastError
: new Error("dev HTTP bridge did not become healthy");
}
async function postJson(
baseUrl: string,
payload: unknown,
): Promise<{ status: number; body: unknown }> {
const response = await fetch(`${baseUrl}/invoke`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
return {
status: response.status,
body: await response.json(),
};
}
describe("electron/devHttpBridge", () => {
beforeEach(() => {
vi.spyOn(console, "log").mockImplementation(() => {});
vi.spyOn(console, "warn").mockImplementation(() => {});
});
afterEach(() => {
for (const bridge of activeBridges.splice(0)) {
bridge.stop();
}
vi.restoreAllMocks();
});
it("health 端点必须声明当前 transport 为 electron-host", async () => {
const { bridge } = await createStartedBridge();
const response = await fetch(`${bridge.url}/health`);
expect(response.status).toBe(200);
expect(response.headers.get("access-control-allow-origin")).toBe("*");
await expect(response.json()).resolves.toEqual({
status: "ok",
transport: "electron-host",
});
});
it("/invoke 只调用注入的 Electron Host invoke 并返回 result envelope", async () => {
const invoke = vi.fn().mockResolvedValue({ accepted: true });
const { bridge } = await createStartedBridge(invoke);
const response = await postJson(bridge.url, {
cmd: " agent_runtime_create_session ",
args: { workspaceRoot: "/tmp/workspace" },
});
expect(response).toEqual({
status: 200,
body: { result: { accepted: true } },
});
expect(invoke).toHaveBeenCalledTimes(1);
expect(invoke).toHaveBeenCalledWith("agent_runtime_create_session", {
workspaceRoot: "/tmp/workspace",
});
});
it("/invoke 缺 command 或 body 非对象时 fail closed 且不调用 invoke", async () => {
const invoke = vi.fn();
const { bridge } = await createStartedBridge(invoke);
await expect(postJson(bridge.url, { args: { ignored: true } })).resolves.toEqual(
{
status: 400,
body: { error: "cmd is required" },
},
);
const response = await fetch(`${bridge.url}/invoke`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(["legacy_command"]),
});
expect(response.status).toBe(200);
await expect(response.json()).resolves.toEqual({
error: "request body must be a JSON object",
});
expect(invoke).not.toHaveBeenCalled();
});
it("/invoke 把 Electron Host 错误投影为 error envelope,不能伪装成功", async () => {
const invoke = vi.fn().mockRejectedValue(new Error("unsupported command"));
const { bridge } = await createStartedBridge(invoke);
await expect(
postJson(bridge.url, { cmd: "legacy_command", args: { a: 1 } }),
).resolves.toEqual({
status: 200,
body: { error: "unsupported command" },
});
});
it("OPTIONS 和未知路径保持固定 HTTP 边界", async () => {
const { bridge } = await createStartedBridge();
const options = await fetch(`${bridge.url}/invoke`, { method: "OPTIONS" });
expect(options.status).toBe(204);
expect(options.headers.get("access-control-allow-methods")).toBe(
"GET,POST,OPTIONS",
);
const notFound = await fetch(`${bridge.url}/unknown`);
expect(notFound.status).toBe(404);
await expect(notFound.json()).resolves.toEqual({ error: "not found" });
});
it("events 端点只向订阅的事件推送 SSE message", async () => {
const { bridge } = await createStartedBridge();
const response = await fetch(`${bridge.url}/events?event=session:update`);
const reader = response.body?.getReader();
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toContain("text/event-stream");
expect(reader).toBeDefined();
bridge.broadcast("ignored:event", { ignored: true });
bridge.broadcast("session:update", { id: "session-1" });
const text = await readUntil(reader!, "session:update");
expect(text).toContain(": connected");
expect(text).toContain("event: message");
expect(text).toContain(
'data: {"event":"session:update","payload":{"id":"session-1"}}',
);
expect(text).not.toContain("ignored:event");
await reader?.cancel();
});
});
async function readUntil(
reader: ReadableStreamDefaultReader<Uint8Array>,
needle: string,
): Promise<string> {
const decoder = new TextDecoder();
let text = "";
const deadline = Date.now() + 2_000;
while (!text.includes(needle) && Date.now() < deadline) {
const result = await reader.read();
if (result.done) {
break;
}
text += decoder.decode(result.value, { stream: true });
}
if (!text.includes(needle)) {
throw new Error(`SSE stream did not include ${needle}`);
}
return text;
}
+31
View File
@@ -0,0 +1,31 @@
import { createRequire } from "node:module";
import type * as Electron from "electron";
const requireElectron = createRequire(import.meta.url);
const electron = requireElectron("electron") as typeof Electron;
export const {
app,
autoUpdater,
BrowserWindow,
dialog,
globalShortcut,
ipcMain,
Menu,
nativeImage,
screen,
session,
shell,
Tray,
} = electron;
export type BrowserWindow = Electron.BrowserWindow;
export type Menu = Electron.Menu;
export type Tray = Electron.Tray;
export type {
IpcMainInvokeEvent,
MenuItemConstructorOptions,
OpenDialogOptions,
SaveDialogOptions,
} from "electron";
+275 -5
View File
@@ -7,23 +7,38 @@ import { AppServerRequestError } from "app-server-client";
import { ElectronHostCommands } from "./hostCommands";
import type { ElectronAppServerHost } from "./appServerHost";
const { openExternalMock } = vi.hoisted(() => ({
openExternalMock: vi.fn(),
}));
const {
getFileIconMock,
getPathMock,
openExternalMock,
openPathMock,
showItemInFolderMock,
} = vi.hoisted(() => {
return {
getFileIconMock: vi.fn(),
getPathMock: vi.fn((_name: string) => os.tmpdir()),
openExternalMock: vi.fn(),
openPathMock: vi.fn(),
showItemInFolderMock: vi.fn(),
};
});
const tempDirs: string[] = [];
type AppServerRequestMock = (
method: string,
params?: unknown,
) => Promise<unknown>;
vi.mock("electron", () => ({
vi.mock("./electronRuntime", () => ({
app: {
getFileIcon: getFileIconMock,
getName: () => "Lime",
getPath: () => os.tmpdir(),
getPath: getPathMock,
getVersion: () => "0.0.0-test",
},
shell: {
openExternal: openExternalMock,
openPath: openPathMock,
showItemInFolder: showItemInFolderMock,
},
}));
@@ -63,6 +78,7 @@ function sessionAlreadyExistsError(sessionId: string) {
afterEach(async () => {
vi.clearAllMocks();
getPathMock.mockImplementation(() => os.tmpdir());
while (tempDirs.length > 0) {
const dir = tempDirs.pop();
if (dir) {
@@ -85,6 +101,194 @@ describe("ElectronHostCommands retired file browser facade", () => {
);
});
describe("ElectronHostCommands retired automation facade", () => {
it.each([
"get_automation_scheduler_config",
"get_automation_status",
"get_automation_health",
"get_automation_jobs",
])("%s 不再作为 Electron Host compat facade 暴露", async (command) => {
const userDataDir = await createTempUserDataDir();
const host = createHost(userDataDir);
await expect(host.invoke(command, {})).rejects.toThrow(
`Electron host command is not implemented: ${command}`,
);
});
});
describe("ElectronHostCommands retired API Key Provider facade", () => {
it.each([
"get_api_key_providers",
"get_system_provider_catalog",
"get_provider_ui_state",
"set_provider_ui_state",
"fetch_provider_models_auto",
])("%s 不再作为 Electron Host provider facade 暴露", async (command) => {
const userDataDir = await createTempUserDataDir();
const host = createHost(userDataDir);
await expect(host.invoke(command, {})).rejects.toThrow(
`Electron host command is not implemented: ${command}`,
);
});
});
describe("ElectronHostCommands local file shell facade", () => {
it("reveal_in_finder 通过 Electron shell 定位本地路径", async () => {
const userDataDir = await createTempUserDataDir();
const host = createHost(userDataDir);
await expect(
host.invoke("reveal_in_finder", { path: "/tmp/demo.txt" }),
).resolves.toEqual({});
expect(showItemInFolderMock).toHaveBeenCalledWith("/tmp/demo.txt");
});
it("open_with_default_app 通过 Electron shell 打开本地路径", async () => {
openPathMock.mockResolvedValueOnce("");
const userDataDir = await createTempUserDataDir();
const host = createHost(userDataDir);
await expect(
host.invoke("open_with_default_app", { path: "/tmp/demo.txt" }),
).resolves.toEqual({});
expect(openPathMock).toHaveBeenCalledWith("/tmp/demo.txt");
});
it("open_with_default_app 应暴露 Electron openPath 失败", async () => {
openPathMock.mockResolvedValueOnce("Cannot open file");
const userDataDir = await createTempUserDataDir();
const host = createHost(userDataDir);
await expect(
host.invoke("open_with_default_app", { path: "/tmp/missing.txt" }),
).rejects.toThrow("Cannot open file");
});
it("get_file_icon_data_url 应通过 Electron 读取系统文件图标", async () => {
getFileIconMock.mockResolvedValueOnce({
isEmpty: () => false,
toDataURL: () => "data:image/png;base64,abc",
});
const userDataDir = await createTempUserDataDir();
const host = createHost(userDataDir);
await expect(
host.invoke("get_file_icon_data_url", { path: "/Applications/Lime.app" }),
).resolves.toBe("data:image/png;base64,abc");
expect(getFileIconMock).toHaveBeenCalledWith("/Applications/Lime.app", {
size: "normal",
});
});
it("get_file_icon_data_url 在系统图标不可用时返回 null", async () => {
getFileIconMock.mockResolvedValueOnce({
isEmpty: () => true,
toDataURL: () => "data:image/png;base64,unused",
});
const userDataDir = await createTempUserDataDir();
const host = createHost(userDataDir);
await expect(
host.invoke("get_file_icon_data_url", { path: "/tmp/missing.txt" }),
).resolves.toBeNull();
});
it("get_file_icon_data_url 应隔离 Electron 图标读取失败", async () => {
getFileIconMock.mockRejectedValueOnce(new Error("icon unavailable"));
const userDataDir = await createTempUserDataDir();
const host = createHost(userDataDir);
await expect(
host.invoke("get_file_icon_data_url", { path: "/tmp/missing.txt" }),
).resolves.toBeNull();
});
it("get_home_dir 应返回 Electron 系统主目录", async () => {
const userDataDir = await createTempUserDataDir();
const homeDir = path.join(userDataDir, "home");
getPathMock.mockImplementation((name: string) => {
return name === "home" ? homeDir : os.tmpdir();
});
const host = createHost(userDataDir);
await expect(host.invoke("get_home_dir")).resolves.toBe(homeDir);
});
it("get_home_dir 在系统主目录不可用时应 fail closed", async () => {
const userDataDir = await createTempUserDataDir();
getPathMock.mockImplementation((name: string) => {
return name === "home" ? "" : os.tmpdir();
});
const host = createHost(userDataDir);
await expect(host.invoke("get_home_dir")).rejects.toThrow("无法获取主目录");
});
it("get_file_manager_locations 应返回存在的系统快捷入口并去重", async () => {
const userDataDir = await createTempUserDataDir();
const homeDir = path.join(userDataDir, "home");
const missingDesktopDir = path.join(userDataDir, "missing-desktop");
const documentsDir = path.join(userDataDir, "Documents");
const downloadsDir = path.join(userDataDir, "Downloads");
await mkdir(homeDir, { recursive: true });
await mkdir(documentsDir, { recursive: true });
await mkdir(downloadsDir, { recursive: true });
getPathMock.mockImplementation((name: string) => {
if (name === "home") {
return homeDir;
}
if (name === "desktop") {
return missingDesktopDir;
}
if (name === "documents") {
return documentsDir;
}
if (name === "downloads") {
return downloadsDir;
}
return os.tmpdir();
});
const host = createHost(userDataDir);
const locations = await host.invoke("get_file_manager_locations");
expect(locations).toEqual(
expect.arrayContaining([
{
id: "home",
label: "个人",
path: homeDir,
kind: "home",
},
{
id: "documents",
label: "文档",
path: documentsDir,
kind: "documents",
},
{
id: "downloads",
label: "下载",
path: downloadsDir,
kind: "downloads",
},
]),
);
const returnedPaths = (locations as Array<{ path: string }>).map(
(location) => location.path,
);
expect(returnedPaths.filter((nextPath) => nextPath === homeDir)).toHaveLength(
1,
);
expect(returnedPaths).not.toContain(missingDesktopDir);
});
});
describe("ElectronHostCommands experimental config", () => {
it("默认读取关闭的 WebMCP 预留配置", async () => {
const userDataDir = await createTempUserDataDir();
@@ -129,6 +333,72 @@ describe("ElectronHostCommands experimental config", () => {
});
});
describe("ElectronHostCommands MCP current source", () => {
it.each([
[
"get_mcp_servers",
"mcpServer/list",
{ servers: [{ id: "server-1", name: "docs" }] },
[{ id: "server-1", name: "docs" }],
],
[
"mcp_list_servers_with_status",
"mcpServerStatus/list",
{ servers: [{ id: "server-1", name: "docs", is_running: false }] },
[{ id: "server-1", name: "docs", is_running: false }],
],
[
"mcp_list_tools",
"mcpTool/list",
{ tools: [{ name: "mcp__docs__search_docs" }] },
[{ name: "mcp__docs__search_docs" }],
],
[
"mcp_list_prompts",
"mcpPrompt/list",
{ prompts: [{ name: "summarize", server_name: "docs" }] },
[{ name: "summarize", server_name: "docs" }],
],
[
"mcp_list_resources",
"mcpResource/list",
{ resources: [{ uri: "docs://readme", server_name: "docs" }] },
[{ uri: "docs://readme", server_name: "docs" }],
],
] as const)(
"%s 应经 App Server current method 返回 MCP 列表",
async (command, expectedMethod, response, expectedResult) => {
const userDataDir = await createTempUserDataDir();
const request = vi.fn(async (method: string, params?: unknown) => {
expect(params).toEqual({});
if (method === expectedMethod) {
return response;
}
throw new Error(`unexpected App Server method: ${method}`);
});
const host = createHost(userDataDir, () => undefined, request);
await expect(host.invoke(command)).resolves.toEqual(expectedResult);
expect(request).toHaveBeenCalledWith(expectedMethod, {});
},
);
it("MCP current 空态不应带 Electron diagnostic facade 元数据", async () => {
const userDataDir = await createTempUserDataDir();
const host = createHost(userDataDir, () => undefined, async (method) => {
if (method === "mcpTool/list") {
return { tools: [] };
}
throw new Error(`unexpected App Server method: ${method}`);
});
const result = await host.invoke("mcp_list_tools");
expect(result).toEqual([]);
expect((result as { __diagnostic?: unknown }).__diagnostic).toBeUndefined();
});
});
describe("ElectronHostCommands model provider current source", () => {
it("get_default_provider 应忽略旧配置值并返回 App Server 当前已配置 Provider", async () => {
const userDataDir = await createTempUserDataDir();
+232 -130
View File
@@ -1,4 +1,4 @@
import { app, shell } from "electron";
import { app, shell } from "./electronRuntime";
import {
AppServerRequestError,
ERROR_CODES,
@@ -13,7 +13,6 @@ import {
METHOD_AGENT_SESSION_TURN_CANCEL,
METHOD_AGENT_SESSION_TURN_START,
METHOD_AGENT_SESSION_UPDATE,
METHOD_AUTOMATION_JOB_LIST,
METHOD_CAPABILITY_LIST,
METHOD_EVIDENCE_EXPORT,
METHOD_KNOWLEDGE_PACK_LIST,
@@ -24,6 +23,11 @@ import {
METHOD_MODEL_PROVIDER_CATALOG_LIST,
METHOD_MODEL_PROVIDER_LIST,
METHOD_MODEL_SYNC_STATE_READ,
METHOD_MCP_PROMPT_LIST,
METHOD_MCP_RESOURCE_LIST,
METHOD_MCP_SERVER_LIST,
METHOD_MCP_SERVER_STATUS_LIST,
METHOD_MCP_TOOL_LIST,
METHOD_PROJECT_MEMORY_READ,
METHOD_SKILL_LIST,
METHOD_SKILL_READ,
@@ -51,7 +55,6 @@ import {
type AgentAppUiRuntimeStatusResponse,
type AgentAppUiRuntimeStopParams,
type ArtifactSummary,
type AutomationJobListResponse,
type CapabilityDescriptor,
type CapabilityListResponse,
type EvidenceExportResponse,
@@ -63,6 +66,11 @@ import {
type ModelProviderCatalogListResponse,
type ModelProviderListResponse,
type ModelSyncStateReadResponse,
type McpPromptListResponse,
type McpResourceListResponse,
type McpServerListResponse,
type McpServerStatusListResponse,
type McpToolListResponse,
type ProjectMemoryReadResponse,
type SkillListResponse,
type SkillReadResponse,
@@ -73,7 +81,7 @@ import {
type WorkspaceReadResponse,
type WorkspaceSkillBindingsListResponse,
} from "app-server-client";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
import { createServer, type Server } from "node:http";
import path from "node:path";
import type { ElectronAppServerHost } from "./appServerHost";
@@ -81,6 +89,16 @@ import type { ElectronAppServerHost } from "./appServerHost";
type HostArgs = Record<string, unknown> | null | undefined;
type AppServerParams = Record<string, unknown>;
type HostEventEmitter = (event: string, payload?: unknown) => void;
type FileManagerLocation = {
id: string;
label: string;
path: string;
kind: string;
};
type FileManagerLocationCandidate = Omit<FileManagerLocation, "path"> & {
path: string | null;
};
type ElectronKnownPathName = "home" | "desktop" | "documents" | "downloads";
const CONFIG_FILE = "config.json";
const OEM_CLOUD_OAUTH_CALLBACK_BRIDGE_EVENT = "oem-cloud-oauth-callback";
@@ -139,16 +157,22 @@ export class ElectronHostCommands {
return await this.#saveExperimentalConfig(args);
case "open_external_url":
return await this.#openExternalUrl(args);
case "reveal_in_finder":
return this.#revealInFinder(args);
case "open_with_default_app":
return await this.#openWithDefaultApp(args);
case "get_home_dir":
return this.#getHomeDir();
case "get_file_manager_locations":
return await this.#getFileManagerLocations();
case "get_file_icon_data_url":
return await this.#getFileIconDataUrl(args);
case "start_oem_cloud_oauth_callback_bridge":
return await this.#startOemCloudOAuthCallbackBridge();
case "aster_agent_init":
return await this.#initAgentRuntime();
case "get_default_provider":
return await this.#getDefaultProvider();
case "get_provider_ui_state":
return await this.#getProviderUiState(args);
case "set_provider_ui_state":
return await this.#setProviderUiState(args);
case "agent_runtime_list_sessions":
return await this.#listAgentRuntimeSessions(args);
case "agent_runtime_get_session":
@@ -171,10 +195,6 @@ export class ElectronHostCommands {
return await this.#getAgentRuntimeToolInventory(args);
case "agent_runtime_list_workspace_skill_bindings":
return await this.#listWorkspaceSkillBindings(args);
case "get_api_key_providers":
return await this.#listModelProviders();
case "get_system_provider_catalog":
return await this.#listModelProviderCatalog();
case "get_model_registry":
return await this.#listModels();
case "get_model_preferences":
@@ -187,8 +207,6 @@ export class ElectronHostCommands {
return await this.#listModelsForProvider(args);
case "get_models_by_tier":
return await this.#listModelsByTier(args);
case "fetch_provider_models_auto":
return await this.#fetchProviderModelsAuto(args);
case "get_provider_alias_config":
return await this.#readProviderAliasConfig(args);
case "get_all_alias_configs":
@@ -221,14 +239,6 @@ export class ElectronHostCommands {
return await this.#listLocalSkillsForApp(args);
case "get_skill_detail":
return await this.#readSkillDetail(args);
case "get_automation_scheduler_config":
return await this.#getAutomationSchedulerConfig();
case "get_automation_status":
return this.#getAutomationStatus();
case "get_automation_health":
return await this.#getAutomationHealth();
case "get_automation_jobs":
return await this.#listAutomationJobs();
case "get_usage_stats":
return this.#getUsageStats();
case "get_model_usage_ranking":
@@ -254,11 +264,15 @@ export class ElectronHostCommands {
case "unified_memory_stats":
return this.#getUnifiedMemoryStats();
case "get_mcp_servers":
return await this.#listMcpServers();
case "mcp_list_servers_with_status":
return await this.#listMcpServersWithStatus();
case "mcp_list_tools":
return await this.#listMcpTools();
case "mcp_list_prompts":
return await this.#listMcpPrompts();
case "mcp_list_resources":
return this.#emptyDiagnosticList(command);
return await this.#listMcpResources();
case "site_get_adapter_catalog_status":
return this.#getSiteAdapterCatalogStatus();
case "site_list_adapters":
@@ -327,6 +341,136 @@ export class ElectronHostCommands {
return {};
}
#revealInFinder(args: HostArgs): Record<string, never> {
const request = readRequest(args);
const targetPath = readRequiredString(request, "path");
shell.showItemInFolder(targetPath);
return {};
}
async #openWithDefaultApp(args: HostArgs): Promise<Record<string, never>> {
const request = readRequest(args);
const targetPath = readRequiredString(request, "path");
const errorMessage = await shell.openPath(targetPath);
if (errorMessage) {
throw new Error(errorMessage);
}
return {};
}
async #getFileIconDataUrl(args: HostArgs): Promise<string | null> {
const request = readRequest(args);
const targetPath = readRequiredString(request, "path");
try {
const icon = await app.getFileIcon(targetPath, { size: "normal" });
if (icon.isEmpty()) {
return null;
}
return icon.toDataURL() || null;
} catch {
return null;
}
}
#getHomeDir(): string {
const homePath = readElectronPath("home");
if (!homePath) {
throw new Error("无法获取主目录");
}
return homePath;
}
async #getFileManagerLocations(): Promise<FileManagerLocation[]> {
const locations: FileManagerLocation[] = [];
const seenPaths = new Set<string>();
const homePath = readElectronPath("home");
await appendFileManagerLocation(locations, seenPaths, {
id: "home",
label: "个人",
kind: "home",
path: homePath,
});
await appendFileManagerLocation(locations, seenPaths, {
id: "desktop",
label: "桌面",
kind: "desktop",
path: readElectronPath("desktop"),
});
await appendFileManagerLocation(locations, seenPaths, {
id: "documents",
label: "文档",
kind: "documents",
path: readElectronPath("documents"),
});
await appendFileManagerLocation(locations, seenPaths, {
id: "downloads",
label: "下载",
kind: "downloads",
path: readElectronPath("downloads"),
});
if (process.platform === "darwin") {
await appendFileManagerLocation(locations, seenPaths, {
id: "applications",
label: "应用程序",
kind: "applications",
path: "/Applications",
});
await appendFileManagerLocation(locations, seenPaths, {
id: "user-applications",
label: "用户应用程序",
kind: "applications",
path: homePath ? path.join(homePath, "Applications") : null,
});
}
if (process.platform === "win32") {
await appendFileManagerLocation(locations, seenPaths, {
id: "start-menu-programs",
label: "应用程序",
kind: "applications",
path: process.env.APPDATA
? path.join(
process.env.APPDATA,
"Microsoft",
"Windows",
"Start Menu",
"Programs",
)
: null,
});
await appendFileManagerLocation(locations, seenPaths, {
id: "common-start-menu-programs",
label: "公共应用程序",
kind: "applications",
path: process.env.PROGRAMDATA
? path.join(
process.env.PROGRAMDATA,
"Microsoft",
"Windows",
"Start Menu",
"Programs",
)
: null,
});
await appendFileManagerLocation(locations, seenPaths, {
id: "program-files",
label: "Program Files",
kind: "applications",
path: process.env.ProgramFiles || null,
});
await appendFileManagerLocation(locations, seenPaths, {
id: "program-files-x86",
label: "Program Files (x86)",
kind: "applications",
path: process.env["ProgramFiles(x86)"] || null,
});
}
return locations;
}
async #startOemCloudOAuthCallbackBridge(): Promise<{
callbackUrl: string;
}> {
@@ -779,36 +923,6 @@ export class ElectronHostCommands {
});
}
async #getProviderUiState(args: HostArgs): Promise<string | null> {
const key = readString(args, "key") ?? readString(readRequest(args), "key");
if (!key) {
return null;
}
const state = toRecord((await this.#readConfig()).provider_ui_state);
const value = state?.[key];
return typeof value === "string" ? value : null;
}
async #setProviderUiState(args: HostArgs): Promise<void> {
const request = readRequest(args);
const key = readString(request, "key");
if (!key) {
throw new Error("set_provider_ui_state requires key");
}
const value = readString(request, "value") ?? "";
const config = await this.#readConfig();
const state = {
...(toRecord(config.provider_ui_state) ?? {}),
[key]: value,
};
await this.#saveConfig({
config: {
...config,
provider_ui_state: state,
},
});
}
async #listAgentRuntimeSessions(args: HostArgs): Promise<unknown[]> {
const request = readRequest(args);
const params: AppServerParams = {
@@ -1137,6 +1251,41 @@ export class ElectronHostCommands {
return response.syncState;
}
async #listMcpServers(): Promise<unknown[]> {
const response = await this.#appServerRequest<McpServerListResponse>(
METHOD_MCP_SERVER_LIST,
);
return response.servers;
}
async #listMcpServersWithStatus(): Promise<unknown[]> {
const response = await this.#appServerRequest<McpServerStatusListResponse>(
METHOD_MCP_SERVER_STATUS_LIST,
);
return response.servers;
}
async #listMcpTools(): Promise<unknown[]> {
const response = await this.#appServerRequest<McpToolListResponse>(
METHOD_MCP_TOOL_LIST,
);
return response.tools;
}
async #listMcpPrompts(): Promise<unknown[]> {
const response = await this.#appServerRequest<McpPromptListResponse>(
METHOD_MCP_PROMPT_LIST,
);
return response.prompts;
}
async #listMcpResources(): Promise<unknown[]> {
const response = await this.#appServerRequest<McpResourceListResponse>(
METHOD_MCP_RESOURCE_LIST,
);
return response.resources;
}
async #listModelRegistryProviderIds(): Promise<string[]> {
await this.#appServerRequest<ModelProviderCatalogListResponse>(
METHOD_MODEL_PROVIDER_CATALOG_LIST,
@@ -1163,28 +1312,6 @@ export class ElectronHostCommands {
return await this.#listModels({ tier });
}
async #fetchProviderModelsAuto(
args: HostArgs,
): Promise<Record<string, unknown>> {
const request = readRequest(args);
const providerId =
readString(request, "providerId") ?? readString(request, "provider_id");
if (!providerId) {
throw new Error("fetch_provider_models_auto requires providerId");
}
return {
models: [],
source: "Error",
error: "实时 Provider 模型接口尚未迁入 App Server。",
request_url: null,
diagnostic_hint:
"当前 Electron Desktop Host 不能把 App Server model/list 快照伪装成实时 /models 结果;请先手动添加模型 ID,后续应补 App Server Provider 实时探测方法。",
error_kind: "other",
should_prompt_error: true,
from_cache: false,
};
}
async #readProviderAliasConfig(args: HostArgs): Promise<unknown | null> {
const request = readRequest(args);
const provider = readString(request, "provider");
@@ -1429,61 +1556,6 @@ export class ElectronHostCommands {
);
}
async #listAutomationJobs(): Promise<unknown[]> {
const response = await this.#appServerRequest<AutomationJobListResponse>(
METHOD_AUTOMATION_JOB_LIST,
);
return response.jobs;
}
async #getAutomationSchedulerConfig(): Promise<Record<string, unknown>> {
const config = await this.#readConfig();
const automationConfig = toRecord(config.automation);
return {
enabled: readBoolean(automationConfig, "enabled") ?? false,
poll_interval_secs:
readNumber(automationConfig, "poll_interval_secs") ?? 30,
enable_history: readBoolean(automationConfig, "enable_history") ?? true,
};
}
#getAutomationStatus(): Record<string, unknown> {
return {
running: false,
last_polled_at: null,
next_poll_at: null,
last_job_count: 0,
total_executions: 0,
active_job_id: null,
active_job_name: null,
};
}
async #getAutomationHealth(): Promise<Record<string, unknown>> {
const jobs = await this.#listAutomationJobs();
return {
total_jobs: jobs.length,
enabled_jobs: jobs.filter((job) => readBoolean(job, "enabled") ?? false)
.length,
pending_jobs: 0,
running_jobs: jobs.filter(
(job) => readString(job, "last_status") === "running",
).length,
failed_jobs: jobs.filter((job) =>
["error", "timeout"].includes(readString(job, "last_status") ?? ""),
).length,
cooldown_jobs: jobs.filter((job) =>
Boolean(readString(job, "auto_disabled_until")),
).length,
stale_running_jobs: 0,
failed_last_24h: 0,
failure_trend_24h: [],
alerts: [],
risky_jobs: [],
generated_at: new Date().toISOString(),
};
}
#getUsageStats(): Record<string, unknown> {
return {
total_conversations: 0,
@@ -1884,6 +1956,36 @@ function readRequiredString(value: unknown, key: string): string {
return next;
}
function readElectronPath(name: ElectronKnownPathName): string | null {
try {
const next = app.getPath(name);
return next.trim() ? next : null;
} catch {
return null;
}
}
async function appendFileManagerLocation(
locations: FileManagerLocation[],
seenPaths: Set<string>,
location: FileManagerLocationCandidate,
): Promise<void> {
const normalizedPath = location.path?.trim() ?? "";
if (!normalizedPath || seenPaths.has(normalizedPath)) {
return;
}
try {
const metadata = await stat(normalizedPath);
if (!metadata.isDirectory()) {
return;
}
} catch {
return;
}
seenPaths.add(normalizedPath);
locations.push({ ...location, path: normalizedPath });
}
function readBoolean(value: unknown, key: string): boolean | null {
const record = toRecord(value);
if (!record || typeof record[key] !== "boolean") {
+34 -14
View File
@@ -35,16 +35,18 @@ describe("electron/ipcChannels", () => {
expect(
isElectronHostCommand("agent_runtime_list_workspace_skill_bindings"),
).toBe(true);
expect(isElectronHostCommand("fetch_provider_models_auto")).toBe(true);
expect(isElectronHostCommand("get_provider_ui_state")).toBe(true);
expect(isElectronHostCommand("fetch_provider_models_auto")).toBe(false);
expect(isElectronHostCommand("get_provider_ui_state")).toBe(false);
expect(isElectronHostCommand("get_local_skills_for_app")).toBe(true);
expect(isElectronHostCommand("get_automation_jobs")).toBe(true);
expect(isElectronHostCommand("get_automation_scheduler_config")).toBe(true);
expect(isElectronHostCommand("get_automation_status")).toBe(true);
expect(isElectronHostCommand("get_automation_health")).toBe(true);
expect(isElectronHostCommand("get_automation_jobs")).toBe(false);
expect(isElectronHostCommand("get_automation_scheduler_config")).toBe(
false,
);
expect(isElectronHostCommand("get_automation_status")).toBe(false);
expect(isElectronHostCommand("get_automation_health")).toBe(false);
expect(isElectronHostCommand("project_memory_get")).toBe(true);
expect(isElectronHostCommand("get_model_registry")).toBe(true);
expect(isElectronHostCommand("get_api_key_providers")).toBe(true);
expect(isElectronHostCommand("get_api_key_providers")).toBe(false);
expect(isElectronHostCommand("list_dir")).toBe(false);
expect(isElectronHostCommand("read_file_preview_cmd")).toBe(false);
expect(isElectronHostCommand("list_executable_skills")).toBe(true);
@@ -53,6 +55,9 @@ describe("electron/ipcChannels", () => {
expect(isElectronHostCommand("get_config")).toBe(true);
expect(isElectronHostCommand("get_experimental_config")).toBe(true);
expect(isElectronHostCommand("save_experimental_config")).toBe(true);
expect(isElectronHostCommand("get_file_icon_data_url")).toBe(true);
expect(isElectronHostCommand("get_file_manager_locations")).toBe(true);
expect(isElectronHostCommand("get_home_dir")).toBe(true);
expect(isElectronHostCommand("agent_app_list_installed")).toBe(true);
expect(isElectronHostCommand("agent_app_runtime_start_task")).toBe(true);
expect(isElectronHostCommand("agent_app_runtime_cancel_task")).toBe(true);
@@ -84,6 +89,8 @@ describe("electron/ipcChannels", () => {
expect(isElectronHostCommand("mcp_list_servers_with_status")).toBe(true);
expect(isElectronHostCommand("site_get_adapter_catalog_status")).toBe(true);
expect(isElectronHostCommand("open_external_url")).toBe(true);
expect(isElectronHostCommand("open_with_default_app")).toBe(true);
expect(isElectronHostCommand("reveal_in_finder")).toBe(true);
expect(isElectronHostCommand("start_oem_cloud_oauth_callback_bridge")).toBe(
true,
);
@@ -140,15 +147,10 @@ describe("electron/ipcChannels", () => {
"agent_runtime_submit_turn",
"agent_runtime_update_session",
"aster_agent_init",
"fetch_provider_models_auto",
"get_all_alias_configs",
"get_api_key_providers",
"get_automation_health",
"get_automation_jobs",
"get_automation_scheduler_config",
"get_automation_status",
"get_default_provider",
"get_local_skills_for_app",
"get_mcp_servers",
"get_model_preferences",
"get_model_registry",
"get_model_registry_provider_ids",
@@ -157,9 +159,12 @@ describe("electron/ipcChannels", () => {
"get_models_for_provider",
"get_provider_alias_config",
"get_skill_detail",
"get_system_provider_catalog",
"knowledge_list_packs",
"list_executable_skills",
"mcp_list_prompts",
"mcp_list_resources",
"mcp_list_servers_with_status",
"mcp_list_tools",
"project_memory_get",
"workspace_ensure_default_ready",
"workspace_ensure_ready",
@@ -185,6 +190,21 @@ describe("electron/ipcChannels", () => {
"start_oem_cloud_oauth_callback_bridge" as never,
),
).toBe(false);
expect(
ELECTRON_APP_SERVER_TRUTH_BRIDGE_COMMANDS.includes(
"get_file_icon_data_url" as never,
),
).toBe(false);
expect(
ELECTRON_APP_SERVER_TRUTH_BRIDGE_COMMANDS.includes(
"get_file_manager_locations" as never,
),
).toBe(false);
expect(
ELECTRON_APP_SERVER_TRUTH_BRIDGE_COMMANDS.includes(
"get_home_dir" as never,
),
).toBe(false);
});
it("IPC channel 命名保持 app 前缀,避免裸 ipcRenderer 通道扩散", () => {
+10 -16
View File
@@ -34,12 +34,6 @@ export const ELECTRON_HOST_COMMANDS = [
"agent_app_runtime_cancel_task",
"agent_app_runtime_get_task",
"agent_app_runtime_submit_host_response",
"fetch_provider_models_auto",
"get_api_key_providers",
"get_automation_health",
"get_automation_jobs",
"get_automation_scheduler_config",
"get_automation_status",
"get_browser_backend_policy",
"get_browser_backends_status",
"get_browser_connector_install_status_cmd",
@@ -52,6 +46,9 @@ export const ELECTRON_HOST_COMMANDS = [
"get_default_provider",
"get_environment_preview",
"get_experimental_config",
"get_file_icon_data_url",
"get_file_manager_locations",
"get_home_dir",
"get_all_alias_configs",
"get_asr_credentials",
"get_local_skills_for_app",
@@ -65,9 +62,7 @@ export const ELECTRON_HOST_COMMANDS = [
"get_models_for_provider",
"get_or_create_default_project",
"get_provider_alias_config",
"get_provider_ui_state",
"get_skill_package_file_association_status",
"get_system_provider_catalog",
"get_usage_stats",
"get_voice_input_config",
"get_voice_instructions",
@@ -81,13 +76,14 @@ export const ELECTRON_HOST_COMMANDS = [
"mcp_list_servers_with_status",
"mcp_list_tools",
"open_external_url",
"open_with_default_app",
"project_memory_get",
"reveal_in_finder",
"report_frontend_crash",
"report_frontend_debug_log",
"save_config",
"save_experimental_config",
"set_skill_package_file_association_default",
"set_provider_ui_state",
"site_get_adapter_catalog_status",
"site_list_adapters",
"start_oem_cloud_oauth_callback_bridge",
@@ -150,15 +146,10 @@ export const ELECTRON_APP_SERVER_TRUTH_BRIDGE_COMMANDS = [
"agent_runtime_list_sessions",
"agent_runtime_get_session",
"agent_runtime_list_workspace_skill_bindings",
"fetch_provider_models_auto",
"get_api_key_providers",
"get_all_alias_configs",
"get_automation_health",
"get_automation_jobs",
"get_automation_scheduler_config",
"get_automation_status",
"get_default_provider",
"get_local_skills_for_app",
"get_mcp_servers",
"get_model_preferences",
"get_model_registry",
"get_model_registry_provider_ids",
@@ -166,10 +157,13 @@ export const ELECTRON_APP_SERVER_TRUTH_BRIDGE_COMMANDS = [
"get_models_by_tier",
"get_models_for_provider",
"get_provider_alias_config",
"get_system_provider_catalog",
"get_skill_detail",
"knowledge_list_packs",
"list_executable_skills",
"mcp_list_prompts",
"mcp_list_resources",
"mcp_list_servers_with_status",
"mcp_list_tools",
"project_memory_get",
"workspace_ensure_default_ready",
"workspace_ensure_ready",
+1 -1
View File
@@ -52,7 +52,7 @@ import {
type SaveDialogOptions,
Tray,
type IpcMainInvokeEvent,
} from "electron";
} from "./electronRuntime";
import { spawn } from "node:child_process";
import { existsSync, mkdirSync, readFileSync } from "node:fs";
import path from "node:path";
+282
View File
@@ -0,0 +1,282 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
ELECTRON_HOST_COMMANDS,
IPC_DEEP_LINK_GET_CURRENT_CHANNEL,
IPC_DEEP_LINK_GET_URLS_CHANNEL,
IPC_DIALOG_OPEN_CHANNEL,
IPC_DIALOG_SAVE_CHANNEL,
IPC_EMIT_CHANNEL,
IPC_GLOBAL_SHORTCUT_COMMAND_CHANNEL,
IPC_INVOKE_CHANNEL,
IPC_SHELL_OPEN_CHANNEL,
IPC_WINDOW_COMMAND_CHANNEL,
} from "./ipcChannels";
const exposedGlobals = new Map<string, unknown>();
const ipcInvoke = vi.hoisted(() => vi.fn());
const ipcSend = vi.hoisted(() => vi.fn());
const ipcOn = vi.hoisted(() => vi.fn());
const ipcRemoveListener = vi.hoisted(() => vi.fn());
const exposeInMainWorld = vi.hoisted(() =>
vi.fn((key: string, value: unknown) => {
exposedGlobals.set(key, value);
}),
);
vi.mock("electron", () => ({
contextBridge: {
exposeInMainWorld,
},
ipcRenderer: {
invoke: ipcInvoke,
send: ipcSend,
on: ipcOn,
removeListener: ipcRemoveListener,
},
}));
type PreloadApi = {
devBridgeFallback: false;
supportsCommand(command: string): boolean;
invoke<T = unknown>(
command: string,
args?: Record<string, unknown>,
): Promise<T>;
listen(event: string, handler: (event: unknown) => void): () => void;
on(event: string, handler: (event: unknown) => void): () => void;
emit(event: string, payload?: unknown): Promise<void>;
send(event: string, payload?: unknown): void;
convertFileSrc(filePath: string, protocol?: string): string;
dialog: {
open(options?: unknown): Promise<unknown>;
save(options?: unknown): Promise<unknown>;
};
shell: {
open(target: string, openWith?: string): Promise<unknown>;
};
window: {
show(): Promise<unknown>;
setTitle(title: string): Promise<unknown>;
setSize(width: number, height: number): Promise<unknown>;
isVisible(): Promise<boolean>;
};
globalShortcut: {
register(shortcut: string): Promise<unknown>;
unregister(shortcut: string): Promise<unknown>;
unregisterAll(): Promise<unknown>;
isRegistered(shortcut: string): Promise<unknown>;
};
deepLink: {
onOpenUrl(handler: (urls: string[]) => void): () => void;
getUrls(): Promise<unknown>;
getCurrent(): Promise<unknown>;
};
};
async function loadPreloadApi(): Promise<PreloadApi> {
vi.resetModules();
exposedGlobals.clear();
await import("./preload");
const api = exposedGlobals.get("electronAPI");
if (!api || typeof api !== "object") {
throw new Error("preload did not expose electronAPI");
}
return api as PreloadApi;
}
describe("electron/preload", () => {
beforeEach(() => {
ipcInvoke.mockReset();
ipcSend.mockReset();
ipcOn.mockReset();
ipcRemoveListener.mockReset();
exposeInMainWorld.mockClear();
exposedGlobals.clear();
});
it("暴露真实 Electron bridge 标记并用 host command 白名单判断支持命令", async () => {
const api = await loadPreloadApi();
expect(exposeInMainWorld).toHaveBeenCalledWith("electronAPI", api);
expect(exposeInMainWorld).toHaveBeenCalledWith("__LIME_ELECTRON__", true);
expect(api.devBridgeFallback).toBe(false);
expect(api.supportsCommand(ELECTRON_HOST_COMMANDS[0])).toBe(true);
expect(api.supportsCommand("capability_draft_list_registered_skills")).toBe(
false,
);
});
it("通过统一 app:invoke 通道转发命令并解包成功响应", async () => {
const api = await loadPreloadApi();
ipcInvoke.mockResolvedValueOnce({
ok: true,
result: { lines: ["{}"] },
});
await expect(
api.invoke("app_server_handle_json_lines", { lines: ["{}"] }),
).resolves.toEqual({ lines: ["{}"] });
expect(ipcInvoke).toHaveBeenCalledWith(
IPC_INVOKE_CHANNEL,
"app_server_handle_json_lines",
{ lines: ["{}"] },
);
});
it("Electron invoke 失败响应必须 fail closed 并保留错误元数据", async () => {
const api = await loadPreloadApi();
ipcInvoke.mockResolvedValueOnce({
ok: false,
error: {
code: "unsupported_command",
message: "Desktop Host 尚未支持命令",
data: { command: "legacy_command" },
},
});
await expect(api.invoke("legacy_command")).rejects.toMatchObject({
message: "Desktop Host 尚未支持命令",
code: "unsupported_command",
data: { command: "legacy_command" },
});
});
it("事件监听统一归一化 payload,并返回反注册函数", async () => {
const api = await loadPreloadApi();
const handler = vi.fn();
const dispose = api.listen("agent-runtime:event", handler);
const listener = ipcOn.mock.calls[0]?.[1] as
| ((_event: unknown, payload: unknown) => void)
| undefined;
expect(ipcOn).toHaveBeenCalledWith("evt:agent-runtime:event", listener);
expect(listener).toBeTypeOf("function");
listener?.({}, { step: "ready" });
expect(handler).toHaveBeenCalledWith({
event: "agent-runtime:event",
payload: { step: "ready" },
});
listener?.({}, { event: "agent-runtime:event", payload: { step: "done" } });
expect(handler).toHaveBeenCalledWith({
event: "agent-runtime:event",
payload: { step: "done" },
});
dispose();
expect(ipcRemoveListener).toHaveBeenCalledWith(
"evt:agent-runtime:event",
listener,
);
});
it("桌面壳能力保持固定 IPC channel,不绕过 Electron Host", async () => {
const api = await loadPreloadApi();
ipcInvoke.mockResolvedValue(undefined);
await api.dialog.open({ properties: ["openFile"] });
await api.dialog.save({ defaultPath: "draft.md" });
await api.shell.open("/tmp/draft.md", "Preview");
await api.window.show();
await api.window.setTitle("Lime");
await api.window.setSize(1024, 768);
await api.globalShortcut.register("CommandOrControl+L");
await api.globalShortcut.unregister("CommandOrControl+L");
await api.globalShortcut.unregisterAll();
await api.globalShortcut.isRegistered("CommandOrControl+L");
await api.deepLink.getUrls();
await api.deepLink.getCurrent();
api.send("frontend:ready", { ok: true });
await api.emit("frontend:loaded");
expect(ipcInvoke).toHaveBeenCalledWith(IPC_DIALOG_OPEN_CHANNEL, {
properties: ["openFile"],
});
expect(ipcInvoke).toHaveBeenCalledWith(IPC_DIALOG_SAVE_CHANNEL, {
defaultPath: "draft.md",
});
expect(ipcInvoke).toHaveBeenCalledWith(
IPC_SHELL_OPEN_CHANNEL,
"/tmp/draft.md",
"Preview",
);
expect(ipcInvoke).toHaveBeenCalledWith(
IPC_WINDOW_COMMAND_CHANNEL,
"show",
);
expect(ipcInvoke).toHaveBeenCalledWith(
IPC_WINDOW_COMMAND_CHANNEL,
"setTitle",
"Lime",
);
expect(ipcInvoke).toHaveBeenCalledWith(
IPC_WINDOW_COMMAND_CHANNEL,
"setSize",
1024,
768,
);
expect(ipcInvoke).toHaveBeenCalledWith(
IPC_GLOBAL_SHORTCUT_COMMAND_CHANNEL,
"register",
"CommandOrControl+L",
);
expect(ipcInvoke).toHaveBeenCalledWith(
IPC_GLOBAL_SHORTCUT_COMMAND_CHANNEL,
"unregister",
"CommandOrControl+L",
);
expect(ipcInvoke).toHaveBeenCalledWith(
IPC_GLOBAL_SHORTCUT_COMMAND_CHANNEL,
"unregisterAll",
);
expect(ipcInvoke).toHaveBeenCalledWith(
IPC_GLOBAL_SHORTCUT_COMMAND_CHANNEL,
"isRegistered",
"CommandOrControl+L",
);
expect(ipcInvoke).toHaveBeenCalledWith(IPC_DEEP_LINK_GET_URLS_CHANNEL);
expect(ipcInvoke).toHaveBeenCalledWith(IPC_DEEP_LINK_GET_CURRENT_CHANNEL);
expect(ipcSend).toHaveBeenCalledWith("app:emit", "frontend:ready", {
ok: true,
});
expect(ipcSend).toHaveBeenCalledWith(IPC_EMIT_CHANNEL, "frontend:loaded", undefined);
});
it("deep link 事件只向 renderer 传递数组 URL 列表", async () => {
const api = await loadPreloadApi();
const handler = vi.fn();
const dispose = api.deepLink.onOpenUrl(handler);
const listener = ipcOn.mock.calls[0]?.[1] as
| ((_event: unknown, payload: unknown) => void)
| undefined;
expect(ipcOn).toHaveBeenCalledWith("evt:deep-link-open-url", listener);
listener?.({}, { payload: ["lime://connect?id=1"] });
listener?.({}, { payload: "lime://connect?id=2" });
expect(handler).toHaveBeenNthCalledWith(1, ["lime://connect?id=1"]);
expect(handler).toHaveBeenNthCalledWith(2, []);
dispose();
expect(ipcRemoveListener).toHaveBeenCalledWith(
"evt:deep-link-open-url",
listener,
);
});
it("convertFileSrc 只处理本地路径,不改写已有 URL", async () => {
const api = await loadPreloadApi();
expect(api.convertFileSrc("https://example.com/a.png")).toBe(
"https://example.com/a.png",
);
expect(api.convertFileSrc("/tmp/a.png")).toBe("file:///tmp/a.png");
expect(api.convertFileSrc("/tmp/a b.png", "asset")).toBe(
"asset://%2Ftmp%2Fa%20b.png",
);
});
});
+1 -1
View File
@@ -10,7 +10,7 @@ const { appState, checkForUpdatesMock, quitAndInstallMock, setFeedURLMock } =
setFeedURLMock: vi.fn(),
}));
vi.mock("electron", () => ({
vi.mock("./electronRuntime", () => ({
app: {
getVersion: () => "1.60.0",
get isPackaged() {
+1 -1
View File
@@ -1,4 +1,4 @@
import { app, autoUpdater } from "electron";
import { app, autoUpdater } from "./electronRuntime";
type UpdateInstallStage =
| "idle"
+49 -9
View File
@@ -85,13 +85,37 @@ Connect deep link 已切到 Electron Desktop Host URL bridge 与 App Server JSON
`poster_material_*` 命名只允许停留在 schema 迁移与治理守卫中,不应重新出现在前端网关、Rust 命令模块或运行时代码里。
模型 Provider 真相集同样遵循单一事实源。当前前端入口为 `src/lib/api/modelRegistry.ts` 中的
模型 Provider 真相集同样遵循单一事实源。当前前端入口为 `src/lib/api/modelRegistry.ts` `src/lib/api/apiKeyProvider.ts` 中的 App Server JSON-RPC methods
- `get_model_registry_provider_ids`
- `model/list`
- `modelPreferences/list`
- `modelSyncState/read`
- `modelProvider/list`
- `modelProvider/catalog/list`
- `modelProvider/read`
- `modelProvider/create`
- `modelProvider/update`
- `modelProvider/delete`
- `modelProvider/sortOrders/update`
- `modelProvider/fetchModels`
- `modelProvider/testConnection`
- `modelProvider/testChat`
- `modelProviderConfig/export`
- `modelProviderConfig/import`
- `modelProviderKey/create`
- `modelProviderKey/update`
- `modelProviderKey/delete`
- `modelProviderKey/next`
- `modelProviderKey/usage/record`
- `modelProviderKey/error/record`
- `modelProviderUiState/read`
- `modelProviderUiState/write`
- `modelProviderAlias/read`
- `modelProviderAlias/list`
`lime-rs/resources/models` 本地模型 catalog 已下线;Provider 元信息以 `get_system_provider_catalog` 为事实源,模型列表以 Provider 实时 `/models` 接口和用户显式 `custom_models` 为事实源。Provider 实时 `/models` 的成功结果可以由 `ModelRegistryService` 持久化缓存 10 天,所有读取先查缓存再访问上游,但该缓存只保存真实接口结果,不得退化成本地 catalog 兜底。`get_model_registry_provider_ids` 仅作为兼容命令保留空结果,不应再读取本地资源、数据库或运行态缓存去“猜” provider 集合。
`lime-rs/resources/models` 本地模型 catalog 已下线;Provider 元信息以 `modelProvider/catalog/list` 为事实源,Provider 配置 / API Key / UI state / 连接测试 / 模型实时拉取均以 `modelProvider*` / `modelProviderKey*` / `modelProviderUiState*` App Server method 为事实源。Provider 实时 `/models` 的成功结果可以由 `ModelRegistryService` 持久化缓存 10 天,所有读取先查缓存再访问上游,但该缓存只保存真实接口结果,不得退化成本地 catalog 兜底。`get_model_registry_provider_ids` 仅作为兼容命令保留空结果,不应再读取本地资源、数据库或运行态缓存去“猜” provider 集合。旧 Provider façade 命令族已判为 `dead``get_api_key_providers``get_system_provider_catalog``get_api_key_provider``read_api_key_provider_config``add_custom_api_key_provider``create_api_key_provider``update_api_key_provider``delete_custom_api_key_provider``delete_api_key_provider``update_provider_sort_orders``update_api_key_provider_sort_orders``export_api_key_providers``export_api_key_provider_config``import_api_key_providers``import_api_key_provider_config``test_api_key_provider_connection``test_api_key_provider_chat``fetch_provider_models_auto``add_api_key``create_api_key_provider_key``delete_api_key``delete_api_key_provider_key``toggle_api_key``update_api_key_alias``update_api_key_provider_key``get_next_api_key``next_api_key_provider_key``record_api_key_usage``record_api_key_provider_key_usage``record_api_key_error``record_api_key_provider_key_error``get_provider_ui_state``read_api_key_provider_ui_state``set_provider_ui_state``write_api_key_provider_ui_state`;不得重新接回前端网关、Electron Host、DevBridge truth、mock priority、runtime surface、Tauri runner 或 Rust dispatcher。
同理,聊天运行时初始化的 `aster_agent_init` 在浏览器 DevBridge 模式下也不能再被放进 `mockPriorityCommands`。只要桥接在线,它就必须优先读取后端真实 `provider_name / model_name`,让聊天入口拿到当前运行时模型。
进一步地,围绕运行时模型解析的真相命令`aster_agent_init``get_default_provider``get_api_key_providers``get_model_registry``get_provider_alias_config``fetch_provider_models_auto``get_model_registry_provider_ids`,在浏览器 DevBridge 模式下如果桥接失败,必须直接抛错,不能再通过 `safeInvoke` 静默退回 mock;否则前端会把“后端未连上 / 命令失败”误显示成假的 Provider / 模型列表。旧 `get_provider_pool_overview` 属于凭证池命令面,已随凭证池退役,不得重新作为运行时模型解析事实源。
进一步地,围绕运行时模型解析的真相入口`aster_agent_init``get_default_provider``model/list``modelProvider/list``modelProvider/catalog/list``modelProvider/fetchModels``modelProviderAlias/read``modelProviderAlias/list``get_model_registry_provider_ids`,在浏览器 DevBridge 模式下如果桥接失败,必须直接抛错,不能再通过 `safeInvoke` 静默退回 mock;否则前端会把“后端未连上 / 命令失败”误显示成假的 Provider / 模型列表。旧 `get_provider_pool_overview` 属于凭证池命令面,已随凭证池退役,不得重新作为运行时模型解析事实源。
同时要明确,`aster_agent_init` 只负责初始化 Agent,并不保证已经完成 Provider 配置;当它未返回 `provider_name / model_name` 时,前端不得把本地硬编码默认值当作真实模型,而应继续回退到 `get_default_provider` + 已配置 Provider/模型注册表解析链,拿到当前工作区真正可用的 `provider/model`
同一条约束也适用于 Prompt Cache 能力判断:运行时与前端都不得因为某个自定义 Provider “长得像 Anthropic 协议”就推断它支持官方 Anthropic Automatic Prompt Caching。当前事实源必须继续按 ProviderType 判断:`anthropic` 走自动缓存能力,`anthropic-compatible` 只保留显式 `cache_control` 语义;若上游没有实现 Automatic Prompt Cache`cached_input_tokens` 为空不能直接归因到 Lime 没发字段。
@@ -186,7 +210,7 @@ Claw / Aster 原完整执行链是 Agent 对话 runtime 的 current 参考实现
前端从本地存储恢复出的 Agent session id 在发送前必须先被 App Server `agentSession/read` 确认存在且归属当前 workspace。明确 `session not found` 或 workspace mismatch 时只能丢弃本地恢复快照并创建新的 App Server session;普通 bridge / network error 必须 fail closed,不得静默创建会话或继续轮询 stale session。Electron `safeInvoke` 返回的 App Server JSON-RPC result envelope 也必须在 `src/lib/api/appServer.ts` 网关层统一解包,业务页面不得各自猜测 `{ result: { lines } }` / `{ lines }` 形状。
`agent_app_runtime_*` 是 Agent App 进入 App Server / AgentRuntime 主链的 Desktop facade`agent_app_runtime_start_task` 必须经 App Server JSON-RPC `agentSession/turn/start` 进入 `RuntimeCore -> AsterBackend -> backend host`,不能再直接复制 `AsterChatRequest -> build_queued_turn_task` 提交流程。`startTask.turnConfig` 必须随 facade 透传,并写入 `RuntimeOptions.hostOptions.asterChatRequest`:扁平 `AsterChatRequest` 字段供 Desktop Aster host 恢复 Claw 原链,`turn_config` 镜像供外部 App Server backend 读取 provider_config / system_prompt / reasoning / sandbox 等配置。cancel / read / host response 在 App Server protocol 覆盖前可继续作为 Desktop compat 适配到既有 `agent_runtime_*` 读写命令,但不得复制 Claw `*_skill_launch.rs`、不得新增垂直 `content_factory_*` Agent 命令,也不得把 `LIME_GATEWAY_*` 直接模型调用宣称为完整 Agent 能力。
`agent_app_runtime_*` 是 Agent App 进入 App Server / AgentRuntime 主链的 Desktop facade`agent_app_runtime_start_task` 必须经 App Server JSON-RPC `agentSession/turn/start` 进入 `RuntimeCore -> AsterBackend -> backend host`,不能再直接复制 `AsterChatRequest -> build_queued_turn_task` 提交流程。`startTask.turnConfig` 必须随 facade 透传,并写入 `RuntimeOptions.hostOptions.asterChatRequest`:扁平 `AsterChatRequest` 字段供 Desktop Aster host 恢复 Claw 原链,`turn_config` 镜像供外部 App Server backend 读取 provider*config / system_prompt / reasoning / sandbox 等配置。cancel / read / host response 在 App Server protocol 覆盖前可继续作为 Desktop compat 适配到既有 `agent_runtime*_`读写命令,但不得复制 Claw`**skill_launch.rs`、不得新增垂直 `content_factory**`Agent 命令,也不得把`LIME*GATEWAY*_` 直接模型调用宣称为完整 Agent 能力。
P17.3 之前禁止真实删除 Agent App 本地数据:`agent_app_uninstall_rehearsal` 只生成 keep-data / delete-data 演练,`agent_app_uninstall` 只能返回同一演练摘要和未删除的 installed list,不得执行 `remove_file` / `remove_dir_all` 或移除 installed state。真实 delete-data 必须等后续路线图单独打开并补齐 evidence / residual audit / confirmation gate。
@@ -416,7 +440,25 @@ Skill 执行链路同样遵循单一命令边界。当前前端入口为 `src/li
这些命令如果仍处在 legacy desktop facade 兼容期,也必须继续保持 DevBridge dispatcher 已桥接;current 新能力优先同步 Electron Desktop Host bridge / App Server JSON-RPC,避免浏览器模式、Electron smoke 或 Playwright 续测时回退成 unknown command。
自动化设置链路同样遵循这条路径。当前主入口为 `src/lib/api/automation.ts`,统一承接:
自动化设置链路同样遵循这条路径。当前主入口为 `src/lib/api/automation.ts`,统一`AppServerClient.request(...)` 承接:
- `automationScheduler/config/read`
- `automationScheduler/config/update`
- `automationScheduler/status`
- `automationJob/list`
- `automationJob/read`
- `automationJob/create`
- `automationJob/update`
- `automationJob/delete`
- `automationJob/runNow`
- `automationJob/health`
- `automationJob/runHistory`
- `automationSchedule/preview`
- `automationSchedule/validate`
这些 App Server JSON-RPC method 属于当前 `设置 -> 系统 -> 自动化` 主路径,缺少必需 result 时必须 fail closed,不得回退 DevBridge mock、renderer mock 或 legacy desktop facade。`automationJob/runNow` 已进入 current 协议,但 App Server 自动化执行器尚未迁完;当前实现必须 fail closed,不能回退旧 Tauri 执行器。
旧自动化命令统一判为 `dead`,不得重新接回前端网关、Electron Desktop Host 白名单、Rust `generate_handler!`、DevBridge dispatcher、`mockPriorityCommands` 或默认 mock
- `get_automation_scheduler_config`
- `update_automation_scheduler_config`
@@ -432,8 +474,6 @@ Skill 执行链路同样遵循单一命令边界。当前前端入口为 `src/li
- `preview_automation_schedule`
- `validate_automation_schedule`
这些命令属于当前 `设置 -> 系统 -> 自动化` 主路径。浏览器模式下如已接通 DevBridge,应优先走真实后端;不要因为 dispatcher 漏接而长期依赖 mock 掩盖设置页报错。
Companion 桌宠链路同样遵循这条路径。当前主入口为 `src/lib/api/companion.ts`,统一承接:
- `companion_get_pet_status`
@@ -632,7 +672,7 @@ npm run verify:local
## 自动化 `agent_turn` 负载补充约定
`create_automation_job` / `update_automation_job``payload.kind = "agent_turn"` 用于持续产出交付物时,允许并推荐透传以下字段:
`automationJob/create` / `automationJob/update``payload.kind = "agent_turn"` 用于持续产出交付物时,允许并推荐透传以下字段:
- `content_id`:绑定长期内容主线,供自动化版本持续沉淀到同一交付链
- `request_metadata`:与运行时 turn 保持同合同,至少可包含 `artifact``harness` 两层
+1 -1
View File
@@ -435,7 +435,7 @@ npm run verify:gui-smoke -- --include-knowledge-product-e2e --reuse-running
1. 进入 `设置 -> 系统 -> 自动化`
2. 确认调度状态、任务列表、健康面板能正常加载
3. 打开控制台,确认浏览器模式接通 DevBridge 时不再出现 `get_automation_jobs``get_automation_health``get_automation_run_history` 的 unknown command 报错
3. 打开控制台 / 网络证据,确认自动化读取经 `app_server_handle_json_lines` 观察到 `automationJob/list``automationJob/health``automationJob/runHistory` 等 App Server method,且没有旧 automation 命令族调用
4. 如当前环境允许创建或编辑任务,再确认提交后列表能刷新,而不是只靠 web mock 静态回显
### 话题模型恢复验证
+2 -2
View File
@@ -359,7 +359,7 @@ npm run verify:gui-smoke -- --include-knowledge-product-e2e --reuse-running
这类问题 **单靠** `lint``typecheck``vitest` 无法覆盖。
默认 `npm run verify:local``npm test``cargo test --manifest-path "lime-rs/Cargo.toml"``npm run verify:gui-smoke` 不允许消耗真实模型 / 图片 Provider 额度。会调用 `agent_runtime_submit_turn``test_api_key_provider_chat`、图片生成、embedding、ASR 或 live AgentRuntime transcript 的测试 / smoke,必须显式 opt-in
默认 `npm run verify:local``npm test``cargo test --manifest-path "lime-rs/Cargo.toml"``npm run verify:gui-smoke` 不允许消耗真实模型 / 图片 Provider 额度。会调用 `agent_runtime_submit_turn`App Server `modelProvider/testChat`、图片生成、embedding、ASR 或 live AgentRuntime transcript 的测试 / smoke,必须显式 opt-in
```bash
npm run verify:gui-smoke -- --include-live-provider-smokes
@@ -467,7 +467,7 @@ CI 里的 `.github/workflows/quality.yml` 结果摘要现在也会透出 `bridge
- 修改首页 / 工作区进入 `Claw` 时的首条自动发送上下文,例如 `initialUserPrompt``initialAutoSendRequestMetadata``harness.service_skill_launch`
- 修改 `site_*` 站点适配器命令族,例如 `site_recommend_adapters``site_get_adapter_launch_readiness``site_import_adapter_yaml_bundle``site_run_adapter`
- 修改 `companion_get_pet_status``companion_launch_pet``companion_send_pet_command`,或调整 Lime 与独立桌宠之间的本地 companion 协议(例如 `pet.provider_overview``pet.open_provider_settings``pet.request_provider_overview_sync``pet.request_pet_cheer``pet.request_pet_next_step``pet.request_chat_reply`
- 修改自动化设置命令族,例如 `get_automation_jobs``create_automation_job``update_automation_job``get_automation_health``get_automation_run_history`,尤其是它们在浏览器模式 DevBridge 与 mock 间的分流
- 修改自动化设置 App Server method 族,例如 `automationJob/list``automationJob/create``automationJob/update``automationJob/health``automationJob/runHistory`;旧 `get_automation_*` / `*_automation_job` 命令已退役,只能作为负向回归扫描对象
- 修改浏览器资料 / 环境预设命令族,或调整它们在 `mockPriorityCommands` 里的优先级
- 修改浏览器连接器命令族,例如安装目录、启用状态、系统连接器、浏览器动作配置、扩展安装状态、打开 Chrome 扩展 / 远程调试页,或主动断开扩展连接
- 修改 `get_model_registry_provider_ids` 兼容空结果、Provider 实时模型读取或用户 `custom_models` 合并语义
@@ -1280,8 +1280,8 @@ git diff --check
264. P3.136 文档守卫:`scripts/electron/current-docs-guard.test.mjs` 已把 `frontend-electron-migration.md` 纳入 high-weight current guidance 扫描,并新增 frontend host current contract 守卫,锁定 Electron 只负责 Desktop Host bridge、preload / IPC 白名单、窗口、托盘、Dock、菜单、updater、签名发布和 sidecar 生命周期,不是第二套后端或 Agent runtime adapter;同时锁定 Codex CLI / `codex-rs` 只提供 App Server 分层参考,Lime 不参考 Codex App UI 或桌面壳实现。`internal/roadmap/appserver/README.md` 索引也已从“切换到 Electron”改为 “Electron Desktop Host current 契约”。
265. P3.136 增量验证:`npx vitest run "scripts/electron/current-docs-guard.test.mjs" --silent=passed-only --disableConsoleIntercept` 通过,10 个 docs guard 测试;`node --check "scripts/check-app-server-client-contract.mjs" && node "scripts/check-app-server-client-contract.mjs"` 通过,App Server client contract guard 121 项;`npx prettier --check` 覆盖本轮文档与守卫文件通过;`git diff --check` 覆盖本轮认领文件通过;`npm run test:contracts` 完整通过,覆盖 agent runtime clients、App Server client contract、command / harness / modality / cleanup / docs boundary。本轮只改 current 文档和守卫,未改 Electron 运行时代码,因此不重复跑 GUI smoke;下一刀应回到真实 GUI 发送后的 event/read model E2E 或 artifact/evidence UI 接入。
266. P3.137 production mock / live-provider gate 收口:继续按“生产不能 mock,只有测试才 mock”规则补 current 主链守卫,本轮只认领 `scripts/check-command-contracts.mjs``scripts/check-app-server-client-contract.mjs``internal/roadmap/appserver/frontend-integration-matrix.md` 与本执行计划记录。`scripts/check-command-contracts.mjs` 的生产 mock-only 扫描已覆盖 `src / electron / packages`,锁住 Electron Desktop Host main、renderer runtime 与 `packages/app-server-client` 不能导入或调用 `explicitMockFallback``invokeMockOnly``mockCommand``clearMocks``invokeExplicitMock``listenExplicitMock`;前端命令统计仍只扫描 `src`,避免 Electron main 内部桥接被误判为前端命令调用。
267. P3.137 Claw streaming live-provider gate`scripts/check-app-server-client-contract.mjs` 已锁定 `scripts/claw-chat-ready-streaming-smoke.mjs` 必须在 `wait-health``prepare-runtime``aster_agent_init``get_api_key_providers``test_api_key_provider_chat` 之前调用 `assertLiveProviderSmokeAllowed(...)`。完整 Claw streaming GUI E2E 必须显式传入 `--allow-live-provider``LIME_ALLOW_LIVE_PROVIDER_SMOKE=1`;未授权时 smoke 必须 fail closed,不能用 mock、无模型探针或 Provider 自动探测替代真实业务闭环。
268. P3.137 未授权 gate 实测:不传 `--allow-live-provider` 运行 `node scripts/claw-chat-ready-streaming-smoke.mjs --timeout-ms 30000 --evidence-dir ".lime/qc/gui-evidence/claw-chat-ready-streaming-gate-check" --prefix "gate-check"`,按预期失败在 live-provider 授权检查,未授权 gate 实测在 `wait-health``prepare-runtime` 和 Provider 探测前 fail closed,未进入 `aster_agent_init``get_api_key_providers``test_api_key_provider_chat`。本轮未运行带 `--allow-live-provider` 的真实模型调用,完整真实 GUI sending E2E 仍需用户明确允许真实 Provider 调用后再执行。
267. P3.137 Claw streaming live-provider gate`scripts/check-app-server-client-contract.mjs` 已锁定 `scripts/claw-chat-ready-streaming-smoke.mjs` 必须在 `wait-health``prepare-runtime``aster_agent_init`App Server `modelProvider/list``modelProvider/testChat` 之前调用 `assertLiveProviderSmokeAllowed(...)`。完整 Claw streaming GUI E2E 必须显式传入 `--allow-live-provider``LIME_ALLOW_LIVE_PROVIDER_SMOKE=1`;未授权时 smoke 必须 fail closed,不能用 mock、无模型探针或 Provider 自动探测替代真实业务闭环。
268. P3.137 未授权 gate 实测:不传 `--allow-live-provider` 运行 `node scripts/claw-chat-ready-streaming-smoke.mjs --timeout-ms 30000 --evidence-dir ".lime/qc/gui-evidence/claw-chat-ready-streaming-gate-check" --prefix "gate-check"`,按预期失败在 live-provider 授权检查,未授权 gate 实测在 `wait-health``prepare-runtime` 和 Provider 探测前 fail closed,未进入 `aster_agent_init`App Server `modelProvider/list``modelProvider/testChat`。本轮未运行带 `--allow-live-provider` 的真实模型调用,完整真实 GUI sending E2E 仍需用户明确允许真实 Provider 调用后再执行。
269. P3.137 增量验证:`node --check "scripts/check-app-server-client-contract.mjs"``node --check "scripts/check-command-contracts.mjs"``node "scripts/check-app-server-client-contract.mjs"``node "scripts/check-command-contracts.mjs"``npx prettier --check "scripts/check-command-contracts.mjs" "scripts/check-app-server-client-contract.mjs" "internal/roadmap/appserver/frontend-integration-matrix.md" "internal/exec-plans/app-server-implementation-plan.md"``git diff --check -- "scripts/check-command-contracts.mjs" "scripts/check-app-server-client-contract.mjs" "internal/roadmap/appserver/frontend-integration-matrix.md" "internal/exec-plans/app-server-implementation-plan.md"` 均通过;`npm run test:contracts` 完整通过,App Server client contract 123 项、command contracts、harness、modality、cleanup 与 docs boundary 均通过。未授权 Claw streaming smoke 的退出码为 1 属预期 gate 结果,不是产品路径通过证据;完整 GUI sending E2E 仍需显式 live-provider 授权后执行。
270. P3.138 evidence pack UI gateway current 化:继续沿 `Frontend -> Electron Desktop Host IPC -> App Server JSON-RPC -> RuntimeCore / backend` 主链推进,本轮只认领 `src/lib/api/agentRuntime/exportClient.ts``exportClient.d.ts``clientFactory.ts``clientFactory.d.ts``clientFactory.test.ts`、新增 `appServerEvidenceExportProjection.ts` / `.test.ts`、新增 `exportClient.test.ts``scripts/check-app-server-client-contract.mjs``internal/roadmap/appserver/frontend-integration-matrix.md` 与本执行计划记录。`exportAgentRuntimeEvidencePack(sessionId)` 现在直接调用 `AppServerClient.exportEvidence({ sessionId, includeEvents: true, includeArtifacts: true, includeEvidencePack: true })`,再通过 `projectAppServerEvidenceExportToRuntimeEvidencePack(...)` 投影成旧 UI 消费的 `AgentRuntimeEvidencePack` snake_case 形状;空 `sessionId` 与缺少 `evidencePack` 都 fail closed,不再回退 `agent_runtime_export_evidence_pack` 或 renderer mock。
271. P3.138 范围边界:本轮没有新增 App Server method,也没有改 Electron / Rust / package 大写集;只消费既有 `evidence/export` renderer-safe App Server gateway。handoff bundle、analysis handoff、replay case、review decision template 与 save review decision 仍暂走 `AGENT_RUNTIME_COMMANDS.exportHandoffBundle/exportAnalysisHandoff/exportReplayCase/exportReviewDecisionTemplate/saveReviewDecision` compat export command,因为 App Server 目前没有等价 method;这些路径不能作为 evidence/export 完成证据,后续需要先补 App Server 协议 / RuntimeCore 事实源再迁。
@@ -1326,7 +1326,7 @@ git diff --check
310. P3.149 contract guard / 测试:`scripts/check-app-server-client-contract.mjs` 新增 renderer project workspace 守卫,锁定 `project.ts` 必须导入 `AppServerClient`、复用 protocol `METHOD_WORKSPACE_*` 常量、经 `requestProjectAppServer(...)` 解 App Server JSON-RPC `result` envelope,并禁止读 / ensure current gateway 回流旧 `workspace_get*``workspace_list``workspace_ensure_*``get_or_create_default_project`;同时锁定 `project.test.ts` 必须用 App Server fake client 验证 `workspace/list/read/byPath/read/default/read/default/ensure/projectsRoot/read/projectPath/resolve/ensureReady`、缓存 / inflight 去重、缺 result fail-closed 和不回退 legacy。
311. P3.149 增量验证:`npx vitest run "src/lib/api/project.test.ts" --silent=passed-only --disableConsoleIntercept` 通过,1 个文件 65 个测试;`node --check "scripts/check-app-server-client-contract.mjs" && node "scripts/check-app-server-client-contract.mjs"` 通过,153 项 App Server client contract guard`npx prettier --check "src/lib/api/project.ts" "src/lib/api/project.test.ts" "scripts/check-app-server-client-contract.mjs" "internal/roadmap/appserver/frontend-integration-matrix.md" "internal/exec-plans/app-server-implementation-plan.md"` 通过;本轮认领文件 `git diff --check` 通过;`npm run test:contracts` 完整通过,App Server client contract 153 项、command contracts 保持 `mock priority commands: 0`,并覆盖 harness、modality、cleanup 与 docs boundary。本轮未改 Electron runtime / GUI 壳,未重复跑 GUI smokeP3.143 已有 Electron Desktop Host + App Server initialize smoke 证据,workspace/project 真实 GUI / E2E 证据仍待补。
312. P3.150 model registry read gateway current 化:继续沿 `Frontend -> Electron Desktop Host bridge -> App Server JSON-RPC -> RuntimeCore / backend` 主链推进,本轮只认领 `src/lib/api/modelRegistry.ts``modelRegistry.test.ts``scripts/check-app-server-client-contract.mjs``internal/roadmap/appserver/frontend-integration-matrix.md` 与本执行计划记录。`getModelRegistry()``getModelPreferences()``getModelSyncState()``getModelsForProvider()``getModelsByTier()``getProviderAliasConfig()``getAllAliasConfigs()` 现在通过 `AppServerClient.request(...)` 消费 `model/list``modelPreferences/list``modelSyncState/read``modelProviderAlias/read``modelProviderAlias/list` App Server current methods,并直接复用 `packages/app-server-client/src/protocol.ts``METHOD_MODEL_*` 常量;缺少 `models / preferences / syncState / configs` 时 fail closed,不回退 `get_model_registry``get_model_preferences``get_model_sync_state``get_models_for_provider``get_models_by_tier``get_provider_alias_config``get_all_alias_configs`、renderer mock 或 legacy facade。
313. P3.150 范围边界:本轮只迁 App Server 已有等价 method 的模型读链,不迁 `refresh_model_registry``search_models``toggle_model_favorite``hide_model``record_model_usage``fetch_provider_models_auto``get_model_registry_provider_ids`,因为 App Server 当前没有完整等价 refresh / write / live provider fetch method;这些命令保持 `compat`,不能作为 current App Server 完成证据`frontend-integration-matrix.md` 已同步把 model registry 读链改为 `partial`,避免把缺真实 GUI / E2E 证据的 API 迁移误判成完整产品闭环。
313. P3.150 范围边界:本轮当时只迁 App Server 已有等价 method 的模型读链,不迁 `refresh_model_registry``search_models``toggle_model_favorite``hide_model``record_model_usage``get_model_registry_provider_ids`。后续 P7 已把旧 `fetch_provider_models_auto` 清退为 `dead` 并迁到 App Server `modelProvider/fetchModels`,不得再按本历史记录保留 compat`frontend-integration-matrix.md` 已同步把 model registry 读链改为 `partial`,避免把缺真实 GUI / E2E 证据的 API 迁移误判成完整产品闭环。
314. P3.150 contract guard / 增量验证:`scripts/check-app-server-client-contract.mjs` 新增 renderer model registry 守卫,锁定 `modelRegistry.ts` 必须导入 `AppServerClient`、复用 protocol `METHOD_MODEL_*` 常量、经 `requestModelRegistryAppServer(...)` 解 App Server JSON-RPC `result` envelope,并禁止读链 current gateway 回流旧 `get_model_*` 与 alias legacy 命令;同时锁定 `modelRegistry.test.ts` 必须用 App Server fake client 验证 `model/list``modelPreferences/list``modelSyncState/read``modelProviderAlias/read``modelProviderAlias/list`、缓存 / inflight 复用、缺 result fail-closed 和不回退 legacy。`npx vitest run "src/lib/api/modelRegistry.test.ts" --silent=passed-only --disableConsoleIntercept` 通过,1 个文件 7 个 tests;`node --check "scripts/check-app-server-client-contract.mjs" && node "scripts/check-app-server-client-contract.mjs"` 通过,155 项 App Server client contract guard`npx prettier --check` 覆盖本轮认领文件通过;本轮认领文件 `git diff --check` 通过。本轮未改 Electron runtime / GUI 壳,未重复跑 GUI smokemodel/provider 真实 GUI / E2E 证据仍待补。
315. P3.151 knowledge pack list gateway current 化:继续沿 `Frontend -> Electron Desktop Host bridge -> App Server JSON-RPC -> RuntimeCore / backend` 主链推进,本轮避让 Electron / Rust / protocol 大写集,只认领 `src/lib/api/knowledge.ts``knowledge.test.ts``scripts/check-app-server-client-contract.mjs``internal/roadmap/appserver/frontend-integration-matrix.md` 与本执行计划记录。`listKnowledgePacks(...)` 现在通过 `AppServerClient.request("knowledgePack/list", ...)` 消费 App Server current method,并复用 `packages/app-server-client/src/protocol.ts``METHOD_KNOWLEDGE_PACK_LIST`;空 `workingDir`、缺少 `workingDir/rootPath``packs` 非数组时 fail closed,不回退 `knowledge_list_packs`、renderer mock 或 Electron legacy facade。
316. P3.151 范围边界 / 守卫:本轮只迁 App Server 已有等价 method 的知识包列表读链,不迁 `knowledge_get_pack``knowledge_import_source``knowledge_compile_pack``knowledge_set_default_pack``knowledge_update_pack_status``knowledge_resolve_context``knowledge_validate_context_run`,因为 App Server 当前没有完整等价详情 / 写入 / 编译 / resolve / validate method;这些命令保持 `compat`,不能作为 current App Server 完成证据。`scripts/check-app-server-client-contract.mjs` 新增 renderer knowledge 守卫,锁定 `knowledge.ts` 必须导入 `AppServerClient`、复用 `METHOD_KNOWLEDGE_PACK_LIST`、经 `requestKnowledgeAppServer(...)` 解 App Server JSON-RPC `result` envelope,并禁止列表读 current gateway 回流 `knowledge_list_packs``invokeMockOnly`;同时锁定 `knowledge.test.ts` 必须用 App Server fake client 验证 `knowledgePack/list``includeArchived`、缺 `workingDir` fail-closed、缺 `packs` 不回退 legacy。
@@ -1346,8 +1346,8 @@ git diff --check
330. P3.156 App Server list profile 收口:`commandPolicy.ts` 现在能识别 `app_server_handle_json_lines` 内的 JSON-RPC `agentSession/list`,并归到 `agent-session-list` 8 秒超时 profile;同批请求若包含 `agentSession/turn/start`,仍优先保留长请求 `app-server-turn-start` profile,避免真实发送被短超时误杀。该修复不新增命令、不新增 mock、不把 Electron main 扩成后端,只给 current Electron Desktop Host IPC -> App Server JSON-RPC 通道补明确 settle 语义。
331. P3.156 增量验证:`npx vitest run "src/lib/dev-bridge/safeInvoke.test.ts" "src/lib/dev-bridge/commandPolicy.test.ts" --silent=passed-only --disableConsoleIntercept` 通过,2 个文件 20 个测试;`npx prettier --check "src/lib/dev-bridge/safeInvoke.ts" "src/lib/dev-bridge/safeInvoke.test.ts" "src/lib/dev-bridge/commandPolicy.ts" "src/lib/dev-bridge/commandPolicy.test.ts"` 通过;本轮认领文件 `git diff --check` 通过;`npm run test:contracts` 完整通过,App Server client contract 163 项、command contracts 保持 `mock priority commands: 0`,并覆盖 harness、modality、cleanup 与 docs boundary。`npm run verify:gui-smoke` 通过,覆盖 smoke-only renderer build、Electron host typecheck / build、App Server sidecar assets 和 Electron smoke `initialize -> initialized`;首次运行撞上并行 `electron/main.ts` 中间态 typecheck 失败,单跑 `npx tsc --noEmit --project tsconfig.electron.json --pretty false` 通过后重跑 GUI smoke 成功。
332. P3.157 API key provider read gateway current 化:继续沿 `Frontend -> Electron Desktop Host bridge -> App Server JSON-RPC -> RuntimeCore / backend` 主链推进,本轮只认领 `src/lib/api/apiKeyProvider.ts``apiKeyProvider.test.ts``scripts/check-app-server-client-contract.mjs``internal/roadmap/appserver/frontend-integration-matrix.md` 与本执行计划记录。`apiKeyProviderApi.getProviders()``getSystemProviderCatalog()` 现在通过 `AppServerClient.request(...)` 消费 `modelProvider/list``modelProvider/catalog/list` App Server current methods,并复用 `packages/app-server-client/src/protocol.ts``METHOD_MODEL_PROVIDER_LIST` / `METHOD_MODEL_PROVIDER_CATALOG_LIST`;缺少 `providers` 时 fail closed,不回退 `get_api_key_providers``get_system_provider_catalog`、renderer mock 或 Electron legacy facade。
333. P3.157 范围边界 / 守卫:本轮只迁 App Server 已有等价 method 的 Provider 列表 / catalog 读链,不迁 `get_api_key_provider`、自定义 Provider 写入、API Key 写入 / 启停 / 导入导出、UI state、连接测试chat test,因为 App Server 当前没有完整等价写 / secret / live provider test method;这些命令保持 Desktop compat,不能作为 current App Server 完成证据`scripts/check-app-server-client-contract.mjs` 新增 renderer API key provider 守卫,锁定 `apiKeyProvider.ts` 必须导入 `AppServerClient`、复用 `METHOD_MODEL_PROVIDER_LIST` / `METHOD_MODEL_PROVIDER_CATALOG_LIST`、经 `requestApiKeyProviderAppServer(...)` 解 App Server JSON-RPC `result` envelope,并禁止读链 current gateway 回流 `safeInvoke("get_api_key_providers")``safeInvoke("get_system_provider_catalog")``invokeMockOnly`;同时锁定 `apiKeyProvider.test.ts` 必须用 App Server fake client 验证 `modelProvider/list``modelProvider/catalog/list`、缺 result fail-closed、不回退 legacy,并保留 `add_api_key` / 连接测试的 compat side-effect 边界
334. P3.157 增量验证:`npx vitest run "src/lib/api/apiKeyProvider.test.ts" --silent=passed-only --disableConsoleIntercept` 通过,1 个文件 9 个测试;`node --check "scripts/check-app-server-client-contract.mjs" && node "scripts/check-app-server-client-contract.mjs"` 通过,165 项 App Server client contract guard`npx prettier --check "src/lib/api/apiKeyProvider.ts" "src/lib/api/apiKeyProvider.test.ts" "scripts/check-app-server-client-contract.mjs" "internal/roadmap/appserver/frontend-integration-matrix.md" "internal/exec-plans/app-server-implementation-plan.md"` 通过;本轮认领文件 `git diff --check` 通过。`src/components/api-key-provider/README.md` 已同步改成 App Server `modelProvider/list` / `modelProvider/catalog/list` 事实源口径legacy `get_api_key_providers` / `get_system_provider_catalog`作为 Desktop compat facade。本轮未改 Electron runtime / GUI 壳,未重复跑 GUI smokeProvider 设置页真实 GUI / E2E 证据仍待补。
333. P3.157 范围边界 / 守卫:本轮当时只迁 App Server 已有等价 method 的 Provider 列表 / catalog 读链;后续 P7 已把 Provider read/write、API Key 写入 / 启停 / 导入导出、UI state、连接测试chat test 与 live fetch 统一迁到 App Server `modelProvider*` / `modelProviderKey*` / `modelProviderUiState*` / `modelProviderConfig*` current method,旧 Provider command family 已判 `dead``scripts/check-app-server-client-contract.mjs` 新增 renderer API key provider 守卫,锁定 `apiKeyProvider.ts` 必须导入 `AppServerClient`、复用 `METHOD_MODEL_PROVIDER_*` 常量、经 `requestApiKeyProviderAppServer(...)` 解 App Server JSON-RPC `result` envelope,并禁止 current gateway 回流旧 Provider `safeInvoke(...)``invokeMockOnly`;同时锁定 `apiKeyProvider.test.ts` 必须用 App Server fake client 验证 `modelProvider*`、缺 result fail-closed、不回退 legacy
334. P3.157 增量验证:`npx vitest run "src/lib/api/apiKeyProvider.test.ts" --silent=passed-only --disableConsoleIntercept` 通过,1 个文件 9 个测试;`node --check "scripts/check-app-server-client-contract.mjs" && node "scripts/check-app-server-client-contract.mjs"` 通过,165 项 App Server client contract guard`npx prettier --check "src/lib/api/apiKeyProvider.ts" "src/lib/api/apiKeyProvider.test.ts" "scripts/check-app-server-client-contract.mjs" "internal/roadmap/appserver/frontend-integration-matrix.md" "internal/exec-plans/app-server-implementation-plan.md"` 通过;本轮认领文件 `git diff --check` 通过。`src/components/api-key-provider/README.md` 已同步改成 App Server `modelProvider/list` / `modelProvider/catalog/list` 事实源口径;后续 P7 已把旧 Provider facade 全部标为 `dead`,不再作为 Desktop compat facade 正向证据。本轮未改 Electron runtime / GUI 壳,未重复跑 GUI smokeProvider 设置页真实 GUI / E2E 证据仍待补。
335. P3.158 current App Server gateway method 常量收口:继续沿 `packages/app-server-client/src/protocol.ts` 作为 renderer-safe 协议事实源推进,本轮只认领 `src/lib/api/agentRuntime/appServerSessionClient.ts``inventoryClient.ts``memory.ts``scripts/check-app-server-client-contract.mjs``internal/roadmap/appserver/frontend-integration-matrix.md` 与本执行计划记录。`agentSession/list``workspaceSkillBindings/list``projectMemory/read` 的前端 gateway 已从本地 `APP_SERVER_METHOD_*` 字符串常量改为直接复用 `METHOD_AGENT_SESSION_LIST``METHOD_WORKSPACE_SKILL_BINDINGS_LIST``METHOD_PROJECT_MEMORY_READ`;这不新增命令、不改变返回投影,只封住 current gateway 复制第二套 method 字符串的漂移面。
336. P3.158 守卫 / 增量验证:`scripts/check-app-server-client-contract.mjs` 已补守卫,锁定 App Server session facade、workspace skill bindings、project memory 聚合读必须从 `packages/app-server-client/src/protocol.ts` 导入 method 常量,并禁止旧 `APP_SERVER_METHOD_*` 本地硬编码回流。`npx vitest run "src/lib/api/memory.test.ts" "src/lib/api/agentRuntime/inventoryClient.test.ts" "src/lib/api/agentRuntime/appServerSessionClient.test.ts" --silent=passed-only --disableConsoleIntercept` 通过,3 个文件 17 个测试;`node --check "scripts/check-app-server-client-contract.mjs" && node "scripts/check-app-server-client-contract.mjs"` 通过,165 项 App Server client contract guard`npx prettier --check "src/lib/api/memory.ts" "src/lib/api/agentRuntime/inventoryClient.ts" "src/lib/api/agentRuntime/appServerSessionClient.ts" "scripts/check-app-server-client-contract.mjs"` 通过;`npm run test:contracts` 完整通过,App Server client contract 165 项、command contracts 保持 `mock priority commands: 0`,并覆盖 harness、modality、cleanup 与 docs boundary。本轮未改 Electron runtime / GUI 壳,未重复跑 GUI smoke;并行进程仍在运行 `npm run typecheck`,该全量类型检查结果不归入本轮证据。
337. P3.158 Electron shell startup command cleanup:针对启动控制台报错 `sync_tray_model_shortcuts``take_pending_skill_package_open_requests``companion_get_pet_status``list_relay_providers`,本轮只收直接阻塞默认启动的 current / shell surface。`sync_tray_model_shortcuts``take_pending_skill_package_open_requests` 已补进 Electron Desktop Host 白名单和 main 进程实现:托盘模型菜单只保存 renderer 传入的菜单投影并通过 `tray-model-selected` 事件回写选择;`.skill` / `.skills` 打开请求由启动参数、macOS `open-file` 与 second-instance 参数进入 pending 队列,前端通过既有 `take_pending_skill_package_open_requests` 取出并清空。它们属于 Electron 桌面壳能力,不进入 App Server truth bridge,也不回退 mock。
@@ -1356,7 +1356,7 @@ git diff --check
340. P3.158 增量验证:`npx vitest run "src/hooks/useCompanionProviderBridge.test.tsx" "electron/ipcChannels.test.ts" --silent=passed-only --disableConsoleIntercept` 通过,2 个文件 11 个测试;`npm run typecheck:electron` 通过;`cargo check --manifest-path "lime-rs/Cargo.toml" -p app-server` 通过且不再输出 `ensure_capability_allowed` dead code warning`npx prettier --check` 覆盖本轮前端 / Electron 文件通过;本轮认领文件 `git diff --check` 通过;`npm run test:contracts` 完整通过,App Server client contract 165 项、command contracts 保持 `mock priority commands: 0`,并覆盖 harness、modality、cleanup 与 docs boundary。`npm run typecheck` 全量前端检查超过 3 分钟无输出,已结束本轮启动的残留 `npm run typecheck` / `tsc --noEmit` 进程;本轮以定向 hook 测试、Electron typecheck、Rust app-server check 与完整 contracts 作为交付证据。
341. P3.159 桌宠默认关闭补齐:继续收 `companion_get_pet_status` 默认触发面,本轮认领 `src/components/settings-v2/agent/providers/index.tsx``src/components/settings-v2/_layout/index.tsx` 与对应测试。Provider 设置页现在只有在 `navigation.enabled_items` 显式包含 `companion` 时才渲染 `CompanionProviderBridgeCard` / `CompanionCapabilityPreferencesCard` 与 companion `TabsContent`;设置首页也复用 `useCompanionEntryEnabled()`,默认不再展示“桌宠”快捷卡。默认关闭时只显示本地 Provider 主区,不挂载桌宠 bridge、不读取桌宠状态、不监听 companion 事件、不订阅 provider data 变化。这一步不新增命令、不新增 mock,也不把 companion 旧命令接回默认启动路径。
342. P3.159 增量验证:`npx vitest run "src/components/settings-v2/_layout/index.test.tsx" "src/components/settings-v2/home/index.test.tsx" "src/components/settings-v2/agent/providers/index.test.tsx" "src/hooks/useCompanionProviderBridge.test.tsx" "src/lib/navigation/sidebarNav.test.ts" --silent=passed-only --disableConsoleIntercept` 通过,5 个文件 47 个测试;`npx prettier --check "src/components/settings-v2/_layout/index.tsx" "src/components/settings-v2/_layout/index.test.tsx" "src/components/settings-v2/agent/providers/index.tsx" "src/components/settings-v2/agent/providers/index.test.tsx" "src/hooks/useCompanionProviderBridge.ts" "src/hooks/useCompanionProviderBridge.test.tsx" "src/hooks/useCompanionEntryEnabled.ts" "src/lib/navigation/sidebarNav.ts" "src/lib/navigation/sidebarNav.test.ts" "internal/exec-plans/app-server-implementation-plan.md"` 通过;本轮认领文件 `git diff --check` 通过。GUI 只读点击路径也已补证据:通过系统 Chrome 从根页面打开账号菜单进入设置页,再切到设置首页,确认常用入口为 3 项且不含“桌宠 / Companion”,Provider 工作区也没有 `provider-workspace-tab-companion`,证据落到 `.lime/qc/gui-evidence/companion-default-off/home-card-summary.json``settings-home-card-check.png`。本轮未改 Electron runtime / App Server protocol / host 白名单,因此未重复跑 `npm run test:contracts`
343. P3.160 Provider 设置页 current GUI / bridge 证据:新增 `scripts/provider-settings-current-smoke.mjs`,通过真实 GUI 点击路径从根页面打开账号菜单进入 AI 服务商设置,再点击“添加模型”进入 catalog 面板;脚本监听 DevBridge `/invoke`,解析 `app_server_handle_json_lines` 的 JSON-RPC lines,要求同时观察到 `modelProvider/list``modelProvider/catalog/list`,并断言没有 `get_api_key_providers` / `get_system_provider_catalog`。该脚本只读取 Provider 列表与系统 catalog,不写入 API Key、不触发连接测试或真实模型调用。
343. P3.160 Provider 设置页 current GUI / bridge 证据:新增 `scripts/provider-settings-current-smoke.mjs`,通过真实 GUI 点击路径从根页面打开账号菜单进入 AI 服务商设置,再点击“添加模型”进入 catalog 面板;脚本监听 DevBridge `/invoke`,解析 `app_server_handle_json_lines` 的 JSON-RPC lines,要求同时观察到 `modelProvider/list``modelProvider/catalog/list`,并断言没有旧 Provider command family。该脚本只读取 Provider 列表与系统 catalog,不写入 API Key、不触发连接测试或真实模型调用。
344. P3.160 增量验证:`node --check "scripts/provider-settings-current-smoke.mjs"` 通过;`node "scripts/provider-settings-current-smoke.mjs" --timeout-ms 120000` 通过,并输出 `.lime/qc/gui-evidence/provider-settings-current/provider-settings-current-summary.json``provider-settings-current-network-invoke.json``provider-settings-current.png`summary 显示 `transport=electron-host``providerPageMounted=true``modelAddCatalogMounted=true``appServerHandleJsonLinesSeen=true``appServerMethodsSeen` 包含 `modelProvider/list``modelProvider/catalog/list``legacyProviderCommandsSeen=[]``missingRequiredAppServerMethods=[]``npx prettier --check "scripts/provider-settings-current-smoke.mjs"` 通过;本轮认领脚本 `git diff --check` 通过;`internal/roadmap/appserver/frontend-integration-matrix.md` 已同步把 API key provider list/catalog 从“缺真实 GUI / E2E 证据”改为已有 provider settings current smoke 证据,仍保留单 Provider、secret 写入、UI state、导入导出和连接测试 compat 缺口。
345. P3.161 提交前 `verify:local` 门禁修复:`verify:local` 在 Vitest smart 批次 8 暴露 `scripts/app-server/release-manifest.test.mjs` 收集阶段 `Expected ident`。根因是 `app-server-release-manifest.mjs` 带 shebangVite transform 对相对导入模块注入 import 后 shebang 落到文件中间成为非法语法;该脚本只通过 `node scripts/...` 调用且文件不是可执行权限,因此移除 shebang 不改变 CLI 入口。测试文件同步迁到 Vitest API,`app-server:manifest:test` 也从 `node scripts/*.test.mjs` 直跑改成 `npx vitest run`,避免保留坏入口。
346. P3.162 侧栏会话 current GUI / bridge 证据:新增 `scripts/sidebar-sessions-current-smoke.mjs`,通过真实 GUI 从根页面打开主侧栏会话 Shelf,预置只读 `agent_last_project_id` 以触发最近对话列表读取,再展开“归档”触发归档列表懒加载;脚本监听 DevBridge `/invoke`,解析 `app_server_handle_json_lines` 的 JSON-RPC lines,要求观察到 `agentSession/list`,最近列表和归档列表都退出 loading,并断言没有 `agent_runtime_list_sessions`。该脚本只读会话列表,不创建会话、不点击会话菜单、不触发归档 / 重命名 / 删除,也不调用真实模型。
@@ -1418,13 +1418,13 @@ git diff --check
402. P3.189 增量验证:`node --check "scripts/check-app-server-client-contract.mjs"` 通过;`node "scripts/check-app-server-client-contract.mjs"` 通过,178 项 App Server client contract guard`npx prettier --check "scripts/check-app-server-client-contract.mjs" "scripts/app-server/external-backend-smoke.mjs"` 通过;`git diff --check -- "scripts/check-app-server-client-contract.mjs" "scripts/app-server/external-backend-smoke.mjs" "internal/exec-plans/app-server-implementation-plan.md"` 通过;`npm run test:contracts` 完整通过,command contracts 保持 `mock priority commands: 0`、Electron host commands 103、DevBridge truth commands 148。真实 GUI 发送后 `event -> read model refresh -> timeline` 与 Codex-style `thread/list/read/archive/unarchive` 仍是后续主缺口。
403. P3.190 packaged failure readback smoke 与防回流守卫:继续沿 `Frontend -> Electron Desktop Host IPC / DevBridge -> app_server_handle_json_lines -> App Server JSON-RPC -> RuntimeCore / backend` 的 App Server sidecar / read model 主链补失败路径证据,本轮只认领 `scripts/app-server/packaged-external-backend-failure-smoke.mjs``scripts/check-app-server-client-contract.mjs` 与本计划记录,不修改 `lime-rs/crates/app-server/src/local_data_source.rs`,也不触碰 App Server protocol / runtime / TS client 大写集。packaged external backend failure smoke 在收到失败的 `agentSession/turn/start` 后,不再只检查 streamed `message.delta` / `turn.failed``evidence/export`,还会调用 `agentSession/read`,断言同一 `sessionId/threadId` 可读回 1 个同 `turnId``failed` turn 且带 `completedAt`summary 同步输出 `readTurns=1``readTurnStatus=failed`。App Server client contract guard 现在要求该 smoke 保留 `connection.readSession``readTurns.length === 1``readTurn.status === "failed"``completedAt``readTurnStatus=${readTurn.status}`,并禁止 `backendMode: "mock"` 回流,防止 packaged failure 证据退回只看通知 / evidence、不证明失败 read model 落地或靠 mock backend 假成功。
404. P3.190 增量验证:`node --check "scripts/check-app-server-client-contract.mjs"` 通过;`node --check "scripts/app-server/packaged-external-backend-failure-smoke.mjs"` 通过;`npx prettier --check "scripts/check-app-server-client-contract.mjs" "scripts/app-server/packaged-external-backend-failure-smoke.mjs" "internal/exec-plans/app-server-implementation-plan.md"` 通过;`git diff --check -- "scripts/check-app-server-client-contract.mjs" "scripts/app-server/packaged-external-backend-failure-smoke.mjs" "internal/exec-plans/app-server-implementation-plan.md"` 通过;`node "scripts/app-server/packaged-external-backend-failure-smoke.mjs"` 通过,summary 显示 `clientEvents=message.delta,turn.failed``evidenceEvents=message.delta,turn.failed``readTurns=1``readTurnStatus=failed``node "scripts/check-app-server-client-contract.mjs"` 通过,178 项 App Server client contract guard`npm run test:contracts` 完整通过,command contracts 保持 `mock priority commands: 0`、Electron host commands 103、DevBridge truth commands 148。本轮未改 GUI 壳 / 前端页面 / App Server protocol,因此未跑 `npm run verify:gui-smoke`;真实 GUI 发送后 `agentSession/event -> agentSession/read -> read model refresh -> timeline` 与 Codex-style `thread/list/read/archive/unarchive` 仍是后续主缺口。
405. P3.191 Claw 工作台进入默认 Electron smoke:按用户确认的方向,默认 `npm run smoke:electron` / `npm run verify:gui-smoke` 不再只证明 renderer 与 App Server `initialize`,而是在 Electron main 中继续等待 Claw / Agent 工作台真实 DOM ready`[data-testid="workspace-shell-scene"]``[data-testid="inputbar-core-container"]``textarea[name="agent-chat-message"]` 必须可见且 composer 可用;同时只过滤本次启动后的 `lime_invoke_error_buffer_v1` / `lime_invoke_trace_buffer_v1` 错误,避免旧 localStorage 残留误杀。`scripts/electron/current-entrypoints.test.mjs` 新增守卫,要求 smoke 保留 workbench shell / composer gate,并明确禁止默认 smoke 触发 `"agentSession/turn/start"``"test_api_key_provider_chat"`;完整 Claw streaming GUI E2E 仍必须走 `scripts/claw-chat-ready-streaming-smoke.mjs --allow-live-provider``LIME_ALLOW_LIVE_PROVIDER_SMOKE=1` 显式授权,不能把默认 smoke 扩成真实 Provider 消耗路径。
405. P3.191 Claw 工作台进入默认 Electron smoke:按用户确认的方向,默认 `npm run smoke:electron` / `npm run verify:gui-smoke` 不再只证明 renderer 与 App Server `initialize`,而是在 Electron main 中继续等待 Claw / Agent 工作台真实 DOM ready`[data-testid="workspace-shell-scene"]``[data-testid="inputbar-core-container"]``textarea[name="agent-chat-message"]` 必须可见且 composer 可用;同时只过滤本次启动后的 `lime_invoke_error_buffer_v1` / `lime_invoke_trace_buffer_v1` 错误,避免旧 localStorage 残留误杀。`scripts/electron/current-entrypoints.test.mjs` 新增守卫,要求 smoke 保留 workbench shell / composer gate,并明确禁止默认 smoke 触发 `"agentSession/turn/start"`App Server `"modelProvider/testChat"`;完整 Claw streaming GUI E2E 仍必须走 `scripts/claw-chat-ready-streaming-smoke.mjs --allow-live-provider``LIME_ALLOW_LIVE_PROVIDER_SMOKE=1` 显式授权,不能把默认 smoke 扩成真实 Provider 消耗路径。
406. P3.191 Electron packaged renderer 相对资源修复:首次把 Claw 工作台纳入 smoke 后,`npm run verify:gui-smoke` 真实失败在 `file:///.../dist/index.html?nativeStartup=1`,页面只剩 index.html 原生启动层文案,`workspace-shell-scene/inputbar/composer` 全部未挂载。根因是 Electron packaged renderer 在 `file://` 下仍使用绝对 `/assets/...` 资源路径,React bundle 加载不到。`vite.config.ts` 现在只在 `command === "build" && LIME_ELECTRON_RENDERER=1` 时设置 `base: "./"`,dev server 不变;入口守卫同步要求 Electron renderer build 脚本设置 `LIME_ELECTRON_RENDERER` 且 Vite config 保留相对 base。重跑 `npm run verify:gui-smoke` 通过,输出包含 `[electron-host] app-server ready protocol=appserver.v0 version=1.59.0``[electron-smoke] app-server initialized protocol=appserver.v0 version=1.59.0``[electron-smoke] claw workbench shell ready`;构建产物 `dist/index.html` 的主脚本 / CSS 已变为 `./assets/...`
407. P3.192 设置页 updater warning 收口:设置页 broad smoke 曾被开发态 About 页更新诊断 warning 误判失败,唯一噪音是缺少 dev `app-update.yml` 时 About 页把 updater 诊断错误打印到 `console.warn("Update check returned a diagnostic error:", ...)``src/components/settings-v2/system/about/index.tsx` 现在仍向用户展示本地化的“暂时无法检查更新”状态,但不把诊断错误写入 console warning`about/index.test.tsx` 新增断言,更新检查返回 `error` 时 UI 不泄露技术细节,也不调用 `console.warn`
408. P3.192 增量验证:`node --check "scripts/electron-smoke.mjs" "scripts/electron/current-entrypoints.test.mjs" "scripts/check-app-server-client-contract.mjs"` 通过;`node "scripts/check-app-server-client-contract.mjs"` 通过,178 项守卫;`npx prettier --check "vite.config.ts" "electron/main.ts" "scripts/electron-smoke.mjs" "scripts/electron/current-entrypoints.test.mjs" "src/components/settings-v2/system/about/index.tsx" "src/components/settings-v2/system/about/index.test.tsx"` 通过;`npx vitest run "src/components/settings-v2/system/about/index.test.tsx" "scripts/electron/current-entrypoints.test.mjs" --silent=passed-only --disableConsoleIntercept` 通过,2 个文件 17 个测试;`npx tsc --noEmit --project "tsconfig.electron.json" --pretty false` 通过;`npm run verify:gui-smoke` 通过并输出 Claw workbench ready`node "scripts/settings-all-pages-current-smoke.mjs" --timeout-ms 120000` 通过,summary 显示 `ok=true``tabs=15``pages=24``invokeCount=97``consoleErrors=0``unexpectedConsoleWarnings=0``invokeResponseErrors=0``npm run test:contracts` 完整通过,command contracts 保持 `mock priority commands: 0`。本轮只把默认 Electron smoke 提升到工作台入口 / composer 可用门槛,不宣称真实 Agent 发送、Provider streaming、event -> read model refresh 的完整 GUI 闭环已完成。
409. P3.193 code runtime fixture smoke current runtime 迁移:继续沿 `Frontend -> Electron Desktop Host IPC / DevBridge -> app_server_handle_json_lines -> App Server JSON-RPC -> RuntimeCore / backend` 主链收正向 runtime 证据,本轮只认领 `scripts/code-runtime-fixture-smoke.mjs``scripts/check-app-server-client-contract.mjs`、本计划与前端矩阵记录,不修改 `lime-rs/crates/app-server/src/local_data_source.rs`,也不触碰 App Server protocol / runtime / TS client 大写集。`scripts/code-runtime-fixture-smoke.mjs` 不再调用 `agent_runtime_create_session``agent_runtime_update_session``agent_runtime_submit_turn``agent_runtime_get_session``agent_runtime_get_thread_read``agent_runtime_export_evidence_pack` 作为正向完成证据;会话创建、provider/model 更新、turn start、read model 轮询与 evidence export 均改为 `app_server_handle_json_lines -> agentSession/start``agentSession/update``agentSession/turn/start``agentSession/read``evidence/export`。submit 继续通过 `RuntimeOptions.hostOptions.asterChatRequest` 携带扁平 Claw/Aster 请求快照,包括 `provider_config`、approval/sandbox、workspace、execution strategy、metadata 与 turn idread 侧对 `agentSession/read` 的 protocol turns 与 legacy `detail.thread_read` 两种形态做脚本内投影,避免再依赖旧 `agent_runtime_get_thread_read`。文件 checkpoint 仍通过 `agent_runtime_list_file_checkpoints` / `agent_runtime_diff_file_checkpoint` 验证,因为 App Server 目前没有等价 checkpoint method;该路径在 evidence 中明确标记为 `usesFileCheckpointCompat`,不能当成 runtime lifecycle current 证据。
410. P3.193 防回流守卫 / 验证边界:`scripts/check-app-server-client-contract.mjs` 新增 code runtime fixture smoke 守卫,要求脚本保留 `app_server_handle_json_lines``agentSession/start``agentSession/update``agentSession/turn/start``agentSession/read``evidence/export``hostOptions.asterChatRequest``provider_config``usesAppServerJsonRpcSubmitTurn / usesAppServerSessionRead / usesAppServerEvidenceExport / usesFileCheckpointCompat` 覆盖字段,同时禁止 `agent_runtime_create_session``agent_runtime_update_session``agent_runtime_submit_turn``agent_runtime_get_session``agent_runtime_get_thread_read``agent_runtime_export_evidence_pack` 与旧 `usesCurrentRuntimeSubmitTurn` 回流;checkpoint 两个 compat 命令不被禁止,退出条件是补齐 App Server checkpoint/diff current method 后再迁。只读复核 Codex 四个 `app-server-*` crate 后确认,Codex 当前是一等 `thread/*` / `turn/*` v2 APILime 当前仍以 `agentSession/*` facade 为事实源;本轮不直接照搬 Codex `thread/*` 命名,后续要等 App Server protocol / runtime / TS client 写集稳定后单独实现。完整 Claw live GUI E2E 仍需显式 `--allow-live-provider` 授权,不能用本离线 fixture smoke 代替真实发送 / streaming / event refresh / `agentSession/read` 回流证据。
411. P3.194 Claw streaming GUI E2E 证据收紧:继续沿第一竖切“真实发送、`agentSession/event``agentSession/read` 回流”缺口推进,本轮只认领 `scripts/claw-chat-ready-streaming-smoke.mjs``scripts/lib/live-provider-smoke-gate.test.mjs``scripts/check-app-server-client-contract.mjs`、本计划与前端矩阵记录,不触碰 Rust protocol / runtime / Electron main 大写集。`scripts/claw-chat-ready-streaming-smoke.mjs``wait-health` 现在必须确认 DevBridge health 返回 `transport=electron-host`,否则直接失败;summary 也新增 `electronHostBridge` pass 断言,防止完整 Claw streaming GUI E2E 误连旧 DevBridge 或非 Electron Host 后产出假 current 证据。该脚本仍必须显式 `--allow-live-provider``LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1` 才会进入 `wait-health``prepare-runtime``aster_agent_init``get_api_key_providers``test_api_key_provider_chat`;默认运行继续在调用 DevBridge / Provider 前 fail closed。
411. P3.194 Claw streaming GUI E2E 证据收紧:继续沿第一竖切“真实发送、`agentSession/event``agentSession/read` 回流”缺口推进,本轮只认领 `scripts/claw-chat-ready-streaming-smoke.mjs``scripts/lib/live-provider-smoke-gate.test.mjs``scripts/check-app-server-client-contract.mjs`、本计划与前端矩阵记录,不触碰 Rust protocol / runtime / Electron main 大写集。`scripts/claw-chat-ready-streaming-smoke.mjs``wait-health` 现在必须确认 DevBridge health 返回 `transport=electron-host`,否则直接失败;summary 也新增 `electronHostBridge` pass 断言,防止完整 Claw streaming GUI E2E 误连旧 DevBridge 或非 Electron Host 后产出假 current 证据。该脚本仍必须显式 `--allow-live-provider``LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1` 才会进入 `wait-health``prepare-runtime``aster_agent_init`App Server `modelProvider/list``modelProvider/testChat`;默认运行继续在调用 DevBridge / Provider 前 fail closed。
412. P3.194 增量验证:`node --check "scripts/claw-chat-ready-streaming-smoke.mjs" "scripts/check-app-server-client-contract.mjs" "scripts/lib/live-provider-smoke-gate.test.mjs"` 通过;`npx vitest run "scripts/lib/live-provider-smoke-gate.test.mjs" --silent=passed-only --disableConsoleIntercept` 通过,13 个测试;`npm run smoke:claw-chat-ready-streaming -- --timeout-ms 30000` 按预期失败于 live provider gate,输出“默认禁止执行”,且未进入 `stage=wait-health``node "scripts/check-app-server-client-contract.mjs"` 通过,179 项 App Server client contract guard`npx prettier --check "scripts/claw-chat-ready-streaming-smoke.mjs" "scripts/check-app-server-client-contract.mjs" "scripts/lib/live-provider-smoke-gate.test.mjs"` 通过;`git diff --check -- "scripts/claw-chat-ready-streaming-smoke.mjs" "scripts/check-app-server-client-contract.mjs" "scripts/lib/live-provider-smoke-gate.test.mjs"` 通过。本轮没有真实 Provider 授权,因此未运行完整 Claw live streaming GUI E2E,也不宣称真实发送 / streaming / event refresh / `agentSession/read` 回流已完成;下一刀仍是在明确授权后运行并修复 `scripts/claw-chat-ready-streaming-smoke.mjs --allow-live-provider` 的真实失败。
413. P3.195 Connect deep link Electron current 化:按“Electron 全面接管前端宿主、Tauri 下线”目标,Connect deep link 生产主链已从旧 Tauri command/event 切到 `Electron protocol/open-url -> plugin-deep-link onOpenUrl/getCurrent -> useDeepLink -> src/lib/api/connect.ts -> App Server JSON-RPC -> RuntimeCore / LocalAppDataSource`。App Server protocol / TS client / processor / runtime / local data source 已提供 `connectDeepLink/resolve``connectOpenDeepLink/resolve``connectRelayApiKey/save``connectCallback/send`;前端 `useDeepLink` 不再监听 `deep-link://new-url``deep-link-connect``deep-link-error`,也不再调用 `handle_deep_link``handle_open_deep_link``save_relay_api_key``useConnectCallback` 改走 `sendConnectCallback` 网关,不再调用旧 `send_connect_callback`。renderer registry list 入口不再打 `list_relay_providers` / `refresh_relay_registry``src/lib/desktop-host/providerMocks.ts` 也移除 Relay registry mock,避免把旧 Tauri registry list 当成测试或生产 fallback。Connect registry 不在启动期加载,只有实际 `lime://connect` resolve/save/callback 时由 App Server 按需读取缓存 / 远程 registry。
414. P3.195 守卫 / 增量验证:新增 `src/lib/api/connect.test.ts` 锁住四个 Connect current method 与 camelCase/snake_case 投影,并断言不经 `safeInvoke`;新增 `src/hooks/useConnectCallback.test.tsx` 锁住成功 / 失败 callback 只经 `sendConnectCallback` 且失败不阻塞主流程;`src/hooks/useDeepLink.test.tsx` 已覆盖运行中 Electron `onOpenUrl` 与启动期 `getCurrent``lime://connect` / `lime://open` 路径。`internal/aiprompts/commands.md` 已同步 Connect deep link current 主链和旧事件 / 旧命令禁止回流清单。定向验证已通过:`npx vitest run "src/lib/api/connect.test.ts" "src/hooks/useConnectCallback.test.tsx" "src/hooks/useDeepLink.test.tsx" --silent=passed-only --disableConsoleIntercept``npm --prefix "packages/app-server-client" run test``cargo test --manifest-path "lime-rs/Cargo.toml" -p app-server-protocol -p app-server --locked``npm run test:contracts`。本刀不新增 Electron 后端 adapter、不引入 mock fallback;剩余 GUI 级证据是后续用真实 Electron 窗口触发 `lime://connect` URL 并确认 Dialog / 保存 API Key / callback 全链路。
@@ -1480,7 +1480,7 @@ git diff --check
464. P3.218 真实 Electron SDK fixture 证据 / 验证边界:`scripts/agent-app/runtime-electron-sdk-fixture-smoke.mjs` 的 external backend fixture 在 `actionRespond` 后输出 `tool.started``tool.result`iframe SDK 第二次 `getTask` 后 fail-closed 断言 `task:toolCall` replay event 与 `result.thread_read.tool_calls` 同时存在;summary 现在把 `toolEvidence` 提到顶层,便于后续自动审阅。真实 smoke 通过:`APP_SERVER_BIN="/Users/coso/Documents/dev/ai/aiclientproxy/lime/lime-rs/target/debug/app-server" node "scripts/agent-app/runtime-electron-sdk-fixture-smoke.mjs" --app-url "http://127.0.0.1:14321/" --timeout-ms 120000 --keep-temp`summary 位于 `.lime/qc/gui-evidence/agent-app-runtime-electron-sdk-fixture/agent-app-runtime-electron-sdk-fixture-summary.json`,显示 `ok=true``turnId=agent-app-turn-1780811535060``statuses.first=running``statuses.second=running``statuses.cancel=cancelled``toolEvidence.toolEventType=task:toolCall``toolEvidence.toolName=WebFetch``toolEvidence.outputPreview=fetched https://example.com/agent-app-sdk-fixture``toolEvidence.readModelStatus=completed``toolEvidence.success=true``artifactEvidence.artifactEventType=artifact:created``consoleErrors=[]`backend log 记录 `turnStart / actionRespond / turnCancel` 且三者共享同一动态 turn id。定向验证通过:`npx vitest run "src/features/agent-app/runtime/agentRuntimeCapabilityHost.test.ts" --silent=passed-only --disableConsoleIntercept``npx vitest run "scripts/agent-app/runtime-electron-sdk-fixture-smoke.test.mjs" --silent=passed-only --disableConsoleIntercept``node "scripts/check-app-server-client-contract.mjs"``npx prettier --check ...` 与本轮认领文件 `git diff --check`。该证据仍不等同 live provider 完整 Claw streaming,也不宣称真实模型已执行完整 WebFetch;它只证明 App Server current read model 的 tool facts 可经 Electron Host Bridge 回流到 Agent App SDK task record。下一刀应转向 live provider 授权后的完整 Claw streaming GUI E2E,或把真实 tool runtime 执行从 fixture evidence 扩到 live turn 证据。
465. P3.219 Electron GUI smoke current 主路径回归:在 P3.218 后补跑产品级 GUI 门槛,确认当前大写集下 Electron Desktop Host、App Server sidecar 和默认 Claw 工作台 shell 仍能真实启动。本轮只记录验证证据,不接管并行 Electron / Rust 写集,也不修改 `lime-rs/crates/app-server/src/local_data_source.rs``npm run verify:gui-smoke` 通过,执行链包含 `generate:agent-runtime-clients``generate:extension-site-adapters``verify:app-version`、renderer smoke build、`packages/app-server-client` build、`typecheck:electron`、Electron main/preload build、desktop assets copy、App Server sidecar build 与 `scripts/electron-smoke.mjs`。运行输出确认 `app-server ready protocol=appserver.v0 version=1.59.0``renderer loaded``app-server initialized protocol=appserver.v0 version=1.59.0``claw workbench shell ready`。开发态 updater 缺少 `.lime/electron-dev-host/Lime-dev.app/Contents/Resources/app-update.yml` 仍只作为 smoke 期间已知诊断输出,不阻塞 GUI smoke。
466. P3.219 边界:该刀证明默认 Electron GUI 主路径在 P3.218 后仍可交付启动,覆盖 Electron Host bridge、App Server sidecar initialize、renderer 加载与 Claw 工作台 shell/composer ready;它不是 live provider 完整 Claw streaming E2E,也不证明真实发送、模型 streaming、tool runtime live 执行、`agentSession/event -> agentSession/read` 刷新或最终 assistant 输出。完整业务闭环仍必须在显式 live provider 授权后运行 `scripts/claw-chat-ready-streaming-smoke.mjs --allow-live-provider`,或继续把真实 tool runtime 执行从 fixture evidence 扩到 live turn 证据。
467. P3.220 Claw streaming live-provider gate 回归:继续沿完整目标的真实业务闭环缺口推进,但当前没有显式 live Provider 授权,因此本轮只验证 fail-closed gate,防止后续把 mock、无模型探针或自动 Provider 探测误当作完整 Claw streaming 证据。运行 `npm run smoke:claw-chat-ready-streaming -- --timeout-ms 30000` 按预期快速失败于 live provider gate,输出“默认禁止执行;如确需运行,请显式传入 --allow-live-provider,或设置 LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1”,且没有进入 `wait-health`、DevBridge health、`aster_agent_init``get_api_key_providers``test_api_key_provider_chat`。该结果说明完整 Claw streaming GUI E2E 仍处于未授权状态,不允许用 default mock、renderer mock fallback、App Server mock backend 或 external backend fixture 替代。
467. P3.220 Claw streaming live-provider gate 回归:继续沿完整目标的真实业务闭环缺口推进,但当前没有显式 live Provider 授权,因此本轮只验证 fail-closed gate,防止后续把 mock、无模型探针或自动 Provider 探测误当作完整 Claw streaming 证据。运行 `npm run smoke:claw-chat-ready-streaming -- --timeout-ms 30000` 按预期快速失败于 live provider gate,输出“默认禁止执行;如确需运行,请显式传入 --allow-live-provider,或设置 LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1”,且没有进入 `wait-health`、DevBridge health、`aster_agent_init`App Server `modelProvider/list``modelProvider/testChat`。该结果说明完整 Claw streaming GUI E2E 仍处于未授权状态,不允许用 default mock、renderer mock fallback、App Server mock backend 或 external backend fixture 替代。
468. P3.221 Agent session history Electron current 证据:继续推进“对话 / 归档 / 历史详情无法获取”主缺口中不需要 live Provider 的 current 子链路,本轮只认领 `scripts/electron/session-history-fixture-smoke.mjs``scripts/electron/session-history-fixture-smoke.test.mjs``scripts/check-app-server-client-contract.mjs` 与本计划 / 前端矩阵记录,不修改 `lime-rs/crates/app-server/src/local_data_source.rs`。真实 Electron fixture 使用 `APP_SERVER_BACKEND_MODE=unavailable`,通过 preload `window.electronAPI.invoke("app_server_handle_json_lines")` 执行 `initialize -> initialized -> agentSession/start -> agentSession/read -> agentSession/update -> agentSession/read -> agentSession/list`,禁止 `agentSession/turn/start`,也不调用模型后端、external backend、legacy `agent_runtime_*`、default mock 或 renderer mock fallback。
469. P3.221 增量验证 / 证据:`npx vitest run "scripts/electron/session-history-fixture-smoke.test.mjs" --silent=passed-only --disableConsoleIntercept` 通过,静态守卫锁住真实 Electron Desktop Host IPC、`APP_SERVER_BACKEND_MODE=unavailable`、current session history methods、hydrate detail 数组和禁 mock / legacy`node --check "scripts/electron/session-history-fixture-smoke.mjs" "scripts/electron/session-history-fixture-smoke.test.mjs"` 通过;`node "scripts/electron/session-history-fixture-smoke.mjs" --timeout-ms 120000 --keep-temp` 通过,summary 位于 `.lime/qc/gui-evidence/agent-session-history-electron-fixture/agent-session-history-electron-fixture-summary.json`,显示 `ok=true``backendMode=unavailable``electronPreloadBridge=true``missingRequiredMethods=[]``forbiddenMethodsSeen=[]``listSessionFound=true``listedSession.title=Electron history fixture restored``consoleErrors=[]`raw evidence 显示 request methods 为 `initialize, agentSession/start, agentSession/read, agentSession/update, agentSession/read, agentSession/list`,两次 read detail 都包含 `turns/items/queued_turns/thread_read``scripts/check-app-server-client-contract.mjs` 新增 guard 防止该 fixture 回流 `agent_runtime_*`、mock backend 或 live provider gate。该证据证明历史详情 hydrate 所需的 App Server current read/list/update 形状可经真实 Electron Host bridge 获取;它仍不等同真实 live turn streaming 或旧历史数据全量迁移完成。
470. P3.221 收口验证 / Forge 事实源校准:本轮补跑 `npx prettier --check "scripts/check-app-server-client-contract.mjs" "internal/exec-plans/app-server-implementation-plan.md" "internal/roadmap/appserver/frontend-integration-matrix.md"``git diff --check -- "scripts/check-app-server-client-contract.mjs" "internal/exec-plans/app-server-implementation-plan.md" "internal/roadmap/appserver/frontend-integration-matrix.md"``node --check "scripts/electron/session-history-fixture-smoke.mjs" "scripts/electron/session-history-fixture-smoke.test.mjs"``npx vitest run "scripts/electron/session-history-fixture-smoke.test.mjs" --silent=passed-only --disableConsoleIntercept` 均通过。按最新 `AGENTS.md`Electron packaging / installer / signing / notarization / updater metadata 的 current 事实源已切到 `forge.config.mjs``electron-forge package``electron-forge make` 与仓库内 Forge maker`electron-builder.yml` / `electron-builder` CLI 按 `dead` 处理;`scripts/check-app-server-client-contract.mjs` 当前已改为锁定 `scripts/run-electron-package-dir.mjs` 使用 Forge package`node "scripts/check-app-server-client-contract.mjs"` 通过 202 项。复跑 `npm run test:contracts` 完整通过,`mock priority commands: 0`,说明 P3.221 session history current 链路不再被旧 builder 守卫误挡;release/updater 文档、docs guard、entrypoint guard、release workflow、staging / verify 脚本与 package lock 的 Forge 成组收口继续归 packaging 写集处理。
@@ -1563,7 +1563,7 @@ git diff --check
547. P3.256 Claw 标题 hydrate 残留修复:接 P3.255 的非阻塞 UI 残留,本轮只认领 Agent Chat topic projection 与 Claw current fixture guard,不改 `lime-rs/crates/app-server/src/local_data_source.rs`,不触发 live Provider。`mapSessionToTopic(...)` 在缺失 / 非法 session timestamp 时不再把 epoch fallback 暴露为 `任务 1970/1/1`,而是显示 `新任务``upsertTopicFromSessionDetail(...)` 在 detail 刷新只带通用 fallback 标题(`新任务``任务 YYYY/M/D`)时保留已有真实标题,避免 App Server read detail 缺少标题时覆盖侧栏已经确认的 session title。`scripts/agent-runtime/claw-chat-current-fixture-smoke.mjs` 新增 `noEpochFallbackTitle` 断言,防止完成页再次出现 `任务 1970/1/1`
548. P3.256 验证 / 证据:定向验证通过:`npx prettier --check "src/components/agent/chat/hooks/agentChatShared.ts" "src/components/agent/chat/hooks/agentChatShared.test.ts" "src/components/agent/chat/hooks/agentSessionTopicViewModel.ts" "src/components/agent/chat/hooks/agentSessionTopicViewModel.unit.test.ts" "scripts/agent-runtime/claw-chat-current-fixture-smoke.mjs" "scripts/agent-runtime/claw-chat-current-fixture-smoke.test.mjs"``npx vitest run "src/components/agent/chat/hooks/agentChatShared.test.ts" "src/components/agent/chat/hooks/agentSessionTopicViewModel.unit.test.ts" "scripts/agent-runtime/claw-chat-current-fixture-smoke.test.mjs" --silent=passed-only --disableConsoleIntercept``git diff --check -- ...`。重建 renderer smoke bundle `npm run build:renderer:electron:smoke` 通过;聚合 `npm run smoke:agent-runtime-current-fixture` 通过,`liveProviderUsed=false`;真实 Electron `npm run smoke:claw-chat-current-fixture -- --timeout-ms 180000` 通过,summary session `claw-chat-current-1780848889813-2661` 显示 `ok=true``guiCompleted.hasEpochFallbackTitle=false``assertions.noEpochFallbackTitle=true``guiUserMessageVisible=true``guiAssistantOutputVisible=true``guiInputRemainsReady=true``guiNotStuckStreaming=true``noConsoleErrors=true``noInvokeErrors=true`,完成页片段已从 `任务 1970/1/1` 变为 `Claw 新闻输入 Electron fixture`。该证据仍是 non-live external fixture backend,不证明 live Provider streaming / 真实 WebSearch 联网;下一刀应继续推进 explicit live gate 或 Agent App/tool runtime live turn evidence。
549. P3.256 默认 Electron GUI smoke 复验:在 2026-06-08 当前并行工作树仍包含 Rust App Server、Electron Host、Agent Chat、脚本目录治理和 updater 写集的状态下,本轮只认领本计划与前端矩阵记录,不修改 `lime-rs/crates/app-server/src/local_data_source.rs``runtime.rs``processor.rs``runtime_backend.rs``electron/appServerHost.ts` 或 Agent Chat streaming 文件。`npm run verify:gui-smoke` 通过,覆盖 `generate:agent-runtime-clients``generate:extension-site-adapters``verify:app-version`、Electron renderer smoke build、`packages/app-server-client` build、Electron host typecheck、main/preload build、desktop assets、App Server sidecar assets 与 `scripts/electron-smoke.mjs`;输出确认 `[electron-smoke] renderer loaded``[electron-host] app-server ready protocol=appserver.v0 version=1.59.0``[electron-smoke] app-server initialized protocol=appserver.v0 version=1.59.0``[electron-smoke] claw workbench shell ready`。该证据证明默认 `Frontend -> Electron Desktop Host IPC -> App Server JSON-RPC` 壳链和 Claw composer 准备态未被当前迁移打断;不发送 turn、不调用 Provider、不证明 `agentSession/event -> agentSession/read` 业务刷新。
550. P3.256 live provider gate fail-closed 复验:`npm run smoke:claw-chat-ready-streaming -- --timeout-ms 30000` 按预期以退出码 1 停在 live Provider 授权检查,提示必须显式传入 `--allow-live-provider` 或设置 `LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1`。该运行没有进入 DevBridge health、`wait-health``prepare-runtime``aster_agent_init``get_api_key_providers``test_api_key_provider_chat` 或真实模型调用,也没有使用 App Server mock backend、renderer mock fallback、`mockPriorityCommands``defaultMocks``invokeMockOnly` 绕过。完整业务闭环仍等待显式 live Provider 授权后运行 `scripts/claw-chat-ready-streaming-smoke.mjs --allow-live-provider`,验证真实发送、streaming、WebSearch/WebFetch 联网、tool runtime live 执行与 read model 回流。
550. P3.256 live provider gate fail-closed 复验:`npm run smoke:claw-chat-ready-streaming -- --timeout-ms 30000` 按预期以退出码 1 停在 live Provider 授权检查,提示必须显式传入 `--allow-live-provider` 或设置 `LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1`。该运行没有进入 DevBridge health、`wait-health``prepare-runtime``aster_agent_init`App Server `modelProvider/list``modelProvider/testChat` 或真实模型调用,也没有使用 App Server mock backend、renderer mock fallback、`mockPriorityCommands``defaultMocks``invokeMockOnly` 绕过。完整业务闭环仍等待显式 live Provider 授权后运行 `scripts/claw-chat-ready-streaming-smoke.mjs --allow-live-provider`,验证真实发送、streaming、WebSearch/WebFetch 联网、tool runtime live 执行与 read model 回流。
551. P3.257 默认 Electron GUI smoke 当前轮复验:在 P3.256 标题 hydrate 修复和当前 dist 重建后,本轮再次执行 `npm run verify:gui-smoke`,命令完整通过。覆盖链路包括 `generate:agent-runtime-clients``generate:extension-site-adapters``verify:app-version`、renderer smoke build、`packages/app-server-client` build、`typecheck:electron`、main/preload build、desktop assets、App Server sidecar assets 和真实 Electron smoke。关键输出确认 `版本一致性检查通过: 1.59.0``app-server ready protocol=appserver.v0 version=1.59.0``app-server initialized protocol=appserver.v0 version=1.59.0``renderer loaded``claw workbench shell ready`。该证据证明当前工作树下 Electron Desktop Host IPC、App Server sidecar initialize 和默认 Claw composer 准备态仍可启动;它不发送 Claw turn,也不调用正式 Provider。
552. P3.257 live Provider 默认门禁当前轮复验:本轮再次执行 `npm run smoke:claw-chat-ready-streaming -- --timeout-ms 30000`,按预期以退出码 1 停在授权检查,输出要求显式传入 `--allow-live-provider` 或设置 `LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1`。该结果证明当前默认质量入口仍 fail-closed,不会在未授权情况下调用真实模型、多模态 Provider 或 WebSearch/WebFetch live path,也没有用 App Server mock backend / renderer mock fallback 绕过。完整整体目标仍需要用户显式授权后再跑 live Provider streaming E2E;在授权前只能继续补 non-live current fixture 与 GUI 可见证据。
553. P3.258 代码产物工作台真实 Electron 复验:继续补完整目标中的“有产物 / 代码工作台”GUI 证据,本轮复跑 `npx vitest run "scripts/electron/code-artifact-workbench-fixture-smoke.test.mjs" --silent=passed-only --disableConsoleIntercept`,4 项静态守卫通过,锁住真实 Electron Desktop Host IPC、App Server JSON-RPC、external fixture backend、artifact snapshot、tool timeline GUI evidence、禁止 live Provider / mock backend / renderer mock / legacy command。随后执行 `npm run smoke:code-artifact-workbench-electron-fixture -- --timeout-ms 180000`,真实 Electron fixture 通过,stage 覆盖 `launch-electron -> wait-renderer -> initialize-app-server -> ensure-default-workspace -> create-code-artifact-session -> bind-gui-workspace -> verify-session-list -> navigate-gui-workspace -> open-session-from-sidebar -> wait-session-hydrated -> open-workbench`
@@ -1595,7 +1595,7 @@ git diff --check
579. P3.269 live WebSearch/WebFetch read envelope 证据补强:继续降低显式授权后才暴露的脚本误判风险,本轮只认领 `scripts/lib/claw-chat-live-web-tool-evidence.mjs``scripts/lib/claw-chat-live-web-tool-evidence.test.mjs``scripts/lib/live-provider-smoke-gate.test.mjs`、本计划与前端矩阵记录,不触碰 live smoke 主流程、Rust App Server、Electron Host 或 Agent Chat runtime 写集。`collectToolCallsFromValue(...)` 现在除 `thread_read/threadRead/runtime_summary/messages/items/turns` 外,还会递归 `result/detail/session/session_detail/sessionDetail` envelope;新增行为测试用 App Server JSON-RPC `agentSession/read``result.detail.thread_read.tool_calls` 形状证明 `WebSearch/WebFetch` turn-scoped evidence 不会因响应外壳漏提取。该刀仍不运行真实 Provider、不调用 live WebSearch/WebFetch;完整整体目标仍需显式授权后跑联网 E2E。
580. P3.270 Electron 领域脚本根目录收缩:继续沿 Forge-only release/updater 与 `scripts/` 根目录治理目标推进,本轮只认领 Electron build / package / smoke / release helper 脚本、`package.json`、Forge config、Electron current guards、`scripts/README.md``scripts/script-root-governance-baseline.json`、release-updater 路线图与本计划记录,不碰 Rust/App Server/Agent Chat 并行写集。已用 `git mv` 将 14 个 Electron current 根脚本迁入 `scripts/electron/``brand-mac-helper-apps`、host/renderer build、desktop assets、App Server packaged assets、dev/preview/package-dir、smoke、release staging 和 package verifier`package.json``forge.config.mjs`、release workflow guard、contract guard、quality planner、Electron current docs/entrypoint guards 与 release updater tests 均同步指向领域目录。`scripts/script-root-governance-baseline.json` 删除这些根目录例外,`npm run governance:scripts``rootFiles=119` 收缩为 `rootFiles=105 retired=0 untrackedNew=0`
581. P3.270 验证 / 边界:定向语法验证通过 `node --check` 覆盖迁入 `scripts/electron/` 的 build / assets / run / smoke / staging / verifier / branding 脚本;定向单测通过 `npx vitest run "scripts/electron/current-entrypoints.test.mjs" "scripts/electron/current-docs-guard.test.mjs" "scripts/electron/brand-mac-helper-apps.test.mjs" "scripts/electron/verify-package-resources.test.mjs" --silent=passed-only --disableConsoleIntercept`4 文件 32 项;release/updater 组合通过 `npx vitest run "scripts/electron/release-updater-assets.test.mjs" "scripts/electron/release-workflow-guard.test.mjs" "scripts/electron/current-docs-guard.test.mjs" --silent=passed-only --disableConsoleIntercept`3 文件 23 项;`npm run governance:electron-release-workflow``npm run governance:scripts` 均通过。该刀只治理 current 脚本事实源和守卫,不声称 macOS DMG/sign/notarize GitHub Actions、Windows Squirrel runner 或 live Provider WebSearch/WebFetch E2E 已完成。
582. P3.271 live Provider gate 递归脚本守卫:只读子代理指出 `scripts/lib/live-provider-smoke-gate.test.mjs` 只扫描 `scripts/` 根层 `.mjs`,而当前 smoke 已迁入 `scripts/agent-app/``scripts/agent-runtime/``scripts/app-server/``scripts/electron/` 等子目录;这会让后续子目录新增真实 `agent_runtime_submit_turn` / `test_api_key_provider_chat` / `knowledge_compile_pack` 调用时绕过未授权 gate 静态守卫。本轮只认领 live provider gate 测试和本计划 / 矩阵记录,把 `listSmokeScripts()` 改成递归扫描 `scripts/**/*.mjs`,排除 `*.test.mjs``scripts/lib/**``check-*` 与测试夹具路径;现有子目录脚本在递归扫描下仍全部通过,证明当前没有未授权 live Provider 子目录回流。该刀不运行真实 Provider、不触碰生产 bridge;完整整体目标仍需显式授权后跑 live streaming / WebSearch-WebFetch 实际联网 E2E。
582. P3.271 live Provider gate 递归脚本守卫:只读子代理指出 `scripts/lib/live-provider-smoke-gate.test.mjs` 只扫描 `scripts/` 根层 `.mjs`,而当前 smoke 已迁入 `scripts/agent-app/``scripts/agent-runtime/``scripts/app-server/``scripts/electron/` 等子目录;这会让后续子目录新增真实 `agent_runtime_submit_turn` / App Server `modelProvider/testChat` / `knowledge_compile_pack` 调用时绕过未授权 gate 静态守卫。本轮只认领 live provider gate 测试和本计划 / 矩阵记录,把 `listSmokeScripts()` 改成递归扫描 `scripts/**/*.mjs`,排除 `*.test.mjs``scripts/lib/**``check-*` 与测试夹具路径;现有子目录脚本在递归扫描下仍全部通过,证明当前没有未授权 live Provider 子目录回流。该刀不运行真实 Provider、不触碰生产 bridge;完整整体目标仍需显式授权后跑 live streaming / WebSearch-WebFetch 实际联网 E2E。
583. P3.272 live WebSearch/WebFetch turn provider / routing 断言:只读子代理指出 live Web 工具 turn 虽记录 `summary.liveWebSubmitTurnConfig`,但 verdict 只校验 long/follow turn 的 provider/model 与 fast-response routing,导致授权后 WebSearch/WebFetch turn 即使走错 provider/model 或开启 fast routing 也可能误通过。本轮只认领 `scripts/claw-chat-ready-streaming-smoke.mjs``scripts/lib/live-provider-smoke-gate.test.mjs`、本计划与矩阵记录;live smoke 现在新增 `liveWebProviderPreferenceHonored``liveWebModelPreferenceHonored``liveWebFastResponseRoutingDisabled`,并纳入最终 verdict,要求 Web 工具 turn 本身同样保持 explicit provider/model 和禁用 fast-response routing。该刀不运行真实 Provider;完整整体目标仍需显式授权后跑 live streaming / WebSearch-WebFetch 实际联网 E2E。
584. P3.273 App Server `detail.thread_read` current 证据保留:只读子代理指出 `readAppServerThreadRead()``agentSession/read` 的 turns 重建最小 thread read,会丢掉 App Server current `detail.thread_read.tool_calls/artifacts/model_routing` 等证据。本轮只认领 `scripts/claw-chat-ready-streaming-smoke.mjs` 与本计划 / 矩阵记录;`appServerThreadReadFromSessionRead(...)` 现在优先展开 `detail.thread_read` / `detail.threadRead`,再补充 active turn、queued turns、latest status 与 routing fallback,确保 smoke summary 的 `threadRead` 不再弱化 App Server current read model。该刀不运行真实 Provider;完整整体目标仍需显式授权后跑 live streaming / WebSearch-WebFetch 实际联网 E2E。
585. P3.274 live WebSearch/WebFetch 输出证据门禁:只读子代理指出工具 `status=completed` 即算通过仍可能弱化“真实 WebFetch 打开页面并确认事实”的证据。本轮只认领 `scripts/lib/claw-chat-live-web-tool-evidence.mjs``scripts/lib/claw-chat-live-web-tool-evidence.test.mjs``scripts/claw-chat-ready-streaming-smoke.mjs``scripts/lib/live-provider-smoke-gate.test.mjs` 与本计划 / 矩阵记录;helper 现在为 required tools 计算 `outputPresent``allRequiredOutputPresent``allRequiredOutputPresentForTurn`,行为测试覆盖完成态但空输出的 `WebFetch` 不满足 output presentlive smoke verdict 新增 `liveWebRequiredToolOutputsPresent`。该刀不运行真实 Provider;完整整体目标仍需显式授权后跑联网 E2E。
@@ -69,7 +69,7 @@
2026-05-18 00:08 已新增 Connector outbox runtime smoke harness`scripts/agent-app/connector-outbox-smoke.mjs`。本轮未启动新的 GUI flow,也未重新调用模型,只用 `--mode replay` 读取既有 live session `f08e135b-58a7-44eb-8f16-fc6809a5d045` / task `agent-app-connector-outbox-runtime-mp9xw68t`,生成 `.lime/qc/gui-evidence/agent-apps/p18-7-e-connector-outbox-runtime-smoke-replay-20260518-summary.json`;断言 `threadReadCompleted / connectorToolCallProjected / outputHadBoundedMetadata / toolCallEvidenceProjected / threadEvidenceProjected / taskEventEvidenceProjected` 全为 true。该脚本后续可在无并行 GUI smoke 时用 `--mode live --provider-preference ... --model-preference ...` 生成新 runtime outbox 证据,但仍不会完成真实 OAuth handshake、secret delivery 或外部 Cloud delivery。
2026-05-18 00:13 已增强 Connector outbox runtime smoke harness live 模式:`scripts/agent-app/connector-outbox-smoke.mjs``--mode live` 不再要求手工传 provider/model;若省略 `--provider-preference / --model-preference`会只读 `get_api_key_providers` / `get_api_key_provider`,优先选择 enabled provider 中的 `deepseek/openai/anthropic/gemini/azure-openai`,并偏向 `flash/mini/lite` 模型。因隔壁正在跑内容工厂 GUI flow,本轮只执行 `node --check``--mode replay`,新 replay 证据为 `.lime/qc/gui-evidence/agent-apps/p18-7-e-connector-outbox-runtime-smoke-replay-auto-provider-20260518-summary.json`;未启动 GUI,也未重新调用模型。
2026-05-18 00:13 已增强 Connector outbox runtime smoke harness live 模式:`scripts/agent-app/connector-outbox-smoke.mjs``--mode live` 不再要求手工传 provider/model;若省略 `--provider-preference / --model-preference`现在必须只读 App Server current `modelProvider/list` / `modelProvider/read`,优先选择 enabled provider 中的 `deepseek/openai/anthropic/gemini/azure-openai`,并偏向 `flash/mini/lite` 模型。`get_api_key_providers` / `get_api_key_provider` 已随 P7 Provider 迁移判为 `dead`,不得再作为 live harness 正向证据。因隔壁正在跑内容工厂 GUI flow,本轮只执行 `node --check``--mode replay`,新 replay 证据为 `.lime/qc/gui-evidence/agent-apps/p18-7-e-connector-outbox-runtime-smoke-replay-auto-provider-20260518-summary.json`;未启动 GUI,也未重新调用模型。
2026-05-18 00:22 已跑通 focused live runtime connector outbox smoke`scripts/agent-app/connector-outbox-smoke.mjs --mode live` 自动选择 `deepseek/deepseek-v4-flash`,生成 `.lime/qc/gui-evidence/agent-apps/p18-7-e-connector-outbox-runtime-smoke-live-auto-provider-final-20260518-summary.json`,断言 `threadReadCompleted / connectorToolCallProjected / outputHadBoundedMetadata / toolCallEvidenceProjected / threadEvidenceProjected / taskEventEvidenceProjected` 全为 true`threadRead.toolCallCount=1`,同一个 `outbox://connector/notion/createPage/...` evidence ref 同时出现在 tool call、thread evidence summary 和 Agent App task events。中途发现 harness 会过早在 evidence 出现时返回、且会选中早期失败 tool call,已修正为优先选带 expected evidence ref 的成功 tool call,并等待 `threadRead.status=completed`;修正后 replay 复核 `.lime/qc/gui-evidence/agent-apps/p18-7-e-connector-outbox-runtime-smoke-live-auto-provider-fixed2-recheck-20260518-summary.json` 也全绿。该 live smoke 仍只证明 non-fixture Cloud Overlay outbox/evidence runtime 链路,不证明真实 OAuth handshake、secret delivery 或外部 Cloud delivery。
@@ -27,7 +27,7 @@
| HTML artifact 工具栏切换 | `HtmlRenderer` 接入外部 `viewMode / previewSize` 受控状态;工作区已有外部工具栏时隐藏内部重复工具栏,源码切换会真实移除 iframe 并渲染源码 | 已实现 |
| `.htm` 产物识别 | `buildArtifactFromWrite``.htm` 构建为 `html` artifact,并标记 `language: "html"` | 已实现 |
| Tauri 权限与 CSP | `tauri.conf*.json` 启用 `assetProtocol`CSP 覆盖 `asset:` / `http://asset.localhost` / `frame-src`capability 覆盖 `html-preview-*``core:webview:allow-create-webview-window` | 已实现 |
| Provider 设置页不再误导 | 文本模型按钮显示“试跑当前模型”,并通过 `test_api_key_provider_chat` 做真实聊天试跑 | 已实现 |
| Provider 设置页不再误导 | 文本模型按钮显示“试跑当前模型”,并通过 App Server current `modelProvider/testChat` 做真实聊天试跑;旧 `test_api_key_provider_chat` 已判为 `dead`,不得作为正向证据恢复 | 已实现 |
| 402 额度错误友好化 | `agentRuntimeErrorPresentation``402 / Payment Required / Insufficient Balance / 余额不足 / 额度不足` 归一为额度不足提示;聊天时间线、折叠摘要、流式失败 assistant patch 与会话概览均在 presentation 层显示友好提示,不再向普通用户暴露 raw Provider error | 已实现 |
| 失败态不过度重复 | 失败详情只保留在时间线错误卡等主解释位置;assistant 正文若只是同一失败详情的重复文案会被隐藏,底部运行状态行只保留“失败 + 耗时”等短状态 | 已实现 |
| 五语言文案 | `settings.json``agent.json` 已覆盖 `zh-CN / zh-TW / en-US / ja-JP / ko-KR` | 已实现 |
@@ -38,16 +38,16 @@ Electron main 是 Desktop Host bridge,不是后端适配层,也不是第二
18. `src/lib/api/memory.ts``getProjectMemory(...)` 聚合读已通过 `AppServerClient.request("projectMemory/read", { projectId })` 进入 App Server current 主链;空 `projectId` 或 App Server 未返回 `memory` 时 fail closed,不再调用 `project_memory_get`、renderer mock 或 Electron legacy facade。`scripts/project-memory-current-smoke.mjs` 已通过真实 Agent 首页 / 工作区证明默认工作区解析后项目记忆读取经 `app_server_handle_json_lines` 观察到 `projectMemory/read`,且未观察到旧 `project_memory_get` 命令。角色 / 世界观 / 大纲 CRUD 仍暂走既有命令,因为 App Server 目前只覆盖项目记忆聚合读。
19. Agent App mock SDK 只允许测试环境显式使用;`mockSdkEnabled` 在生产构建中硬关闭,`buildMockCapabilityProfile``MockCapabilityHost``createMockLimeCapabilityTransport` 都必须通过 test-only 断言,且 mock transport 只允许测试夹具显式导入。public SDK / production runtime 不再导出或导入 mock transport、mock host、mock capability profile 或 mock handler 类型;生产 Agent App runtime 缺真实 App Server / Electron current method 时必须 fail closed,不能用 mock capability profile、mock host 或 mock transport 作为产品闭环。
20. `src/lib/api/project.ts` 的 workspace/project 读与目录就绪链路已通过 `AppServerClient.request(...)` 进入 App Server current 主链,并直接复用 `packages/app-server-client/src/protocol.ts``METHOD_WORKSPACE_*` 常量;`workspace/list``workspace/read``workspace/byPath/read``workspace/default/read``workspace/default/ensure``workspace/projectsRoot/read``workspace/projectPath/resolve``workspace/ensureReady` 缺少必需 result 时 fail closed,不再把 `workspace_get*``workspace_list``workspace_ensure_*``get_or_create_default_project` 当作前端事实源。`workspace_create/update/delete/set_default` 与 content 相关命令仍暂保 compat,因为 App Server 目前没有完整等价写 method。
21. `src/lib/api/modelRegistry.ts` 的模型 registry / preference / sync 读链,以及 provider / tier / alias scoped 读链的前端 gateway,已通过 `AppServerClient.request(...)` 进入 App Server current 主链,并直接复用 `packages/app-server-client/src/protocol.ts``METHOD_MODEL_*` 常量;`model/list``modelPreferences/list``modelSyncState/read``modelProviderAlias/read``modelProviderAlias/list` 缺少必需 result 时 fail closed,不再把 `get_model_registry``get_model_preferences``get_model_sync_state``get_models_for_provider``get_models_by_tier``get_provider_alias_config``get_all_alias_configs` 当作前端事实源。`scripts/model-registry-current-smoke.mjs` 已通过真实设置页“添加模型”面板证明 `model/list``modelPreferences/list``modelSyncState/read``app_server_handle_json_lines` 到达 App Server,且未观察到旧 `get_model_*` 命令、模型写入 / refresh side-effect 或 `agentSession/turn/start`。本 GUI 证据不覆盖 `modelProviderAlias/read` / `modelProviderAlias/list` 的 alias GUI 触发,不覆盖 provider / tier scoped 查询或 provider ids 兼容查询。`refresh_model_registry``search_models``toggle_model_favorite``hide_model``record_model_usage``fetch_provider_models_auto` 与 provider ids 兼容查询仍暂保 compat,因为 App Server 目前没有完整等价写 / refresh / live fetch method。
21. `src/lib/api/modelRegistry.ts` 的模型 registry / preference / sync 读链,以及 provider / tier / alias scoped 读链的前端 gateway,已通过 `AppServerClient.request(...)` 进入 App Server current 主链,并直接复用 `packages/app-server-client/src/protocol.ts``METHOD_MODEL_*` / `METHOD_MODEL_PROVIDER_*` 常量;`model/list``modelPreferences/list``modelSyncState/read``modelProvider/fetchModels``modelProviderAlias/read``modelProviderAlias/list` 缺少必需 result 时 fail closed,不再把 `get_model_registry``get_model_preferences``get_model_sync_state``get_models_for_provider``get_models_by_tier``get_provider_alias_config``get_all_alias_configs``fetch_provider_models_auto` 当作前端事实源。`scripts/model-registry-current-smoke.mjs` 已通过真实设置页“添加模型”面板证明 `model/list``modelPreferences/list``modelSyncState/read``app_server_handle_json_lines` 到达 App Server,且未观察到旧 `get_model_*` 命令、模型写入 / refresh side-effect 或 `agentSession/turn/start`。本 GUI 证据不覆盖 `modelProviderAlias/read` / `modelProviderAlias/list` 的 alias GUI 触发,不覆盖 provider / tier scoped 查询或 provider ids 兼容查询。`refresh_model_registry``search_models``toggle_model_favorite``hide_model``record_model_usage` 与 provider ids 兼容查询仍暂保 compat,因为 App Server 目前没有完整等价写 / refresh method。
22. `src/lib/api/knowledge.ts``listKnowledgePacks(...)` 已通过 `AppServerClient.request("knowledgePack/list", ...)` 进入 App Server current 主链;空 `workingDir`、缺少 `workingDir/rootPath``packs` 非数组时 fail closed,不再把 `knowledge_list_packs`、renderer mock 或 Electron legacy facade 当作前端事实源。`scripts/knowledge-current-smoke.mjs` 已通过真实 GUI 点击路径证明项目资料页面列表读取经 `app_server_handle_json_lines` 观察到 `knowledgePack/list`,且未观察到旧 `knowledge_list_packs` 命令。`knowledge_get_pack``knowledge_import_source``knowledge_compile_pack``knowledge_set_default_pack``knowledge_update_pack_status``knowledge_resolve_context``knowledge_validate_context_run` 仍暂保 compat,因为 App Server 当前只覆盖知识包列表读链。
23. `src/lib/api/skill-execution.ts``listExecutableSkills()``getSkillDetail(...)` 已通过 `AppServerClient.request(...)` 进入 App Server current 主链,并直接复用 `packages/app-server-client/src/protocol.ts``METHOD_SKILL_LIST` / `METHOD_SKILL_READ``skill/list``skill/read` 缺少必需 result 时 fail closed,不再把 `list_executable_skills``get_skill_detail`、renderer mock 或 Electron legacy facade 当作前端读事实源。`scripts/skills-current-smoke.mjs` 已通过真实 GUI slash skill preflight 证明 `skill/list``app_server_handle_json_lines` 返回 `result.skills` 数组,且未观察到旧 Skill 命令、`execute_skill``agentSession/turn/start``skill/read` 真实 GUI 触发与 `execute_skill` 仍暂保 compat side-effect,因为 App Server 当前没有完整等价执行 method。
24. `src/lib/api/agentApps.ts``listInstalledAgentApps(...)` 已通过 `AppServerClient.request("agentAppInstalled/list", {})` 进入 App Server current 主链;缺少 `states``issues` 时 fail closed,不再把 `agent_app_list_installed`、renderer mock 或 Electron legacy facade 当作前端事实源。`scripts/agent-app/installed-current-smoke.mjs` 已通过真实 GUI 点击路径证明 Agent Apps 管理页已安装列表读取经 `app_server_handle_json_lines` 观察到 `agentAppInstalled/list`,且未观察到旧 `agent_app_list_installed` 命令。`agent_app_save_installed_state`、install / uninstall / shell 等写入或 desktop side-effect 命令仍暂保 compat,因为 App Server 当前只覆盖已安装列表读链;UI runtime start / status / stop 已拆到 `agentAppUiRuntime/*` current method。`scripts/agent-app/ui-runtime-current-smoke.mjs` 已通过真实前端 gateway 证明 UI runtime `status` / `stop` / `start` 均经 `app_server_handle_json_lines -> agentAppUiRuntime/*` 到达 App Server,未观察到旧 `agent_app_*_ui_runtime` 命令,并用一次性不存在的 appId 验证 `start` fail closed、不写 installed state、不启动子进程;`scripts/agent-app/ui-runtime-stdio-fixture-smoke.mjs` 已进一步证明 App Server stdio / RuntimeCore 可用 fixture installed state 启动真实 npm dev 子进程,完成 `/api/bootstrap` ready、`entryUrl` 可达、status running 与 stop stopped 生命周期;`scripts/agent-app/ui-runtime-electron-fixture-smoke.mjs` 已证明真实 Electron-host GUI 能从正式侧栏进入 fixture Agent App runtime 页面,发出 `agentAppUiRuntime/start`,并让 iframe surface 打开 App Server 返回的 `entryUrl`。该证据只覆盖 Agent App UI runtime 生命周期,不覆盖 Agent 对话 turn、tool runtime、evidence 或 Claw/Aster 完整执行链。
25. `src/lib/api/automation.ts``getAutomationJobs()` 已通过 `AppServerClient.request("automationJob/list", {})` 进入 App Server current 主链;缺少 `jobs` 时 fail closed,不再把 `get_automation_jobs`、renderer mock 或 Electron legacy facade 当作前端事实源。`scripts/automation-current-smoke.mjs` 已通过真实 GUI 点击路径证明持续流程任务列表读取经 `app_server_handle_json_lines` 观察到 `automationJob/list`,且未观察到旧 `get_automation_jobs` 命令。scheduler / status / get single / create / update / delete / run / health / history / schedule preview / validate 等命令仍暂保 compat,因为 App Server 当前只覆盖自动化任务列表读链
26. `src/lib/api/apiKeyProvider.ts``getProviders()``getSystemProviderCatalog()` 已通过 `AppServerClient.request(...)` 进入 App Server current 主链,并直接复用 `METHOD_MODEL_PROVIDER_LIST` / `METHOD_MODEL_PROVIDER_CATALOG_LIST`;缺少 `providers` 时 fail closed,不再把 `get_api_key_providers``get_system_provider_catalog`、renderer mock 或 Electron legacy facade 当作前端事实源。`scripts/provider-settings-current-smoke.mjs` 已通过真实 GUI 点击路径证明 AI 服务商设置页和“添加模型”catalog 读取经 `app_server_handle_json_lines` 观察到 `modelProvider/list``modelProvider/catalog/list`,且未观察到旧 Provider 命令。单 Provider 读取、自定义 Provider 写入、API Key 写入 / 启停 / 导入导出、UI state、连接测试和 chat test 仍暂保 compat,因为 App Server 当前没有完整等价 secret / live provider test method
25. `src/lib/api/automation.ts`scheduler / job / schedule 网关已通过 `AppServerClient.request(...)` 进入 App Server current 主链,覆盖 `automationScheduler/config/read|update|status``automationJob/list|read|create|update|delete|runNow|health|runHistory``automationSchedule/preview|validate`;缺少必需 result 时 fail closed,不再把 `get_automation_*``*_automation_job`、renderer mock 或 Electron legacy facade 当作前端事实源。`scripts/automation-current-smoke.mjs` 已通过真实 GUI 点击路径证明持续流程任务列表读取经 `app_server_handle_json_lines` 观察到 `automationJob/list`,且未观察到旧 automation 命令族。旧 Tauri / Electron / DevBridge automation command surface 已判为 `dead` 并从 runner、dispatcher、Electron host 白名单和 mock priority 中移除;`automationJob/runNow` 已进入 current 协议,但 App Server 自动化执行器尚未迁完,当前实现 fail closed,不回退旧 Tauri 执行器
26. `src/lib/api/apiKeyProvider.ts` 的 Provider list/catalog/read/create/update/delete/sort、API Key create/update/delete/next/usage/error、config import/export、UI state、连接测试与 chat test 已通过 `AppServerClient.request(...)` 进入 App Server current 主链,并直接复用 `METHOD_MODEL_PROVIDER_*``METHOD_MODEL_PROVIDER_KEY_*``METHOD_MODEL_PROVIDER_UI_STATE_*` `METHOD_MODEL_PROVIDER_CONFIG_*`;缺少必需 result 时 fail closed,不再把 `get_api_key_providers``get_system_provider_catalog``get_provider_ui_state``set_provider_ui_state`renderer mock 或 Electron legacy facade 当作前端事实源。`scripts/provider-settings-current-smoke.mjs` 已通过真实 GUI 点击路径证明 AI 服务商设置页和“添加模型”catalog 读取经 `app_server_handle_json_lines` 观察到 `modelProvider/list``modelProvider/catalog/list`,且未观察到旧 Provider 命令。单 Provider、secret 写入、UI state、导入导出和连接测试的 GUI 逐项证据仍缺,但协议、client、processor 与前端 gateway 已走 App Server current
27. `src/lib/dev-bridge/safeInvoke.ts` 的 Electron IPC 分支已复用命令超时 profile;`app_server_handle_json_lines` 内的 `agentSession/list` 识别为 `agent-session-list` 8 秒 profile,超时后 fail closed 并记录 `electron-ipc` trace / error,不回退 legacy desktop host、DevBridge mock 或 renderer mock。侧栏最近对话 / 归档通过 `listAgentRuntimeSessions -> agentSession/list` 读取时,底层 App Server JSON-RPC 无返回也必须 settle,不能让 UI 永久 loading。`scripts/sidebar-sessions-current-smoke.mjs` 已通过真实侧栏 GUI 读取证明 `agentSession/list``app_server_handle_json_lines` 到达 Electron-host App Server,最近列表和归档列表都能退出 loading,且未观察到 `agent_runtime_list_sessions`P3.197 进一步要求 seeded remembered workspace(默认 `workspace-1`)不得出现在任何 `agentSession/list.params.workspaceId`,避免本地记忆项目污染最近 / 归档列表;P3.198 已把 seeded workspace 禁污染、current App Server list path、legacy list 禁止和失败 summary options 固化为 `scripts/sidebar-sessions-current-smoke.test.mjs` 静态守卫。
28. `src/lib/api/agentRuntime/appServerSessionClient.ts``src/lib/api/agentRuntime/inventoryClient.ts``src/lib/api/memory.ts` 已把 `agentSession/list``workspaceSkillBindings/list``projectMemory/read` 的前端 method 引用收敛到 `packages/app-server-client/src/protocol.ts``METHOD_*` 常量;这些 current gateway 不再各自硬编码 method 字符串,避免 Electron / App Server / content-studio 等后续消费者复制出第二套协议事实源。
29. `lime-rs/crates/app-server/src/local_data_source.rs``agentSession/list` / `agentSession/read` 数据源已覆盖 current timeline 表与真实旧 `agent_messages` message-only 会话;列表去重合并后统一排序 / limit,详情对无 timeline entries 的既有会话调用 `lime_agent` 读取真实 runtime detail。该补齐发生在 App Server JSON-RPC 后端事实源内,不新增 Electron 后端逻辑、不回退旧 Tauri、不引入 mock。
30. `scripts/settings-all-pages-current-smoke.mjs` 已补真实设置页 broad smoke:通过 Electron-host DevBridge 进入设置页,逐项点击 15 个设置入口并覆盖 24 个页面 / 子页快照,断言无 DevBridge 断连、无 unsupported command 文案、无 safeInvoke error buffer、无 console error / 未分类 warning、无 invoke JSON-RPC error response;最新证据位于 `.lime/qc/gui-evidence/settings-current/settings-current-summary.json`,显示 `ok=true``tabs=15``pages=24``invokeCount=97``consoleErrors=0``unexpectedConsoleWarnings=0``invokeResponseErrors=0`。About 页更新检查在开发态缺少 `app-update.yml` 时不再向 console 写 updater 诊断 warning,只保留用户可见的失败状态。该证据只证明设置页首屏点击不会再因桥接 unsupported / warning 断裂,并观察到 `app_server_handle_json_lines` 中的 `workspace/default/ensure``agentAppInstalled/list``modelProvider/list``model/list``modelPreferences/list``modelSyncState/read``knowledgePack/list``agentSession/list``automationJob/list``projectMemory/read``workspace/list` 等 current method。`get_experimental_config` / `save_experimental_config` 已接到 Electron `config.json` current 事实源;同一网络证据包含 `get_provider_ui_state``get_usage_stats``unified_memory_stats`、MCP / browser connector / updater / voice / automation scheduler 等 Desktop compat 或 degraded 桌面壳诊断命令,所以不能把 P3.177 / P3.192 误判为“设置页全部 current 化”。
30. `scripts/settings-all-pages-current-smoke.mjs` 已补真实设置页 broad smoke:通过 Electron-host DevBridge 进入设置页,逐项点击 15 个设置入口并覆盖 24 个页面 / 子页快照,断言无 DevBridge 断连、无 unsupported command 文案、无 safeInvoke error buffer、无 console error / 未分类 warning、无 invoke JSON-RPC error response;最新证据位于 `.lime/qc/gui-evidence/settings-current/settings-current-summary.json`,显示 `ok=true``tabs=15``pages=24``invokeCount=97``consoleErrors=0``unexpectedConsoleWarnings=0``invokeResponseErrors=0`。About 页更新检查在开发态缺少 `app-update.yml` 时不再向 console 写 updater 诊断 warning,只保留用户可见的失败状态。该证据只证明设置页首屏点击不会再因桥接 unsupported / warning 断裂,并观察到 `app_server_handle_json_lines` 中的 `workspace/default/ensure``agentAppInstalled/list``modelProvider/list``model/list``modelPreferences/list``modelSyncState/read``knowledgePack/list``agentSession/list``automationJob/list``projectMemory/read``workspace/list` 等 current method。`get_experimental_config` / `save_experimental_config` 已接到 Electron `config.json` current 事实源;该历史网络证据包含 `get_provider_ui_state`,但 Provider UI state 现已迁入 `modelProviderUiState/read|write` current 主链,旧 Provider façade 不得继续作为 Desktop compat 证据;`get_usage_stats``unified_memory_stats`、MCP / browser connector / updater / voice / automation scheduler 等仍是 Desktop compat 或 degraded 桌面壳诊断命令,所以不能把 P3.177 / P3.192 误判为“设置页全部 current 化”。
31. `scripts/code-runtime-fixture-smoke.mjs` 的离线 code runtime fixture smoke 已把正向 runtime 证据迁到 App Server JSON-RPC:会话、provider/model update、自然语言 turn start、read model 轮询与 evidence export 均经 `app_server_handle_json_lines -> agentSession/start/update/turn/start/read -> evidence/export`;脚本禁止再把 `agent_runtime_create_session``agent_runtime_update_session``agent_runtime_submit_turn``agent_runtime_get_session``agent_runtime_get_thread_read``agent_runtime_export_evidence_pack` 当成完成证据。file checkpoint / diff 仍保留 `agent_runtime_list_file_checkpoints``agent_runtime_diff_file_checkpoint` compat,因为 App Server 目前没有等价 checkpoint method;该 smoke 仍是 localhost fixture 离线证据,不等同于完整 Claw live GUI streaming E2E,也不等同于 Codex 一等 `thread/*` 已实现。
32. `scripts/claw-chat-ready-streaming-smoke.mjs` 已作为 Chromium + DevBridge(electron-host transport) Claw live streaming E2E 当前入口收紧证据边界,不等同 Playwright `_electron.launch()` / 真实 Electron renderer IPC E2E:必须显式 `--allow-live-provider``LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1` 才会进入 DevBridge / Provider 阶段;进入后必须先确认 DevBridge health 的 `transport=electron-host`,否则不能把结果当作 Electron current GUI 证据。该脚本已经通过网络捕获和 summary 断言覆盖 `agentSession/turn/start``agentSession/turn/cancel``agentSession/event``agentSession/read`、event 后 read model 回流、无 runtime mock fallback、provider/model preference 保持与恢复 turn 可见性;本轮只补了 electron-host 与未授权 gate 守卫,尚未获得 live provider 授权运行真实 streaming。
33. `AppSidebar``AgentChatWorkspace` 的 workspace 过滤投影已收口:本地记忆项目只保留导航上下文,不能在未验证前成为 App Server `agentSession/list.workspaceId` 过滤条件;Agent 工作台仅在 `projectSelectionSource=remembered` 且项目尚未加载为同一 ID 时暂缓 runtime workspace,显式外部项目、初始会话、话题绑定项目、默认项目和手动选择继续按 current 主路径进入 `useAgentChatUnified`
@@ -89,21 +89,21 @@ Electron main 是 Desktop Host bridge,不是后端适配层,也不是第二
## 4. Workspace / Skill / Model 矩阵
| 用户能力 | App Server method | Electron Host bridge | 前端 caller | 当前状态 | 备注 |
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| workspace list/read/default/ensure | `workspace/*` | 通用 JSONL 可达;legacy `workspace_*` facade 仅兼容投影 | `src/lib/api/project.ts -> AppServerClient.request` | `partial` | 读 / ensure gateway 已直连 App Server method,并有契约守卫;workspace 写命令与 content 仍是 compat,仍缺真实 GUI / E2E 证据。 |
| skill list/read | `skill/list` / `skill/read` | 通用 JSONL 可达;legacy `list_executable_skills` / `get_skill_detail` facade 仅兼容投影 | `src/lib/api/skill-execution.ts -> AppServerClient.request` | `partial` | list/read 已直连 App Server`scripts/skills-current-smoke.mjs` 已补 slash skill preflight 真实 GUI / bridge 证据,确认 `skill/list` 返回 `result.skills` 数组且无旧 Skill 命令、`execute_skill``agentSession/turn/start``skill/read` GUI 触发和 `execute_skill` current 化仍缺。 |
| workspace registered skills / skill bindings | `workspaceRegisteredSkills/list` / `workspaceSkillBindings/list` | `app_server_handle_json_lines -> workspaceRegisteredSkills/list` / `workspaceSkillBindings/list` | `capabilityDraftsApi.listRegisteredSkills` / `createInventoryClient().listWorkspaceSkillBindings` | `partial` | 前端 registered skills discovery 与 runtime binding readiness gateway 均已直连 App Server method`scripts/workspace-skill-bindings-current-smoke.mjs` 已补真实 Skills 用户安装页 / 已保存技能面板 GUI / bridge 证据,确认 `workspaceRegisteredSkills/list` 返回有效 `skills` 数组、`workspaceSkillBindings/list` 返回有效 `bindings` 数组,且无旧 `capability_draft_list_registered_skills` / `agent_runtime_list_workspace_skill_bindings`。Capability Draft create / verify / register 与完整 Skills 工作台执行闭环仍缺。 |
| project memory aggregate read | `projectMemory/read` | `app_server_handle_json_lines -> projectMemory/read` | `getProjectMemory` | `partial` | 聚合读已直连 App Server method`scripts/project-memory-current-smoke.mjs` 已补真实 GUI / bridge 证据,确认 Agent 首页默认工作区记忆读取 `projectMemory/read` 且无旧 `project_memory_get` 命令;角色 / 世界观 / 大纲 CRUD 仍是 compat。 |
| model registry/preferences/sync read | `model/list` / `modelPreferences/list` / `modelSyncState/read` | 通用 JSONL 可达;legacy `get_model_*` facade 仅兼容投影 | `src/lib/api/modelRegistry.ts -> AppServerClient.request` | `partial` | `scripts/model-registry-current-smoke.mjs` 已补设置页“添加模型”真实 GUI / bridge 证据,确认 `model/list``modelPreferences/list``modelSyncState/read` 均返回有效 result 且无旧 model 命令、模型写入 / refresh side-effect 或 Agent turnalias/provider/tier scoped gateway 已 current 化但 GUI 证据仍缺,provider ids、favorite / refresh / live fetch 等仍是 compat 或待补 App Server method。 |
| API key provider list/catalog | `modelProvider/*` | 通用 JSONL 可达;legacy `get_api_key_providers` / `get_system_provider_catalog` facade 仅兼容投影 | `src/lib/api/apiKeyProvider.ts -> AppServerClient.request` | `partial` | Provider 列表 / catalog 读链已直连 App Server`scripts/provider-settings-current-smoke.mjs` 已补真实 GUI / bridge 证据,确认设置页读取 `modelProvider/list`、添加模型 catalog 读取 `modelProvider/catalog/list` 且无旧 Provider 命令;单 Provider、secret 写入、UI state、导入导出和连接测试仍是 compat。 |
| knowledge pack list | `knowledgePack/list` | 通用 JSONL 可达;legacy `knowledge_list_packs` facade 仅兼容投影 | `src/lib/api/knowledge.ts -> AppServerClient.request` | `partial` | 知识包列表读链已直连 App Server;`scripts/knowledge-current-smoke.mjs` 已补真实 GUI / bridge 证据,确认项目资料页面读取 `knowledgePack/list` 且无旧 `knowledge_list_packs` 命令;get/import/compile/default/status/resolve/validate 仍是 compat。 |
| Agent App installed list | `agentAppInstalled/list` | 通用 JSONL 可达;legacy `agent_app_list_installed` facade 仅兼容投影 | `src/lib/api/agentApps.ts -> AppServerClient.request` | `partial` | 已安装列表读链已直连 App Server;`scripts/agent-app/installed-current-smoke.mjs` 已补真实 GUI / bridge 证据,确认 Agent Apps 页面读取 `agentAppInstalled/list` 且无旧 `agent_app_list_installed` 命令;保存、安装、卸载和 shell side-effect 仍是 compat。 |
| automation job list | `automationJob/list` | 通用 JSONL 可达;legacy `get_automation_jobs` facade 仅兼容投影 | `src/lib/api/automation.ts -> AppServerClient.request` | `partial` | 自动化任务列表读链已直连 App Server`scripts/automation-current-smoke.mjs` 已补真实 GUI / bridge 证据,确认持续流程任务列表读取 `automationJob/list` 且无旧 `get_automation_jobs` 命令;scheduler/status/write/run/history 等仍是 compat。 |
| Connect deep link resolve/open | `connectDeepLink/resolve` / `connectOpenDeepLink/resolve` | Electron protocol/open-url / 首启参数 / `second-instance` -> preload `deepLink.getCurrent/onOpenUrl` -> `app_server_handle_json_lines` | `useDeepLink -> src/lib/api/connect.ts` | `wired` | `scripts/connect-deep-link-current-smoke.mjs` 已用真实 Electron 首启参数证明 `lime://connect` 经 preload bridge 到达 `connectDeepLink/resolve` 并出现确认弹窗;`scripts/connect-open-deep-link-current-smoke.mjs` 已用真实 Electron 首启参数证明 `lime://open?kind=prompt&slug=gemini-longform-master...``connectOpenDeepLink/resolve` 到达 Agent 官网入口 banner / prompt 状态。两条证据均未观察到旧 Tauri deep-link 事件或旧 Connect 命令。 |
| Connect API Key save / callback | `connectRelayApiKey/save` / `connectCallback/send` | Electron preload deep link -> `app_server_handle_json_lines -> connectRelayApiKey/save / connectCallback/send`;旧 `save_relay_api_key` / `send_connect_callback` 不得回流 | `useDeepLink` / `useConnectCallback -> src/lib/api/connect.ts` | `wired` | `scripts/connect-deep-link-save-current-smoke.mjs` 已用真实 Electron 首启参数、隔离 app data 与本地 registry fixture 点击“确认添加”,证明确认后弹窗关闭,`connectDeepLink/resolve``connectRelayApiKey/save``connectCallback/send` 均经 `electron-ipc``app_server_handle_json_lines` 到达 App Server,且未观察到旧 Tauri deep-link 事件或旧 Connect 命令;本证据只覆盖 callback JSON-RPC method path,外部 HTTPS webhook 网络投递仍未验证。 |
| Agent App UI runtime | `agentAppUiRuntime/start` / `agentAppUiRuntime/status` / `agentAppUiRuntime/stop` | 通用 JSONL 可达;legacy `agent_app_start_ui_runtime` / `agent_app_get_ui_runtime_status` / `agent_app_stop_ui_runtime` facade 仅兼容投影 | `src/lib/api/agentApps.ts -> AppServerClient.request` | `wired` | protocol / schema / TS client / Rust processor / RuntimeCore helper / 前端 gateway 已成组接入 App Server current method,生产 gateway 不再 safeInvoke 旧 `agent_app_*` 或 mock`scripts/check-app-server-client-contract.mjs``src/lib/api/agentApps.test.ts` 已守住 no legacy fallback。`scripts/agent-app/ui-runtime-current-smoke.mjs` 已证明真实前端 gateway 的 status / stop / start 经 `app_server_handle_json_lines -> agentAppUiRuntime/*` 到达 App Server,且未观察到旧 `agent_app_*_ui_runtime``scripts/agent-app/ui-runtime-stdio-fixture-smoke.mjs` 已用临时 app data 和 fixture installed state 证明 App Server stdio current 链路能真实 start -> `/api/bootstrap` ready -> entryUrl -> status running -> stop`scripts/agent-app/ui-runtime-electron-fixture-smoke.mjs` 已证明真实 Electron-host GUI 能从正式侧栏进入 runtime page,发出 `agentAppUiRuntime/start`iframe 打开 `entryUrl` 并加载“内容工厂 / 工作台状态”。该 `wired` 只针对 UI 子进程生命周期,不代表 Agent App 对话 turn 完成;后续对话仍必须复用 `agentSession/* -> RuntimeCore -> AsterBackend`。 |
| Agent App task facade | `agentSession/start` / `agentSession/turn/start` / `agentSession/read` / `agentSession/action/respond` / `agentSession/turn/cancel` | `agent_app_runtime_*` Electron Desktop Host facade -> App Server JSON-RPC -> RuntimeCorelegacy `agent_runtime_*` 只作为负向回归扫描对象,不得作为完成证据 | `src/lib/api/agentAppRuntime.ts -> safeInvoke -> electron/hostCommands.ts` | `wired` | `electron/hostCommands.ts` 已把 `agent_app_runtime_start_task/get_task/cancel_task/submit_host_response` 投影到 `agentSession/start``agentSession/turn/start``agentSession/read``agentSession/action/respond``agentSession/turn/cancel`,并保留 `RuntimeOptions.hostOptions.asterChatRequest``turn_config` 镜像。`scripts/agent-app/runtime-electron-fixture-smoke.mjs` 已用真实 Electron preload bridge 和显式 external backend fixture 证明四条 facade 可进入同一 App Server RuntimeCore session`scripts/agent-app/runtime-electron-task-fixture-smoke.mjs` 已进一步证明真实 Electron renderer bridge 可完成 start -> read blocked -> host response -> read running -> cancel -> read cancelledsummary / backend log 记录 `turnStart/actionRespond/turnCancel`,且 `RuntimeOptions.hostOptions.asterChatRequest``turn_config` 镜像抵达 external backend。`P3.216` 已补 Agent App iframe SDK Host Bridge 真实 Electron fixture,证明 iframe 内 `createLimeHostBridgeCapabilityInvoker -> lime.agent.startTask/getTask/submitHostResponse/cancelTask` 经正式 runtime page / Host Bridge / Electron IPC 进入 App Server current 主链;`P3.217` 已把 RuntimeCore `artifact.snapshot` 通过 `agentSession/read.detail.thread_read.artifacts` / `detail.artifacts` 回流到 SDK task record,并用同一 SDK fixture 断言 `artifact:created``evidence:recorded``contentFactoryWorkspacePatch``P3.218` 已把 App Server read model 的 `thread_read.tool_calls` / `tool_calls` replay 成 SDK task record 的 `task:toolCall`,真实 Electron SDK fixture summary 显示 `toolEvidence.toolEventType=task:toolCall``toolName=WebFetch``readModelStatus=completed`。该证据不使用 mock backend、不回退旧 `agent_runtime_*`,但仍不等同于 live provider 完整 Claw streaming,或真实模型执行完整 WebFetch。 |
| 用户能力 | App Server method | Electron Host bridge | 前端 caller | 当前状态 | 备注 |
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| workspace list/read/default/ensure | `workspace/*` | 通用 JSONL 可达;legacy `workspace_*` facade 仅兼容投影 | `src/lib/api/project.ts -> AppServerClient.request` | `partial` | 读 / ensure gateway 已直连 App Server method,并有契约守卫;workspace 写命令与 content 仍是 compat,仍缺真实 GUI / E2E 证据。 |
| skill list/read | `skill/list` / `skill/read` | 通用 JSONL 可达;legacy `list_executable_skills` / `get_skill_detail` facade 仅兼容投影 | `src/lib/api/skill-execution.ts -> AppServerClient.request` | `partial` | list/read 已直连 App Server`scripts/skills-current-smoke.mjs` 已补 slash skill preflight 真实 GUI / bridge 证据,确认 `skill/list` 返回 `result.skills` 数组且无旧 Skill 命令、`execute_skill``agentSession/turn/start``skill/read` GUI 触发和 `execute_skill` current 化仍缺。 |
| workspace registered skills / skill bindings | `workspaceRegisteredSkills/list` / `workspaceSkillBindings/list` | `app_server_handle_json_lines -> workspaceRegisteredSkills/list` / `workspaceSkillBindings/list` | `capabilityDraftsApi.listRegisteredSkills` / `createInventoryClient().listWorkspaceSkillBindings` | `partial` | 前端 registered skills discovery 与 runtime binding readiness gateway 均已直连 App Server method`scripts/workspace-skill-bindings-current-smoke.mjs` 已补真实 Skills 用户安装页 / 已保存技能面板 GUI / bridge 证据,确认 `workspaceRegisteredSkills/list` 返回有效 `skills` 数组、`workspaceSkillBindings/list` 返回有效 `bindings` 数组,且无旧 `capability_draft_list_registered_skills` / `agent_runtime_list_workspace_skill_bindings`。Capability Draft create / verify / register 与完整 Skills 工作台执行闭环仍缺。 |
| project memory aggregate read | `projectMemory/read` | `app_server_handle_json_lines -> projectMemory/read` | `getProjectMemory` | `partial` | 聚合读已直连 App Server method`scripts/project-memory-current-smoke.mjs` 已补真实 GUI / bridge 证据,确认 Agent 首页默认工作区记忆读取 `projectMemory/read` 且无旧 `project_memory_get` 命令;角色 / 世界观 / 大纲 CRUD 仍是 compat。 |
| model registry/preferences/sync read | `model/list` / `modelPreferences/list` / `modelSyncState/read` / `modelProvider/fetchModels` / `modelProviderAlias/*` | 通用 JSONL 可达;legacy `get_model_*` / `fetch_provider_models_auto` facade 仅允许停留在负向守卫和历史记录 | `src/lib/api/modelRegistry.ts -> AppServerClient.request` | `partial` | `scripts/model-registry-current-smoke.mjs` 已补设置页“添加模型”真实 GUI / bridge 证据,确认 `model/list``modelPreferences/list``modelSyncState/read` 均返回有效 result 且无旧 model 命令、模型写入 / refresh side-effect 或 Agent turnalias/provider/tier scoped gateway 与 provider live fetch 已 current 化但 GUI 逐项证据仍缺,provider ids、favorite / refresh 等仍是 compat 或待补 App Server method。 |
| API key provider list/catalog | `modelProvider/*` / `modelProviderKey/*` / `modelProviderUiState/*` / `modelProviderConfig/*` | 通用 JSONL 可达;legacy `get_api_key_providers` / `get_system_provider_catalog` / `get_provider_ui_state` / `set_provider_ui_state` facade 已判为 `dead`,不得回流 | `src/lib/api/apiKeyProvider.ts -> AppServerClient.request` | `partial` | Provider list/catalog/read/write、API Key 写入 / 启停、UI state、导入导出、连接测试与 chat test gateway 已直连 App Server`scripts/provider-settings-current-smoke.mjs` 已补真实 GUI / bridge 证据,确认设置页读取 `modelProvider/list`、添加模型 catalog 读取 `modelProvider/catalog/list` 且无旧 Provider 命令;单 Provider、secret 写入、UI state、导入导出和连接测试的 GUI 逐项证据仍缺。 |
| knowledge pack list | `knowledgePack/list` | 通用 JSONL 可达;legacy `knowledge_list_packs` facade 仅兼容投影 | `src/lib/api/knowledge.ts -> AppServerClient.request` | `partial` | 知识包列表读链已直连 App Server;`scripts/knowledge-current-smoke.mjs` 已补真实 GUI / bridge 证据,确认项目资料页面读取 `knowledgePack/list` 且无旧 `knowledge_list_packs` 命令;get/import/compile/default/status/resolve/validate 仍是 compat。 |
| Agent App installed list | `agentAppInstalled/list` | 通用 JSONL 可达;legacy `agent_app_list_installed` facade 仅兼容投影 | `src/lib/api/agentApps.ts -> AppServerClient.request` | `partial` | 已安装列表读链已直连 App Server;`scripts/agent-app/installed-current-smoke.mjs` 已补真实 GUI / bridge 证据,确认 Agent Apps 页面读取 `agentAppInstalled/list` 且无旧 `agent_app_list_installed` 命令;保存、安装、卸载和 shell side-effect 仍是 compat。 |
| automation scheduler/job/schedule | `automationScheduler/config/read` / `automationScheduler/config/update` / `automationScheduler/status` / `automationJob/list` / `automationJob/read` / `automationJob/create` / `automationJob/update` / `automationJob/delete` / `automationJob/runNow` / `automationJob/health` / `automationJob/runHistory` / `automationSchedule/preview` / `automationSchedule/validate` | `app_server_handle_json_lines -> automation*`;旧 `get_automation_*` / `*_automation_job` Tauri、Electron facade、DevBridge dispatcher 和 mock surface 已判为 `dead`,不得回流 | `src/lib/api/automation.ts -> AppServerClient.request` | `partial` | 自动化 scheduler/job/schedule 网关已直连 App Server method,并有前端 no legacy fallback 测试`scripts/automation-current-smoke.mjs` 已补真实 GUI / bridge 证据,确认持续流程任务列表读取 `automationJob/list` 且无旧 automation 命令族。`automationJob/runNow` 仍是 current 缺口:协议、client、前端和 processor 已接入,但 App Server 自动化执行器尚未迁完,当前 fail closed,不回退旧 Tauri 执行器;`scripts/managed-objective-automation-smoke.mjs` 也改为只走 `automationJob/create/runNow/runHistory`,遇到该执行器缺口时失败而不是调用旧命令。 |
| Connect deep link resolve/open | `connectDeepLink/resolve` / `connectOpenDeepLink/resolve` | Electron protocol/open-url / 首启参数 / `second-instance` -> preload `deepLink.getCurrent/onOpenUrl` -> `app_server_handle_json_lines` | `useDeepLink -> src/lib/api/connect.ts` | `wired` | `scripts/connect-deep-link-current-smoke.mjs` 已用真实 Electron 首启参数证明 `lime://connect` 经 preload bridge 到达 `connectDeepLink/resolve` 并出现确认弹窗;`scripts/connect-open-deep-link-current-smoke.mjs` 已用真实 Electron 首启参数证明 `lime://open?kind=prompt&slug=gemini-longform-master...``connectOpenDeepLink/resolve` 到达 Agent 官网入口 banner / prompt 状态。两条证据均未观察到旧 Tauri deep-link 事件或旧 Connect 命令。 |
| Connect API Key save / callback | `connectRelayApiKey/save` / `connectCallback/send` | Electron preload deep link -> `app_server_handle_json_lines -> connectRelayApiKey/save / connectCallback/send`;旧 `save_relay_api_key` / `send_connect_callback` 不得回流 | `useDeepLink` / `useConnectCallback -> src/lib/api/connect.ts` | `wired` | `scripts/connect-deep-link-save-current-smoke.mjs` 已用真实 Electron 首启参数、隔离 app data 与本地 registry fixture 点击“确认添加”,证明确认后弹窗关闭,`connectDeepLink/resolve``connectRelayApiKey/save``connectCallback/send` 均经 `electron-ipc``app_server_handle_json_lines` 到达 App Server,且未观察到旧 Tauri deep-link 事件或旧 Connect 命令;本证据只覆盖 callback JSON-RPC method path,外部 HTTPS webhook 网络投递仍未验证。 |
| Agent App UI runtime | `agentAppUiRuntime/start` / `agentAppUiRuntime/status` / `agentAppUiRuntime/stop` | 通用 JSONL 可达;legacy `agent_app_start_ui_runtime` / `agent_app_get_ui_runtime_status` / `agent_app_stop_ui_runtime` facade 仅兼容投影 | `src/lib/api/agentApps.ts -> AppServerClient.request` | `wired` | protocol / schema / TS client / Rust processor / RuntimeCore helper / 前端 gateway 已成组接入 App Server current method,生产 gateway 不再 safeInvoke 旧 `agent_app_*` 或 mock`scripts/check-app-server-client-contract.mjs``src/lib/api/agentApps.test.ts` 已守住 no legacy fallback。`scripts/agent-app/ui-runtime-current-smoke.mjs` 已证明真实前端 gateway 的 status / stop / start 经 `app_server_handle_json_lines -> agentAppUiRuntime/*` 到达 App Server,且未观察到旧 `agent_app_*_ui_runtime``scripts/agent-app/ui-runtime-stdio-fixture-smoke.mjs` 已用临时 app data 和 fixture installed state 证明 App Server stdio current 链路能真实 start -> `/api/bootstrap` ready -> entryUrl -> status running -> stop`scripts/agent-app/ui-runtime-electron-fixture-smoke.mjs` 已证明真实 Electron-host GUI 能从正式侧栏进入 runtime page,发出 `agentAppUiRuntime/start`iframe 打开 `entryUrl` 并加载“内容工厂 / 工作台状态”。该 `wired` 只针对 UI 子进程生命周期,不代表 Agent App 对话 turn 完成;后续对话仍必须复用 `agentSession/* -> RuntimeCore -> AsterBackend`。 |
| Agent App task facade | `agentSession/start` / `agentSession/turn/start` / `agentSession/read` / `agentSession/action/respond` / `agentSession/turn/cancel` | `agent_app_runtime_*` Electron Desktop Host facade -> App Server JSON-RPC -> RuntimeCorelegacy `agent_runtime_*` 只作为负向回归扫描对象,不得作为完成证据 | `src/lib/api/agentAppRuntime.ts -> safeInvoke -> electron/hostCommands.ts` | `wired` | `electron/hostCommands.ts` 已把 `agent_app_runtime_start_task/get_task/cancel_task/submit_host_response` 投影到 `agentSession/start``agentSession/turn/start``agentSession/read``agentSession/action/respond``agentSession/turn/cancel`,并保留 `RuntimeOptions.hostOptions.asterChatRequest``turn_config` 镜像。`scripts/agent-app/runtime-electron-fixture-smoke.mjs` 已用真实 Electron preload bridge 和显式 external backend fixture 证明四条 facade 可进入同一 App Server RuntimeCore session`scripts/agent-app/runtime-electron-task-fixture-smoke.mjs` 已进一步证明真实 Electron renderer bridge 可完成 start -> read blocked -> host response -> read running -> cancel -> read cancelledsummary / backend log 记录 `turnStart/actionRespond/turnCancel`,且 `RuntimeOptions.hostOptions.asterChatRequest``turn_config` 镜像抵达 external backend。`P3.216` 已补 Agent App iframe SDK Host Bridge 真实 Electron fixture,证明 iframe 内 `createLimeHostBridgeCapabilityInvoker -> lime.agent.startTask/getTask/submitHostResponse/cancelTask` 经正式 runtime page / Host Bridge / Electron IPC 进入 App Server current 主链;`P3.217` 已把 RuntimeCore `artifact.snapshot` 通过 `agentSession/read.detail.thread_read.artifacts` / `detail.artifacts` 回流到 SDK task record,并用同一 SDK fixture 断言 `artifact:created``evidence:recorded``contentFactoryWorkspacePatch``P3.218` 已把 App Server read model 的 `thread_read.tool_calls` / `tool_calls` replay 成 SDK task record 的 `task:toolCall`,真实 Electron SDK fixture summary 显示 `toolEvidence.toolEventType=task:toolCall``toolName=WebFetch``readModelStatus=completed`。该证据不使用 mock backend、不回退旧 `agent_runtime_*`,但仍不等同于 live provider 完整 Claw streaming,或真实模型执行完整 WebFetch。 |
## 5. 第一竖切交付标准
@@ -123,7 +123,7 @@ Electron main 是 Desktop Host bridge,不是后端适配层,也不是第二
8. GUI 证据以 Electron 为准:`npm run smoke:electron` 已证明 Electron renderer + Desktop Host bridge + App Server sidecar initialize + Claw 工作台 shell/composer ready 可用;Rust/TS 单测不能单独证明可交付。
9. 第一竖切已有前端业务闭环测试覆盖 App Server turn notification 触发 read model refresh;尚需补真实 GUI 发送 / event refresh / thread read refresh 的业务 E2E,不能把 initialize smoke 或 hook 测试误判成完整聊天闭环。
10. `agent_runtime_submit_turn` / `agent_runtime_interrupt_turn` / `agent_runtime_respond_action`、desktop-host mock、mock-only 测试或 legacy facade 投影只能作为负向回归扫描对象,不能作为 Agent turn lifecycle 的完成证据。
11. 完整 Claw streaming GUI E2E 必须显式传入 `--allow-live-provider``LIME_ALLOW_LIVE_PROVIDER_SMOKE=1`;未授权时 smoke 必须在 `wait-health``prepare-runtime``aster_agent_init``get_api_key_providers``test_api_key_provider_chat` 之前 fail closed,不能用 mock、无模型探针或 Provider 自动探测替代真实业务闭环。
11. 完整 Claw streaming GUI E2E 必须显式传入 `--allow-live-provider``LIME_ALLOW_LIVE_PROVIDER_SMOKE=1`;未授权时 smoke 必须在 `wait-health``prepare-runtime``aster_agent_init``modelProvider/list``modelProvider/testChat` 之前 fail closed,不能用 mock、无模型探针或 Provider 自动探测替代真实业务闭环。
12. 离线 code runtime fixture smoke 可以作为 localhost fixture 下的 App Server JSON-RPC runtime 证据,但必须经 `agentSession/turn/start``agentSession/read``evidence/export`file checkpoint compat 只能标记为剩余缺口,不能把旧 `agent_runtime_*` runtime facade 重新算作第一竖切完成证据。
13. 用户可见发送态必须能由 App Server current `turn.completed` 终态收口;前端收到 `turn.completed` 后不能继续等待 `turn.final_done` 或长 timeout。若 `turn.completed` 携带 assistant 正文则直接完成;若正文为空但已有工具 / 产物等真实完成信号则软完成;若正文为空且没有真实完成信号则立即落失败态并释放输入框。`turn.final_done` 只能作为兼容 / 补充终态,不得成为 current GUI 退出发送态的唯一条件。
14. 从本地存储恢复出的 session id 在发送前必须先被 App Server `agentSession/read` 确认存在且属于当前 workspace;确认失败时只能丢弃本地快照并创建新的 App Server session,不能继续轮询已不存在的 session 或向 stale session 提交 turn。
@@ -164,7 +164,7 @@ Electron main 是 Desktop Host bridge,不是后端适配层,也不是第二
30. `P3.217`Agent App SDK artifact / evidence read model 回流已补真实 Electron fixture 证据。App Server `agentSession/read` 现在把 RuntimeCore artifact summaries 同步暴露到 `detail.thread_read.artifacts``detail.artifacts`SDK task record 可从 current read model replay `artifact:created``evidence:recorded`summary 位于 `.lime/qc/gui-evidence/agent-app-runtime-electron-sdk-fixture/agent-app-runtime-electron-sdk-fixture-summary.json`,显示 `artifactEvidence.artifactEventType=artifact:created``artifactEvidence.evidenceEventType=evidence:recorded``resultArtifactCount=1`。下一刀不再打磨 fixture lifecycle,应转向完整 tool runtime 执行证据或 live provider 授权后的 Claw streaming GUI E2E。
31. `P3.218`Agent App SDK tool call read model replay 已补真实 Electron fixture 证据。`AgentRuntimeCapabilityHost` 现在从 App Server `agentSession/read` detail 的 `thread_read.tool_calls` / `tool_calls` 读取工具事实,并 replay 为 SDK task record 的 `task:toolCall`summary 位于 `.lime/qc/gui-evidence/agent-app-runtime-electron-sdk-fixture/agent-app-runtime-electron-sdk-fixture-summary.json`,显示 `toolEvidence.toolEventType=task:toolCall``toolEvidence.toolName=WebFetch``toolEvidence.outputPreview=fetched https://example.com/agent-app-sdk-fixture``toolEvidence.readModelStatus=completed``toolEvidence.success=true`。下一刀应转向 live provider 授权后的完整 Claw streaming GUI E2E,或把真实 tool runtime 执行从 fixture evidence 扩到 live turn 证据。
32. `P3.219`P3.218 后的默认 Electron GUI smoke 已通过,`npm run verify:gui-smoke` 覆盖 renderer smoke build、Electron main/preload build、`packages/app-server-client` build、Electron host typecheck、App Server sidecar build 与 `scripts/electron/smoke.mjs`,输出确认 `app-server initialized protocol=appserver.v0 version=1.59.0``claw workbench shell ready`。该证据只证明默认 Electron GUI 主路径仍可启动,不等同 live provider 完整 Claw streaming、真实发送、tool runtime live 执行或 `agentSession/event -> agentSession/read` 业务刷新。
33. `P3.220`:未授权 Claw streaming smoke gate 已回归验证,`npm run smoke:claw-chat-ready-streaming -- --timeout-ms 30000` 按预期在 live provider gate 快速失败,并提示必须显式传入 `--allow-live-provider` 或设置 `LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1`;该运行没有进入 `wait-health`、DevBridge health、`aster_agent_init``get_api_key_providers``test_api_key_provider_chat`。完整 Claw streaming GUI E2E 仍等待 live provider 授权,不能用 mock、无模型探针或 external backend fixture 代替。
33. `P3.220`:未授权 Claw streaming smoke gate 已回归验证,`npm run smoke:claw-chat-ready-streaming -- --timeout-ms 30000` 按预期在 live provider gate 快速失败,并提示必须显式传入 `--allow-live-provider` 或设置 `LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1`;该运行没有进入 `wait-health`、DevBridge health、`aster_agent_init``modelProvider/list``modelProvider/testChat`。完整 Claw streaming GUI E2E 仍等待 live provider 授权,不能用 mock、无模型探针或 external backend fixture 代替。
34. `P3.221` / `P3.237`Agent session history Electron fixture 已补真实 current 证据。`scripts/electron/session-history-fixture-smoke.mjs` 使用真实 Electron preload `app_server_handle_json_lines``APP_SERVER_BACKEND_MODE=unavailable`,执行 `initialize -> agentSession/start -> agentSession/read -> agentSession/update(title/provider/model) -> agentSession/update(archived=true) -> agentSession/read -> agentSession/list`summary 位于 `.lime/qc/gui-evidence/agent-session-history-electron-fixture/agent-session-history-electron-fixture-summary.json`。P3.221 证明两次 `read.detail` 都包含 history hydrate 所需的 `turns/items/queued_turns/thread_read` 且 list 可读回更新标题;P3.237 进一步锁住内存 session 的 `archived=true` 必须 fail closed,错误文案包含 `agentSession/update archived is only supported for persisted current timeline sessions`,并确认 `listedSessionArchivedAt == null`。该证据不触发 `agentSession/turn/start`,不调用模型后端,不使用 legacy `agent_runtime_*`、mock backend 或 renderer mock fallbackpersisted current timeline archive / unarchive / sidecar restart readback 已由 P3.246 补真实闭环。
35. `P3.239`:侧栏归档 / 恢复入口已补 current boundary 静态守卫。`src/components/AppSidebar.current-boundary.test.ts` 锁住 `handleToggleSessionArchive` 只能经 `updateAgentRuntimeSession({ session_id, archived })`session gateway update / delete 只能委托 `createAppServerSessionClient()`App Server gateway 必须使用 `agentSession/list/read/update` 事实源;该守卫只防回流,不证明 persisted timeline archive / unarchive 成功。
36. `P3.240`session gateway 归档请求投影已补 current boundary 守卫。`src/lib/api/agentRuntime/sessionClient.current-boundary.test.ts` 锁住 `createSessionClient(...)` 对归档、恢复和兼容 delete 的请求必须投影为 `appServerClient.updateSession({ sessionId, archived })`,并确认注入的 legacy `invokeCommand` 不被调用;该守卫补齐侧栏 UI 与 App Server session gateway 之间的中间层证据,仍不证明 persisted timeline archive / unarchive 成功。
@@ -185,7 +185,7 @@ Electron main 是 Desktop Host bridge,不是后端适配层,也不是第二
51. `P3.265`P3.262 的 live WebSearch/WebFetch 工具事实门禁已收紧为 turn-scoped。`scripts/claw-chat-ready-streaming-smoke.mjs` 现在从 App Server `agentSession/read` 投影中提取 `turn_id` / `turnId`,并只接受本次 `liveWebTurnId` 下完成态 `WebSearch``WebFetch``requiredForTurn` / `allRequiredCompletedForTurn` 是 verdict 条件,历史 session 中其他 turn 的工具调用不能让本次 live 工具 turn 误通过。`scripts/lib/live-provider-smoke-gate.test.mjs` 已补静态守卫。本证据仍不代表真实 Provider 已运行,下一刀仍是显式授权后跑 live streaming / WebSearch-WebFetch 实际联网 E2E。
52. `P3.268`P3.265 的 turn-scoped live WebSearch/WebFetch 判定已从 smoke 内联逻辑抽到 `scripts/lib/claw-chat-live-web-tool-evidence.mjs`,并补 `scripts/lib/claw-chat-live-web-tool-evidence.test.mjs` 行为单测。测试明确证明旧 turn 的完成态工具调用不能让本次 `liveWebTurnId` 通过,且支持 App Server read model 中 `turn_id` / `turnId` 与 inherited turn scope。真实 live Provider 仍未运行,下一刀仍需授权后跑完整联网 E2E。
53. `P3.269`live WebSearch/WebFetch helper 已补 App Server JSON-RPC / read model envelope 覆盖。`collectToolCallsFromValue(...)` 现在会递归 `result/detail/session/session_detail/sessionDetail`,行为测试锁住 `agentSession/read` 常见 `result.detail.thread_read.tool_calls` 形状,避免授权后因只传入 JSON-RPC response / read result 外壳而漏掉工具事实。真实 live Provider 仍未运行,下一刀仍需授权后跑完整联网 E2E。
54. `P3.271`live Provider gate 已从根层脚本扫描扩展为递归扫描 `scripts/**/*.mjs``scripts/lib/live-provider-smoke-gate.test.mjs` 现在覆盖子目录 smoke,排除测试文件、`scripts/lib/**``check-*` 与测试夹具路径;递归扫描下当前脚本仍无未授权 `agent_runtime_submit_turn` / `test_api_key_provider_chat` / `knowledge_compile_pack` 回流。真实 live Provider 仍未运行,下一刀仍需授权后跑完整联网 E2E。
54. `P3.271`live Provider gate 已从根层脚本扫描扩展为递归扫描 `scripts/**/*.mjs``scripts/lib/live-provider-smoke-gate.test.mjs` 现在覆盖子目录 smoke,排除测试文件、`scripts/lib/**``check-*` 与测试夹具路径;递归扫描下当前脚本仍无未授权 `agent_runtime_submit_turn` / App Server `modelProvider/testChat` / `knowledge_compile_pack` 回流。真实 live Provider 仍未运行,下一刀仍需授权后跑完整联网 E2E。
55. `P3.272`live WebSearch/WebFetch turn 已补自身 provider/model 与 fast routing verdict。`scripts/claw-chat-ready-streaming-smoke.mjs` 现在要求 Web 工具 turn 的 `turn_config` 同样命中 explicit provider/model,且 `metadata.harness.fast_response_routing` 未开启;这些断言已纳入最终 pass 条件。真实 live Provider 仍未运行,下一刀仍需授权后跑完整联网 E2E。
56. `P3.273``claw-chat-ready-streaming` 的 final `threadRead` summary 已保留 App Server current `detail.thread_read``appServerThreadReadFromSessionRead(...)` 现在优先展开 `detail.thread_read/threadRead`,避免把工具事实、artifact 和 routing 证据降级成由 turns 重建的最小投影。真实 live Provider 仍未运行,下一刀仍需授权后跑完整联网 E2E。
57. `P3.274`live WebSearch/WebFetch verdict 已补工具输出非空证据。`scripts/lib/claw-chat-live-web-tool-evidence.mjs` 现在计算 `outputPresent` / `allRequiredOutputPresentForTurn``scripts/claw-chat-ready-streaming-smoke.mjs` 要求本次 live Web turn 下 WebSearch/WebFetch 均 completed 且有输出预览。真实 live Provider 仍未运行,下一刀仍需授权后跑完整联网 E2E。
@@ -60,7 +60,7 @@
- 2026-05-26:复跑 P6 非 live continuation 守卫与命令边界:`env -u LIME_ALLOW_LIVE_PROVIDER_SMOKE -u LIME_REAL_API_TEST npx vitest run "scripts/lib/managed-objective-continuation-smoke-core.test.mjs" "scripts/lib/managed-objective-guardrails-core.test.mjs"` 13 条通过,`cargo test --manifest-path "lime-rs/Cargo.toml" objective_continuation --lib` 14 条通过,`npm run test:contracts` 通过。该组证据覆盖自动续跑 allow / skip / budget_limited / needs_input / completed 停止、模型工具面禁用 objective 命令,以及 current 命令契约同步;仍不声称真实 Provider 多轮 smoke 已完成。
- 2026-05-26P6 continuation smoke 默认改为 localhost OpenAI-compatible fixture`scripts/managed-objective-continuation-smoke.mjs` 未显式授权时不再使用 `LIME_AGENT_QC_PROVIDER / LIME_E2E_PROVIDER / LIME_DEFAULT_PROVIDER` 选择真实 Provider,显式传 `--provider-preference` / `--model-preference` 但缺少 `--allow-live-provider` 会立即失败;首轮 `agent_runtime_submit_turn` 带 Direct `providerConfig` 指向 fixture。自动续跑会继承当前 provider/model/base_url,避免第二轮退回真实 Provider 池,同时不把 `api_key` 写入持久化队列 payload。该证据把 P6 多轮 smoke 从“必须消耗 live Provider”收敛为默认离线可验证;是否额外要求 live Provider 证据仍由发布验收口径单独决定。
- 2026-05-26:第 9 节补齐 automation `AgentTurn` owner-session evidence 回归:新增拆分后的 `automation_owner_session_evidence_tests.rs`,证明 `Skill/Intelligent` automation due job 的 finish metadata 会保留 `harness.managed_objective`、workspace skill runtime enable、`continuation_policy.dispatch=agent_runtime_submit_turn` 与 runtime `session_id`,并可通过 `AgentRunDao::list_runs_by_session` 查询到 automation owner run。验证已覆盖 `cargo test --manifest-path "lime-rs/Cargo.toml" automation_owner_session_evidence --lib`,不触发真实 Provider。
- 2026-05-26:第 9 节补齐默认离线 automation owner smoke 入口:新增 `npm run smoke:managed-objective-automation`,脚本启动 localhost OpenAI-compatible fixture,先把 Aster 全局 Provider 临时切到 fixture,再创建 enabled automation job 和未来一次性 schedule,避免创建时把 managed objective 同步成 paused,再手动 `run_automation_job_now`smoke 会轮询 runtime session 直到 fixture 收到 `/v1/chat/completions`,再导出 `agent_runtime_export_evidence_pack`,断言 automation run history、managed objective owner metadata evidence pack owner audit 都可回看。该 smoke 默认不读取 Deepseek 等真实 Provider 环境偏好,也不伪造未验证 workspace skill runtime enable;验证已覆盖 `env -u LIME_ALLOW_LIVE_PROVIDER_SMOKE -u LIME_REAL_API_TEST npm run smoke:managed-objective-automation -- --timeout-ms 180000 --no-write`fixture chat completion 请求数为 1,不触发真实 Provider
- 2026-05-26:第 9 节补齐默认离线 automation owner smoke 入口:新增 `npm run smoke:managed-objective-automation`,脚本启动 localhost OpenAI-compatible fixture,先把 Aster 全局 Provider 临时切到 fixture,再创建 enabled automation job 和未来一次性 schedule,避免创建时把 managed objective 同步成 paused。2026-06-08 自动化命令迁移后,该 smoke 已改为只通过 App Server `automationJob/create` / `automationJob/runNow` / `automationJob/runHistory` 验证;由于 App Server 自动化执行器尚未迁完,当前遇到 `automationJob/runNow` 执行器缺口时 fail closed,不能再回退旧 Tauri 自动化命令。后续完成 App Server 执行器迁移后,再恢复 owner run history、managed objective owner metadata evidence pack owner audit 的离线 fixture 通过证据
## 2. P0:文档与边界落盘
+1 -1
View File
@@ -64,7 +64,7 @@
24. `agent_runtime_export_evidence_pack` 返回值已透出 `completionAuditSummary`,前端 normalizer 和 Harness 面板会展示 evidence-based decision、owner / ToolCall / artifact 计数与 blocking reasons,让 completion audit 不再只停留在落盘文件。
25. Agent envelope presentation contract 已能消费 `completionAuditSummary`:只有 `completed` 且 automation owner / Workspace Skill ToolCall / artifact-or-timeline evidence 三项齐全时才进入 `evidence_ready``verifying` 或缺证据不会误报为可固化。
26. Workspace 已注册能力面板已预留 `completionAuditSummariesByDirectory` 注入边界:当某个 skill 的 audit summary 为 evidence-based `completed` 时,“转成 Agent 草案”入口会复用现有 Managed Job 草案创建链;未 completed 或缺证据时仍禁用。
27. Workspace 已注册能力面板已补“审计最近运行”入口:对匹配 Managed Job 复用 `get_automation_run_history` 找到最近 automation run 的 session,再调用 `agent_runtime_export_evidence_pack` 获取 `completionAuditSummary` 并回填对应 skill,不新增查询命令或平行 evidence。
27. Workspace 已注册能力面板已补“审计最近运行”入口:对匹配 Managed Job 复用 `getAutomationRunHistory()` 当前网关,经 App Server `automationJob/runHistory` 找到最近 automation run 的 session,再调用 evidence export 当前网关获取 `completionAuditSummary` 并回填对应 skill,不新增查询命令或平行 evidence。
28. Agent envelope 草案摘要已补齐 `Memory / Widget / Permission / Schedule / Evidence / Runbook` 六块组成:Memory 引用 verification report 与运行修正,Widget 展示 Managed Job 状态、产物、审计结论和下一步动作。
29. Agent card / sharing 已采用派生形态:`workspace-local/<skill-directory>` 由已注册 Skill、Managed Job 和 completion audit 派生;共享范围先限制在当前 workspace / team,不进入 public Marketplace,也不新增 Agent card 存储表。
30. Workspace/team sharing discovery 边界已显式展示:同 workspace 成员通过 registered skill discovery 发现 `.agents/skills/<skill-directory>`,复用同一 Managed Job / evidence 事实源,不新增分享命令或 Marketplace。
@@ -52,7 +52,7 @@
24. Evidence export 返回值、前端 API normalizer 与 Harness 面板已接入 `completionAuditSummary`:导出问题证据包后可直接在 UI 看到 evidence-based decision 与阻塞原因。
25. Agent envelope presentation contract 已接入 completion audit gate`completed` 且必要 evidence 齐全才进入 `evidence_ready`;非 completed audit 仍保持缺证据态,避免把 verifying 误报为可固化。
26. Workspace 已注册能力面板已补 evidence-gated Agent envelope 入口边界:按 skill directory 注入 completion audit summary 后,只有 completed + 必要 evidence 齐全才启用“转成 Agent 草案”,并复用现有 Managed Job 草案创建链。
27. Workspace 已注册能力面板已补最近运行审计入口:从匹配 Managed Job 调用既有 `get_automation_run_history` `agent_runtime_export_evidence_pack`把最近 automation run 的 evidence summary 回填到对应 skill 的 Agent envelope gate。
27. Workspace 已注册能力面板已补最近运行审计入口:从匹配 Managed Job 调用 `getAutomationRunHistory()` 当前网关与 evidence export 当前网关,底层经 App Server `automationJob/runHistory``evidence/export` 把最近 automation run 的 evidence summary 回填到对应 skill 的 Agent envelope gate。
28. Agent envelope 草案摘要已补完整组成:Runbook、Memory、Widget、Permission、Schedule、Evidence 均有 presentation 字段与 Workspace 展示,仍只作为产品组合面,不新增执行实体。
29. Agent card 与 sharing 已收敛为派生展示:Agent card id 使用 `workspace-local/<skill-directory>`,事实源来自 registered skill、Managed Job 和 completion auditsharing 先限定 workspace / team 范围,不做 public Marketplace。
30. Workspace/team sharing discovery 已明确:团队成员通过同一 workspace root 的 registered skill discovery 发现 `.agents/skills/<skill-directory>`,并复用同一 Managed Job / evidence 事实源。
+28 -105
View File
@@ -161,7 +161,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "app-server"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"anyhow",
"app-server-protocol",
@@ -169,6 +169,8 @@ dependencies = [
"aster-core",
"async-trait",
"chrono",
"chrono-tz",
"cron",
"lime-agent",
"lime-core",
"lime-knowledge",
@@ -186,7 +188,7 @@ dependencies = [
[[package]]
name = "app-server-agent-backend"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"anyhow",
"aster-core",
@@ -202,7 +204,7 @@ dependencies = [
[[package]]
name = "app-server-client"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"app-server-protocol",
"app-server-transport",
@@ -213,7 +215,7 @@ dependencies = [
[[package]]
name = "app-server-daemon"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"app-server-protocol",
"app-server-transport",
@@ -225,7 +227,7 @@ dependencies = [
[[package]]
name = "app-server-protocol"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"schemars",
"serde",
@@ -234,7 +236,7 @@ dependencies = [
[[package]]
name = "app-server-test-client"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"anyhow",
"app-server-client",
@@ -245,7 +247,7 @@ dependencies = [
[[package]]
name = "app-server-transport"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"app-server-protocol",
"serde_json",
@@ -1115,12 +1117,6 @@ dependencies = [
"cpufeatures",
]
[[package]]
name = "block"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -1568,35 +1564,6 @@ dependencies = [
"cc",
]
[[package]]
name = "cocoa"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c"
dependencies = [
"bitflags 2.11.0",
"block",
"cocoa-foundation",
"core-foundation 0.10.1",
"core-graphics 0.24.0",
"foreign-types 0.5.0",
"libc",
"objc",
]
[[package]]
name = "cocoa-foundation"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d"
dependencies = [
"bitflags 2.11.0",
"block",
"core-foundation 0.10.1",
"core-graphics-types 0.2.0",
"objc",
]
[[package]]
name = "colorchoice"
version = "1.0.4"
@@ -1763,20 +1730,7 @@ checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
dependencies = [
"bitflags 1.3.2",
"core-foundation 0.9.4",
"core-graphics-types 0.1.3",
"foreign-types 0.5.0",
"libc",
]
[[package]]
name = "core-graphics"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
dependencies = [
"bitflags 2.11.0",
"core-foundation 0.10.1",
"core-graphics-types 0.2.0",
"core-graphics-types",
"foreign-types 0.5.0",
"libc",
]
@@ -1792,17 +1746,6 @@ dependencies = [
"libc",
]
[[package]]
name = "core-graphics-types"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
dependencies = [
"bitflags 2.11.0",
"core-foundation 0.10.1",
"libc",
]
[[package]]
name = "coreaudio-rs"
version = "0.11.3"
@@ -2390,7 +2333,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0087a01fc8591217447d28005379fb5a183683cc83f0a4707af28cc6603f70fb"
dependencies = [
"core-graphics 0.23.2",
"core-graphics",
"foreign-types-shared 0.3.1",
"icrate",
"libc",
@@ -3963,7 +3906,7 @@ dependencies = [
[[package]]
name = "lime-agent"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"anyhow",
"aster-core",
@@ -3992,7 +3935,7 @@ dependencies = [
[[package]]
name = "lime-browser-runtime"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"chrono",
"futures",
@@ -4009,7 +3952,7 @@ dependencies = [
[[package]]
name = "lime-cli"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"clap",
"lime-core",
@@ -4021,7 +3964,7 @@ dependencies = [
[[package]]
name = "lime-config"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"async-trait",
"lime-core",
@@ -4037,7 +3980,7 @@ dependencies = [
[[package]]
name = "lime-core"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"aster-models",
"async-trait",
@@ -4093,7 +4036,7 @@ dependencies = [
[[package]]
name = "lime-gateway"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"aes",
"axum 0.7.9",
@@ -4123,7 +4066,7 @@ dependencies = [
[[package]]
name = "lime-infra"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"chrono",
"dashmap 5.5.3",
@@ -4143,7 +4086,7 @@ dependencies = [
[[package]]
name = "lime-knowledge"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"chrono",
"hex",
@@ -4156,7 +4099,7 @@ dependencies = [
[[package]]
name = "lime-mcp"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"async-trait",
"dirs 5.0.1",
@@ -4172,7 +4115,7 @@ dependencies = [
[[package]]
name = "lime-media-runtime"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"axum 0.7.9",
"base64 0.22.1",
@@ -4204,7 +4147,7 @@ dependencies = [
[[package]]
name = "lime-processor"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"async-trait",
"lime-core",
@@ -4223,7 +4166,7 @@ dependencies = [
[[package]]
name = "lime-providers"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"anyhow",
"async-stream",
@@ -4278,7 +4221,7 @@ dependencies = [
[[package]]
name = "lime-server"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"aster-core",
"async-stream",
@@ -4322,7 +4265,7 @@ dependencies = [
[[package]]
name = "lime-server-utils"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"axum 0.7.9",
"futures",
@@ -4337,14 +4280,13 @@ dependencies = [
[[package]]
name = "lime-services"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"anyhow",
"aster-core",
"async-trait",
"base64 0.22.1",
"chrono",
"cocoa",
"dashmap 5.5.3",
"dirs 5.0.1",
"futures",
@@ -4353,7 +4295,6 @@ dependencies = [
"lime-core",
"lime-providers",
"md5",
"objc",
"once_cell",
"parking_lot",
"proptest",
@@ -4382,7 +4323,7 @@ dependencies = [
[[package]]
name = "lime-skills"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"async-trait",
"dirs 5.0.1",
@@ -4400,7 +4341,7 @@ dependencies = [
[[package]]
name = "lime-websocket"
version = "1.60.0"
version = "1.61.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -4503,15 +4444,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]]
name = "markup5ever"
version = "0.12.1"
@@ -5028,15 +4960,6 @@ dependencies = [
"url",
]
[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]]
name = "objc-sys"
version = "0.3.5"
+1 -1
View File
@@ -11,7 +11,7 @@ exclude = [
resolver = "2"
[workspace.package]
version = "1.60.0"
version = "1.61.0"
edition = "2021"
authors = ["coso"]
repository = "https://github.com/aiclientproxy/lime"
+442
View File
@@ -14,7 +14,25 @@ pub use app_server_protocol::AgentSessionTurnStartParams;
pub use app_server_protocol::AppServerMethodKind;
pub use app_server_protocol::AppServerMethodSpec;
pub use app_server_protocol::ArtifactReadParams;
pub use app_server_protocol::AutomationJobCreateParams;
pub use app_server_protocol::AutomationJobDeleteResponse;
pub use app_server_protocol::AutomationJobHealthParams;
pub use app_server_protocol::AutomationJobHealthResponse;
pub use app_server_protocol::AutomationJobIdParams;
pub use app_server_protocol::AutomationJobListResponse;
pub use app_server_protocol::AutomationJobReadResponse;
pub use app_server_protocol::AutomationJobRunHistoryParams;
pub use app_server_protocol::AutomationJobRunHistoryResponse;
pub use app_server_protocol::AutomationJobRunNowResponse;
pub use app_server_protocol::AutomationJobUpdateParams;
pub use app_server_protocol::AutomationJobWriteResponse;
pub use app_server_protocol::AutomationScheduleParams;
pub use app_server_protocol::AutomationSchedulePreviewResponse;
pub use app_server_protocol::AutomationScheduleValidateResponse;
pub use app_server_protocol::AutomationSchedulerConfigReadResponse;
pub use app_server_protocol::AutomationSchedulerConfigUpdateParams;
pub use app_server_protocol::AutomationSchedulerConfigUpdateResponse;
pub use app_server_protocol::AutomationSchedulerStatusResponse;
pub use app_server_protocol::CapabilityListParams;
pub use app_server_protocol::EvidenceExportParams;
pub use app_server_protocol::FileSystemDirectoryListing;
@@ -30,6 +48,11 @@ use app_server_protocol::JsonRpcRequest;
use app_server_protocol::JsonRpcResponse;
pub use app_server_protocol::KnowledgeListPacksParams;
pub use app_server_protocol::KnowledgeListPacksResponse;
pub use app_server_protocol::McpPromptListResponse;
pub use app_server_protocol::McpResourceListResponse;
pub use app_server_protocol::McpServerListResponse;
pub use app_server_protocol::McpServerStatusListResponse;
pub use app_server_protocol::McpToolListResponse;
pub use app_server_protocol::ModelListParams;
pub use app_server_protocol::ModelProviderAliasReadParams;
pub use app_server_protocol::ProjectMemoryReadParams;
@@ -62,7 +85,19 @@ pub use app_server_protocol::METHOD_AGENT_SESSION_START;
pub use app_server_protocol::METHOD_AGENT_SESSION_TURN_CANCEL;
pub use app_server_protocol::METHOD_AGENT_SESSION_TURN_START;
pub use app_server_protocol::METHOD_ARTIFACT_READ;
pub use app_server_protocol::METHOD_AUTOMATION_JOB_CREATE;
pub use app_server_protocol::METHOD_AUTOMATION_JOB_DELETE;
pub use app_server_protocol::METHOD_AUTOMATION_JOB_HEALTH;
pub use app_server_protocol::METHOD_AUTOMATION_JOB_LIST;
pub use app_server_protocol::METHOD_AUTOMATION_JOB_READ;
pub use app_server_protocol::METHOD_AUTOMATION_JOB_RUN_HISTORY;
pub use app_server_protocol::METHOD_AUTOMATION_JOB_RUN_NOW;
pub use app_server_protocol::METHOD_AUTOMATION_JOB_UPDATE;
pub use app_server_protocol::METHOD_AUTOMATION_SCHEDULER_CONFIG_READ;
pub use app_server_protocol::METHOD_AUTOMATION_SCHEDULER_CONFIG_UPDATE;
pub use app_server_protocol::METHOD_AUTOMATION_SCHEDULER_STATUS;
pub use app_server_protocol::METHOD_AUTOMATION_SCHEDULE_PREVIEW;
pub use app_server_protocol::METHOD_AUTOMATION_SCHEDULE_VALIDATE;
pub use app_server_protocol::METHOD_CAPABILITY_LIST;
pub use app_server_protocol::METHOD_EVIDENCE_EXPORT;
pub use app_server_protocol::METHOD_FILE_SYSTEM_LIST_DIRECTORY;
@@ -70,6 +105,11 @@ pub use app_server_protocol::METHOD_FILE_SYSTEM_READ_FILE_PREVIEW;
pub use app_server_protocol::METHOD_INITIALIZE;
pub use app_server_protocol::METHOD_INITIALIZED;
pub use app_server_protocol::METHOD_KNOWLEDGE_PACK_LIST;
pub use app_server_protocol::METHOD_MCP_PROMPT_LIST;
pub use app_server_protocol::METHOD_MCP_RESOURCE_LIST;
pub use app_server_protocol::METHOD_MCP_SERVER_LIST;
pub use app_server_protocol::METHOD_MCP_SERVER_STATUS_LIST;
pub use app_server_protocol::METHOD_MCP_TOOL_LIST;
pub use app_server_protocol::METHOD_MODEL_LIST;
pub use app_server_protocol::METHOD_MODEL_PREFERENCES_LIST;
pub use app_server_protocol::METHOD_MODEL_PROVIDER_ALIAS_LIST;
@@ -301,6 +341,104 @@ impl AppServerClient {
self.typed_request(typed::list_automation_jobs())
}
pub fn read_automation_scheduler_config(&mut self) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::read_automation_scheduler_config())
}
pub fn update_automation_scheduler_config(
&mut self,
params: AutomationSchedulerConfigUpdateParams,
) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::update_automation_scheduler_config(params))
}
pub fn read_automation_scheduler_status(&mut self) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::read_automation_scheduler_status())
}
pub fn read_automation_job(
&mut self,
params: AutomationJobIdParams,
) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::read_automation_job(params))
}
pub fn create_automation_job(
&mut self,
params: AutomationJobCreateParams,
) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::create_automation_job(params))
}
pub fn update_automation_job(
&mut self,
params: AutomationJobUpdateParams,
) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::update_automation_job(params))
}
pub fn delete_automation_job(
&mut self,
params: AutomationJobIdParams,
) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::delete_automation_job(params))
}
pub fn run_automation_job_now(
&mut self,
params: AutomationJobIdParams,
) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::run_automation_job_now(params))
}
pub fn read_automation_health(
&mut self,
params: AutomationJobHealthParams,
) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::read_automation_health(params))
}
pub fn read_automation_run_history(
&mut self,
params: AutomationJobRunHistoryParams,
) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::read_automation_run_history(params))
}
pub fn preview_automation_schedule(
&mut self,
params: AutomationScheduleParams,
) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::preview_automation_schedule(params))
}
pub fn validate_automation_schedule(
&mut self,
params: AutomationScheduleParams,
) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::validate_automation_schedule(params))
}
pub fn list_mcp_servers(&mut self) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::list_mcp_servers())
}
pub fn list_mcp_servers_with_status(&mut self) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::list_mcp_servers_with_status())
}
pub fn list_mcp_tools(&mut self) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::list_mcp_tools())
}
pub fn list_mcp_prompts(&mut self) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::list_mcp_prompts())
}
pub fn list_mcp_resources(&mut self) -> Result<JsonRpcRequest, ClientError> {
self.typed_request(typed::list_mcp_resources())
}
pub fn read_project_memory(
&mut self,
params: ProjectMemoryReadParams,
@@ -535,6 +673,97 @@ pub mod typed {
TypedRequest::new(METHOD_AUTOMATION_JOB_LIST, serde_json::json!({}))
}
pub fn read_automation_scheduler_config() -> TypedRequest<serde_json::Value> {
TypedRequest::new(
METHOD_AUTOMATION_SCHEDULER_CONFIG_READ,
serde_json::json!({}),
)
}
pub fn update_automation_scheduler_config(
params: AutomationSchedulerConfigUpdateParams,
) -> TypedRequest<AutomationSchedulerConfigUpdateParams> {
TypedRequest::new(METHOD_AUTOMATION_SCHEDULER_CONFIG_UPDATE, params)
}
pub fn read_automation_scheduler_status() -> TypedRequest<serde_json::Value> {
TypedRequest::new(METHOD_AUTOMATION_SCHEDULER_STATUS, serde_json::json!({}))
}
pub fn read_automation_job(
params: AutomationJobIdParams,
) -> TypedRequest<AutomationJobIdParams> {
TypedRequest::new(METHOD_AUTOMATION_JOB_READ, params)
}
pub fn create_automation_job(
params: AutomationJobCreateParams,
) -> TypedRequest<AutomationJobCreateParams> {
TypedRequest::new(METHOD_AUTOMATION_JOB_CREATE, params)
}
pub fn update_automation_job(
params: AutomationJobUpdateParams,
) -> TypedRequest<AutomationJobUpdateParams> {
TypedRequest::new(METHOD_AUTOMATION_JOB_UPDATE, params)
}
pub fn delete_automation_job(
params: AutomationJobIdParams,
) -> TypedRequest<AutomationJobIdParams> {
TypedRequest::new(METHOD_AUTOMATION_JOB_DELETE, params)
}
pub fn run_automation_job_now(
params: AutomationJobIdParams,
) -> TypedRequest<AutomationJobIdParams> {
TypedRequest::new(METHOD_AUTOMATION_JOB_RUN_NOW, params)
}
pub fn read_automation_health(
params: AutomationJobHealthParams,
) -> TypedRequest<AutomationJobHealthParams> {
TypedRequest::new(METHOD_AUTOMATION_JOB_HEALTH, params)
}
pub fn read_automation_run_history(
params: AutomationJobRunHistoryParams,
) -> TypedRequest<AutomationJobRunHistoryParams> {
TypedRequest::new(METHOD_AUTOMATION_JOB_RUN_HISTORY, params)
}
pub fn preview_automation_schedule(
params: AutomationScheduleParams,
) -> TypedRequest<AutomationScheduleParams> {
TypedRequest::new(METHOD_AUTOMATION_SCHEDULE_PREVIEW, params)
}
pub fn validate_automation_schedule(
params: AutomationScheduleParams,
) -> TypedRequest<AutomationScheduleParams> {
TypedRequest::new(METHOD_AUTOMATION_SCHEDULE_VALIDATE, params)
}
pub fn list_mcp_servers() -> TypedRequest<serde_json::Value> {
TypedRequest::new(METHOD_MCP_SERVER_LIST, serde_json::json!({}))
}
pub fn list_mcp_servers_with_status() -> TypedRequest<serde_json::Value> {
TypedRequest::new(METHOD_MCP_SERVER_STATUS_LIST, serde_json::json!({}))
}
pub fn list_mcp_tools() -> TypedRequest<serde_json::Value> {
TypedRequest::new(METHOD_MCP_TOOL_LIST, serde_json::json!({}))
}
pub fn list_mcp_prompts() -> TypedRequest<serde_json::Value> {
TypedRequest::new(METHOD_MCP_PROMPT_LIST, serde_json::json!({}))
}
pub fn list_mcp_resources() -> TypedRequest<serde_json::Value> {
TypedRequest::new(METHOD_MCP_RESOURCE_LIST, serde_json::json!({}))
}
pub fn read_project_memory(
params: ProjectMemoryReadParams,
) -> TypedRequest<ProjectMemoryReadParams> {
@@ -675,6 +904,30 @@ mod tests {
assert_eq!(request.params.expect("params"), json!({}));
}
#[test]
fn mcp_list_helpers_use_current_methods_and_empty_params() {
let mut client = AppServerClient::new();
let servers = client.list_mcp_servers().expect("servers");
let status = client
.list_mcp_servers_with_status()
.expect("server status");
let tools = client.list_mcp_tools().expect("tools");
let prompts = client.list_mcp_prompts().expect("prompts");
let resources = client.list_mcp_resources().expect("resources");
assert_eq!(servers.method, METHOD_MCP_SERVER_LIST);
assert_eq!(servers.params.expect("params"), json!({}));
assert_eq!(status.method, METHOD_MCP_SERVER_STATUS_LIST);
assert_eq!(status.params.expect("params"), json!({}));
assert_eq!(tools.method, METHOD_MCP_TOOL_LIST);
assert_eq!(tools.params.expect("params"), json!({}));
assert_eq!(prompts.method, METHOD_MCP_PROMPT_LIST);
assert_eq!(prompts.params.expect("params"), json!({}));
assert_eq!(resources.method, METHOD_MCP_RESOURCE_LIST);
assert_eq!(resources.params.expect("params"), json!({}));
}
#[test]
fn list_capabilities_preserves_optional_scope_params() {
let mut client = AppServerClient::new();
@@ -884,7 +1137,91 @@ mod tests {
include_archived: true,
})
.expect("knowledge packs");
let scheduler_config = client
.read_automation_scheduler_config()
.expect("automation scheduler config");
let scheduler_config_update = client
.update_automation_scheduler_config(AutomationSchedulerConfigUpdateParams {
config: json!({
"enabled": true,
"poll_interval_secs": 60,
"enable_history": true,
}),
})
.expect("automation scheduler config update");
let scheduler_status = client
.read_automation_scheduler_status()
.expect("automation scheduler status");
let jobs = client.list_automation_jobs().expect("automation jobs");
let job = client
.read_automation_job(AutomationJobIdParams {
id: "job-1".to_string(),
})
.expect("automation job");
let created_job = client
.create_automation_job(AutomationJobCreateParams {
request: json!({
"name": "每日简报",
"workspace_id": "workspace-main",
"schedule": {
"kind": "every",
"every_secs": 3600,
},
"payload": {
"kind": "agent_turn",
"prompt": "总结今天重点",
"web_search": false,
},
}),
})
.expect("automation job create");
let updated_job = client
.update_automation_job(AutomationJobUpdateParams {
id: "job-1".to_string(),
request: json!({
"enabled": false,
}),
})
.expect("automation job update");
let deleted_job = client
.delete_automation_job(AutomationJobIdParams {
id: "job-1".to_string(),
})
.expect("automation job delete");
let run_now = client
.run_automation_job_now(AutomationJobIdParams {
id: "job-1".to_string(),
})
.expect("automation job run now");
let health = client
.read_automation_health(AutomationJobHealthParams {
query: Some(json!({
"top_limit": 3,
})),
})
.expect("automation health");
let history = client
.read_automation_run_history(AutomationJobRunHistoryParams {
id: "job-1".to_string(),
limit: Some(10),
})
.expect("automation run history");
let preview = client
.preview_automation_schedule(AutomationScheduleParams {
schedule: json!({
"kind": "every",
"every_secs": 3600,
}),
})
.expect("automation schedule preview");
let validate = client
.validate_automation_schedule(AutomationScheduleParams {
schedule: json!({
"kind": "every",
"every_secs": 3600,
}),
})
.expect("automation schedule validate");
let memory = client
.read_project_memory(ProjectMemoryReadParams {
project_id: "workspace-main".to_string(),
@@ -901,8 +1238,88 @@ mod tests {
"includeArchived": true,
})
);
assert_eq!(
scheduler_config.method,
METHOD_AUTOMATION_SCHEDULER_CONFIG_READ
);
assert_eq!(
scheduler_config_update.method,
METHOD_AUTOMATION_SCHEDULER_CONFIG_UPDATE
);
assert_eq!(
scheduler_config_update.params.expect("params"),
json!({
"config": {
"enabled": true,
"poll_interval_secs": 60,
"enable_history": true,
},
})
);
assert_eq!(scheduler_status.method, METHOD_AUTOMATION_SCHEDULER_STATUS);
assert_eq!(jobs.method, METHOD_AUTOMATION_JOB_LIST);
assert_eq!(jobs.params.expect("params"), json!({}));
assert_eq!(job.method, METHOD_AUTOMATION_JOB_READ);
assert_eq!(job.params.expect("params"), json!({ "id": "job-1" }));
assert_eq!(created_job.method, METHOD_AUTOMATION_JOB_CREATE);
assert_eq!(
created_job.params.expect("params"),
json!({
"request": {
"name": "每日简报",
"workspace_id": "workspace-main",
"schedule": {
"kind": "every",
"every_secs": 3600,
},
"payload": {
"kind": "agent_turn",
"prompt": "总结今天重点",
"web_search": false,
},
},
})
);
assert_eq!(updated_job.method, METHOD_AUTOMATION_JOB_UPDATE);
assert_eq!(
updated_job.params.expect("params"),
json!({
"id": "job-1",
"request": {
"enabled": false,
},
})
);
assert_eq!(deleted_job.method, METHOD_AUTOMATION_JOB_DELETE);
assert_eq!(run_now.method, METHOD_AUTOMATION_JOB_RUN_NOW);
assert_eq!(health.method, METHOD_AUTOMATION_JOB_HEALTH);
assert_eq!(
health.params.expect("params"),
json!({
"query": {
"top_limit": 3,
},
})
);
assert_eq!(history.method, METHOD_AUTOMATION_JOB_RUN_HISTORY);
assert_eq!(
history.params.expect("params"),
json!({
"id": "job-1",
"limit": 10,
})
);
assert_eq!(preview.method, METHOD_AUTOMATION_SCHEDULE_PREVIEW);
assert_eq!(
preview.params.expect("params"),
json!({
"schedule": {
"kind": "every",
"every_secs": 3600,
},
})
);
assert_eq!(validate.method, METHOD_AUTOMATION_SCHEDULE_VALIDATE);
assert_eq!(memory.method, METHOD_PROJECT_MEMORY_READ);
assert_eq!(
memory.params.expect("params"),
@@ -1143,7 +1560,19 @@ mod tests {
assert!(methods.contains(&METHOD_WORKSPACE_SKILL_BINDINGS_LIST));
assert!(methods.contains(&METHOD_AGENT_APP_INSTALLED_LIST));
assert!(methods.contains(&METHOD_KNOWLEDGE_PACK_LIST));
assert!(methods.contains(&METHOD_AUTOMATION_SCHEDULER_CONFIG_READ));
assert!(methods.contains(&METHOD_AUTOMATION_SCHEDULER_CONFIG_UPDATE));
assert!(methods.contains(&METHOD_AUTOMATION_SCHEDULER_STATUS));
assert!(methods.contains(&METHOD_AUTOMATION_JOB_LIST));
assert!(methods.contains(&METHOD_AUTOMATION_JOB_READ));
assert!(methods.contains(&METHOD_AUTOMATION_JOB_CREATE));
assert!(methods.contains(&METHOD_AUTOMATION_JOB_UPDATE));
assert!(methods.contains(&METHOD_AUTOMATION_JOB_DELETE));
assert!(methods.contains(&METHOD_AUTOMATION_JOB_RUN_NOW));
assert!(methods.contains(&METHOD_AUTOMATION_JOB_HEALTH));
assert!(methods.contains(&METHOD_AUTOMATION_JOB_RUN_HISTORY));
assert!(methods.contains(&METHOD_AUTOMATION_SCHEDULE_PREVIEW));
assert!(methods.contains(&METHOD_AUTOMATION_SCHEDULE_VALIDATE));
assert!(methods.contains(&METHOD_PROJECT_MEMORY_READ));
assert!(methods.contains(&METHOD_AGENT_SESSION_EVENT));
assert!(is_app_server_request_method(METHOD_CAPABILITY_LIST));
@@ -1161,7 +1590,20 @@ mod tests {
METHOD_AGENT_APP_INSTALLED_LIST
));
assert!(is_app_server_request_method(METHOD_KNOWLEDGE_PACK_LIST));
assert!(is_app_server_request_method(
METHOD_AUTOMATION_SCHEDULER_CONFIG_READ
));
assert!(is_app_server_request_method(
METHOD_AUTOMATION_SCHEDULER_CONFIG_UPDATE
));
assert!(is_app_server_request_method(
METHOD_AUTOMATION_SCHEDULER_STATUS
));
assert!(is_app_server_request_method(METHOD_AUTOMATION_JOB_LIST));
assert!(is_app_server_request_method(METHOD_AUTOMATION_JOB_CREATE));
assert!(is_app_server_request_method(
METHOD_AUTOMATION_SCHEDULE_VALIDATE
));
assert!(is_app_server_request_method(METHOD_PROJECT_MEMORY_READ));
assert!(is_app_server_request_method(
METHOD_AGENT_SESSION_TURN_CANCEL
@@ -1704,6 +1704,62 @@
"title": "ArtifactSummary",
"type": "object"
},
"AutomationJobCreateParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"request": true
},
"required": [
"request"
],
"title": "AutomationJobCreateParams",
"type": "object"
},
"AutomationJobDeleteResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"deleted": {
"type": "boolean"
}
},
"required": [
"deleted"
],
"title": "AutomationJobDeleteResponse",
"type": "object"
},
"AutomationJobHealthParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"query": true
},
"title": "AutomationJobHealthParams",
"type": "object"
},
"AutomationJobHealthResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"health": true
},
"required": [
"health"
],
"title": "AutomationJobHealthResponse",
"type": "object"
},
"AutomationJobIdParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"title": "AutomationJobIdParams",
"type": "object"
},
"AutomationJobListResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
@@ -1716,6 +1772,171 @@
"title": "AutomationJobListResponse",
"type": "object"
},
"AutomationJobReadResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"job": true
},
"title": "AutomationJobReadResponse",
"type": "object"
},
"AutomationJobRunHistoryParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"id": {
"type": "string"
},
"limit": {
"format": "uint",
"minimum": 0,
"type": [
"integer",
"null"
]
}
},
"required": [
"id"
],
"title": "AutomationJobRunHistoryParams",
"type": "object"
},
"AutomationJobRunHistoryResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"runs": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "AutomationJobRunHistoryResponse",
"type": "object"
},
"AutomationJobRunNowResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"result": true
},
"required": [
"result"
],
"title": "AutomationJobRunNowResponse",
"type": "object"
},
"AutomationJobUpdateParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"id": {
"type": "string"
},
"request": true
},
"required": [
"id",
"request"
],
"title": "AutomationJobUpdateParams",
"type": "object"
},
"AutomationJobWriteResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"job": true
},
"required": [
"job"
],
"title": "AutomationJobWriteResponse",
"type": "object"
},
"AutomationScheduleParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"schedule": true
},
"required": [
"schedule"
],
"title": "AutomationScheduleParams",
"type": "object"
},
"AutomationSchedulePreviewResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"nextRunAt": {
"type": [
"null",
"string"
]
}
},
"title": "AutomationSchedulePreviewResponse",
"type": "object"
},
"AutomationScheduleValidateResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": [
"null",
"string"
]
},
"valid": {
"type": "boolean"
}
},
"required": [
"valid"
],
"title": "AutomationScheduleValidateResponse",
"type": "object"
},
"AutomationSchedulerConfigReadResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"config": true
},
"required": [
"config"
],
"title": "AutomationSchedulerConfigReadResponse",
"type": "object"
},
"AutomationSchedulerConfigUpdateParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"config": true
},
"required": [
"config"
],
"title": "AutomationSchedulerConfigUpdateParams",
"type": "object"
},
"AutomationSchedulerConfigUpdateResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"config": true
},
"required": [
"config"
],
"title": "AutomationSchedulerConfigUpdateResponse",
"type": "object"
},
"AutomationSchedulerStatusResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"status": true
},
"required": [
"status"
],
"title": "AutomationSchedulerStatusResponse",
"type": "object"
},
"BusinessObjectRef": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
@@ -3448,6 +3669,66 @@
"title": "KnowledgeListPacksResponse",
"type": "object"
},
"McpPromptListResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"prompts": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "McpPromptListResponse",
"type": "object"
},
"McpResourceListResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"resources": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "McpResourceListResponse",
"type": "object"
},
"McpServerListResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"servers": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "McpServerListResponse",
"type": "object"
},
"McpServerStatusListResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"servers": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "McpServerStatusListResponse",
"type": "object"
},
"McpToolListResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"tools": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "McpToolListResponse",
"type": "object"
},
"ModelListParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
@@ -3536,6 +3817,321 @@
"title": "ModelProviderCatalogListResponse",
"type": "object"
},
"ModelProviderConfigExportParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"includeKeys": {
"default": null,
"type": [
"boolean",
"null"
]
}
},
"title": "ModelProviderConfigExportParams",
"type": "object"
},
"ModelProviderConfigExportResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"configJson": {
"type": "string"
}
},
"required": [
"configJson"
],
"title": "ModelProviderConfigExportResponse",
"type": "object"
},
"ModelProviderConfigImportParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"configJson": {
"type": "string"
}
},
"required": [
"configJson"
],
"title": "ModelProviderConfigImportParams",
"type": "object"
},
"ModelProviderConfigImportResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"errors": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"importedApiKeys": {
"format": "uint",
"minimum": 0,
"type": "integer"
},
"importedProviders": {
"format": "uint",
"minimum": 0,
"type": "integer"
},
"skippedProviders": {
"format": "uint",
"minimum": 0,
"type": "integer"
},
"success": {
"type": "boolean"
}
},
"required": [
"importedApiKeys",
"importedProviders",
"skippedProviders",
"success"
],
"title": "ModelProviderConfigImportResponse",
"type": "object"
},
"ModelProviderCreateParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"provider": true
},
"required": [
"provider"
],
"title": "ModelProviderCreateParams",
"type": "object"
},
"ModelProviderDeleteParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"providerId": {
"type": "string"
}
},
"required": [
"providerId"
],
"title": "ModelProviderDeleteParams",
"type": "object"
},
"ModelProviderDeleteResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"deleted": {
"type": "boolean"
}
},
"required": [
"deleted"
],
"title": "ModelProviderDeleteResponse",
"type": "object"
},
"ModelProviderFetchModelsParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"providerId": {
"type": "string"
}
},
"required": [
"providerId"
],
"title": "ModelProviderFetchModelsParams",
"type": "object"
},
"ModelProviderFetchModelsResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"diagnosticHint": {
"type": [
"null",
"string"
]
},
"error": {
"type": [
"null",
"string"
]
},
"errorKind": {
"type": [
"null",
"string"
]
},
"fromCache": {
"type": "boolean"
},
"models": {
"default": [],
"items": true,
"type": "array"
},
"requestUrl": {
"type": [
"null",
"string"
]
},
"shouldPromptError": {
"type": "boolean"
},
"source": {
"type": "string"
}
},
"required": [
"fromCache",
"shouldPromptError",
"source"
],
"title": "ModelProviderFetchModelsResponse",
"type": "object"
},
"ModelProviderKeyCreateParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"alias": {
"type": [
"null",
"string"
]
},
"apiKey": {
"type": "string"
},
"providerId": {
"type": "string"
},
"replaceExisting": {
"default": null,
"type": [
"boolean",
"null"
]
}
},
"required": [
"apiKey",
"providerId"
],
"title": "ModelProviderKeyCreateParams",
"type": "object"
},
"ModelProviderKeyDeleteParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"keyId": {
"type": "string"
}
},
"required": [
"keyId"
],
"title": "ModelProviderKeyDeleteParams",
"type": "object"
},
"ModelProviderKeyDeleteResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"deleted": {
"type": "boolean"
}
},
"required": [
"deleted"
],
"title": "ModelProviderKeyDeleteResponse",
"type": "object"
},
"ModelProviderKeyEventParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"keyId": {
"type": "string"
}
},
"required": [
"keyId"
],
"title": "ModelProviderKeyEventParams",
"type": "object"
},
"ModelProviderKeyNextParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"providerId": {
"type": "string"
}
},
"required": [
"providerId"
],
"title": "ModelProviderKeyNextParams",
"type": "object"
},
"ModelProviderKeyNextResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"apiKey": {
"type": [
"null",
"string"
]
},
"keyId": {
"type": [
"null",
"string"
]
}
},
"title": "ModelProviderKeyNextResponse",
"type": "object"
},
"ModelProviderKeyUpdateParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"alias": {
"type": [
"null",
"string"
]
},
"enabled": {
"type": [
"boolean",
"null"
]
},
"keyId": {
"type": "string"
}
},
"required": [
"keyId"
],
"title": "ModelProviderKeyUpdateParams",
"type": "object"
},
"ModelProviderKeyWriteResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"key": true
},
"required": [
"key"
],
"title": "ModelProviderKeyWriteResponse",
"type": "object"
},
"ModelProviderListResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
@@ -3548,6 +4144,268 @@
"title": "ModelProviderListResponse",
"type": "object"
},
"ModelProviderMutationResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ModelProviderMutationResponse",
"type": "object"
},
"ModelProviderReadParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"providerId": {
"type": "string"
}
},
"required": [
"providerId"
],
"title": "ModelProviderReadParams",
"type": "object"
},
"ModelProviderReadResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"provider": true
},
"title": "ModelProviderReadResponse",
"type": "object"
},
"ModelProviderSortOrderItem": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"providerId": {
"type": "string"
},
"sortOrder": {
"format": "int32",
"type": "integer"
}
},
"required": [
"providerId",
"sortOrder"
],
"title": "ModelProviderSortOrderItem",
"type": "object"
},
"ModelProviderSortOrdersUpdateParams": {
"$defs": {
"ModelProviderSortOrderItem": {
"properties": {
"providerId": {
"type": "string"
},
"sortOrder": {
"format": "int32",
"type": "integer"
}
},
"required": [
"providerId",
"sortOrder"
],
"type": "object"
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"sortOrders": {
"default": [],
"items": {
"$ref": "#/$defs/ModelProviderSortOrderItem"
},
"type": "array"
}
},
"title": "ModelProviderSortOrdersUpdateParams",
"type": "object"
},
"ModelProviderTestChatParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"modelName": {
"type": [
"null",
"string"
]
},
"prompt": {
"type": "string"
},
"providerId": {
"type": "string"
}
},
"required": [
"prompt",
"providerId"
],
"title": "ModelProviderTestChatParams",
"type": "object"
},
"ModelProviderTestChatResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"content": {
"type": [
"null",
"string"
]
},
"error": {
"type": [
"null",
"string"
]
},
"latencyMs": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"raw": {
"type": [
"null",
"string"
]
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"title": "ModelProviderTestChatResponse",
"type": "object"
},
"ModelProviderTestConnectionParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"modelName": {
"type": [
"null",
"string"
]
},
"providerId": {
"type": "string"
}
},
"required": [
"providerId"
],
"title": "ModelProviderTestConnectionParams",
"type": "object"
},
"ModelProviderTestConnectionResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": [
"null",
"string"
]
},
"latencyMs": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"models": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"title": "ModelProviderTestConnectionResponse",
"type": "object"
},
"ModelProviderUiStateReadParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"key": {
"type": "string"
}
},
"required": [
"key"
],
"title": "ModelProviderUiStateReadParams",
"type": "object"
},
"ModelProviderUiStateReadResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"value": {
"type": [
"null",
"string"
]
}
},
"title": "ModelProviderUiStateReadResponse",
"type": "object"
},
"ModelProviderUiStateWriteParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"key",
"value"
],
"title": "ModelProviderUiStateWriteParams",
"type": "object"
},
"ModelProviderUpdateParams": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"patch": true,
"providerId": {
"type": "string"
}
},
"required": [
"patch",
"providerId"
],
"title": "ModelProviderUpdateParams",
"type": "object"
},
"ModelProviderWriteResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"provider": true
},
"required": [
"provider"
],
"title": "ModelProviderWriteResponse",
"type": "object"
},
"ModelSyncStateReadResponse": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
@@ -4056,7 +4914,19 @@
"agentSession/turn/start",
"agentSession/update",
"artifact/read",
"automationJob/create",
"automationJob/delete",
"automationJob/health",
"automationJob/list",
"automationJob/read",
"automationJob/runHistory",
"automationJob/runNow",
"automationJob/update",
"automationSchedule/preview",
"automationSchedule/validate",
"automationScheduler/config/read",
"automationScheduler/config/update",
"automationScheduler/status",
"capability/list",
"connectCallback/send",
"connectDeepLink/resolve",
@@ -4067,12 +4937,35 @@
"fileSystem/readFilePreview",
"initialize",
"knowledgePack/list",
"mcpPrompt/list",
"mcpResource/list",
"mcpServer/list",
"mcpServerStatus/list",
"mcpTool/list",
"model/list",
"modelPreferences/list",
"modelProvider/catalog/list",
"modelProvider/create",
"modelProvider/delete",
"modelProvider/fetchModels",
"modelProvider/list",
"modelProvider/read",
"modelProvider/sortOrders/update",
"modelProvider/testChat",
"modelProvider/testConnection",
"modelProvider/update",
"modelProviderAlias/list",
"modelProviderAlias/read",
"modelProviderConfig/export",
"modelProviderConfig/import",
"modelProviderKey/create",
"modelProviderKey/delete",
"modelProviderKey/error/record",
"modelProviderKey/next",
"modelProviderKey/update",
"modelProviderKey/usage/record",
"modelProviderUiState/read",
"modelProviderUiState/write",
"modelSyncState/read",
"projectMemory/read",
"skill/list",
@@ -62,10 +62,58 @@
"kind": "request",
"method": "artifact/read"
},
{
"kind": "request",
"method": "automationJob/create"
},
{
"kind": "request",
"method": "automationJob/delete"
},
{
"kind": "request",
"method": "automationJob/health"
},
{
"kind": "request",
"method": "automationJob/list"
},
{
"kind": "request",
"method": "automationJob/read"
},
{
"kind": "request",
"method": "automationJob/runHistory"
},
{
"kind": "request",
"method": "automationJob/runNow"
},
{
"kind": "request",
"method": "automationJob/update"
},
{
"kind": "request",
"method": "automationSchedule/preview"
},
{
"kind": "request",
"method": "automationSchedule/validate"
},
{
"kind": "request",
"method": "automationScheduler/config/read"
},
{
"kind": "request",
"method": "automationScheduler/config/update"
},
{
"kind": "request",
"method": "automationScheduler/status"
},
{
"kind": "request",
"method": "capability/list"
@@ -110,6 +158,26 @@
"kind": "request",
"method": "knowledgePack/list"
},
{
"kind": "request",
"method": "mcpPrompt/list"
},
{
"kind": "request",
"method": "mcpResource/list"
},
{
"kind": "request",
"method": "mcpServer/list"
},
{
"kind": "request",
"method": "mcpServerStatus/list"
},
{
"kind": "request",
"method": "mcpTool/list"
},
{
"kind": "request",
"method": "model/list"
@@ -122,10 +190,42 @@
"kind": "request",
"method": "modelProvider/catalog/list"
},
{
"kind": "request",
"method": "modelProvider/create"
},
{
"kind": "request",
"method": "modelProvider/delete"
},
{
"kind": "request",
"method": "modelProvider/fetchModels"
},
{
"kind": "request",
"method": "modelProvider/list"
},
{
"kind": "request",
"method": "modelProvider/read"
},
{
"kind": "request",
"method": "modelProvider/sortOrders/update"
},
{
"kind": "request",
"method": "modelProvider/testChat"
},
{
"kind": "request",
"method": "modelProvider/testConnection"
},
{
"kind": "request",
"method": "modelProvider/update"
},
{
"kind": "request",
"method": "modelProviderAlias/list"
@@ -134,6 +234,46 @@
"kind": "request",
"method": "modelProviderAlias/read"
},
{
"kind": "request",
"method": "modelProviderConfig/export"
},
{
"kind": "request",
"method": "modelProviderConfig/import"
},
{
"kind": "request",
"method": "modelProviderKey/create"
},
{
"kind": "request",
"method": "modelProviderKey/delete"
},
{
"kind": "request",
"method": "modelProviderKey/error/record"
},
{
"kind": "request",
"method": "modelProviderKey/next"
},
{
"kind": "request",
"method": "modelProviderKey/update"
},
{
"kind": "request",
"method": "modelProviderKey/usage/record"
},
{
"kind": "request",
"method": "modelProviderUiState/read"
},
{
"kind": "request",
"method": "modelProviderUiState/write"
},
{
"kind": "request",
"method": "modelSyncState/read"
@@ -237,7 +377,25 @@
"ArtifactReadParams",
"ArtifactReadResponse",
"ArtifactSummary",
"AutomationJobCreateParams",
"AutomationJobDeleteResponse",
"AutomationJobHealthParams",
"AutomationJobHealthResponse",
"AutomationJobIdParams",
"AutomationJobListResponse",
"AutomationJobReadResponse",
"AutomationJobRunHistoryParams",
"AutomationJobRunHistoryResponse",
"AutomationJobRunNowResponse",
"AutomationJobUpdateParams",
"AutomationJobWriteResponse",
"AutomationScheduleParams",
"AutomationSchedulePreviewResponse",
"AutomationScheduleValidateResponse",
"AutomationSchedulerConfigReadResponse",
"AutomationSchedulerConfigUpdateParams",
"AutomationSchedulerConfigUpdateResponse",
"AutomationSchedulerStatusResponse",
"BusinessObjectRef",
"CapabilityDescriptor",
"CapabilityListParams",
@@ -267,6 +425,11 @@
"InitializeResponse",
"KnowledgeListPacksParams",
"KnowledgeListPacksResponse",
"McpPromptListResponse",
"McpResourceListResponse",
"McpServerListResponse",
"McpServerStatusListResponse",
"McpToolListResponse",
"ModelListParams",
"ModelListResponse",
"ModelPreferencesListResponse",
@@ -274,7 +437,38 @@
"ModelProviderAliasReadParams",
"ModelProviderAliasReadResponse",
"ModelProviderCatalogListResponse",
"ModelProviderConfigExportParams",
"ModelProviderConfigExportResponse",
"ModelProviderConfigImportParams",
"ModelProviderConfigImportResponse",
"ModelProviderCreateParams",
"ModelProviderDeleteParams",
"ModelProviderDeleteResponse",
"ModelProviderFetchModelsParams",
"ModelProviderFetchModelsResponse",
"ModelProviderKeyCreateParams",
"ModelProviderKeyDeleteParams",
"ModelProviderKeyDeleteResponse",
"ModelProviderKeyEventParams",
"ModelProviderKeyNextParams",
"ModelProviderKeyNextResponse",
"ModelProviderKeyUpdateParams",
"ModelProviderKeyWriteResponse",
"ModelProviderListResponse",
"ModelProviderMutationResponse",
"ModelProviderReadParams",
"ModelProviderReadResponse",
"ModelProviderSortOrderItem",
"ModelProviderSortOrdersUpdateParams",
"ModelProviderTestChatParams",
"ModelProviderTestChatResponse",
"ModelProviderTestConnectionParams",
"ModelProviderTestConnectionResponse",
"ModelProviderUiStateReadParams",
"ModelProviderUiStateReadResponse",
"ModelProviderUiStateWriteParams",
"ModelProviderUpdateParams",
"ModelProviderWriteResponse",
"ModelSyncStateReadResponse",
"OpenDeepLinkPayload",
"PlatformInfo",
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"request": true
},
"required": [
"request"
],
"title": "AutomationJobCreateParams",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"deleted": {
"type": "boolean"
}
},
"required": [
"deleted"
],
"title": "AutomationJobDeleteResponse",
"type": "object"
}
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"query": true
},
"title": "AutomationJobHealthParams",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"health": true
},
"required": [
"health"
],
"title": "AutomationJobHealthResponse",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"title": "AutomationJobIdParams",
"type": "object"
}
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"job": true
},
"title": "AutomationJobReadResponse",
"type": "object"
}
@@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"id": {
"type": "string"
},
"limit": {
"format": "uint",
"minimum": 0,
"type": [
"integer",
"null"
]
}
},
"required": [
"id"
],
"title": "AutomationJobRunHistoryParams",
"type": "object"
}
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"runs": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "AutomationJobRunHistoryResponse",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"result": true
},
"required": [
"result"
],
"title": "AutomationJobRunNowResponse",
"type": "object"
}
@@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"id": {
"type": "string"
},
"request": true
},
"required": [
"id",
"request"
],
"title": "AutomationJobUpdateParams",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"job": true
},
"required": [
"job"
],
"title": "AutomationJobWriteResponse",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"schedule": true
},
"required": [
"schedule"
],
"title": "AutomationScheduleParams",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"nextRunAt": {
"type": [
"null",
"string"
]
}
},
"title": "AutomationSchedulePreviewResponse",
"type": "object"
}
@@ -0,0 +1,19 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": [
"null",
"string"
]
},
"valid": {
"type": "boolean"
}
},
"required": [
"valid"
],
"title": "AutomationScheduleValidateResponse",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"config": true
},
"required": [
"config"
],
"title": "AutomationSchedulerConfigReadResponse",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"config": true
},
"required": [
"config"
],
"title": "AutomationSchedulerConfigUpdateParams",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"config": true
},
"required": [
"config"
],
"title": "AutomationSchedulerConfigUpdateResponse",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"status": true
},
"required": [
"status"
],
"title": "AutomationSchedulerStatusResponse",
"type": "object"
}
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"prompts": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "McpPromptListResponse",
"type": "object"
}
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"resources": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "McpResourceListResponse",
"type": "object"
}
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"servers": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "McpServerListResponse",
"type": "object"
}
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"servers": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "McpServerStatusListResponse",
"type": "object"
}
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"tools": {
"default": [],
"items": true,
"type": "array"
}
},
"title": "McpToolListResponse",
"type": "object"
}
@@ -0,0 +1,14 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"includeKeys": {
"default": null,
"type": [
"boolean",
"null"
]
}
},
"title": "ModelProviderConfigExportParams",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"configJson": {
"type": "string"
}
},
"required": [
"configJson"
],
"title": "ModelProviderConfigExportResponse",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"configJson": {
"type": "string"
}
},
"required": [
"configJson"
],
"title": "ModelProviderConfigImportParams",
"type": "object"
}
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"errors": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"importedApiKeys": {
"format": "uint",
"minimum": 0,
"type": "integer"
},
"importedProviders": {
"format": "uint",
"minimum": 0,
"type": "integer"
},
"skippedProviders": {
"format": "uint",
"minimum": 0,
"type": "integer"
},
"success": {
"type": "boolean"
}
},
"required": [
"importedApiKeys",
"importedProviders",
"skippedProviders",
"success"
],
"title": "ModelProviderConfigImportResponse",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"provider": true
},
"required": [
"provider"
],
"title": "ModelProviderCreateParams",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"providerId": {
"type": "string"
}
},
"required": [
"providerId"
],
"title": "ModelProviderDeleteParams",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"deleted": {
"type": "boolean"
}
},
"required": [
"deleted"
],
"title": "ModelProviderDeleteResponse",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"providerId": {
"type": "string"
}
},
"required": [
"providerId"
],
"title": "ModelProviderFetchModelsParams",
"type": "object"
}
@@ -0,0 +1,50 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"diagnosticHint": {
"type": [
"null",
"string"
]
},
"error": {
"type": [
"null",
"string"
]
},
"errorKind": {
"type": [
"null",
"string"
]
},
"fromCache": {
"type": "boolean"
},
"models": {
"default": [],
"items": true,
"type": "array"
},
"requestUrl": {
"type": [
"null",
"string"
]
},
"shouldPromptError": {
"type": "boolean"
},
"source": {
"type": "string"
}
},
"required": [
"fromCache",
"shouldPromptError",
"source"
],
"title": "ModelProviderFetchModelsResponse",
"type": "object"
}
@@ -0,0 +1,30 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"alias": {
"type": [
"null",
"string"
]
},
"apiKey": {
"type": "string"
},
"providerId": {
"type": "string"
},
"replaceExisting": {
"default": null,
"type": [
"boolean",
"null"
]
}
},
"required": [
"apiKey",
"providerId"
],
"title": "ModelProviderKeyCreateParams",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"keyId": {
"type": "string"
}
},
"required": [
"keyId"
],
"title": "ModelProviderKeyDeleteParams",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"deleted": {
"type": "boolean"
}
},
"required": [
"deleted"
],
"title": "ModelProviderKeyDeleteResponse",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"keyId": {
"type": "string"
}
},
"required": [
"keyId"
],
"title": "ModelProviderKeyEventParams",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"providerId": {
"type": "string"
}
},
"required": [
"providerId"
],
"title": "ModelProviderKeyNextParams",
"type": "object"
}
@@ -0,0 +1,19 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"apiKey": {
"type": [
"null",
"string"
]
},
"keyId": {
"type": [
"null",
"string"
]
}
},
"title": "ModelProviderKeyNextResponse",
"type": "object"
}
@@ -0,0 +1,25 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"alias": {
"type": [
"null",
"string"
]
},
"enabled": {
"type": [
"boolean",
"null"
]
},
"keyId": {
"type": "string"
}
},
"required": [
"keyId"
],
"title": "ModelProviderKeyUpdateParams",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"key": true
},
"required": [
"key"
],
"title": "ModelProviderKeyWriteResponse",
"type": "object"
}
@@ -0,0 +1,5 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ModelProviderMutationResponse",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"providerId": {
"type": "string"
}
},
"required": [
"providerId"
],
"title": "ModelProviderReadParams",
"type": "object"
}
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"provider": true
},
"title": "ModelProviderReadResponse",
"type": "object"
}
@@ -0,0 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"providerId": {
"type": "string"
},
"sortOrder": {
"format": "int32",
"type": "integer"
}
},
"required": [
"providerId",
"sortOrder"
],
"title": "ModelProviderSortOrderItem",
"type": "object"
}
@@ -0,0 +1,32 @@
{
"$defs": {
"ModelProviderSortOrderItem": {
"properties": {
"providerId": {
"type": "string"
},
"sortOrder": {
"format": "int32",
"type": "integer"
}
},
"required": [
"providerId",
"sortOrder"
],
"type": "object"
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"sortOrders": {
"default": [],
"items": {
"$ref": "#/$defs/ModelProviderSortOrderItem"
},
"type": "array"
}
},
"title": "ModelProviderSortOrdersUpdateParams",
"type": "object"
}
@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"modelName": {
"type": [
"null",
"string"
]
},
"prompt": {
"type": "string"
},
"providerId": {
"type": "string"
}
},
"required": [
"prompt",
"providerId"
],
"title": "ModelProviderTestChatParams",
"type": "object"
}
@@ -0,0 +1,39 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"content": {
"type": [
"null",
"string"
]
},
"error": {
"type": [
"null",
"string"
]
},
"latencyMs": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"raw": {
"type": [
"null",
"string"
]
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"title": "ModelProviderTestChatResponse",
"type": "object"
}
@@ -0,0 +1,19 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"modelName": {
"type": [
"null",
"string"
]
},
"providerId": {
"type": "string"
}
},
"required": [
"providerId"
],
"title": "ModelProviderTestConnectionParams",
"type": "object"
}
@@ -0,0 +1,36 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"error": {
"type": [
"null",
"string"
]
},
"latencyMs": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"models": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"title": "ModelProviderTestConnectionResponse",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"key": {
"type": "string"
}
},
"required": [
"key"
],
"title": "ModelProviderUiStateReadParams",
"type": "object"
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"value": {
"type": [
"null",
"string"
]
}
},
"title": "ModelProviderUiStateReadResponse",
"type": "object"
}
@@ -0,0 +1,17 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"key",
"value"
],
"title": "ModelProviderUiStateWriteParams",
"type": "object"
}
@@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"patch": true,
"providerId": {
"type": "string"
}
},
"required": [
"patch",
"providerId"
],
"title": "ModelProviderUpdateParams",
"type": "object"
}
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"provider": true
},
"required": [
"provider"
],
"title": "ModelProviderWriteResponse",
"type": "object"
}
@@ -55,7 +55,30 @@ pub const V0_SCHEMA_TYPE_NAMES: &[&str] = &[
"AgentAppUiRuntimeStatusResponse",
"KnowledgeListPacksParams",
"KnowledgeListPacksResponse",
"AutomationSchedulerConfigReadResponse",
"AutomationSchedulerConfigUpdateParams",
"AutomationSchedulerConfigUpdateResponse",
"AutomationSchedulerStatusResponse",
"AutomationJobListResponse",
"AutomationJobIdParams",
"AutomationJobReadResponse",
"AutomationJobCreateParams",
"AutomationJobWriteResponse",
"AutomationJobUpdateParams",
"AutomationJobDeleteResponse",
"AutomationJobRunNowResponse",
"AutomationJobHealthParams",
"AutomationJobHealthResponse",
"AutomationJobRunHistoryParams",
"AutomationJobRunHistoryResponse",
"AutomationScheduleParams",
"AutomationSchedulePreviewResponse",
"AutomationScheduleValidateResponse",
"McpServerListResponse",
"McpServerStatusListResponse",
"McpToolListResponse",
"McpPromptListResponse",
"McpResourceListResponse",
"ProjectMemoryReadParams",
"ProjectMemoryReadResponse",
"ModelListParams",
@@ -64,6 +87,37 @@ pub const V0_SCHEMA_TYPE_NAMES: &[&str] = &[
"ModelSyncStateReadResponse",
"ModelProviderListResponse",
"ModelProviderCatalogListResponse",
"ModelProviderReadParams",
"ModelProviderReadResponse",
"ModelProviderCreateParams",
"ModelProviderWriteResponse",
"ModelProviderUpdateParams",
"ModelProviderDeleteParams",
"ModelProviderDeleteResponse",
"ModelProviderSortOrderItem",
"ModelProviderSortOrdersUpdateParams",
"ModelProviderMutationResponse",
"ModelProviderConfigExportParams",
"ModelProviderConfigExportResponse",
"ModelProviderConfigImportParams",
"ModelProviderConfigImportResponse",
"ModelProviderTestConnectionParams",
"ModelProviderTestConnectionResponse",
"ModelProviderTestChatParams",
"ModelProviderTestChatResponse",
"ModelProviderFetchModelsParams",
"ModelProviderFetchModelsResponse",
"ModelProviderKeyCreateParams",
"ModelProviderKeyWriteResponse",
"ModelProviderKeyUpdateParams",
"ModelProviderKeyDeleteParams",
"ModelProviderKeyDeleteResponse",
"ModelProviderKeyNextParams",
"ModelProviderKeyNextResponse",
"ModelProviderKeyEventParams",
"ModelProviderUiStateReadParams",
"ModelProviderUiStateReadResponse",
"ModelProviderUiStateWriteParams",
"ModelProviderAliasReadParams",
"ModelProviderAliasReadResponse",
"ModelProviderAliasListResponse",
@@ -128,13 +182,48 @@ pub const METHOD_AGENT_APP_UI_RUNTIME_START: &str = "agentAppUiRuntime/start";
pub const METHOD_AGENT_APP_UI_RUNTIME_STATUS: &str = "agentAppUiRuntime/status";
pub const METHOD_AGENT_APP_UI_RUNTIME_STOP: &str = "agentAppUiRuntime/stop";
pub const METHOD_KNOWLEDGE_PACK_LIST: &str = "knowledgePack/list";
pub const METHOD_AUTOMATION_SCHEDULER_CONFIG_READ: &str = "automationScheduler/config/read";
pub const METHOD_AUTOMATION_SCHEDULER_CONFIG_UPDATE: &str = "automationScheduler/config/update";
pub const METHOD_AUTOMATION_SCHEDULER_STATUS: &str = "automationScheduler/status";
pub const METHOD_AUTOMATION_JOB_LIST: &str = "automationJob/list";
pub const METHOD_AUTOMATION_JOB_READ: &str = "automationJob/read";
pub const METHOD_AUTOMATION_JOB_CREATE: &str = "automationJob/create";
pub const METHOD_AUTOMATION_JOB_UPDATE: &str = "automationJob/update";
pub const METHOD_AUTOMATION_JOB_DELETE: &str = "automationJob/delete";
pub const METHOD_AUTOMATION_JOB_RUN_NOW: &str = "automationJob/runNow";
pub const METHOD_AUTOMATION_JOB_HEALTH: &str = "automationJob/health";
pub const METHOD_AUTOMATION_JOB_RUN_HISTORY: &str = "automationJob/runHistory";
pub const METHOD_AUTOMATION_SCHEDULE_PREVIEW: &str = "automationSchedule/preview";
pub const METHOD_AUTOMATION_SCHEDULE_VALIDATE: &str = "automationSchedule/validate";
pub const METHOD_MCP_SERVER_LIST: &str = "mcpServer/list";
pub const METHOD_MCP_SERVER_STATUS_LIST: &str = "mcpServerStatus/list";
pub const METHOD_MCP_TOOL_LIST: &str = "mcpTool/list";
pub const METHOD_MCP_PROMPT_LIST: &str = "mcpPrompt/list";
pub const METHOD_MCP_RESOURCE_LIST: &str = "mcpResource/list";
pub const METHOD_PROJECT_MEMORY_READ: &str = "projectMemory/read";
pub const METHOD_MODEL_LIST: &str = "model/list";
pub const METHOD_MODEL_PREFERENCES_LIST: &str = "modelPreferences/list";
pub const METHOD_MODEL_SYNC_STATE_READ: &str = "modelSyncState/read";
pub const METHOD_MODEL_PROVIDER_LIST: &str = "modelProvider/list";
pub const METHOD_MODEL_PROVIDER_CATALOG_LIST: &str = "modelProvider/catalog/list";
pub const METHOD_MODEL_PROVIDER_READ: &str = "modelProvider/read";
pub const METHOD_MODEL_PROVIDER_CREATE: &str = "modelProvider/create";
pub const METHOD_MODEL_PROVIDER_UPDATE: &str = "modelProvider/update";
pub const METHOD_MODEL_PROVIDER_DELETE: &str = "modelProvider/delete";
pub const METHOD_MODEL_PROVIDER_SORT_ORDERS_UPDATE: &str = "modelProvider/sortOrders/update";
pub const METHOD_MODEL_PROVIDER_CONFIG_EXPORT: &str = "modelProviderConfig/export";
pub const METHOD_MODEL_PROVIDER_CONFIG_IMPORT: &str = "modelProviderConfig/import";
pub const METHOD_MODEL_PROVIDER_TEST_CONNECTION: &str = "modelProvider/testConnection";
pub const METHOD_MODEL_PROVIDER_TEST_CHAT: &str = "modelProvider/testChat";
pub const METHOD_MODEL_PROVIDER_FETCH_MODELS: &str = "modelProvider/fetchModels";
pub const METHOD_MODEL_PROVIDER_KEY_CREATE: &str = "modelProviderKey/create";
pub const METHOD_MODEL_PROVIDER_KEY_UPDATE: &str = "modelProviderKey/update";
pub const METHOD_MODEL_PROVIDER_KEY_DELETE: &str = "modelProviderKey/delete";
pub const METHOD_MODEL_PROVIDER_KEY_NEXT: &str = "modelProviderKey/next";
pub const METHOD_MODEL_PROVIDER_KEY_USAGE_RECORD: &str = "modelProviderKey/usage/record";
pub const METHOD_MODEL_PROVIDER_KEY_ERROR_RECORD: &str = "modelProviderKey/error/record";
pub const METHOD_MODEL_PROVIDER_UI_STATE_READ: &str = "modelProviderUiState/read";
pub const METHOD_MODEL_PROVIDER_UI_STATE_WRITE: &str = "modelProviderUiState/write";
pub const METHOD_MODEL_PROVIDER_ALIAS_READ: &str = "modelProviderAlias/read";
pub const METHOD_MODEL_PROVIDER_ALIAS_LIST: &str = "modelProviderAlias/list";
pub const METHOD_CONNECT_DEEP_LINK_RESOLVE: &str = "connectDeepLink/resolve";
@@ -267,10 +356,78 @@ pub const APP_SERVER_METHODS: &[AppServerMethodSpec] = &[
method: METHOD_KNOWLEDGE_PACK_LIST,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_SCHEDULER_CONFIG_READ,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_SCHEDULER_CONFIG_UPDATE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_SCHEDULER_STATUS,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_JOB_LIST,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_JOB_READ,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_JOB_CREATE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_JOB_UPDATE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_JOB_DELETE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_JOB_RUN_NOW,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_JOB_HEALTH,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_JOB_RUN_HISTORY,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_SCHEDULE_PREVIEW,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_AUTOMATION_SCHEDULE_VALIDATE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MCP_SERVER_LIST,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MCP_SERVER_STATUS_LIST,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MCP_TOOL_LIST,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MCP_PROMPT_LIST,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MCP_RESOURCE_LIST,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_PROJECT_MEMORY_READ,
kind: AppServerMethodKind::Request,
@@ -295,6 +452,78 @@ pub const APP_SERVER_METHODS: &[AppServerMethodSpec] = &[
method: METHOD_MODEL_PROVIDER_CATALOG_LIST,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_READ,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_CREATE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_UPDATE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_DELETE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_SORT_ORDERS_UPDATE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_CONFIG_EXPORT,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_CONFIG_IMPORT,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_TEST_CONNECTION,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_TEST_CHAT,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_FETCH_MODELS,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_KEY_CREATE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_KEY_UPDATE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_KEY_DELETE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_KEY_NEXT,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_KEY_USAGE_RECORD,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_KEY_ERROR_RECORD,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_UI_STATE_READ,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_UI_STATE_WRITE,
kind: AppServerMethodKind::Request,
},
AppServerMethodSpec {
method: METHOD_MODEL_PROVIDER_ALIAS_READ,
kind: AppServerMethodKind::Request,
@@ -639,6 +868,41 @@ pub struct EvidencePackArtifact {
pub bytes: usize,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct McpServerListResponse {
#[serde(default)]
pub servers: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct McpServerStatusListResponse {
#[serde(default)]
pub servers: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct McpToolListResponse {
#[serde(default)]
pub tools: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct McpPromptListResponse {
#[serde(default)]
pub prompts: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct McpResourceListResponse {
#[serde(default)]
pub resources: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AgentSessionListParams {
@@ -886,6 +1150,30 @@ pub struct KnowledgeListPacksResponse {
pub packs: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationSchedulerConfigReadResponse {
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationSchedulerConfigUpdateParams {
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationSchedulerConfigUpdateResponse {
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationSchedulerStatusResponse {
pub status: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobListResponse {
@@ -893,6 +1181,99 @@ pub struct AutomationJobListResponse {
pub jobs: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobIdParams {
pub id: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobReadResponse {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub job: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobCreateParams {
pub request: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobWriteResponse {
pub job: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobUpdateParams {
pub id: String,
pub request: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobDeleteResponse {
pub deleted: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobRunNowResponse {
pub result: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobHealthParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub query: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobHealthResponse {
pub health: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobRunHistoryParams {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationJobRunHistoryResponse {
#[serde(default)]
pub runs: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationScheduleParams {
pub schedule: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationSchedulePreviewResponse {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_run_at: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AutomationScheduleValidateResponse {
pub valid: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ProjectMemoryReadParams {
@@ -948,6 +1329,245 @@ pub struct ModelProviderCatalogListResponse {
pub providers: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderReadParams {
pub provider_id: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderReadResponse {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderCreateParams {
pub provider: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderWriteResponse {
pub provider: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderUpdateParams {
pub provider_id: String,
pub patch: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderDeleteParams {
pub provider_id: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderDeleteResponse {
pub deleted: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderSortOrderItem {
pub provider_id: String,
pub sort_order: i32,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderSortOrdersUpdateParams {
#[serde(default)]
pub sort_orders: Vec<ModelProviderSortOrderItem>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderMutationResponse {}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderConfigExportParams {
#[serde(default)]
pub include_keys: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderConfigExportResponse {
pub config_json: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderConfigImportParams {
pub config_json: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderConfigImportResponse {
pub success: bool,
pub imported_providers: usize,
pub imported_api_keys: usize,
pub skipped_providers: usize,
#[serde(default)]
pub errors: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderTestConnectionParams {
pub provider_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model_name: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderTestConnectionResponse {
pub success: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub latency_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub models: Option<Vec<String>>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderTestChatParams {
pub provider_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model_name: Option<String>,
pub prompt: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderTestChatResponse {
pub success: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub latency_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderFetchModelsParams {
pub provider_id: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderFetchModelsResponse {
#[serde(default)]
pub models: Vec<serde_json::Value>,
pub source: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub diagnostic_hint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error_kind: Option<String>,
pub should_prompt_error: bool,
pub from_cache: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderKeyCreateParams {
pub provider_id: String,
pub api_key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub alias: Option<String>,
#[serde(default)]
pub replace_existing: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderKeyWriteResponse {
pub key: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderKeyUpdateParams {
pub key_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub alias: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderKeyDeleteParams {
pub key_id: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderKeyDeleteResponse {
pub deleted: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderKeyNextParams {
pub provider_id: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderKeyNextResponse {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub api_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_id: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderKeyEventParams {
pub key_id: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderUiStateReadParams {
pub key: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderUiStateReadResponse {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderUiStateWriteParams {
pub key: String,
pub value: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelProviderAliasReadParams {
@@ -1994,13 +2614,48 @@ mod tests {
METHOD_AGENT_APP_UI_RUNTIME_STATUS,
METHOD_AGENT_APP_UI_RUNTIME_STOP,
METHOD_KNOWLEDGE_PACK_LIST,
METHOD_AUTOMATION_SCHEDULER_CONFIG_READ,
METHOD_AUTOMATION_SCHEDULER_CONFIG_UPDATE,
METHOD_AUTOMATION_SCHEDULER_STATUS,
METHOD_AUTOMATION_JOB_LIST,
METHOD_AUTOMATION_JOB_READ,
METHOD_AUTOMATION_JOB_CREATE,
METHOD_AUTOMATION_JOB_UPDATE,
METHOD_AUTOMATION_JOB_DELETE,
METHOD_AUTOMATION_JOB_RUN_NOW,
METHOD_AUTOMATION_JOB_HEALTH,
METHOD_AUTOMATION_JOB_RUN_HISTORY,
METHOD_AUTOMATION_SCHEDULE_PREVIEW,
METHOD_AUTOMATION_SCHEDULE_VALIDATE,
METHOD_MCP_SERVER_LIST,
METHOD_MCP_SERVER_STATUS_LIST,
METHOD_MCP_TOOL_LIST,
METHOD_MCP_PROMPT_LIST,
METHOD_MCP_RESOURCE_LIST,
METHOD_PROJECT_MEMORY_READ,
METHOD_MODEL_LIST,
METHOD_MODEL_PREFERENCES_LIST,
METHOD_MODEL_SYNC_STATE_READ,
METHOD_MODEL_PROVIDER_LIST,
METHOD_MODEL_PROVIDER_CATALOG_LIST,
METHOD_MODEL_PROVIDER_READ,
METHOD_MODEL_PROVIDER_CREATE,
METHOD_MODEL_PROVIDER_UPDATE,
METHOD_MODEL_PROVIDER_DELETE,
METHOD_MODEL_PROVIDER_SORT_ORDERS_UPDATE,
METHOD_MODEL_PROVIDER_CONFIG_EXPORT,
METHOD_MODEL_PROVIDER_CONFIG_IMPORT,
METHOD_MODEL_PROVIDER_TEST_CONNECTION,
METHOD_MODEL_PROVIDER_TEST_CHAT,
METHOD_MODEL_PROVIDER_FETCH_MODELS,
METHOD_MODEL_PROVIDER_KEY_CREATE,
METHOD_MODEL_PROVIDER_KEY_UPDATE,
METHOD_MODEL_PROVIDER_KEY_DELETE,
METHOD_MODEL_PROVIDER_KEY_NEXT,
METHOD_MODEL_PROVIDER_KEY_USAGE_RECORD,
METHOD_MODEL_PROVIDER_KEY_ERROR_RECORD,
METHOD_MODEL_PROVIDER_UI_STATE_READ,
METHOD_MODEL_PROVIDER_UI_STATE_WRITE,
METHOD_MODEL_PROVIDER_ALIAS_READ,
METHOD_MODEL_PROVIDER_ALIAS_LIST,
METHOD_CONNECT_DEEP_LINK_RESOLVE,
@@ -33,7 +33,25 @@ use crate::ArtifactContentStatus;
use crate::ArtifactReadParams;
use crate::ArtifactReadResponse;
use crate::ArtifactSummary;
use crate::AutomationJobCreateParams;
use crate::AutomationJobDeleteResponse;
use crate::AutomationJobHealthParams;
use crate::AutomationJobHealthResponse;
use crate::AutomationJobIdParams;
use crate::AutomationJobListResponse;
use crate::AutomationJobReadResponse;
use crate::AutomationJobRunHistoryParams;
use crate::AutomationJobRunHistoryResponse;
use crate::AutomationJobRunNowResponse;
use crate::AutomationJobUpdateParams;
use crate::AutomationJobWriteResponse;
use crate::AutomationScheduleParams;
use crate::AutomationSchedulePreviewResponse;
use crate::AutomationScheduleValidateResponse;
use crate::AutomationSchedulerConfigReadResponse;
use crate::AutomationSchedulerConfigUpdateParams;
use crate::AutomationSchedulerConfigUpdateResponse;
use crate::AutomationSchedulerStatusResponse;
use crate::BusinessObjectRef;
use crate::CapabilityDescriptor;
use crate::CapabilityListParams;
@@ -69,6 +87,11 @@ use crate::JsonRpcRequest;
use crate::JsonRpcResponse;
use crate::KnowledgeListPacksParams;
use crate::KnowledgeListPacksResponse;
use crate::McpPromptListResponse;
use crate::McpResourceListResponse;
use crate::McpServerListResponse;
use crate::McpServerStatusListResponse;
use crate::McpToolListResponse;
use crate::ModelListParams;
use crate::ModelListResponse;
use crate::ModelPreferencesListResponse;
@@ -76,7 +99,38 @@ use crate::ModelProviderAliasListResponse;
use crate::ModelProviderAliasReadParams;
use crate::ModelProviderAliasReadResponse;
use crate::ModelProviderCatalogListResponse;
use crate::ModelProviderConfigExportParams;
use crate::ModelProviderConfigExportResponse;
use crate::ModelProviderConfigImportParams;
use crate::ModelProviderConfigImportResponse;
use crate::ModelProviderCreateParams;
use crate::ModelProviderDeleteParams;
use crate::ModelProviderDeleteResponse;
use crate::ModelProviderFetchModelsParams;
use crate::ModelProviderFetchModelsResponse;
use crate::ModelProviderKeyCreateParams;
use crate::ModelProviderKeyDeleteParams;
use crate::ModelProviderKeyDeleteResponse;
use crate::ModelProviderKeyEventParams;
use crate::ModelProviderKeyNextParams;
use crate::ModelProviderKeyNextResponse;
use crate::ModelProviderKeyUpdateParams;
use crate::ModelProviderKeyWriteResponse;
use crate::ModelProviderListResponse;
use crate::ModelProviderMutationResponse;
use crate::ModelProviderReadParams;
use crate::ModelProviderReadResponse;
use crate::ModelProviderSortOrderItem;
use crate::ModelProviderSortOrdersUpdateParams;
use crate::ModelProviderTestChatParams;
use crate::ModelProviderTestChatResponse;
use crate::ModelProviderTestConnectionParams;
use crate::ModelProviderTestConnectionResponse;
use crate::ModelProviderUiStateReadParams;
use crate::ModelProviderUiStateReadResponse;
use crate::ModelProviderUiStateWriteParams;
use crate::ModelProviderUpdateParams;
use crate::ModelProviderWriteResponse;
use crate::ModelSyncStateReadResponse;
use crate::OpenDeepLinkPayload;
use crate::PlatformInfo;
@@ -284,7 +338,36 @@ fn v0_schemas() -> Vec<GeneratedJsonSchema> {
typed_schema::<AgentAppUiRuntimeStatusResponse>("AgentAppUiRuntimeStatusResponse"),
typed_schema::<KnowledgeListPacksParams>("KnowledgeListPacksParams"),
typed_schema::<KnowledgeListPacksResponse>("KnowledgeListPacksResponse"),
typed_schema::<AutomationSchedulerConfigReadResponse>(
"AutomationSchedulerConfigReadResponse",
),
typed_schema::<AutomationSchedulerConfigUpdateParams>(
"AutomationSchedulerConfigUpdateParams",
),
typed_schema::<AutomationSchedulerConfigUpdateResponse>(
"AutomationSchedulerConfigUpdateResponse",
),
typed_schema::<AutomationSchedulerStatusResponse>("AutomationSchedulerStatusResponse"),
typed_schema::<AutomationJobListResponse>("AutomationJobListResponse"),
typed_schema::<AutomationJobIdParams>("AutomationJobIdParams"),
typed_schema::<AutomationJobReadResponse>("AutomationJobReadResponse"),
typed_schema::<AutomationJobCreateParams>("AutomationJobCreateParams"),
typed_schema::<AutomationJobWriteResponse>("AutomationJobWriteResponse"),
typed_schema::<AutomationJobUpdateParams>("AutomationJobUpdateParams"),
typed_schema::<AutomationJobDeleteResponse>("AutomationJobDeleteResponse"),
typed_schema::<AutomationJobRunNowResponse>("AutomationJobRunNowResponse"),
typed_schema::<AutomationJobHealthParams>("AutomationJobHealthParams"),
typed_schema::<AutomationJobHealthResponse>("AutomationJobHealthResponse"),
typed_schema::<AutomationJobRunHistoryParams>("AutomationJobRunHistoryParams"),
typed_schema::<AutomationJobRunHistoryResponse>("AutomationJobRunHistoryResponse"),
typed_schema::<AutomationScheduleParams>("AutomationScheduleParams"),
typed_schema::<AutomationSchedulePreviewResponse>("AutomationSchedulePreviewResponse"),
typed_schema::<AutomationScheduleValidateResponse>("AutomationScheduleValidateResponse"),
typed_schema::<McpServerListResponse>("McpServerListResponse"),
typed_schema::<McpServerStatusListResponse>("McpServerStatusListResponse"),
typed_schema::<McpToolListResponse>("McpToolListResponse"),
typed_schema::<McpPromptListResponse>("McpPromptListResponse"),
typed_schema::<McpResourceListResponse>("McpResourceListResponse"),
typed_schema::<ProjectMemoryReadParams>("ProjectMemoryReadParams"),
typed_schema::<ProjectMemoryReadResponse>("ProjectMemoryReadResponse"),
typed_schema::<ModelListParams>("ModelListParams"),
@@ -293,6 +376,37 @@ fn v0_schemas() -> Vec<GeneratedJsonSchema> {
typed_schema::<ModelSyncStateReadResponse>("ModelSyncStateReadResponse"),
typed_schema::<ModelProviderListResponse>("ModelProviderListResponse"),
typed_schema::<ModelProviderCatalogListResponse>("ModelProviderCatalogListResponse"),
typed_schema::<ModelProviderReadParams>("ModelProviderReadParams"),
typed_schema::<ModelProviderReadResponse>("ModelProviderReadResponse"),
typed_schema::<ModelProviderCreateParams>("ModelProviderCreateParams"),
typed_schema::<ModelProviderWriteResponse>("ModelProviderWriteResponse"),
typed_schema::<ModelProviderUpdateParams>("ModelProviderUpdateParams"),
typed_schema::<ModelProviderDeleteParams>("ModelProviderDeleteParams"),
typed_schema::<ModelProviderDeleteResponse>("ModelProviderDeleteResponse"),
typed_schema::<ModelProviderSortOrderItem>("ModelProviderSortOrderItem"),
typed_schema::<ModelProviderSortOrdersUpdateParams>("ModelProviderSortOrdersUpdateParams"),
typed_schema::<ModelProviderMutationResponse>("ModelProviderMutationResponse"),
typed_schema::<ModelProviderConfigExportParams>("ModelProviderConfigExportParams"),
typed_schema::<ModelProviderConfigExportResponse>("ModelProviderConfigExportResponse"),
typed_schema::<ModelProviderConfigImportParams>("ModelProviderConfigImportParams"),
typed_schema::<ModelProviderConfigImportResponse>("ModelProviderConfigImportResponse"),
typed_schema::<ModelProviderTestConnectionParams>("ModelProviderTestConnectionParams"),
typed_schema::<ModelProviderTestConnectionResponse>("ModelProviderTestConnectionResponse"),
typed_schema::<ModelProviderTestChatParams>("ModelProviderTestChatParams"),
typed_schema::<ModelProviderTestChatResponse>("ModelProviderTestChatResponse"),
typed_schema::<ModelProviderFetchModelsParams>("ModelProviderFetchModelsParams"),
typed_schema::<ModelProviderFetchModelsResponse>("ModelProviderFetchModelsResponse"),
typed_schema::<ModelProviderKeyCreateParams>("ModelProviderKeyCreateParams"),
typed_schema::<ModelProviderKeyWriteResponse>("ModelProviderKeyWriteResponse"),
typed_schema::<ModelProviderKeyUpdateParams>("ModelProviderKeyUpdateParams"),
typed_schema::<ModelProviderKeyDeleteParams>("ModelProviderKeyDeleteParams"),
typed_schema::<ModelProviderKeyDeleteResponse>("ModelProviderKeyDeleteResponse"),
typed_schema::<ModelProviderKeyNextParams>("ModelProviderKeyNextParams"),
typed_schema::<ModelProviderKeyNextResponse>("ModelProviderKeyNextResponse"),
typed_schema::<ModelProviderKeyEventParams>("ModelProviderKeyEventParams"),
typed_schema::<ModelProviderUiStateReadParams>("ModelProviderUiStateReadParams"),
typed_schema::<ModelProviderUiStateReadResponse>("ModelProviderUiStateReadResponse"),
typed_schema::<ModelProviderUiStateWriteParams>("ModelProviderUiStateWriteParams"),
typed_schema::<ModelProviderAliasReadParams>("ModelProviderAliasReadParams"),
typed_schema::<ModelProviderAliasReadResponse>("ModelProviderAliasReadResponse"),
typed_schema::<ModelProviderAliasListResponse>("ModelProviderAliasListResponse"),
+2
View File
@@ -23,6 +23,8 @@ anyhow.workspace = true
async-trait.workspace = true
aster.workspace = true
chrono.workspace = true
chrono-tz.workspace = true
cron.workspace = true
app-server-protocol.workspace = true
app-server-transport.workspace = true
lime-core.workspace = true
File diff suppressed because it is too large Load Diff
+650
View File
@@ -12,6 +12,13 @@ use app_server_protocol::AgentSessionEventParams;
use app_server_protocol::AgentSessionListParams;
use app_server_protocol::AgentSessionUpdateParams;
use app_server_protocol::ArtifactReadParams;
use app_server_protocol::AutomationJobCreateParams;
use app_server_protocol::AutomationJobHealthParams;
use app_server_protocol::AutomationJobIdParams;
use app_server_protocol::AutomationJobRunHistoryParams;
use app_server_protocol::AutomationJobUpdateParams;
use app_server_protocol::AutomationScheduleParams;
use app_server_protocol::AutomationSchedulerConfigUpdateParams;
use app_server_protocol::CapabilityListParams;
use app_server_protocol::ClientInfo;
use app_server_protocol::ConnectCallbackSendParams;
@@ -32,6 +39,23 @@ use app_server_protocol::JsonRpcResponse;
use app_server_protocol::KnowledgeListPacksParams;
use app_server_protocol::ModelListParams;
use app_server_protocol::ModelProviderAliasReadParams;
use app_server_protocol::ModelProviderConfigExportParams;
use app_server_protocol::ModelProviderConfigImportParams;
use app_server_protocol::ModelProviderCreateParams;
use app_server_protocol::ModelProviderDeleteParams;
use app_server_protocol::ModelProviderFetchModelsParams;
use app_server_protocol::ModelProviderKeyCreateParams;
use app_server_protocol::ModelProviderKeyDeleteParams;
use app_server_protocol::ModelProviderKeyEventParams;
use app_server_protocol::ModelProviderKeyNextParams;
use app_server_protocol::ModelProviderKeyUpdateParams;
use app_server_protocol::ModelProviderReadParams;
use app_server_protocol::ModelProviderSortOrdersUpdateParams;
use app_server_protocol::ModelProviderTestChatParams;
use app_server_protocol::ModelProviderTestConnectionParams;
use app_server_protocol::ModelProviderUiStateReadParams;
use app_server_protocol::ModelProviderUiStateWriteParams;
use app_server_protocol::ModelProviderUpdateParams;
use app_server_protocol::PlatformInfo;
use app_server_protocol::ProjectMemoryReadParams;
use app_server_protocol::ServerCapabilities;
@@ -56,7 +80,19 @@ use app_server_protocol::METHOD_AGENT_SESSION_TURN_CANCEL;
use app_server_protocol::METHOD_AGENT_SESSION_TURN_START;
use app_server_protocol::METHOD_AGENT_SESSION_UPDATE;
use app_server_protocol::METHOD_ARTIFACT_READ;
use app_server_protocol::METHOD_AUTOMATION_JOB_CREATE;
use app_server_protocol::METHOD_AUTOMATION_JOB_DELETE;
use app_server_protocol::METHOD_AUTOMATION_JOB_HEALTH;
use app_server_protocol::METHOD_AUTOMATION_JOB_LIST;
use app_server_protocol::METHOD_AUTOMATION_JOB_READ;
use app_server_protocol::METHOD_AUTOMATION_JOB_RUN_HISTORY;
use app_server_protocol::METHOD_AUTOMATION_JOB_RUN_NOW;
use app_server_protocol::METHOD_AUTOMATION_JOB_UPDATE;
use app_server_protocol::METHOD_AUTOMATION_SCHEDULER_CONFIG_READ;
use app_server_protocol::METHOD_AUTOMATION_SCHEDULER_CONFIG_UPDATE;
use app_server_protocol::METHOD_AUTOMATION_SCHEDULER_STATUS;
use app_server_protocol::METHOD_AUTOMATION_SCHEDULE_PREVIEW;
use app_server_protocol::METHOD_AUTOMATION_SCHEDULE_VALIDATE;
use app_server_protocol::METHOD_CAPABILITY_LIST;
use app_server_protocol::METHOD_CONNECT_CALLBACK_SEND;
use app_server_protocol::METHOD_CONNECT_DEEP_LINK_RESOLVE;
@@ -68,12 +104,35 @@ use app_server_protocol::METHOD_FILE_SYSTEM_READ_FILE_PREVIEW;
use app_server_protocol::METHOD_INITIALIZE;
use app_server_protocol::METHOD_INITIALIZED;
use app_server_protocol::METHOD_KNOWLEDGE_PACK_LIST;
use app_server_protocol::METHOD_MCP_PROMPT_LIST;
use app_server_protocol::METHOD_MCP_RESOURCE_LIST;
use app_server_protocol::METHOD_MCP_SERVER_LIST;
use app_server_protocol::METHOD_MCP_SERVER_STATUS_LIST;
use app_server_protocol::METHOD_MCP_TOOL_LIST;
use app_server_protocol::METHOD_MODEL_LIST;
use app_server_protocol::METHOD_MODEL_PREFERENCES_LIST;
use app_server_protocol::METHOD_MODEL_PROVIDER_ALIAS_LIST;
use app_server_protocol::METHOD_MODEL_PROVIDER_ALIAS_READ;
use app_server_protocol::METHOD_MODEL_PROVIDER_CATALOG_LIST;
use app_server_protocol::METHOD_MODEL_PROVIDER_CONFIG_EXPORT;
use app_server_protocol::METHOD_MODEL_PROVIDER_CONFIG_IMPORT;
use app_server_protocol::METHOD_MODEL_PROVIDER_CREATE;
use app_server_protocol::METHOD_MODEL_PROVIDER_DELETE;
use app_server_protocol::METHOD_MODEL_PROVIDER_FETCH_MODELS;
use app_server_protocol::METHOD_MODEL_PROVIDER_KEY_CREATE;
use app_server_protocol::METHOD_MODEL_PROVIDER_KEY_DELETE;
use app_server_protocol::METHOD_MODEL_PROVIDER_KEY_ERROR_RECORD;
use app_server_protocol::METHOD_MODEL_PROVIDER_KEY_NEXT;
use app_server_protocol::METHOD_MODEL_PROVIDER_KEY_UPDATE;
use app_server_protocol::METHOD_MODEL_PROVIDER_KEY_USAGE_RECORD;
use app_server_protocol::METHOD_MODEL_PROVIDER_LIST;
use app_server_protocol::METHOD_MODEL_PROVIDER_READ;
use app_server_protocol::METHOD_MODEL_PROVIDER_SORT_ORDERS_UPDATE;
use app_server_protocol::METHOD_MODEL_PROVIDER_TEST_CHAT;
use app_server_protocol::METHOD_MODEL_PROVIDER_TEST_CONNECTION;
use app_server_protocol::METHOD_MODEL_PROVIDER_UI_STATE_READ;
use app_server_protocol::METHOD_MODEL_PROVIDER_UI_STATE_WRITE;
use app_server_protocol::METHOD_MODEL_PROVIDER_UPDATE;
use app_server_protocol::METHOD_MODEL_SYNC_STATE_READ;
use app_server_protocol::METHOD_PROJECT_MEMORY_READ;
use app_server_protocol::METHOD_SKILL_LIST;
@@ -184,13 +243,76 @@ impl RequestProcessor {
}
METHOD_AGENT_APP_UI_RUNTIME_STOP => self.handle_agent_app_ui_runtime_stop(params).await,
METHOD_KNOWLEDGE_PACK_LIST => self.handle_knowledge_pack_list(params).await,
METHOD_AUTOMATION_SCHEDULER_CONFIG_READ => {
self.handle_automation_scheduler_config_read().await
}
METHOD_AUTOMATION_SCHEDULER_CONFIG_UPDATE => {
self.handle_automation_scheduler_config_update(params).await
}
METHOD_AUTOMATION_SCHEDULER_STATUS => self.handle_automation_scheduler_status().await,
METHOD_AUTOMATION_JOB_LIST => self.handle_automation_job_list().await,
METHOD_AUTOMATION_JOB_READ => self.handle_automation_job_read(params).await,
METHOD_AUTOMATION_JOB_CREATE => self.handle_automation_job_create(params).await,
METHOD_AUTOMATION_JOB_UPDATE => self.handle_automation_job_update(params).await,
METHOD_AUTOMATION_JOB_DELETE => self.handle_automation_job_delete(params).await,
METHOD_AUTOMATION_JOB_RUN_NOW => self.handle_automation_job_run_now(params).await,
METHOD_AUTOMATION_JOB_HEALTH => self.handle_automation_job_health(params).await,
METHOD_AUTOMATION_JOB_RUN_HISTORY => {
self.handle_automation_job_run_history(params).await
}
METHOD_AUTOMATION_SCHEDULE_PREVIEW => {
self.handle_automation_schedule_preview(params).await
}
METHOD_AUTOMATION_SCHEDULE_VALIDATE => {
self.handle_automation_schedule_validate(params).await
}
METHOD_MCP_SERVER_LIST => self.handle_mcp_server_list().await,
METHOD_MCP_SERVER_STATUS_LIST => self.handle_mcp_server_status_list().await,
METHOD_MCP_TOOL_LIST => self.handle_mcp_tool_list().await,
METHOD_MCP_PROMPT_LIST => self.handle_mcp_prompt_list().await,
METHOD_MCP_RESOURCE_LIST => self.handle_mcp_resource_list().await,
METHOD_PROJECT_MEMORY_READ => self.handle_project_memory_read(params).await,
METHOD_MODEL_LIST => self.handle_model_list(params).await,
METHOD_MODEL_PREFERENCES_LIST => self.handle_model_preferences_list().await,
METHOD_MODEL_SYNC_STATE_READ => self.handle_model_sync_state_read().await,
METHOD_MODEL_PROVIDER_LIST => self.handle_model_provider_list().await,
METHOD_MODEL_PROVIDER_CATALOG_LIST => self.handle_model_provider_catalog_list().await,
METHOD_MODEL_PROVIDER_READ => self.handle_model_provider_read(params).await,
METHOD_MODEL_PROVIDER_CREATE => self.handle_model_provider_create(params).await,
METHOD_MODEL_PROVIDER_UPDATE => self.handle_model_provider_update(params).await,
METHOD_MODEL_PROVIDER_DELETE => self.handle_model_provider_delete(params).await,
METHOD_MODEL_PROVIDER_SORT_ORDERS_UPDATE => {
self.handle_model_provider_sort_orders_update(params).await
}
METHOD_MODEL_PROVIDER_CONFIG_EXPORT => {
self.handle_model_provider_config_export(params).await
}
METHOD_MODEL_PROVIDER_CONFIG_IMPORT => {
self.handle_model_provider_config_import(params).await
}
METHOD_MODEL_PROVIDER_TEST_CONNECTION => {
self.handle_model_provider_test_connection(params).await
}
METHOD_MODEL_PROVIDER_TEST_CHAT => self.handle_model_provider_test_chat(params).await,
METHOD_MODEL_PROVIDER_FETCH_MODELS => {
self.handle_model_provider_fetch_models(params).await
}
METHOD_MODEL_PROVIDER_KEY_CREATE => self.handle_model_provider_key_create(params).await,
METHOD_MODEL_PROVIDER_KEY_UPDATE => self.handle_model_provider_key_update(params).await,
METHOD_MODEL_PROVIDER_KEY_DELETE => self.handle_model_provider_key_delete(params).await,
METHOD_MODEL_PROVIDER_KEY_NEXT => self.handle_model_provider_key_next(params).await,
METHOD_MODEL_PROVIDER_KEY_USAGE_RECORD => {
self.handle_model_provider_key_usage_record(params).await
}
METHOD_MODEL_PROVIDER_KEY_ERROR_RECORD => {
self.handle_model_provider_key_error_record(params).await
}
METHOD_MODEL_PROVIDER_UI_STATE_READ => {
self.handle_model_provider_ui_state_read(params).await
}
METHOD_MODEL_PROVIDER_UI_STATE_WRITE => {
self.handle_model_provider_ui_state_write(params).await
}
METHOD_MODEL_PROVIDER_ALIAS_READ => self.handle_model_provider_alias_read(params).await,
METHOD_MODEL_PROVIDER_ALIAS_LIST => self.handle_model_provider_alias_list().await,
METHOD_CONNECT_DEEP_LINK_RESOLVE => self.handle_connect_deep_link_resolve(params).await,
@@ -528,6 +650,166 @@ impl RequestProcessor {
dispatch_result(response)
}
async fn handle_automation_scheduler_config_read(&self) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let response = self
.runtime
.read_automation_scheduler_config()
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_scheduler_config_update(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: AutomationSchedulerConfigUpdateParams = parse_params(params)?;
let response = self
.runtime
.update_automation_scheduler_config(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_scheduler_status(&self) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let response = self
.runtime
.read_automation_scheduler_status()
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_job_read(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: AutomationJobIdParams = parse_params(params)?;
let response = self
.runtime
.read_automation_job(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_job_create(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: AutomationJobCreateParams = parse_params(params)?;
let response = self
.runtime
.create_automation_job(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_job_update(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: AutomationJobUpdateParams = parse_params(params)?;
let response = self
.runtime
.update_automation_job(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_job_delete(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: AutomationJobIdParams = parse_params(params)?;
let response = self
.runtime
.delete_automation_job(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_job_run_now(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: AutomationJobIdParams = parse_params(params)?;
let response = self
.runtime
.run_automation_job_now(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_job_health(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: AutomationJobHealthParams = parse_params(params)?;
let response = self
.runtime
.read_automation_health(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_job_run_history(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: AutomationJobRunHistoryParams = parse_params(params)?;
let response = self
.runtime
.read_automation_run_history(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_schedule_preview(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: AutomationScheduleParams = parse_params(params)?;
let response = self
.runtime
.preview_automation_schedule(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_automation_schedule_validate(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: AutomationScheduleParams = parse_params(params)?;
let response = self
.runtime
.validate_automation_schedule(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_project_memory_read(
&self,
params: Option<serde_json::Value>,
@@ -542,6 +824,56 @@ impl RequestProcessor {
dispatch_result(response)
}
async fn handle_mcp_server_list(&self) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let response = self
.runtime
.list_mcp_servers()
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_mcp_server_status_list(&self) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let response = self
.runtime
.list_mcp_servers_with_status()
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_mcp_tool_list(&self) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let response = self
.runtime
.list_mcp_tools()
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_mcp_prompt_list(&self) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let response = self
.runtime
.list_mcp_prompts()
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_mcp_resource_list(&self) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let response = self
.runtime
.list_mcp_resources()
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_list(
&self,
params: Option<serde_json::Value>,
@@ -596,6 +928,258 @@ impl RequestProcessor {
dispatch_result(response)
}
async fn handle_model_provider_read(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderReadParams = parse_params(params)?;
let response = self
.runtime
.read_model_provider(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_create(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderCreateParams = parse_params(params)?;
let response = self
.runtime
.create_model_provider(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_update(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderUpdateParams = parse_params(params)?;
let response = self
.runtime
.update_model_provider(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_delete(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderDeleteParams = parse_params(params)?;
let response = self
.runtime
.delete_model_provider(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_sort_orders_update(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderSortOrdersUpdateParams = parse_params(params)?;
let response = self
.runtime
.update_model_provider_sort_orders(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_config_export(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderConfigExportParams = parse_params(params)?;
let response = self
.runtime
.export_model_provider_config(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_config_import(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderConfigImportParams = parse_params(params)?;
let response = self
.runtime
.import_model_provider_config(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_test_connection(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderTestConnectionParams = parse_params(params)?;
let response = self
.runtime
.test_model_provider_connection(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_test_chat(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderTestChatParams = parse_params(params)?;
let response = self
.runtime
.test_model_provider_chat(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_fetch_models(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderFetchModelsParams = parse_params(params)?;
let response = self
.runtime
.fetch_model_provider_models(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_key_create(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderKeyCreateParams = parse_params(params)?;
let response = self
.runtime
.create_model_provider_key(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_key_update(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderKeyUpdateParams = parse_params(params)?;
let response = self
.runtime
.update_model_provider_key(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_key_delete(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderKeyDeleteParams = parse_params(params)?;
let response = self
.runtime
.delete_model_provider_key(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_key_next(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderKeyNextParams = parse_params(params)?;
let response = self
.runtime
.read_next_model_provider_key(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_key_usage_record(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderKeyEventParams = parse_params(params)?;
let response = self
.runtime
.record_model_provider_key_usage(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_key_error_record(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderKeyEventParams = parse_params(params)?;
let response = self
.runtime
.record_model_provider_key_error(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_ui_state_read(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderUiStateReadParams = parse_params(params)?;
let response = self
.runtime
.read_model_provider_ui_state(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_ui_state_write(
&self,
params: Option<serde_json::Value>,
) -> Result<RpcDispatch, JsonRpcError> {
self.ensure_initialized()?;
let params: ModelProviderUiStateWriteParams = parse_params(params)?;
let response = self
.runtime
.write_model_provider_ui_state(params)
.await
.map_err(to_jsonrpc_error)?;
dispatch_result(response)
}
async fn handle_model_provider_alias_read(
&self,
params: Option<serde_json::Value>,
@@ -1232,6 +1816,72 @@ mod tests {
}
}
#[tokio::test]
async fn mcp_list_methods_require_initialized_and_return_current_empty_state() {
let processor = RequestProcessor::new(RuntimeCore::default());
let blocked = processor
.handle_request(JsonRpcRequest::new(
RequestId::Integer(1),
METHOD_MCP_TOOL_LIST,
Some(json!({})),
))
.await
.expect("blocked response");
assert!(matches!(
&blocked[0],
JsonRpcMessage::Error(error) if error.error.code == error_codes::NOT_INITIALIZED
));
processor
.handle_request(JsonRpcRequest::new(
RequestId::Integer(2),
METHOD_INITIALIZE,
Some(
serde_json::to_value(InitializeParams {
client_info: ClientInfo {
name: "test-client".to_string(),
title: None,
version: None,
},
capabilities: ClientCapabilities::default(),
})
.expect("initialize params"),
),
))
.await
.expect("initialize");
processor.handle_notification(JsonRpcNotification::new(
METHOD_INITIALIZED,
Some(json!({})),
));
let cases = [
(RequestId::Integer(3), METHOD_MCP_SERVER_LIST, "servers"),
(
RequestId::Integer(4),
METHOD_MCP_SERVER_STATUS_LIST,
"servers",
),
(RequestId::Integer(5), METHOD_MCP_TOOL_LIST, "tools"),
(RequestId::Integer(6), METHOD_MCP_PROMPT_LIST, "prompts"),
(RequestId::Integer(7), METHOD_MCP_RESOURCE_LIST, "resources"),
];
for (id, method, field) in cases {
let messages = processor
.handle_request(JsonRpcRequest::new(id, method, Some(json!({}))))
.await
.expect("mcp list response");
match &messages[0] {
JsonRpcMessage::Response(response) => {
assert_eq!(response.result[field], json!([]));
}
other => panic!("expected response, got {other:?}"),
}
}
}
#[tokio::test]
async fn evidence_export_requires_initialized_and_returns_read_model_snapshot() {
let runtime = RuntimeCore::default();
+640
View File
@@ -35,7 +35,25 @@ use app_server_protocol::ArtifactContentStatus;
use app_server_protocol::ArtifactReadParams;
use app_server_protocol::ArtifactReadResponse;
use app_server_protocol::ArtifactSummary;
use app_server_protocol::AutomationJobCreateParams;
use app_server_protocol::AutomationJobDeleteResponse;
use app_server_protocol::AutomationJobHealthParams;
use app_server_protocol::AutomationJobHealthResponse;
use app_server_protocol::AutomationJobIdParams;
use app_server_protocol::AutomationJobListResponse;
use app_server_protocol::AutomationJobReadResponse;
use app_server_protocol::AutomationJobRunHistoryParams;
use app_server_protocol::AutomationJobRunHistoryResponse;
use app_server_protocol::AutomationJobRunNowResponse;
use app_server_protocol::AutomationJobUpdateParams;
use app_server_protocol::AutomationJobWriteResponse;
use app_server_protocol::AutomationScheduleParams;
use app_server_protocol::AutomationSchedulePreviewResponse;
use app_server_protocol::AutomationScheduleValidateResponse;
use app_server_protocol::AutomationSchedulerConfigReadResponse;
use app_server_protocol::AutomationSchedulerConfigUpdateParams;
use app_server_protocol::AutomationSchedulerConfigUpdateResponse;
use app_server_protocol::AutomationSchedulerStatusResponse;
use app_server_protocol::CapabilityListParams;
use app_server_protocol::CapabilityListResponse;
use app_server_protocol::ClientInfo;
@@ -58,6 +76,11 @@ use app_server_protocol::FileSystemReadFilePreviewParams;
use app_server_protocol::JsonRpcError;
use app_server_protocol::KnowledgeListPacksParams;
use app_server_protocol::KnowledgeListPacksResponse;
use app_server_protocol::McpPromptListResponse;
use app_server_protocol::McpResourceListResponse;
use app_server_protocol::McpServerListResponse;
use app_server_protocol::McpServerStatusListResponse;
use app_server_protocol::McpToolListResponse;
use app_server_protocol::ModelListParams;
use app_server_protocol::ModelListResponse;
use app_server_protocol::ModelPreferencesListResponse;
@@ -65,7 +88,37 @@ use app_server_protocol::ModelProviderAliasListResponse;
use app_server_protocol::ModelProviderAliasReadParams;
use app_server_protocol::ModelProviderAliasReadResponse;
use app_server_protocol::ModelProviderCatalogListResponse;
use app_server_protocol::ModelProviderConfigExportParams;
use app_server_protocol::ModelProviderConfigExportResponse;
use app_server_protocol::ModelProviderConfigImportParams;
use app_server_protocol::ModelProviderConfigImportResponse;
use app_server_protocol::ModelProviderCreateParams;
use app_server_protocol::ModelProviderDeleteParams;
use app_server_protocol::ModelProviderDeleteResponse;
use app_server_protocol::ModelProviderFetchModelsParams;
use app_server_protocol::ModelProviderFetchModelsResponse;
use app_server_protocol::ModelProviderKeyCreateParams;
use app_server_protocol::ModelProviderKeyDeleteParams;
use app_server_protocol::ModelProviderKeyDeleteResponse;
use app_server_protocol::ModelProviderKeyEventParams;
use app_server_protocol::ModelProviderKeyNextParams;
use app_server_protocol::ModelProviderKeyNextResponse;
use app_server_protocol::ModelProviderKeyUpdateParams;
use app_server_protocol::ModelProviderKeyWriteResponse;
use app_server_protocol::ModelProviderListResponse;
use app_server_protocol::ModelProviderMutationResponse;
use app_server_protocol::ModelProviderReadParams;
use app_server_protocol::ModelProviderReadResponse;
use app_server_protocol::ModelProviderSortOrdersUpdateParams;
use app_server_protocol::ModelProviderTestChatParams;
use app_server_protocol::ModelProviderTestChatResponse;
use app_server_protocol::ModelProviderTestConnectionParams;
use app_server_protocol::ModelProviderTestConnectionResponse;
use app_server_protocol::ModelProviderUiStateReadParams;
use app_server_protocol::ModelProviderUiStateReadResponse;
use app_server_protocol::ModelProviderUiStateWriteParams;
use app_server_protocol::ModelProviderUpdateParams;
use app_server_protocol::ModelProviderWriteResponse;
use app_server_protocol::ModelSyncStateReadResponse;
use app_server_protocol::ProjectMemoryReadParams;
use app_server_protocol::ProjectMemoryReadResponse;
@@ -290,6 +343,146 @@ pub trait AppDataSource: Send + Sync {
async fn list_automation_jobs(&self) -> Result<AutomationJobListResponse, RuntimeCoreError>;
async fn list_mcp_servers(&self) -> Result<McpServerListResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"mcpServer/list is not available without an app data source".to_string(),
))
}
async fn list_mcp_servers_with_status(
&self,
) -> Result<McpServerStatusListResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"mcpServerStatus/list is not available without an app data source".to_string(),
))
}
async fn list_mcp_tools(&self) -> Result<McpToolListResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"mcpTool/list is not available without an app data source".to_string(),
))
}
async fn list_mcp_prompts(&self) -> Result<McpPromptListResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"mcpPrompt/list is not available without an app data source".to_string(),
))
}
async fn list_mcp_resources(&self) -> Result<McpResourceListResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"mcpResource/list is not available without an app data source".to_string(),
))
}
async fn read_automation_scheduler_config(
&self,
) -> Result<AutomationSchedulerConfigReadResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationScheduler/config/read is not available without an app data source"
.to_string(),
))
}
async fn update_automation_scheduler_config(
&self,
_params: AutomationSchedulerConfigUpdateParams,
) -> Result<AutomationSchedulerConfigUpdateResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationScheduler/config/update is not available without an app data source"
.to_string(),
))
}
async fn read_automation_scheduler_status(
&self,
) -> Result<AutomationSchedulerStatusResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationScheduler/status is not available without an app data source".to_string(),
))
}
async fn read_automation_job(
&self,
_params: AutomationJobIdParams,
) -> Result<AutomationJobReadResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationJob/read is not available without an app data source".to_string(),
))
}
async fn create_automation_job(
&self,
_params: AutomationJobCreateParams,
) -> Result<AutomationJobWriteResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationJob/create is not available without an app data source".to_string(),
))
}
async fn update_automation_job(
&self,
_params: AutomationJobUpdateParams,
) -> Result<AutomationJobWriteResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationJob/update is not available without an app data source".to_string(),
))
}
async fn delete_automation_job(
&self,
_params: AutomationJobIdParams,
) -> Result<AutomationJobDeleteResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationJob/delete is not available without an app data source".to_string(),
))
}
async fn run_automation_job_now(
&self,
_params: AutomationJobIdParams,
) -> Result<AutomationJobRunNowResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationJob/runNow requires the App Server automation executor".to_string(),
))
}
async fn read_automation_health(
&self,
_params: AutomationJobHealthParams,
) -> Result<AutomationJobHealthResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationJob/health is not available without an app data source".to_string(),
))
}
async fn read_automation_run_history(
&self,
_params: AutomationJobRunHistoryParams,
) -> Result<AutomationJobRunHistoryResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationJob/runHistory is not available without an app data source".to_string(),
))
}
async fn preview_automation_schedule(
&self,
_params: AutomationScheduleParams,
) -> Result<AutomationSchedulePreviewResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationSchedule/preview is not available without an app data source".to_string(),
))
}
async fn validate_automation_schedule(
&self,
_params: AutomationScheduleParams,
) -> Result<AutomationScheduleValidateResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"automationSchedule/validate is not available without an app data source".to_string(),
))
}
async fn read_project_memory(
&self,
params: ProjectMemoryReadParams,
@@ -312,6 +505,169 @@ pub trait AppDataSource: Send + Sync {
&self,
) -> Result<ModelProviderCatalogListResponse, RuntimeCoreError>;
async fn read_model_provider(
&self,
_params: ModelProviderReadParams,
) -> Result<ModelProviderReadResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProvider/read is not available without an app data source".to_string(),
))
}
async fn create_model_provider(
&self,
_params: ModelProviderCreateParams,
) -> Result<ModelProviderWriteResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProvider/create is not available without an app data source".to_string(),
))
}
async fn update_model_provider(
&self,
_params: ModelProviderUpdateParams,
) -> Result<ModelProviderWriteResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProvider/update is not available without an app data source".to_string(),
))
}
async fn delete_model_provider(
&self,
_params: ModelProviderDeleteParams,
) -> Result<ModelProviderDeleteResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProvider/delete is not available without an app data source".to_string(),
))
}
async fn update_model_provider_sort_orders(
&self,
_params: ModelProviderSortOrdersUpdateParams,
) -> Result<ModelProviderMutationResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProvider/sortOrders/update is not available without an app data source"
.to_string(),
))
}
async fn export_model_provider_config(
&self,
_params: ModelProviderConfigExportParams,
) -> Result<ModelProviderConfigExportResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProviderConfig/export is not available without an app data source".to_string(),
))
}
async fn import_model_provider_config(
&self,
_params: ModelProviderConfigImportParams,
) -> Result<ModelProviderConfigImportResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProviderConfig/import is not available without an app data source".to_string(),
))
}
async fn test_model_provider_connection(
&self,
_params: ModelProviderTestConnectionParams,
) -> Result<ModelProviderTestConnectionResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProvider/testConnection is not available without an app data source".to_string(),
))
}
async fn test_model_provider_chat(
&self,
_params: ModelProviderTestChatParams,
) -> Result<ModelProviderTestChatResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProvider/testChat is not available without an app data source".to_string(),
))
}
async fn fetch_model_provider_models(
&self,
_params: ModelProviderFetchModelsParams,
) -> Result<ModelProviderFetchModelsResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProvider/fetchModels is not available without an app data source".to_string(),
))
}
async fn create_model_provider_key(
&self,
_params: ModelProviderKeyCreateParams,
) -> Result<ModelProviderKeyWriteResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProviderKey/create is not available without an app data source".to_string(),
))
}
async fn update_model_provider_key(
&self,
_params: ModelProviderKeyUpdateParams,
) -> Result<ModelProviderKeyWriteResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProviderKey/update is not available without an app data source".to_string(),
))
}
async fn delete_model_provider_key(
&self,
_params: ModelProviderKeyDeleteParams,
) -> Result<ModelProviderKeyDeleteResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProviderKey/delete is not available without an app data source".to_string(),
))
}
async fn read_next_model_provider_key(
&self,
_params: ModelProviderKeyNextParams,
) -> Result<ModelProviderKeyNextResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProviderKey/next is not available without an app data source".to_string(),
))
}
async fn record_model_provider_key_usage(
&self,
_params: ModelProviderKeyEventParams,
) -> Result<ModelProviderMutationResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProviderKey/usage/record is not available without an app data source".to_string(),
))
}
async fn record_model_provider_key_error(
&self,
_params: ModelProviderKeyEventParams,
) -> Result<ModelProviderMutationResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProviderKey/error/record is not available without an app data source".to_string(),
))
}
async fn read_model_provider_ui_state(
&self,
_params: ModelProviderUiStateReadParams,
) -> Result<ModelProviderUiStateReadResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProviderUiState/read is not available without an app data source".to_string(),
))
}
async fn write_model_provider_ui_state(
&self,
_params: ModelProviderUiStateWriteParams,
) -> Result<ModelProviderMutationResponse, RuntimeCoreError> {
Err(RuntimeCoreError::Backend(
"modelProviderUiState/write is not available without an app data source".to_string(),
))
}
async fn read_model_provider_alias(
&self,
params: ModelProviderAliasReadParams,
@@ -560,6 +916,28 @@ impl AppDataSource for NoopAppDataSource {
Ok(AutomationJobListResponse::default())
}
async fn list_mcp_servers(&self) -> Result<McpServerListResponse, RuntimeCoreError> {
Ok(McpServerListResponse::default())
}
async fn list_mcp_servers_with_status(
&self,
) -> Result<McpServerStatusListResponse, RuntimeCoreError> {
Ok(McpServerStatusListResponse::default())
}
async fn list_mcp_tools(&self) -> Result<McpToolListResponse, RuntimeCoreError> {
Ok(McpToolListResponse::default())
}
async fn list_mcp_prompts(&self) -> Result<McpPromptListResponse, RuntimeCoreError> {
Ok(McpPromptListResponse::default())
}
async fn list_mcp_resources(&self) -> Result<McpResourceListResponse, RuntimeCoreError> {
Ok(McpResourceListResponse::default())
}
async fn read_project_memory(
&self,
_params: ProjectMemoryReadParams,
@@ -1571,6 +1949,122 @@ impl RuntimeCore {
self.app_data_source.list_automation_jobs().await
}
pub async fn list_mcp_servers(&self) -> Result<McpServerListResponse, RuntimeCoreError> {
self.app_data_source.list_mcp_servers().await
}
pub async fn list_mcp_servers_with_status(
&self,
) -> Result<McpServerStatusListResponse, RuntimeCoreError> {
self.app_data_source.list_mcp_servers_with_status().await
}
pub async fn list_mcp_tools(&self) -> Result<McpToolListResponse, RuntimeCoreError> {
self.app_data_source.list_mcp_tools().await
}
pub async fn list_mcp_prompts(&self) -> Result<McpPromptListResponse, RuntimeCoreError> {
self.app_data_source.list_mcp_prompts().await
}
pub async fn list_mcp_resources(&self) -> Result<McpResourceListResponse, RuntimeCoreError> {
self.app_data_source.list_mcp_resources().await
}
pub async fn read_automation_scheduler_config(
&self,
) -> Result<AutomationSchedulerConfigReadResponse, RuntimeCoreError> {
self.app_data_source
.read_automation_scheduler_config()
.await
}
pub async fn update_automation_scheduler_config(
&self,
params: AutomationSchedulerConfigUpdateParams,
) -> Result<AutomationSchedulerConfigUpdateResponse, RuntimeCoreError> {
self.app_data_source
.update_automation_scheduler_config(params)
.await
}
pub async fn read_automation_scheduler_status(
&self,
) -> Result<AutomationSchedulerStatusResponse, RuntimeCoreError> {
self.app_data_source
.read_automation_scheduler_status()
.await
}
pub async fn read_automation_job(
&self,
params: AutomationJobIdParams,
) -> Result<AutomationJobReadResponse, RuntimeCoreError> {
self.app_data_source.read_automation_job(params).await
}
pub async fn create_automation_job(
&self,
params: AutomationJobCreateParams,
) -> Result<AutomationJobWriteResponse, RuntimeCoreError> {
self.app_data_source.create_automation_job(params).await
}
pub async fn update_automation_job(
&self,
params: AutomationJobUpdateParams,
) -> Result<AutomationJobWriteResponse, RuntimeCoreError> {
self.app_data_source.update_automation_job(params).await
}
pub async fn delete_automation_job(
&self,
params: AutomationJobIdParams,
) -> Result<AutomationJobDeleteResponse, RuntimeCoreError> {
self.app_data_source.delete_automation_job(params).await
}
pub async fn run_automation_job_now(
&self,
params: AutomationJobIdParams,
) -> Result<AutomationJobRunNowResponse, RuntimeCoreError> {
self.app_data_source.run_automation_job_now(params).await
}
pub async fn read_automation_health(
&self,
params: AutomationJobHealthParams,
) -> Result<AutomationJobHealthResponse, RuntimeCoreError> {
self.app_data_source.read_automation_health(params).await
}
pub async fn read_automation_run_history(
&self,
params: AutomationJobRunHistoryParams,
) -> Result<AutomationJobRunHistoryResponse, RuntimeCoreError> {
self.app_data_source
.read_automation_run_history(params)
.await
}
pub async fn preview_automation_schedule(
&self,
params: AutomationScheduleParams,
) -> Result<AutomationSchedulePreviewResponse, RuntimeCoreError> {
self.app_data_source
.preview_automation_schedule(params)
.await
}
pub async fn validate_automation_schedule(
&self,
params: AutomationScheduleParams,
) -> Result<AutomationScheduleValidateResponse, RuntimeCoreError> {
self.app_data_source
.validate_automation_schedule(params)
.await
}
pub async fn read_project_memory(
&self,
params: ProjectMemoryReadParams,
@@ -1609,6 +2103,152 @@ impl RuntimeCore {
self.app_data_source.list_model_provider_catalog().await
}
pub async fn read_model_provider(
&self,
params: ModelProviderReadParams,
) -> Result<ModelProviderReadResponse, RuntimeCoreError> {
self.app_data_source.read_model_provider(params).await
}
pub async fn create_model_provider(
&self,
params: ModelProviderCreateParams,
) -> Result<ModelProviderWriteResponse, RuntimeCoreError> {
self.app_data_source.create_model_provider(params).await
}
pub async fn update_model_provider(
&self,
params: ModelProviderUpdateParams,
) -> Result<ModelProviderWriteResponse, RuntimeCoreError> {
self.app_data_source.update_model_provider(params).await
}
pub async fn delete_model_provider(
&self,
params: ModelProviderDeleteParams,
) -> Result<ModelProviderDeleteResponse, RuntimeCoreError> {
self.app_data_source.delete_model_provider(params).await
}
pub async fn update_model_provider_sort_orders(
&self,
params: ModelProviderSortOrdersUpdateParams,
) -> Result<ModelProviderMutationResponse, RuntimeCoreError> {
self.app_data_source
.update_model_provider_sort_orders(params)
.await
}
pub async fn export_model_provider_config(
&self,
params: ModelProviderConfigExportParams,
) -> Result<ModelProviderConfigExportResponse, RuntimeCoreError> {
self.app_data_source
.export_model_provider_config(params)
.await
}
pub async fn import_model_provider_config(
&self,
params: ModelProviderConfigImportParams,
) -> Result<ModelProviderConfigImportResponse, RuntimeCoreError> {
self.app_data_source
.import_model_provider_config(params)
.await
}
pub async fn test_model_provider_connection(
&self,
params: ModelProviderTestConnectionParams,
) -> Result<ModelProviderTestConnectionResponse, RuntimeCoreError> {
self.app_data_source
.test_model_provider_connection(params)
.await
}
pub async fn test_model_provider_chat(
&self,
params: ModelProviderTestChatParams,
) -> Result<ModelProviderTestChatResponse, RuntimeCoreError> {
self.app_data_source.test_model_provider_chat(params).await
}
pub async fn fetch_model_provider_models(
&self,
params: ModelProviderFetchModelsParams,
) -> Result<ModelProviderFetchModelsResponse, RuntimeCoreError> {
self.app_data_source
.fetch_model_provider_models(params)
.await
}
pub async fn create_model_provider_key(
&self,
params: ModelProviderKeyCreateParams,
) -> Result<ModelProviderKeyWriteResponse, RuntimeCoreError> {
self.app_data_source.create_model_provider_key(params).await
}
pub async fn update_model_provider_key(
&self,
params: ModelProviderKeyUpdateParams,
) -> Result<ModelProviderKeyWriteResponse, RuntimeCoreError> {
self.app_data_source.update_model_provider_key(params).await
}
pub async fn delete_model_provider_key(
&self,
params: ModelProviderKeyDeleteParams,
) -> Result<ModelProviderKeyDeleteResponse, RuntimeCoreError> {
self.app_data_source.delete_model_provider_key(params).await
}
pub async fn read_next_model_provider_key(
&self,
params: ModelProviderKeyNextParams,
) -> Result<ModelProviderKeyNextResponse, RuntimeCoreError> {
self.app_data_source
.read_next_model_provider_key(params)
.await
}
pub async fn record_model_provider_key_usage(
&self,
params: ModelProviderKeyEventParams,
) -> Result<ModelProviderMutationResponse, RuntimeCoreError> {
self.app_data_source
.record_model_provider_key_usage(params)
.await
}
pub async fn record_model_provider_key_error(
&self,
params: ModelProviderKeyEventParams,
) -> Result<ModelProviderMutationResponse, RuntimeCoreError> {
self.app_data_source
.record_model_provider_key_error(params)
.await
}
pub async fn read_model_provider_ui_state(
&self,
params: ModelProviderUiStateReadParams,
) -> Result<ModelProviderUiStateReadResponse, RuntimeCoreError> {
self.app_data_source
.read_model_provider_ui_state(params)
.await
}
pub async fn write_model_provider_ui_state(
&self,
params: ModelProviderUiStateWriteParams,
) -> Result<ModelProviderMutationResponse, RuntimeCoreError> {
self.app_data_source
.write_model_provider_ui_state(params)
.await
}
pub async fn read_model_provider_alias(
&self,
params: ModelProviderAliasReadParams,
-8
View File
@@ -10,10 +10,6 @@ default = []
local-whisper = ["voice-core/local-whisper"]
local-sensevoice = ["voice-core/local-sensevoice"]
[lints.rust]
# 允许 objc 宏展开时携带的 cargo-clippy 伪 feature,避免 unexpected_cfgs 噪音。
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("cargo-clippy"))'] }
[dependencies]
# 项目内 crate
lime-core.workspace = true
@@ -75,7 +71,3 @@ proptest.workspace = true
[target.'cfg(windows)'.dependencies]
winapi.workspace = true
winreg.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
cocoa.workspace = true
objc.workspace = true
@@ -9,30 +9,14 @@
//! - 获取文件元信息
//! - 获取文件权限和 MIME 类型
#![allow(deprecated, unexpected_cfgs)]
use base64::{engine::general_purpose, Engine as _};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{self, Metadata};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::Semaphore;
use std::time::UNIX_EPOCH;
use tracing::{debug, error};
static FILE_ICON_DATA_URL_CACHE: Lazy<Mutex<HashMap<String, Option<String>>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static FILE_ICON_RESOLVE_SEMAPHORE: Lazy<Arc<Semaphore>> =
Lazy::new(|| Arc::new(Semaphore::new(2)));
const FILE_ICON_RESOLVE_ACQUIRE_TIMEOUT: Duration = Duration::from_millis(80);
const FILE_ICON_RESOLVE_TIMEOUT: Duration = Duration::from_millis(900);
/// 文件条目
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileEntry {
@@ -100,19 +84,6 @@ pub struct FilePreview {
pub error: Option<String>,
}
/// 文件管理器快捷入口
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileManagerLocation {
/// 稳定 ID
pub id: String,
/// 展示名称
pub label: String,
/// 入口目录绝对路径
pub path: String,
/// 入口类型
pub kind: String,
}
/// 获取文件扩展名
fn get_file_extension(path: &Path) -> Option<String> {
path.extension()
@@ -125,469 +96,6 @@ fn is_hidden_file(name: &str) -> bool {
name.starts_with('.')
}
fn encode_png_data_url(bytes: &[u8]) -> Option<String> {
if bytes.is_empty() {
return None;
}
Some(format!(
"data:image/png;base64,{}",
general_purpose::STANDARD.encode(bytes)
))
}
fn temporary_icon_png_path() -> PathBuf {
let unique = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_nanos())
.unwrap_or(0);
std::env::temp_dir().join(format!(
"lime-file-icon-{}-{unique}.png",
std::process::id()
))
}
#[cfg(any(target_os = "macos", test))]
fn normalize_bundle_icon_file_name(value: &str) -> Option<String> {
let trimmed = value.trim().trim_matches('"').trim();
if trimmed.is_empty() {
return None;
}
Path::new(trimmed)
.file_name()
.and_then(|name| name.to_str())
.map(|name| name.trim().to_string())
.filter(|name| !name.is_empty())
}
fn resolve_cached_file_icon_data_url(
path: &Path,
metadata: &Metadata,
name: &str,
) -> Option<String> {
if !should_resolve_file_icon(path, metadata, name) {
return None;
}
let cache_key = path.to_string_lossy().to_string();
if let Some(cached) = FILE_ICON_DATA_URL_CACHE.lock().get(&cache_key).cloned() {
return cached;
}
let icon_data_url = resolve_platform_file_icon_data_url(path, metadata, name);
FILE_ICON_DATA_URL_CACHE
.lock()
.insert(cache_key, icon_data_url.clone());
icon_data_url
}
fn get_cached_file_icon_data_url(path: &Path) -> Option<String> {
FILE_ICON_DATA_URL_CACHE
.lock()
.get(&path.to_string_lossy().to_string())
.and_then(|cached| cached.clone())
}
fn get_file_icon_cache_entry(path: &Path) -> Option<Option<String>> {
FILE_ICON_DATA_URL_CACHE
.lock()
.get(&path.to_string_lossy().to_string())
.cloned()
}
fn should_resolve_file_icon(path: &Path, metadata: &Metadata, name: &str) -> bool {
#[cfg(target_os = "macos")]
{
let _ = (path, metadata, name);
true
}
#[cfg(target_os = "windows")]
{
let _ = metadata;
let extension = path
.extension()
.and_then(|extension| extension.to_str())
.unwrap_or("");
matches!(
extension.to_ascii_lowercase().as_str(),
"exe" | "lnk" | "appref-ms"
) || name.ends_with(".lnk")
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
let _ = (path, metadata, name);
false
}
}
#[cfg(target_os = "macos")]
fn resolve_platform_file_icon_data_url(
path: &Path,
metadata: &Metadata,
_name: &str,
) -> Option<String> {
resolve_macos_system_icon_data_url(path).or_else(|| {
if is_macos_app_bundle(path, metadata) {
resolve_macos_app_icon_data_url(path)
} else {
None
}
})
}
#[cfg(target_os = "windows")]
fn resolve_platform_file_icon_data_url(
path: &Path,
_metadata: &Metadata,
_name: &str,
) -> Option<String> {
resolve_windows_associated_icon_data_url(path)
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn resolve_platform_file_icon_data_url(
_path: &Path,
_metadata: &Metadata,
_name: &str,
) -> Option<String> {
None
}
#[cfg(target_os = "macos")]
fn resolve_macos_app_icon_data_url(app_path: &Path) -> Option<String> {
let resources_dir = app_path.join("Contents").join("Resources");
let icon_path = resolve_macos_bundle_icon_path(app_path, &resources_dir)
.or_else(|| find_first_icns_file(&resources_dir))?;
convert_macos_icns_to_png_data_url(&icon_path)
}
#[cfg(target_os = "macos")]
fn is_macos_app_bundle(path: &Path, metadata: &Metadata) -> bool {
metadata.is_dir()
&& path
.extension()
.and_then(|extension| extension.to_str())
.map(|extension| extension.eq_ignore_ascii_case("app"))
.unwrap_or(false)
}
#[cfg(target_os = "macos")]
fn resolve_macos_system_icon_data_url(path: &Path) -> Option<String> {
use cocoa::base::{id, nil};
use cocoa::foundation::{NSAutoreleasePool, NSDictionary, NSSize, NSString, NSUInteger};
use objc::{class, msg_send, sel, sel_impl};
unsafe {
let pool = NSAutoreleasePool::new(nil);
let path_string = path.to_string_lossy();
let ns_path = NSString::alloc(nil).init_str(path_string.as_ref());
let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
let icon: id = msg_send![workspace, iconForFile: ns_path];
if icon == nil {
pool.drain();
return None;
}
let _: () = msg_send![icon, setSize: NSSize::new(128.0, 128.0)];
let tiff_data: id = msg_send![icon, TIFFRepresentation];
if tiff_data == nil {
pool.drain();
return None;
}
let bitmap_rep: id = msg_send![class!(NSBitmapImageRep), imageRepWithData: tiff_data];
if bitmap_rep == nil {
pool.drain();
return None;
}
let properties = NSDictionary::dictionary(nil);
let png_type = 4 as NSUInteger;
let png_data: id =
msg_send![bitmap_rep, representationUsingType: png_type properties: properties];
let bytes = nsdata_to_vec(png_data);
pool.drain();
bytes.and_then(|bytes| encode_png_data_url(&bytes))
}
}
#[cfg(target_os = "macos")]
unsafe fn nsdata_to_vec(data: cocoa::base::id) -> Option<Vec<u8>> {
use cocoa::base::nil;
use cocoa::foundation::NSData;
if data == nil {
return None;
}
let len = data.length() as usize;
if len == 0 {
return None;
}
let ptr = data.bytes() as *const u8;
if ptr.is_null() {
return None;
}
Some(std::slice::from_raw_parts(ptr, len).to_vec())
}
#[cfg(target_os = "macos")]
fn resolve_macos_bundle_icon_path(app_path: &Path, resources_dir: &Path) -> Option<PathBuf> {
let info_plist = app_path.join("Contents").join("Info.plist");
if !info_plist.is_file() {
return None;
}
let output = std::process::Command::new("/usr/bin/plutil")
.args(["-extract", "CFBundleIconFile", "raw", "-o", "-"])
.arg(info_plist)
.output()
.ok()?;
if !output.status.success() {
return None;
}
let icon_name = normalize_bundle_icon_file_name(&String::from_utf8_lossy(&output.stdout))?;
let mut candidates = Vec::with_capacity(2);
candidates.push(resources_dir.join(&icon_name));
if Path::new(&icon_name).extension().is_none() {
candidates.push(resources_dir.join(format!("{icon_name}.icns")));
}
candidates.into_iter().find(|candidate| candidate.is_file())
}
#[cfg(target_os = "macos")]
fn find_first_icns_file(resources_dir: &Path) -> Option<PathBuf> {
let read_dir = fs::read_dir(resources_dir).ok()?;
let mut candidates: Vec<PathBuf> = read_dir
.filter_map(|entry| entry.ok().map(|entry| entry.path()))
.filter(|path| {
path.extension()
.and_then(|extension| extension.to_str())
.map(|extension| extension.eq_ignore_ascii_case("icns"))
.unwrap_or(false)
})
.collect();
candidates.sort_by_key(|path| {
let file_name = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("")
.to_ascii_lowercase();
(
!file_name.contains("appicon"),
!file_name.contains("icon"),
file_name,
)
});
candidates.into_iter().next()
}
#[cfg(target_os = "macos")]
fn convert_macos_icns_to_png_data_url(icon_path: &Path) -> Option<String> {
let output_path = temporary_icon_png_path();
let result = std::process::Command::new("/usr/bin/sips")
.args(["-s", "format", "png"])
.arg(icon_path)
.arg("--out")
.arg(&output_path)
.output();
let converted = match result {
Ok(output) if output.status.success() => fs::read(&output_path).ok(),
_ => None,
};
let _ = fs::remove_file(&output_path);
converted.and_then(|bytes| encode_png_data_url(&bytes))
}
#[cfg(target_os = "windows")]
fn resolve_windows_associated_icon_data_url(path: &Path) -> Option<String> {
let output_path = temporary_icon_png_path();
let script = r#"
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon($env:LIME_ICON_SOURCE)
if ($null -eq $icon) { exit 2 }
$bitmap = $icon.ToBitmap()
$bitmap.Save($env:LIME_ICON_OUTPUT, [System.Drawing.Imaging.ImageFormat]::Png)
$bitmap.Dispose()
$icon.Dispose()
"#;
let encoded_script = encode_powershell_script(script);
let result = std::process::Command::new("powershell")
.args([
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-EncodedCommand",
&encoded_script,
])
.env("LIME_ICON_SOURCE", path)
.env("LIME_ICON_OUTPUT", &output_path)
.output();
let rendered = match result {
Ok(output) if output.status.success() => fs::read(&output_path).ok(),
_ => None,
};
let _ = fs::remove_file(&output_path);
rendered.and_then(|bytes| encode_png_data_url(&bytes))
}
#[cfg(target_os = "windows")]
fn encode_powershell_script(script: &str) -> String {
let mut bytes = Vec::with_capacity(script.len() * 2);
for unit in script.encode_utf16() {
bytes.extend_from_slice(&unit.to_le_bytes());
}
general_purpose::STANDARD.encode(bytes)
}
fn append_file_manager_location(
locations: &mut Vec<FileManagerLocation>,
seen_paths: &mut std::collections::HashSet<String>,
id: &str,
label: &str,
kind: &str,
path: Option<PathBuf>,
) {
let Some(path) = path else {
return;
};
if !path.is_dir() {
return;
}
let normalized_path = path.to_string_lossy().to_string();
if normalized_path.trim().is_empty() || !seen_paths.insert(normalized_path.clone()) {
return;
}
locations.push(FileManagerLocation {
id: id.to_string(),
label: label.to_string(),
path: normalized_path,
kind: kind.to_string(),
});
}
/// 获取文件管理器快捷入口
pub fn file_manager_locations() -> Vec<FileManagerLocation> {
let mut locations = Vec::new();
let mut seen_paths = std::collections::HashSet::new();
let home_dir = dirs::home_dir();
append_file_manager_location(
&mut locations,
&mut seen_paths,
"home",
"个人",
"home",
home_dir.clone(),
);
append_file_manager_location(
&mut locations,
&mut seen_paths,
"desktop",
"桌面",
"desktop",
dirs::desktop_dir(),
);
append_file_manager_location(
&mut locations,
&mut seen_paths,
"documents",
"文档",
"documents",
dirs::document_dir(),
);
append_file_manager_location(
&mut locations,
&mut seen_paths,
"downloads",
"下载",
"downloads",
dirs::download_dir(),
);
#[cfg(target_os = "macos")]
{
append_file_manager_location(
&mut locations,
&mut seen_paths,
"applications",
"应用程序",
"applications",
Some(PathBuf::from("/Applications")),
);
append_file_manager_location(
&mut locations,
&mut seen_paths,
"user-applications",
"用户应用程序",
"applications",
home_dir.map(|home| home.join("Applications")),
);
}
#[cfg(target_os = "windows")]
{
append_file_manager_location(
&mut locations,
&mut seen_paths,
"start-menu-programs",
"应用程序",
"applications",
std::env::var_os("APPDATA").map(|dir| {
PathBuf::from(dir)
.join("Microsoft")
.join("Windows")
.join("Start Menu")
.join("Programs")
}),
);
append_file_manager_location(
&mut locations,
&mut seen_paths,
"common-start-menu-programs",
"公共应用程序",
"applications",
std::env::var_os("PROGRAMDATA").map(|dir| {
PathBuf::from(dir)
.join("Microsoft")
.join("Windows")
.join("Start Menu")
.join("Programs")
}),
);
append_file_manager_location(
&mut locations,
&mut seen_paths,
"program-files",
"Program Files",
"applications",
std::env::var_os("ProgramFiles").map(PathBuf::from),
);
append_file_manager_location(
&mut locations,
&mut seen_paths,
"program-files-x86",
"Program Files (x86)",
"applications",
std::env::var_os("ProgramFiles(x86)").map(PathBuf::from),
);
}
locations
}
/// 将 Unix 文件模式转换为权限字符串(如 -rw-r--r--
#[cfg(unix)]
fn mode_to_string(mode: u32, is_dir: bool, is_symlink: bool) -> String {
@@ -886,8 +394,6 @@ pub fn list_directory(path: &str) -> DirectoryListing {
// 获取 MIME 类型
let mime_type = get_mime_type(&path, &metadata);
let icon_data_url = get_cached_file_icon_data_url(&path);
Some(FileEntry {
name: name.clone(),
path: path.to_string_lossy().to_string(),
@@ -900,7 +406,7 @@ pub fn list_directory(path: &str) -> DirectoryListing {
mode,
mime_type: Some(mime_type),
is_symlink,
icon_data_url,
icon_data_url: None,
})
})
.collect();
@@ -1018,52 +524,6 @@ pub fn read_file_preview(path: &str, max_size: Option<usize>) -> FilePreview {
}
}
/// 服务接口:异步获取文件图标
pub async fn get_file_icon_data_url(path: String) -> Result<Option<String>, String> {
let path_buf = PathBuf::from(&path);
if let Some(cached) = get_file_icon_cache_entry(&path_buf) {
return Ok(cached);
}
let semaphore = Arc::clone(&FILE_ICON_RESOLVE_SEMAPHORE);
let permit =
match tokio::time::timeout(FILE_ICON_RESOLVE_ACQUIRE_TIMEOUT, semaphore.acquire_owned())
.await
{
Ok(Ok(permit)) => permit,
Ok(Err(_)) => return Ok(None),
Err(_) => return Ok(None),
};
let resolve_task = tokio::task::spawn_blocking(move || {
let _permit = permit;
let metadata = fs::metadata(&path_buf).ok()?;
let name = path_buf
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("");
resolve_cached_file_icon_data_url(&path_buf, &metadata, name)
});
match tokio::time::timeout(FILE_ICON_RESOLVE_TIMEOUT, resolve_task).await {
Ok(Ok(icon_data_url)) => Ok(icon_data_url),
Ok(Err(join_error)) => Err(format!("文件图标读取任务失败: {join_error}")),
Err(_) => Ok(None),
}
}
/// 服务接口:获取用户主目录
pub async fn get_home_dir() -> Result<String, String> {
dirs::home_dir()
.map(|p| p.to_string_lossy().to_string())
.ok_or_else(|| "无法获取主目录".to_string())
}
/// 服务接口:获取文件管理器快捷入口
pub async fn get_file_manager_locations() -> Result<Vec<FileManagerLocation>, String> {
Ok(file_manager_locations())
}
/// 服务接口:创建新文件
pub async fn create_file(path: String) -> Result<(), String> {
let path_buf = PathBuf::from(&path);
@@ -1154,79 +614,6 @@ pub async fn get_file_name(path: String) -> Result<String, String> {
.map(|s| s.to_string())
.ok_or_else(|| "无法获取文件名".to_string())
}
/// 服务接口:在 Finder 中显示文件
pub async fn reveal_in_finder(path: String) -> Result<(), String> {
let path_buf = PathBuf::from(&path);
if !path_buf.exists() {
return Err("文件或目录不存在".to_string());
}
#[cfg(target_os = "macos")]
{
std::process::Command::new("open")
.args(["-R", &path])
.spawn()
.map_err(|e| format!("无法打开 Finder: {e}"))?;
}
#[cfg(target_os = "windows")]
{
std::process::Command::new("explorer")
.args(["/select,", &path])
.spawn()
.map_err(|e| format!("无法打开资源管理器: {}", e))?;
}
#[cfg(target_os = "linux")]
{
// 尝试使用 xdg-open 打开父目录
let parent = path_buf.parent().unwrap_or(&path_buf);
std::process::Command::new("xdg-open")
.arg(parent)
.spawn()
.map_err(|e| format!("无法打开文件管理器: {}", e))?;
}
Ok(())
}
/// 服务接口:使用默认应用打开文件
pub async fn open_with_default_app(path: String) -> Result<(), String> {
let path_buf = PathBuf::from(&path);
if !path_buf.exists() {
return Err("文件不存在".to_string());
}
#[cfg(target_os = "macos")]
{
std::process::Command::new("open")
.arg(&path)
.spawn()
.map_err(|e| format!("无法打开文件: {e}"))?;
}
#[cfg(target_os = "windows")]
{
std::process::Command::new("cmd")
.args(["/C", "start", "", &path])
.spawn()
.map_err(|e| format!("无法打开文件: {}", e))?;
}
#[cfg(target_os = "linux")]
{
std::process::Command::new("xdg-open")
.arg(&path)
.spawn()
.map_err(|e| format!("无法打开文件: {}", e))?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1270,48 +657,6 @@ mod tests {
assert_eq!(error, "目录已存在");
}
#[test]
fn test_normalize_bundle_icon_file_name() {
assert_eq!(
normalize_bundle_icon_file_name(" AppIcon.icns\n"),
Some("AppIcon.icns".to_string())
);
assert_eq!(
normalize_bundle_icon_file_name("../Resources/AppIcon"),
Some("AppIcon".to_string())
);
assert_eq!(normalize_bundle_icon_file_name(" "), None);
}
#[cfg(target_os = "macos")]
#[test]
fn test_resolve_macos_bundle_icon_path_from_plist() {
let temp_dir = tempfile::tempdir().expect("应创建临时目录");
let app_dir = temp_dir.path().join("Demo.app");
let contents_dir = app_dir.join("Contents");
let resources_dir = contents_dir.join("Resources");
fs::create_dir_all(&resources_dir).expect("应创建应用资源目录");
fs::write(resources_dir.join("AppIcon.icns"), []).expect("应创建图标文件");
fs::write(
contents_dir.join("Info.plist"),
r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
</dict>
</plist>
"#,
)
.expect("应写入 Info.plist");
assert_eq!(
resolve_macos_bundle_icon_path(&app_dir, &resources_dir),
Some(resources_dir.join("AppIcon.icns"))
);
}
#[test]
fn test_is_text_file() {
assert!(is_text_file(Some("txt")));
-42
View File
@@ -1234,28 +1234,6 @@ pub fn run() {
commands::site_capability_cmd::site_run_adapter,
commands::site_capability_cmd::site_debug_run_adapter,
commands::site_capability_cmd::site_save_adapter_result,
// API Key Provider commands
commands::api_key_provider_cmd::get_system_provider_catalog,
commands::api_key_provider_cmd::get_api_key_providers,
commands::api_key_provider_cmd::get_api_key_provider,
commands::api_key_provider_cmd::add_custom_api_key_provider,
commands::api_key_provider_cmd::update_api_key_provider,
commands::api_key_provider_cmd::delete_custom_api_key_provider,
commands::api_key_provider_cmd::add_api_key,
commands::api_key_provider_cmd::delete_api_key,
commands::api_key_provider_cmd::toggle_api_key,
commands::api_key_provider_cmd::update_api_key_alias,
commands::api_key_provider_cmd::get_next_api_key,
commands::api_key_provider_cmd::record_api_key_usage,
commands::api_key_provider_cmd::record_api_key_error,
commands::api_key_provider_cmd::get_provider_ui_state,
commands::api_key_provider_cmd::set_provider_ui_state,
commands::api_key_provider_cmd::update_provider_sort_orders,
commands::api_key_provider_cmd::export_api_key_providers,
commands::api_key_provider_cmd::import_api_key_providers,
// API Key Provider connection test command
commands::api_key_provider_cmd::test_api_key_provider_connection,
commands::api_key_provider_cmd::test_api_key_provider_chat,
// Telemetry commands
commands::telemetry_cmd::get_request_logs,
commands::telemetry_cmd::get_request_log_detail,
@@ -1386,7 +1364,6 @@ pub fn run() {
commands::model_registry_cmd::get_provider_alias_config,
commands::model_registry_cmd::get_all_alias_configs,
commands::model_registry_cmd::fetch_provider_models_from_api,
commands::model_registry_cmd::fetch_provider_models_auto,
// WebSocket commands
commands::websocket_cmd::get_websocket_status,
commands::websocket_cmd::get_websocket_connections,
@@ -1418,16 +1395,11 @@ pub fn run() {
crate::services::sysinfo_service::subscribe_sysinfo,
crate::services::sysinfo_service::unsubscribe_sysinfo,
// File browser commands
crate::services::file_browser_service::get_home_dir,
crate::services::file_browser_service::get_file_manager_locations,
crate::services::file_browser_service::get_file_icon_data_url,
crate::services::file_browser_service::create_file,
crate::services::file_browser_service::create_directory,
crate::services::file_browser_service::delete_file,
crate::services::file_browser_service::rename_file,
crate::services::file_browser_service::get_file_name,
crate::services::file_browser_service::reveal_in_finder,
crate::services::file_browser_service::open_with_default_app,
// Webview commands
commands::webview_cmd::create_webview_panel,
commands::webview_cmd::close_webview_panel,
@@ -1671,20 +1643,6 @@ pub fn run() {
crate::voice::commands::cancel_recording,
crate::voice::commands::get_recording_status,
crate::voice::commands::list_audio_devices,
// Automation commands
commands::automation_cmd::get_automation_scheduler_config,
commands::automation_cmd::update_automation_scheduler_config,
commands::automation_cmd::get_automation_status,
commands::automation_cmd::get_automation_jobs,
commands::automation_cmd::get_automation_job,
commands::automation_cmd::create_automation_job,
commands::automation_cmd::update_automation_job,
commands::automation_cmd::delete_automation_job,
commands::automation_cmd::run_automation_job_now,
commands::automation_cmd::get_automation_health,
commands::automation_cmd::get_automation_run_history,
commands::automation_cmd::preview_automation_schedule,
commands::automation_cmd::validate_automation_schedule,
// Telegram 远程触发命令
commands::gateway_channel_cmd::gateway_channel_start,
commands::gateway_channel_cmd::gateway_channel_stop,
+4 -551
View File
@@ -1,556 +1,9 @@
//! API Key Provider Tauri 命令
//! API Key Provider service state.
//!
//! 提供 API Key Provider 管理的前端调用接口
//!
//! **Feature: provider-ui-refactor**
//! **Validates: Requirements 9.1**
//! Provider 管理命令已迁入 App Server `modelProvider/*` current 主链
use crate::database::dao::api_key_provider::{
ApiKeyEntry, ApiKeyProvider, ApiProviderPromptCacheMode, ApiProviderType, ProviderWithKeys,
};
use crate::database::system_providers::get_system_providers;
use crate::database::DbConnection;
use lime_services::api_key_provider_service::{
ApiKeyProviderService, ChatTestResult, ConnectionTestResult, ImportResult,
};
use serde::{Deserialize, Serialize};
use lime_services::api_key_provider_service::ApiKeyProviderService;
use std::sync::Arc;
use tauri::State;
/// API Key Provider 服务状态封装
/// API Key Provider 服务状态封装
pub struct ApiKeyProviderServiceState(pub Arc<ApiKeyProviderService>);
// ============================================================================
// 请求/响应类型
// ============================================================================
/// 添加自定义 Provider 请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddCustomProviderRequest {
pub name: String,
#[serde(rename = "type")]
pub provider_type: String,
pub api_host: String,
pub api_version: Option<String>,
pub project: Option<String>,
pub location: Option<String>,
pub region: Option<String>,
pub prompt_cache_mode: Option<String>,
}
/// 更新 Provider 请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateProviderRequest {
pub name: Option<String>,
/// Provider 类型(系统/自定义 Provider 均可修改)
#[serde(rename = "type")]
pub provider_type: Option<String>,
pub api_host: Option<String>,
pub enabled: Option<bool>,
pub sort_order: Option<i32>,
pub api_version: Option<String>,
pub project: Option<String>,
pub location: Option<String>,
pub region: Option<String>,
pub prompt_cache_mode: Option<String>,
/// 自定义模型列表
pub custom_models: Option<Vec<String>>,
}
/// 添加 API Key 请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddApiKeyRequest {
pub provider_id: String,
pub api_key: String,
pub alias: Option<String>,
#[serde(default)]
pub replace_existing: bool,
}
/// Provider 显示数据(用于前端)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderDisplay {
pub id: String,
pub name: String,
#[serde(rename = "type")]
pub provider_type: String,
pub api_host: String,
pub is_system: bool,
pub group: String,
pub enabled: bool,
pub sort_order: i32,
pub api_version: Option<String>,
pub project: Option<String>,
pub location: Option<String>,
pub region: Option<String>,
/// 自定义模型列表
pub custom_models: Vec<String>,
/// 当前 Provider 声明的 Prompt Cache 模式(前端优先使用该值,不再只按 type 猜)
pub prompt_cache_mode: Option<String>,
pub api_key_count: usize,
pub created_at: String,
pub updated_at: String,
}
/// API Key 显示数据(用于前端,掩码显示)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyDisplay {
pub id: String,
pub provider_id: String,
/// 掩码后的 API Key
pub api_key_masked: String,
pub alias: Option<String>,
pub enabled: bool,
pub usage_count: i64,
pub error_count: i64,
pub last_used_at: Option<String>,
pub created_at: String,
}
/// Provider 完整显示数据(包含 API Keys)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderWithKeysDisplay {
#[serde(flatten)]
pub provider: ProviderDisplay,
pub api_keys: Vec<ApiKeyDisplay>,
}
/// 系统 Provider Catalog 条目(用于前端快速填充)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemProviderCatalogItem {
pub id: String,
pub name: String,
#[serde(rename = "type")]
pub provider_type: String,
pub api_host: String,
pub group: String,
pub sort_order: i32,
pub api_version: Option<String>,
/// 兼容旧版本前端/历史配置的别名 ID
pub legacy_ids: Vec<String>,
}
// ============================================================================
// 辅助函数
// ============================================================================
/// 将 API Key 转换为掩码显示
fn mask_api_key(key: &str) -> String {
let chars: Vec<char> = key.chars().collect();
if chars.len() <= 12 {
"****".to_string()
} else {
let prefix: String = chars[..6].iter().collect();
let suffix: String = chars[chars.len() - 4..].iter().collect();
format!("{prefix}****{suffix}")
}
}
/// 将 ApiKeyProvider 转换为 ProviderDisplay
fn provider_to_display(provider: &ApiKeyProvider, api_key_count: usize) -> ProviderDisplay {
ProviderDisplay {
id: provider.id.clone(),
name: provider.name.clone(),
provider_type: provider.effective_provider_type().to_string(),
api_host: provider.api_host.clone(),
is_system: provider.is_system,
group: provider.group.to_string(),
enabled: provider.enabled,
sort_order: provider.sort_order,
api_version: provider.api_version.clone(),
project: provider.project.clone(),
location: provider.location.clone(),
region: provider.region.clone(),
custom_models: provider.custom_models.clone(),
prompt_cache_mode: provider
.effective_prompt_cache_mode()
.map(|mode| mode.to_string()),
api_key_count,
created_at: provider.created_at.to_rfc3339(),
updated_at: provider.updated_at.to_rfc3339(),
}
}
/// 将 ApiKeyEntry 转换为 ApiKeyDisplay(需要解密后掩码)
fn api_key_to_display(key: &ApiKeyEntry, service: &ApiKeyProviderService) -> ApiKeyDisplay {
// 解密后掩码显示
let masked = match service.decrypt_api_key(&key.api_key_encrypted) {
Ok(decrypted) => mask_api_key(&decrypted),
Err(_) => "****".to_string(),
};
ApiKeyDisplay {
id: key.id.clone(),
provider_id: key.provider_id.clone(),
api_key_masked: masked,
alias: key.alias.clone(),
enabled: key.enabled,
usage_count: key.usage_count,
error_count: key.error_count,
last_used_at: key.last_used_at.map(|t| t.to_rfc3339()),
created_at: key.created_at.to_rfc3339(),
}
}
/// 将 ProviderWithKeys 转换为 ProviderWithKeysDisplay
fn provider_with_keys_to_display(
pwk: &ProviderWithKeys,
service: &ApiKeyProviderService,
) -> ProviderWithKeysDisplay {
let api_keys: Vec<ApiKeyDisplay> = pwk
.api_keys
.iter()
.map(|k| api_key_to_display(k, service))
.collect();
ProviderWithKeysDisplay {
provider: provider_to_display(&pwk.provider, pwk.api_keys.len()),
api_keys,
}
}
/// 为系统 Provider 提供兼容旧版本的别名 ID
fn get_legacy_ids(provider_id: &str) -> Vec<String> {
match provider_id {
"lime-hub" => vec![format!("{}{}", "lobe", "hub")],
"google" => vec!["gemini".to_string()],
"zhipuai" => vec!["zhipu".to_string()],
"alibaba" => vec!["dashscope".to_string(), "qwen".to_string()],
"moonshotai" => vec!["moonshot".to_string()],
"xai" => vec!["grok".to_string()],
"github-models" => vec!["github".to_string()],
"github-copilot" => vec!["copilot".to_string()],
"google-vertex" => vec!["vertexai".to_string()],
"azure-openai" => vec!["azure".to_string()],
"amazon-bedrock" => vec!["aws-bedrock".to_string(), "bedrock".to_string()],
"togetherai" => vec!["together".to_string()],
"fireworks-ai" => vec!["fireworks".to_string(), "fireworksai".to_string()],
"xiaomi" => vec!["mimo".to_string(), "xiaomimimo".to_string()],
"siliconflow" => vec!["silicon".to_string(), "siliconcloud".to_string()],
"302ai" => vec!["ai302".to_string()],
"new-api" => vec!["newapi".to_string()],
"vercel-gateway" => vec!["vercelaigateway".to_string()],
"fal" => vec!["falai".to_string()],
"yi" => vec!["zeroone".to_string()],
"infini" => vec!["infiniai".to_string()],
"doubao" => vec!["volcengine".to_string()],
"airgate-openai-images" => vec!["airgate".to_string(), "k8ray".to_string()],
"baidu-cloud" => vec!["wenxin".to_string()],
"tencent-cloud-ti" => vec!["tencentcloud".to_string()],
_ => vec![],
}
}
fn system_provider_to_catalog_item(
provider: crate::database::system_providers::SystemProviderDef,
) -> SystemProviderCatalogItem {
SystemProviderCatalogItem {
id: provider.id.to_string(),
name: provider.name.to_string(),
provider_type: provider.provider_type.to_string(),
api_host: provider.api_host.to_string(),
group: provider.group.to_string(),
sort_order: provider.sort_order,
api_version: provider.api_version.map(|v| v.to_string()),
legacy_ids: get_legacy_ids(provider.id),
}
}
// ============================================================================
// Tauri 命令
// ============================================================================
/// 获取系统 Provider Catalog
///
/// 用于前端动态构建 Provider 列表,避免前后端维护两份静态清单。
#[tauri::command]
pub fn get_system_provider_catalog() -> Result<Vec<SystemProviderCatalogItem>, String> {
Ok(get_system_providers()
.into_iter()
.map(system_provider_to_catalog_item)
.collect())
}
/// 获取所有 API Key Provider(包含 API Keys
#[tauri::command]
pub fn get_api_key_providers(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
) -> Result<Vec<ProviderWithKeysDisplay>, String> {
let providers = service.0.get_all_providers(&db)?;
Ok(providers
.iter()
.map(|p| provider_with_keys_to_display(p, &service.0))
.collect())
}
/// 获取单个 API Key Provider(包含 API Keys
#[tauri::command]
pub fn get_api_key_provider(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
id: String,
) -> Result<Option<ProviderWithKeysDisplay>, String> {
let provider = service.0.get_provider(&db, &id)?;
Ok(provider.map(|p| provider_with_keys_to_display(&p, &service.0)))
}
/// 添加自定义 Provider
#[tauri::command]
pub fn add_custom_api_key_provider(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
request: AddCustomProviderRequest,
) -> Result<ProviderDisplay, String> {
let provider_type: ApiProviderType = request
.provider_type
.parse()
.map_err(|e: String| format!("无效的 Provider 类型: {e}"))?;
let provider = service.0.add_custom_provider(
&db,
request.name,
provider_type,
request.api_host,
request.api_version,
request.project,
request.location,
request.region,
request
.prompt_cache_mode
.map(|mode| mode.parse::<ApiProviderPromptCacheMode>())
.transpose()
.map_err(|e: String| format!("无效的 Prompt Cache 模式: {e}"))?,
)?;
Ok(provider_to_display(&provider, 0))
}
/// 更新 Provider 配置
#[tauri::command]
pub fn update_api_key_provider(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
id: String,
request: UpdateProviderRequest,
) -> Result<ProviderDisplay, String> {
// 解析 provider_type(如果提供)
let provider_type: Option<ApiProviderType> = request
.provider_type
.map(|t| t.parse())
.transpose()
.map_err(|e: String| format!("无效的 Provider 类型: {e}"))?;
let provider = service.0.update_provider(
&db,
&id,
request.name,
provider_type,
request.api_host,
request.enabled,
request.sort_order,
request.api_version,
request.project,
request.location,
request.region,
request
.prompt_cache_mode
.map(|mode| mode.parse::<ApiProviderPromptCacheMode>())
.transpose()
.map_err(|e: String| format!("无效的 Prompt Cache 模式: {e}"))?,
request.custom_models,
)?;
// 获取 API Key 数量
let full_provider = service.0.get_provider(&db, &id)?;
let api_key_count = full_provider.map(|p| p.api_keys.len()).unwrap_or(0);
Ok(provider_to_display(&provider, api_key_count))
}
/// 删除自定义 Provider
#[tauri::command]
pub fn delete_custom_api_key_provider(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
id: String,
) -> Result<bool, String> {
service.0.delete_custom_provider(&db, &id)
}
/// 添加 API Key
#[tauri::command]
pub fn add_api_key(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
request: AddApiKeyRequest,
) -> Result<ApiKeyDisplay, String> {
let key = service.0.add_api_key(
&db,
&request.provider_id,
&request.api_key,
request.alias,
request.replace_existing,
)?;
Ok(api_key_to_display(&key, &service.0))
}
/// 删除 API Key
#[tauri::command]
pub fn delete_api_key(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
key_id: String,
) -> Result<bool, String> {
service.0.delete_api_key(&db, &key_id)
}
/// 切换 API Key 启用状态
#[tauri::command]
pub fn toggle_api_key(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
key_id: String,
enabled: bool,
) -> Result<ApiKeyDisplay, String> {
let key = service.0.toggle_api_key(&db, &key_id, enabled)?;
Ok(api_key_to_display(&key, &service.0))
}
/// 更新 API Key 别名
#[tauri::command]
pub fn update_api_key_alias(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
key_id: String,
alias: Option<String>,
) -> Result<ApiKeyDisplay, String> {
let key = service.0.update_api_key_alias(&db, &key_id, alias)?;
Ok(api_key_to_display(&key, &service.0))
}
/// 获取下一个可用的 API Key(用于 API 调用)
#[tauri::command]
pub fn get_next_api_key(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
provider_id: String,
) -> Result<Option<String>, String> {
service.0.get_next_api_key(&db, &provider_id)
}
/// 记录 API Key 使用
#[tauri::command]
pub fn record_api_key_usage(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
key_id: String,
) -> Result<(), String> {
service.0.record_usage(&db, &key_id)
}
/// 记录 API Key 错误
#[tauri::command]
pub fn record_api_key_error(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
key_id: String,
) -> Result<(), String> {
service.0.record_error(&db, &key_id)
}
/// 获取 UI 状态
#[tauri::command]
pub fn get_provider_ui_state(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
key: String,
) -> Result<Option<String>, String> {
service.0.get_ui_state(&db, &key)
}
/// 设置 UI 状态
#[tauri::command]
pub fn set_provider_ui_state(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
key: String,
value: String,
) -> Result<(), String> {
service.0.set_ui_state(&db, &key, &value)
}
/// 批量更新 Provider 排序顺序
/// **Validates: Requirements 8.4**
#[tauri::command]
pub fn update_provider_sort_orders(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
sort_orders: Vec<(String, i32)>,
) -> Result<(), String> {
service.0.update_provider_sort_orders(&db, sort_orders)
}
/// 导出 Provider 配置
#[tauri::command]
pub fn export_api_key_providers(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
include_keys: bool,
) -> Result<String, String> {
let config = service.0.export_config(&db, include_keys)?;
serde_json::to_string_pretty(&config).map_err(|e| format!("序列化失败: {e}"))
}
/// 导入 Provider 配置
#[tauri::command]
pub fn import_api_key_providers(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
config_json: String,
) -> Result<ImportResult, String> {
service.0.import_config(&db, &config_json)
}
// ============================================================================
// 连接测试命令
// ============================================================================
/// 测试 API Key Provider 连接
///
/// 方案 C 实现:
/// 1. 默认使用 /v1/models 端点测试
/// 2. 如果提供了 model_name,用该模型发送简单请求
///
/// # 参数
/// - `provider_id`: Provider ID
/// - `model_name`: 可选的模型名称,用于发送测试请求
#[tauri::command]
pub async fn test_api_key_provider_connection(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
_model_registry_state: State<'_, crate::commands::model_registry_cmd::ModelRegistryState>,
provider_id: String,
model_name: Option<String>,
) -> Result<ConnectionTestResult, String> {
service
.0
.test_connection_with_fallback_models(&db, &provider_id, model_name, Vec::new())
.await
}
#[tauri::command]
pub async fn test_api_key_provider_chat(
db: State<'_, DbConnection>,
service: State<'_, ApiKeyProviderServiceState>,
_model_registry_state: State<'_, crate::commands::model_registry_cmd::ModelRegistryState>,
provider_id: String,
model_name: Option<String>,
prompt: String,
) -> Result<ChatTestResult, String> {
service
.0
.test_chat_with_fallback_models(&db, &provider_id, model_name, prompt, Vec::new())
.await
}
-245
View File
@@ -1,245 +0,0 @@
//! 自动化任务命令
use crate::app::AppState;
use crate::config::GlobalConfigManagerState;
use crate::database::{lock_db, DbConnection};
use crate::services::automation_service::health::{
query_automation_health, AutomationHealthQuery, AutomationHealthResult,
};
use crate::services::automation_service::schedule::{
preview_next_run as preview_next_run_for_schedule, validate_schedule as validate_schedule_value,
};
use crate::services::automation_service::{
AutomationCycleResult, AutomationJobDraft, AutomationJobRecord, AutomationJobUpdate,
AutomationPayload, AutomationServiceState, AutomationStatus,
};
use lime_core::config::{AutomationExecutionMode, DeliveryConfig, TaskSchedule};
use lime_core::database::dao::agent_run::AgentRun;
use serde::{Deserialize, Serialize};
use tauri::{AppHandle, State};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutomationSchedulerConfigResponse {
pub enabled: bool,
pub poll_interval_secs: u64,
pub enable_history: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutomationJobRequest {
pub name: String,
pub description: Option<String>,
pub enabled: Option<bool>,
pub workspace_id: String,
pub execution_mode: Option<AutomationExecutionMode>,
pub schedule: TaskSchedule,
pub payload: AutomationPayload,
pub delivery: Option<DeliveryConfig>,
pub timeout_secs: Option<u64>,
pub max_retries: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct UpdateAutomationJobRequest {
pub name: Option<String>,
pub description: Option<String>,
pub enabled: Option<bool>,
pub workspace_id: Option<String>,
pub execution_mode: Option<AutomationExecutionMode>,
pub schedule: Option<TaskSchedule>,
pub payload: Option<AutomationPayload>,
pub delivery: Option<DeliveryConfig>,
pub timeout_secs: Option<u64>,
pub clear_timeout_secs: Option<bool>,
pub max_retries: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScheduleValidationResult {
pub valid: bool,
pub error: Option<String>,
}
#[tauri::command]
pub async fn get_automation_scheduler_config(
config_manager: State<'_, GlobalConfigManagerState>,
) -> Result<AutomationSchedulerConfigResponse, String> {
let config = config_manager.0.config();
Ok(AutomationSchedulerConfigResponse {
enabled: config.automation.enabled,
poll_interval_secs: config.automation.poll_interval_secs,
enable_history: config.automation.enable_history,
})
}
#[tauri::command]
pub async fn update_automation_scheduler_config(
state: State<'_, AppState>,
config_manager: State<'_, GlobalConfigManagerState>,
automation_state: State<'_, AutomationServiceState>,
config: AutomationSchedulerConfigResponse,
app: AppHandle,
) -> Result<(), String> {
let was_enabled = {
let state = state.read().await;
state.config.automation.enabled
};
let next_config = {
let mut state = state.write().await;
state.config.automation.enabled = config.enabled;
state.config.automation.poll_interval_secs = config.poll_interval_secs.max(5);
state.config.automation.enable_history = config.enable_history;
state.config.clone()
};
config_manager.0.save_config(&next_config).await?;
let new_config = next_config.automation.clone();
let mut service = automation_state.0.write().await;
service.update_config(new_config);
service.set_app_handle(app);
let self_ref = automation_state.0.clone();
if config.enabled && !was_enabled {
service.start(self_ref).await?;
} else if !config.enabled && was_enabled {
service.stop().await?;
}
Ok(())
}
#[tauri::command]
pub async fn get_automation_status(
automation_state: State<'_, AutomationServiceState>,
) -> Result<AutomationStatus, String> {
Ok(automation_state.1.read().clone())
}
#[tauri::command]
pub async fn get_automation_jobs(
automation_state: State<'_, AutomationServiceState>,
) -> Result<Vec<AutomationJobRecord>, String> {
let service = automation_state.0.read().await;
service.list_jobs()
}
#[tauri::command]
pub async fn get_automation_job(
automation_state: State<'_, AutomationServiceState>,
id: String,
) -> Result<Option<AutomationJobRecord>, String> {
let service = automation_state.0.read().await;
service.get_job(id.trim())
}
#[tauri::command]
pub async fn create_automation_job(
automation_state: State<'_, AutomationServiceState>,
request: AutomationJobRequest,
) -> Result<AutomationJobRecord, String> {
let service = automation_state.0.read().await;
service.create_job(AutomationJobDraft {
name: request.name,
description: request.description,
enabled: request.enabled.unwrap_or(true),
workspace_id: request.workspace_id,
execution_mode: request
.execution_mode
.unwrap_or(AutomationExecutionMode::Intelligent),
schedule: request.schedule,
payload: request.payload,
delivery: request.delivery.unwrap_or_default(),
timeout_secs: request.timeout_secs,
max_retries: request.max_retries.unwrap_or(3),
})
}
#[tauri::command]
pub async fn update_automation_job(
automation_state: State<'_, AutomationServiceState>,
id: String,
request: UpdateAutomationJobRequest,
) -> Result<AutomationJobRecord, String> {
let service = automation_state.0.read().await;
service.update_job(
id.trim(),
AutomationJobUpdate {
name: request.name,
description: request.description,
enabled: request.enabled,
workspace_id: request.workspace_id,
execution_mode: request.execution_mode,
schedule: request.schedule,
payload: request.payload,
delivery: request.delivery,
timeout_secs: if request.clear_timeout_secs.unwrap_or(false) {
Some(None)
} else {
request.timeout_secs.map(Some)
},
max_retries: request.max_retries,
},
)
}
#[tauri::command]
pub async fn delete_automation_job(
automation_state: State<'_, AutomationServiceState>,
id: String,
) -> Result<bool, String> {
let service = automation_state.0.read().await;
service.delete_job(id.trim())
}
#[tauri::command]
pub async fn run_automation_job_now(
automation_state: State<'_, AutomationServiceState>,
id: String,
) -> Result<AutomationCycleResult, String> {
let service = automation_state.0.read().await;
service.run_job_now(id.trim()).await
}
#[tauri::command]
pub async fn get_automation_health(
db: State<'_, DbConnection>,
query: Option<AutomationHealthQuery>,
) -> Result<AutomationHealthResult, String> {
query_automation_health(db.inner(), query)
}
#[tauri::command]
pub async fn get_automation_run_history(
db: State<'_, DbConnection>,
id: String,
limit: Option<usize>,
) -> Result<Vec<AgentRun>, String> {
let conn = lock_db(db.inner())?;
lime_core::database::dao::agent_run::AgentRunDao::list_runs_by_source_ref(
&conn,
"automation",
id.trim(),
limit.unwrap_or(20),
)
.map_err(|e| format!("查询自动化运行历史失败: {e}"))
}
#[tauri::command]
pub async fn preview_automation_schedule(schedule: TaskSchedule) -> Result<Option<String>, String> {
preview_next_run_for_schedule(&schedule).map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn validate_automation_schedule(
schedule: TaskSchedule,
) -> Result<ScheduleValidationResult, String> {
match validate_schedule_value(&schedule, chrono::Utc::now()) {
Ok(()) => Ok(ScheduleValidationResult {
valid: true,
error: None,
}),
Err(error) => Ok(ScheduleValidationResult {
valid: false,
error: Some(error.to_string()),
}),
}
}
-1
View File
@@ -5,7 +5,6 @@ pub mod agent_cmd;
pub mod api_key_provider_cmd;
pub mod asr_cmd;
pub mod aster_agent_cmd;
pub mod automation_cmd;
pub mod auxiliary_model_selection;
pub mod browser_connector_cmd;
pub mod browser_environment_cmd;
+1 -82
View File
@@ -28,7 +28,7 @@ pub async fn get_model_registry(
/// 获取模型注册表中所有 provider_id(去重且有序)。
///
/// 本地模型目录已下线,Provider 元信息改由 `get_system_provider_catalog`
/// 本地模型目录已下线,Provider 元信息改由 App Server `modelProvider/catalog/list`
/// 和用户配置提供;这里保留命令兼容并返回空集合。
#[tauri::command]
pub async fn get_model_registry_provider_ids(
@@ -217,84 +217,3 @@ pub async fn fetch_provider_models_from_api(
.fetch_models_from_api(&provider_id, &api_host, &api_key)
.await
}
/// 从 Provider API 获取模型列表(自动获取 API Key)
///
/// 自动从数据库获取 Provider 的 API Key,然后调用 /v1/models 端点
///
/// # 参数
/// - `provider_id`: Provider ID(如 "siliconflow", "openai"
#[tauri::command]
pub async fn fetch_provider_models_auto(
state: State<'_, ModelRegistryState>,
db: tauri::State<'_, crate::database::DbConnection>,
api_key_service: tauri::State<
'_,
crate::commands::api_key_provider_cmd::ApiKeyProviderServiceState,
>,
provider_id: String,
) -> Result<FetchModelsResult, String> {
// 获取 Provider 信息
let provider = api_key_service
.0
.get_provider(&db, &provider_id)?
.ok_or_else(|| format!("Provider 不存在: {provider_id}"))?;
// 获取 API Host
let api_host = provider.provider.api_host.clone();
if api_host.is_empty() {
return Err("Provider 没有配置 API Host".to_string());
}
let provider_type = provider.provider.effective_provider_type();
{
let guard = state.read().await;
let service = guard
.as_ref()
.ok_or_else(|| "模型注册服务未初始化".to_string())?;
match service.get_cached_provider_models(&provider_id, &api_host, Some(provider_type)) {
Ok(Some(cached)) => return Ok(cached),
Ok(None) => {}
Err(error) => {
tracing::warn!(
"[ModelRegistry] 读取 Provider 模型缓存失败,继续获取 API Key: {error}"
);
}
}
}
let requires_api_key = ModelRegistryService::requires_api_key_for_model_fetch(
&provider_id,
&api_host,
provider_type,
);
// 获取 API Key(支持本地免 Key 渠道)
let api_key = if requires_api_key {
api_key_service
.0
.get_next_api_key(&db, &provider_id)?
.ok_or_else(|| format!("Provider {provider_id} 没有可用的 API Key"))?
} else {
api_key_service
.0
.get_next_api_key(&db, &provider_id)?
.unwrap_or_default()
};
let guard = state.read().await;
let service = guard
.as_ref()
.ok_or_else(|| "模型注册服务未初始化".to_string())?;
service
.fetch_models_from_api_with_hints(
&provider_id,
&api_host,
&api_key,
Some(provider_type),
&provider.provider.custom_models,
)
.await
}
+22 -132
View File
@@ -5,7 +5,6 @@
mod agent_apps;
mod agent_sessions;
mod app_runtime;
mod automation;
mod browser;
mod capability_drafts;
mod channels;
@@ -105,10 +104,6 @@ pub async fn handle_command(
return Ok(result);
}
if let Some(result) = automation::try_handle(state, cmd, args.as_ref()).await? {
return Ok(result);
}
if let Some(result) = logs::try_handle(state, cmd, args.as_ref()).await? {
return Ok(result);
}
@@ -863,23 +858,6 @@ mod tests {
assert!(error.to_string().contains("Dev Bridge 未持有 AppHandle"));
}
#[tokio::test]
async fn fetch_provider_models_auto_is_bridged() {
let state = make_test_state();
let error = handle_command(
&state,
"fetch_provider_models_auto",
Some(serde_json::json!({
"providerId": "ollama"
})),
)
.await
.expect_err("missing provider should fail after bridge routing");
assert!(error.to_string().contains("Provider 不存在: ollama"));
}
#[tokio::test]
async fn get_provider_alias_config_is_bridged() {
let state = make_test_state();
@@ -898,123 +876,35 @@ mod tests {
}
#[tokio::test]
async fn api_key_provider_write_flow_is_bridged() {
async fn retired_api_key_provider_commands_are_not_dev_bridge_facades() {
let state = make_test_state();
let created = handle_command(
&state,
"add_custom_api_key_provider",
Some(serde_json::json!({
"request": {
"name": "SenseNova E2E",
"type": "openai",
"api_host": "https://api.sensenova.cn/compatible-mode/v2"
}
})),
)
.await
.expect("custom provider should be created through bridge");
let provider_id = created["id"].as_str().unwrap().to_string();
handle_command(
&state,
"update_api_key_provider",
Some(serde_json::json!({
"id": provider_id,
"request": {
"enabled": true,
"custom_models": ["SenseChat-5"]
}
})),
)
.await
.expect("custom provider should be updated through bridge");
let key = handle_command(
&state,
"add_api_key",
Some(serde_json::json!({
"request": {
"provider_id": provider_id,
"api_key": "sk-bridge-test",
"alias": "bridge"
}
})),
)
.await
.expect("api key should be added through bridge");
assert_eq!(key["provider_id"], created["id"]);
assert_ne!(key["api_key_masked"], "sk-bridge-test");
let provider = handle_command(
&state,
for command in [
"get_api_key_provider",
Some(serde_json::json!({
"id": created["id"].as_str().unwrap()
})),
)
.await
.expect("custom provider should be readable through bridge");
assert_eq!(provider["id"], created["id"]);
assert_eq!(
provider["api_host"],
"https://api.sensenova.cn/compatible-mode/v2"
);
assert_eq!(provider["custom_models"][0], "SenseChat-5");
assert_eq!(provider["api_keys"].as_array().unwrap().len(), 1);
let deleted = handle_command(
&state,
"add_custom_api_key_provider",
"update_api_key_provider",
"delete_custom_api_key_provider",
Some(serde_json::json!({
"id": created["id"].as_str().unwrap()
})),
)
.await
.expect("custom provider should be deleted through bridge");
assert_eq!(deleted, serde_json::json!(true));
}
#[tokio::test]
async fn test_api_key_provider_connection_is_bridged() {
let state = make_test_state();
let error = handle_command(
&state,
"add_api_key",
"delete_api_key",
"toggle_api_key",
"update_api_key_alias",
"get_next_api_key",
"record_api_key_usage",
"record_api_key_error",
"get_provider_ui_state",
"set_provider_ui_state",
"update_provider_sort_orders",
"export_api_key_providers",
"import_api_key_providers",
"test_api_key_provider_connection",
Some(serde_json::json!({
"providerId": "missing-provider"
})),
)
.await
.expect_err("missing provider should fail after bridge routing");
assert!(error
.to_string()
.contains("Provider 不存在: missing-provider"));
}
#[tokio::test]
async fn test_api_key_provider_chat_is_bridged() {
let state = make_test_state();
let error = handle_command(
&state,
"test_api_key_provider_chat",
Some(serde_json::json!({
"providerId": "missing-provider",
"prompt": "hello"
})),
)
.await
.expect_err("missing provider should fail after bridge routing");
] {
let error = handle_command(&state, command, Some(serde_json::json!({})))
.await
.expect_err("retired API Key Provider command should be unknown");
assert!(error
.to_string()
.contains("Provider 不存在: missing-provider"));
assert!(error.to_string().contains("[DevBridge] 未知命令"));
}
}
#[tokio::test]
@@ -54,31 +54,6 @@ pub async fn try_handle(
crate::commands::agent_app_cmd::agent_app_uninstall(request).await?,
)?
}
"agent_app_start_ui_runtime" => {
let request = parse_nested_arg(&args_or_default(args), "request")?;
let config = { state.server.read().await.config.clone() };
serde_json::to_value(
crate::commands::agent_app_cmd::agent_app_start_ui_runtime_for_dev_bridge(
request,
&config,
state.db.as_ref(),
Some(state.api_key_provider_service.as_ref()),
)
.await?,
)?
}
"agent_app_get_ui_runtime_status" => {
let request = parse_nested_arg(&args_or_default(args), "request")?;
serde_json::to_value(
crate::commands::agent_app_cmd::agent_app_get_ui_runtime_status(request).await?,
)?
}
"agent_app_stop_ui_runtime" => {
let request = parse_nested_arg(&args_or_default(args), "request")?;
serde_json::to_value(
crate::commands::agent_app_cmd::agent_app_stop_ui_runtime(request).await?,
)?
}
"agent_app_select_directory" => {
let request = parse_optional_nested_arg(&args_or_default(args), "request")?;
let app_handle = require_app_handle(state)?;
@@ -106,90 +81,6 @@ pub async fn try_handle(
.await?,
)?
}
"agent_app_runtime_start_task" => {
let request = parse_nested_arg(&args_or_default(args), "request")?;
let app_handle = require_app_handle(state)?;
let aster_state = app_handle.state::<crate::agent::AsterAgentState>();
let db = app_handle.state::<crate::database::DbConnection>();
let api_key_provider_service =
app_handle
.state::<crate::commands::api_key_provider_cmd::ApiKeyProviderServiceState>();
let logs = app_handle.state::<crate::app::LogState>();
let config_manager = app_handle.state::<crate::config::GlobalConfigManagerState>();
let mcp_manager = app_handle.state::<crate::mcp::McpManagerState>();
let automation_state =
app_handle.state::<crate::services::automation_service::AutomationServiceState>();
serde_json::to_value(
crate::commands::agent_app_runtime_cmd::start_task::agent_app_runtime_start_task(
app_handle.clone(),
aster_state,
db,
api_key_provider_service,
logs,
config_manager,
mcp_manager,
automation_state,
request,
)
.await?,
)?
}
"agent_app_runtime_cancel_task" => {
let request = parse_nested_arg(&args_or_default(args), "request")?;
let app_handle = require_app_handle(state)?;
let aster_state = app_handle.state::<crate::agent::AsterAgentState>();
serde_json::to_value(
crate::commands::agent_app_runtime_cmd::cancel_task::agent_app_runtime_cancel_task(
app_handle.clone(),
aster_state,
request,
)
.await?,
)?
}
"agent_app_runtime_get_task" => {
let request = parse_nested_arg(&args_or_default(args), "request")?;
let app_handle = require_app_handle(state)?;
let aster_state = app_handle.state::<crate::agent::AsterAgentState>();
let db = app_handle.state::<crate::database::DbConnection>();
let api_key_provider_service =
app_handle
.state::<crate::commands::api_key_provider_cmd::ApiKeyProviderServiceState>();
let logs = app_handle.state::<crate::app::LogState>();
let config_manager = app_handle.state::<crate::config::GlobalConfigManagerState>();
let mcp_manager = app_handle.state::<crate::mcp::McpManagerState>();
let automation_state =
app_handle.state::<crate::services::automation_service::AutomationServiceState>();
serde_json::to_value(
crate::commands::agent_app_runtime_cmd::task_snapshot::agent_app_runtime_get_task(
app_handle.clone(),
aster_state,
db,
api_key_provider_service,
logs,
config_manager,
mcp_manager,
automation_state,
request,
)
.await?,
)?
}
"agent_app_runtime_submit_host_response" => {
let request = parse_nested_arg(&args_or_default(args), "request")?;
let app_handle = require_app_handle(state)?;
let aster_state = app_handle.state::<crate::agent::AsterAgentState>();
let db = app_handle.state::<crate::database::DbConnection>();
serde_json::to_value(
crate::commands::agent_app_runtime_cmd::host_response::agent_app_runtime_submit_host_response(
app_handle.clone(),
aster_state,
db,
request,
)
.await?,
)?
}
_ => return Ok(None),
};
@@ -87,7 +87,6 @@ pub(super) async fn try_handle(
| "agent_runtime_diff_file_checkpoint"
| "agent_runtime_restore_file_checkpoint"
| "agent_runtime_get_tool_inventory"
| "agent_runtime_list_workspace_skill_bindings"
| "agent_runtime_replay_request"
| "agent_runtime_update_session"
| "agent_runtime_delete_session"
@@ -658,18 +657,6 @@ pub(super) async fn try_handle(
.await?,
)?
}
"agent_runtime_list_workspace_skill_bindings" => {
let request = parse_request::<
crate::commands::aster_agent_cmd::AgentRuntimeListWorkspaceSkillBindingsRequest,
>(args)?;
serde_json::to_value(
crate::commands::aster_agent_cmd::agent_runtime_list_workspace_skill_bindings(
request,
)
.await?,
)?
}
"agent_runtime_replay_request" => {
let request = parse_request::<
crate::commands::aster_agent_cmd::AgentRuntimeReplayRequestRequest,
@@ -36,15 +36,6 @@ pub(super) async fn try_handle(
.await;
serde_json::to_value(preview)?
}
"get_default_provider" => {
let default_provider_ref = { state.server.read().await.default_provider_ref.clone() };
let provider = default_provider_ref.read().await.clone();
serde_json::json!(provider)
}
"get_endpoint_providers" => {
let providers = { state.server.read().await.config.endpoint_providers.clone() };
serde_json::to_value(providers)?
}
"get_server_diagnostics" => {
let (status, capability_routing, response_cache, request_dedup, idempotency) = {
let server = state.server.read().await;

Some files were not shown because too many files have changed in this diff Show More