mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-01 15:30:52 +08:00
Release v1.66.0
This commit is contained in:
@@ -123,6 +123,12 @@ internal/roadmap/i18n/**
|
||||
!internal/roadmap/agentapp/
|
||||
internal/roadmap/agentapp/**
|
||||
!internal/roadmap/agentapp/*.md
|
||||
!internal/roadmap/agentworkbench/
|
||||
internal/roadmap/agentworkbench/**
|
||||
!internal/roadmap/agentworkbench/*.md
|
||||
!internal/roadmap/subagents/
|
||||
internal/roadmap/subagents/**
|
||||
!internal/roadmap/subagents/*.md
|
||||
!internal/roadmap/zuanjia/
|
||||
internal/roadmap/zuanjia/**
|
||||
!internal/roadmap/zuanjia/*.md
|
||||
|
||||
+23
-21
@@ -1,42 +1,44 @@
|
||||
## Lime v1.65.0
|
||||
## Lime v1.66.0
|
||||
|
||||
<sub>The Simplified Chinese release notes are the primary version. This English page is a companion for international readers.</sub>
|
||||
|
||||
### New Features
|
||||
|
||||
- `lime media video generate` now uses the current media runtime to create and execute video-generation tasks, resolve service endpoint/API key from local config or environment variables, and persist progress, error, and artifact state.
|
||||
- `@limecloud/agent-runtime-projection` now exposes dedicated builders for artifact, context, conversation, diagnostic, hydration, lifecycle, permission, plan approval, queue, thread item, and tool events.
|
||||
- Agent Chat projections continue to consume the shared projection package for runtime state, queue, permissions, history hydration, tool timeline, thread items, and context-event read model semantics.
|
||||
- Agent App runtime now connects to the current App Server client and capability APIs, allowing standalone Agent Apps to reuse the JSON-RPC client, capability host, and runtime projection path.
|
||||
- Agent Runtime standard packages now include App Server facts, fixture replay, subagents, refs, and validation support for shared runtime, projection, and UI consumption.
|
||||
- App Server workspace protocol now exposes project-summary reads, with matching Rust client, npm `app-server-client`, and frontend project API updates.
|
||||
- The Agent input bar can read and surface project context so workspace project summaries can participate in the main chat orchestration path.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fixed the video-generation skill recommendation so it points at the current `lime media video generate --prompt "..." --aspect-ratio 9:16` entrypoint.
|
||||
- Fixed governance wording that could still treat old `lime-rs/src/**` files as current Rust owners; restoring old Tauri wrappers, stubs, or legacy facades is now explicitly guarded as backflow.
|
||||
- Removed duplicate voice settings dependencies on ASR credentials and shortcut runtime state, reducing old command-surface coupling and making the current voice model install state the default source for model status.
|
||||
- Fixed Agent UI projection summary and subagents read-model naming so old Team Workbench terminology no longer leaks into the current model.
|
||||
- Fixed Agent App runtime page and projection bridge wiring for the current capability host / client APIs, reducing drift between standalone apps and the desktop host.
|
||||
- Fixed several state-sync issues around the input bar, tool display, workspace send runtime, and thread grouping.
|
||||
- Fixed DevBridge command policy and legacy surface catalog classification for retired command surfaces.
|
||||
|
||||
### Improvements And Refactors
|
||||
|
||||
- Physically removed the old orphaned `lime-rs/src/**` directory, including old Tauri command, service, dev_bridge, and runner implementations; Rust backend ownership is now centered on `lime-rs/crates/**`.
|
||||
- Moved agent tools catalog, execution, and inventory into `lime-rs/crates/agent`; the old path now exists only as deletion history, not as a runtime owner.
|
||||
- Split video-generation CLI logic into `lime-rs/crates/lime-cli/src/video.rs` and moved video task execution into `lime-rs/crates/media-runtime/src/video_worker.rs`.
|
||||
- Reduced duplicated Agent Chat frontend projection state machines by moving reusable event semantics into the npm projection package.
|
||||
- Simplified the voice and hotkey settings boundary: the voice page focuses on models, instructions, and preferences, while the hotkey page owns global shortcut configuration and validation.
|
||||
- Converged the Agent Chat workbench path from old Team Workspace components, selectors, canvas runtime, and suggestion helpers onto the current subagents / workbench presentation, deleting a large amount of retired team-workspace UI surface.
|
||||
- Split `AppSidebar` into account, appearance, invite, search, session, navigation-target, and style modules, reducing single-file complexity.
|
||||
- Removed Companion API, settings card, provider overview, desktop mock, and sidebar-entry remnants from the current settings and provider surfaces.
|
||||
- Continued moving input-bar project context, team preference, project storage, and workspace selection logic into hooks and focused helpers.
|
||||
- Expanded the Agent Runtime / Agent UI npm packages with standard contracts, fixtures, projection helpers, runtime facts, and UI exports to reduce duplicated GUI and SDK implementation.
|
||||
|
||||
### Tests And Quality
|
||||
|
||||
- Added Agent Runtime projection unit coverage for artifact, context, diagnostic, hydration, lifecycle, permission, routing, and turn context events, and expanded the main projection regression suite.
|
||||
- Updated App Server client contracts, Harness contracts, Rust current-boundary guards, legacy tool permission guards, and Electron current rules guards to prevent old paths from becoming fact sources again.
|
||||
- Expanded regressions for the input bar, Markdown rendering, Agent Chat home surface, voice settings, hotkey settings, media tasks, gallery materials, session images, and video diagnostics.
|
||||
- Updated the root app, Rust workspace, CLI npm package, Agent App runtime package, App Server client package, Agent Runtime client dependency, and lockfiles to `1.65.0`.
|
||||
- Expanded App Server protocol catalog, workspace project API, npm `app-server-client`, Agent Runtime client, projection, UI contracts, and fixture replay regressions.
|
||||
- Updated AppSidebar, Agent Chat input bar, workspace scene, workspace send, settings v2, Agent App runtime page, and i18n resource tests.
|
||||
- Updated Electron SDK fixture smoke, tool-surface smoke, command-contract checks, quality-task planning, and i18n readiness reporting.
|
||||
- Updated the root app, Rust workspace, CLI npm package, Agent App runtime package, App Server client package, Agent Runtime client dependency, and lockfiles to `1.66.0`.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Updated AGENTS, quality workflow, governance, command-boundary, and parallel-collaboration docs to record the June 10, 2026 `lime-rs/src/**` deletion and the directory-level dead-surface decision rule.
|
||||
- Updated production command current migration, Tauri wrapper inventory / cleanup queue, tech-debt tracking, and the App Server frontend integration matrix.
|
||||
- Updated Agent Runtime projection and Lime CLI npm package documentation with the current projection modules and video-generation entrypoint.
|
||||
- Added Agent Workbench and Subagents roadmap entries, including acceptance, iteration plan, parallel workstreams, and task board artifacts.
|
||||
- Updated Agent Runtime, Agent UI standard gap, completion audit, implementation plan, test cases, and adjacent protocol documentation.
|
||||
- Updated quality workflow, command-boundary, Playwright E2E, protocol standards map, and tech-debt tracking docs for the current workbench / subagents / App Server boundaries.
|
||||
|
||||
### Other
|
||||
|
||||
- This release continues to center release facts on App Server JSON-RPC, Electron Desktop Host, current clients, `lime-rs/crates/**`, and machine-readable guards while blocking old Tauri wrappers and renderer mocks from returning to GUI production paths.
|
||||
- This release keeps release facts centered on App Server JSON-RPC, Electron Desktop Host, current npm clients, `lime-rs/crates/**`, and machine-readable guards while preventing old Team Workspace, Companion, and legacy command surfaces from flowing back into the product path.
|
||||
|
||||
**Full changes**: `v1.64.0` -> `v1.65.0`
|
||||
**Full changes**: `v1.65.0` -> `v1.66.0`
|
||||
|
||||
+23
-21
@@ -1,34 +1,36 @@
|
||||
## Lime v1.65.0
|
||||
## Lime v1.66.0
|
||||
|
||||
### 新功能
|
||||
- `lime media video generate` 接入 current media runtime,可直接创建并执行视频生成任务,按本地配置或环境变量解析服务端点与 API Key,并写回进度、错误和产物状态。
|
||||
- `@limecloud/agent-runtime-projection` 新增 artifact、context、conversation、diagnostic、hydration、lifecycle、permission、plan approval、queue、thread item 与 tool event 等标准投影构建模块。
|
||||
- Agent Chat 投影继续复用共享 projection 包,统一运行态、队列、权限、历史 hydrate、工具轨迹、线程条目与上下文事件的 read model 语义。
|
||||
- Agent App runtime 增加 App Server current client / capability API 接入,独立 Agent App 可以复用当前 JSON-RPC 客户端、能力宿主与运行态投影。
|
||||
- Agent Runtime 标准包补齐 App Server facts、fixture replay、subagents、refs 与 validation 支持,方便外部运行态、投影层和 UI 组件消费同一组事实。
|
||||
- App Server workspace 协议新增项目摘要读取能力,并同步到 Rust client、npm `app-server-client` 与前端项目 API。
|
||||
- Agent 输入框增加项目上下文读取与展示能力,支持把当前工作区项目摘要带入主对话编排。
|
||||
|
||||
### 修复
|
||||
- 修复视频生成 skill 的推荐命令,优先指向 `lime media video generate --prompt "..." --aspect-ratio 9:16` current 入口。
|
||||
- 修复旧 `lime-rs/src/**` 仍可能被误判为 current Rust owner 的治理口径,明确恢复旧 Tauri wrapper、stub 或 legacy facade 都属于回流。
|
||||
- 修复语音设置页对 ASR credential 与快捷键运行态的重复依赖,降低旧命令面影响,默认语音模型状态以 current voice model install state 为准。
|
||||
- 修复 Agent UI projection summary 与 subagents read model 的命名和汇总口径,减少 Team Workbench 旧语义残留。
|
||||
- 修复 Agent App runtime 页面和投影桥接对 current capability host / client API 的接线,降低独立 App 与桌面宿主之间的协议漂移。
|
||||
- 修复输入框、工具展示、workspace send runtime 与 thread grouping 的若干状态同步问题。
|
||||
- 修复 DevBridge command policy 与 legacy surface catalog 对旧命令面的分类,避免已退场路径继续被误判为 current。
|
||||
|
||||
### 优化与重构
|
||||
- 物理删除旧 `lime-rs/src/**` 孤儿目录及旧 Tauri command / service / dev_bridge / runner 实现,Rust 后端事实源收敛到 `lime-rs/crates/**`。
|
||||
- 将 agent tools catalog、execution 与 inventory 迁入 `lime-rs/crates/agent`,旧路径只保留为删除记录,不再作为运行时 owner。
|
||||
- 将视频生成 CLI 逻辑拆到 `lime-rs/crates/lime-cli/src/video.rs`,并把视频任务执行能力沉入 `lime-rs/crates/media-runtime/src/video_worker.rs`。
|
||||
- 收缩 Agent Chat 前端投影中的重复状态机,把可复用事件规范沉淀到 npm projection 包,减少 React 组件侧重复分支。
|
||||
- 简化语音与热键设置边界:语音页聚焦模型、指令和偏好,快捷键页承接全局热键配置与校验。
|
||||
- Agent Chat 工作台主线从旧 Team Workspace 组件、selector、canvas runtime 与 suggestion 工具收敛到 subagents / workbench current 表达,删除大批旧 team workspace UI 面。
|
||||
- `AppSidebar` 拆分为 account、appearance、invite、search、session、navigation target 与样式等子模块,显著降低单文件复杂度。
|
||||
- 下线 Companion 相关 API、设置卡片、provider overview、desktop mock 与侧边入口残留,减少旧 companion 能力对当前设置页和 provider 面的干扰。
|
||||
- 输入框项目上下文、team preference、project storage 与 workspace selection 逻辑继续向 hook / helper 分层收敛。
|
||||
- Agent Runtime / Agent UI npm 包继续补齐标准 contracts、fixtures、projection、runtime facts 与 UI exports,减少 GUI 与 SDK 的重复实现。
|
||||
|
||||
### 测试与质量
|
||||
- 新增 Agent Runtime projection 的 artifact、context、diagnostic、hydration、lifecycle、permission、routing 与 turn context 单测,并扩展主 projection 回归。
|
||||
- 更新 App Server client contract、Harness contract、Rust current boundary、legacy tool permission guard 与 Electron current rules guard,防止旧路径重新成为事实源。
|
||||
- 扩展输入框、Markdown、Agent Chat home surface、语音设置、热键设置、媒体任务、图库素材、session images 与视频诊断相关回归。
|
||||
- 根应用、Rust workspace、CLI npm package、Agent App runtime package、App Server client package、Agent Runtime client 依赖与锁文件版本统一更新到 `1.65.0`。
|
||||
- 扩展 App Server protocol catalog、workspace project API、npm `app-server-client`、Agent Runtime client、projection、UI contracts 与 fixture replay 回归。
|
||||
- 更新 AppSidebar、Agent Chat inputbar、workspace scene、workspace send、settings v2、Agent App runtime page 与 i18n 资源相关测试。
|
||||
- 更新 Electron SDK fixture smoke、tool surface smoke、command contract 检查、质量任务规划与 i18n readiness 报告。
|
||||
- 根应用、Rust workspace、CLI npm package、Agent App runtime package、App Server client package、Agent Runtime client 依赖与锁文件版本统一更新到 `1.66.0`。
|
||||
|
||||
### 文档
|
||||
- 更新 AGENTS、工程质量、治理、命令边界与并行协作文档,记录 `lime-rs/src/**` 已于 2026-06-10 删除以及目录级 dead 判定口径。
|
||||
- 更新 production command current migration、Tauri wrapper inventory / cleanup queue、tech debt tracker 与 App Server frontend integration matrix。
|
||||
- 更新 Agent Runtime projection 与 Lime CLI npm package 文档,补充 current 投影模块与视频生成入口说明。
|
||||
- 新增 Agent Workbench 与 Subagents 路线图入口,补充 acceptance、iteration plan、parallel workstreams 与 task board。
|
||||
- 更新 Agent Runtime、Agent UI 标准落差、completion audit、implementation plan、test cases 与 adjacent protocols 文档。
|
||||
- 更新工程质量、命令边界、Playwright E2E、协议标准地图与技术债追踪文档,记录当前 workbench / subagents / App Server 主线边界。
|
||||
|
||||
### 其他
|
||||
- 本版继续以 App Server JSON-RPC、Electron Desktop Host、current clients、`lime-rs/crates/**` 与机器可读守卫作为发布事实源,阻断旧 Tauri wrapper 和 renderer mock 对 GUI 主路径的回流。
|
||||
- 本版继续把发布事实源收敛到 App Server JSON-RPC、Electron Desktop Host、current npm clients、`lime-rs/crates/**` 与机器可读守卫,避免旧 Team Workspace、Companion 和 legacy command 面回流。
|
||||
|
||||
**完整变更**: `v1.64.0` -> `v1.65.0`
|
||||
**完整变更**: `v1.65.0` -> `v1.66.0`
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
encodeMessage,
|
||||
type JsonRpcMessage,
|
||||
type JsonRpcRequest,
|
||||
} from "app-server-client";
|
||||
} from "@limecloud/app-server-client";
|
||||
|
||||
const {
|
||||
fakeConnection,
|
||||
@@ -118,8 +118,8 @@ Object.defineProperty(process, "resourcesPath", {
|
||||
value: process.cwd(),
|
||||
});
|
||||
|
||||
vi.mock("app-server-client", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("app-server-client")>();
|
||||
vi.mock("@limecloud/app-server-client", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@limecloud/app-server-client")>();
|
||||
return {
|
||||
...actual,
|
||||
AppServerSidecarLifecycle: FakeAppServerSidecarLifecycle,
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
type JsonRpcMessage,
|
||||
type RequestId,
|
||||
type SidecarLaunchConfig,
|
||||
} from "app-server-client";
|
||||
} from "@limecloud/app-server-client";
|
||||
import { app } from "./electronRuntime";
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -14,7 +14,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { AppServerRequestError } from "app-server-client";
|
||||
import { AppServerRequestError } from "@limecloud/app-server-client";
|
||||
import { ElectronHostCommands } from "./hostCommands";
|
||||
import type { ElectronAppServerHost } from "./appServerHost";
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
METHOD_WORKSPACE_BY_PATH_READ,
|
||||
METHOD_WORKSPACE_DEFAULT_ENSURE,
|
||||
METHOD_WORKSPACE_DEFAULT_READ,
|
||||
METHOD_WORKSPACE_ENSURE,
|
||||
METHOD_WORKSPACE_ENSURE_READY,
|
||||
METHOD_WORKSPACE_LIST,
|
||||
METHOD_WORKSPACE_PROJECTS_ROOT_READ,
|
||||
@@ -56,6 +57,7 @@ import {
|
||||
type ModelListResponse,
|
||||
type ModelProviderListResponse,
|
||||
type ProjectMemoryReadResponse,
|
||||
type WorkspaceEnsureProjectResponse,
|
||||
type SkillListResponse,
|
||||
type WorkspaceEnsureReadyResponse,
|
||||
type WorkspaceListResponse,
|
||||
@@ -63,7 +65,7 @@ import {
|
||||
type WorkspaceProjectsRootReadResponse,
|
||||
type WorkspaceReadResponse,
|
||||
type WorkspaceSkillBindingsListResponse,
|
||||
} from "app-server-client";
|
||||
} from "@limecloud/app-server-client";
|
||||
import { spawn } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
@@ -367,6 +369,8 @@ export class ElectronHostCommands {
|
||||
return await this.#readWorkspace(args);
|
||||
case "workspace_get_by_path":
|
||||
return await this.#readWorkspaceByPath(args);
|
||||
case "workspace_ensure":
|
||||
return await this.#ensureWorkspace(args);
|
||||
case "workspace_get_projects_root":
|
||||
return await this.#readWorkspaceProjectsRoot();
|
||||
case "workspace_resolve_project_path":
|
||||
@@ -1732,6 +1736,34 @@ export class ElectronHostCommands {
|
||||
return response.rootPath;
|
||||
}
|
||||
|
||||
async #ensureWorkspace(args: HostArgs): Promise<unknown> {
|
||||
const request = readRequest(args);
|
||||
const name =
|
||||
readString(request, "name") ?? readString(args, "name") ?? "untitled";
|
||||
const rootPath =
|
||||
readString(request, "rootPath") ??
|
||||
readString(request, "root_path") ??
|
||||
readString(args, "rootPath") ??
|
||||
readString(args, "root_path");
|
||||
if (!rootPath) {
|
||||
throw new Error("workspace_ensure requires rootPath");
|
||||
}
|
||||
const workspaceType =
|
||||
readString(request, "workspaceType") ??
|
||||
readString(request, "workspace_type") ??
|
||||
readString(args, "workspaceType") ??
|
||||
readString(args, "workspace_type");
|
||||
const response = await this.#appServerRequest<WorkspaceEnsureProjectResponse>(
|
||||
METHOD_WORKSPACE_ENSURE,
|
||||
{
|
||||
name,
|
||||
rootPath,
|
||||
...(workspaceType ? { workspaceType } : {}),
|
||||
},
|
||||
);
|
||||
return response.workspace;
|
||||
}
|
||||
|
||||
async #ensureDefaultWorkspaceReady(): Promise<unknown | null> {
|
||||
const workspace = await this.#ensureDefaultWorkspace();
|
||||
const id = readString(workspace, "id");
|
||||
@@ -4197,7 +4229,6 @@ function buildDefaultConfig(): Record<string, unknown> {
|
||||
workspace_preferences: {
|
||||
schema_version: 3,
|
||||
media_defaults: {},
|
||||
companion_defaults: {},
|
||||
service_models: {},
|
||||
},
|
||||
navigation: { schema_version: 3, enabled_items: [] },
|
||||
|
||||
@@ -63,6 +63,7 @@ describe("electron/ipcChannels", () => {
|
||||
expect(isElectronHostCommand("read_file_preview_cmd")).toBe(false);
|
||||
expect(isElectronHostCommand("list_executable_skills")).toBe(false);
|
||||
expect(isElectronHostCommand("workspace_list")).toBe(true);
|
||||
expect(isElectronHostCommand("workspace_ensure")).toBe(true);
|
||||
expect(isElectronHostCommand("workspace_get_by_path")).toBe(true);
|
||||
expect(isElectronHostCommand("workspace_set_default")).toBe(false);
|
||||
expect(isElectronHostCommand("get_config")).toBe(true);
|
||||
@@ -198,6 +199,7 @@ describe("electron/ipcChannels", () => {
|
||||
"get_default_provider",
|
||||
"get_local_skills_for_app",
|
||||
"project_memory_get",
|
||||
"workspace_ensure",
|
||||
"workspace_ensure_default_ready",
|
||||
"workspace_ensure_ready",
|
||||
"workspace_get",
|
||||
|
||||
@@ -74,6 +74,7 @@ export const ELECTRON_HOST_COMMANDS = [
|
||||
"voice_models_download",
|
||||
"voice_models_get_install_state",
|
||||
"voice_models_list_catalog",
|
||||
"workspace_ensure",
|
||||
"workspace_ensure_default_ready",
|
||||
"workspace_ensure_ready",
|
||||
"workspace_get",
|
||||
@@ -131,6 +132,7 @@ export const ELECTRON_APP_SERVER_TRUTH_BRIDGE_COMMANDS = [
|
||||
"get_default_provider",
|
||||
"get_local_skills_for_app",
|
||||
"project_memory_get",
|
||||
"workspace_ensure",
|
||||
"workspace_ensure_default_ready",
|
||||
"workspace_ensure_ready",
|
||||
"workspace_get",
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ import {
|
||||
PROTOCOL_VERSION,
|
||||
SERVER_NAME,
|
||||
type InitializeResponse,
|
||||
} from "app-server-client";
|
||||
} from "@limecloud/app-server-client";
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
- `overview.md` - 项目架构总览与模块分层
|
||||
- `agent-protocol-standards-map.md` - Agent 公共标准、Lime 开发协议、友链与未来拆分候选地图
|
||||
- `agent-ui-runtime-standard.md` - Agent UI / Runtime 四包职责、宿主接入、主聊天 projection 迁移分类与守卫要求
|
||||
- Agent UI / Runtime 当前宿主链路固定为 `AgentRuntimeClient -> projectAgentUiState -> AgentUiProjectionView`;任何第二宿主复用都必须回到 `agent-ui-runtime-standard.md`。
|
||||
- `query-loop.md` - 运行时 Query Loop current 主链、提交入口与执行边界
|
||||
- `prompt-foundation.md` - 基础 Prompt current 主链、system prompt 组装顺序与 current/compat 分类
|
||||
- `task-agent-taxonomy.md` - Task / Agent / Coordinator current taxonomy、current/compat 分类与协调边界
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
| Persistence Map | `persistence-map.md` | FileArtifact、sidecar、version、checkpoint、snapshot refs | Artifact 标准全文 |
|
||||
| Quality Workflow | `quality-workflow.md` | 本地校验、GUI smoke、contracts、风险分类 | 产品标准定义 |
|
||||
|
||||
当前 Agent UI / Runtime 的宿主复用验收链路固定为 `AgentRuntimeClient -> projectAgentUiState -> AgentUiProjectionView`;公共标准只定义可移植语义,Lime 实现必须在 `agent-ui-runtime-standard.md` 中继续收敛到这条链路。
|
||||
|
||||
## 未来可拆标准候选
|
||||
|
||||
这些方向已经在 Lime 中出现稳定压力,但是否独立成公共标准,取决于是否能脱离 Lime 私有实现并服务其他 Agent 产品。
|
||||
|
||||
@@ -23,12 +23,12 @@ App Server JSON-RPC current methods
|
||||
|
||||
| 包 | 分类 | 拥有的事实 | 禁止承接 |
|
||||
| --- | --- | --- | --- |
|
||||
| `@limecloud/agent-runtime-client` | current runtime facade | `agentSession/turn/start`、`agentSession/turn/cancel`、`agentSession/action/respond`、`agentSession/read`、`evidence/export`、`agentSession/event` 的 TypeScript facade | 新 JSON-RPC method、Electron IPC、legacy command、mock fallback、独立 `readTask` 协议 |
|
||||
| `@limecloud/agent-ui-contracts` | current 类型事实源 | execution event、Agent UI adapter event、message parts、process timeline、execution graph、projection state、projector interface | 投影逻辑、React 组件、JSON-RPC client、session store、业务对象 |
|
||||
| `@limecloud/agent-runtime-projection` | current headless projection | event store index、scope selector、latest selector、summary selector、read model、`projectAgentUiState`、`createAgentUiProjector` | React、DOM、i18n hook、App Server client、Electron bridge、mock、宿主业务路径 |
|
||||
| `@limecloud/agent-runtime-ui` | current React primitives | `AgentUiProjectionState` 的标准渲染组件、message parts、timeline、execution graph、tool/action/artifact/evidence 事实展示、action intent callbacks | JSON-RPC 订阅、session 持久化、业务路由、全局主题、Prompt/知识库/素材/审核业务壳 |
|
||||
| `@limecloud/agent-runtime-client` | current runtime facade | `agentSession/turn/start`、`agentSession/turn/cancel`、`agentSession/action/respond`、`agentSession/read`、`evidence/export`、`agentSession/event` 的 TypeScript facade;browser-safe 子路径 `@limecloud/agent-runtime-client/sessionGateway` 提供 `createAgentRuntimeClientFromSessionGateway(...)`,把已有 App Server session gateway 适配为标准 lifecycle client | 新 JSON-RPC method、Electron IPC、legacy command、mock fallback、独立 `readTask` 协议、宿主业务 session 创建 |
|
||||
| `@limecloud/agent-ui-contracts` | current 类型事实源 | execution event、Agent UI adapter event、message parts、process timeline、execution graph、Subagents、projection state、projector interface | 投影逻辑、React 组件、JSON-RPC client、session store、业务对象 |
|
||||
| `@limecloud/agent-runtime-projection` | current headless projection | event store index、scope selector、latest selector、summary selector、read model、`projectAgentUiState`、`createAgentUiProjector`、`buildAgentUiSubagentsModel` | React、DOM、i18n hook、App Server client、Electron bridge、mock、宿主业务路径 |
|
||||
| `@limecloud/agent-runtime-ui` | current React primitives | `AgentUiProjectionState` 的标准渲染组件、message parts、timeline、execution graph、Subagents、tool/action/artifact/evidence 事实展示、action intent callbacks | JSON-RPC 订阅、session 持久化、业务路由、全局主题、Prompt/知识库/素材/审核业务壳 |
|
||||
|
||||
`app-server-client` 仍是底层 App Server JSON-RPC 与 sidecar lifecycle client;`agent-runtime-client` 是对外标准 facade,不是第二套协议。
|
||||
`@limecloud/app-server-client` 仍是底层 App Server JSON-RPC 与 sidecar lifecycle client;`@limecloud/agent-runtime-client` 是对外标准 facade,不是第二套协议。
|
||||
|
||||
## Canonical 包命名决策
|
||||
|
||||
@@ -75,6 +75,7 @@ src/refs.ts -> artifact refs 等跨宿主引用提取
|
||||
src/routing.ts -> routing run.status / decision payload projection helpers
|
||||
src/runtimeFacts.ts -> runtime entity/status/phase/topology 标准事实解释
|
||||
src/summary.ts -> host-neutral summary / surface / lane selectors
|
||||
src/subagents.ts -> AgentUiSubagentsModel 标准模型 selector
|
||||
src/subagentStatusEvents.ts -> subagent_status_changed team / handoff / worker notification projection helpers
|
||||
src/threadItems.ts -> thread item -> AgentUiProjectionEvent builders / action item builders / subagent activity builders / task owner metadata parser
|
||||
src/toolEvents.ts -> tool_start / tool_end / tool_progress / tool delta -> AgentUiProjectionEvent builders
|
||||
@@ -93,7 +94,9 @@ src/labels.ts -> 默认 label、status 和 meta formatter
|
||||
src/messages.tsx -> AgentTimeline / UIMessagePartsView
|
||||
src/processTimeline.tsx -> ProcessTimelineView
|
||||
src/executionGraph.tsx -> ExecutionGraphView
|
||||
src/refs.tsx -> ArtifactRefList / EvidenceRefList / AgentUiRefList
|
||||
src/runtimeFacts.tsx -> RuntimeFactsPanel / RuntimeFactCard / action/tool lists
|
||||
src/subagents.tsx -> SubagentsView / SubagentThreadList / SubagentDelegationList / SubagentActivityList
|
||||
src/projectionView.tsx -> AgentUiProjectionView 标准组合入口
|
||||
src/index.ts -> barrel exports only
|
||||
```
|
||||
@@ -135,7 +138,7 @@ src/index.ts -> barrel exports only
|
||||
|
||||
- scope 匹配、latest event index、artifact / evidence / action lookup 的本地重写。
|
||||
- action / task / artifact / evidence / diagnostics 分组的本地重写。
|
||||
- Team Workbench surface / lane 聚合的本地重写。
|
||||
- Subagents 线程、委派调用、活动摘要、隔离摘要聚合的本地重写。
|
||||
- 从 UI 状态反推 runtime fact 的逻辑。
|
||||
- mock fallback 或 legacy command fallback。
|
||||
|
||||
@@ -158,13 +161,13 @@ src/index.ts -> barrel exports only
|
||||
| `routingProjection.ts` | compat adapter | 只允许把主聊天 routing decision / limit state / limit event 字段映射到标准 `buildAgentUiRoutingStatusEvent`;run.status 的 owner/scope/phase/surface/persistence 与 routing / limit payload 已由标准 helper 接管 | 周边投影模块改为直接消费标准 routing helper 后删除 |
|
||||
| `runtimeLifecycleProjection.ts` 中 lifecycle / model / task profile 构建 | compat adapter | 只允许把主聊天 `thread_started` / `turn_started` / `turn_completed` / `turn_failed` / `runtime_status` / `model_change` / `task_profile_resolved` 字段映射到标准 `buildAgentUiThreadStartedEvent` / `buildAgentUiRunStartedEvent` / `buildAgentUiRunFinishedEvent` / `buildAgentUiRunFailedEvent` / `buildAgentUiRuntimeStatusEvent` / `buildAgentUiRuntimeTeamChangedEvent` / `buildAgentUiModelChangeEvent` / `buildAgentUiTaskProfileResolvedEvent` | 周边投影模块改为直接消费标准 lifecycle helper 后删除通用兼容构建 |
|
||||
| `subagentStatusProjection.ts` | compat adapter | 只允许把主聊天 `runtime_status` 字段映射到标准 `buildAgentUiRuntimeTeamChangedEvent`,并把 `subagent_status_changed` 字段映射到标准 `buildAgentUiSubagentStatusChangedEvents`;runtime status 来源 `team.changed` 与 subagent status 来源 team / handoff / transcript / worker notification 复合事件的 owner/scope/phase/surface/persistence/payload 已由标准 helper 接管 | 周边投影模块改为直接消费标准 subagent status helper 后删除 |
|
||||
| `threadItemProjection.ts` 中 thread item 主事件构建 | compat adapter | 只允许把主聊天 `AgentThreadItem` 映射到标准 `buildAgentUiThreadItemEvent` / `buildAgentUiThreadItemBase`,并通过 `extractAgentUiTaskOwnerChangeProjection` 读取 TaskUpdate owner metadata;审批、用户输入、subagent activity、worker notification 和计划审批 metadata 已由标准 helper 接管,Team Workbench 附加事件暂留本地 adapter | 周边投影模块改为直接消费标准 thread item helper 后删除通用兼容构建 |
|
||||
| `threadItemProjection.ts` 中 thread item 主事件构建 | compat adapter | 只允许把主聊天 `AgentThreadItem` 映射到标准 `buildAgentUiThreadItemEvent` / `buildAgentUiThreadItemBase`,并通过 `extractAgentUiTaskOwnerChangeProjection` 读取 TaskUpdate owner metadata;审批、用户输入、subagent activity、worker notification 和计划审批 metadata 已由标准 helper 接管;旧协作视图附加事件只能作为主聊天 presentation residual 暂留 | 周边投影模块改为直接消费标准 thread item helper 后删除通用兼容构建 |
|
||||
| `toolEventProjection.ts` | compat adapter | 只允许把主聊天 `tool_start` / `tool_end` / `tool_progress` / `tool_output_delta` / `tool_input_delta` 字段映射到标准 `buildAgentUiTool*` helper;tool lifecycle、artifact refs、diagnostic keys 和 plan approval 附加 action 已由标准 helper 接管 | 周边投影模块改为直接消费标准 tool helper 后删除 |
|
||||
| `agentUiEventProjection.ts` 中从主聊天事件构建 `AgentUiProjectionEvent` 的代码 | compat adapter | 仍依赖主聊天 `agentProtocol` 和历史事件形状,是迁移输入适配层;通用 envelope / sequence 规则已迁入标准 projection 包 | host-neutral 构建规则继续分批迁入标准 projection 包或 App Server event adapter 后收缩 |
|
||||
| `conversationProjectionStore.ts` | current host store adapter | 只保留 external store、订阅、stream diagnostics 和兼容导出名,selector 委托标准包 | 标准 store adapter 被两个宿主复用后继续收缩 |
|
||||
| `agentUiProjectionSummary.ts` 中本地化 label / formatter | current presentation adapter | 只负责中文/本地化展示和主聊天 UI 细节 | 标准 UI package 提供可配置 formatter 后再迁 |
|
||||
| `agentUiProjectionSummary.ts` 中 selector / event type set / surface lane | moved current | 已迁到 `@limecloud/agent-runtime-projection` | 守卫阻止回流 |
|
||||
| `agentUiTeamWorkbenchViewModel.ts` | current host view model | 负责主聊天 Team Workbench 的 item / chip / action / target view model | host-neutral selector 继续下沉后保留 presentation 层 |
|
||||
| `agentUiSubagentsViewModel.ts` | compat presentation residual | 只允许消费标准 projection / `state.subagents` / summary selector 后生成主聊天本地 item / chip / action / target 展示模型;不得作为 SDK 类型、组件、state 字段或新宿主 API;旧协作命名文件已退出 projection adapter 命名 | host-neutral selector 下沉到 `@limecloud/agent-runtime-projection` 后,只保留宿主业务 action route;剩余 workspace / 产品测试参数名随产品侧迁移删除 |
|
||||
| `*.js` / `*.d.ts` 旧生成物残留 | deprecated | 只能做薄转导或待删除,不得保留旧实现 | 删除需要独立确认并同步守卫 |
|
||||
|
||||
任何新增 Agent UI 标准 selector 必须先进入 `@limecloud/agent-runtime-projection`;主聊天目录只能做适配、展示和业务交互。
|
||||
@@ -174,6 +177,8 @@ src/index.ts -> barrel exports only
|
||||
Agent App Runtime 是当前第二个真实宿主基线:
|
||||
|
||||
```text
|
||||
AgentRuntimeClient -> projectAgentUiState -> AgentUiProjectionView
|
||||
|
||||
Agent App host run state
|
||||
-> buildAgentAppStandardRuntimeEvents(...)
|
||||
-> projectAgentUiState(...)
|
||||
@@ -187,6 +192,14 @@ Agent App 可以继续保留自己的 summary、运行抽屉、业务对象 pres
|
||||
|
||||
标准 UI 必须支持多 action controls。`AgentRuntimeEventProjection.action` 保持兼容单按钮读取,`AgentRuntimeEventProjection.actions` 承接 approve / reject / answer / retry / stop 等多按钮 intent;`agent-runtime-ui` 只能渲染这些 intent 并回调宿主,不得自行解释业务结果。
|
||||
|
||||
Agent App 第二宿主的 runtime client 接入以 `src/features/agent-app/runtime/agentRuntimeClientApi.ts` 为 current adapter。`AgentRuntimeCapabilityHost` 可以直接注入 `AgentRuntimeClient`,由该 adapter 把 `lime.agent.startTask/getTask/cancelTask/submitHostResponse` 分别投影到 `startTurn/readThread/cancelTurn/respondAction`。`startTask` 必须携带已经存在的 `sessionId`,缺失时 fail closed,不在前端伪造 session 或独立 `readTask` 协议。adapter 负责写入 `runtimeOptions.hostOptions.asterChatRequest`,并保留 `turn_config` 镜像,保证后端 host 与 App Server backend 都能读取同一份 provider / 权限 / sandbox / system prompt / metadata 配置。`readThread` 返回的 `detail.thread_read` / `detail.threadRead` 是 Agent App host replay artifact、tool call 和 evidence 的 read model 事实源。
|
||||
|
||||
Agent App 默认运行页必须通过 `src/features/agent-app/runtime/agentRuntimeAppServerClient.ts` 先调用 App Server current `agentSession/start` 创建或绑定 session,再把该 sessionId 交给 `agentRuntimeClientApi.ts` 进入 `startTurn/readThread/cancelTurn/respondAction`。`readSession -> readThread` 的通用 lifecycle 适配必须复用 `@limecloud/agent-runtime-client/sessionGateway` 的 `createAgentRuntimeClientFromSessionGateway(...)`;Agent App feature 只保留 `agent_app.task` business object session resolver 与默认页面装配,不得重新实现私有 session gateway。renderer 运行时代码不得为了取 session gateway 适配器而从 `@limecloud/agent-runtime-client` 根入口导入运行时值,否则会把 Node 侧 App Server client 打进前端包。`src/features/agent-app/ui/AgentAppRuntimePage.tsx` 默认注入 `createDefaultAgentAppRuntimeHostOptions()`;这条默认宿主链路不得在标准 client 失败后回退 `agent_app_runtime_*`、renderer mock 或 desktop-host default mock,而应 fail closed。
|
||||
|
||||
`AgentRuntimeCapabilityHost` 默认不再隐式回退 `agent_app_runtime_*` compat facade:没有注入标准 `AgentRuntimeClient`,也没有显式传入 compat `api` 时必须 fail closed。`src/lib/api/agentAppRuntime.ts` 与 `agent_app_runtime_*` 只保留为显式兼容入口和迁移期守卫,服务尚未迁完的既有桌面路径;新增第二宿主能力必须优先走 `AgentRuntimeClient` adapter。compat facade 只能委托、适配和退场,不得复制运行时提交、read model 拼装、tool replay 或 mock fallback。
|
||||
|
||||
DevBridge policy 中 `agent_app_runtime_*` 必须归类为 no-mock compat:它们可以 fail closed、走真实 Electron Desktop Host / App Server current 通道或作为迁移期显式兼容入口,但不得继续放在 `bridgeTruthCommands` 里充当后续演进事实源,也不得进入 `mockPriorityCommands`、desktop-host default mock 或 renderer mock fallback。
|
||||
|
||||
## App Server 与 Runtime 配合
|
||||
|
||||
Agent Runtime 后端事实源是 App Server JSON-RPC current 主链:
|
||||
@@ -246,12 +259,24 @@ Electron 只作为 Desktop Host bridge 和 sidecar lifecycle host。新增 runti
|
||||
25. `agent-ui-contracts` 的 `index.ts` 只能做 barrel exports,events / runtime / projection / messages / timeline / graph 必须按职责拆分。
|
||||
26. 标准 UI 不得恢复旧私有 tree 命名或旧 Agent App `data-agent-run-projection-*` DOM;过程结构以 message parts、process timeline、execution graph 和 runtime facts 为标准 surface。
|
||||
27. `agent-runtime-ui` 用户可见文案必须可由 labels / formatter props 注入;默认 fallback 不得替代宿主五语言 i18n。
|
||||
28. Agent App 第二宿主必须支持注入标准 `AgentRuntimeClient`,并通过 `startTurn/readThread/cancelTurn/respondAction` 进入 App Server current 主链;没有标准 client 或显式 compat `api` 时必须 fail closed。
|
||||
29. Agent App 标准 client adapter 不得依赖 `safeInvoke`、legacy task command string、renderer mock、desktop-host default mock 或独立 task read protocol。
|
||||
30. `@limecloud/agent-runtime-client/sessionGateway` 必须提供 browser-safe 的 `createAgentRuntimeClientFromSessionGateway(...)`,把已有 App Server session gateway 适配成 `startTurn/readThread/cancelTurn/respondAction` lifecycle client;宿主 feature 不得复制这段通用映射,也不得从根入口导入该运行时值。
|
||||
31. Agent App 默认运行页必须注入 App Server-backed 标准 runtime host options,通过 `agentSession/start` 创建 session,再进入标准 client adapter;页面回归必须证明默认 Host Bridge 调用 App Server current methods,且未调用 `agent_app_runtime_*` compat facade。
|
||||
32. `agent_app_runtime_*` 不得停留在 DevBridge `bridgeTruthCommands`;只允许作为 no-mock compat residual 和显式兼容入口,且退出条件是所有既有桌面路径迁到标准 `AgentRuntimeClient` / App Server lifecycle 后删除 `src/lib/api/agentAppRuntime.ts`。
|
||||
|
||||
最低验证入口:
|
||||
|
||||
```bash
|
||||
npm --prefix packages/agent-runtime-projection run test
|
||||
npx vitest run "src/components/agent/chat/projection/agentUiProjectionSummary.test.ts" "src/components/agent/chat/projection/conversationProjectionStore.test.ts" "src/components/agent/chat/projection/agentUiTeamWorkbenchViewModel.test.ts" --silent=passed-only --disableConsoleIntercept
|
||||
npm --prefix packages/agent-runtime-ui run test
|
||||
npm run test:contracts
|
||||
```
|
||||
|
||||
`agentUiSubagentsViewModel.ts` 仍是主聊天 presentation residual 时,可额外跑对应产品侧定向测试;它只能证明 presentation residual 没退化,不能替代标准包验证:
|
||||
|
||||
```bash
|
||||
npx vitest run "src/components/agent/chat/projection/agentUiProjectionSummary.test.ts" "src/components/agent/chat/projection/conversationProjectionStore.test.ts" --silent=passed-only --disableConsoleIntercept
|
||||
npm run test:contracts
|
||||
```
|
||||
|
||||
@@ -260,7 +285,7 @@ npm run test:contracts
|
||||
## 下一步收缩顺序
|
||||
|
||||
1. 给 `agent-ui-contracts` 增加更清晰的 event taxonomy / schema fixture,减少 string-only 事件漂移。
|
||||
2. 把第二宿主从 host run state adapter 继续推进到 `AgentRuntimeClient -> App Server current methods -> Agent App host run state` 的端到端接入。
|
||||
3. 保持并验证 `AgentRuntimeClient -> projectAgentUiState -> AgentUiProjectionView` 这条跨宿主标准链路,避免第二宿主回退成私有 UI projection。
|
||||
2. 给第二宿主补 Agent App 专项 Electron fixture,直接证明 `AgentRuntimeClient -> Agent App host run state -> projectAgentUiState -> AgentUiProjectionView` 的完整链路;聚合 `verify:gui-smoke` 已作为桌面壳不回归证据,但不能替代第二宿主专项闭环。
|
||||
3. 继续收缩默认 `agent_app_runtime_*` compat facade;当前 DevBridge 已降为 no-mock compat residual,下一刀应删除或替换未注入标准 client 时的默认 fallback。
|
||||
4. 收缩主聊天旧 projection 的兼容导出名和旧生成物。
|
||||
5. 将重复出现的 Team Workbench view model selector 分批下沉,保留宿主业务 action route。
|
||||
5. 将 `agentUiSubagentsViewModel.ts` 的 host-neutral selector 分批下沉为标准 Subagents selector,保留宿主业务 action route,并删除 workspace / 产品测试里的旧命名 residual。
|
||||
|
||||
@@ -501,15 +501,7 @@ Skill 执行链路同样遵循单一命令边界。当前前端入口为 `src/li
|
||||
- `preview_automation_schedule`
|
||||
- `validate_automation_schedule`
|
||||
|
||||
Companion 桌宠链路同样遵循这条路径。当前主入口为 `src/lib/api/companion.ts`,统一承接:
|
||||
|
||||
- `companion_get_pet_status`
|
||||
- `companion_launch_pet`
|
||||
- `companion_send_pet_command`
|
||||
|
||||
Lime 主应用会在本地维护 `ws://127.0.0.1:45554/companion/pet` 的桌宠 companion 入口。前端如需感知桌宠连接状态,应继续通过 `companion-pet-status` 事件监听统一状态,不要在页面或 Hook 里自行直连本地 `WebSocket`。
|
||||
|
||||
如果 companion 协议继续扩展,也应优先延续“Lime 做宿主、桌宠只收脱敏派生状态”的边界。例如 provider 概览相关能力,允许 Lime 通过 `companion_send_pet_command` 下发诸如 `pet.provider_overview` 这类脱敏摘要,并允许桌宠通过 `pet.open_provider_settings` 请求 Lime 聚焦主窗口并跳到 `设置 -> AI 服务商`,或通过 `pet.request_provider_overview_sync` 请求 Lime 立即重发最新的脱敏摘要;桌宠交互增强能力也应继续走这条主链,例如双击 / 三击桌宠后发出 `pet.request_pet_cheer`、`pet.request_pet_next_step`,或通过 `pet.request_chat_reply` 携带用户输入文本,请求 Lime 代为调用当前可聊天模型,再统一回写 `pet.show_bubble`;但不允许桌宠直接读取凭证文件、数据库或已下线的完整凭证 HTTP 接口。
|
||||
Companion 桌宠链路已下线,分类为 `dead`。`src/lib/api/companion.ts`、全局 companion hook、设置页桌宠管理、桌宠能力偏好、provider overview 同步、默认 mock 与 `companion_get_pet_status` / `companion_launch_pet` / `companion_send_pet_command` 命令面均不得重新接回前端网关、Desktop Host、DevBridge 或 App Server current 主链。后续如果重新设计独立桌面伴随能力,必须重新定义 current 事实源和协议边界,而不是复活旧 `companion_*` 命令、`companion-pet-status` 事件或本地 `ws://127.0.0.1:45554/companion/pet` 入口。
|
||||
|
||||
## 命令契约的事实源
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
- 如果 Playwright 工具当前还在 deferred surface,优先用 `ToolSearch` 的精确选择名,例如 `select:mcp__playwright__browser_click`;不要把 `playwright_browser_click`、`browser click` 之类同义词反复丢给 `ToolSearch`
|
||||
- 能走真实后端就走真实后端;浏览器模式暂不支持或尚未桥接的能力,允许走 mock
|
||||
- `verify:gui-smoke` 内部的 browser runtime 校验默认走无界面浏览器会话;它只证明主链可启动,不替代后续真实页面交互验证
|
||||
- `lime-pet` 原生桌宠属于独立仓库与原生窗口壳,不纳入当前 WebView Playwright 的直接操控范围;在 Lime 主仓里只验证 `companion_*` API、状态事件与主窗口唤起链路,桌宠窗口移动、点击命中与原生层动画仍需额外手工 smoke
|
||||
- 如果 companion 协议新增了 provider 摘要、桌宠回跳设置、桌宠主动请求同步,或双击 / 三击 / 文本对话触发的桌宠 LLM 交互事件,Playwright 续测只覆盖 Lime 主仓内的“状态事件是否触发”“是否跳到 `设置 -> AI 服务商`”“是否重发脱敏摘要”“是否调用宿主侧 LLM 代理逻辑”和“主窗口是否被唤起”,不在 WebView 层尝试直接操控原生桌宠 UI
|
||||
- Companion 桌宠链路已在 Lime 主仓下线并归类为 `dead`;Playwright 续测不再验证 `companion_*` API、`companion-pet-status` 状态事件、桌宠设置回跳或主窗口唤起链路
|
||||
- 如未来重新设计独立桌面伴随能力,必须先定义新的 current 协议与 GUI 入口,再补对应 Playwright 覆盖;不得复用旧 `companion_*` 命令或本地 `companion/pet` 协议作为续测依据
|
||||
- 共享网关控制页已下线,托盘也不再展示网关状态或地址;共享网关 `/v1/routes` 与 selector HTTP 路由也已下线,不再对“启动/停止网关、复制网关地址、路由/curl 示例、selector 路由、托盘运行态文案”做 GUI 续测;server 验证只关注标准 `/v1/messages` 与 `/v1/chat/completions` 主链,如需看运行时状态,走开发者页或实验页的诊断面板
|
||||
- 旧设置页里的“安全与性能 / 容错配置”已经下线,不再对这些页签、表单或命令写入路径做 GUI 续测;如果还要验证提示路由,只围绕当前输入框 `get_hint_routes` 读取链与提示展示,不再寻找旧设置页入口
|
||||
- 初装引导里的旧插件选择 / 插件安装 / 配置切换链路已经下线,不再对 `config-switch` 推荐安装、Provider Switch 页面或相关命令做 GUI 续测;当前 onboarding 只围绕现役语音体验流程验证
|
||||
@@ -464,20 +464,20 @@ npm run verify:gui-smoke -- --include-knowledge-product-e2e --reuse-running
|
||||
4. 验证工具开关恢复的是该话题最近一次 session runtime,而不是主题级 localStorage 默认值
|
||||
5. 如首次切回旧话题时只能命中 fallback,再继续切换一次,确认第二次开始已优先走 runtime 恢复
|
||||
|
||||
### 话题 Team 恢复验证
|
||||
### 话题 Subagents 恢复验证
|
||||
|
||||
1. 进入同一工作区中的两个话题
|
||||
2. 在话题 A 里选择一个 builtin Team,在话题 B 里选择另一个 builtin 或 custom Team
|
||||
2. 在话题 A 里选择一个 builtin Subagents profile,在话题 B 里选择另一个 builtin 或 custom profile
|
||||
3. 在两个话题之间来回切换,必要时新建一个空白话题再切回
|
||||
4. 验证 Team 选择器、摘要区和 Team Workbench 展示恢复的是该话题最近一次 `recent_team_selection`,而不是主题级 localStorage 的旧值
|
||||
5. 对 custom Team 额外确认:切回后 label / description / roles 没丢;如果本轮是从 fallback 回填,继续切换一次确认第二次开始已优先走 runtime 恢复
|
||||
6. 如果当前项目已有子代理或父会话上下文,再发送一条新消息,确认 Team Workbench 的 shadow 卡片与当前 Team 恢复一致,不会退回到全局 theme fallback;本轮如涉及 `harness.team_memory_shadow`,这里就是最小 GUI 续测锚点
|
||||
4. 验证 Subagents profile 选择器、摘要区和子代理展示恢复的是该话题最近一次 `recent_team_selection`,而不是主题级 localStorage 的旧值
|
||||
5. 对 custom profile 额外确认:切回后 label / description / roles 没丢;如果本轮是从 fallback 回填,继续切换一次确认第二次开始已优先走 runtime 恢复
|
||||
6. 如果当前项目已有子代理或父会话上下文,再发送一条新消息,确认子代理影子卡片与当前 profile 恢复一致,不会退回到全局 theme fallback;本轮如涉及 `harness.team_memory_shadow`,这里就是最小 GUI 续测锚点
|
||||
|
||||
### 子代理 current 字段验证
|
||||
|
||||
1. 准备一个带 Team 或父子会话上下文的工作区,并触发一次子代理创建
|
||||
2. 如果当前入口支持显式名称或工作目录,优先带上 `name` 与绝对 `cwd`;如果 UI 暂无显式入口,至少复用现有 flow 创建一个 child session,并在详情区观察其展示名与工作目录
|
||||
3. 验证 child session / Team Workbench 优先显示显式 `name`,而不是退回 `agent_type`、profile label 或 task summary fallback
|
||||
3. 验证 child session / Subagents 展示优先显示显式 `name`,而不是退回 `agent_type`、profile label 或 task summary fallback
|
||||
4. 如果本轮涉及 `teamName`,确认 child 会回挂到当前 Team,上下文里能按该名字识别,不会出现重复成员或错挂到其它 Team
|
||||
5. 验证 child 的 `working_dir` 与详情展示反映请求的绝对 `cwd`;如果请求非法相对路径,前端应看到明确失败,而不是静默回退父目录
|
||||
6. 如果当前入口或调试面板暴露 `mode / isolation`,传入非空值时前端应看到明确 unsupported,而不是静默创建 child session
|
||||
|
||||
@@ -186,7 +186,7 @@ npm run smoke:claw-chat-current-fixture
|
||||
|
||||
如果本轮是在清退插件中心模块,插件安装 / 管理 / UI / RPC 命令族也必须同步从 `src/lib/api` 网关、页面入口、设置入口、Rust 注册、DevBridge 与默认 mock 中撤掉;当前 GUI 不再保留插件中心或动态插件侧栏入口。最低校验至少包含 `npm run test:contracts`、`npm run governance:legacy-report` 与受影响前端回归。
|
||||
|
||||
如果本轮涉及 `companion_*` 桌宠命令族,还要同步检查本地 companion `WebSocket` 入口、前端 `src/lib/api/companion.ts` 网关、Rust 注册、治理目录册以及浏览器模式 mock 返回形态;浏览器模式下这组命令默认也要保持可 mock,不要让桌宠接入把默认页面渲染链路卡死。
|
||||
`companion_*` 桌宠命令族已下线并归类为 `dead`。如果本轮发现 `companion_get_pet_status`、`companion_launch_pet`、`companion_send_pet_command`、`src/lib/api/companion.ts`、桌宠设置页、桌宠偏好或 `companion-pet-status` 事件重新出现在生产入口、Desktop Host、DevBridge、默认 mock 或 App Server 主链,应先按旧路回流处理并删除;不得把它们恢复成浏览器模式 mock fallback。
|
||||
|
||||
如果本轮涉及 team runtime 工具面或主线程用户消息工具,还要同步检查 Rust catalog / inventory、runtime 注册、浏览器 fallback mock 与前端 tool display;`Agent / TeamCreate / TeamDelete / SendMessage / ListPeers` 必须保持同一组 current surface,`SendUserMessage` 也必须继续停留在 current 主线程工具面,不要把已删除的 `SubAgentTask` compat 工具重新接回 Rust catalog、runtime 注册、mock 或前端 tool display。
|
||||
|
||||
@@ -489,7 +489,7 @@ CI 里的 `.github/workflows/quality.yml` 结果摘要现在也会透出 `bridge
|
||||
- 修改 `execution_runtime.recent_access_mode / recent_theme / recent_session_mode / recent_gate_key / recent_run_title / recent_content_id` 恢复语义,或前端 `harness.access_mode / harness.theme / harness.session_mode / harness.gate_key / harness.run_title / harness.content_id` steady-state 去重逻辑
|
||||
- 修改首页 / 工作区进入 `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`)
|
||||
- 发现已下线的 `companion_get_pet_status`、`companion_launch_pet`、`companion_send_pet_command`、`companion-pet-status` 或旧桌宠本地 companion 协议回流
|
||||
- 修改自动化设置 App Server method 族,例如 `automationJob/list`、`automationJob/create`、`automationJob/update`、`automationJob/health` 或 `automationJob/runHistory`;旧 `get_automation_*` / `*_automation_job` 命令已退役,只能作为负向回归扫描对象
|
||||
- 修改浏览器资料 / 环境预设命令族,或调整它们在 `mockPriorityCommands` 里的优先级
|
||||
- 修改浏览器连接器命令族,例如安装目录、启用状态、系统连接器、浏览器动作配置、扩展安装状态、打开 Chrome 扩展 / 远程调试页,或主动断开扩展连接
|
||||
|
||||
@@ -33,3 +33,7 @@
|
||||
| CCD-011 | 2026-05-09 | Task Center / Root Patch Scripts | 根目录遗留 `patch_dark.js`、`patch_flare.js`、`patch_navbar.js` 只是一次性字符串替换脚本;当前 Task Center Chrome 样式已收敛到 `ChatNavbar.tsx` 的 token 化主链,脚本目标 `taskCenterChromeShellClassName` 已不再存在 | 继续保留会让后续开发误判为可运行维护脚本,并可能把旧的字符串替换方式重新带回 UI 主链 | 已删除 3 个脚本,并在 `legacySurfaceCatalog` 中补 `root-task-center-patch-scripts` 守卫;后续不得恢复根目录补丁脚本式 UI 修改,Task Center 样式只沿现有组件和 token 主链演进 | `src/components/agent/chat/components/ChatNavbar.tsx` | 已完成 |
|
||||
| CCD-012 | 2026-06-09 | Frontend DevBridge / Command Governance | `src/lib/dev-bridge/**` 容易被误判为旧 Rust / Tauri DevBridge 的同类退场对象;当前实际应拆成 `current` renderer bridge(`safeInvoke`、HTTP client、`app_server_handle_json_lines`、事件监听、可用性探测)与 `compat / deprecated` 命令 policy / mock fallback 两部分治理。后续治理事实源已固定到本条执行规则与 production command current 迁移计划,不以聊天、handoff 或临时计划备注为准 | 若后续清命令时整目录删除,会破坏 Electron Desktop Host / App Server JSON-RPC 的 renderer 传输链;若继续把已迁旧命令留在 `commandPolicy.ts`、`mockPriorityCommands.ts`、desktop-host mock 或旧 smoke 正向证据中,又会让 retired 命令通过 mock / policy 回流成假 current | 后续每个命令组迁到 App Server / Electron Desktop Host current 后,同轮检查并收缩 `commandPolicy.ts`、`mockPriorityCommands.ts`、desktop-host mock、旧 smoke、catalog 和 retired guard;删不动且跨命令组长期存在的 legacy command policy / mock residual 必须同时登记到对应执行计划和本条 `CCD-012`,写清命令名、阻塞文件、分类、退出条件和下一次验证入口。下一小步按 `internal/exec-plans/production-command-current-migration-plan.md` 当前主线优先处理 P6 session files 工作台写读链或 P14 recording / transcription / polish / output side-effect,并把残留旧命令字符串分类为 `compat / deprecated`、`dead / retired guard-only` 或 `test-only`,不得只写“后续清理” | `internal/aiprompts/governance.md` / `internal/aiprompts/commands.md` / `internal/aiprompts/parallel-agent-collaboration.md` / `internal/aiprompts/quality-workflow.md` / `internal/roadmap/appserver/README.md` / `internal/exec-plans/production-command-current-migration-plan.md` | 进行中 |
|
||||
| CCD-013 | 2026-06-10 | Rust 主 crate / Tauri Wrapper | `lime-rs/src/**` 是脱离 cargo 构建图的孤儿目录(workspace 只含 `crates/*`,`src/` 没有 `lib.rs` / `main.rs` 也没有 `[package]` 段),曾沉积约 18.7 万行 / 113 个 `#[tauri::command]` / 19 个 `*_cmd.rs`,过去几天通过 50+ 个 TW-Q* 子任务串行抠删 | 持续制造治理噪音、误导接手 Agent、长期占据并行进程精力;删除是纯文本操作 | 已物理删除整个目录(含 `commands/services/dev_bridge/app/agent/skills/voice/tray/config/theme/data/tests/agent_tools` 与顶层 6 个 `.rs`),唯一活引用 `agent_tools/{catalog,execution,inventory}.rs` 已迁到 `lime-rs/crates/agent/src/agent_tools/`;3 份相关执行计划标 superseded;治理脚本 `check-app-server-client-contract.mjs` 删 16 个失效 invariant、`check-harness-contracts.mjs` 删 backend mapping 部分、`legacyToolPermissionGuard.test.ts` 移除 `lime-rs/src` 扫描根、`rustCommandsCurrentBoundary.test.ts` 重写为目录不可恢复守卫;AGENTS.md 12/13 条收紧;验证:`cargo check --workspace` 通过、`npm run test:contracts` 全过、`npx vitest run src/lib/governance` 187/187 通过 | `internal/exec-plans/tauri-wrapper-quick-cleanup-queue.md`(superseded)/ `internal/exec-plans/tauri-wrapper-command-inventory.md`(superseded)/ `internal/exec-plans/rust-commands-current-migration-cleanup-plan.md`(superseded)/ `src/lib/governance/rustCommandsCurrentBoundary.test.ts` | 已完成 |
|
||||
|
||||
## CCD-012 residual log
|
||||
|
||||
- `2026-06-10`:`agent_app_runtime_start_task`、`agent_app_runtime_cancel_task`、`agent_app_runtime_get_task`、`agent_app_runtime_submit_host_response` 已从 `src/lib/dev-bridge/commandPolicy.ts#bridgeTruthCommands` 降为 `noMockFallbackCompatCommands`。当前分类:`compat / fail-closed residual`。阻塞文件:`src/lib/api/agentAppRuntime.ts` 显式兼容网关、尚未迁完的既有桌面路径、显式兼容测试;`AgentRuntimeCapabilityHost` 默认已改为无标准 `AgentRuntimeClient` / 无显式 compat `api` 时 fail closed,不再隐式回退旧 facade。退出条件:所有 Agent App 宿主入口迁到标准 `AgentRuntimeClient` / App Server lifecycle 后,删除 `src/lib/api/agentAppRuntime.ts`,并把四个命令移出 no-mock compat 集合。验证入口:`src/features/agent-app/runtime/agentRuntimeCapabilityHost.test.ts`、`src/lib/dev-bridge/commandPolicy.test.ts`、`scripts/check-command-contracts.mjs`、`npm run test:contracts`。
|
||||
|
||||
@@ -211,14 +211,14 @@ Agent App 不只复用 UI runtime start/status/stop,而是复用 Agent turn ru
|
||||
8. 前端 `src/lib/api/agentRuntime/threadClient.ts` 的 turn lifecycle 已可消费标准 `AgentRuntimeClient` facade:`submit / cancel / respond / read` 先从旧 UI 请求 shape 投影为 App Server current 参数,再委托 `startTurn / cancelTurn / respondAction / readThread`。该文件仍是迁移期 compat gateway,负责旧 DTO、事件路由、read model 投影、file checkpoint / queue / compact / replay 等尚未进入标准 client 的业务面,不允许继续扩展成第二套 runtime SDK。
|
||||
9. 根依赖、TypeScript paths 与 Vite alias 已接入 `@limecloud/agent-runtime-client`,避免标准包只停留在 package 目录而无法被前端 current 网关消费。
|
||||
10. `@limecloud/agent-runtime-projection` 已承接 host-neutral 的 Agent UI projection event index / scope selector / latest selector。主聊天 `conversationProjectionStore` 只保留本地 external store、stream diagnostics 和兼容导出名,事件筛选与索引不再在主聊天目录内作为公共事实解释继续扩展。
|
||||
11. `@limecloud/agent-runtime-projection` 已承接 host-neutral 的 Agent UI summary selectors:事件类型分组、action / task / artifact / evidence / diagnostics 计数、notable latest events、Team Workbench surface / lane 聚合和 artifact latest lookup。主聊天 `agentUiProjectionSummary.ts` 只保留本地化 label、展示文案 formatter 和兼容导出名,不再作为这些 selector 的事实源。
|
||||
11. `@limecloud/agent-runtime-projection` 已承接 host-neutral 的 Agent UI summary selectors:事件类型分组、action / task / artifact / evidence / diagnostics 计数、notable latest events、Subagents surface / lane 聚合和 artifact latest lookup。主聊天 `agentUiProjectionSummary.ts` 只保留本地化 label、展示文案 formatter 和兼容导出名,不再作为这些 selector 的事实源。
|
||||
12. `internal/aiprompts/agent-ui-runtime-standard.md` 已成为四包职责、宿主接入、App Server runtime 配合、主聊天 projection 迁移分类和守卫要求的集中事实源;后续改标准包或主聊天 projection 前应先读该文档。
|
||||
13. `scripts/check-app-server-client-contract.mjs` 已守住标准包名入口,扫描根依赖、lockfile、TypeScript paths、Vite alias 和 `packages/*/package.json`,防止早期讨论包名回流成新的物理包或 alias。
|
||||
14. Agent App Runtime 已作为第二个真实宿主基线消费标准 projection:`agentRunProjectionState.ts` 从 host run state 生成标准 `AgentUiProjectionState`,`AgentRunProjectionPanel.tsx` 直接渲染 `AgentUiProjectionView`,宿主 summary / action callback / artifact presentation 保留在 Agent App adapter 层。
|
||||
|
||||
退出条件补充:
|
||||
|
||||
1. 新 Agent UI 页面默认走 `AgentRuntimeClient -> execution events -> projectAgentUiState -> AgentUiProjectionView`。
|
||||
1. 新 Agent UI 页面默认走 `AgentRuntimeClient -> projectAgentUiState -> AgentUiProjectionView`,中间的 execution events 适配由宿主边界负责。
|
||||
2. 不允许把组件本地树或 UI 本地状态定义成 runtime fact source。
|
||||
3. `readThread` 当前映射 `agentSession/read`;独立 `readTask` 只能等 App Server current method 出现后再接入。
|
||||
4. 至少两个真实宿主复用 `@limecloud/agent-ui-contracts`、`@limecloud/agent-runtime-projection` 与 `@limecloud/agent-runtime-ui` 后,才能继续收缩旧主聊天专用 projection / UI 壳。
|
||||
|
||||
@@ -118,7 +118,7 @@ AgentUI 可以消费 raw stream event、profile event、snapshot 和 read model
|
||||
RuntimeEvent / AgentRuntimeProfileEvent
|
||||
-> ThreadReadModel / TaskSnapshot / EvidenceSummary
|
||||
-> Agent UI envelope projection
|
||||
-> Workspace / Harness / Timeline / Task Capsule / Team Workbench
|
||||
-> Workspace / Harness / Timeline / Task Capsule / Subagents
|
||||
```
|
||||
|
||||
P1 允许 AgentUI 对 `schemaVersion = lime-profile-0.4.0` 的 dotted profile event 静默保持 stream 活跃;P2 开始必须把这些 facts 归入 `ThreadReadModel`,再由 AgentUI 只读展示。
|
||||
|
||||
@@ -77,7 +77,7 @@ AgentRuntime 标准正好把这些能力定义成 execution facts:
|
||||
| Permission / sandbox / hook / process | Claw/ToolRuntime 有相关事实,但 Agent App Host 可见度不足 | 将 permission、sandbox、hook、process 事件进入 projection diagnostics / evidence,不让 App 自建权限判断 | P1 |
|
||||
| Tool inventory snapshot | 标准要求当前 scope 下可用 tools / policy / capabilities 可解释 | `lime.skills / lime.tools / lime.models` 能力目录需要从 Host 投影给 App,App 只显示可用/不可用 | P1 |
|
||||
| History / recovery / snapshot repair | 单会话全流程仍红,说明状态递进和 task snapshot 稳定性还不够 | 打开历史 App task 时先用 snapshot 恢复,再按 sequence 补事件 | P2 |
|
||||
| Subagents / jobs / remote channels | 标准覆盖完整委派和远程工作,但内容工厂当前 P0 不是多 agent workbench | 先保持 solo task + required Skills;等 runtime 暴露 parent-child graph 后再进入 Team Workbench | P2 |
|
||||
| Subagents / jobs / remote channels | 标准覆盖完整委派和远程工作,但内容工厂当前 P0 不是多 agent workbench | 先保持 solo task + required Skills;等 runtime 暴露 parent-child graph 后再进入 Subagents | P2 |
|
||||
|
||||
## 6. 暂缓或禁止
|
||||
|
||||
@@ -88,7 +88,7 @@ AgentRuntime 标准正好把这些能力定义成 execution facts:
|
||||
| 内容工厂直连 OpenAI-compatible API 或 `LIME_GATEWAY_*` 完成主流程 | dead | 只能得到模型补全,拿不到 Skills、Tools、routing、cost、evidence、HITL。 |
|
||||
| 每个 App 复制 Claw `*_skill_launch.rs` | dead | 破坏 Claw capability catalog 复用,制造权限和证据旁路。 |
|
||||
| App 内完整实现 AgentUI runtime store | dead | UI projection 只能在 Host 层统一,App 只能消费标准视图。 |
|
||||
| Team Workbench / remote teammate 全量默认进入每个 App | deferred | 需要 subagent/job/channel runtime facts 先稳定。 |
|
||||
| Subagents / remote teammate 全量默认进入每个 App | deferred | 需要 subagent/job/channel runtime facts 先稳定。 |
|
||||
| 用最终正文反推 artifact、Skill 成功或 evidence pass | deprecated | 标准明确要求 missing facts 标为 `unknown/unavailable/stale/blocked`,不能猜。 |
|
||||
|
||||
## 7. current / compat / deprecated / dead 分类
|
||||
|
||||
@@ -33,7 +33,7 @@ AgentUI v0.6.1 定义的是 runtime-first projection 标准,核心能力包括
|
||||
|
||||
- Runtime event projection:`run.* / text.* / reasoning.* / tool.* / action.* / queue.* / artifact.* / evidence.* / session.* / team.*` 等 event class。
|
||||
- 标准表面:Composer、Message Parts、Runtime Status、Tool UI、Human-in-the-loop、Task Capsule、Artifact Workspace、Timeline / Evidence、Session / Tabs。
|
||||
- Team Workbench:Team Roster、Work Board、Delegation Graph、Handoff Lane、Worker Notifications、Review Lane、Teammate Transcript、Background / Remote Teammate、Team Policy。
|
||||
- Subagents:Subagent Threads、Delegation Calls、Handoff / Review Activity、Worker Notifications、Teammate Transcript、Background / Remote Teammate、Team Policy。
|
||||
- Client implementation:projection store、progressive rendering、session hydration、queue vs steer、controlled writes、performance metrics。
|
||||
- Agent Runtime profile tests:验证 `RuntimeEvent / ThreadReadModel / TaskSnapshot / EvidencePack` 能投影到 UI,同时 UI 不变成 runtime truth。
|
||||
|
||||
@@ -79,7 +79,7 @@ Agent App MVP 必须先满足:真实 runtime facts -> Host Run UI -> 业务 ar
|
||||
| Evidence / Review / Replay lane | Evidence refs 已可投影,完整 Evidence panel、review verdict、replay lane 未统一 | 使用 `agent_runtime_export_evidence_pack` 作为单一事实源,Review / Replay 不再各自重算状态 | P1 |
|
||||
| Session hydration | AgentRuntime 有 ThreadReadModel,Agent App Host Run UI 还没有完整旧 session 渐进恢复体验 | 打开历史任务时先 shell / snapshot / recent process,再懒加载 timeline、tool output、artifact、evidence | P2 |
|
||||
| Performance metrics | completion gates 记录结果,但 Host UI 尚未系统展示 submit-to-status / first-text / paint latency | 在 projection 或 evidence 中保留 `metric.changed`,普通 UI 低噪声,诊断视图可检查 | P2 |
|
||||
| Team Workbench | Lime AgentRuntime / Claw 有 subagent、team prompt、spawn/wait/close 能力,但 Agent App Host UI 未投影 team surfaces | 只有当 runtime 暴露 parent/child lineage、worker notification、team phase 后,才进入 roster / delegation / review lane | P2 |
|
||||
| Subagents | Lime AgentRuntime / Claw 有 subagent、team prompt、spawn/wait/close 能力,但 Agent App Host UI 未投影 subagent surfaces | 只有当 runtime 暴露 parent/child lineage、worker notification、team phase 后,才进入 threads / delegation / review activity | P2 |
|
||||
|
||||
## 5. 暂缓或不应直接采用的部分
|
||||
|
||||
@@ -91,7 +91,7 @@ Agent App MVP 必须先满足:真实 runtime facts -> Host Run UI -> 业务 ar
|
||||
| AgentUI event schema 直接替代 AgentRuntime | 禁止 | AgentUI 是 projection schema,不拥有 execution、policy、artifact、evidence truth。 |
|
||||
| App 内实现完整 AgentUI runtime | 禁止 | 会让业务 App 变成第二套 Agent Runtime,回到“每个 App 自建 AI 同事”的问题。 |
|
||||
| 业务 App 直连模型 / provider API | 禁止 | 绕过 Lime model routing、Skills、ToolRuntime、usage、cost、policy、Evidence。 |
|
||||
| Team Workbench 全量表面一次性落地 | 暂缓 | 需要 runtime 先稳定暴露 `agent.spawned / worker.notification / team.changed / handoff / review` facts。 |
|
||||
| Subagents 全量表面一次性落地 | 暂缓 | 需要 runtime 先稳定暴露 `agent.spawned / worker.notification / team.changed / handoff / review` facts。 |
|
||||
| Remote teammate / background teammate UI | 暂缓 | 需要 remote task / background scheduler 的 durable facts;不能用普通 running 文案伪造。 |
|
||||
| Work Board 作为所有 Agent App 默认壳 | 暂缓 | 不是所有业务 App 都是看板心智;只能在 runtime 有 work item truth 时投影。 |
|
||||
|
||||
@@ -108,7 +108,7 @@ Agent App MVP 必须先满足:真实 runtime facts -> Host Run UI -> 业务 ar
|
||||
| Artifact Workspace | 内容工厂 workspace patch、artifact refs 已接入 | 通用 preview / edit / version / diff / export 还未抽象 | 内容工厂可先写回业务对象,通用 artifact workspace 需要补。 |
|
||||
| Timeline / Evidence | facts rail、timeline、Evidence refs 已可见 | Evidence detail / replay / review lane 仍分散 | 可作为 MVP,但完整 evidence 审计仍要回到 EvidencePack。 |
|
||||
| Session / Tabs | AgentRuntime 有 thread read snapshot | Agent App Host UI 未实现旧任务渐进恢复和 tab resource policy | 暂不作为内容工厂阻塞项,列入 Host 层后续。 |
|
||||
| Team Workbench | Claw / AgentRuntime 有 subagent 和 team runtime 基础 | Agent App 无 roster / delegation / worker notification / handoff lane | 暂缓全量;先保证 solo_run 与 required Skills。 |
|
||||
| Subagents | Claw / AgentRuntime 有 subagent 和 team runtime 基础 | Agent App 无 threads / delegation / worker notification / handoff activity | 暂缓全量;先保证 solo_run 与 required Skills。 |
|
||||
| Diagnostics / Metrics | Smoke evidence、completion audit、cost/usage facts 已有 | 用户可见 latency metrics、safe diagnostics surface 不完整 | 先用于测试和 evidence,后续进入 Host 诊断视图。 |
|
||||
|
||||
## 6.1 继续审计:Lime 已有 AgentUI 投影资产
|
||||
@@ -120,7 +120,7 @@ Agent App MVP 必须先满足:真实 runtime facts -> Host Run UI -> 业务 ar
|
||||
| 标准 event / owner / scope / surface 类型 | `src/components/agent/chat/projection/agentUiEventProjection.ts` 定义 `AgentUiProjectionEvent`、`AgentUiEventClass`、`AgentUiSurface`、`AgentUiRuntimeEntity`、`AgentUiTopology` | 不应在 Agent App 里再定义一套 AgentUI 类型。 |
|
||||
| Claw runtime event -> AgentUI projection adapter | `buildAgentUiProjectionEvents(event, context)` 已覆盖 `run.* / text.delta / reasoning.delta / tool.* / action.* / queue.changed / artifact.* / evidence.changed / team.*` | Agent App 下一刀应接入或复用该 adapter,而不是从 `task:*` 直接渲染到底。 |
|
||||
| Projection store | `conversationProjectionStore.ts` 提供 record / select by type / surface / scope / tool / action / artifact / evidence | Host Run UI 可使用同一个 projection store 或同构 reducer,避免第二套 UI state owner。 |
|
||||
| Team Workbench 投影与视图模型 | `agentUiTeamWorkbenchViewModel.ts`、`AgentUiTeamWorkbenchSurfaceView.tsx` 已存在 | Team Workbench 不是零实现;缺口是 Agent App runtime facts 尚未投影和挂到 Host Run UI。 |
|
||||
| Subagents 投影与视图模型 | `agentUiSubagentsViewModel.ts`、`AgentUiProjectionState.subagents`、`SubagentsView` 已存在 | Subagents 不是零实现;缺口是 Agent App runtime facts 尚未投影和挂到 Host Run UI。 |
|
||||
| Claw stream 运行时接入 | `agentStreamRuntimeHandler.ts` 调用 `buildAgentUiProjectionEvents` 并记录 projection events | Claw 已经证明 stream -> AgentUI projection 的路径可行;Agent App 应复用这条路径。 |
|
||||
| Team runtime 接入 | `team-workspace-runtime/runtimeEventSubscriptions.ts` 已把 subagent/team stream 记录为 AgentUI projection events | Agent App 的 team / subagent 后续应收敛到同一 projection,而不是另写 roster / worker notification。 |
|
||||
| 后端 Agent App task projection first-cut | 当前脏工作树中 `runtime_turn.rs` 已把 profile/runtime events 投影为 `task:runtimeEvent` payload,含 `taskEvents`、`runtimeEvent`、`streamKind`、artifact/evidence/action/tool 映射 | 后端已有 first-cut,但仍是 Agent App task event 形态;前端还需要桥接到 AgentUI event class。 |
|
||||
@@ -141,7 +141,7 @@ Agent App MVP 必须先满足:真实 runtime facts -> Host Run UI -> 业务 ar
|
||||
2. 后端 task projection first-cut 仍输出 `task:*` 事件;前端 bridge 已可标准化为 `run.* / tool.* / action.* / artifact.* / evidence.*`,但尚未挂入 Host UI。
|
||||
3. Ordered parts 的 state adapter、view model 和独立 panel 已可按 projection `sequence / partId / toolCallId / actionId` 排序,但现有 Host Run 抽屉尚未用它替换 `timeline + thinking + execution + output` 分区。
|
||||
4. HITL 只有 request facts 和 submit host response 命令,Host Run UI 还缺 `action.required -> action.resolved` 的受控卡片闭环。
|
||||
5. 现有 Claw AgentUI projection 的 Team / Remote / Background 能力不能直接宣称 Agent App 已支持;需要 Agent App runtime facts 接入后才算。
|
||||
5. 现有 Claw AgentUI projection 的 Subagents / Remote / Background 能力不能直接宣称 Agent App 已支持;需要 Agent App runtime facts 接入后才算。
|
||||
|
||||
## 6.2 Schema parity 与 Agent App 可用子集
|
||||
|
||||
@@ -181,7 +181,7 @@ P1/P2 才进入的子集:
|
||||
2. **Artifact Workspace**:preview、edit、version、diff、export 必须先由 Host artifact service 持有。
|
||||
3. **Evidence Review / Replay Lane**:必须消费 EvidencePack / review / replay 事实,不能 UI 推断。
|
||||
4. **Session Hydration**:历史任务先恢复 snapshot,再懒加载 full timeline / tool output / evidence。
|
||||
5. **Team Workbench**:等 Agent App runtime 暴露 parent/child lineage、worker notification、handoff、review facts 后再启用。
|
||||
5. **Subagents**:等 Agent App runtime 暴露 parent/child lineage、worker notification、handoff、review facts 后再启用。
|
||||
|
||||
## 7. 对内容工厂和后续 Agent App 的落地规则
|
||||
|
||||
@@ -219,7 +219,7 @@ P0: Agent App Host -> existing AgentUI projection bridge
|
||||
| AgentUI 标准要和 Lime 对比,找出哪些可用于 Agent App | 本文档第 3-6.2 节已经按能力、surface 与 schema parity 分类 | done | 保持文档随实现更新。 |
|
||||
| Lime 不是所有 AgentUI 都支持 | 第 4、5、6、6.1、6.2 节列出缺口和暂缓项 | done | 缺口进入 P0/P1/P2。 |
|
||||
| Agent App 不应每个 App 自己实现 AI 同事 | 第 1、7 节明确 Host Run UI 是 current,App 内 fallback 是 compat | done | 后续用守卫/测试封住 App 私有运行 UI 回流。 |
|
||||
| 需要复用 Lime / Claw 已有 UI 封装 | 第 6.1 节确认 Claw 已有 AgentUI projection adapter/store/Team view model;新增 `agentUiProjectionBridge.ts` 复用同一 `AgentUiProjectionEvent` 类型;新增 `agentUiProjectionViewModel.ts` 生成 Host Run ordered parts;新增 `AgentRunProjectionPanel.tsx` 独立渲染 projection view;新增 `agentRunProjectionState.ts` 适配现有 Host Run state | partial | 现有 `AgentRunHostDrawer.tsx` 还没接入 projection panel。 |
|
||||
| 需要复用 Lime / Claw 已有 UI 封装 | 第 6.1 节确认 Claw 已有 AgentUI projection adapter/store/Subagents view model;新增 `agentUiProjectionBridge.ts` 复用同一 `AgentUiProjectionEvent` 类型;新增 `agentUiProjectionViewModel.ts` 生成 Host Run ordered parts;新增 `AgentRunProjectionPanel.tsx` 独立渲染 projection view;新增 `agentRunProjectionState.ts` 适配现有 Host Run state | partial | 现有 `AgentRunHostDrawer.tsx` 还没接入 projection panel。 |
|
||||
| 内容工厂需要真实 AI Agent 能力,不是直调 API | 现有 roadmap / completion audit 记录 required Skills、model、usage、cost、artifact、evidence gates;本轮未重跑全流程 | partial | 继续由 runtime/GUI 持有写集进程跑 completion E2E。 |
|
||||
| 运行过程要有思考、执行、工具、Skill、流式输出且不消失 | Host first-cut 已有 `ThinkingBlock`、`InlineToolProcessStep`、Markdown renderer;`agentRunProjectionState.ts -> agentUiProjectionBridge.ts -> agentUiProjectionViewModel.ts -> AgentRunProjectionPanel.tsx` 已形成独立 projection renderer 链路;projection view model 已保证 running tool/process 默认展开、终态工具/status 折叠、最终成稿保持展开;projection panel 已把 Artifact / Evidence refs 渲染为独立卡片 | partial | 让现有 Host drawer 使用 projection panel,并补 Claw renderer 深复用。 |
|
||||
| 模型、Token、费用等要支持 | `runtimeProcess.model / usage / cost` 和 Host metric cards 已有;Agent App projection bridge 已支持 `metric.changed` diagnostics,view model / panel 可把模型、Token、费用指标渲染为 diagnostics 卡片 | done/first-cut | Host Drawer 接入后继续补 cost/limit diagnostics 视觉验收。 |
|
||||
@@ -277,7 +277,7 @@ P0: Agent App Host -> existing AgentUI projection bridge
|
||||
|
||||
AgentUI 中 **核心 runtime projection、message parts、Tool UI、HITL、Artifact、Evidence、Task Capsule、Runtime Profile tests** 都适合进入 Agent App;但进入方式必须是 Lime Host 统一封装,而不是每个 App 直接引入一套 AgentUI 实现。
|
||||
|
||||
Lime 现在已经具备 `solo_run + required Skills + Host Run UI first-cut` 的基础,足够支撑内容工厂继续验证真实业务流程;但还不能宣称完整支持 AgentUI v0.6.1,因为 ordered parts、HITL 控件、Artifact Workspace、Evidence Review/Replay、Session Hydration、Team Workbench 仍未完整落地。
|
||||
Lime 现在已经具备 `solo_run + required Skills + Host Run UI first-cut` 的基础,足够支撑内容工厂继续验证真实业务流程;但还不能宣称完整支持 AgentUI v0.6.1,因为 ordered parts、HITL 控件、Artifact Workspace、Evidence Review/Replay、Session Hydration、Subagents 仍未完整落地。
|
||||
|
||||
最终产品边界保持不变:
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ agent_runtime_submit_turn
|
||||
|
||||
| 要求 | 状态 | 证据 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| 分析 Codex / Claude Code / 公开方案并形成 Lime 标准化方向 | done | `internal/roadmap/agentruntime/prd.md:21` | PRD 固定“公开标准 + Lime 严格 profile + current runtime 实现映射”。 |
|
||||
| 分析公开方案 / Claude Code / 运行时实现参考并形成 Lime 标准化方向 | done | `internal/roadmap/agentruntime/prd.md:21` | PRD 固定“公开标准 + Lime 严格 profile + current runtime 实现映射”。 |
|
||||
| 在 Lime `internal/roadmap/agentruntime` 写 PRD、背景、目的、收益、用户故事 | done | `internal/roadmap/agentruntime/prd.md:8`、`internal/roadmap/agentruntime/prd.md:43`、`internal/roadmap/agentruntime/prd.md:96`、`internal/roadmap/agentruntime/prd.md:221` | PRD 覆盖背景、目标、收益、用户故事、范围和验收。 |
|
||||
| 增加架构图、流程图、时序图 | done | `internal/roadmap/agentruntime/diagrams.md:7`、`internal/roadmap/agentruntime/diagrams.md:56`、`internal/roadmap/agentruntime/diagrams.md:84` | 图纸集覆盖总体架构、相邻协议协同、主流程、submit turn、tool approval、evidence export、task retry、remote/subagent、UI 只读投影。 |
|
||||
| 增加测试用例文档 | done | `internal/roadmap/agentruntime/test-cases.md:1` | 测试矩阵覆盖 schema、identity、read model、routing、tool/action、evidence/replay/review、相邻协议、GUI smoke 和治理守卫。 |
|
||||
| 更新 AgentUI 匹配方案 | done | `src/components/agent/chat/projection/agentUiProjectionSummary.ts`、`src/components/agent/chat/projection/agentUiTeamWorkbenchViewModel.ts`、`src/components/agent/chat/components/AgentUiTeamWorkbenchSurfaceView.tsx` | AgentUI current projection 已接 key-based presentation mapper;Team Workbench / Harness / Reliability / Artifact timeline 已消费 mapper。 |
|
||||
| 更新 AgentUI 匹配方案 | done | `src/components/agent/chat/projection/agentUiProjectionSummary.ts`、`src/components/agent/chat/projection/agentUiSubagentsViewModel.ts`、`packages/agent-runtime-ui/src/subagents.tsx` | AgentUI current projection 已接 key-based presentation mapper;Subagents / Harness / Reliability / Artifact timeline 已消费 mapper。 |
|
||||
| 对齐 AgentContext / AgentEvidence / AgentPolicy | done | `internal/roadmap/agentruntime/adjacent-protocols.md:1`、`lime-rs/src/services/runtime_agent_profile_projection_service.rs:16`、`lime-rs/src/services/runtime_evidence_pack_service.rs` | 相邻协议被固定为 owner refs;Runtime 只串联执行事实,不吞并上下文、策略和证据 schema。 |
|
||||
| 修复 `agentruntime_profile` private module 编译错误 | done | `lime-rs/src/commands/aster_agent_cmd/mod.rs:340`、`lime-rs/src/commands/aster_agent_cmd/mod.rs:400` | 模块保持私有实现,向 crate 内 re-export `AgentRuntimeProfileEvent/Stream`,避免 service 直接依赖私有 module path。 |
|
||||
| 拆分 `runtime_evidence_pack_service.rs` | done | `lime-rs/src/services/runtime_evidence_request_telemetry_service.rs:1`、`lime-rs/src/services/runtime_agent_profile_projection_service.rs:1`、`lime-rs/src/services/runtime_evidence_completion_audit_service.rs:1`、`lime-rs/src/services/runtime_evidence_modality_contract_service.rs:1`、`lime-rs/src/services/runtime_evidence_auxiliary_runtime_service.rs:1`、`lime-rs/src/services/runtime_evidence_verification_service.rs:1`、`lime-rs/src/services/runtime_evidence_observability_service.rs:1`、`lime-rs/src/services/runtime_evidence_gap_service.rs:1`、`lime-rs/src/services/runtime_evidence_pack_output_service.rs:1`、`lime-rs/src/services/runtime_evidence_markdown_locale_service.rs:1`、`lime-rs/src/services/runtime_evidence_pack_service_tests.rs:1` | 已拆出 request telemetry、profile projection、completion audit / controlled GET evidence、modality contract、auxiliary runtime、verification / artifact validator、observability / signal coverage、known gaps、pack output renderer、Markdown locale copy、artifact index、JSON/path/tool helper 与大体量单测 fixture;主编排已降到约 377 行。 |
|
||||
@@ -55,7 +55,7 @@ agent_runtime_submit_turn
|
||||
| P3 Evidence 同源导出 | done | `lime-rs/src/services/runtime_evidence_pack_service.rs`、`lime-rs/src/services/runtime_evidence_request_telemetry_service.rs`、`lime-rs/src/services/runtime_replay_case_service.rs:128`、`lime-rs/src/services/runtime_analysis_handoff_service.rs:209` | `evidence_runtime_should_export_agent_runtime_profile_spine`、request telemetry 空摘要测试、replay/analysis/review 定向测试 | Evidence facts JSON 同源完成;Markdown presentation locale 属于 PX,已完成 current 结构标题与核心标签。 |
|
||||
| P4 Task / routing / permission / tool | done | `lime-rs/src/commands/aster_agent_cmd/agentruntime_profile.rs:294`、`lime-rs/src/commands/aster_agent_cmd/runtime_task_profile.rs:52`、`lime-rs/src/commands/aster_agent_cmd/runtime_turn.rs:6001` | `routing_events_match_single_candidate_fact_shape`、`runtime_tool_profile_should_follow_real_tool_start_and_end_once`、`runtime_tool_profile_should_fallback_to_item_tool_call_failure`、`collect_runtime_request_resolution_side_events_should_emit_routing_not_possible` | Policy allow/deny/ask facts 已进入 profile;更细的 obligation UI 展示可后续增强。 |
|
||||
| P5 Subagent / Job / Remote Channel | done | `lime-rs/src/services/runtime_agent_profile_projection_service.rs:385`、`lime-rs/src/services/runtime_agent_profile_projection_service.rs:467`、`lime-rs/src/services/runtime_agent_profile_projection_service.rs:648` | `evidence_runtime_should_export_subagent_parent_child_profile_events`、`evidence_runtime_should_export_job_profile_events_from_owner_runs`、`evidence_runtime_should_export_job_item_profile_events_from_owner_run_metadata`、`evidence_runtime_should_export_remote_channel_resume_repair_profile_events` | 已覆盖 owner run / parent-child / resume-repair facts;真实远端端到端恢复可作为产品级 E2E 后续。 |
|
||||
| PX 全球本地化守卫 | done/current, weak/legacy | `src/components/agent/chat/projection/agentUiProjectionSummary.ts`、`src/i18n/resources/*/agent.json`、`lime-rs/src/services/runtime_evidence_markdown_locale_service.rs` | `agentUiTeamWorkbenchViewModel` locale 单测、5 locale key count 一致、i18n namespace tests、Evidence/Replay/Analysis/Review Markdown locale Rust 定向测试 | AgentRuntime projection 与 current Markdown 导出结构标题已本地化;legacy diagnostics 和正文级 copy 完整化不是本轮完成口径。 |
|
||||
| PX 全球本地化守卫 | done/current, weak/legacy | `src/components/agent/chat/projection/agentUiProjectionSummary.ts`、`src/i18n/resources/*/agent.json`、`lime-rs/src/services/runtime_evidence_markdown_locale_service.rs` | `agentUiSubagentsViewModel` locale 单测、5 locale key count 一致、i18n namespace tests、Evidence/Replay/Analysis/Review Markdown locale Rust 定向测试 | AgentRuntime projection 与 current Markdown 导出结构标题已本地化;legacy diagnostics 和正文级 copy 完整化不是本轮完成口径。 |
|
||||
|
||||
## 5. 关键测试用例覆盖
|
||||
|
||||
@@ -67,7 +67,7 @@ agent_runtime_submit_turn
|
||||
| AR-ROUTE | done | routing single candidate / not possible / multi candidate decided 已有 stream 方法、projection 和 evidence tests。 |
|
||||
| AR-ACTION / AR-POL | done | policy/action profile shape、tool hook real event 优先、ToolCall fallback 去重、permission signal coverage tests。 |
|
||||
| AR-EVID / AR-EVID-LINK | done | `agent_runtime_export_evidence_pack` 输出 `runtime.json` / `artifacts.json` / observability correlation;request telemetry 无匹配时输出空摘要。 |
|
||||
| AR-UI-LINK | done | `AgentUI projection presentation 使用 i18n key` 已在 projection mapper、Team Workbench、Harness、Reliability、Artifact timeline 单测覆盖。 |
|
||||
| AR-UI-LINK | done | `AgentUI projection presentation 使用 i18n key` 已在 projection mapper、Subagents、Harness、Reliability、Artifact timeline 单测覆盖。 |
|
||||
| AR-CONTRACT | done | 本轮未新增命令边界;已有 `agent_runtime_*` current command contract 在既有 contract 测试口径内。 |
|
||||
| AR-GUI | done/weak | `npm run verify:gui-smoke` 已覆盖 GUI 壳、DevBridge、workspace、runtime tool surface 等最小主路径;真实 tool approval 人机交互可继续补 Playwright 场景。 |
|
||||
| AR-GOV | done/weak | current/deprecated/dead 分类已写入路线图;更强自动扫描守卫可在后续治理轮次继续收。 |
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
- 本地结构测试能用真实 runtime 输出构造 `submit-turn-event.json` 等价 fixture。
|
||||
- 无 `session/thread/turn` 的 runtime 主路径事件不能进入 current read model。
|
||||
- 前端未知事件控制器能识别 Lime Profile 事件为已知旁路事实,不污染用户可见 projection。
|
||||
- AgentUI projection presentation mapper 支持用 key-based i18n 渲染 event type、phase/status、control、surface;Team Workbench、Harness、可靠性诊断和 Artifact timeline 这类 current projection 入口不得退回中文 fallback,不把 stable facts 翻译回写到 runtime/read model。
|
||||
- AgentUI projection presentation mapper 支持用 key-based i18n 渲染 event type、phase/status、control、surface;Subagents、Harness、可靠性诊断和 Artifact timeline 这类 current projection 入口不得退回中文 fallback,不把 stable facts 翻译回写到 runtime/read model。
|
||||
- `turn.submitted / model.failed / task.failed` 等 profile fixtures 的 `type/status/failureCategory` 在不同 locale 下保持不变。
|
||||
|
||||
## 5. P2:ThreadReadModel 与 Context 收口
|
||||
|
||||
@@ -173,7 +173,7 @@ Owner 侧对应测试文档:
|
||||
| AR-EVID-LINK-003 | completeness 按分类声明 | evidence export | 至少按 `runtime/telemetry/sources/claims/artifacts/verification/privacy/replay` 中适用分类输出状态 |
|
||||
| AR-UI-LINK-001 | AgentUI 静默兼容 P1 profile event | `schemaVersion=lime-profile-0.4.0` 且 `type=turn.submitted` | unknown event controller 不告警,保持 stream 活跃 |
|
||||
| AR-UI-LINK-002 | AgentUI 不把 profile event 写成 UI truth | dotted profile event stream | 只进入 runtime/read-model 投影或忽略;不得创建 `agentui_profile_store` / UI-only runtime status |
|
||||
| AR-UI-LINK-003 | AgentUI projection presentation 使用 i18n key | Team Workbench / Harness / Reliability / Artifact timeline 的 projection event、phase、control、source、surface | `buildAgentUiTeamWorkbenchViewModel(..., { t })` 与 current projection 组件能输出 locale 文案;`type/status/phase/control/sourceType` stable facts 不被翻译回写 |
|
||||
| AR-UI-LINK-003 | AgentUI projection presentation 使用 i18n key | Subagents / Harness / Reliability / Artifact timeline 的 projection event、phase、control、source、surface | `buildAgentUiSubagentsViewModel(..., { t })`、`AgentUiProjectionState.subagents` 与 `SubagentsView` 能输出 locale 文案;`type/status/phase/control/sourceType` stable facts 不被翻译回写 |
|
||||
|
||||
## 12. Contract / command mapping 用例
|
||||
|
||||
@@ -245,7 +245,7 @@ npm run governance:legacy-report
|
||||
| 改 evidence/replay/review | Evidence Consistency 用例 + `npm run test:contracts` |
|
||||
| 改 Workspace/Harness runtime 展示 | GUI smoke + ReadModel 用例 |
|
||||
| 改 AgentContext / AgentPolicy / AgentEvidence linkage | 对应 AR-CTX / AR-POL / AR-EVID-LINK 用例 + 定向 owner ref 测试 |
|
||||
| 改 AgentUI profile event 兼容 / presentation mapper | `agentStreamUnknownEventController` / `agentStreamListenerReadinessController` / `agentUiTeamWorkbenchViewModel` / `AgentUiTeamWorkbenchSurfaceView` / `TeamWorkbenchSummaryPanel` / `HarnessStatusPanel` / `AgentThreadReliabilityPanel` / `AgentThreadTimelineArtifactCard` 定向单测 |
|
||||
| 改 AgentUI profile event 兼容 / presentation mapper | `agentStreamUnknownEventController` / `agentStreamListenerReadinessController` / `agentUiSubagentsViewModel` / `SubagentsView` / `HarnessStatusPanel` / `AgentThreadReliabilityPanel` / `AgentThreadTimelineArtifactCard` 定向单测 |
|
||||
| 改 deprecated/compat 路径 | Governance guard + 相关 contract 用例 |
|
||||
| 改 Rust runtime 主链 | App Server / RuntimeCore 定向 Rust 测试 + Runtime identity / Event 用例 + 必要时 `npm run verify:local` |
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
> 状态:v0.6.0 标准对齐完成;后续只跟踪 future boundary 与 v0.5 尾项增强
|
||||
> 更新时间:2026-05-11
|
||||
> 标准版本:Agent UI `v0.6.0`,commit `dcf4bc5`(`Release Agent UI v0.6.0 team workbench`)
|
||||
> 范围:对齐 `/Users/coso/Documents/dev/ai/limecloud/agentui` v0.6.0、Claude Code、Codex、Vercel AI SDK、assistant-ui、LangGraph、OpenAI ChatKit/Apps SDK 等调研结论,并回挂 Lime 当前实现缺口。
|
||||
> 标准版本:Agent UI `v0.6.0`,commit `dcf4bc5`
|
||||
> 范围:对齐 `/Users/coso/Documents/dev/ai/limecloud/agentui` v0.6.0、Claude Code、Vercel AI SDK、assistant-ui、LangGraph、OpenAI ChatKit/Apps SDK 等调研结论,并回挂 Lime 当前实现缺口。
|
||||
|
||||
## 1. 主目标
|
||||
|
||||
Lime AgentUI 的主目标不是只修复 thinking 样式,而是把对话工作台全流程收敛到一个可观察、可控制、可交付、可追溯的 projection 主链。v0.6.0 之后,主链范围从 `Conversation + Process + Task + Artifact + Evidence` 扩展到 `Team Workbench`,但 Lime 不因此新增第四类 runtime taxonomy。
|
||||
Lime AgentUI 的主目标不是只修复 thinking 样式,而是把对话工作台全流程收敛到一个可观察、可控制、可交付、可追溯的 projection 主链。v0.6.0 之后,主链范围从 `Conversation + Process + Task + Artifact + Evidence` 扩展到 `Subagents`,但 Lime 不因此新增第四类 runtime taxonomy。
|
||||
|
||||
事实源声明:
|
||||
|
||||
@@ -15,7 +15,7 @@ Lime AgentUI 的主目标不是只修复 thinking 样式,而是把对话工作
|
||||
Agent runtime event / subagent runtime event / automation job event / remote task event
|
||||
-> Agent UI envelope projection
|
||||
-> conversationProjectionStore.agentUi
|
||||
-> Conversation / Inline Process / Task Capsule / Artifact / Evidence / Diagnostics / Team Workbench UI
|
||||
-> Conversation / Inline Process / Task Capsule / Artifact / Evidence / Diagnostics / Subagents UI
|
||||
```
|
||||
|
||||
固定规则:
|
||||
@@ -24,7 +24,7 @@ Agent runtime event / subagent runtime event / automation job event / remote tas
|
||||
2. Lime execution taxonomy 仍只承认 `agent turn`、`subagent turn`、`automation job` 三类一等执行实体。
|
||||
3. Agent UI `runtimeEntity` 映射只能落到:`agent_turn`、`subagent_turn`、`automation_job`,以及 adapter 层的 `external_task`、`work_item`。
|
||||
4. `scheduler tick`、`execution run`、worker notification、review lane、remote heartbeat 都不能反向定义新的 Lime runtime taxonomy。
|
||||
5. Team Workbench 是 projection surface,不是新的执行引擎。
|
||||
5. Subagents 是 projection surface,不是新的执行引擎。
|
||||
|
||||
## 2. 标准引用与追溯页
|
||||
|
||||
@@ -32,11 +32,11 @@ Agent UI 标准引用统一落到专门页面,便于后续追溯,不在 Lime
|
||||
|
||||
| 类型 | 路径 / URL | 用途 |
|
||||
| --- | --- | --- |
|
||||
| v0.6.0 版本说明 | `/Users/coso/Documents/dev/ai/limecloud/agentui/docs/zh/versions/v0.6.0/overview.md` | 确认 Team Workbench、team event、runtime alignment 的版本边界。 |
|
||||
| v0.6.0 版本说明 | `/Users/coso/Documents/dev/ai/limecloud/agentui/docs/zh/versions/v0.6.0/overview.md` | 确认 Subagents、team event、runtime alignment 的版本边界。 |
|
||||
| v0.6.0 changelog | `/Users/coso/Documents/dev/ai/limecloud/agentui/docs/zh/versions/v0.6.0/changelog.md` | 追踪新增 surfaces、controls、event classes 与 schema 字段。 |
|
||||
| v0.6.0 specification | `/Users/coso/Documents/dev/ai/limecloud/agentui/docs/zh/versions/v0.6.0/specification.md` | 规范级字段、事件与投影规则。 |
|
||||
| 全流程 taxonomy | `/Users/coso/Documents/dev/ai/limecloud/agentui/docs/zh/reference/flow-and-taxonomy.md` | Conversation、Process、Task、Team、Artifact、Evidence、Diagnostics 的全流程分类。 |
|
||||
| 来源索引 | `/Users/coso/Documents/dev/ai/limecloud/agentui/docs/zh/reference/source-index.md` | Claude Code、Codex、AI SDK、assistant-ui、LangGraph、ChatKit 等来源追溯。 |
|
||||
| 来源索引 | `/Users/coso/Documents/dev/ai/limecloud/agentui/docs/zh/reference/source-index.md` | Claude Code、AI SDK、assistant-ui、LangGraph、ChatKit 等来源追溯。 |
|
||||
| Runtime projection contract | `/Users/coso/Documents/dev/ai/limecloud/agentui/docs/zh/contracts/runtime-event-projection.md` | runtime source event 到 Agent UI envelope 的投影约束。 |
|
||||
| AgentRuntime Profile | `/Users/coso/Documents/dev/ai/limecloud/agentruntime/docs/zh/specification.md`、`internal/roadmap/agentruntime/README.md` | Lime runtime facts 主链与 `lime-profile-0.4.0` profile event 边界。 |
|
||||
| AgentContext | `/Users/coso/Documents/dev/ai/limecloud/agentcontext/docs/zh/specification.md` | context envelope、selection、budget、compaction、missing context 的 owner 边界。 |
|
||||
@@ -57,14 +57,14 @@ Agent UI 标准已经从“thinking/progressive rendering 局部规则”扩展
|
||||
| Tool lifecycle | input streaming、input available、running、progress、output available、output error 分阶段。 | 真实 source event 投影为 `tool.args.delta`、`tool.progress`、`tool.output.delta`、`tool.result`、`tool.failed`。 |
|
||||
| HITL / action | approval、plan decision、structured input、clarification、permission grant 保持结构化,不从 assistant 文本反推。 | `action.required` / `action.resolved` 已进入 projection;delegated approval 已补 plan approval 请求方 / 目标 session / 权限 / plan ref 结构化显示。 |
|
||||
| Task / queue | foreground turn、queued steer、task capsule、background job 独立分类。 | queue/steer、`agent_turn/subagent_turn/automation_job` 已进入 projection;`work_item` 通过 team formation / review fix / TaskUpdate owner change 接入;`external_task` 通过 remote task metadata 接入。 |
|
||||
| Team Workbench surfaces | `team_roster`、`work_board`、`delegation_graph`、`handoff_lane`、`worker_notifications`、`review_lane`、`teammate_transcript`、`background_teammate`、`remote_teammate`、`team_policy`。 | 10 个标准 surface 已进入 summary、lane、surface detail、操作视图与 action target 定位;后续只补更深 teammate drilldown 和 future command boundary。 |
|
||||
| Subagents surfaces | `team_roster`、`work_board`、`delegation_graph`、`handoff_lane`、`worker_notifications`、`review_lane`、`teammate_transcript`、`background_teammate`、`remote_teammate`、`team_policy`。 | 10 个标准 surface 已进入 summary、lane、surface detail、操作视图与 action target 定位;后续只补更深 teammate drilldown 和 future command boundary。 |
|
||||
| Team topology | `coordinator_team`、`parallel_workers`、`specialist_handoff`、`review_team`、`human_agent_board`、`background_teammate`、`remote_teammate`。 | coordinator / parallel / specialist handoff / review / background / remote / human board 均已有 projection baseline;直接 board/team 写回、原生 A2A ingress、remote control callback 归入 future product boundary。 |
|
||||
| Team events | `agent.spawned`、`agent.completed`、`agent.handoff`、`team.changed`、`worker.notification`、`review.requested`、`review.completed`。 | 标准 event class 与 subagent/handoff/review 基线已接入;reviewer teammate baseline 已进 roster;handoff bundle / analysis handoff 导出已作为 runtime handoff source 以 `handoff_requested` 写入 `handoff_lane`;accepted/returned/resumed 的 phase mapping 已在 adapter 层就绪,并已接入最小真实 source:`subagent_status_changed` 证明 accepted/returned,`team_control_projection(action=resume)` 证明 resumed;reassignment 与 surface 消费继续收口。 |
|
||||
| Team controls | `delegate`、`assign`、`continue_agent`、`wait`、`stop`、`close`、`request_review`。 | 标准 controls 已有 source projection 与 UI 定位;本地 child session control 走真实 handler,非本地 target 只定位或回填 prompt,不伪造完成态。 |
|
||||
| Runtime execution alignment | `runtimeEntity=agent_turn/subagent_turn/automation_job/external_task/work_item`,保留 parent/child、runtime status、team phase、queue/parallelism facts。 | Lime taxonomy 已吻合;本地三类 runtime 为一等实体,`external_task/work_item` 只作为 adapter surface,不反向新增 runtime taxonomy。 |
|
||||
| Worker notification separation | worker result / failure / kill / usage / transcript ref 不能冒充用户消息或 final answer。 | terminal subagent status 与 archived subagent activity 已独立投影 `worker.notification`;usage / duration / tool count / transcriptRef / result_ref 均来自结构化 source。 |
|
||||
| Artifact / evidence | artifact body 离开 transcript,evidence/replay/review 走 durable refs。 | artifact/evidence/review/requested-fix execution metadata 已接入;artifact body 继续留在 artifact service/workbench。 |
|
||||
| Context / permission / diagnostics | context budget、missing、compaction、policy、sandbox、safe diagnostics 独立分类。 | runtime context、permission、metrics 已有 projection;memory budget / missing context 等细分 source event 属于 v0.5 尾项增强,不阻塞 v0.6 Team Workbench baseline。 |
|
||||
| Context / permission / diagnostics | context budget、missing、compaction、policy、sandbox、safe diagnostics 独立分类。 | runtime context、permission、metrics 已有 projection;memory budget / missing context 等细分 source event 属于 v0.5 尾项增强,不阻塞 v0.6 Subagents baseline。 |
|
||||
|
||||
### 3.1 AgentRuntime Profile 与相邻标准增补
|
||||
|
||||
@@ -73,7 +73,7 @@ Agent UI 标准已经从“thinking/progressive rendering 局部规则”扩展
|
||||
```text
|
||||
AgentRuntimeProfileEvent / ThreadReadModel / EvidenceSummary
|
||||
-> Agent UI envelope projection
|
||||
-> Workspace / Harness / Timeline / Task Capsule / Team Workbench
|
||||
-> Workspace / Harness / Timeline / Task Capsule / Subagents
|
||||
```
|
||||
|
||||
固定规则:
|
||||
@@ -109,24 +109,24 @@ AgentRuntimeProfileEvent / ThreadReadModel / EvidenceSummary
|
||||
| Permission/security/policy | `runtime_status.metadata` 的 permission / confirmation / profile / decision 投影为 `permission.changed`。 | `agentUiEventProjection.ts`、runtime action、permission UI | `current` + 待增强 |
|
||||
| Diagnostics/metrics | warning/error/thread diagnostics、runtime cost/performance/routing/limit/quota 写入标准投影。 | `agentUiEventProjection.ts`、`conversationProjectionStore.ts`、`agentUiProjectionSummary.ts`、`HarnessStatusPanel.tsx`、`AgentThreadReliabilityPanel.tsx`、`agentStreamPerformanceMetrics.ts` | `current` |
|
||||
|
||||
### 4.2 v0.6.0 Team Workbench 再审计
|
||||
### 4.2 v0.6.0 Subagents 再审计
|
||||
|
||||
| v0.6.0 要求 | Lime 当前事实 | 主要入口 | 结论 / 缺口 |
|
||||
| --- | --- | --- | --- |
|
||||
| `runtimeEntity` 映射 | Lime taxonomy 已定义 `agent turn` / `subagent turn` / `automation job`;projection type 已显式携带 `runtimeEntity`。 | `internal/aiprompts/task-agent-taxonomy.md`、`agentUiEventProjection.ts`、`session_store.rs`、`subagent_runtime.rs`、`automation_service/*`、`remoteTaskAgentUiProjection.ts` | `current` + 待增强:本地 `agent_turn/subagent_turn/automation_job` 已接入;`work_item` 已通过 team formation -> work board baseline 写入;`external_task` 已从 gateway provenance -> `agent_runs.metadata` -> Team Workbench session-scoped projection baseline 接入。 |
|
||||
| `runtimeEntity` 映射 | Lime taxonomy 已定义 `agent turn` / `subagent turn` / `automation job`;projection type 已显式携带 `runtimeEntity`。 | `internal/aiprompts/task-agent-taxonomy.md`、`agentUiEventProjection.ts`、`session_store.rs`、`subagent_runtime.rs`、`automation_service/*`、`remoteTaskAgentUiProjection.ts` | `current` + 待增强:本地 `agent_turn/subagent_turn/automation_job` 已接入;`work_item` 已通过 team formation -> work board baseline 写入;`external_task` 已从 gateway provenance -> `agent_runs.metadata` -> Subagents session-scoped projection baseline 接入。 |
|
||||
| Team queue / parallelism facts | 数据源存在:`team_phase`、`team_parallel_budget`、`team_active_count`、`team_queued_count`、`provider_concurrency_group`、`queue_reason`。 | `session_store.rs`、`teamWorkspaceRuntime.ts`、`hooks/useTeamWorkspaceRuntime.ts`、`liveRuntimeProjector.ts` | `current`:已进入 `run.status`、`team.changed`、`agent.changed`、`task.changed` envelope;surface 详情消费继续收口。 |
|
||||
| Team Workbench surfaces | Lime 有 TeamWorkbench summary、team runtime cards、board canvas selectors、selected session detail;summary panel 已从 `conversationProjectionStore.agentUi` 消费 v0.6 全量 10 个标准 surface 的计数、latest events、三类 surface lane 与 10 个可展开 surface detail;lane / surface 聚合已下沉为 `agentUiProjectionSummary.ts`,可交互 section / item / action / target 模型已下沉为 `agentUiTeamWorkbenchViewModel.ts`,并通过 `AgentUiTeamWorkbenchSurfaceView.tsx` 接入 panel;点击 `teammate_transcript/open_detail` 后已展示 Transcript Zoom 定位面,读取本地 child session 概览、live activity preview,并在没有 live activity 时按 child session 历史 activity snapshot 回填正文预览;Transcript Zoom 已从 child session detail 的 `queued_turns` 与 thread item `request_user_input` / `approval_request` / `tool_call` / message 类事件拆出输入队列、工具活动与近期消息,不从正文合成状态;点击工作台 action 后会按 agent / task / work item / transcript / artifact ref 串联同一目标的相关 projection 事件;`team_reassignment` 已在 surface detail 与操作视图中显示重新指派目标、负责人流转与原因;requested fix / worker notification 的 result ref、artifact ids / paths、evidence id 已进入 action target 与相关链路;pending requested fix work item 已可回填输入框,作为人工确认后的真实执行发起入口;也可在用户显式点击工作台 action 后通过 `submitAgentUiWorkbenchPrompt` 直接提交主线程 runtime turn,并在 request metadata 写入 `harness.agent_ui_workbench.action=execute_requested_fix`;回填 / 提交 prompt 都要求下一轮执行把结果写入 `metadata.requestedFixExecutionResults`;`artifact_snapshot` metadata 中的 `requestedFixExecutionResults` 已可即时回写 `work_board` 修复完成态;`work_board` source fact 已提供负责人 selector/callback,可回填 TaskUpdate owner 更新指令,并等待真实 `owner_change` source 确认;remote task source fact 已显示为“远端任务已定位”,避免 actual source baseline 仍被误报为“远端未接入”。 | `TeamWorkbenchSummaryPanel.tsx`、`AgentUiTeamWorkbenchSurfaceView.tsx`、`agentUiProjectionSummary.ts`、`agentUiTeamWorkbenchViewModel.ts`、`teamWorkspaceRuntime.ts`、`team-workspace-runtime/*` | `current` + 待增强:已有 scoped summary consumer、`Team 拓扑 / Worker 流 / Review-Handoff` lanes、native collapsible surface detail、可见操作视图、action target 定位闭环、related teammate projection chain、artifact/evidence/result ref 追溯、local child session action routing、reassignment 展示 baseline,以及 `teammate_transcript/open_detail` 结构化 source / zoom / live activity + 历史 activity snapshot + queued turn / thread item drilldown 预览;已补非本地 target route taxonomy 与可见状态(located_only / remote_task_source_located / unsupported_review / unsupported_handoff / unsupported_work_item / seeded_work_item / submitted_work_item / seeded_reassignment / work_item_source_located),并接入重指派 selector/callback 的 TaskUpdate 指令回填;更直接的 board/team API 写回已审计为 future command boundary;后续只在新增 current 命令 / shared task board service 后接入,不在 UI 本地伪造;无人工确认后台执行策略与更深 teammate 操作闭环继续排后。 |
|
||||
| Subagents surfaces | Lime 已把 10 个标准 surface 收敛到 `AgentUiProjectionState.subagents`、`buildAgentUiSubagentsModel`、`agentUiSubagentsViewModel.ts` 和 `SubagentsView`;projection summary selector 继续提供 surface / lane / latest event 聚合,产品侧只能消费这些读模型和 action callback。 | `packages/agent-ui-contracts/src/projection.ts`、`packages/agent-runtime-projection/src/subagents.ts`、`packages/agent-runtime-ui/src/subagents.tsx`、`src/components/agent/chat/projection/agentUiSubagentsViewModel.ts`、`agentUiProjectionSummary.ts`、`teamWorkspaceRuntime.ts`、`team-workspace-runtime/*` | `current` + 待增强:标准包已经承接 threads、delegation calls、activities、active/completed/failed thread ids 与 isolation 摘要;产品侧旧本地 panel / board 入口已退出 current 标准面。更直接的 board/team API 写回仍是 future command boundary;后续只在新增 current 命令 / shared task board service 后接入,不在 UI 本地伪造完成态。 |
|
||||
| `agent.spawned` / `agent.completed` | subagent spawn/status/activity 和 child session 存储存在,标准 projection 已从 `subagent_status_changed` 派生 lifecycle。 | `subagent_runtime.rs`、`runtimeEventSubscriptions.ts`、`agentUiEventProjection.ts` | `current`:`agent.changed` 继续保留为 summary 兼容事件。 |
|
||||
| `team.changed` | team phase、membership、queue/concurrency facts 在 runtime UI 有聚合;发送前 team formation state 已作为结构化 source 写入 Agent UI projection。 | `session_store.rs`、`teamWorkspaceRuntime.ts`、`liveRuntimeProjector.ts`、`agentUiEventProjection.ts`、`teamFormationAgentUiProjection.ts`、`useWorkspaceSendActions.ts` | `current` + 待增强:runtime status、subagent status 与 team formation 已派生 `team.changed`;merging/done 的更细 source event 仍待增强。 |
|
||||
| `worker.notification` | subagent activity、tool/task/action 事件已进入 projection store,terminal subagent status 与 archived subagent activity 已独立投影 worker notification;`subagent_status_changed` parser / projection 已支持 `usage`、`duration_ms`、`tool_count`、`result_ref`,terminal notification 顶层携带 `transcriptRef` 与 `workerUsage`;Rust status emitter 已在 terminal status 输出 session usage、latest turn duration、tool count 与 latest AgentMessage runtime item `result_ref`;Team Workbench view model 已把 worker usage / duration / tool count / result ref 展示到 subtitle / chips。 | `runtimeEventSubscriptions.ts`、`liveRuntimeProjector.ts`、`agentProtocol.ts`、`agentUiEventProjection.ts`、`agentUiTeamWorkbenchViewModel.ts`、`subagent_control.rs`、`subagent_runtime.rs` | `current` + 待增强:usage/duration/tool count 与 terminal `result_ref` 都已有真实 source;`result_ref` 来自 latest turn 的 durable Agent runtime item ref,后续如需 artifact 级结果可再接 artifact source。 |
|
||||
| `agent.handoff` | analysis handoff 与 runtime handoff bundle 导出已通过 evidence helper 以 `handoff_requested` 投影到 `handoff_lane`,Team Workbench 已展示 status、from/to、resume target 与 context boundary;`buildAgentUiHandoffProjectionEvents` 已按 source-provided status 映射 `accepted/acting/reconciling/completed/failed/cancelled/waiting` phase,plan approval、send input、wait/resume/close 有结构化控制。 | `HarnessStatusPanel.tsx`、`agentUiEventProjection.ts`、`agentUiProjectionSummary.ts`、`agentUiTeamWorkbenchViewModel.ts`、`subagent_runtime.rs`、`useWorkspaceTeamSessionControlRuntime.ts` | `current` + 待增强:runtime handoff bundle / analysis handoff 已有真实 `handoff_requested` source event;`subagent_status_changed` 现在会把 child running/completed/failed/cancelled 映射为 specialist handoff accepted/returned/failed/cancelled;`team_control_projection(action=resume)` 会在真实 affected session 返回后映射为 resumed;仍缺更独立的 handoff protocol callback 与外部 specialist lifecycle。 |
|
||||
| `review.requested` / `review.completed` | Harness review decision 导出/保存已通过 review helper 投影到 `review_lane`;导出 review template 时也会记录 `team_control_projection(control=request_review)`,把人工审核请求作为 review work item 暴露给 Team Workbench;surface detail 已展示 decision、reviewer、risk、checklist、followup、regression counts、requested fixes 与 regression requirement preview;review completed 的 reviewer 已额外进入 team roster;`verification_summary` 的 failure/recovered facts 已回写到 review payload 和 requested-fix work item;`team_control_projection(action=reassign)` 已能表达 work item reassignment source;`TaskUpdate` owner 变更已输出结构化 `owner_change` 并由 `item_completed` 投影到 `work_board` assign/reassign。 | `HarnessStatusPanel.tsx`、`agentUiEventProjection.ts`、`teamControlAgentUiProjection.ts`、`agentUiProjectionSummary.ts`、evidence/review runtime | `current` + 待增强:reassignment adapter baseline 与 TaskUpdate owner_change board source 已有;requested fix execution 已能从 `artifact_snapshot` metadata 即时回写 `work_board`,并从 evidence artifact metadata 进入 verification summary 与 review decision;前端 reassignment selector/callback 已接入为 TaskUpdate owner 更新指令回填,负责人变化仍以真实 `owner_change` source 确认;requested fix 已可回填输入框作为人工确认后的执行发起入口,也可在用户显式点击后直接提交 runtime turn,且 prompt 已声明 `requestedFixExecutionResults` metadata contract;board 完成态已按 source-provided executionStatus 可见化,后台无确认自动执行仍不伪造。 |
|
||||
| `worker.notification` | subagent activity、tool/task/action 事件已进入 projection store,terminal subagent status 与 archived subagent activity 已独立投影 worker notification;`subagent_status_changed` parser / projection 已支持 `usage`、`duration_ms`、`tool_count`、`result_ref`,terminal notification 顶层携带 `transcriptRef` 与 `workerUsage`;Rust status emitter 已在 terminal status 输出 session usage、latest turn duration、tool count 与 latest AgentMessage runtime item `result_ref`;Subagents view model 已把 worker usage / duration / tool count / result ref 展示到 subtitle / chips。 | `runtimeEventSubscriptions.ts`、`liveRuntimeProjector.ts`、`agentProtocol.ts`、`agentUiEventProjection.ts`、`agentUiSubagentsViewModel.ts`、`subagent_control.rs`、`subagent_runtime.rs` | `current` + 待增强:usage/duration/tool count 与 terminal `result_ref` 都已有真实 source;`result_ref` 来自 latest turn 的 durable Agent runtime item ref,后续如需 artifact 级结果可再接 artifact source。 |
|
||||
| `agent.handoff` | analysis handoff 与 runtime handoff bundle 导出已通过 evidence helper 以 `handoff_requested` 投影到 `handoff_lane`,Subagents 已展示 status、from/to、resume target 与 context boundary;`buildAgentUiHandoffProjectionEvents` 已按 source-provided status 映射 `accepted/acting/reconciling/completed/failed/cancelled/waiting` phase,plan approval、send input、wait/resume/close 有结构化控制。 | `HarnessStatusPanel.tsx`、`agentUiEventProjection.ts`、`agentUiProjectionSummary.ts`、`agentUiSubagentsViewModel.ts`、`subagent_runtime.rs`、`useWorkspaceTeamSessionControlRuntime.ts` | `current` + 待增强:runtime handoff bundle / analysis handoff 已有真实 `handoff_requested` source event;`subagent_status_changed` 现在会把 child running/completed/failed/cancelled 映射为 specialist handoff accepted/returned/failed/cancelled;`team_control_projection(action=resume)` 会在真实 affected session 返回后映射为 resumed;仍缺更独立的 handoff protocol callback 与外部 specialist lifecycle。 |
|
||||
| `review.requested` / `review.completed` | Harness review decision 导出/保存已通过 review helper 投影到 `review_lane`;导出 review template 时也会记录 `team_control_projection(control=request_review)`,把人工审核请求作为 review work item 暴露给 Subagents;surface detail 已展示 decision、reviewer、risk、checklist、followup、regression counts、requested fixes 与 regression requirement preview;review completed 的 reviewer 已额外进入 team roster;`verification_summary` 的 failure/recovered facts 已回写到 review payload 和 requested-fix work item;`team_control_projection(action=reassign)` 已能表达 work item reassignment source;`TaskUpdate` owner 变更已输出结构化 `owner_change` 并由 `item_completed` 投影到 `work_board` assign/reassign。 | `HarnessStatusPanel.tsx`、`agentUiEventProjection.ts`、`teamControlAgentUiProjection.ts`、`agentUiProjectionSummary.ts`、evidence/review runtime | `current` + 待增强:reassignment adapter baseline 与 TaskUpdate owner_change board source 已有;requested fix execution 已能从 `artifact_snapshot` metadata 即时回写 `work_board`,并从 evidence artifact metadata 进入 verification summary 与 review decision;前端 reassignment selector/callback 已接入为 TaskUpdate owner 更新指令回填,负责人变化仍以真实 `owner_change` source 确认;requested fix 可回填输入框作为人工确认后的执行发起入口;board 完成态按 source-provided executionStatus 可见化,后台无确认自动执行仍不伪造。 |
|
||||
| `background_teammate` | durable automation job 是 current 执行实体;automation 创建/加载/更新/删除/立即运行已投影为 `runtimeEntity=automation_job`、`background_teammate`、`task_capsule` 与 terminal `worker.notification`。 | `lime-rs/src/services/automation_service/*`、`lime-rs/src/commands/automation_cmd.rs`、`src/lib/api/automation.ts`、`automationJobAgentUiProjection.ts`、`settings-v2/system/automation/index.tsx`、`WorkspaceRegisteredSkillsPanel.tsx` | `current` + 待增强:settings automation 与 capability drafts managed automation 入口已接持续刷新;仍可继续把 skills automation 等其它消费入口接入同一 helper。 |
|
||||
| `remote_teammate` | current gateway ingress(Telegram / Feishu / Discord / Wechat)已在 `agent.run` 传入 `source_metadata.remote_task`,包含 channel、account、remote task id、inbound message identity 与 Agent Card;`rpc_handler.rs` 会把 snake/camel 两种 source metadata 持久化到 `agent_runs.metadata`,terminal metadata 合并时保留 provenance;`remoteTaskAgentUiProjection.ts` 已能从 `AgentRun.metadata.source_metadata.remote_task` 构造 `runtimeEntity=external_task` projection;`TeamWorkbenchSummaryPanel` 通过 `useRemoteTaskExecutionRunProjection` 轮询当前 session 的 execution runs 并写入 `conversationProjectionStore.agentUi`。 | `internal/aiprompts/remote-runtime.md`、`channelsRuntime.ts`、`gateway_channel_cmd.rs`、`lime-rs/crates/gateway/src/*`、`rpc_handler.rs`、`remoteTaskAgentUiProjection.ts`、`useRemoteTaskExecutionRunProjection.ts`、`TeamWorkbenchSummaryPanel.tsx` | `current` + `partial`:gateway remote ingress 已成为 Agent UI `remote_task_projection` actual source baseline;terminal `AgentRun.status` 仍优先,且 `remote_task.event/taskStatus/status/state/phase` 已可驱动 A2A lifecycle(queued/running/needs_input/auth_required/completed/failed/cancelled),不从 heartbeat、gateway 日志文本、assistant prose 或普通 session message 猜测;`agent_runs.metadata.result.artifacts` / top-level `artifacts`、`remote_task.artifactUpdates` 与嵌套 `remote_task.task/a2aTask.artifacts` 已可在存在 `remote_task` provenance 时作为 remote artifact update source 投影,并保留 `contentRef/contentUrl/mimeType/byteSize/digest/preview` 等远端内容指针;Team Workbench remote surface 会展示内容指针、类型、校验与预览,且 URL 展示会去掉 query,避免把签名参数直接铺在 UI;`remote_task.inputRequired/authRequired/authStatus` 已可把 running run 投影为 `needs_input/auth_required` 并额外输出 `action.required(surface=remote_teammate, control=answer)`,但 terminal run 会压过 stale input/auth source;真正原生 A2A ingress 与 remote artifact store/content 回流已审计为 future product boundary;当前 v0.6 projection baseline 以 gateway provenance + structured remote_task metadata 为合规 source。 |
|
||||
| `remote_teammate` | current gateway ingress(Telegram / Feishu / Discord / Wechat)已在 `agent.run` 传入 `source_metadata.remote_task`,包含 channel、account、remote task id、inbound message identity 与 Agent Card;`rpc_handler.rs` 会把 snake/camel 两种 source metadata 持久化到 `agent_runs.metadata`,terminal metadata 合并时保留 provenance;`remoteTaskAgentUiProjection.ts` 已能从 `AgentRun.metadata.source_metadata.remote_task` 构造 `runtimeEntity=external_task` projection,并写入 `conversationProjectionStore.agentUi`。 | `internal/aiprompts/remote-runtime.md`、`channelsRuntime.ts`、`gateway_channel_cmd.rs`、`lime-rs/crates/gateway/src/*`、`rpc_handler.rs`、`remoteTaskAgentUiProjection.ts`、`useRemoteTaskExecutionRunProjection.ts` | `current` + `partial`:gateway remote ingress 已成为 Agent UI `remote_task_projection` actual source baseline;terminal `AgentRun.status` 仍优先,且 `remote_task.event/taskStatus/status/state/phase` 已可驱动 A2A lifecycle,不从 heartbeat、gateway 日志文本、assistant prose 或普通 session message 猜测;远端 artifact 指针继续保留为 refs;真正原生 A2A ingress 与 remote artifact store/content 回流已审计为 future product boundary。 |
|
||||
| `work_board` / `assign` | Team board canvas 和 task capsule 有 UI 基础;发送前 `TeamWorkspaceRuntimeFormationState` 已记录 `team_formation_projection`,输出 `agent.changed(surface=team_roster/control=assign)` 与 `task.changed(surface=work_board/control=assign)`;`TaskUpdate` owner 变更会输出 `owner_change` metadata,`item_completed` projection 会把初次 owner 写入映射为 `assign`,把 owner 切换映射为 `reassign`。 | `team-workspace-board/*`、`TaskCenterTabStrip.tsx`、`teamWorkspaceRuntime.ts`、`teamFormationAgentUiProjection.ts`、`useWorkspaceSendActions.ts` | `current` + 待增强:assignment / TaskUpdate owner_change reassignment baseline 不再依赖 runtime status hack 或 peer message 文本;已提供可见 reassignment selector/callback 的 TaskUpdate 指令回填;后续若要做到无 prompt 的直接 live board update,需先新增 current board/team 命令边界或 shared task board service;当前不从 UI 本地写状态,review work item 与完成态继续只认 source fact。 |
|
||||
| Team controls | spawn/send/wait/resume/close 已有;stop 通过前台 stopSending;subagent task projection 已把 `idle/queued/running/terminal` 映射为 `continue_agent/wait/stop/close`;`agent.spawned` 已携带 `delegate`;`team_control_projection` adapter 已覆盖 `delegate/assign/continue_agent/wait/stop/close/request_review` 标准 control,并可用 `action=reassign` + `control=assign` 表达 work item reassignment;当 source 提供 `resolvedStatus` 时会优先映射为 top-level `phase/runtimeStatus`,避免已知完成/失败/assigned 状态被固定显示成 queued/completed;Team formation 已作为 actual assignment source 投影 `assign/work_item`;Harness review template 导出已作为 actual review request source 投影 `request_review/work_item`。 | `useWorkspaceTeamSessionControlRuntime.ts`、`teamControlAgentUiProjection.ts`、`teamFormationAgentUiProjection.ts`、`HarnessStatusPanel.tsx`、`agentUiEventProjection.ts`、`subagent_runtime.rs`、`agent_control.rs` | `current` + 待增强:现有 control 均已有实际来源;reviewer teammate baseline 已进入 roster;requested fix execution artifact metadata 已进入 `work_board` 即时投影与 review decision source;reassignment adapter、TaskUpdate owner_change source 与 source-provided `resolvedStatus` 映射已有;前端可见 reassignment selector/callback 已接入为 TaskUpdate 指令回填,真实负责人变化仍等 `owner_change` source;requested fixes 已可回填输入框作为人工确认后的执行发起入口,也可在用户显式点击后直接提交 runtime turn,且 prompt 已声明 `requestedFixExecutionResults` metadata contract;board 完成态已按 source-provided executionStatus 可见化,后台无确认自动执行仍不伪造。 |
|
||||
| Delegated approval identity | team lead / teammate plan approval 已结构化,能发出 `action_resolved`;direct `action_resolved(plan_approval)` projection 已保留 `targetSessionId`、`planFile`、`planId` 与 `awaitingLeaderApproval`,summary/detail 已显示请求方、目标 session、权限模式、leader 审批等待与 plan ref,不只剩 metadata keys。 | `subagent_runtime.rs`、`agent_control.rs`、`runtimeEventSubscriptions.ts`、`agentUiEventProjection.ts`、`agentUiProjectionSummary.ts` | `current` + 待增强:后续如 runtime 提供更细 policy / transcript ref,可继续进入同一结构化详情,不从 prose 反推。 |
|
||||
| Teammate transcript | child session / parent context 存在;`subagent_status_changed` 已派生 `agent.changed(surface=teammate_transcript, control=open_detail, transcriptRef=...)`;summary consumer 与 view model 已能把 transcript ref 作为 action target 定位;Summary panel 已展示 Transcript Zoom 基线,解析 parent session、child session 与 latest turn,并通过 Team Workbench action routing 可落到本地 child session;zoom 内已读取本地 child session 概览与 `liveActivityBySessionId` 中的最近进展;无 live activity 时会读取 child session 历史 activity snapshot 作为正文预览;zoom 内已用 child session detail 的 `queued_turns` 与 thread item source type 拆出输入队列、结构化待输入/审批、工具活动与近期消息有界预览;已补 related teammate projection chain,按结构化 id 串联 transcript / worker notification / board / roster 等同目标事件。 | `agentUiEventProjection.ts`、`agentUiTeamWorkbenchViewModel.ts`、`TeamWorkbenchSummaryPanel.tsx`、`useWorkspaceCanvasSceneRuntime.tsx`、`teamWorkspaceRuntime.ts`、`activityPreviewSelectors.ts` | `current` + 待增强:transcript ref 事实源、target 定位、zoom 定位面、本地 child session 焦点、live activity preview、历史 activity snapshot fallback、queued turn / thread item 结构化 drilldown 与 related projection chain 已接入;仍需真实 pending input 操作 handler / 更深 transcript drilldown,且继续避免把 teammate 内部输出直接混进主 final answer。 |
|
||||
| Teammate transcript | child session / parent context 存在;`subagent_status_changed` 已派生 `agent.changed(surface=teammate_transcript, control=open_detail, transcriptRef=...)`;summary selector 与 Subagents view model 已能把 transcript ref 作为 action target 定位;本地 child session 焦点、live activity preview、历史 activity snapshot fallback 与 queued turn / thread item drilldown 均只消费结构化 fact,不从正文合成状态。 | `agentUiEventProjection.ts`、`agentUiSubagentsViewModel.ts`、`useWorkspaceCanvasSceneRuntime.tsx`、`teamWorkspaceRuntime.ts` | `current` + 待增强:transcript ref 事实源、target 定位、本地 child session 焦点、live activity preview、历史 activity snapshot fallback、queued turn / thread item 结构化 drilldown 与 related projection chain 已接入;仍需真实 pending input 操作 handler / 更深 transcript drilldown,且继续避免把 teammate 内部输出直接混进主 final answer。 |
|
||||
|
||||
## 5. Current / compat / deprecated / dead 分类
|
||||
|
||||
@@ -138,7 +138,7 @@ AgentRuntimeProfileEvent / ThreadReadModel / EvidenceSummary
|
||||
| `threadItems` / `threadTurns` / `thread_read` timeline | `current` | 完成态过程归档、历史恢复、evidence 链路的主事实源。 | 无。继续演进。 |
|
||||
| Inline active process | `current` | active turn 的首要过程投影;running 时优先于 timeline detail。 | 无。继续演进。 |
|
||||
| Historical timeline archive | `current` | completed process 的归档投影;默认折叠。 | 无。继续演进。 |
|
||||
| Team workspace runtime cards / live runtime projector | `current` | subagent/team runtime 的 current UI 读模型。 | 标准 surfaces 补齐后,作为 Team Workbench projection consumer。 |
|
||||
| Subagents runtime cards / live runtime projector | `current` | subagent/team runtime 的 current UI 读模型。 | 标准 surfaces 补齐后,作为 Subagents projection consumer。 |
|
||||
| Subagent runtime controls `spawn/send_input/wait/resume/close` | `current` | Lime teammate / child session 的 current 控制入口。 | 对齐标准 control 名称,补 `delegate/assign/continue_agent/wait/stop/close/request_review` 的 adapter。 |
|
||||
| Automation job | `current` | durable background execution 的 current 执行实体;创建、更新、删除、立即运行、列表 / 状态 / run history 刷新已投影为 `runtimeEntity=automation_job`、`background_teammate`、`task_capsule` 与 terminal `worker.notification`。 | 后续其它 automation 消费入口继续接同一 projection helper;不得拆出新 runtime taxonomy。 |
|
||||
| Remote task projection | `current` + actual source `partial` | `remoteTaskAgentUiProjection.ts` 是 `remote_task_projection` 的唯一 current builder;它输出 `agent.changed/task.changed(surface=remote_teammate, runtimeEntity=external_task)`、artifact refs 与 terminal `worker.notification`,`agentUiEventProjection.ts` 只保留兼容 wrapper。2026-05-10 已接入 gateway provenance:渠道 runtime 在 `agent.run` 写入 `source_metadata.remote_task`,RPC terminal metadata 合并保留该 source,前端按 terminal `AgentRun.status`、结构化 remote task status 与 metadata 投影当前 session remote teammate。 | terminal `AgentRun.status` 仍优先;非 terminal run 可消费 `remote_task.event/taskStatus/status/state/phase` 这类结构化 remote task source,不得用 heartbeat、gateway 日志、assistant prose、普通 session message 或 teammate XML/JSON 文本反推。真正原生 A2A ingress、remote artifact store/content 回流与更完整远端任务详情归入 future product boundary;current baseline 继续只消费结构化 remote_task source。 |
|
||||
@@ -148,7 +148,7 @@ AgentRuntimeProfileEvent / ThreadReadModel / EvidenceSummary
|
||||
| Active turn leading timeline duplicate | `deprecated` | 与 inline process 同屏重复时必须被抑制。 | 保持只在没有 inline process fact 时作为 fallback。 |
|
||||
| 从 assistant prose 推断 tool/artifact/success | `dead` | 与标准冲突;后续不得新增。 | 现有必要解析只能停留在 migration/fallback,并登记退出条件。 |
|
||||
| 从 teammate XML/JSON 文本反推 worker result / approval | `dead` | v0.6 要求 worker notification 与 delegated approval 是结构化 fact。 | 只能保留在历史迁移或外部兼容边界;current 不再新增。 |
|
||||
| `execution run` 作为 coordinator / task taxonomy | `dead` | `ExecutionTracker` 只能做观测层,不定义分工、调度或 parent/child 编排。 | 新设计不得把 `agent_runs` 当成 Team Workbench coordinator。 |
|
||||
| `execution run` 作为 coordinator / task taxonomy | `dead` | `ExecutionTracker` 只能做观测层,不定义分工、调度或 parent/child 编排。 | 新设计不得把 `agent_runs` 当成 Subagents coordinator。 |
|
||||
| Remote heartbeat 作为 terminal completion | `dead` | remote teammate 需要 remote truth / task status / artifact update,不得用 idle tick 猜完成。 | future remote adapter 必须保留 remote task id 和真实 terminal state。 |
|
||||
|
||||
## 6. 后续增强 / future boundary 清单
|
||||
@@ -160,11 +160,11 @@ AgentRuntimeProfileEvent / ThreadReadModel / EvidenceSummary
|
||||
| P0 | `runtimeEntity` 显式字段 / 类型 | 已可机械证明 `agent_turn/subagent_turn/automation_job` 进入 Agent UI envelope;`work_item` 已有 team formation -> work board baseline;`external_task` 已有 remote task projection helper。 | 继续禁止新建第四类 runtime taxonomy;remote helper 只能消费真实 remote source。 |
|
||||
| P0 | team queue / parallelism facts 进入标准 envelope | Team UI 与 Agent UI projection 已共享 team/provider queue facts。 | 继续补 surface 详情消费,避免各 UI 局部重算。 |
|
||||
| P0 | `agent.spawned` / `agent.completed` 独立于 `agent.changed` | 已表达 teammate lifecycle、worker terminal result 与 parent/child lineage。 | 后续补更精确的 spawn source 与 transcript refs。 |
|
||||
| P0 | `worker.notification` 独立 surface | terminal subagent status 与 archived subagent activity 已进入 `worker_notifications`,不进入主 transcript/final answer;terminal status projection 已保留 `transcriptRef`,Rust status emitter 已提供 usage / duration / tool count / result_ref,Team Workbench 操作视图已显示这些 worker result facts。 | 已补 latest turn AgentMessage 的 durable runtime item `result_ref` source;后续如需 artifact 级结果再接 artifact source,仍不从 teammate 文本反推。 |
|
||||
| P1 | Team Workbench surfaces consumer 基线已接入 | summary panel 已展示 roster、board、delegation、handoff、notifications、review、transcript、background、remote、policy 的 scoped counts、latest events、`Team 拓扑 / Worker 流 / Review-Handoff` lanes、可展开 surface detail 与 `工作台操作视图`;独立 view model 已保留 action target 与 attention 状态,并把 `review_requested_fix` work item 显式展示为可指派 Review fix,且能展示结构化 regression outcome;`team_reassignment` work item 已显示重新指派目标、负责人流转、原因与 action label;panel 已能把 action item 回传并在右侧定位标准 target;subagent status 已提供 teammate transcript ref target;`teammate_transcript/open_detail` 已有 Transcript Zoom 定位面,解析 child session / latest turn,并展示本地 child session 概览、live activity preview、历史 activity snapshot fallback、同目标 related projection chain 与 requested fix / worker result 的 artifact/evidence/result ref 追溯。 | 已补非本地 target route taxonomy、可见状态、requested fix 显式点击后的 runtime turn 提交与重指派 selector/callback 的 TaskUpdate 指令回填;直接 board/team API 写回已审计为 future command boundary,当前不作为 v0.6 projection 对齐阻塞项;后台无确认策略和更完整 teammate drilldown 排后。 |
|
||||
| P1 | `agent.handoff` 基线已建模 | analysis handoff helper 与 handoff bundle 导出都已保留 from/to/reason/resume target/context boundary,并写入 `handoff_lane`;Team Workbench surface detail / 操作视图已能显示 handoff status、from/to、resume target 与 context boundary;handoff projection adapter 已把真实 source status 映射到标准 phase。 | 最小真实 source 已接:accepted/returned 来自 `subagent_status_changed`,resumed 来自真实 resume control response;后续补更独立的 handoff protocol callback,不从 review decision 或 prose 合成。 |
|
||||
| P0 | `worker.notification` 独立 surface | terminal subagent status 与 archived subagent activity 已进入 `worker_notifications`,不进入主 transcript/final answer;terminal status projection 已保留 `transcriptRef`,Rust status emitter 已提供 usage / duration / tool count / result_ref,Subagents 操作视图已显示这些 worker result facts。 | 已补 latest turn AgentMessage 的 durable runtime item `result_ref` source;后续如需 artifact 级结果再接 artifact source,仍不从 teammate 文本反推。 |
|
||||
| P1 | Subagents surfaces consumer 基线已接入 | summary panel 已展示 roster、board、delegation、handoff、notifications、review、transcript、background、remote、policy 的 scoped counts、latest events、`Team 拓扑 / Worker 流 / Review-Handoff` lanes、可展开 surface detail 与 `工作台操作视图`;独立 view model 已保留 action target 与 attention 状态,并把 `review_requested_fix` work item 显式展示为可指派 Review fix,且能展示结构化 regression outcome;`team_reassignment` work item 已显示重新指派目标、负责人流转、原因与 action label;panel 已能把 action item 回传并在右侧定位标准 target;subagent status 已提供 teammate transcript ref target;`teammate_transcript/open_detail` 已有 Transcript Zoom 定位面,解析 child session / latest turn,并展示本地 child session 概览、live activity preview、历史 activity snapshot fallback、同目标 related projection chain 与 requested fix / worker result 的 artifact/evidence/result ref 追溯。 | 已补非本地 target route taxonomy、可见状态、requested fix 显式点击后的 runtime turn 提交与重指派 selector/callback 的 TaskUpdate 指令回填;直接 board/team API 写回已审计为 future command boundary,当前不作为 v0.6 projection 对齐阻塞项;后台无确认策略和更完整 teammate drilldown 排后。 |
|
||||
| P1 | `agent.handoff` 基线已建模 | analysis handoff helper 与 handoff bundle 导出都已保留 from/to/reason/resume target/context boundary,并写入 `handoff_lane`;Subagents surface detail / 操作视图已能显示 handoff status、from/to、resume target 与 context boundary;handoff projection adapter 已把真实 source status 映射到标准 phase。 | 最小真实 source 已接:accepted/returned 来自 `subagent_status_changed`,resumed 来自真实 resume control response;后续补更独立的 handoff protocol callback,不从 review decision 或 prose 合成。 |
|
||||
| P1 | Review lane 基线已与 evidence 合流 | `review.requested` / `review.completed` 已进入 `review_lane` 并关联 evidence refs;review template 导出也会同步记录 `request_review/work_item` team control projection;surface detail 已展示 reviewer、risk、checklist、followup、regression、requested fixes 与 regression requirement preview;`review.completed` 已把 reviewer 投影为 `team_roster` reviewer teammate;requested fixes 已派生为 `work_board` work item,并在工作台操作视图中显示修复正文、pending 状态、regression outcome 和 assign target;`team_control_projection(action=reassign)` 已能表达 reassignment source。 | requested fix execution artifact metadata 已进入 evidence / review decision 链路;TaskUpdate owner_change source 已接入 work_board;前端 reassignment selector/callback 已以 TaskUpdate 指令回填方式接入;当前已支持把 pending fix 回填输入框,由用户确认发送后执行,也支持用户显式点击后直接提交主线程 runtime turn,并要求后续写入 `requestedFixExecutionResults` metadata;artifact snapshot 一旦带回该 metadata,会即时把 board 完成状态按 source-provided executionStatus 展示为“待执行修复 / 修复完成”等可见状态。 |
|
||||
| P1 | Control 接线尾项 | `team_control_projection` adapter 已能表达 `delegate/assign/reassign/continue_agent/wait/stop/close/request_review`;现有 wait/resume/send_input/close/stop 已接 UI 操作且带 session scope;`agent.spawned` 已作为 delegation source 携带 `delegate`;Team formation 已把 actual assignment source 投影为 `assign/work_item`;Harness review template 导出已把 actual request_review source 投影为 review work item;Team Workbench action click 已只做标准 target 定位;无本地 child session 时会明确返回并显示 located_only / remote_task_source_located / unsupported_review / unsupported_handoff / unsupported_work_item / seeded_work_item;pending requested fix 可回填输入框,也可在用户显式点击后提交 runtime turn;两条路径都不伪造运行时完成。 | 当前 control 来源已覆盖主路径;更细 review teammate 状态、无人工确认后台执行策略与无 prompt board/team API 写回都属于后续产品增强,直接写回需先补 current 命令边界。 |
|
||||
| P1 | Control 接线尾项 | `team_control_projection` adapter 已能表达 `delegate/assign/reassign/continue_agent/wait/stop/close/request_review`;现有 wait/resume/send_input/close/stop 已接 UI 操作且带 session scope;`agent.spawned` 已作为 delegation source 携带 `delegate`;Team formation 已把 actual assignment source 投影为 `assign/work_item`;Harness review template 导出已把 actual request_review source 投影为 review work item;Subagents action click 已只做标准 target 定位;无本地 child session 时会明确返回并显示 located_only / remote_task_source_located / unsupported_review / unsupported_handoff / unsupported_work_item / seeded_work_item;pending requested fix 可回填输入框,也可在用户显式点击后提交 runtime turn;两条路径都不伪造运行时完成。 | 当前 control 来源已覆盖主路径;更细 review teammate 状态、无人工确认后台执行策略与无 prompt board/team API 写回都属于后续产品增强,直接写回需先补 current 命令边界。 |
|
||||
| P2 | Background teammate 持续刷新 | settings automation 页刷新、创建、更新、删除、立即运行,以及 capability drafts managed automation 列表/开关已记录 `automation_job_projection`,并持续投影到 `background_teammate` / `task_capsule` / `worker_notifications`。 | 保持 automation job 为 current 执行实体;后续只需补 skills automation 等其它消费入口,不新增 runtime taxonomy。 |
|
||||
| P2 | Remote teammate adapter | `remoteTaskAgentUiProjection.ts` 已提供 `external_task -> remote_teammate` adapter baseline,保留 Agent Card、remote task id、input/auth、artifact updates 与 terminal worker notification;gateway current ingress 已把真实 channel provenance / remote task id / Agent Card 通过 `agent_runs.metadata` 接入该 helper。 | 真正原生 A2A ingress、remote artifact store/content 回流、远端详情与控制 callback 已登记为 future product boundary;current baseline 不用假 remote surface、heartbeat completion 或普通文本反推状态。 |
|
||||
| P2 | Context/memory 细分事件 | v0.5 尾项仍存在。 | memory budget、missing context、retrieval refs 结构化 source event。 |
|
||||
@@ -172,55 +172,55 @@ AgentRuntimeProfileEvent / ThreadReadModel / EvidenceSummary
|
||||
|
||||
## 7. 下一刀排序
|
||||
|
||||
按对整体目标完成度的增量排序;若隔壁进程正在改 v0.6 Team Workbench / controls 文件,本进程只选择已确认非重叠的低冲突 projection 接线,不抢 Rust 高冲突实现文件或同一批 Team UI patch。
|
||||
按对整体目标完成度的增量排序;若隔壁进程正在改 v0.6 Subagents / controls 文件,本进程只选择已确认非重叠的低冲突 projection 接线,不抢 Rust 高冲突实现文件或同一批 Team UI patch。
|
||||
|
||||
1. **深化 Team Workbench 专门视图**:summary consumer、scoped lane selector、native collapsible surface detail、view model、可见 `工作台操作视图`、action target 定位、requested fixes work item + regression outcome 展示、local child session action routing、`teammate_transcript` source / Transcript Zoom / live activity + 历史 activity snapshot preview,以及 related teammate projection chain 已接入;非本地 target route taxonomy 与可见状态已补(含 work_board work item 未接入态、requested fix submitted_work_item、reassignment source fact 已定位态与 seeded_reassignment),直接 board/team API 写回已审计为 future command boundary;统一门禁已于 2026-05-10 fresh green;原生 A2A artifact content 已登记为 future product boundary,后台无确认策略和更完整 teammate drilldown 排后。
|
||||
1. **深化 Subagents 专门视图**:summary consumer、scoped lane selector、native collapsible surface detail、view model、可见 `工作台操作视图`、action target 定位、requested fixes work item + regression outcome 展示、local child session action routing、`teammate_transcript` source / Transcript Zoom / live activity + 历史 activity snapshot preview,以及 related teammate projection chain 已接入;非本地 target route taxonomy 与可见状态已补(含 work_board work item 未接入态、requested fix submitted_work_item、reassignment source fact 已定位态与 seeded_reassignment),直接 board/team API 写回已审计为 future command boundary;统一门禁已于 2026-05-10 fresh green;原生 A2A artifact content 已登记为 future product boundary,后台无确认策略和更完整 teammate drilldown 排后。
|
||||
2. **补 Review lane 余量**:review request 已有 actual source,surface detail 已展示 reviewer、risk、checklist、followup、regression、requested fixes 与 regression requirement preview;review 保存后的 `verification_summary` failure/recovered facts 已进入 review payload 与 requested-fix work item;`team_control_projection(action=reassign)` 已能表达 reassignment source,TaskUpdate owner_change 已作为真实 board source 投影到 work_board;pending requested fix 已支持显式点击后提交 runtime turn;无 prompt 的直接 board/team API 写回已降级为 future command boundary;统一门禁已收敛,后续只在新增 current board/team command 或 specialist handoff protocol 后继续接入;原生 A2A artifact content 已登记为 future product boundary。
|
||||
3. **补其它 automation 消费入口**:settings automation 与 capability drafts managed automation 已接持续刷新;后续如 skills automation 列表需要展示 background teammate,也接 `recordAutomationJobsAgentUiProjection`,不新增 runtime taxonomy。
|
||||
4. **remote teammate / A2A source 接线**:gateway actual source、remote task status/input/auth/artifact refs baseline 已有;真正原生 A2A ingress、remote artifact store/content 回流与远端控制 callback 已登记为 future product boundary,不用假 remote surface。
|
||||
5. **v0.5 尾项回补**:Context/memory 细分 source event、其他 provider tool input streaming、HITL/action 历史重放归档,排在 v0.6 Team Workbench 主缺口之后。
|
||||
5. **v0.5 尾项回补**:Context/memory 细分 source event、其他 provider tool input streaming、HITL/action 历史重放归档,排在 v0.6 Subagents 主缺口之后。
|
||||
|
||||
## 8. 2026-05-09 v0.6.0 再审计记录
|
||||
|
||||
- 已确认 Agent UI 标准版本为 `v0.6.0`,local commit 为 `dcf4bc5`,公开 schema 为 `https://limecloud.github.io/agentui/schemas/agentui-event.schema.json`。
|
||||
- 已确认 Lime current taxonomy 只承认 `agent turn`、`subagent turn`、`automation job`,与 Agent UI `runtimeEntity` 的三类本地映射一致。
|
||||
- 已确认 Lime 已有 subagent runtime、child session、team phase、queue/provider concurrency 数据源,主要缺口在标准 projection envelope 和 Team Workbench surface 消费。
|
||||
- 已确认 Lime 已有 subagent runtime、child session、team phase、queue/provider concurrency 数据源,主要缺口在标准 projection envelope 和 Subagents surface 消费。
|
||||
- 已确认 projection type 已补 v0.6 `runtimeEntity`、team controls 枚举与 `agent.spawned/agent.completed/team.changed/worker.notification/agent.handoff/review.*` event class;未完成项集中在 surface 详情消费、controls adapter、background/remote/board adapter。
|
||||
- 已将旧下一刀从 Context/memory、provider tool streaming、HITL replay 调整为 v0.6 P0:先补 `runtimeEntity`、queue/parallelism facts、Team Workbench event classes。
|
||||
- 已将旧下一刀从 Context/memory、provider tool streaming、HITL replay 调整为 v0.6 P0:先补 `runtimeEntity`、queue/parallelism facts、Subagents event classes。
|
||||
- 已推进 Lime 实现增量:`runtime_status.metadata` 会投影 `runtimeEntity` 与 team queue/provider facts;`subagent_status_changed` 会派生 `agent.spawned`、`agent.completed`、`team.changed`、`worker.notification`;analysis handoff、runtime handoff bundle 与 review decision 已通过 helper 投影到 `agent.handoff`、`review.requested`、`review.completed`;handoff 导出只标记 `handoff_requested`,表示交接包已生成但尚未被 specialist 接受。`agent.handoff` adapter 已按 source-provided status 映射 `accepted/acting/reconciling/completed/failed/cancelled/waiting` phase;accepted/returned/resumed 已有最小 runtime/control source,仍不从 review decision、assistant prose、teammate transcript 或 heartbeat 合成。
|
||||
- 已补 automation job projection 增量:SceneApp / Service Skill 创建自动化任务时记录 `automation_job_projection`,同时输出 `task.changed`、`agent.changed(surface=background_teammate)`,terminal 结构化记录可输出 `worker.notification`;surface selector 已支持按 `AgentUiSurface` 从 `conversationProjectionStore.agentUi` 读取。
|
||||
- 已补 background teammate 持续刷新增量:`recordAutomationJobsAgentUiProjection` 支持批量记录 automation job 列表刷新;settings automation 页在刷新、创建、更新、删除、立即运行后会把 automation job 结构化记录到 Agent UI projection。
|
||||
- 已补 capability drafts managed automation 增量:`WorkspaceRegisteredSkillsPanel` 加载 / 开关 managed automation job 时会记录 automation job projection,让 workspace skill 绑定视图里的后台 teammate 与 settings automation 共享同一事实源。
|
||||
- 已补 Team Workbench surface consumer 增量:`TeamWorkbenchSummaryPanel` 通过 `useAgentUiProjectionEvents` 读取当前 session 的 Agent UI projection,并展示 `team_roster/work_board/delegation_graph/handoff_lane/worker_notifications/review_lane/teammate_transcript/background_teammate/remote_teammate/team_policy` 全量 surface 的计数与 latest events;对应回归覆盖 10 个 surface。
|
||||
- 已补 Team Workbench scoped lane 增量:`agentUiProjectionSummary.ts` 现在提供可复用 lane selector,同一 summary panel 按 `Team 拓扑`、`Worker 流`、`Review / Handoff` 三类聚合 latest events,避免 10 个 surface 只停留在扁平计数。
|
||||
- 已补 Team Workbench surface detail 增量:`agentUiProjectionSummary.ts` 现在提供 10 个标准 surface 的 detail selector,`TeamWorkbenchSummaryPanel` 展示 native collapsible `Surface 专门视图`,让每个 surface 可直接查看 latest events、phase 与 detail,而不是只能看总数。
|
||||
- 已补 Team Workbench action 定位闭环:`agentUiTeamWorkbenchViewModel.ts` 将 10 个标准 surface 归一成 section / item / action / target / attention 模型,`AgentUiTeamWorkbenchSurfaceView.tsx` 将其渲染为 `工作台操作视图`;`TeamWorkbenchSummaryPanel.tsx` 现在接入 `onWorkbenchAction`,点击 action 会回传 item 并在右侧展示标准 target 明细,但不会从文本推断状态或伪造 remote / review / handoff 运行时调用。
|
||||
- 已补 teammate transcript source 增量:`subagent_status_changed` 现在额外派生 `agent.changed(surface=teammate_transcript, control=open_detail)`,携带 `transcriptRef`;`agentUiTeamWorkbenchViewModel.ts` 对 `teammate_transcript` 优先使用 `transcriptRef` 作为 action target,保证 Team Workbench 可以定位队友 transcript,而不是把队友内部输出混入主 final answer。
|
||||
- 已补 teammate transcript zoom 定位基线:`TeamWorkbenchSummaryPanel` 点击 `teammate_transcript/open_detail` 后展示 Transcript Zoom,显式列出 `transcriptRef`、父会话、子会话与最新回合;`useWorkspaceCanvasSceneRuntime.tsx` 的 action routing 会从 `childSessionId:turnId` 或 `parent/childSessionId` ref 解析本地 child session 焦点。该面板只做定位,不读取或复制 child transcript 正文到主 final answer。
|
||||
- 已补 Subagents surface consumer 增量:`agentUiSubagentsViewModel.ts` 通过 `useAgentUiProjectionEvents` 读取当前 session 的 Agent UI projection,并支持展示 `team_roster/work_board/delegation_graph/handoff_lane/worker_notifications/review_lane/teammate_transcript/background_teammate/remote_teammate/team_policy` 全量 surface 的计数与 latest events;对应回归覆盖 10 个 surface。
|
||||
- 已补 Subagents scoped lane 增量:`agentUiProjectionSummary.ts` 现在提供可复用 lane selector,同一 summary consumer 按 `Team 拓扑`、`Worker 流`、`Review / Handoff` 三类聚合 latest events,避免 10 个 surface 只停留在扁平计数。
|
||||
- 已补 Subagents surface detail 增量:`agentUiProjectionSummary.ts` 现在提供 10 个标准 surface 的 detail selector,`SubagentsView` 展示 native collapsible `Surface 专门视图`,让每个 surface 可直接查看 latest events、phase 与 detail,而不是只能看总数。
|
||||
- 已补 Subagents action 定位闭环:`agentUiSubagentsViewModel.ts` 将 10 个标准 surface 归一成 section / item / action / target / attention 模型,`SubagentsView` 将其渲染为 `工作台操作视图`;产品侧 action callback 点击后会回传 item 并在右侧展示标准 target 明细,但不会从文本推断状态或伪造 remote / review / handoff 运行时调用。
|
||||
- 已补 teammate transcript source 增量:`subagent_status_changed` 现在额外派生 `agent.changed(surface=teammate_transcript, control=open_detail)`,携带 `transcriptRef`;`agentUiSubagentsViewModel.ts` 对 `teammate_transcript` 优先使用 `transcriptRef` 作为 action target,保证 Subagents 可以定位队友 transcript,而不是把队友内部输出混入主 final answer。
|
||||
- 已补 teammate transcript zoom 定位基线:`SubagentsView` 点击 `teammate_transcript/open_detail` 后展示 Transcript Zoom,显式列出 `transcriptRef`、父会话、子会话与最新回合;`useWorkspaceCanvasSceneRuntime.tsx` 的 action routing 会从 `childSessionId:turnId` 或 `parent/childSessionId` ref 解析本地 child session 焦点。该面板只做定位,不读取或复制 child transcript 正文到主 final answer。
|
||||
- 已补 teammate transcript live activity 预览基线:Transcript Zoom 在命中本地 child session 时展示子会话名称、状态、回合状态、角色、任务摘要,并读取 `liveActivityBySessionId[childSessionId]` 的最近进展;这不是从主 assistant prose 反推,也不会把 teammate 输出写入主 final answer。
|
||||
- 已补 teammate transcript 历史正文 fallback:当 Transcript Zoom 命中本地 child session 但没有 live activity 时,`TeamWorkbenchSummaryPanel` 会用 `getAgentRuntimeSession(childSessionId, { historyLimit: 20 })` 读取子会话详情,并复用 `extractSessionActivitySnapshot` 生成最近 3 条历史 activity preview;该 preview 仍只展示在 teammate_transcript zoom 内,不写入主 final answer。
|
||||
- 已补 selected teammate related projection chain:`TeamWorkbenchSummaryPanel` 点击任一工作台 action 后,会按 agent / task / work item / review / handoff / remote task / transcript ref 精确匹配并展示同一目标的相关 projection 事件,用于把 roster、board、transcript、worker notification 串起来;该链路只读取结构化 Agent UI projection,不合成新的 runtime fact。
|
||||
- 已补 teammate transcript 历史正文 fallback:当 Transcript Zoom 命中本地 child session 但没有 live activity 时,`SubagentsView` 会用 `getAgentRuntimeSession(childSessionId, { historyLimit: 20 })` 读取子会话详情,并复用 `extractSessionActivitySnapshot` 生成最近 3 条历史 activity preview;该 preview 仍只展示在 teammate_transcript zoom 内,不写入主 final answer。
|
||||
- 已补 selected teammate related projection chain:`SubagentsView` 点击任一工作台 action 后,会按 agent / task / work item / review / handoff / remote task / transcript ref 精确匹配并展示同一目标的相关 projection 事件,用于把 roster、board、transcript、worker notification 串起来;该链路只读取结构化 Agent UI projection,不合成新的 runtime fact。
|
||||
- 已补 worker notification result facts 前端 adapter:`agentProtocol.ts` 会解析 `subagent_status_changed` 的 `usage`、`duration_ms`、`tool_count`、`result_ref`;`agentUiEventProjection.ts` 在 terminal `worker.notification` 上保留 `transcriptRef`、`workerUsage` 和 payload 中的 duration / tool count / result ref。该增量只透传 source event 真值,不从 assistant prose 或 teammate transcript 反推。
|
||||
- 已补 worker notification Rust source 增量:`SubagentRuntimeStatus` 只在 terminal status 上解析 session token usage、latest turn duration、latest turn tool count 与 latest AgentMessage runtime item `result_ref`,`SubagentStatusChangedEvent` 输出 `usage`、`duration_ms`、`tool_count`、`result_ref` 给前端 adapter;`result_ref` 来自 runtime snapshot item,不从 teammate 文本反推。
|
||||
- 已补 worker notification GUI baseline:`agentUiTeamWorkbenchViewModel.ts` 会把 worker `transcriptRef`、`workerUsage`、duration、tool count 与 result ref 展示到操作视图 subtitle / chips;该展示仍只消费结构化 projection payload,不把 teammate 输出当作 worker result。
|
||||
- 已补 Review lane 细状态展示:`formatAgentUiProjectionEventAuxiliaryDetail` 会把 review payload 中的 `decisionStatus`、`reviewer`、`riskLevel`、`checklistCount`、`followupActionCount`、`regressionRequirementCount` 转成 lane 辅助详情,并在 Team Workbench surface detail 中展示。
|
||||
- 已补 Review requested fixes 明细:`buildAgentUiReviewProjectionEvents` 会保留 `requestedFixes`、`followupActions`、`regressionRequirements` 预览列表;`HarnessStatusPanel` 导出 / 保存 review decision 时把人工审核 followup actions 与 regression requirements 写入 projection;Team Workbench surface detail 展示首条修复与回归要求。
|
||||
- 已补 Review requested fixes work item 可见基线:`review.completed` 派生的 `task.changed(surface=work_board, runtimeEntity=work_item)` 现在会在 Team Workbench view model 中以修复正文作为 title,显示 `Review fix` / `pending` chip,并保留 `assign` control 与 review/work item target;这仍不代表修复已执行或已回写完成。
|
||||
- 已补 Review requested fix regression facts 基线:`HarnessStatusPanel` 会把 review template / save 结果里的 `verification_summary.focus_verification_failure_outcomes`、`focus_verification_recovered_outcomes` 与 artifact validator outcome 转为 `regressionOutcome`,写入 `review.requested` / `review.completed` payload 和 requested-fix work item;Team Workbench view model 会在修复项 subtitle / chip 显示 `回归:recovered|blocking_failure`。该增量只消费结构化 review/evidence fact,不把 pending fix 标成已执行。
|
||||
- 已补 worker notification GUI baseline:`agentUiSubagentsViewModel.ts` 会把 worker `transcriptRef`、`workerUsage`、duration、tool count 与 result ref 展示到操作视图 subtitle / chips;该展示仍只消费结构化 projection payload,不把 teammate 输出当作 worker result。
|
||||
- 已补 Review lane 细状态展示:`formatAgentUiProjectionEventAuxiliaryDetail` 会把 review payload 中的 `decisionStatus`、`reviewer`、`riskLevel`、`checklistCount`、`followupActionCount`、`regressionRequirementCount` 转成 lane 辅助详情,并在 Subagents surface detail 中展示。
|
||||
- 已补 Review requested fixes 明细:`buildAgentUiReviewProjectionEvents` 会保留 `requestedFixes`、`followupActions`、`regressionRequirements` 预览列表;`HarnessStatusPanel` 导出 / 保存 review decision 时把人工审核 followup actions 与 regression requirements 写入 projection;Subagents surface detail 展示首条修复与回归要求。
|
||||
- 已补 Review requested fixes work item 可见基线:`review.completed` 派生的 `task.changed(surface=work_board, runtimeEntity=work_item)` 现在会在 Subagents view model 中以修复正文作为 title,显示 `Review fix` / `pending` chip,并保留 `assign` control 与 review/work item target;这仍不代表修复已执行或已回写完成。
|
||||
- 已补 Review requested fix regression facts 基线:`HarnessStatusPanel` 会把 review template / save 结果里的 `verification_summary.focus_verification_failure_outcomes`、`focus_verification_recovered_outcomes` 与 artifact validator outcome 转为 `regressionOutcome`,写入 `review.requested` / `review.completed` payload 和 requested-fix work item;Subagents view model 会在修复项 subtitle / chip 显示 `回归:recovered|blocking_failure`。该增量只消费结构化 review/evidence fact,不把 pending fix 标成已执行。
|
||||
- 已补 requested fix execution source:`agentUiEventProjection.ts` 会从真实 `artifact_snapshot.metadata.requestedFixExecutionResults` 即时派生 `task.changed(surface=work_board, taskEvent=review_requested_fix)`,同时 `runtime_evidence_pack_service.rs` 从 `FileArtifact.metadata.requestedFixExecutionResults` / `requested_fix_execution_results` 收集执行结果,`runtime_review_decision_service.rs` 会把该结果带入 `verificationSummary.requestedFixExecutionResults` 并在 review decision Markdown 中显示 completed / recovered / blocked 统计与 `result_ref`;该链路只消费 artifact/evidence metadata,不从 review prose 或 teammate transcript 推断完成态。
|
||||
- 已完成 requested fix regression facts 定向复验:3 个相关测试文件 / 64 个测试通过,相关 ESLint 通过,`npm run typecheck` 通过;`npm run verify:local` 与 `npm run verify:gui-smoke` 被隔壁 v0.6 Rust/protocol 并行变更阻断,失败点是 `AgentTokenUsage` 尚未实现 `PartialEq/Eq`,本轮为避免冲突未修改 Rust 高冲突文件。
|
||||
- 已补 Review reviewer teammate 基线:`review.completed` 里存在 `reviewer` 时,`buildAgentUiReviewProjectionEvents` 会额外输出 `agent.changed(surface=team_roster, topology=review_team, runtimeEntity=work_item)`,保留 reviewer 名称、review id、decision/risk 与 `open_detail` target;该事件来自 review evidence fact,不从 prose 反推。
|
||||
- 已完成 reviewer teammate 基线复验:`npm run verify:local` 通过,覆盖 app-version、lint、typecheck、Vitest smart 52 批、contracts、Rust `cargo test --manifest-path lime-rs/Cargo.toml` 与 GUI smoke。
|
||||
- 已完成 requested fixes work item 展示增量复验:`npm run verify:local` 通过,覆盖 app-version、lint、typecheck、Vitest smart 52 批、contracts、Rust `cargo test --manifest-path lime-rs/Cargo.toml` 与 GUI smoke;GUI smoke 跑通 workspace-ready、browser-runtime、site-adapters、agent-service-skill-entry、agent-runtime-tool-surface、agent-runtime-tool-surface-page、knowledge-gui、design-canvas。
|
||||
- 已补 Team controls projection 基线:`subagent_status_changed -> task.changed` 会按 teammate status 给出 `continue_agent/wait/stop/close` 标准 control,`agent.spawned` 会给出 `delegate`;`teamControlAgentUiProjection.ts` 已把 wait/resume/send_input/close/stop 等实际 Team 操作记录为 `team_control_projection`,输出 `team.changed(surface=team_policy)` 与 `task.changed(surface=work_board)`;adapter 同时已支持 `delegate/assign/request_review`,分别落到 `delegation_graph/work_board/review_lane`,并优先消费 source-provided `resolvedStatus` 作为 top-level phase/runtimeStatus。
|
||||
- 已补 Team Workbench 非本地 target route taxonomy 与可见状态:`routeAgentUiTeamWorkbenchAction` 在命中本地 child session 时继续接 `continue_agent/wait/close/stop` 真实 handler;没有本地 child session 时按标准 target 返回 `located_only`、`remote_task_source_located`、`unsupported_review`、`unsupported_handoff`、`unsupported_work_item`,pending requested fix 可返回 `seeded_work_item` 并回填输入框,`team_reassignment` source fact 可返回 `work_item_source_located`,负责人 selector/callback 可返回 `seeded_reassignment` 并回填 TaskUpdate 指令;`TeamWorkbenchSummaryPanel` 会展示“只定位 / 远端任务已定位 / 审核未接入 / 交接未接入 / 工作项未接入 / 已回填输入 / 重指派已回填”等状态,让 UI 保持定位可追溯,同时不伪造 remote / review / handoff runtime 调用,也不伪造 work_board 完成态。
|
||||
- 已补 requested fix work board 状态可见化:`agentUiTeamWorkbenchViewModel.ts` 会把 source-provided `executionStatus` 显示为“待执行修复 / 修复完成 / 修复失败”等状态,并把 pending assign 标为“指派修复”、completed open_detail 标为“查看修复结果”;该增量只消费 review/evidence fact,不伪造修复执行。
|
||||
- 已补 requested fix 执行发起入口 baseline:`routeAgentUiTeamWorkbenchAction` 对 pending `review_requested_fix` work item 可生成修复 prompt 并通过 `seedAgentUiWorkbenchPrompt` 回填输入框,返回 `seeded_work_item`;发送仍由用户确认触发,不自动伪造执行中或完成态。
|
||||
- 已补 Subagents 非本地 target route taxonomy 与可见状态:产品侧 Subagents action callback 在命中本地 child session 时继续接 `continue_agent/wait/close/stop` 真实 handler;没有本地 child session 时按标准 target 返回 `located_only`、`remote_task_source_located`、`unsupported_review`、`unsupported_handoff`、`unsupported_work_item`,pending requested fix 可返回 `seeded_work_item` 并回填输入框,`team_reassignment` source fact 可返回 `work_item_source_located`,负责人 selector/callback 可返回 `seeded_reassignment` 并回填 TaskUpdate 指令;`SubagentsView` 会展示“只定位 / 远端任务已定位 / 审核未接入 / 交接未接入 / 工作项未接入 / 已回填输入 / 重指派已回填”等状态,让 UI 保持定位可追溯,同时不伪造 remote / review / handoff runtime 调用,也不伪造 work_board 完成态。
|
||||
- 已补 requested fix work board 状态可见化:`agentUiSubagentsViewModel.ts` 会把 source-provided `executionStatus` 显示为“待执行修复 / 修复完成 / 修复失败”等状态,并把 pending assign 标为“指派修复”、completed open_detail 标为“查看修复结果”;该增量只消费 review/evidence fact,不伪造修复执行。
|
||||
- 已补 requested fix 执行发起入口 baseline:产品侧 Subagents action callback 对 pending `review_requested_fix` work item 可生成修复 prompt 并回填输入框,返回 `seeded_work_item`;发送仍由用户确认触发,不自动伪造执行中或完成态。
|
||||
- 已补 requested fix seed prompt 的 execution result metadata contract:pending fix 回填输入框时会要求后续执行把 `requestedFixExecutionResults` 写入 evidence/artifact metadata,字段覆盖 requestedFix、executionStatus、regressionOutcome、resultRef 与 artifactIds/artifactPaths;`artifact_snapshot` 带回该 metadata 后会即时进入 `work_board`,该增量只消费真实 source 回写契约,不伪造完成态。
|
||||
- 已补 Team formation assignment source:发送前生成本地 Team formation 时,会通过 `teamFormationAgentUiProjection.ts` 记录 `team.changed(surface=team_roster)`、`agent.changed(surface=team_roster/control=assign)` 与 `task.changed(surface=work_board/control=assign)`;Team control 操作也已携带当前 `sessionId`,避免 summary panel 过滤不到 wait/resume/close/stop 事件。
|
||||
- 已补 TaskUpdate owner_change reassignment source:`TaskUpdateTool` 在 owner 真正变化时输出结构化 `ownerChange` / `owner_change`;`agentUiEventProjection.ts` 只从 `item_completed + tool_call(TaskUpdate) + metadata.updated_fields includes owner` 派生 `work_board` assign/reassign,保留 previous/next assignee 与 source tool/task list id,不从 `task_assignment` peer message、assistant prose 或 teammate transcript 反推负责人变化。
|
||||
- 已补 Review request actual source:`HarnessStatusPanel` 导出人工审核模板时,除了 `review.requested` evidence event,还会记录 `team_control_projection(control=request_review)`,以 `review_relative_root` 作为 review work item,落到 `review_lane` 并保留 `sessionId/threadId` scope。
|
||||
- 已补 Remote teammate actual source baseline:`remoteTaskAgentUiProjection.ts` 提供 `remote_task_projection`,将结构化 remote task 映射为 `agent.changed/task.changed(surface=remote_teammate, runtimeEntity=external_task)`,保留 Agent Card、remote task id、input/auth、artifact refs,并且只在真实 terminal status 下输出 `worker.notification`,避免 heartbeat 猜完成;input/auth need 会额外输出 `action.required(surface=remote_teammate, control=answer)`,让远端待输入 / 待鉴权成为结构化 HITL fact;当 `remote_task` provenance 提供 `event/taskStatus/status/state/phase` 或嵌套 `task/a2aTask` artifact updates 时也会进入同一 projection,terminal `AgentRun.status` 仍优先压过 stale remote/input/auth source;`agentUiEventProjection.ts` 内的旧 remote teammate builder 已改为兼容 wrapper,统一委托该 helper。2026-05-10 已把 `gateway_channel_*` / gateway crate 接到 current source:渠道 runtime 写入 `source_metadata.remote_task`,`agent_runs.metadata` 保留 provenance,`useRemoteTaskExecutionRunProjection` 再按当前 session 投影到 Team Workbench;剩余原生 A2A ingress 与 remote artifact store/content 回流已登记为 future product boundary。
|
||||
- 已补 Remote teammate actual source baseline:`remoteTaskAgentUiProjection.ts` 提供 `remote_task_projection`,将结构化 remote task 映射为 `agent.changed/task.changed(surface=remote_teammate, runtimeEntity=external_task)`,保留 Agent Card、remote task id、input/auth、artifact refs,并且只在真实 terminal status 下输出 `worker.notification`,避免 heartbeat 猜完成;input/auth need 会额外输出 `action.required(surface=remote_teammate, control=answer)`,让远端待输入 / 待鉴权成为结构化 HITL fact;当 `remote_task` provenance 提供 `event/taskStatus/status/state/phase` 或嵌套 `task/a2aTask` artifact updates 时也会进入同一 projection,terminal `AgentRun.status` 仍优先压过 stale remote/input/auth source;`agentUiEventProjection.ts` 内的旧 remote teammate builder 已改为兼容 wrapper,统一委托该 helper。2026-05-10 已把 `gateway_channel_*` / gateway crate 接到 current source:渠道 runtime 写入 `source_metadata.remote_task`,`agent_runs.metadata` 保留 provenance,`useRemoteTaskExecutionRunProjection` 再按当前 session 投影到 Subagents;剩余原生 A2A ingress 与 remote artifact store/content 回流已登记为 future product boundary。
|
||||
- 已补 active stream overlay 收口:`text_delta` 继续走低频 overlay 实时显示,`final_done` 后回填到 message content/contentParts;失败态保留已有 tool/process contentParts 并追加失败正文,避免“无最终正文”错误把工具过程清空。
|
||||
- 已完成 2026-05-10 final gate fresh 复验:`npm run verify:local` 统一门禁通过,覆盖 app-version、lint、typecheck、Vitest smart 52 批、contracts、Rust 全量与 GUI smoke;此前旧 stale gate / DevBridge 不可用问题已由 fresh unified gate 证据替代,不再阻塞 v0.6.0 标准对齐完成判定。
|
||||
|
||||
@@ -230,21 +230,21 @@ AgentRuntimeProfileEvent / ThreadReadModel / EvidenceSummary
|
||||
- 前端与契约门禁:`npm run verify:app-version` 通过(版本 `1.32.0`),`npm run lint` 通过,`npm run typecheck` 通过,`npm test` 通过(Vitest smart 52 批),`npm run test:contracts` 通过,`npm run check:agent-runtime-clients` 通过,`npm run harness:doc-freshness` clean。
|
||||
- Rust 门禁:`npm run verify:local` 内的 `cargo test --manifest-path lime-rs/Cargo.toml` 通过;修复本地 `lime-rs/target` 构建缓存后,单独复核 `cargo test --manifest-path "lime-rs/Cargo.toml"` 也通过:`lime` root unit tests 1295 passed,`src/main.rs` 0 passed,integration tests 2 passed,真实联网测试 2 ignored。
|
||||
- GUI 门禁:`npm run verify:local` 内的 `npm run verify:gui-smoke` 通过;覆盖 DevBridge health、前端壳、workspace-ready、browser-runtime、site-adapters、agent-service-skill-entry、agent-runtime-tool-surface、agent-runtime-tool-surface-page、knowledge-gui、design-canvas,并完成本轮 smoke Chrome profile 清理。
|
||||
- 完成判定:Agent UI v0.6.0 projection / Team Workbench / review lane / work_board / worker notification / remote teammate baseline 已达到可交付门槛。`direct board/team write-back`、`native A2A ingress`、`remote artifact store/content bytes`、`remote control callback`、无人工确认后台执行策略与更深 teammate drilldown 均保持 future product / command boundary,不作为 v0.6.0 完成阻塞项。
|
||||
- 完成判定:Agent UI v0.6.0 projection / Subagents / review lane / work_board / worker notification / remote teammate baseline 已达到可交付门槛。`direct board/team write-back`、`native A2A ingress`、`remote artifact store/content bytes`、`remote control callback`、无人工确认后台执行策略与更深 teammate drilldown 均保持 future product / command boundary,不作为 v0.6.0 完成阻塞项。
|
||||
|
||||
## 8.2 2026-05-10 Board/team 直接写回命令边界审计
|
||||
|
||||
- 已审计 current 命令边界:`src/lib/api`、App Server / Electron Desktop Host 命令边界、`agentCommandCatalog.json`、`mockPriorityCommands.ts` 与 `defaultMocks` 均没有可复用的 board/team 直接写回命令;`TaskCreate/TaskList/TaskGet/TaskUpdate` 当前是 aster runtime tool / inventory,不是 legacy desktop facade command。
|
||||
- 已确认真实可写事实源仍是 `TaskUpdateTool`:它通过 `resolve_task_board_state` / `persist_task_board_state` 读写 session task board,并在 owner 变化时输出 `ownerChange` / `owner_change` metadata;Agent UI 只消费该结构化 metadata 派生 `work_board` assign/reassign。
|
||||
- 已明确不新增临时 UI 本地写回或平行 legacy desktop command。若未来要做无 prompt direct board/team write-back,必须先新增 App Server current method 或 shared task board service,并同步前端网关、Electron / App Server 命令边界、治理目录册与测试 mock,同时解决 runtime `shared_task_list_storage` 与 session `extension_data` 的一致性。
|
||||
- 因此 v0.6 标准对齐口径调整为:`work_board` / reassignment 的合规 baseline 是 `Team Workbench selector -> TaskUpdate prompt/runtime turn -> TaskUpdateTool owner_change source -> Agent UI projection`;无 prompt 直接写回归入 future product command boundary,不再作为本轮 projection 对齐阻塞项。
|
||||
- 因此 v0.6 标准对齐口径调整为:`work_board` / reassignment 的合规 baseline 是 `Subagents selector -> TaskUpdate prompt/runtime turn -> TaskUpdateTool owner_change source -> Agent UI projection`;无 prompt 直接写回归入 future product command boundary,不再作为本轮 projection 对齐阻塞项。
|
||||
|
||||
## 8.3 2026-05-10 Playwright E2E 与真实 child context 复验
|
||||
|
||||
- 已补浏览器 DevBridge subagent control 命令族:`agent_runtime_spawn_subagent`、`agent_runtime_send_subagent_input`、`agent_runtime_wait_subagents`、`agent_runtime_resume_subagent`、`agent_runtime_close_subagent` 已接入 dispatcher 与 HTTP cooldown bypass,契约仍落在现有 `agent_runtime_*` current 主链;后续不得在 `lime-rs/src/commands/**` 追加新业务逻辑。
|
||||
- 已补 session store limited history 空会话探测:`get_runtime_session_detail_with_history_page` 不再因为 persisted empty fast path 跳过 runtime overlay / subagent context;`agent_runtime_get_session(parent, { historyLimit })` 能返回 `child_subagent_sessions`,空 child detail 也能返回 `subagent_parent_context`。
|
||||
- Playwright E2E 复验已覆盖:普通会话 tab 不显示 `AgentUI N` 内部 badge;Team Workbench `Agent UI v0.6 / 10 events`、10 个 surface、三类 lane、`工作台操作视图`、`Surface 专门视图`、Harness AgentUI 投影、真实 child context 与 console `0 error / 0 warning` 均有证据。
|
||||
- 证据索引:`internal/exec-plans/agentui-playwright-e2e-2026-05-10.md`;截图与 JSON 位于 `internal/exec-plans/evidence/agentui-e2e-2026-05-10/09-session-store-subagent-context.json`、`09-team-workbench-real-child-context-after-session-store-fix.png`、`10-transcript-open-detail-real-child-focus-context.png`、`10-console-errors-final-real-child-focus.txt`。
|
||||
- Playwright E2E 复验已覆盖:普通会话 tab 不显示 `AgentUI N` 内部 badge;Subagents `Agent UI v0.6 / 10 events`、10 个 surface、三类 lane、`工作台操作视图`、`Surface 专门视图`、Harness AgentUI 投影、真实 child context 与 console `0 error / 0 warning` 均有证据。
|
||||
- 证据索引:`internal/exec-plans/agentui-playwright-e2e-2026-05-10.md`;截图与 JSON 位于 `internal/exec-plans/evidence/agentui-e2e-2026-05-10/`,覆盖 session store subagent context、真实 child context、transcript detail focus 与 console final check。
|
||||
- 复验命令:`cargo test --manifest-path "lime-rs/Cargo.toml" -p lime-agent should_probe -- --nocapture`、`cargo test --manifest-path "lime-rs/Cargo.toml" dev_bridge --lib -- --nocapture`、相关 Vitest / ESLint、`npm run typecheck`、`npm run test:contracts`、`npm run verify:gui-smoke -- --reuse-running` 均通过。
|
||||
- 已补 child focus `返回主助手` 点击稳定性:embedded Team board sticky header 提升到 `z-40`,返回按钮增加独立可点击层级与 `data-testid`;Playwright harness 证据位于 `internal/exec-plans/evidence/product-backend-ux-e2e-2026-05-10/01-child-focus-return-parent-before-click.png`、`02-return-parent-after-click.png`、`02-return-parent-hit-target.json`、`02-console-errors-return-parent.txt`,点击前命中目标为 `BUTTON`,点击后返回按钮消失,console `0 error / 0 warning`。
|
||||
- 剩余项继续降级为 E2E 深度增强:让真实 child turn 产出最小 activity preview、避免 requested-fix action 在测试中误触发真实 runtime turn、在 DevBridge `3030` 恢复后用真实 child session 再复跑同一返回路径;这些不阻塞 v0.6.0 标准 baseline 完成判定。
|
||||
@@ -272,8 +272,8 @@ AgentRuntimeProfileEvent / ThreadReadModel / EvidenceSummary
|
||||
- 已补 subagent stream 投影入口:`runtimeEventSubscriptions.ts` 将 team workspace 的 `subagent_status_changed` 与 `agent_subagent_stream:<session>` 写入 `conversationProjectionStore.agentUi`。
|
||||
- 已补 automation job 投影入口:`buildAgentUiAutomationJobProjectionEvents` 与 `recordAutomationJobAgentUiProjection` 将结构化 automation job record 投影为 `automation_job` runtime entity,并接入 SceneApp / Service Skill 自动化创建路径。
|
||||
- 已补 projection 消费收口:`agentUiProjectionSummary.ts`、`useConversationProjectionStore.ts`、`TaskCenterTabStrip`、`AgentThreadTimelineArtifactCard`、`AgentThreadReliabilityPanel`、`HarnessStatusPanel` 已开始按 scoped selector 消费标准投影。
|
||||
- 已补相关回归测试:adapter、store、runtime handler、provider tool delta、MCP notification、hydration、metrics、Harness evidence、TaskCenter、Artifact、Diagnostics、team runtime subscription、v0.6 Team Workbench surface summary 均已有定向覆盖;下一步重点补 controls adapter 与 richer lane 级 UI 回归。
|
||||
- 已补相关回归测试:adapter、store、runtime handler、provider tool delta、MCP notification、hydration、metrics、Harness evidence、TaskCenter、Artifact、Diagnostics、team runtime subscription、v0.6 Subagents surface summary 均已有定向覆盖;下一步重点补 controls adapter 与 richer lane 级 UI 回归。
|
||||
|
||||
## 10. 与路线图主线关系
|
||||
|
||||
这份文件继续服务 `internal/roadmap/agentui/README.md` 的主目标:AgentUI 不是聊天页,而是 `Conversation + Process + Task + Artifact + Evidence + Team Workbench` 工作台。Thinking UI 的修复只是 P0 体感缺口;v0.6.0 的 flow/taxonomy 用于约束后续每一刀都回到同一条 current projection 主链。
|
||||
这份文件继续服务 `internal/roadmap/agentui/README.md` 的主目标:AgentUI 不是聊天页,而是 `Conversation + Process + Task + Artifact + Evidence + Subagents` 工作台。Thinking UI 的修复只是 P0 体感缺口;v0.6.0 的 flow/taxonomy 用于约束后续每一刀都回到同一条 current projection 主链。
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# Lime Agent Workbench 路线图
|
||||
|
||||
> 状态:active
|
||||
> 更新时间:2026-06-11
|
||||
> 范围:`/Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench` 文档站、Lime AgentUI SDK 标准、AgentRuntime 配合边界、Subagents 标准化。
|
||||
|
||||
## 主目标
|
||||
|
||||
把 `lime-agent-workbench` 从“参考 AG-UI 的中文文档站”推进成 Lime 内部 AgentUI / AgentRuntime 标准指引,并让后续实现只能向 Lime current 主链收敛:
|
||||
|
||||
```text
|
||||
App Server / RuntimeCore / ExecutionBackend
|
||||
-> RuntimeEvent / ThreadReadModel / TaskSnapshot / EvidencePack
|
||||
-> @limecloud/agent-ui-contracts
|
||||
-> @limecloud/agent-runtime-projection
|
||||
-> @limecloud/agent-runtime-ui
|
||||
-> Product Apps / Content Studio / Agent Apps
|
||||
```
|
||||
|
||||
Workbench 不是外部 SDK 套壳,也不是第二套 runtime。外部 AG-UI、AI SDK、OpenAI Agents JS、LangGraph 等只能作为参考;Lime 的事实源仍是 App Server、RuntimeCore、ExecutionBackend、AgentUI Projection、Evidence / Replay / Review 和治理分类。
|
||||
|
||||
## 固定结论
|
||||
|
||||
1. AgentRuntime 拥有执行事实;AgentUI 只消费 facts 并投影 UI。
|
||||
2. UI 标准使用 `UIMessageParts`、`ProcessTimeline`、`ExecutionGraph`、ToolGroup、ActionRequired、ArtifactRef、EvidenceRef;不再使用非标准组件树术语。
|
||||
3. TypeScript 包线拆为四个 current 包,`@limecloud/agent-ui` 只允许作为 future facade。
|
||||
4. 产品应用不能各自复制过程组件、runtime client、投影 reducer 或工具状态机。
|
||||
5. `subagents` 必须进入 RuntimeEvent / TaskSnapshot / ExecutionGraph / `AgentUiProjectionState.subagents` 主链,不能靠 assistant 正文或本地 React state 表达。
|
||||
6. 文档站必须和 Lime 现有代码、路线图、App Server / AgentRuntime / AgentUI 事实源对齐;不能写成外部标准翻译稿。
|
||||
|
||||
## 当前阶段
|
||||
|
||||
当前处于 `v0.2.x -> v0.4.x` 标准化阶段:
|
||||
|
||||
| 阶段 | 状态 | 目标 |
|
||||
| --- | --- | --- |
|
||||
| v0.1.0 | done | 建站、发布 GitHub Pages、形成中文文档骨架。 |
|
||||
| v0.2.0 | done | 去掉非标准树形过程方向,初步对齐 AG-UI / AI SDK / Lime runtime 现实。 |
|
||||
| v0.3.0 | active | 补齐 Subagents、SDK 包边界、runtime client、projection conformance、Lime 现有实现迁移路线。 |
|
||||
| v0.4.0 | active | 从文档标准推进到 Lime 主仓包实现、guard、Artifact/Evidence refs surface、Agent App 真实 smoke 和 Content Studio 标准 UI surface adoption。 |
|
||||
|
||||
## 阅读顺序
|
||||
|
||||
| 文档 | 作用 |
|
||||
| --- | --- |
|
||||
| [iteration-plan.md](iteration-plan.md) | 本次迭代目标、缺口、写集和完成判定。 |
|
||||
| [task-board.md](task-board.md) | 当前迭代可并行认领的任务板、写集、验收和推荐下一刀。 |
|
||||
| [parallel-workstreams.md](parallel-workstreams.md) | 多进程并行认领方式、每条 workstream 的写集和验收。 |
|
||||
| [acceptance.md](acceptance.md) | Workbench 标准、SDK、runtime、subagents 的统一验收矩阵。 |
|
||||
| [standard-package-release-runbook.md](standard-package-release-runbook.md) | 标准 npm 包发布、Content Studio 安装接入和 GUI smoke 的阻塞解除步骤。 |
|
||||
|
||||
相关既有路线图:
|
||||
|
||||
- `internal/roadmap/agentui/README.md`
|
||||
- `internal/roadmap/agentruntime/README.md`
|
||||
- `internal/roadmap/agentruntime/agentui-adoption-gap.md`
|
||||
- `internal/roadmap/agentruntime/agentruntime-standard-adoption-gap.md`
|
||||
- `internal/roadmap/agentapp/README.md`
|
||||
|
||||
## current / compat / deprecated / dead
|
||||
|
||||
### current
|
||||
|
||||
- `lime-agent-workbench` GitHub Pages 中文标准站。
|
||||
- Lime App Server JSON-RPC + RuntimeCore + ExecutionBackend runtime facts。
|
||||
- `RuntimeEvent / ThreadReadModel / TaskSnapshot / EvidencePack`。
|
||||
- `@limecloud/agent-ui-contracts`、`@limecloud/agent-runtime-projection`、`@limecloud/agent-runtime-ui`、`@limecloud/agent-runtime-client`。
|
||||
- Product Apps 通过 runtime client + projection + React surfaces 复用 AgentUI。
|
||||
- Agent App 运行页通过标准 `AgentRuntimeClient`、`projectAgentUiState` 和 `AgentUiProjectionView` 接入共享 SDK seed。
|
||||
- Agent App Host action 决策已走 current `AgentRuntimeClient.respondAction -> agentSession/action/respond`;Host drawer 主体只保留抽屉、指标和标准 projection 接线,本地 process fallback / projection 输入增强已拆成独立模块。
|
||||
- Agent App 真实 Electron 主路径已通过 smoke:`Agent Apps` 聚合入口 -> runtime iframe SDK -> App Server current `agentSession/*` -> Host Agent Run 标准 projection DOM。
|
||||
- Content Studio `AI agents` 工作台和通用 `AgentSessionPanel` 已统一通过 `AgentUiProjectionSurface` 消费共享 projection read model,并输出 `.agent-ui-projection` / `.agent-ui-main` / `.agent-ui-sidecar` 标准 DOM surface;其 standalone/dev App Server turn 主链已通过 `@limecloud/agent-runtime-client/sessionGateway` 包装为标准 `AgentRuntimeClient`。标准 npm 链路已发布并接入 `@limecloud/app-server-client@1.66.0` 与 `@limecloud/agent-runtime-client@0.1.1`,Content Studio 不能安装误发的无 scope 链路。
|
||||
- `@limecloud/agent-runtime-ui` 已有 `ArtifactRefList` / `EvidenceRefList`,引用内容由宿主 artifact workspace / evidence pack owner 打开。
|
||||
- `@limecloud/agent-runtime-ui` 已有 `SubagentsView`,只消费 `state.subagents`。
|
||||
|
||||
### compat
|
||||
|
||||
- Lime 现有 `packages/app-server-client` 作为 `@limecloud/app-server-client` 的本地开发事实源。
|
||||
- 未来 `@limecloud/agent-ui` facade 作为非 current 可选聚合入口。
|
||||
- 产品应用本地 `messages / executionEvents / process panel / host bridge service` 作为迁移缓存;已接入共享 SDK 的页面不得再新增平级本地过程事实源。
|
||||
- Agent App task event 作为 projection 输入,但必须桥接到标准 event class。
|
||||
|
||||
退出条件:facade 明确不导出 runtime transport,产品应用只显式依赖 current 四包后,不再用规划名描述 current owner。
|
||||
|
||||
### deprecated
|
||||
|
||||
- 每个产品应用自建 Agent 过程组件。
|
||||
- 从 assistant prose 解析工具成功、artifact 类型、evidence verdict。
|
||||
- 把 UI component tree 当成 runtime protocol。
|
||||
- AgentUI React 组件直接订阅 Provider 或读取 App Server DB。
|
||||
|
||||
### dead
|
||||
|
||||
- 非标准树形过程术语作为 Lime 标准术语或协议。
|
||||
- 已清退的旧协作 UI surface 作为 current 类型、组件、state 字段或 SDK API。
|
||||
- 第二套 Agent App runtime facts。
|
||||
- 业务 App 直连 Provider API 作为生产 Agent 能力。
|
||||
- 把外部 SDK 作为 Lime runtime owner 直接套用。
|
||||
|
||||
## 本轮下一刀
|
||||
|
||||
优先补 Lime 主仓与 `lime-agent-workbench` 之间的实现闭环:
|
||||
|
||||
1. 补 Content Studio GUI smoke / 产品回归,证明 `.agent-ui-projection` 标准 surface 在真实页面中可运行,并把边界审计纳入常规验证。
|
||||
2. Agent App projection bridge 已拆成 normalization facade、projection builders、field readers、runtime event adapter 和 mapping helpers;下一步只在标准 projection 空态覆盖足够后下线本地 process fallback。
|
||||
3. 继续收紧治理 guard,防止非标准树术语、本地 process owner、直连 Provider runtime 和第二套 runtime facts 回流。
|
||||
|
||||
## 并行协作规则
|
||||
|
||||
多进程并行时,先只读 `git status --short`,再认领窄写集。默认不要多个进程同时编辑同一个文件;如果必须改同一导航文件,先完成内容页,再由一个进程统一接导航。
|
||||
|
||||
本路线图目录是并行协作入口。新进程先读 `task-board.md` 认领窄写集,完成后把进度写回 `iteration-plan.md` 的进度日志,避免聊天记录成为唯一状态源。
|
||||
|
||||
发布与 Content Studio 接入阶段先读 `standard-package-release-runbook.md`。当前 scoped registry 发布与 Content Studio 依赖接入已完成;后续提升完成度前必须先补 GUI smoke / 产品回归证据。
|
||||
@@ -0,0 +1,122 @@
|
||||
# Agent Workbench 验收矩阵
|
||||
|
||||
> 状态:active
|
||||
> 范围:文档站标准、SDK 边界、AgentRuntime 配合、Subagents、治理与发布。
|
||||
|
||||
## 1. 文档站验收
|
||||
|
||||
| 项 | 必须满足 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| 架构 | App Server、RuntimeCore、ExecutionBackend、AgentUI Projection、Product Apps 的关系清楚 | `docs/concepts/architecture.md` |
|
||||
| 协议参考 | AG-UI、AI SDK、OpenAI Agents JS 等只作为 reference,不成为 Lime owner | `docs/agentic-protocols.md` |
|
||||
| 标准术语 | 使用 UIMessageParts、ProcessTimeline、ExecutionGraph、ToolGroup、ActionRequired、ArtifactRef、EvidenceRef | `docs/contracts/ui-projection.md` |
|
||||
| 禁止旧词 | 不把非标准树形过程术语写成 Lime 标准 | 全站搜索 |
|
||||
| 导航 | 新增核心页面能从 sidebar 访问 | `docs/.vitepress/config.mts` |
|
||||
| 构建 | VitePress build 通过 | `npm run docs:build` |
|
||||
|
||||
## 2. SDK 验收
|
||||
|
||||
| 包 | current 职责 | 禁止 |
|
||||
| --- | --- | --- |
|
||||
| `@limecloud/agent-ui-contracts` | types、schemas、fixtures、version constants | React、transport、provider、产品应用依赖 |
|
||||
| `@limecloud/agent-runtime-projection` | projector、selectors、hydration、replay、reconciliation、Subagents model | React DOM、Electron、Provider SDK、App Server DB |
|
||||
| `@limecloud/agent-runtime-ui` | React primitives、surfaces、callbacks、i18n label contract、CSS / DOM contract | runtime truth、Provider key、JSON-RPC transport |
|
||||
| `@limecloud/agent-runtime-client` | App Server JSON-RPC、Host bridge、events、read APIs、action response、evidence export | UI projection state、React component、Provider Store owner |
|
||||
| `@limecloud/agent-ui` | future facade,只 re-export UI 侧包 | runtime transport、业务实现 |
|
||||
|
||||
最小 API 验收:
|
||||
|
||||
- contracts 有 RuntimeEvent、ThreadReadModel、TaskSnapshot、ProjectionState、ArtifactRef、EvidenceRef、AgentUiSubagentsModel。
|
||||
- projection 能 `projectAgentUiState`、`createAgentUiProjector`、`replayAgentUiFixture`、`replayAppServerFacts`、`buildAgentUiSubagentsModel`。
|
||||
- react 组件只接受 projection state 和 command callbacks;Subagents 只消费 `state.subagents`;Artifact/Evidence refs 只展示轻量 ref 并通过 callback 交还宿主。
|
||||
- runtime-client 有 `startTurn`、`subscribeEvents`、`nextEvent`、`readThread`、`respondAction`、`cancelTurn`、`exportEvidence`。
|
||||
|
||||
## 3. Runtime 配合验收
|
||||
|
||||
| 能力 | 必须事实化 | UI 表达 |
|
||||
| --- | --- | --- |
|
||||
| turn 生命周期 | `turn.submitted / started / completed / failed` | Runtime status、message shell |
|
||||
| 模型输出 | `model.delta / completed / failed` | UIMessageParts text |
|
||||
| reasoning / plan | `reasoning.delta / summary`、`plan.delta / final` | reasoning part、ProcessTimeline |
|
||||
| 工具 | `tool.started / args / progress / result / failed` | ToolGroup、ExecutionGraph step |
|
||||
| 人工动作 | `action.required / resolved / cancelled / expired` | ActionRequired |
|
||||
| artifact | `artifact.changed / versioned / exported` | ArtifactRef、artifact workspace |
|
||||
| evidence | `evidence.changed / exported / review.verdict` | Evidence lane、review/replay |
|
||||
| subagent | `subagent.started / updated / completed / failed` | Subagents threads、ExecutionGraph child |
|
||||
| handoff / review | `handoff.requested / completed`、`review.verdict` | Subagents delegation / activity summary |
|
||||
| 断流恢复 | `stream.repaired / snapshot.updated` | stale / repairing 状态 |
|
||||
|
||||
不合格情况:
|
||||
|
||||
- 只有 assistant prose,没有 typed payload。
|
||||
- UI 从本地 state 推断工具成功或 evidence verdict。
|
||||
- 产品应用直读 Provider key 或 App Server DB。
|
||||
- Runtime provider 输出 React props。
|
||||
|
||||
## 4. Subagents 验收
|
||||
|
||||
| 项 | 必须满足 |
|
||||
| --- | --- |
|
||||
| identity | subagent 必须有 `subagentId`,并关联 parent task / run / step。 |
|
||||
| lineage | parent/child、handoff、dependency edges 可被 ExecutionGraph 表达。 |
|
||||
| lifecycle | started、progress、blocked、completed、failed、cancelled 可区分。 |
|
||||
| communication | channel message、worker notification、handoff request 不伪装成普通用户消息。 |
|
||||
| control | cancel / pause / resume / respond action 进入 runtime control plane。 |
|
||||
| evidence | 子代理输出能进入 artifact/evidence refs,保留 correlation ids。 |
|
||||
| projection | `AgentUiProjectionState.subagents` 有 threads、delegationCalls、activities、active/completed/failed thread ids 和 isolation 摘要。 |
|
||||
| degradation | 缺 lineage 时显示 unknown / unavailable,不猜测团队结构。 |
|
||||
|
||||
## 5. Conformance Fixture 验收
|
||||
|
||||
每个 runtime provider 或 SDK 切片至少准备以下 fixture:
|
||||
|
||||
| Fixture | 覆盖 |
|
||||
| --- | --- |
|
||||
| text-basic | streaming delta + final reconciliation |
|
||||
| tool-success | tool args/progress/result + output ref |
|
||||
| tool-failure | failure category + recovery action |
|
||||
| hitl-action | action required/resolved + waiting state |
|
||||
| artifact-evidence | artifact changed + evidence exported |
|
||||
| stream-repair | interrupted stream + read model repair |
|
||||
| subagent-handoff | parent task -> child subagent -> handoff/review |
|
||||
|
||||
通过标准:
|
||||
|
||||
- Projection replay 幂等。
|
||||
- 重放顺序由 `sequence` 和 scope ids 决定。
|
||||
- 大输出只通过 refs 表达。
|
||||
- 断流后能从 read model 修复。
|
||||
|
||||
## 6. Lime 主仓迁移验收
|
||||
|
||||
| 现有路径 | 目标 | 验收 |
|
||||
| --- | --- | --- |
|
||||
| `packages/app-server-client` | `@limecloud/agent-runtime-client` seed | 不生成 UI projection,不带 React。 |
|
||||
| `packages/agent-runtime-projection` | `@limecloud/agent-runtime-projection` current | projector / selectors 在 React 外可测,输出 `state.subagents`。 |
|
||||
| `packages/agent-runtime-ui` | `@limecloud/agent-runtime-ui` current | 只消费 projection state,Subagents 不重算 runtime facts,Artifact/Evidence refs 不读取大 payload。 |
|
||||
| `packages/agent-app-runtime/projection` | compat adapter | 迁完后不保留第二套 projection owner。 |
|
||||
| 产品应用 local process component | deprecated | 迁到共享 ProcessTimeline / ExecutionGraph / Subagents surfaces。 |
|
||||
|
||||
## 7. 发布验收
|
||||
|
||||
| 项 | 命令 / 证据 |
|
||||
| --- | --- |
|
||||
| 构建 | `npm run docs:build` |
|
||||
| 版本 | `package.json`、`package-lock.json` 一致 |
|
||||
| 更新记录 | `docs/development/updates.md` |
|
||||
| GitHub Pages | `.github/workflows/deploy.yml` |
|
||||
| tag / push | 仅在用户明确要求后执行 |
|
||||
|
||||
## 8. 完成度口径
|
||||
|
||||
| 完成度 | 口径 |
|
||||
| --- | --- |
|
||||
| 30% | 文档站骨架和核心概念存在。 |
|
||||
| 50% | P0 文档齐全,构建通过,导航完整。 |
|
||||
| 70% | SDK API 草案、fixture、conformance 和迁移表完整。 |
|
||||
| 85% | Lime 主仓 seed 包实现可被至少一个产品应用消费。 |
|
||||
| 95% | 四包 seed、SDK reference、refs surface、Agent App seed adoption 和 Content Studio adoption blueprint 完成,但仍缺真实产品主路径 smoke。 |
|
||||
| 97% | Agent App 主路径已通过真实 Electron smoke 证明复用 runtime-client + projection + React surfaces;Content Studio 仍停留在 adoption blueprint。 |
|
||||
| 98% | Content Studio 产品 UI 主路径已统一到标准 projection surface,且有边界审计阻止页面直接拼共享 primitives;但 App Server turn 主链仍未抽成标准 session gateway 形状。 |
|
||||
| 98.5% | Content Studio App Server turn 主链已抽到标准 session gateway 形状,`nextEvent()` 保持 `agentSession/event` notification 合同,sidecar service 已委托 gateway;标准包已发布到 `@limecloud` organization:`@limecloud/app-server-client@1.66.0` 与 `@limecloud/agent-runtime-client@0.1.1`。Content Studio 已固定安装 registry 版 `@limecloud/agent-runtime-client@0.1.1`,并通过 `@limecloud/agent-runtime-client/sessionGateway` 包装现有 gateway;首轮误发的无 scope `app-server-client@1.66.0` 和依赖旧名的 `@limecloud/agent-runtime-client@0.1.0` 只作为 compat-misrelease 记录。GUI smoke 仍未补齐,因此不提升到 100%。 |
|
||||
| 100% | Content Studio 主路径真实复用 runtime-client + projection + React surfaces,且 Agent App / Content Studio 均有 smoke / contract 证明。 |
|
||||
@@ -0,0 +1,217 @@
|
||||
# Agent Workbench v0.3.0 迭代计划
|
||||
|
||||
> 状态:active
|
||||
> 创建时间:2026-06-10
|
||||
> 目标仓库:`/Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench`
|
||||
> Lime 规则归档:`internal/roadmap/agentworkbench/`
|
||||
|
||||
## 主目标
|
||||
|
||||
把 Agent Workbench 做成 Lime AgentUI / AgentRuntime 标准指引,能指导 Content Studio、Agent Apps 和后续 Lime 主仓 SDK 收敛实现,而不是只提供概念介绍。
|
||||
|
||||
本次迭代完成后,应能回答:
|
||||
|
||||
1. Lime AgentUI 标准表面有哪些,哪些是 current owner。
|
||||
2. `@limecloud/agent-ui-contracts`、`@limecloud/agent-runtime-projection`、`@limecloud/agent-runtime-ui` 与 `@limecloud/agent-runtime-client` 如何拆包,为什么不合成一个大包。
|
||||
3. App Server / RuntimeCore / ExecutionBackend 需要输出哪些 facts,AgentUI 如何消费。
|
||||
4. Subagents 在 Lime 中怎么建模、投影、验收。
|
||||
5. 现有 Lime 包和产品应用如何迁移,哪些路径是 compat / deprecated / dead。
|
||||
|
||||
## 当前事实
|
||||
|
||||
| 事实 | 状态 |
|
||||
| --- | --- |
|
||||
| `lime-agent-workbench` 已有 VitePress 文档站和 GitHub Pages workflow | done |
|
||||
| `package.json` 版本为 `0.2.0` | done |
|
||||
| 文档已有 architecture、runtime-event、ui-projection、quickstart、SDK overview | done |
|
||||
| 文档已经去除非标准树形过程术语作为标准方向 | done |
|
||||
| `/subagents` 顶层页缺失 | done |
|
||||
| SDK 四包已从文档规划推进到 package-level API / fixture / conformance seed | done |
|
||||
| runtime-client 与 App Server Host 配合仍偏概念 | done |
|
||||
| Workbench 与 Lime 主仓现有包的迁移关系需要更明确 | partial |
|
||||
| Content Studio UI 主路径已统一到标准 AgentUI surface | partial |
|
||||
|
||||
## 写集边界
|
||||
|
||||
本轮推荐写集:
|
||||
|
||||
```text
|
||||
/Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench/docs/**
|
||||
/Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench/docs/.vitepress/config.mts
|
||||
/Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench/README.md
|
||||
/Users/coso/Documents/dev/ai/aiclientproxy/lime/internal/roadmap/agentworkbench/**
|
||||
```
|
||||
|
||||
谨慎写集:
|
||||
|
||||
```text
|
||||
/Users/coso/Documents/dev/ai/aiclientproxy/lime/packages/agent-runtime-client/**
|
||||
/Users/coso/Documents/dev/ai/aiclientproxy/lime/packages/agent-ui-contracts/**
|
||||
/Users/coso/Documents/dev/ai/aiclientproxy/lime/packages/agent-runtime-projection/**
|
||||
/Users/coso/Documents/dev/ai/aiclientproxy/lime/packages/agent-runtime-ui/**
|
||||
```
|
||||
|
||||
谨慎写集需要先盘点当前 dirty diff,避免覆盖其他进程工作。
|
||||
|
||||
本轮禁止顺手写集:
|
||||
|
||||
```text
|
||||
/Users/coso/Documents/dev/ai/aiclientproxy/lime/src/features/agent-app/**
|
||||
/Users/coso/Documents/dev/ai/aiclientproxy/lime/src/lib/dev-bridge/**
|
||||
/Users/coso/Documents/dev/ai/aiclientproxy/lime/lime-rs/**
|
||||
```
|
||||
|
||||
除非另起实现分支并明确认领,否则不要在本次文档标准化任务里夹写这些生产代码。
|
||||
|
||||
## P0 任务
|
||||
|
||||
### P0.1 Subagents 标准页
|
||||
|
||||
目标:新增 `/subagents` 顶层页,固定 Lime 对 subagent、worker、task、job、channel、handoff、team review 的标准表达。
|
||||
|
||||
必须覆盖:
|
||||
|
||||
- Runtime owner:`task.created / subagent.started / channel.message / handoff.requested / review.verdict` 等事件家族。
|
||||
- UI projection:ExecutionGraph、Subagents threads、delegation calls、activity summary、Artifact / Evidence refs。
|
||||
- 控制面:spawn、cancel、pause、resume、handoff、respond_action。
|
||||
- 降级:solo run、missing lineage、unavailable remote channel。
|
||||
- 禁止:assistant prose 代表子代理完成、本地 UI state 自建 worker truth。
|
||||
|
||||
验收:
|
||||
|
||||
- `docs/subagents.md` 存在,并在 nav/sidebar 中可访问。
|
||||
- 页面明确 `current / compat / deprecated / dead`。
|
||||
- 页面链接到 Runtime 事件、读模型、UI 投影、Agents 概念页。
|
||||
|
||||
### P0.2 SDK 四包标准页补强
|
||||
|
||||
目标:让 TypeScript SDK 文档能直接指导 Lime 主仓包实现。
|
||||
|
||||
必须覆盖:
|
||||
|
||||
- `@limecloud/agent-ui-contracts`:types、schemas、fixtures、versioning。
|
||||
- `@limecloud/agent-runtime-projection`:projector、selectors、hydration、reconciliation、replay、Subagents model。
|
||||
- `@limecloud/agent-runtime-ui`:React primitives、surfaces、callbacks、i18n label contract、CSS / DOM contract。
|
||||
- `@limecloud/agent-runtime-client`:JSON-RPC、Host bridge、event subscription、read APIs、errors。
|
||||
- `@limecloud/agent-ui`:future optional facade,只 re-export UI 侧,不带 transport。
|
||||
|
||||
验收:
|
||||
|
||||
- SDK 章节至少新增 React surfaces / conformance / package boundaries 页面,或等价补全现有页面。
|
||||
- 文档明确禁止依赖方向。
|
||||
- 文档给出最小 API 草案和 fixture replay 示例。
|
||||
|
||||
### P0.3 Runtime 配合页补强
|
||||
|
||||
目标:把 AgentRuntime 与 AgentUI 的配合写成实现合同,而不是概念图。
|
||||
|
||||
必须覆盖:
|
||||
|
||||
- RuntimeEvent envelope 与 scope id。
|
||||
- ThreadReadModel / TaskSnapshot 的 hydration 责任。
|
||||
- App Server Host JSON-RPC 方法分组。
|
||||
- Event stream repair、snapshot reconciliation、large output refs。
|
||||
- Provider Store、Tool inventory、Policy / Permission / Sandbox owner。
|
||||
|
||||
验收:
|
||||
|
||||
- `contracts/app-server-host.md`、`contracts/runtime-read-model.md`、`quickstart/runtime-provider.md` 至少有一处明确端到端实现路径。
|
||||
- Runtime provider 文档强调不依赖 React、不生成 UI props。
|
||||
|
||||
### P0.4 构建和发布前验证
|
||||
|
||||
目标:保证 GitHub Pages 可发布。
|
||||
|
||||
必须运行:
|
||||
|
||||
```bash
|
||||
npm run docs:build
|
||||
```
|
||||
|
||||
可选运行:
|
||||
|
||||
```bash
|
||||
npm run docs:dev
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
- VitePress build 通过。
|
||||
- 新增页面无断链或导航缺页。
|
||||
- 若版本号变化,必须同步 `package.json`、`package-lock.json` 和 release 文档。
|
||||
|
||||
## P1 任务
|
||||
|
||||
| 任务 | 目标 |
|
||||
| --- | --- |
|
||||
| Workbench vs Lime 主仓迁移表 | 把现有 packages 与目标四包逐项映射,给出退出条件。 |
|
||||
| Conformance fixture 设计 | done:定义纯文本、工具成功、工具失败、HITL、artifact/evidence、断流恢复、subagent handoff fixture,并落到 `@limecloud/agent-ui-contracts`。 |
|
||||
| Content Studio 接入蓝图 | 把 Content Studio 从产品页面角度接入 runtime-client + projection + react surfaces。 |
|
||||
| Governance guard 设计 | 后续在 Lime 主仓加扫描,防止非标准树形过程术语 / 本地 process component / provider direct API 回流。 |
|
||||
| 多进程任务板 | done:把 runtime-client、App Server facts runner、governance guard、product adoption、React surfaces、release integration 拆成可认领 workstream。 |
|
||||
|
||||
## P2 任务
|
||||
|
||||
| 任务 | 目标 |
|
||||
| --- | --- |
|
||||
| SDK seed 实现 | 在 Lime 主仓 packages 中把 contracts/projection/react/runtime-client seed 变成可消费包。 |
|
||||
| Runtime provider fixture runner | done:`@limecloud/agent-runtime-projection` 已能 replay 标准 contracts fixtures,并能消费 App Server current read / event / evidence export facts。 |
|
||||
| AgentUI React smoke | done:`@limecloud/agent-runtime-ui` 已用标准 fixture replay state 渲染 MessageParts、ProcessTimeline、ExecutionGraph、ActionRequired、Subagents。 |
|
||||
| GitHub Pages 版本发布 | 按版本标签发布 `v0.3.0` 或后续版本。 |
|
||||
|
||||
## 进度日志
|
||||
|
||||
| 时间 | 进度 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| 2026-06-10 | 建立 `internal/roadmap/agentworkbench` 迭代入口,固定主目标、写集、P0/P1/P2。 | 本文件、`README.md`、`parallel-workstreams.md`、`acceptance.md`。 |
|
||||
| 2026-06-10 | 完成 Workbench P0 文档标准化:新增 Subagents、TypeScript package boundaries、React surfaces、Conformance,补强 App Server Host、Runtime read model、Runtime provider,并接入导航。 | `docs/subagents.md`、`docs/sdk/typescript/package-boundaries.md`、`docs/sdk/typescript/react-surfaces.md`、`docs/sdk/typescript/conformance.md`、`docs/.vitepress/config.mts`。 |
|
||||
| 2026-06-10 | 完成发布前构建验证。 | `npm run docs:build` 通过;仅有 VitePress chunk size warning,非阻塞。 |
|
||||
| 2026-06-10 | 完成 Lime 主仓 SDK contracts seed:`@limecloud/agent-ui-contracts` 新增标准 conformance fixtures、runtime/read model/projection/fixture validation helpers,并扩展 subagent/handoff/review scope ids。 | `packages/agent-ui-contracts/src/fixtures.ts`、`packages/agent-ui-contracts/src/validation.ts`、`npm --prefix packages/agent-ui-contracts run test` 通过。 |
|
||||
| 2026-06-10 | 完成 projection fixture replay seed:`@limecloud/agent-runtime-projection` 新增 `replayAgentUiFixture`,标准 fixtures 可投影到 ProjectionState,并补 subagent graph node 识别。 | `packages/agent-runtime-projection/src/fixtureReplay.ts`、`packages/agent-runtime-projection/tests/fixtureReplay.test.mjs`、`npm --prefix packages/agent-runtime-projection run test` 通过。 |
|
||||
| 2026-06-10 | 完成 React fixture smoke:`@limecloud/agent-runtime-ui` 使用标准 fixture replay state 做 server render,覆盖 MessageParts、ProcessTimeline、ExecutionGraph、ActionRequired 与 subagent handoff graph。 | `packages/agent-runtime-ui/tests/ui.test.mjs`、`npm --prefix packages/agent-runtime-ui run test` 通过。 |
|
||||
| 2026-06-10 | 建立多进程任务板,明确剩余 workstream、窄写集、current owner、禁止方向、验证命令和推荐下一刀。 | `internal/roadmap/agentworkbench/task-board.md`。 |
|
||||
| 2026-06-10 | 完成 Runtime Client Conformance:`@limecloud/agent-runtime-client/sessionGateway` 适配已有 App Server session gateway 为完整 `AgentRuntimeClient`,覆盖 lifecycle、readThread、exportEvidence、runtime event dispatch / nextEvent、transport error propagation 与 browser-safe 子路径。 | `packages/agent-runtime-client/src/sessionGateway.ts`、`packages/agent-runtime-client/tests/client.test.mjs`、`npm --prefix packages/agent-runtime-client run test` 通过。 |
|
||||
| 2026-06-10 | 完成 Workstream 2:App Server Facts Runner。`@limecloud/agent-runtime-projection` 新增纯 App Server facts adapter,把 `agentSession/event`、`agentSession/read`、`evidence/export` 投影为标准 execution events 和 `AgentUiProjectionState`。 | `packages/agent-runtime-projection/src/appServerFacts.ts`、`packages/agent-runtime-projection/tests/appServerFacts.test.mjs`、`npm --prefix packages/agent-runtime-projection run test` 通过。 |
|
||||
| 2026-06-10 | 完成 Subagents 标准模型 seed:contracts 增加并校验完整 `AgentUiSubagentsModel`,projection 输出 `state.subagents`,React `SubagentsView` 只消费该模型。 | `packages/agent-ui-contracts/src/projection.ts`、`packages/agent-ui-contracts/src/validation.ts`、`packages/agent-runtime-projection/src/subagents.ts`、`packages/agent-runtime-ui/src/subagents.tsx`。 |
|
||||
| 2026-06-10 | 补齐四包 README SDK 使用说明:current 包名、事件分类、lifecycle、transport、state/hydration、React surfaces、conformance、package metadata 与禁止方向。 | `packages/agent-ui-contracts/README.md`、`packages/agent-runtime-client/README.md`、`packages/agent-runtime-projection/README.md`、`packages/agent-runtime-ui/README.md`。 |
|
||||
| 2026-06-10 | 完成 AgentUI 防回流治理 guard 验证:非标准树术语、本地 process owner、直连 Provider runtime 已进入 legacy surface catalog 机械守卫。 | `src/lib/governance/legacySurfaceCatalog.json`、`src/lib/governance/legacySurfaceCatalog.test.ts`;`npm test -- src/lib/governance/legacySurfaceCatalog.test.ts`、`npm run governance:legacy-report`、`npm run test:contracts` 通过。 |
|
||||
| 2026-06-10 | 完成 Agent App seed adoption 验证:运行页默认宿主使用标准 `AgentRuntimeClient`,Host Agent Run 面板使用共享 `AgentUiProjectionView`,action callback 回到宿主。 | `src/features/agent-app/runtime/agentRuntimeClientApi.ts`、`src/features/agent-app/runtime/agentRuntimeAppServerClient.ts`、`src/features/agent-app/runtime/agentRunProjectionState.ts`、`src/features/agent-app/ui/AgentRunProjectionPanel.tsx`;相关定向测试通过。 |
|
||||
| 2026-06-10 | 补强 Workbench SDK 文档站:contracts validation、types/events/fixtures、projection projector/selectors/replay、runtime-client events、runtime-ui Subagents 与 conformance 均对齐四包 current owner。 | `/Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench/docs/sdk/typescript/**`;`npm run docs:build` 通过。 |
|
||||
| 2026-06-10 | 补齐 Runtime UI Artifact / Evidence refs 标准 surface:React 包新增 `AgentUiRefList`、`ArtifactRefList`、`EvidenceRefList`,组合视图接入 artifact/evidence callbacks 和 labels。 | `packages/agent-runtime-ui/src/refs.tsx`、`packages/agent-runtime-ui/src/projectionView.tsx`、`packages/agent-runtime-ui/tests/ui.test.mjs`;`npm --prefix packages/agent-runtime-ui run test` 通过。 |
|
||||
| 2026-06-10 | 按 AG-UI / AI SDK 的页面颗粒度补强 Lime 自有 SDK reference,但事实源仍是 Lime 四包:新增 event families、projection state、transport contract、provider boundary、callbacks、refs、conformance runner、AgentUI 最小面板页面。 | `/Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench/docs/sdk/typescript/**`、`docs/quickstart/agentui-minimal-panel.md`、`docs/.vitepress/config.mts`。 |
|
||||
| 2026-06-10 | 完成 Content Studio adoption blueprint:把 Host Provider Runtime PRD 转成 Workbench 接入指南,明确产品 owns / does not own、标准 surface 映射、compat 迁移表和 blocked 行为。 | `/Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench/docs/tutorials/content-studio.md`。 |
|
||||
| 2026-06-10 | 完成本轮验证:四包定向测试、Workbench 文档构建、Agent Runtime current fixture smoke、术语扫描和 diff whitespace 检查均通过。 | `npm --prefix packages/agent-ui-contracts run test`、`npm --prefix packages/agent-runtime-client run test`、`npm --prefix packages/agent-runtime-projection run test`、`npm --prefix packages/agent-runtime-ui run test`、`npm run smoke:agent-runtime-current-fixture`、`npm run docs:build`、非标准树形过程术语扫描无 current docs 命中。 |
|
||||
| 2026-06-10 | 完成 Agent App 真实主路径 smoke:正式 `Agent Apps` 聚合入口启动 runtime iframe,SDK 经 Host Bridge 调用 `startTask/getTask/submitHostResponse/cancelTask`,App Server current `agentSession/*` 与 external backend 贯通,Host Agent Run 使用共享 `AgentUiProjectionView` 渲染标准 projection DOM。 | `scripts/agent-app/runtime-electron-sdk-fixture-smoke.mjs` 连续通过;summary 证据位于 `.lime/qc/gui-evidence/agent-app-runtime-electron-sdk-fixture/agent-app-runtime-electron-sdk-fixture-summary.json`,包含 `agentSession/start`、`agentSession/turn/start`、`agentSession/action/respond`、`agentSession/read`、`agentSession/turn/cancel`、`agent-app-sdk-artifact-1`、`evidence:.lime/artifacts/agent-app-sdk-artifact-1.json`。 |
|
||||
| 2026-06-10 | 修正 App Server artifact evidence replay 进入标准 AgentUI projection 的映射:`evidence:recorded` 现在可从 runtime replay `refs` / `payload.artifactRef` 得到 `EvidenceRefList` DOM;Host projection smoke 不再把已处理 action 误判为必须存在 pending `ActionRequiredList`。 | `src/features/agent-app/runtime/agentUiProjectionBridge.ts`、`src/features/agent-app/runtime/agentUiProjectionBridge.test.ts`、`src/features/agent-app/ui/AgentRunProjectionPanel.test.tsx`、`scripts/agent-app/runtime-electron-sdk-fixture-smoke.test.mjs`;`node --check`、`npx eslint --max-warnings 0 ...`、`npm test -- scripts/agent-app/runtime-electron-sdk-fixture-smoke.test.mjs src/features/agent-app/runtime/agentUiProjectionBridge.test.ts src/features/agent-app/ui/AgentRunProjectionPanel.test.tsx` 通过。 |
|
||||
| 2026-06-11 | 完成 Content Studio 标准 UI surface 接入:`AI agents` 工作台和通用 `AgentSessionPanel` 统一通过 `AgentUiProjectionSurface` 消费共享 projection read model,产品页面不再直接拼共享 timeline / runtime facts primitives,并补边界审计阻止 primitives 回流。 | `/Users/coso/Documents/dev/ai/limecloud/content-studio/src/renderer/src/components/agent/AgentUiProjectionSurface.tsx`、`src/renderer/src/components/agent/AgentSessionPanel.tsx`、`src/renderer/src/components/agents/AgentsWorkbench.tsx`、`scripts/lime-agent-boundary-audit.mjs`;`npm run verify:lime-agent`、`npm run test:functional -- --test-name-pattern "Lime Agent 边界审计会阻断 runtime/key/UI 协议回流"`、相关 `git diff --check` 通过。 |
|
||||
| 2026-06-11 | 完成 Content Studio session gateway 标准包接入:standalone/dev App Server turn 主链已通过 `@limecloud/agent-runtime-client/sessionGateway` 的 `createAgentRuntimeClientFromSessionGateway(...)` 包装为标准 `AgentRuntimeClient`,`nextEvent()` 保持标准 `agentSession/event` notification 形状,内部轮询通过标准 client 消费 notification 后再提取 runtime event。 | `/Users/coso/Documents/dev/ai/limecloud/content-studio/package.json`、`package-lock.json`、`src/main/services/appServerAgentRuntimeGateway.ts`、`scripts/lime-agent-boundary-audit.mjs`、`tests/functional/content-flow.test.mjs`、Content Studio LimeAgent 路线图工件;`npm run verify:lime-agent`、定向 functional、`npx tsc --noEmit --pretty false` 通过。 |
|
||||
| 2026-06-11 | 补齐标准包发布前门槛:修复 `app-server-client` method catalog / schema fixture 漂移,补齐 `WorkspaceEnsureProject*` checked-in schema,两个标准包包级测试和 package dry-run 均通过。 | `packages/app-server-client/tests/client.test.mjs`、`lime-rs/crates/app-server-protocol/schema/json/**`、`lime-rs/crates/app-server-protocol/src/protocol/v0/tests/catalog.rs`;`npm --prefix packages/app-server-client run test`、`npm --prefix packages/agent-runtime-client run test`、`cargo test --manifest-path "lime-rs/Cargo.toml" -p app-server-protocol schema_fixtures_match_generated_output`、`npm pack --dry-run --json --ignore-scripts` 通过;该行是发布前 checkpoint,当前 registry 状态以后续 scoped 发布记录为准。 |
|
||||
| 2026-06-11 | 固化标准包发布与 Content Studio 产品接入 runbook:明确 scoped 发布顺序、失败处理、禁止 `file:` / 绝对路径依赖、Content Studio runtime-client 接入步骤和 GUI smoke 完成判定。 | runbook 已更新为当前状态:`@limecloud/app-server-client@1.66.0` 与 `@limecloud/agent-runtime-client@0.1.1` 已发布并接入;早期 npm 认证失败和误发只作为历史记录。 |
|
||||
| 2026-06-11 | 收掉 Agent App runtime bridge 巨型文件红线:把字段读取 / payload helper 抽到 `agentUiProjectionFieldReaders.ts`,把 `AgentUiProjectionEvent -> AgentRuntimeExecutionEvent` 适配抽到 `agentUiRuntimeEventAdapter.ts`,`agentUiProjectionBridge.ts` 从 `1253` 行降到 `895` 行。 | `src/features/agent-app/runtime/agentUiProjectionBridge.ts`、`src/features/agent-app/runtime/agentUiProjectionFieldReaders.ts`、`src/features/agent-app/runtime/agentUiRuntimeEventAdapter.ts`;`npm test -- src/features/agent-app/runtime/agentUiProjectionBridge.test.ts src/features/agent-app/runtime/agentUiProjectionViewModel.test.ts src/features/agent-app/ui/AgentRunProjectionPanel.test.tsx`、`npx eslint ... --max-warnings 0`、`git diff --check` 通过。 |
|
||||
| 2026-06-11 | 收缩 Agent App Host run 本地过程面:Host action 测试迁到 current `respondAction -> agentSession/action/respond`,`AgentRunHostDrawer` 拆成宿主壳、projection input enrichment 和本地 fallback,主抽屉降到 `382` 行;标准 projection 有内容时不再混入本地 process view。 | `src/features/agent-app/ui/AgentAppRuntimePage.agentRun.test.tsx`、`src/features/agent-app/ui/AgentRunHostDrawer.tsx`、`src/features/agent-app/ui/AgentRunHostDrawerFallback.tsx`、`src/features/agent-app/ui/AgentRunHostDrawerProjectionInput.ts`;`npx eslint ... --max-warnings 0`、`npm test -- src/features/agent-app/ui/AgentAppRuntimePage.agentRun.test.tsx src/features/agent-app/ui/AgentRunProjectionPanel.test.tsx src/features/agent-app/runtime/agentUiProjectionViewModel.test.ts src/features/agent-app/runtime/agentUiProjectionBridge.test.ts src/features/agent-app/runtime/agentRunProjectionState.test.ts`、`git diff --check` 通过。 |
|
||||
| 2026-06-11 | 完成 Agent App projection builders 二次拆分:`agentUiProjectionBridge.ts` 只保留 task event normalization、sequence 和 runtime event facade,projection builders 进入 `agentUiProjectionBuilders.ts`,owner / scope / status / control mapping 进入 `agentUiProjectionMapping.ts`,所有拆分文件均低于 `800` 行预警线。 | `src/features/agent-app/runtime/agentUiProjectionBridge.ts`、`src/features/agent-app/runtime/agentUiProjectionBuilders.ts`、`src/features/agent-app/runtime/agentUiProjectionMapping.ts`;`npx eslint ... --max-warnings 0`、`npm test -- src/features/agent-app/ui/AgentAppRuntimePage.agentRun.test.tsx src/features/agent-app/ui/AgentRunProjectionPanel.test.tsx src/features/agent-app/runtime/agentUiProjectionViewModel.test.ts src/features/agent-app/runtime/agentUiProjectionBridge.test.ts src/features/agent-app/runtime/agentRunProjectionState.test.ts`、`git diff --check` 通过。 |
|
||||
| 2026-06-11 | 补 Agent App projection 边界结构守卫:新增测试防止 `agentUiProjectionBridge.ts` 重新承接 builders / mapping 职责,并机械检查 bridge、builders、mapping、field readers、runtime adapter 均低于 `800` 行预警线。 | `src/features/agent-app/runtime/agentUiProjectionBoundary.test.ts`;`npx eslint src/features/agent-app/runtime/agentUiProjectionBoundary.test.ts ... --max-warnings 0`、`npm test -- src/features/agent-app/runtime/agentUiProjectionBoundary.test.ts src/features/agent-app/runtime/agentUiProjectionBridge.test.ts`、`git diff --check` 通过。 |
|
||||
| 2026-06-11 | 补 Agent App HostDrawer 边界结构守卫:新增测试防止主抽屉直接 import `InlineToolProcessStep` / `MarkdownRenderer` / `ThinkingBlock` 等本地 process 组件,并要求本地 fallback 只能作为退场 residual 存在。 | `src/features/agent-app/ui/AgentRunHostDrawerBoundary.test.ts`;`npx eslint src/features/agent-app/ui/AgentRunHostDrawerBoundary.test.ts ... --max-warnings 0`、`npm test -- src/features/agent-app/ui/AgentRunHostDrawerBoundary.test.ts src/features/agent-app/ui/AgentAppRuntimePage.agentRun.test.tsx src/features/agent-app/ui/AgentRunProjectionPanel.test.tsx`、`git diff --check` 通过。 |
|
||||
| 2026-06-11 | 完成 Agent App HostDrawer 主路径标准化收口:`AgentRunHostDrawer` 始终渲染标准 `AgentRunProjectionPanel`,不再 import 或调用本地 process fallback,空态也交给标准 projection;`AgentRunHostDrawerFallback.tsx` 仅作为待删除的 `deprecated` residual 保留。 | `src/features/agent-app/ui/AgentRunHostDrawer.tsx`、`src/features/agent-app/ui/AgentRunHostDrawerBoundary.test.ts`;`npx eslint src/features/agent-app/ui/AgentRunHostDrawer.tsx src/features/agent-app/ui/AgentRunHostDrawerBoundary.test.ts src/features/agent-app/ui/AgentRunHostDrawerFallback.tsx src/features/agent-app/ui/AgentRunHostDrawerProjectionInput.ts --max-warnings 0`、`npm test -- src/features/agent-app/ui/AgentRunHostDrawerBoundary.test.ts src/features/agent-app/ui/AgentAppRuntimePage.agentRun.test.tsx src/features/agent-app/ui/AgentRunProjectionPanel.test.tsx`、`git diff --check` 通过。 |
|
||||
| 2026-06-11 | 完成 Agent App HostDrawer fallback 防回流治理升级:`legacySurfaceCatalog` 新增 `agent-app-host-drawer-local-process-fallback`,将主抽屉重新调用本地 fallback 归类为 `dead`;contract guard 更新为 current `sharedProjectionInput -> buildAgentRunStandardProjectionStateFromState(sharedProjectionInput)` 形状。 | `src/lib/governance/legacySurfaceCatalog.json`、`src/lib/governance/legacySurfaceCatalog.test.ts`、`scripts/check-app-server-client-contract.mjs`;`npm test -- src/lib/governance/legacySurfaceCatalog.test.ts`、`npm run governance:legacy-report`、`npm run test:contracts`、`git diff --check` 通过。 |
|
||||
| 2026-06-11 | 历史 checkpoint:首次复核标准包发布门槛时 npm 认证未就绪,因此停在未发布状态。 | 该行只解释早期阻塞来源;当前 scoped registry 发布状态以后续 `@limecloud/app-server-client@1.66.0`、`@limecloud/agent-runtime-client@0.1.1` 发布记录为准。 |
|
||||
| 2026-06-11 | 修正标准包发布口径:无 scope `app-server-client@1.66.0` 已误发到个人包列表,`@limecloud/agent-runtime-client@0.1.0` 已误依赖旧名;最终标准链路改为 `@limecloud/app-server-client@1.66.0` 与 `@limecloud/agent-runtime-client@0.1.1`,开发态通过 scoped alias 指向本地源码。 | `packages/app-server-client/package.json`、`packages/agent-runtime-client/package.json`、`packages/agent-runtime-client/src/**`、`electron/**`、`vite.config.ts`、`scripts/electron/build-host.mjs`、`packages/agent-app-runtime/build.mjs`、`package-lock.json`、`internal/roadmap/agentworkbench/standard-package-release-runbook.md`。 |
|
||||
| 2026-06-11 | 完成 scoped 标准包 registry 发布与 Content Studio 接入复核:`@limecloud/app-server-client@1.66.0`、`@limecloud/agent-runtime-client@0.1.1` 均可从 npm registry 查询,Content Studio 固定依赖 `@limecloud/agent-runtime-client: 0.1.1`,lockfile 解析到 `@limecloud` organization tarball,并禁止无 scope `app-server-client` 回流。 | `npm view "@limecloud/app-server-client@1.66.0"`、`npm view "@limecloud/agent-runtime-client@0.1.1"`、`/Users/coso/Documents/dev/ai/limecloud/content-studio/package.json`、`package-lock.json`、`scripts/lime-agent-boundary-audit.mjs`。 |
|
||||
|
||||
## 完成判定
|
||||
|
||||
本次迭代完成需要同时满足:
|
||||
|
||||
1. `lime-agent-workbench` 文档站覆盖 P0.1-P0.3。
|
||||
2. 导航可访问新增页面。
|
||||
3. `npm run docs:build` 通过。
|
||||
4. 本路线图进度日志记录已完成项和剩余缺口。
|
||||
5. 未覆盖其他进程的 Lime 主仓 dirty diff。
|
||||
|
||||
## 当前完成度
|
||||
|
||||
路线图工件完成度:`100%`。
|
||||
|
||||
Workbench / Lime AgentUI 标准整体完成度:`98.5%`。当前已完成 P0 文档标准页、导航接入、构建验证、contracts seed、projection fixture replay、App Server facts runner、Subagents 标准模型、Artifact/Evidence refs React surface、React fixture smoke、runtime-client conformance、四包 README SDK 化、治理 guard 验证、Agent App 真实主路径 smoke、Content Studio adoption blueprint、Content Studio 标准 UI surface 接入、Content Studio session gateway 形状收敛、scoped 标准包 registry 发布、Content Studio 固定安装并接入 `@limecloud/agent-runtime-client/sessionGateway`、标准包发布前测试 / pack dry-run 门槛、标准包发布 / Content Studio 接入 runbook、Agent App runtime bridge 巨型文件拆分、Agent App Host run 本地过程面收缩、Agent App projection builders / mapping 二次拆分、Agent App projection 边界结构守卫、Agent App HostDrawer 边界结构守卫、Agent App HostDrawer 始终渲染标准 projection 的主路径收口,以及 HostDrawer fallback 防回流治理 catalog / contract guard;剩余缺口是 Content Studio GUI smoke / 产品回归、Agent App 本地 process fallback 文件物理删除、正式版本发布,以及需要真实 Provider 授权时的 live evidence。完成度暂不升到 `100%`,因为真实页面 smoke 仍未闭环。
|
||||
|
||||
后续拆分风险:`src/features/agent-app/runtime/agentUiProjectionBridge.ts` 已降到 `82` 行,`agentUiProjectionBuilders.ts` 为 `482` 行,`agentUiProjectionMapping.ts` 为 `373` 行,`src/features/agent-app/ui/AgentRunHostDrawer.tsx` 已降到 `382` 行,当前不再触碰 `800` 行预警和 `1000` 行硬红线;新增 Agent App 协议映射必须进入 builders / mapping / field readers 对应边界,不得塞回 bridge。`AgentRunHostDrawerFallback.tsx` 已退出主路径,仅作为待删除的 `deprecated` residual,不能继续承接标准过程展示或 projection 空态。
|
||||
@@ -0,0 +1,120 @@
|
||||
# Agent Workbench 并行工作流
|
||||
|
||||
> 状态:active
|
||||
> 目的:允许多个进程同时推进 `lime-agent-workbench`,减少互相覆盖。
|
||||
|
||||
## 并行前置规则
|
||||
|
||||
每个进程开始前必须先执行:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
然后声明本轮只认领一个窄写集。不要多个进程同时编辑同一文件;导航文件和版本文件由最后一个集成进程统一修改。
|
||||
|
||||
当前实现阶段的任务认领以 [task-board.md](task-board.md) 为准;本文件保留文档站阶段的并行拆分和通用协作规则。
|
||||
|
||||
## Workstream A:Subagents 标准
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 主目标 | 补齐 `/subagents` 顶层标准页。 |
|
||||
| 推荐写集 | `docs/subagents.md`、必要时 `docs/concepts/agents.md`。 |
|
||||
| 暂不触碰 | `docs/.vitepress/config.mts`,除非当前进程负责集成导航。 |
|
||||
| 依赖 | Runtime 事件、TaskSnapshot、ExecutionGraph、Subagents 概念。 |
|
||||
| 验收 | 页面明确 runtime facts、UI surfaces、control plane、降级状态和禁止方向。 |
|
||||
|
||||
建议章节:
|
||||
|
||||
1. Subagents 在 Lime 中是什么。
|
||||
2. Runtime 事实与 scope ids。
|
||||
3. ExecutionGraph / Subagents 投影。
|
||||
4. Handoff / review / worker notification。
|
||||
5. 与 Agent App / Content Studio 的采用边界。
|
||||
6. Conformance fixture。
|
||||
7. current / compat / deprecated / dead。
|
||||
|
||||
## Workstream B:SDK 包边界
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 主目标 | 把 TypeScript SDK 文档从包名规划补成实现边界。 |
|
||||
| 推荐写集 | `docs/sdk/typescript/**`。 |
|
||||
| 暂不触碰 | `packages/**` 生产代码,除非另行认领实现任务。 |
|
||||
| 依赖 | AI SDK `UIMessage.parts` 参考、AG-UI event stream 参考、Lime current packages。 |
|
||||
| 验收 | 四包职责、依赖方向、API 草案、fixture replay、版本策略完整。 |
|
||||
|
||||
建议新增或补强页面:
|
||||
|
||||
- `docs/sdk/typescript/react-surfaces.md`
|
||||
- `docs/sdk/typescript/conformance.md`
|
||||
- `docs/sdk/typescript/package-boundaries.md`
|
||||
|
||||
## Workstream C:Runtime 配合
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 主目标 | 把 App Server / RuntimeCore / ExecutionBackend 与 AgentUI 的配合写成实现合同。 |
|
||||
| 推荐写集 | `docs/contracts/app-server-host.md`、`docs/contracts/runtime-read-model.md`、`docs/quickstart/runtime-provider.md`。 |
|
||||
| 暂不触碰 | Lime 主仓 `src/lib/dev-bridge/**`、`lime-rs/**`。 |
|
||||
| 依赖 | `internal/roadmap/agentruntime/README.md`、`internal/roadmap/agentruntime/agentruntime-standard-adoption-gap.md`。 |
|
||||
| 验收 | Runtime provider 不依赖 React;UI 不拥有 facts;read model / event stream repair / action response 有明确 owner。 |
|
||||
|
||||
## Workstream D:Content Studio / Product App 接入
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 主目标 | 把 Workbench 标准落到 Content Studio 和未来 Agent Apps 的接入路径。 |
|
||||
| 推荐写集 | `docs/tutorials/content-studio.md`、`docs/profiles/content-studio.md`、`docs/quickstart/product-app.md`。 |
|
||||
| 暂不触碰 | `content-studio` 生产代码。 |
|
||||
| 依赖 | Host Provider Runtime PRD、Agent App runtime roadmap。 |
|
||||
| 验收 | 产品应用只提交业务 context 和 callbacks,不传 Provider key、不读 App Server DB、不自建 process component。 |
|
||||
|
||||
## Workstream E:导航 / 构建 / 发布集成
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 主目标 | 统一接入 nav/sidebar、构建验证和发布准备。 |
|
||||
| 推荐写集 | `docs/.vitepress/config.mts`、`README.md`、`docs/development/updates.md`、`package.json`、`package-lock.json`。 |
|
||||
| 前置 | A-D 至少一个内容页已完成。 |
|
||||
| 验收 | `npm run docs:build` 通过;版本和更新记录一致。 |
|
||||
|
||||
注意:修改版本号、commit、tag、push 属于高风险操作,必须由用户明确要求后执行。
|
||||
|
||||
## 推荐并行顺序
|
||||
|
||||
```text
|
||||
A Subagents 标准
|
||||
B SDK 包边界
|
||||
C Runtime 配合
|
||||
D Product App 接入
|
||||
-> E 导航 / 构建 / 发布集成
|
||||
```
|
||||
|
||||
如果只开两个进程:
|
||||
|
||||
| 进程 | 认领 |
|
||||
| --- | --- |
|
||||
| 进程 1 | A + C |
|
||||
| 进程 2 | B + D |
|
||||
| 集成进程 | E |
|
||||
|
||||
## 冲突处理
|
||||
|
||||
| 情况 | 处理 |
|
||||
| --- | --- |
|
||||
| 发现目标文件已有未理解改动 | 停止写入该文件,切只读审阅。 |
|
||||
| 两个进程都需要改导航 | 先不接导航,完成内容页后交给 E。 |
|
||||
| 发现文档标准和 Lime current 实现冲突 | 先写 conflict note 到对应页面,不直接改生产代码。 |
|
||||
| 发现需要生产实现才能闭环 | 回写 `iteration-plan.md` 的 P1/P2,不在文档进程里夹写代码。 |
|
||||
|
||||
## 每个进程收尾格式
|
||||
|
||||
每个进程完成后,在 `iteration-plan.md` 追加一行进度日志:
|
||||
|
||||
```markdown
|
||||
| 2026-06-10 | 完成 Workstream A:Subagents 标准页。 | `docs/subagents.md`、`npm run docs:build`。 |
|
||||
```
|
||||
|
||||
如果没有跑构建,要明确写原因和下一步。
|
||||
@@ -0,0 +1,162 @@
|
||||
# Agent Workbench 标准包发布与产品接入 Runbook
|
||||
|
||||
> 状态:published-and-adopted; gui-smoke-pending
|
||||
> 更新时间:2026-06-11
|
||||
> 适用范围:`@limecloud/app-server-client@1.66.0`、`@limecloud/agent-runtime-client@0.1.1`、Content Studio 标准 runtime-client 接入。
|
||||
|
||||
## 目标
|
||||
|
||||
把已通过本地 conformance 的 Agent Runtime 标准包发布到 npm registry,然后让 Content Studio 通过正式 npm 依赖消费 `@limecloud/agent-runtime-client/sessionGateway`,完成:
|
||||
|
||||
```text
|
||||
Content Studio App Server session gateway
|
||||
-> @limecloud/agent-runtime-client/sessionGateway
|
||||
-> @limecloud/agent-runtime-projection
|
||||
-> @limecloud/agent-runtime-ui
|
||||
-> AgentUiProjectionSurface
|
||||
```
|
||||
|
||||
scoped 发布和产品依赖接入已完成;剩余 `98.5% -> 100%` 的主线缺口是 Content Studio GUI smoke / 产品回归。不得使用 `file:`、本机绝对路径、tarball 临时路径或复制源码的方式伪装完成。
|
||||
|
||||
## 当前状态
|
||||
|
||||
| 项 | 状态 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| `@limecloud/app-server-client@1.66.0` | published | 已发布到 `@limecloud` organization;本地开发仍通过 scoped alias 指向 `packages/app-server-client/src/index.ts`。 |
|
||||
| `@limecloud/agent-runtime-client@0.1.1` | published | 已发布到 `@limecloud` organization;runtime-client 依赖 `@limecloud/app-server-client@1.66.0`。 |
|
||||
| `app-server-client@1.66.0` | compat-misrelease | 已误发布到 `wutongci` 用户包列表;不作为 Lime 标准链路完成证据,不继续扩展。 |
|
||||
| `@limecloud/agent-runtime-client@0.1.0` | compat-misrelease | 已发布但依赖无 scope `app-server-client`;不作为 Content Studio 接入版本。 |
|
||||
| npm 登录状态 | ready | `npm whoami -> wutongci`,账号对 `@limecloud` organization 包有 read-write 权限。 |
|
||||
| Content Studio runtime-client 接入 | adopted | Content Studio 固定安装 registry 版 `@limecloud/agent-runtime-client@0.1.1`,lockfile 解析到间接 `@limecloud/app-server-client@1.66.0`,主路径通过 `@limecloud/agent-runtime-client/sessionGateway` 进入标准 `AgentRuntimeClient`。 |
|
||||
|
||||
## 风险分类
|
||||
|
||||
| 分类 | 路径 / 行为 | 规则 |
|
||||
| --- | --- | --- |
|
||||
| current | `@limecloud/app-server-client` | 当前 `@limecloud/agent-runtime-client` 的 App Server JSON-RPC 依赖包;开发态通过 scoped alias 指向本地 `packages/app-server-client/src/index.ts`。 |
|
||||
| current | `@limecloud/agent-runtime-client/sessionGateway` | 标准 runtime client facade,负责 lifecycle、event、read、action response、evidence export。 |
|
||||
| current | Content Studio `ContentStudioAgentRuntimeSessionGateway` | 产品侧 session gateway 适配层,已压薄为标准包 adapter,只保留 `agentSession/start`、`artifact/read` 和 sidecar transport。 |
|
||||
| compat | Content Studio 本地 gateway 形状 | 只允许作为标准 runtime-client adapter 的产品侧补充,不允许继续长第二套 runtime client。 |
|
||||
| dead | `file:` / 绝对路径依赖 / 复制 dist | 不允许作为产品接入完成证据。 |
|
||||
|
||||
## Registry 复核
|
||||
|
||||
当前已完成发布,复核时在 Lime 主仓运行:
|
||||
|
||||
```bash
|
||||
npm view "@limecloud/app-server-client@1.66.0" version
|
||||
npm view "@limecloud/agent-runtime-client@0.1.1" version
|
||||
npm view "@limecloud/agent-runtime-client@0.1.1" dependencies
|
||||
```
|
||||
|
||||
当前通过标准:
|
||||
|
||||
- `npm view "@limecloud/app-server-client@1.66.0"` 返回 `1.66.0`。
|
||||
- `npm view "@limecloud/agent-runtime-client@0.1.1"` 返回 `0.1.1`。
|
||||
- `@limecloud/agent-runtime-client@0.1.1` 依赖 `@limecloud/app-server-client: 1.66.0`。
|
||||
- 无 scope `app-server-client@1.66.0` 和 `@limecloud/agent-runtime-client@0.1.0` 只保留为 `compat-misrelease` 记录,不作为任何产品接入证据。
|
||||
|
||||
## 已通过的发布门槛
|
||||
|
||||
发布前已在 Lime 主仓通过:
|
||||
|
||||
```bash
|
||||
npm --prefix packages/app-server-client run test
|
||||
npm --prefix packages/agent-runtime-client run test
|
||||
cargo test --manifest-path "lime-rs/Cargo.toml" -p app-server-protocol schema_fixtures_match_generated_output
|
||||
cargo test --manifest-path "lime-rs/Cargo.toml" -p app-server-protocol app_server_method_catalog_keeps_request_and_notification_methods_together
|
||||
cd packages/app-server-client && npm pack --dry-run --json --ignore-scripts
|
||||
cd ../agent-runtime-client && npm pack --dry-run --json --ignore-scripts
|
||||
```
|
||||
|
||||
通过标准:
|
||||
|
||||
- 两个 dry-run 包只包含 `README.md`、`dist/**`、`package.json`。
|
||||
- 不从 workspace root 运行目标包 pack,避免误把根包当成发布包。
|
||||
|
||||
## 发布动作记录
|
||||
|
||||
已按以下顺序发布 scoped 标准包:
|
||||
|
||||
```bash
|
||||
cd packages/app-server-client
|
||||
npm publish --access public
|
||||
|
||||
cd ../agent-runtime-client
|
||||
npm publish --access public
|
||||
```
|
||||
|
||||
发布后验证:
|
||||
|
||||
```bash
|
||||
npm view "@limecloud/app-server-client@1.66.0" version
|
||||
npm view "@limecloud/agent-runtime-client@0.1.1" version
|
||||
```
|
||||
|
||||
后续如果需要新版本发布,仍必须重新走用户明确确认;commit、tag、push 也不自动执行。
|
||||
|
||||
## Content Studio 接入状态
|
||||
|
||||
路径:`/Users/coso/Documents/dev/ai/limecloud/content-studio`
|
||||
|
||||
已固定依赖:
|
||||
|
||||
```bash
|
||||
"@limecloud/agent-runtime-client": "0.1.1"
|
||||
```
|
||||
|
||||
已确认:
|
||||
|
||||
- lockfile 解析到 `@limecloud/agent-runtime-client@0.1.1`。
|
||||
- 间接依赖解析到 `@limecloud/app-server-client@1.66.0`。
|
||||
- lockfile 不再解析到无 scope `app-server-client`。
|
||||
- `package.json` 不出现 `file:`、绝对路径或 tarball 临时路径。
|
||||
|
||||
代码接入结果:
|
||||
|
||||
1. Content Studio 主进程侧从 `@limecloud/agent-runtime-client/sessionGateway` 导入 `createAgentRuntimeClientFromSessionGateway`。
|
||||
2. 现有 `ContentStudioAgentRuntimeSessionGateway` 被包装成标准 `AgentRuntimeClient`。
|
||||
3. `startTurn/readThread/cancelTurn/respondAction/exportEvidence/nextEvent` 经过标准 client facade。
|
||||
4. `ContentStudioAgentRuntimeSessionGateway.nextEvent()` 保留 `agentSession/event` notification 形状。
|
||||
5. 边界审计已要求实际导入 `@limecloud/agent-runtime-client/sessionGateway`,并检查 scoped registry lockfile。
|
||||
|
||||
禁止:
|
||||
|
||||
- 不把 `nextEvent()` 改回裸 `RuntimeEvent`。
|
||||
- 不在 Content Studio 复制 `@limecloud/agent-runtime-client` 的实现。
|
||||
- 不让 renderer 直连 Provider key 或 App Server DB。
|
||||
- 不恢复本地过程组件作为标准 AgentUI owner。
|
||||
|
||||
## Content Studio 验证
|
||||
|
||||
已运行:
|
||||
|
||||
```bash
|
||||
npm run verify:lime-agent
|
||||
npm run test:functional -- --test-name-pattern "Lime Agent 边界审计会阻断 runtime/key/UI 协议回流"
|
||||
npx tsc --noEmit --pretty false
|
||||
```
|
||||
|
||||
仍需补 GUI smoke / 产品回归,证明真实页面存在并可运行:
|
||||
|
||||
```text
|
||||
.agent-ui-projection
|
||||
.agent-ui-main
|
||||
.agent-ui-sidecar
|
||||
```
|
||||
|
||||
GUI smoke 未完成前,不得把整体目标标为 `100%`。
|
||||
|
||||
## 完成判定
|
||||
|
||||
只有同时满足以下条件,才能把整体目标从 `98.5%` 更新为 `100%`:
|
||||
|
||||
1. `@limecloud/app-server-client@1.66.0` 和 `@limecloud/agent-runtime-client@0.1.1` 都可从 npm registry 查询。
|
||||
2. Content Studio 通过正式 npm 依赖安装 `@limecloud/agent-runtime-client@0.1.1`。
|
||||
3. Content Studio 主路径实际消费 `@limecloud/agent-runtime-client/sessionGateway`。
|
||||
4. `runtime-client -> projection -> runtime-ui -> AgentUiProjectionSurface` 链路有产品验证。
|
||||
5. Content Studio GUI smoke / 产品回归通过。
|
||||
|
||||
## 当前剩余缺口
|
||||
|
||||
截至 `2026-06-11`,scoped registry 发布和 Content Studio runtime-client 接入已完成。下一刀是 Content Studio GUI smoke / 产品回归,证明 `.agent-ui-projection` / `.agent-ui-main` / `.agent-ui-sidecar` 在真实页面可运行。已误发的无 scope `app-server-client@1.66.0` 与依赖旧名的 `@limecloud/agent-runtime-client@0.1.0` 只作为 compat-misrelease 记录,不进入 Content Studio 接入。
|
||||
@@ -0,0 +1,374 @@
|
||||
# Agent Workbench 当前迭代任务板
|
||||
|
||||
> 状态:active
|
||||
> 更新时间:2026-06-11
|
||||
> 目的:把本次 Agent Workbench 迭代拆成可并行认领的窄任务,后续多开进程时以本文件为任务入口。
|
||||
|
||||
## 主目标
|
||||
|
||||
把 Agent Workbench 从文档站推进成 Lime AgentUI / AgentRuntime 标准指引和实现种子。所有新增实现必须继续向以下 current 主链收敛:
|
||||
|
||||
```text
|
||||
App Server / RuntimeCore / ExecutionBackend
|
||||
-> RuntimeEvent / ThreadReadModel / TaskSnapshot / EvidencePack
|
||||
-> @limecloud/agent-ui-contracts
|
||||
-> @limecloud/agent-runtime-projection
|
||||
-> @limecloud/agent-runtime-ui
|
||||
-> Product Apps / Content Studio / Agent Apps
|
||||
```
|
||||
|
||||
外部 AG-UI、AI SDK、OpenAI Agents JS、LangGraph 等只作为参考;不能成为 Lime runtime owner,也不能让产品应用绕开 App Server / RuntimeCore 直接拥有 Provider key 或 runtime facts。
|
||||
|
||||
## 当前完成度
|
||||
|
||||
| 维度 | 状态 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| Workbench P0 文档站 | done | Subagents、SDK、runtime host、read model、provider 文档已补齐,`npm run docs:build` 已通过。 |
|
||||
| Contracts seed | done | `@limecloud/agent-ui-contracts` 已有 fixtures、validation、完整 `AgentUiSubagentsModel`、subagent / handoff / review scope ids。 |
|
||||
| Projection replay seed | done | `@limecloud/agent-runtime-projection` 已能 replay contracts fixtures,并输出 `state.subagents`。 |
|
||||
| React fixture smoke | done | `@limecloud/agent-runtime-ui` 已用 fixture replay state 做 server render smoke,Subagents 只消费 `state.subagents`。 |
|
||||
| Runtime client conformance | done | `@limecloud/agent-runtime-client/sessionGateway` 已补完整 runtime client conformance,覆盖 lifecycle、read、evidence export、event dispatch / nextEvent、transport error 和 browser-safe 子路径。 |
|
||||
| App Server facts runner | done | `@limecloud/agent-runtime-projection` 已有纯 App Server facts adapter,可 replay `agentSession/event`、`agentSession/read`、`evidence/export` 到标准 projection。 |
|
||||
| SDK package README | done | 四包 README 已补 current 包名、生命周期、transport、state/hydration、React surface、conformance、package metadata。 |
|
||||
| Governance guard | done | 已通过 legacy surface catalog、catalog test、legacy report 和 contract guard 防止非标准树术语、本地 process owner、HostDrawer fallback 回流、provider direct API、第二套 runtime facts 回流。 |
|
||||
| Artifact / Evidence refs React surface | done | `@limecloud/agent-runtime-ui` 已新增 `ArtifactRefList`、`EvidenceRefList`、`AgentUiRefList`,组合视图输出 refs DOM contract,包级测试通过。 |
|
||||
| SDK reference completeness | done | Workbench 新增 event families、projection state、transport、provider boundary、callbacks、refs、conformance runner、minimal panel 页面,导航已接入。 |
|
||||
| Standard package publish gate | done | `@limecloud/app-server-client@1.66.0`、`@limecloud/agent-runtime-client@0.1.1` 已发布到 `@limecloud` organization;无 scope `app-server-client@1.66.0` 和依赖旧名的 `@limecloud/agent-runtime-client@0.1.0` 只作为 compat-misrelease 记录,不作为标准完成证据。 |
|
||||
| Product app seed adoption | partial | Agent App 真实 Electron 主路径已证明复用标准 `AgentRuntimeClient`、共享 `AgentUiProjectionView` 与 Artifact/Evidence refs;Host action 决策已走 current `respondAction -> agentSession/action/respond`,Host drawer 已成为始终渲染标准 projection 的宿主壳;Content Studio UI 主路径已统一到标准 `AgentUiProjectionSurface`,并已通过 registry 版 `@limecloud/agent-runtime-client@0.1.1/sessionGateway` 包装 App Server turn 主链,GUI smoke 仍待补。 |
|
||||
|
||||
整体目标完成度:`98.5%`。口径见 [acceptance.md](acceptance.md)。
|
||||
|
||||
## 并行认领规则
|
||||
|
||||
1. 开始前先执行 `git status --short`,只读盘点目标写集。
|
||||
2. 每个进程只认领一个 workstream,不跨写集夹写。
|
||||
3. 发现目标文件已有未理解改动,停止写入该文件,切只读审阅。
|
||||
4. `iteration-plan.md` 只追加进度日志,不重写其他进程的历史记录。
|
||||
5. 修改命令边界、runtime client、App Server host、DevBridge 或 mock 时,必须同时回到 `current / compat / deprecated / dead` 分类。
|
||||
6. 不主动执行 `git commit`、`git push`、tag、版本发布;这些只在用户明确要求后做。
|
||||
|
||||
## Workstream 0:集成协调
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | active |
|
||||
| 推荐负责人 | 单进程 |
|
||||
| 写集 | `internal/roadmap/agentworkbench/**` |
|
||||
| 禁止夹写 | `packages/**`、`src/**`、`lime-rs/**` |
|
||||
| 目标 | 维护本任务板、合并各进程进度、更新完成度口径。 |
|
||||
|
||||
验收:
|
||||
|
||||
- 每个完成的 workstream 都在 `iteration-plan.md` 增加进度日志。
|
||||
- `README.md`、`parallel-workstreams.md`、`acceptance.md` 与本文件不冲突。
|
||||
- 新增任务均有写集、验证命令和 current owner。
|
||||
|
||||
## Workstream 1:Runtime Client Conformance
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | done |
|
||||
| 写集 | `packages/agent-runtime-client/**` |
|
||||
| 前置 | 先盘点该目录 dirty diff;若发现其他进程正在编辑,停止写入。 |
|
||||
| current owner | `@limecloud/agent-runtime-client` 只拥有 App Server JSON-RPC、Host bridge、events、read APIs、action response、evidence export。 |
|
||||
| 禁止方向 | 生成 projection state、包含 React、持有 Provider key、直读 App Server DB、内置 mock fallback 作为生产路径。 |
|
||||
|
||||
任务:
|
||||
|
||||
- 盘点现有 `README.md`、`package.json`、`src/index.ts`、`tests/client.test.mjs`、`src/sessionGateway.ts` dirty diff。
|
||||
- 补齐 startTurn、subscribeEvents、getThreadReadModel、respondAction、cancelTurn、exportEvidence 的最小 conformance。
|
||||
- 补 transport error、stream interruption、action response、evidence export 的错误分类测试。
|
||||
- 文档明确 runtime client 与 projection / react 的依赖边界。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
npm --prefix packages/agent-runtime-client run test
|
||||
```
|
||||
|
||||
若改动触及 App Server JSON-RPC 方法名、bridge 或 mock 边界,还要运行:
|
||||
|
||||
```bash
|
||||
npm run test:contracts
|
||||
```
|
||||
|
||||
完成证据:
|
||||
|
||||
- runtime client 不导出 UI projection state。
|
||||
- tests 覆盖正常路径和错误路径。
|
||||
- `iteration-plan.md` 追加日志。
|
||||
|
||||
当前证据:
|
||||
|
||||
- `packages/agent-runtime-client/src/sessionGateway.ts` 返回完整 `AgentRuntimeClient`,只适配 App Server session gateway,不生成 projection state。
|
||||
- `packages/agent-runtime-client/tests/client.test.mjs` 覆盖 lifecycle、`exportEvidence`、`dispatchEvent`、`nextEvent`、`drainEvents`、缺 surface fail closed、transport error propagation 和 browser-safe bundle guard。
|
||||
- `npm --prefix packages/agent-runtime-client run test` 通过。
|
||||
|
||||
## Workstream 2:App Server Facts Runner
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | done |
|
||||
| 写集 | 优先 `packages/agent-runtime-projection/**`;如必须接 App Server client,另行认领 `packages/app-server-client/**`。 |
|
||||
| current owner | App Server / RuntimeCore 输出 facts;projection 只消费 facts 并生成 UI state。 |
|
||||
| 禁止方向 | 在 projection 内发起 Provider 请求、直读数据库、根据 assistant prose 推断工具或 evidence 状态。 |
|
||||
|
||||
任务:
|
||||
|
||||
- 定义从 App Server read model / facts export 到 `RuntimeEvent[]` 或 fixture replay 输入的 adapter。
|
||||
- 支持 snapshot reconciliation、stream repair、大输出 refs、subagent scope ids。
|
||||
- 添加 replay 幂等测试,覆盖至少 text、tool、hitl、artifact/evidence、subagent handoff。
|
||||
- 若需要新增 fixture,只放在 contracts 包或 projection test fixture,不在产品应用里私有定义。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
npm --prefix packages/agent-runtime-projection run test
|
||||
```
|
||||
|
||||
必要时加:
|
||||
|
||||
```bash
|
||||
npm --prefix packages/agent-ui-contracts run test
|
||||
```
|
||||
|
||||
完成证据:
|
||||
|
||||
- App Server facts 可进入标准 projection replay。
|
||||
- projection 不拥有 runtime truth。
|
||||
- `iteration-plan.md` 追加日志。
|
||||
|
||||
当前证据:
|
||||
|
||||
- `packages/agent-runtime-projection/src/appServerFacts.ts` 新增 headless adapter,消费 `agentSession/event`、`agentSession/read`、`evidence/export` facts,输出标准 `AgentRuntimeExecutionEvent[]` 和 `AgentUiProjectionState`。
|
||||
- adapter 不创建 App Server client、不订阅 JSON-RPC、不直读数据库;`waitingAction` 映射为 `action.required` + waiting runtime state。
|
||||
- `packages/agent-runtime-projection/tests/appServerFacts.test.mjs` 覆盖 text、tool、HITL、artifact/evidence、read model hydration、evidence export 与幂等去重。
|
||||
- `npm --prefix packages/agent-runtime-projection run test` 通过。
|
||||
|
||||
## Workstream 3:Governance Guard
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | done |
|
||||
| 写集 | 优先 `src/lib/governance/**`、相关 contract / governance tests;如新增脚本,必须放 `scripts/<domain>/` 或所属 package。 |
|
||||
| current owner | 仓库治理 catalog / contract tests 是防回流事实源。 |
|
||||
| 禁止方向 | 只写文档提醒、不加机械守卫;把 mock fallback 当生产可交付路径。 |
|
||||
|
||||
任务:
|
||||
|
||||
- 增加或扩展扫描规则,防止非标准树形过程术语作为标准术语回流。
|
||||
- 防止产品应用新增本地 process component 作为 AgentUI 标准 owner。
|
||||
- 防止生产路径直连 Provider API 绕开 App Server / RuntimeCore。
|
||||
- 防止第二套 Agent App runtime facts 与标准 SDK 平级扩张。
|
||||
- 对允许存在的历史引用标注 `test-only`、`retired` 或 `historical reference`。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
npm run governance:legacy-report
|
||||
npm run test:contracts
|
||||
```
|
||||
|
||||
若新增或移动脚本:
|
||||
|
||||
```bash
|
||||
npm run governance:scripts
|
||||
```
|
||||
|
||||
完成证据:
|
||||
|
||||
- 旧路被 catalog 或 contract test 机械封住。
|
||||
- 保留的 compat / deprecated 均有退出条件。
|
||||
- `iteration-plan.md` 追加日志。
|
||||
|
||||
当前证据:
|
||||
|
||||
- `src/lib/governance/legacySurfaceCatalog.json` 已登记 `agent-ui-nonstandard-tree-terminology`、`agent-ui-local-process-owner-terminology`、`agent-app-host-drawer-local-process-fallback`、`agent-ui-direct-provider-runtime-surface`。
|
||||
- `src/lib/governance/legacySurfaceCatalog.test.ts` 断言 AgentUI seed 包不得恢复非标准树术语。
|
||||
- `scripts/check-app-server-client-contract.mjs` 已对齐当前 `sharedProjectionInput -> buildAgentRunStandardProjectionStateFromState(sharedProjectionInput)` 接线,防止 contract guard 把实现拉回旧参数形状。
|
||||
- `npm test -- src/lib/governance/legacySurfaceCatalog.test.ts`、`npm run governance:legacy-report`、`npm run test:contracts` 通过。
|
||||
|
||||
## Workstream 4:Product App Adoption Blueprint
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | partial |
|
||||
| 写集 | 优先 Workbench 文档:`/Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench/docs/tutorials/**`、`docs/profiles/**`;Content Studio 产品接入另行认领 `/Users/coso/Documents/dev/ai/limecloud/content-studio/src/renderer/src/components/agent/**`、`/Users/coso/Documents/dev/ai/limecloud/content-studio/src/renderer/src/components/agents/**`、`/Users/coso/Documents/dev/ai/limecloud/content-studio/scripts/lime-agent-boundary-audit.mjs`。 |
|
||||
| current owner | 产品应用只提供业务 context、callbacks、workspace surfaces;runtime facts 来自 runtime client + projection。 |
|
||||
| 禁止方向 | Content Studio / Agent Apps 自建第二套过程组件、直接拼 Provider params、复制 projection reducer。 |
|
||||
|
||||
任务:
|
||||
|
||||
- 用 Content Studio Host Provider Runtime PRD 作为第一条 adoption blueprint。
|
||||
- 写清 Product App 需要提供的 business context、artifact workspace、action callbacks、i18n keys。
|
||||
- 写清 Product App 不应该拥有的 runtime client、tool state machine、subagent lineage truth。
|
||||
- 给出迁移表:local process panel -> shared ProcessTimeline / ExecutionGraph / ActionRequired。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd /Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench
|
||||
npm run docs:build
|
||||
```
|
||||
|
||||
完成证据:
|
||||
|
||||
- Workbench 文档能指导 Content Studio 接入共享 AgentUI。
|
||||
- 未触碰生产代码或已另行登记生产写集。
|
||||
- `iteration-plan.md` 追加日志。
|
||||
|
||||
当前证据:
|
||||
|
||||
- `src/features/agent-app/runtime/agentRuntimeClientApi.ts` 把 Agent App `startTask / getTask / cancelTask / submitHostResponse` 适配到标准 `AgentRuntimeClient.startTurn / readThread / cancelTurn / respondAction`。
|
||||
- `src/features/agent-app/runtime/agentRuntimeAppServerClient.ts` 使用 `@limecloud/agent-runtime-client/sessionGateway` 包装 App Server `agentSession/*` current 方法。
|
||||
- `src/features/agent-app/ui/AgentRunProjectionPanel.tsx` 渲染共享 `AgentUiProjectionView`,并把标准 action callback 回传宿主。
|
||||
- `src/features/agent-app/ui/AgentAppRuntimePage.agentRun.test.tsx` 已把 Host action 断言迁到 current `appServerClientMocks.respondAction`,拒绝动作精确点击 action-required 列表内按钮,避免旧 Host response mock 被误当成 current 完成证据。
|
||||
- `scripts/agent-app/runtime-electron-sdk-fixture-smoke.mjs` 已通过真实 Electron fixture:`Agent Apps` 聚合入口 -> runtime iframe SDK -> App Server current `agentSession/*` -> Host Agent Run 标准 projection DOM。
|
||||
- `src/features/agent-app/runtime/agentUiProjectionBridge.ts` 已把 App Server artifact replay 的 `evidence:recorded` refs 投影到标准 `EvidenceRefList`。
|
||||
- `src/features/agent-app/runtime/agentUiProjectionBridge.ts` 已拆出 `agentUiProjectionFieldReaders.ts` 与 `agentUiRuntimeEventAdapter.ts`,主文件从 `1253` 行降到 `895` 行,低于 `1000` 行治理红线;相邻 Agent App projection 回归通过。
|
||||
- `src/features/agent-app/runtime/agentUiProjectionBridge.ts` 已继续压薄为 `82` 行 normalization facade,projection builders / owner-scope-status mapping 分别进入 `agentUiProjectionBuilders.ts` 与 `agentUiProjectionMapping.ts`;相关文件均低于 `800` 行预警线。
|
||||
- `src/features/agent-app/runtime/agentUiProjectionBoundary.test.ts` 已补结构守卫,防止 bridge 重新承接 builders / mapping 职责,并机械检查 projection runtime 拆分文件低于 `800` 行。
|
||||
- `src/features/agent-app/ui/AgentRunHostDrawer.tsx` 已拆出 `AgentRunHostDrawerProjectionInput.ts` 并压薄为标准 projection 宿主壳;主抽屉始终渲染 `AgentRunProjectionPanel`,不再 import 或调用本地 process fallback,标准 projection 负责空态。
|
||||
- `src/features/agent-app/ui/AgentRunHostDrawerFallback.tsx` 文件仍存在,但已退出主路径,只作为待删除的 `deprecated` residual;物理删除需单独确认。
|
||||
- `src/features/agent-app/ui/AgentRunHostDrawerBoundary.test.ts` 已补结构守卫,防止主抽屉直接 import 本地 process 组件、fallback 文件或旧条件分支,并要求空态交给标准 projection。
|
||||
- `/Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench/docs/tutorials/content-studio.md` 已补完整 adoption blueprint,明确 Content Studio owns / does not own、标准 surface 映射、compat 迁移表和 blocked 行为。
|
||||
- `/Users/coso/Documents/dev/ai/limecloud/content-studio/src/renderer/src/components/agent/AgentUiProjectionSurface.tsx` 已作为产品侧标准 surface adapter,统一组合 `@limecloud/agent-runtime-ui` 的 timeline / facts primitives 与 Content Studio artifact/evidence refs。
|
||||
- Content Studio `AgentsWorkbench` / `AgentSessionPanel` 已改为消费 `AgentUiProjectionSurface`,不再直接散装组合共享 AgentUI primitives。
|
||||
- `/Users/coso/Documents/dev/ai/limecloud/content-studio/scripts/lime-agent-boundary-audit.mjs` 已增加边界检查,阻止产品页面绕过标准 surface 直接拼共享 primitives,并检查 `.agent-ui-projection` / `.agent-ui-main` / `.agent-ui-sidecar` DOM contract。
|
||||
- `/Users/coso/Documents/dev/ai/limecloud/content-studio/src/main/services/appServerAgentRuntimeGateway.ts` 已抽出 `ContentStudioAgentRuntimeSessionGateway`,并通过 `@limecloud/agent-runtime-client/sessionGateway` 的 `createAgentRuntimeClientFromSessionGateway(...)` 包装成标准 `AgentRuntimeClient`;本地 gateway 只保留 `agentSession/start`、`artifact/read` 和 sidecar transport 适配。
|
||||
- `ContentStudioAgentRuntimeSessionGateway.nextEvent()` 已保持标准 `agentSession/event` notification 形状,内部 turn drain 通过标准 `runtimeClient.nextEvent()` 消费 notification,再由本地 guard 提取裸 `RuntimeEvent`。
|
||||
- 标准包发布前门槛已通过:`npm --prefix packages/app-server-client run test`、`npm --prefix packages/agent-runtime-client run test`、`cargo test --manifest-path "lime-rs/Cargo.toml" -p app-server-protocol schema_fixtures_match_generated_output`、两个 package 目录内的 `npm pack --dry-run --json --ignore-scripts` 均通过。
|
||||
- `npm view "@limecloud/app-server-client@1.66.0" version` 与 `npm view "@limecloud/agent-runtime-client@0.1.1" version` 已可查询;`npm view "app-server-client@1.66.0" version` 与 `npm view "@limecloud/agent-runtime-client@0.1.0" version` 只作为误发兼容记录。
|
||||
- [standard-package-release-runbook.md](standard-package-release-runbook.md) 已更新为 scoped registry 发布、Content Studio 接入、GUI smoke 验收和 compat-misrelease 处理证据。
|
||||
|
||||
## Workstream 5:React Artifact / Evidence Refs
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | done |
|
||||
| 写集 | `packages/agent-runtime-ui/**`、Workbench runtime-ui docs |
|
||||
| current owner | `@limecloud/agent-runtime-ui` 只渲染 `state.artifacts` / `state.evidence` 轻量 refs,并通过 callback 交还宿主。 |
|
||||
| 禁止方向 | 在 React 包里读取 artifact body、导出 evidence pack、推断 review verdict 或复制大 payload。 |
|
||||
|
||||
完成证据:
|
||||
|
||||
- `packages/agent-runtime-ui/src/refs.tsx` 新增 `AgentUiRefList`、`ArtifactRefList`、`EvidenceRefList`。
|
||||
- `AgentUiProjectionView` 接入 `onSelectArtifactRef` / `onSelectEvidenceRef` 和 refs labels。
|
||||
- `packages/agent-runtime-ui/tests/ui.test.mjs` 覆盖 artifact/evidence fixture render 和稳定 DOM contract。
|
||||
- `npm --prefix packages/agent-runtime-ui run test` 通过。
|
||||
- `src/features/agent-app/runtime/agentRunProjectionState.ts` 使用 `projectAgentUiState` 生成标准 `AgentUiProjectionState`。
|
||||
- 定向测试 `agentRuntimeClientApi`、`agentRuntimeAppServerClient`、`agentRuntimeCapabilityHost`、`AgentAppRuntimePage.hostBridge`、`AgentRunProjectionPanel` 已通过。
|
||||
|
||||
剩余:
|
||||
|
||||
- Content Studio 已安装并接入 `@limecloud/agent-runtime-client/sessionGateway`;`package-lock.json` 解析到 `@limecloud/agent-runtime-client@0.1.1` 与间接 `@limecloud/app-server-client@1.66.0` registry tarball。
|
||||
- Content Studio 仍缺真实 GUI smoke / 产品回归证据。
|
||||
- Agent App 本地 process fallback 文件仍存在但已退出主路径;不得再把它接回主抽屉或扩成主展示 owner。
|
||||
- Agent App 后续新增协议映射必须进入 builders / mapping / field readers 对应边界,不得塞回 `agentUiProjectionBridge.ts`。
|
||||
- Agent App 主抽屉后续新增 UI 必须继续走标准 projection panel 或独立宿主壳;空态也必须交给标准 projection。
|
||||
|
||||
## Workstream 5:SDK React Surfaces Adoption
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | done |
|
||||
| 写集 | `packages/agent-runtime-ui/**`,必要时只读产品应用现有过程组件。 |
|
||||
| current owner | `@limecloud/agent-runtime-ui` 只消费 projection state 和 command callbacks。 |
|
||||
| 禁止方向 | React surface 直接订阅 Provider、发 JSON-RPC、持有 runtime facts。 |
|
||||
|
||||
任务:
|
||||
|
||||
- 固定 MessageParts、ProcessTimeline、ExecutionGraph、ActionRequired、Artifact / Evidence surfaces 的 props contract。
|
||||
- 把复杂状态分支保留在 projection / selectors,React 测试只覆盖渲染与事件接线。
|
||||
- 准备产品应用可复用的最小 surface 示例,但不把业务逻辑塞进 UI 包。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
npm --prefix packages/agent-runtime-ui run test
|
||||
npm --prefix packages/agent-runtime-ui run typecheck
|
||||
```
|
||||
|
||||
完成证据:
|
||||
|
||||
- React surfaces 不再依赖产品本地 runtime state。
|
||||
- fixture smoke 覆盖核心 surfaces。
|
||||
- `SubagentsView` 只消费 `state.subagents`,不在组件内重新筛 `graph` / `readModel`。
|
||||
- `RuntimeFactCard` 输出稳定 `agent-runtime-event` CSS contract。
|
||||
- `iteration-plan.md` 追加日志。
|
||||
|
||||
## Workstream 6:Release Integration
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | blocked |
|
||||
| 阻塞 | 需要用户明确要求版本号、commit、tag、push 或 GitHub Pages 发布。 |
|
||||
| 写集 | Workbench `package.json`、`package-lock.json`、`docs/development/updates.md`、GitHub workflow;必要时版本 tag。 |
|
||||
| current owner | GitHub Pages workflow + Workbench package version。 |
|
||||
|
||||
任务:
|
||||
|
||||
- 只有在用户明确要求后,才更新版本号并发布。
|
||||
- 发布前运行 docs build。
|
||||
- 若进入 Lime 主仓版本相关文件,必须按仓库发版规则运行版本一致性检查。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd /Users/coso/Documents/dev/ai/limecloud/lime-agent-workbench
|
||||
npm run docs:build
|
||||
```
|
||||
|
||||
完成证据:
|
||||
|
||||
- 版本、更新记录、构建、tag / push 状态一致。
|
||||
- 发布行为已由用户明确授权。
|
||||
|
||||
## Workstream 7:Standard Package Release / Content Studio Runtime Client Adoption
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | in-progress |
|
||||
| 阻塞 | scoped registry 发布和 Content Studio 依赖接入已完成;剩余阻塞是 Content Studio GUI smoke / 产品回归。 |
|
||||
| 写集 | 发布阶段优先 `packages/app-server-client/**`、`packages/agent-runtime-client/**`;接入阶段另行认领 `/Users/coso/Documents/dev/ai/limecloud/content-studio/package.json`、lockfile、`src/main/services/appServerAgentRuntimeGateway.ts`、`scripts/lime-agent-boundary-audit.mjs`。 |
|
||||
| current owner | npm registry 正式包 + Content Studio 标准 runtime-client facade。 |
|
||||
| 禁止方向 | `file:` / 绝对路径依赖、复制 dist、把 Content Studio 本地 gateway 继续扩成第二套 runtime client。 |
|
||||
|
||||
任务:
|
||||
|
||||
- 维持 [standard-package-release-runbook.md](standard-package-release-runbook.md) 中的 scoped registry 事实源。
|
||||
- 保持 Content Studio 固定安装并消费 `@limecloud/agent-runtime-client@0.1.1/sessionGateway`。
|
||||
- 补 Content Studio GUI smoke / 产品回归。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
npm view "@limecloud/app-server-client@1.66.0" version
|
||||
npm view "@limecloud/agent-runtime-client@0.1.1" version
|
||||
```
|
||||
|
||||
Content Studio 接入后再运行:
|
||||
|
||||
```bash
|
||||
npm run verify:lime-agent
|
||||
npm run test:functional -- --test-name-pattern "Lime Agent 边界审计会阻断 runtime/key/UI 协议回流"
|
||||
npx tsc --noEmit --pretty false
|
||||
```
|
||||
|
||||
完成证据:
|
||||
|
||||
- 两个目标包均可从 npm registry 查询。
|
||||
- Content Studio lockfile 消费正式 registry 包。
|
||||
- Content Studio 主路径实际调用 `@limecloud/agent-runtime-client/sessionGateway`。
|
||||
- `npm run verify:lime-agent`、定向 functional 和 `npx tsc --noEmit --pretty false` 已通过。
|
||||
- GUI smoke 证明 `.agent-ui-projection` 标准 surface 可运行。
|
||||
|
||||
## 当前推荐下一刀
|
||||
|
||||
优先级按对整体目标的提升排序:
|
||||
|
||||
1. Content Studio GUI smoke / 产品回归。证明 `.agent-ui-projection` 标准 surface 在真实页面中可运行,且边界审计进入常规验证。
|
||||
2. Agent App 本地 process fallback 物理删除。`AgentRunHostDrawerFallback.tsx` 已退出主路径并有结构守卫防回流;删除文件属于高风险文件系统操作,需要单独确认后执行。
|
||||
3. 真实 Provider / live evidence。只有在明确授权真实 Provider 后,补 live streaming / evidence 验证;日常回归继续使用 current fixture 和 GUI smoke。
|
||||
|
||||
不要优先做发布集成;当前 v0.3.0 / v0.4.0 仍缺 Content Studio 真实产品主路径和必要的收口拆分,发布只能算阶段快照。
|
||||
@@ -0,0 +1,73 @@
|
||||
# Subagents 路线图
|
||||
|
||||
## 目标
|
||||
|
||||
`/subagents` 对齐成熟终端代理的 subagents 行为:它是本地 slash command,用于启用、展示或切换子代理线程状态;它不是 Team 选择器,不是任务分工弹窗,也不是协作画布 / dock / workbench。
|
||||
|
||||
参考事实:
|
||||
|
||||
- `/subagents` 是本地 TUI 事件,不把命令文本发给模型。
|
||||
- `/agent` 与 `/subagents` 都进入本地 agent picker,picker 标题使用 `Subagents`。
|
||||
- Subagents 导航只维护 thread entry、稳定顺序、label、running/closed 状态和 active thread,不提供 Team profile 配置 UI。
|
||||
|
||||
## 当前事实源
|
||||
|
||||
### current
|
||||
|
||||
- 聊天输入中的 `/subagents` 是本地 slash action,命中后只调用 `onOpenSubagents`,不发送给模型,不走 mock,不新增 Electron IPC 或 App Server JSON-RPC 命令。
|
||||
- 输入栏 plus menu 的 Subagents 状态只表达当前 turn 可使用 Subagents/thread runtime,不打开 Team picker、Team profile editor 或协作工作台。
|
||||
- Subagents 运行态展示继续收敛到 runtime projection / workspace scene runtime:
|
||||
- `src/components/agent/chat/team-workspace-runtime/liveRuntimeProjector.ts`
|
||||
- `src/components/agent/chat/team-workspace-runtime/runtimeEventSubscriptions.ts`
|
||||
- `src/components/agent/chat/team-workspace-runtime/runtimeStateReconciler.ts`
|
||||
- `src/components/agent/chat/workspace/useWorkspaceTeamSessionRuntime.ts`
|
||||
- App Server / RuntimeCore 后续若提供 first-class subagent thread list,前端只允许接到 Subagents/thread projection,不得恢复 Team 配置面。
|
||||
|
||||
### compat
|
||||
|
||||
- `TeamDefinition`、`selectedTeam`、`recent_team_selection`、`teamWorkspaceRuntime`、`useTeamWorkspaceRuntime`、`lime.chat.team_selection.v1.general` 仍是历史兼容命名,只能承载现有 runtime metadata / session projection。
|
||||
- 兼容字段可以继续被发送边界读取,但不允许重新变成用户可见 Team 概念、profile 编辑器、任务分工模板或自定义 Team 存储入口。
|
||||
- 退出条件:App Server / RuntimeCore 提供 current `SubagentThread` / `SubagentProfile` 事实源后,将上述 compat 命名迁成 subagent/thread 语义,并删除不再被 current projection 使用的存储 key。
|
||||
|
||||
### dead
|
||||
|
||||
- `TeamSelector`、`TeamSelectorPanel`、`inputbarTeamSelectorCopy`。
|
||||
- `TeamSuggestionBar`、`teamSuggestion` 主动评分推荐条、`onEnableSuggestedTeam` / `handleEnableSuggestedTeam` / `enableSuggestedTeam` 旧 preset 写入口。
|
||||
- Team 选择弹窗、Team profile editor、自定义分工的新建 / 编辑 / 删除 / 复制入口。
|
||||
- 自定义 Team 克隆 / 本地写入入口:`cloneTeamDefinitionAsCustom`、`saveCustomTeams`、`buildWorkspaceSettingsWithCustomTeams`、`lime.chat.custom_teams.v1`。
|
||||
- Team 画布、Team workspace board、Team dock 与 Team summary panel。
|
||||
- Team memory shadow card / panel、AgentUI 旧协作操作 surface 以及 `team-workspace-board/` 整个目录。
|
||||
- `empty-state-team-selector`、`team-selector-stub` 等旧 test id / DOM surface。
|
||||
- `agentChat.inputbar.teamSelector.*` 与 Team canvas / board / dock / formation / selectedSession / activityPreview / operations / canvasLane / teamPreset i18n key。
|
||||
- `agentChat.inputbar.teamSuggestion.*` i18n key 与 `team-suggestion-bar` DOM surface。
|
||||
|
||||
## 实施约束
|
||||
|
||||
1. `/subagents` 必须保持本地 command:执行后返回 handled,不调用 `rawSendMessage`,不把 `/subagents` 文本送入模型。
|
||||
2. UI 只展示 Subagents/thread/runtime 状态;不得弹 TeamSelector Popover,不得打开 TeamSelectorPanel,不得出现 Team 画布或旧协作工作台。
|
||||
3. 新增用户可见文案必须使用 Subagents / 子代理语义,并覆盖 `zh-CN / zh-TW / en-US / ja-JP / ko-KR`。
|
||||
4. 治理守卫必须阻止旧 Team UI 文件、路径、props、test id 和 i18n key 回流。
|
||||
5. 保留的 `team*` compat 命名必须标明用途和退出条件,不能继续承接新功能。
|
||||
|
||||
## 本轮实施切口
|
||||
|
||||
1. 删除 Team selector/panel/canvas/workbench/dock 用户可见实现和测试。
|
||||
2. `/subagents` 接到本地 executor 与输入栏 Subagents 状态,不再走弹窗式 Team 配置。
|
||||
3. 清理 Team selector/canvas/workbench i18n key 与组件索引。
|
||||
4. 补治理目录册和质量脚本守卫,防止旧 Team UI surface 回流。
|
||||
5. 用定向单测、i18n 测试、治理报告和 contract 测试验证本地 command 与旧入口清退。
|
||||
|
||||
## 2026-06-10 实施记录
|
||||
|
||||
- 已删除 `TeamSelector` / `TeamSelectorPanel` / `inputbarTeamSelectorCopy`,`/subagents` 保持本地 command,不再触发任务分工弹窗。
|
||||
- 已删除 Team 画布、Team workspace board、dock、summary panel、memory shadow card 和 `team-workspace-board/` 目录;Subagents 展示回到 runtime projection / thread 状态主链。
|
||||
- 已删除 `TeamSuggestionBar` 与 `teamSuggestion` 主动推荐链;复杂输入不再弹出或插入推荐条,Subagents 只通过显式 `/subagents`、工具状态和线程展示进入。
|
||||
- 已删除未使用的自定义 Team 克隆与本地 `custom_teams` 写入入口;workspace settings 中的历史 `customTeams` 只保留只读 compat 解析。
|
||||
- 已把用户可见文案从 `Team current tools`、`Team Memory`、`Team member`、`teammate` 等改为 Subagents / 子代理语义;兼容 key 仅保留在协议、storage 和 runtime metadata 边界。
|
||||
- 已补 `legacySurfaceCatalog` dead surface 守卫,覆盖旧文件、路径、props、test id、Team 画布术语和旧展示文案,防止后续恢复。
|
||||
|
||||
## 后续退出条件
|
||||
|
||||
- Runtime 提供 first-class Subagents thread/profile schema 后,迁掉 `TeamDefinition` / `selectedTeam` / `recent_team_selection` 等 compat 命名。
|
||||
- `agentTeamWorkspace` namespace 只剩 current runtimeStatus / liveRuntime / control 文案后,评估是否改名为 Subagents namespace。
|
||||
- 若 `teamMemorySnapshot` 仍只作为历史 metadata 展示,保持只读 compat;若无 current 消费,按 `dead` 删除并补守卫。
|
||||
Generated
+23
-23
@@ -161,7 +161,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "app-server"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-server-protocol",
|
||||
@@ -201,7 +201,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "app-server-client"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"app-server-protocol",
|
||||
"app-server-transport",
|
||||
@@ -212,7 +212,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "app-server-daemon"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"app-server-protocol",
|
||||
"app-server-transport",
|
||||
@@ -224,7 +224,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "app-server-protocol"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -233,7 +233,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "app-server-test-client"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-server-client",
|
||||
@@ -244,7 +244,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "app-server-transport"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"app-server-protocol",
|
||||
"serde_json",
|
||||
@@ -3903,7 +3903,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-agent"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"aster-core",
|
||||
@@ -3932,7 +3932,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-browser-runtime"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"futures",
|
||||
@@ -3949,7 +3949,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-cli"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"lime-core",
|
||||
@@ -3961,7 +3961,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-config"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"lime-core",
|
||||
@@ -3977,7 +3977,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-core"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"aster-models",
|
||||
"async-trait",
|
||||
@@ -4033,7 +4033,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-gateway"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"axum 0.7.9",
|
||||
@@ -4063,7 +4063,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-infra"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dashmap 5.5.3",
|
||||
@@ -4083,7 +4083,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-knowledge"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
@@ -4096,7 +4096,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-mcp"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"dirs 5.0.1",
|
||||
@@ -4112,7 +4112,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-media-runtime"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"base64 0.22.1",
|
||||
@@ -4144,7 +4144,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-processor"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"lime-core",
|
||||
@@ -4163,7 +4163,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-providers"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -4218,7 +4218,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-server"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"aster-core",
|
||||
"async-stream",
|
||||
@@ -4262,7 +4262,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-server-utils"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"futures",
|
||||
@@ -4277,7 +4277,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-services"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"aster-core",
|
||||
@@ -4320,7 +4320,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-skills"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"dirs 5.0.1",
|
||||
@@ -4338,7 +4338,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lime-websocket"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ exclude = [
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
edition = "2021"
|
||||
authors = ["coso"]
|
||||
repository = "https://github.com/aiclientproxy/lime"
|
||||
|
||||
@@ -200,6 +200,8 @@ pub use app_server_protocol::WechatConfiguredAccount;
|
||||
pub use app_server_protocol::WindowsStartupCheck;
|
||||
pub use app_server_protocol::WindowsStartupDiagnosticsResponse;
|
||||
pub use app_server_protocol::WorkspaceEnsureParams;
|
||||
pub use app_server_protocol::WorkspaceEnsureProjectParams;
|
||||
pub use app_server_protocol::WorkspaceEnsureProjectResponse;
|
||||
pub use app_server_protocol::WorkspaceEnsureReadyResponse;
|
||||
pub use app_server_protocol::WorkspaceListResponse;
|
||||
pub use app_server_protocol::WorkspacePathReadParams;
|
||||
@@ -347,6 +349,7 @@ pub use app_server_protocol::METHOD_WECHAT_CHANNEL_ACCOUNT_LIST;
|
||||
pub use app_server_protocol::METHOD_WORKSPACE_BY_PATH_READ;
|
||||
pub use app_server_protocol::METHOD_WORKSPACE_DEFAULT_ENSURE;
|
||||
pub use app_server_protocol::METHOD_WORKSPACE_DEFAULT_READ;
|
||||
pub use app_server_protocol::METHOD_WORKSPACE_ENSURE;
|
||||
pub use app_server_protocol::METHOD_WORKSPACE_ENSURE_READY;
|
||||
pub use app_server_protocol::METHOD_WORKSPACE_LIST;
|
||||
pub use app_server_protocol::METHOD_WORKSPACE_PROJECTS_ROOT_READ;
|
||||
@@ -516,6 +519,13 @@ impl AppServerClient {
|
||||
self.typed_request(typed::read_workspace_by_path(params))
|
||||
}
|
||||
|
||||
pub fn ensure_workspace(
|
||||
&mut self,
|
||||
params: WorkspaceEnsureProjectParams,
|
||||
) -> Result<JsonRpcRequest, ClientError> {
|
||||
self.typed_request(typed::ensure_workspace(params))
|
||||
}
|
||||
|
||||
pub fn read_default_workspace(&mut self) -> Result<JsonRpcRequest, ClientError> {
|
||||
self.typed_request(typed::read_default_workspace())
|
||||
}
|
||||
@@ -1352,6 +1362,12 @@ pub mod typed {
|
||||
TypedRequest::new(METHOD_WORKSPACE_BY_PATH_READ, params)
|
||||
}
|
||||
|
||||
pub fn ensure_workspace(
|
||||
params: WorkspaceEnsureProjectParams,
|
||||
) -> TypedRequest<WorkspaceEnsureProjectParams> {
|
||||
TypedRequest::new(METHOD_WORKSPACE_ENSURE, params)
|
||||
}
|
||||
|
||||
pub fn read_default_workspace() -> TypedRequest<serde_json::Value> {
|
||||
TypedRequest::new(METHOD_WORKSPACE_DEFAULT_READ, serde_json::json!({}))
|
||||
}
|
||||
@@ -2408,6 +2424,13 @@ mod tests {
|
||||
root_path: "/workspace/project".to_string(),
|
||||
})
|
||||
.expect("workspace by path");
|
||||
let ensured_workspace = client
|
||||
.ensure_workspace(WorkspaceEnsureProjectParams {
|
||||
name: "content-studio".to_string(),
|
||||
root_path: "/workspace/content-studio".to_string(),
|
||||
workspace_type: Some("general".to_string()),
|
||||
})
|
||||
.expect("ensure workspace");
|
||||
let default_workspace = client.read_default_workspace().expect("default workspace");
|
||||
let ensured_default = client
|
||||
.ensure_default_workspace()
|
||||
@@ -2439,6 +2462,15 @@ mod tests {
|
||||
workspace_by_path.params.expect("params"),
|
||||
json!({ "rootPath": "/workspace/project" })
|
||||
);
|
||||
assert_eq!(ensured_workspace.method, METHOD_WORKSPACE_ENSURE);
|
||||
assert_eq!(
|
||||
ensured_workspace.params.expect("params"),
|
||||
json!({
|
||||
"name": "content-studio",
|
||||
"rootPath": "/workspace/content-studio",
|
||||
"workspaceType": "general",
|
||||
})
|
||||
);
|
||||
assert_eq!(default_workspace.method, METHOD_WORKSPACE_DEFAULT_READ);
|
||||
assert_eq!(ensured_default.method, METHOD_WORKSPACE_DEFAULT_ENSURE);
|
||||
assert_eq!(projects_root.method, METHOD_WORKSPACE_PROJECTS_ROOT_READ);
|
||||
@@ -3411,6 +3443,7 @@ mod tests {
|
||||
assert!(methods.contains(&METHOD_AGENT_APP_SHELL_PREPARE));
|
||||
assert!(methods.contains(&METHOD_WORKSPACE_READ));
|
||||
assert!(methods.contains(&METHOD_WORKSPACE_BY_PATH_READ));
|
||||
assert!(methods.contains(&METHOD_WORKSPACE_ENSURE));
|
||||
assert!(methods.contains(&METHOD_WORKSPACE_DEFAULT_READ));
|
||||
assert!(methods.contains(&METHOD_WORKSPACE_DEFAULT_ENSURE));
|
||||
assert!(methods.contains(&METHOD_WORKSPACE_PROJECTS_ROOT_READ));
|
||||
|
||||
@@ -19717,6 +19717,48 @@
|
||||
"title": "WorkspaceEnsureParams",
|
||||
"type": "object"
|
||||
},
|
||||
"WorkspaceEnsureProjectParams": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"rootPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"workspaceType": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"rootPath"
|
||||
],
|
||||
"title": "WorkspaceEnsureProjectParams",
|
||||
"type": "object"
|
||||
},
|
||||
"WorkspaceEnsureProjectResponse": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"rootCreated": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"workspace": true
|
||||
},
|
||||
"required": [
|
||||
"created",
|
||||
"rootCreated",
|
||||
"workspace"
|
||||
],
|
||||
"title": "WorkspaceEnsureProjectResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"WorkspaceEnsureReadyResponse": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
@@ -20197,6 +20239,7 @@
|
||||
"workspace/byPath/read",
|
||||
"workspace/default/ensure",
|
||||
"workspace/default/read",
|
||||
"workspace/ensure",
|
||||
"workspace/ensureReady",
|
||||
"workspace/list",
|
||||
"workspace/projectPath/resolve",
|
||||
|
||||
@@ -922,6 +922,10 @@
|
||||
"kind": "request",
|
||||
"method": "workspace/default/read"
|
||||
},
|
||||
{
|
||||
"kind": "request",
|
||||
"method": "workspace/ensure"
|
||||
},
|
||||
{
|
||||
"kind": "request",
|
||||
"method": "workspace/ensureReady"
|
||||
@@ -1385,6 +1389,8 @@
|
||||
"WindowsStartupCheck",
|
||||
"WindowsStartupDiagnosticsResponse",
|
||||
"WorkspaceEnsureParams",
|
||||
"WorkspaceEnsureProjectParams",
|
||||
"WorkspaceEnsureProjectResponse",
|
||||
"WorkspaceEnsureReadyResponse",
|
||||
"WorkspaceListResponse",
|
||||
"WorkspacePathReadParams",
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"rootPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"workspaceType": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"rootPath"
|
||||
],
|
||||
"title": "WorkspaceEnsureProjectParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"rootCreated": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"workspace": true
|
||||
},
|
||||
"required": [
|
||||
"created",
|
||||
"rootCreated",
|
||||
"workspace"
|
||||
],
|
||||
"title": "WorkspaceEnsureProjectResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -70,6 +70,7 @@ pub const APP_SERVER_METHODS: &[AppServerMethodSpec] = &[
|
||||
method!(METHOD_SESSION_FILE_LIST, Request),
|
||||
method!(METHOD_WORKSPACE_LIST, Request),
|
||||
method!(METHOD_WORKSPACE_READ, Request),
|
||||
method!(METHOD_WORKSPACE_ENSURE, Request),
|
||||
method!(METHOD_WORKSPACE_BY_PATH_READ, Request),
|
||||
method!(METHOD_WORKSPACE_DEFAULT_READ, Request),
|
||||
method!(METHOD_WORKSPACE_DEFAULT_ENSURE, Request),
|
||||
|
||||
@@ -43,6 +43,7 @@ pub const METHOD_SESSION_FILE_DELETE: &str = "sessionFile/delete";
|
||||
pub const METHOD_SESSION_FILE_LIST: &str = "sessionFile/list";
|
||||
pub const METHOD_WORKSPACE_LIST: &str = "workspace/list";
|
||||
pub const METHOD_WORKSPACE_READ: &str = "workspace/read";
|
||||
pub const METHOD_WORKSPACE_ENSURE: &str = "workspace/ensure";
|
||||
pub const METHOD_WORKSPACE_BY_PATH_READ: &str = "workspace/byPath/read";
|
||||
pub const METHOD_WORKSPACE_DEFAULT_READ: &str = "workspace/default/read";
|
||||
pub const METHOD_WORKSPACE_DEFAULT_ENSURE: &str = "workspace/default/ensure";
|
||||
|
||||
@@ -89,10 +89,12 @@ pub const V0_SCHEMA_TYPE_NAMES: &[&str] = &[
|
||||
"SessionFileMutationResponse",
|
||||
"WorkspaceReadParams",
|
||||
"WorkspacePathReadParams",
|
||||
"WorkspaceEnsureProjectParams",
|
||||
"WorkspaceProjectPathResolveParams",
|
||||
"WorkspaceEnsureParams",
|
||||
"WorkspaceListResponse",
|
||||
"WorkspaceReadResponse",
|
||||
"WorkspaceEnsureProjectResponse",
|
||||
"WorkspaceProjectsRootReadResponse",
|
||||
"WorkspaceProjectPathResolveResponse",
|
||||
"WorkspaceEnsureReadyResponse",
|
||||
|
||||
@@ -47,6 +47,7 @@ fn app_server_method_catalog_keeps_request_and_notification_methods_together() {
|
||||
METHOD_SESSION_FILE_LIST,
|
||||
METHOD_WORKSPACE_LIST,
|
||||
METHOD_WORKSPACE_READ,
|
||||
METHOD_WORKSPACE_ENSURE,
|
||||
METHOD_WORKSPACE_BY_PATH_READ,
|
||||
METHOD_WORKSPACE_DEFAULT_READ,
|
||||
METHOD_WORKSPACE_DEFAULT_ENSURE,
|
||||
|
||||
@@ -13,6 +13,15 @@ pub struct WorkspacePathReadParams {
|
||||
pub root_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceEnsureProjectParams {
|
||||
pub name: String,
|
||||
pub root_path: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub workspace_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceProjectPathResolveParams {
|
||||
@@ -41,6 +50,14 @@ pub struct WorkspaceReadResponse {
|
||||
pub workspace: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceEnsureProjectResponse {
|
||||
pub workspace: serde_json::Value,
|
||||
pub created: bool,
|
||||
pub root_created: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceProjectsRootReadResponse {
|
||||
|
||||
@@ -159,10 +159,12 @@ pub(super) fn v0_schemas() -> Vec<GeneratedJsonSchema> {
|
||||
typed_schema::<SessionFileMutationResponse>("SessionFileMutationResponse"),
|
||||
typed_schema::<WorkspaceReadParams>("WorkspaceReadParams"),
|
||||
typed_schema::<WorkspacePathReadParams>("WorkspacePathReadParams"),
|
||||
typed_schema::<WorkspaceEnsureProjectParams>("WorkspaceEnsureProjectParams"),
|
||||
typed_schema::<WorkspaceProjectPathResolveParams>("WorkspaceProjectPathResolveParams"),
|
||||
typed_schema::<WorkspaceEnsureParams>("WorkspaceEnsureParams"),
|
||||
typed_schema::<WorkspaceListResponse>("WorkspaceListResponse"),
|
||||
typed_schema::<WorkspaceReadResponse>("WorkspaceReadResponse"),
|
||||
typed_schema::<WorkspaceEnsureProjectResponse>("WorkspaceEnsureProjectResponse"),
|
||||
typed_schema::<WorkspaceProjectsRootReadResponse>("WorkspaceProjectsRootReadResponse"),
|
||||
typed_schema::<WorkspaceProjectPathResolveResponse>("WorkspaceProjectPathResolveResponse"),
|
||||
typed_schema::<WorkspaceEnsureReadyResponse>("WorkspaceEnsureReadyResponse"),
|
||||
|
||||
@@ -291,6 +291,8 @@ use app_server_protocol::WechatRuntimeModelSetParams;
|
||||
use app_server_protocol::WechatRuntimeModelSetResponse;
|
||||
use app_server_protocol::WindowsStartupDiagnosticsResponse;
|
||||
use app_server_protocol::WorkspaceEnsureParams;
|
||||
use app_server_protocol::WorkspaceEnsureProjectParams;
|
||||
use app_server_protocol::WorkspaceEnsureProjectResponse;
|
||||
use app_server_protocol::WorkspaceEnsureReadyResponse;
|
||||
use app_server_protocol::WorkspaceListResponse;
|
||||
use app_server_protocol::WorkspacePathReadParams;
|
||||
@@ -511,6 +513,13 @@ impl AppDataSource for LocalAppDataSource {
|
||||
workspaces::read_workspace_by_path(&self.db, params)
|
||||
}
|
||||
|
||||
async fn ensure_project_workspace(
|
||||
&self,
|
||||
params: WorkspaceEnsureProjectParams,
|
||||
) -> Result<WorkspaceEnsureProjectResponse, RuntimeCoreError> {
|
||||
workspaces::ensure_project_workspace(&self.db, params)
|
||||
}
|
||||
|
||||
async fn read_default_workspace(&self) -> Result<WorkspaceReadResponse, RuntimeCoreError> {
|
||||
workspaces::read_default_workspace(&self.db)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::data_error;
|
||||
use crate::RuntimeCoreError;
|
||||
use app_server_protocol::WorkspaceEnsureParams;
|
||||
use app_server_protocol::WorkspaceEnsureProjectParams;
|
||||
use app_server_protocol::WorkspaceEnsureProjectResponse;
|
||||
use app_server_protocol::WorkspaceEnsureReadyResponse;
|
||||
use app_server_protocol::WorkspaceListResponse;
|
||||
use app_server_protocol::WorkspacePathReadParams;
|
||||
@@ -75,6 +77,61 @@ pub(crate) fn read_workspace_by_path(
|
||||
Ok(WorkspaceReadResponse { workspace })
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_project_workspace(
|
||||
db: &DbConnection,
|
||||
params: WorkspaceEnsureProjectParams,
|
||||
) -> Result<WorkspaceEnsureProjectResponse, RuntimeCoreError> {
|
||||
let conn = database::lock_db(db).map_err(data_error)?;
|
||||
let root = PathBuf::from(params.root_path.trim());
|
||||
if params.root_path.trim().is_empty() {
|
||||
return Err(data_error("workspace root_path is required"));
|
||||
}
|
||||
|
||||
let root_path = root
|
||||
.to_str()
|
||||
.ok_or_else(|| data_error("invalid workspace root_path"))?
|
||||
.to_string();
|
||||
if let Some(existing) =
|
||||
read_workspace_by_root_path(&conn, Path::new(&root_path)).map_err(data_error)?
|
||||
{
|
||||
let existed = root.is_dir();
|
||||
fs::create_dir_all(&root).map_err(data_error)?;
|
||||
return Ok(WorkspaceEnsureProjectResponse {
|
||||
workspace: existing,
|
||||
created: false,
|
||||
root_created: !existed,
|
||||
});
|
||||
}
|
||||
|
||||
let workspace_type = normalize_workspace_type(params.workspace_type.as_deref())?;
|
||||
let name = normalize_workspace_name(¶ms.name, &root_path);
|
||||
let existed = root.is_dir();
|
||||
fs::create_dir_all(&root).map_err(data_error)?;
|
||||
|
||||
let now = Utc::now().timestamp_millis();
|
||||
let id = Uuid::new_v4().to_string();
|
||||
let icon = default_workspace_icon(workspace_type);
|
||||
conn.execute(
|
||||
"INSERT INTO workspaces (
|
||||
id, name, workspace_type, root_path, is_default, settings_json,
|
||||
icon, color, is_favorite, is_archived, tags_json, default_persona_id,
|
||||
created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, 0, '{}', ?, NULL, 0, 0, '[]', NULL, ?, ?)",
|
||||
params![id, name, workspace_type, root_path, icon, now, now],
|
||||
)
|
||||
.map_err(|error| data_error(format!("ensure workspace failed: {error}")))?;
|
||||
|
||||
let workspace = read_workspace_by_id(&conn, &id)
|
||||
.map_err(data_error)?
|
||||
.ok_or_else(|| data_error("failed to reload ensured workspace"))?;
|
||||
Ok(WorkspaceEnsureProjectResponse {
|
||||
workspace,
|
||||
created: true,
|
||||
root_created: !existed,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn read_default_workspace(
|
||||
db: &DbConnection,
|
||||
) -> Result<WorkspaceReadResponse, RuntimeCoreError> {
|
||||
@@ -314,6 +371,41 @@ fn workspace_id(value: &Value) -> Option<&str> {
|
||||
value.get("id").and_then(Value::as_str)
|
||||
}
|
||||
|
||||
fn normalize_workspace_type(value: Option<&str>) -> Result<&'static str, RuntimeCoreError> {
|
||||
match value.map(str::trim).filter(|value| !value.is_empty()) {
|
||||
None => Ok("general"),
|
||||
Some("persistent") => Ok("persistent"),
|
||||
Some("temporary") => Ok("temporary"),
|
||||
Some("general") => Ok("general"),
|
||||
Some(value) => Err(data_error(format!(
|
||||
"unsupported workspace_type '{value}', only persistent / temporary / general are supported"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_workspace_name(name: &str, root_path: &str) -> String {
|
||||
let trimmed = name.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return trimmed.to_string();
|
||||
}
|
||||
|
||||
Path::new(root_path)
|
||||
.file_name()
|
||||
.and_then(|value| value.to_str())
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or("未命名项目")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn default_workspace_icon(workspace_type: &str) -> Option<&'static str> {
|
||||
match workspace_type {
|
||||
"general" => Some("💬"),
|
||||
"persistent" | "temporary" => Some("📁"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sanitize_project_dir_name(name: &str) -> String {
|
||||
let sanitized: String = name
|
||||
.trim()
|
||||
|
||||
@@ -126,12 +126,12 @@ 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::ProjectMaterialImportFromUrlParams;
|
||||
use app_server_protocol::ProjectMaterialListParams;
|
||||
use app_server_protocol::ProjectMaterialLookupParams;
|
||||
use app_server_protocol::ProjectMaterialUpdateParams;
|
||||
use app_server_protocol::ProjectMaterialUploadParams;
|
||||
use app_server_protocol::ProjectMemoryReadParams;
|
||||
use app_server_protocol::ServerCapabilities;
|
||||
use app_server_protocol::ServerInfo;
|
||||
use app_server_protocol::SessionFileGetOrCreateParams;
|
||||
@@ -156,7 +156,6 @@ use app_server_protocol::SkillRemoteInspectParams;
|
||||
use app_server_protocol::SkillRepositoryDeleteParams;
|
||||
use app_server_protocol::SkillRepositorySaveParams;
|
||||
use app_server_protocol::SkillScaffoldCreateParams;
|
||||
use app_server_protocol::UsageStatsRangeParams;
|
||||
use app_server_protocol::UnifiedMemoryAnalyzeParams;
|
||||
use app_server_protocol::UnifiedMemoryCreateParams;
|
||||
use app_server_protocol::UnifiedMemoryDeleteParams;
|
||||
@@ -166,6 +165,7 @@ use app_server_protocol::UnifiedMemoryListParams;
|
||||
use app_server_protocol::UnifiedMemorySearchParams;
|
||||
use app_server_protocol::UnifiedMemorySemanticSearchParams;
|
||||
use app_server_protocol::UnifiedMemoryUpdateParams;
|
||||
use app_server_protocol::UsageStatsRangeParams;
|
||||
use app_server_protocol::VoiceAsrCredentialCreateParams;
|
||||
use app_server_protocol::VoiceAsrCredentialIdParams;
|
||||
use app_server_protocol::VoiceAsrCredentialUpdateParams;
|
||||
@@ -178,6 +178,7 @@ use app_server_protocol::WechatLoginStartParams;
|
||||
use app_server_protocol::WechatLoginWaitParams;
|
||||
use app_server_protocol::WechatRuntimeModelSetParams;
|
||||
use app_server_protocol::WorkspaceEnsureParams;
|
||||
use app_server_protocol::WorkspaceEnsureProjectParams;
|
||||
use app_server_protocol::WorkspacePathReadParams;
|
||||
use app_server_protocol::WorkspaceProjectPathResolveParams;
|
||||
use app_server_protocol::WorkspaceReadParams;
|
||||
@@ -339,7 +340,6 @@ 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_PROJECT_MATERIAL_CONTENT;
|
||||
use app_server_protocol::METHOD_PROJECT_MATERIAL_COUNT;
|
||||
use app_server_protocol::METHOD_PROJECT_MATERIAL_DELETE;
|
||||
@@ -348,6 +348,7 @@ use app_server_protocol::METHOD_PROJECT_MATERIAL_IMPORT_FROM_URL;
|
||||
use app_server_protocol::METHOD_PROJECT_MATERIAL_LIST;
|
||||
use app_server_protocol::METHOD_PROJECT_MATERIAL_UPDATE;
|
||||
use app_server_protocol::METHOD_PROJECT_MATERIAL_UPLOAD;
|
||||
use app_server_protocol::METHOD_PROJECT_MEMORY_READ;
|
||||
use app_server_protocol::METHOD_SESSION_FILE_DELETE;
|
||||
use app_server_protocol::METHOD_SESSION_FILE_GET_OR_CREATE;
|
||||
use app_server_protocol::METHOD_SESSION_FILE_LIST;
|
||||
@@ -378,9 +379,6 @@ use app_server_protocol::METHOD_SKILL_REPOSITORY_DELETE;
|
||||
use app_server_protocol::METHOD_SKILL_REPOSITORY_LIST;
|
||||
use app_server_protocol::METHOD_SKILL_REPOSITORY_SAVE;
|
||||
use app_server_protocol::METHOD_TELEGRAM_CHANNEL_PROBE;
|
||||
use app_server_protocol::METHOD_USAGE_STATS_DAILY_TRENDS_LIST;
|
||||
use app_server_protocol::METHOD_USAGE_STATS_MODEL_RANKING_LIST;
|
||||
use app_server_protocol::METHOD_USAGE_STATS_READ;
|
||||
use app_server_protocol::METHOD_UNIFIED_MEMORY_ANALYZE;
|
||||
use app_server_protocol::METHOD_UNIFIED_MEMORY_CREATE;
|
||||
use app_server_protocol::METHOD_UNIFIED_MEMORY_DELETE;
|
||||
@@ -391,6 +389,9 @@ use app_server_protocol::METHOD_UNIFIED_MEMORY_SEARCH;
|
||||
use app_server_protocol::METHOD_UNIFIED_MEMORY_SEMANTIC_SEARCH;
|
||||
use app_server_protocol::METHOD_UNIFIED_MEMORY_STATS;
|
||||
use app_server_protocol::METHOD_UNIFIED_MEMORY_UPDATE;
|
||||
use app_server_protocol::METHOD_USAGE_STATS_DAILY_TRENDS_LIST;
|
||||
use app_server_protocol::METHOD_USAGE_STATS_MODEL_RANKING_LIST;
|
||||
use app_server_protocol::METHOD_USAGE_STATS_READ;
|
||||
use app_server_protocol::METHOD_VOICE_ASR_CREDENTIAL_CREATE;
|
||||
use app_server_protocol::METHOD_VOICE_ASR_CREDENTIAL_DEFAULT_SET;
|
||||
use app_server_protocol::METHOD_VOICE_ASR_CREDENTIAL_DELETE;
|
||||
@@ -411,6 +412,7 @@ use app_server_protocol::METHOD_WECHAT_CHANNEL_RUNTIME_MODEL_SET;
|
||||
use app_server_protocol::METHOD_WORKSPACE_BY_PATH_READ;
|
||||
use app_server_protocol::METHOD_WORKSPACE_DEFAULT_ENSURE;
|
||||
use app_server_protocol::METHOD_WORKSPACE_DEFAULT_READ;
|
||||
use app_server_protocol::METHOD_WORKSPACE_ENSURE;
|
||||
use app_server_protocol::METHOD_WORKSPACE_ENSURE_READY;
|
||||
use app_server_protocol::METHOD_WORKSPACE_LIST;
|
||||
use app_server_protocol::METHOD_WORKSPACE_PROJECTS_ROOT_READ;
|
||||
@@ -545,6 +547,7 @@ impl RequestProcessor {
|
||||
METHOD_AGENT_SESSION_READ => self.handle_session_read(params).await,
|
||||
METHOD_WORKSPACE_LIST => self.handle_workspace_list().await,
|
||||
METHOD_WORKSPACE_READ => self.handle_workspace_read(params).await,
|
||||
METHOD_WORKSPACE_ENSURE => self.handle_workspace_ensure(params).await,
|
||||
METHOD_WORKSPACE_BY_PATH_READ => self.handle_workspace_by_path_read(params).await,
|
||||
METHOD_WORKSPACE_DEFAULT_READ => self.handle_workspace_default_read().await,
|
||||
METHOD_WORKSPACE_DEFAULT_ENSURE => self.handle_workspace_default_ensure().await,
|
||||
@@ -1313,6 +1316,20 @@ impl RequestProcessor {
|
||||
dispatch_result(response)
|
||||
}
|
||||
|
||||
async fn handle_workspace_ensure(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<RpcDispatch, JsonRpcError> {
|
||||
self.ensure_initialized()?;
|
||||
let params: WorkspaceEnsureProjectParams = parse_params(params)?;
|
||||
let response = self
|
||||
.runtime
|
||||
.ensure_project_workspace(params)
|
||||
.await
|
||||
.map_err(to_jsonrpc_error)?;
|
||||
dispatch_result(response)
|
||||
}
|
||||
|
||||
async fn handle_workspace_default_read(&self) -> Result<RpcDispatch, JsonRpcError> {
|
||||
self.ensure_initialized()?;
|
||||
let response = self
|
||||
|
||||
@@ -347,6 +347,8 @@ use app_server_protocol::WechatRuntimeModelSetParams;
|
||||
use app_server_protocol::WechatRuntimeModelSetResponse;
|
||||
use app_server_protocol::WindowsStartupDiagnosticsResponse;
|
||||
use app_server_protocol::WorkspaceEnsureParams;
|
||||
use app_server_protocol::WorkspaceEnsureProjectParams;
|
||||
use app_server_protocol::WorkspaceEnsureProjectResponse;
|
||||
use app_server_protocol::WorkspaceEnsureReadyResponse;
|
||||
use app_server_protocol::WorkspaceListResponse;
|
||||
use app_server_protocol::WorkspacePathReadParams;
|
||||
@@ -638,6 +640,15 @@ pub trait AppDataSource: Send + Sync {
|
||||
params: WorkspacePathReadParams,
|
||||
) -> Result<WorkspaceReadResponse, RuntimeCoreError>;
|
||||
|
||||
async fn ensure_project_workspace(
|
||||
&self,
|
||||
_params: WorkspaceEnsureProjectParams,
|
||||
) -> Result<WorkspaceEnsureProjectResponse, RuntimeCoreError> {
|
||||
Err(RuntimeCoreError::Backend(
|
||||
"workspace/ensure is not available without an app data source".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn read_default_workspace(&self) -> Result<WorkspaceReadResponse, RuntimeCoreError>;
|
||||
|
||||
async fn ensure_default_workspace(&self) -> Result<WorkspaceReadResponse, RuntimeCoreError>;
|
||||
@@ -2306,6 +2317,15 @@ impl AppDataSource for NoopAppDataSource {
|
||||
Ok(WorkspaceReadResponse::default())
|
||||
}
|
||||
|
||||
async fn ensure_project_workspace(
|
||||
&self,
|
||||
_params: WorkspaceEnsureProjectParams,
|
||||
) -> Result<WorkspaceEnsureProjectResponse, RuntimeCoreError> {
|
||||
Err(RuntimeCoreError::Backend(
|
||||
"workspace/ensure is not available without an app data source".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn read_default_workspace(&self) -> Result<WorkspaceReadResponse, RuntimeCoreError> {
|
||||
Ok(WorkspaceReadResponse::default())
|
||||
}
|
||||
@@ -4494,6 +4514,13 @@ impl RuntimeCore {
|
||||
self.app_data_source.read_workspace_by_path(params).await
|
||||
}
|
||||
|
||||
pub async fn ensure_project_workspace(
|
||||
&self,
|
||||
params: WorkspaceEnsureProjectParams,
|
||||
) -> Result<WorkspaceEnsureProjectResponse, RuntimeCoreError> {
|
||||
self.app_data_source.ensure_project_workspace(params).await
|
||||
}
|
||||
|
||||
pub async fn read_default_workspace(&self) -> Result<WorkspaceReadResponse, RuntimeCoreError> {
|
||||
self.app_data_source.read_default_workspace().await
|
||||
}
|
||||
|
||||
@@ -22,18 +22,17 @@ pub use path_utils::{collapse_tilde, contains_tilde, expand_tilde};
|
||||
pub use types::{
|
||||
generate_secure_api_key, AmpConfig, AmpModelMapping, ApiKeyEntry, AsrCredentialEntry,
|
||||
AsrProviderType, AutomationExecutionMode, AutomationSettings, BaiduConfig, ChannelsConfig,
|
||||
ChatAppearanceConfig, CloudflareTunnelConfig, CompanionDefaultsConfig, Config,
|
||||
ConversationSettings, CrashReportingConfig, CredentialEntry, CredentialPoolConfig,
|
||||
CustomProviderConfig, DeliveryConfig, DiscordAccountConfig, DiscordActionsConfig,
|
||||
DiscordAgentComponentsConfig, DiscordAutoPresenceConfig, DiscordBotConfig,
|
||||
DiscordChannelConfig, DiscordExecApprovalsConfig, DiscordGuildConfig, DiscordIntentsConfig,
|
||||
DiscordThreadBindingsConfig, DiscordUiComponentsConfig, DiscordUiConfig,
|
||||
DiscordVoiceAutoJoinConfig, DiscordVoiceConfig, EndpointProvidersConfig, EnvironmentConfig,
|
||||
EnvironmentVariableOverride, ExperimentalFeatures, FeishuAccountConfig, FeishuBotConfig,
|
||||
FeishuGroupConfig, GatewayConfig, GatewayTunnelConfig, GeminiApiKeyEntry,
|
||||
HintRouteSettingsEntry, HintRouterSettings, ImageGenConfig, InjectionRuleConfig,
|
||||
InjectionSettings, LoggingConfig, MemoryAutoConfig, MemoryConfig, MemoryEmbeddingConfig,
|
||||
MemoryEmbeddingProvider, MemoryProfileConfig, MemoryResolveConfig,
|
||||
ChatAppearanceConfig, CloudflareTunnelConfig, Config, ConversationSettings,
|
||||
CrashReportingConfig, CredentialEntry, CredentialPoolConfig, CustomProviderConfig,
|
||||
DeliveryConfig, DiscordAccountConfig, DiscordActionsConfig, DiscordAgentComponentsConfig,
|
||||
DiscordAutoPresenceConfig, DiscordBotConfig, DiscordChannelConfig, DiscordExecApprovalsConfig,
|
||||
DiscordGuildConfig, DiscordIntentsConfig, DiscordThreadBindingsConfig,
|
||||
DiscordUiComponentsConfig, DiscordUiConfig, DiscordVoiceAutoJoinConfig, DiscordVoiceConfig,
|
||||
EndpointProvidersConfig, EnvironmentConfig, EnvironmentVariableOverride, ExperimentalFeatures,
|
||||
FeishuAccountConfig, FeishuBotConfig, FeishuGroupConfig, GatewayConfig, GatewayTunnelConfig,
|
||||
GeminiApiKeyEntry, HintRouteSettingsEntry, HintRouterSettings, ImageGenConfig,
|
||||
InjectionRuleConfig, InjectionSettings, LoggingConfig, MemoryAutoConfig, MemoryConfig,
|
||||
MemoryEmbeddingConfig, MemoryEmbeddingProvider, MemoryProfileConfig, MemoryResolveConfig,
|
||||
MemorySoulArtifactVoiceConfig, MemorySoulArtifactVoiceSource, MemorySoulConfig,
|
||||
MemorySoulImportSource, MemorySourcesConfig, ModelInfo, ModelsConfig, MultiSearchConfig,
|
||||
MultiSearchEngineEntryConfig, NativeAgentConfig, NavigationConfig, OpenAIAsrConfig,
|
||||
|
||||
@@ -692,9 +692,6 @@ pub struct WorkspacePreferencesConfig {
|
||||
/// 全局媒体生成默认设置
|
||||
#[serde(default)]
|
||||
pub media_defaults: MediaGenerationDefaultsConfig,
|
||||
/// 桌宠能力偏好设置
|
||||
#[serde(default)]
|
||||
pub companion_defaults: CompanionDefaultsConfig,
|
||||
/// 服务模型设置
|
||||
#[serde(default, skip_serializing_if = "ServiceModelsConfig::is_default")]
|
||||
pub service_models: ServiceModelsConfig,
|
||||
@@ -710,7 +707,6 @@ impl Default for WorkspacePreferencesConfig {
|
||||
schema_version: current_workspace_preferences_schema_version(),
|
||||
agent_response_language: None,
|
||||
media_defaults: MediaGenerationDefaultsConfig::default(),
|
||||
companion_defaults: CompanionDefaultsConfig::default(),
|
||||
service_models: ServiceModelsConfig::default(),
|
||||
}
|
||||
}
|
||||
@@ -752,14 +748,6 @@ pub struct MediaGenerationDefaultsConfig {
|
||||
pub voice: MediaGenerationPreferenceConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
pub struct CompanionDefaultsConfig {
|
||||
#[serde(default)]
|
||||
pub general: MediaGenerationPreferenceConfig,
|
||||
#[serde(default)]
|
||||
pub tts: MediaGenerationPreferenceConfig,
|
||||
}
|
||||
|
||||
fn default_service_model_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
@@ -854,13 +842,13 @@ impl ServiceModelsConfig {
|
||||
|
||||
/// 导航栏模块配置
|
||||
///
|
||||
/// 配置左侧导航栏里可显式开启的隐藏系统入口
|
||||
/// 保留历史导航配置字段;当前不再提供可显式开启的隐藏系统入口
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct NavigationConfig {
|
||||
/// 工作区导航默认值版本
|
||||
#[serde(default)]
|
||||
pub schema_version: u8,
|
||||
/// 已显式开启的隐藏系统入口列表;固定主导航不写入配置
|
||||
/// 历史隐藏系统入口列表;归一化时清空,固定主导航不写入配置
|
||||
#[serde(default = "default_enabled_nav_items")]
|
||||
pub enabled_items: Vec<String>,
|
||||
}
|
||||
@@ -878,7 +866,7 @@ impl Default for NavigationConfig {
|
||||
}
|
||||
}
|
||||
|
||||
const CURRENT_CONFIGURABLE_NAV_ITEM_IDS: &[&str] = &["plugins", "companion"];
|
||||
const CURRENT_CONFIGURABLE_NAV_ITEM_IDS: &[&str] = &[];
|
||||
|
||||
fn normalize_navigation_enabled_items(items: &[String]) -> Vec<String> {
|
||||
let mut normalized = Vec::new();
|
||||
@@ -2984,17 +2972,14 @@ mod unit_tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_workspace_preferences_preserves_current_custom_values() {
|
||||
fn test_normalize_workspace_preferences_clears_current_custom_values() {
|
||||
let mut config = Config::default();
|
||||
config.navigation.enabled_items = vec!["plugins".to_string(), "companion".to_string()];
|
||||
|
||||
let changed = config.normalize_workspace_preferences();
|
||||
|
||||
assert!(!changed);
|
||||
assert_eq!(
|
||||
config.navigation.enabled_items,
|
||||
vec!["plugins".to_string(), "companion".to_string()]
|
||||
);
|
||||
assert!(changed);
|
||||
assert!(config.navigation.enabled_items.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3031,63 +3016,7 @@ mod unit_tests {
|
||||
let changed = config.normalize_workspace_preferences();
|
||||
|
||||
assert!(changed);
|
||||
assert_eq!(
|
||||
config.navigation.enabled_items,
|
||||
vec!["plugins".to_string(), "companion".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workspace_preferences_supports_companion_defaults_roundtrip() {
|
||||
let mut config = Config::default();
|
||||
config
|
||||
.workspace_preferences
|
||||
.companion_defaults
|
||||
.general
|
||||
.preferred_provider_id = Some("deepseek".to_string());
|
||||
config
|
||||
.workspace_preferences
|
||||
.companion_defaults
|
||||
.general
|
||||
.preferred_model_id = Some("deepseek-chat".to_string());
|
||||
config
|
||||
.workspace_preferences
|
||||
.companion_defaults
|
||||
.tts
|
||||
.preferred_provider_id = Some("openai-tts".to_string());
|
||||
|
||||
let value =
|
||||
serde_json::to_value(&config).expect("config should serialize companion defaults");
|
||||
let parsed: Config =
|
||||
serde_json::from_value(value).expect("config should deserialize companion defaults");
|
||||
|
||||
assert_eq!(
|
||||
parsed
|
||||
.workspace_preferences
|
||||
.companion_defaults
|
||||
.general
|
||||
.preferred_provider_id
|
||||
.as_deref(),
|
||||
Some("deepseek")
|
||||
);
|
||||
assert_eq!(
|
||||
parsed
|
||||
.workspace_preferences
|
||||
.companion_defaults
|
||||
.general
|
||||
.preferred_model_id
|
||||
.as_deref(),
|
||||
Some("deepseek-chat")
|
||||
);
|
||||
assert_eq!(
|
||||
parsed
|
||||
.workspace_preferences
|
||||
.companion_defaults
|
||||
.tts
|
||||
.preferred_provider_id
|
||||
.as_deref(),
|
||||
Some("openai-tts")
|
||||
);
|
||||
assert!(config.navigation.enabled_items.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -201,11 +201,7 @@ impl SkillService {
|
||||
app_type: &AppType,
|
||||
_installed_states: &HashMap<String, SkillState>,
|
||||
) -> Result<Vec<Skill>> {
|
||||
self.list_local_skills_with_scope(
|
||||
app_type,
|
||||
_installed_states,
|
||||
LocalSkillCatalogScope::All,
|
||||
)
|
||||
self.list_local_skills_with_scope(app_type, _installed_states, LocalSkillCatalogScope::All)
|
||||
}
|
||||
|
||||
pub fn list_local_skills_with_scope(
|
||||
@@ -1820,11 +1816,9 @@ metadata:
|
||||
|
||||
#[test]
|
||||
fn user_scope_catalog_roots_should_exclude_project_roots() {
|
||||
let roots = SkillService::get_catalog_roots_for_scope(
|
||||
&AppType::Lime,
|
||||
LocalSkillCatalogScope::User,
|
||||
)
|
||||
.expect("resolve user skill roots");
|
||||
let roots =
|
||||
SkillService::get_catalog_roots_for_scope(&AppType::Lime, LocalSkillCatalogScope::User)
|
||||
.expect("resolve user skill roots");
|
||||
|
||||
assert!(roots
|
||||
.iter()
|
||||
|
||||
Generated
+13
-12
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "lime",
|
||||
"version": "1.65.0",
|
||||
"version": "1.66.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "lime",
|
||||
"version": "1.65.0",
|
||||
"version": "1.66.0",
|
||||
"license": "GPL-3.0-only",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
@@ -14,7 +14,7 @@
|
||||
"dependencies": {
|
||||
"@babel/standalone": "^7.29.0",
|
||||
"@floating-ui/react": "^0.27.16",
|
||||
"@limecloud/agent-runtime-client": "0.1.0",
|
||||
"@limecloud/agent-runtime-client": "0.1.1",
|
||||
"@limecloud/agent-runtime-projection": "0.1.0",
|
||||
"@limecloud/agent-runtime-ui": "0.1.0",
|
||||
"@limecloud/agent-ui-contracts": "0.1.0",
|
||||
@@ -3071,6 +3071,10 @@
|
||||
"resolved": "packages/agent-ui-contracts",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@limecloud/app-server-client": {
|
||||
"resolved": "packages/app-server-client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@limecloud/lime-cli": {
|
||||
"resolved": "packages/lime-cli-npm",
|
||||
"link": true
|
||||
@@ -7704,10 +7708,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/app-server-client": {
|
||||
"resolved": "packages/app-server-client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/appdmg": {
|
||||
"version": "0.6.6",
|
||||
"resolved": "https://registry.npmjs.org/appdmg/-/appdmg-0.6.6.tgz",
|
||||
@@ -19793,7 +19793,7 @@
|
||||
},
|
||||
"packages/agent-app-runtime": {
|
||||
"name": "@limecloud/agent-app-runtime",
|
||||
"version": "1.65.0",
|
||||
"version": "1.66.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -19801,10 +19801,10 @@
|
||||
},
|
||||
"packages/agent-runtime-client": {
|
||||
"name": "@limecloud/agent-runtime-client",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"app-server-client": "1.65.0"
|
||||
"@limecloud/app-server-client": "1.66.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -19848,7 +19848,8 @@
|
||||
}
|
||||
},
|
||||
"packages/app-server-client": {
|
||||
"version": "1.65.0",
|
||||
"name": "@limecloud/app-server-client",
|
||||
"version": "1.66.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -19856,7 +19857,7 @@
|
||||
},
|
||||
"packages/lime-cli-npm": {
|
||||
"name": "@limecloud/lime-cli",
|
||||
"version": "1.65.0",
|
||||
"version": "1.66.0",
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm64"
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "1.65.0",
|
||||
"version": "1.66.0",
|
||||
"description": "AI content workspace for Chinese creators: desktop writing, research, prompt management, knowledge base, and multi-model workflows.",
|
||||
"keywords": [
|
||||
"ai",
|
||||
@@ -223,7 +223,7 @@
|
||||
"dependencies": {
|
||||
"@babel/standalone": "^7.29.0",
|
||||
"@floating-ui/react": "^0.27.16",
|
||||
"@limecloud/agent-runtime-client": "0.1.0",
|
||||
"@limecloud/agent-runtime-client": "0.1.1",
|
||||
"@limecloud/agent-runtime-projection": "0.1.0",
|
||||
"@limecloud/agent-runtime-ui": "0.1.0",
|
||||
"@limecloud/agent-ui-contracts": "0.1.0",
|
||||
|
||||
@@ -12,7 +12,7 @@ const distDir = resolve(__dirname, 'dist');
|
||||
const workspaceAliases = [
|
||||
{ find: '@', replacement: resolve(root, 'src') },
|
||||
{
|
||||
find: 'app-server-client',
|
||||
find: '@limecloud/app-server-client',
|
||||
replacement: resolve(root, 'packages/app-server-client/src/index.ts'),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@limecloud/agent-app-runtime",
|
||||
"version": "1.65.0",
|
||||
"version": "1.66.0",
|
||||
"type": "module",
|
||||
"description": "Lime Agent App browser-side runtime SDK: bridge transport, capability adapters, and task orchestration.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# @limecloud/agent-runtime-client
|
||||
|
||||
`@limecloud/agent-runtime-client` 是 Lime Agent Runtime 的标准 TypeScript client facade。它不定义新协议,也不重建运行时;当前实现只复用 `app-server-client` 中已经存在的 App Server JSON-RPC current 主链。
|
||||
`@limecloud/agent-runtime-client` 是 Lime Agent Runtime 的标准 TypeScript client facade。它不定义新协议,也不重建运行时;当前实现只复用 `@limecloud/app-server-client` 中已经存在的 App Server JSON-RPC current 主链。
|
||||
|
||||
## Boundary
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
- 暴露 `AgentRuntimeClient` 标准接口。
|
||||
- 暴露 `createAgentRuntimeClient(...)` 工厂。
|
||||
- 暴露 browser-safe 子路径 `@limecloud/agent-runtime-client/sessionGateway`,用于把已有 App Server session gateway 适配为标准 runtime client。
|
||||
- 委托 App Server current methods:`agentSession/turn/start`、`agentSession/turn/cancel`、`agentSession/action/respond`、`agentSession/read`、`evidence/export`。
|
||||
- 订阅和分发 `agentSession/event` runtime events。
|
||||
|
||||
@@ -18,7 +19,31 @@
|
||||
- 直接渲染 UI。
|
||||
- 伪造独立 `readTask` 协议。
|
||||
|
||||
## Usage
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @limecloud/agent-runtime-client
|
||||
```
|
||||
|
||||
根入口面向 Node / host 侧 App Server transport;renderer 或浏览器 bundle 默认使用 `./sessionGateway` 子路径,避免把 sidecar / stdio 能力打进前端包。
|
||||
|
||||
## Package Mapping
|
||||
|
||||
AgentUI / AgentRuntime 当前四包链路:
|
||||
|
||||
```text
|
||||
@limecloud/agent-runtime-client
|
||||
-> App Server JSON-RPC current facade
|
||||
-> agentSession/event + agentSession/read
|
||||
-> @limecloud/agent-runtime-projection
|
||||
-> @limecloud/agent-runtime-ui
|
||||
```
|
||||
|
||||
`@limecloud/agent-runtime-client` 只拥有 transport facade。它不导出 `AgentUiProjectionState`,也不依赖 `@limecloud/agent-runtime-projection` 或 `@limecloud/agent-runtime-ui`。
|
||||
|
||||
## Root Usage
|
||||
|
||||
根入口适合 App Server connection owner 使用。它直接复用 `@limecloud/app-server-client` 的 `AppServerConnection`,不新增 runtime protocol:
|
||||
|
||||
```ts
|
||||
import {
|
||||
@@ -46,9 +71,150 @@ const thread = await runtime.readThread({ sessionId: "session-1" });
|
||||
|
||||
`readThread` 当前映射到 App Server `agentSession/read`。如果宿主需要 task 视图,应先从 session、turns 和 events 投影,不要在 client 包里伪造第二套 task protocol。
|
||||
|
||||
## Client API
|
||||
|
||||
| Method | App Server owner | Result | Rule |
|
||||
| --- | --- | --- | --- |
|
||||
| `startTurn(params, options?)` | `agentSession/turn/start` | turn start response | 只提交 runtime intent,不生成 UI state。 |
|
||||
| `cancelTurn(params, options?)` | `agentSession/turn/cancel` | cancel response | 失败直接向上抛出,不本地假设已取消。 |
|
||||
| `respondAction(params, options?)` | `agentSession/action/respond` | action response | 不乐观改 pending action,等待 runtime facts。 |
|
||||
| `readThread(params, options?)` | `agentSession/read` | session read model | 供 projection hydration / repair 使用。 |
|
||||
| `exportEvidence(params, options?)` | `evidence/export` | evidence export response | 缺 surface 时 fail closed,不伪造空 evidence。 |
|
||||
| `subscribeEvents(listener)` | `agentSession/event` | unsubscribe handle | 只分发 App Server runtime events。 |
|
||||
| `dispatchEvent(message)` | local event router | boolean | 用于现有 gateway 把 JSON-RPC notification 喂给 client。 |
|
||||
| `nextEvent(timeoutMs?)` | gateway event source | notification | 优先 gateway `nextEvent`,其次 `drainEvents`。 |
|
||||
|
||||
`options.timeoutMs` 只影响底层 App Server request / event source。超时不等于 turn 失败;宿主应再用 `readThread` 或 runtime event 修复状态。
|
||||
|
||||
## Runtime Lifecycle
|
||||
|
||||
标准 turn 生命周期由 App Server / RuntimeCore 拥有:
|
||||
|
||||
```text
|
||||
startTurn
|
||||
-> agentSession/event: turn.started / model.delta / tool.* / action.*
|
||||
-> readThread for hydration or repair
|
||||
-> respondAction / cancelTurn when user intent occurs
|
||||
-> agentSession/event: turn.completed / turn.failed / action.resolved
|
||||
-> exportEvidence when host needs replay / review package
|
||||
```
|
||||
|
||||
client 只传递 lifecycle intent 和 facts,不维护 tool 状态机、subagent lineage、Provider 参数或 UI 完成态。
|
||||
|
||||
## Browser-Safe Session Gateway
|
||||
|
||||
Renderer 宿主如果已经有自己的 App Server gateway,应从 browser-safe 子路径导入 session gateway 适配器,避免把根入口中的 Node 侧 sidecar / stdio client 打进前端包。该适配器仍返回标准 `AgentRuntimeClient`,覆盖 turn lifecycle、`readThread`、`exportEvidence`、`subscribeEvents`、`dispatchEvent` 和 `nextEvent`;宿主缺少 evidence 或 event source 时会 fail closed,不会静默回退 mock。
|
||||
|
||||
推荐传入函数式 gateway。如果你的 `AppServerClient` 是 class instance,方法内部依赖 `this.request`,不要直接裸传类方法,应在产品网关里包成闭包:
|
||||
|
||||
```ts
|
||||
import { createAgentRuntimeClientFromSessionGateway } from "@limecloud/agent-runtime-client/sessionGateway";
|
||||
|
||||
const runtime = createAgentRuntimeClientFromSessionGateway({
|
||||
startTurn: (params, options) => appServerClient.startTurn(params, options),
|
||||
readSession: (params, options) => appServerClient.readSession(params, options),
|
||||
cancelTurn: (params, options) => appServerClient.cancelTurn(params, options),
|
||||
respondAction: (params, options) => appServerClient.respondAction(params, options),
|
||||
exportEvidence: (params, options) => appServerClient.exportEvidence(params, options),
|
||||
nextEvent: (timeoutMs) => appServerClient.nextEvent(timeoutMs),
|
||||
});
|
||||
```
|
||||
|
||||
最小 lifecycle-only 宿主可以只实现 turn、read 和 action response:
|
||||
|
||||
```ts
|
||||
import type { AgentRuntimeLifecycleClient } from "@limecloud/agent-runtime-client/sessionGateway";
|
||||
|
||||
const runtime: AgentRuntimeLifecycleClient =
|
||||
createAgentRuntimeClientFromSessionGateway({
|
||||
startTurn: (params) => appServerClient.startTurn(params),
|
||||
readSession: (params) => appServerClient.readSession(params),
|
||||
cancelTurn: (params) => appServerClient.cancelTurn(params),
|
||||
respondAction: (params) => appServerClient.respondAction(params),
|
||||
});
|
||||
```
|
||||
|
||||
## Transport Contract
|
||||
|
||||
| Transport surface | Current rule |
|
||||
| --- | --- |
|
||||
| Root entry | 适合 Node / host owner,复用 `@limecloud/app-server-client` 的 `AppServerConnection`。 |
|
||||
| `./sessionGateway` | browser-safe,适合 renderer 复用已有 App Server gateway。 |
|
||||
| Electron / Desktop Host | 只能由宿主 gateway 封装,不能在本包直接 import bridge helper。 |
|
||||
| Event source | `nextEvent(timeoutMs?)` 或 `drainEvents(limit?)`,只接受 `agentSession/event` notification。 |
|
||||
| Mock / fixture | 只允许测试显式传入,不允许 production transport fallback。 |
|
||||
|
||||
如果 gateway 方法来自 class instance,必须在宿主里包成闭包,避免丢失 `this`。本包不会替宿主绑定私有 client 实例。
|
||||
|
||||
## Product App Flow
|
||||
|
||||
产品应用接入时保持这条链路:
|
||||
|
||||
```text
|
||||
Product App business context
|
||||
-> AgentRuntimeClient.startTurn / readThread / respondAction
|
||||
-> App Server agentSession/*
|
||||
-> RuntimeCore / ExecutionBackend
|
||||
-> agentSession/event + agentSession/read
|
||||
-> @limecloud/agent-runtime-projection
|
||||
-> @limecloud/agent-runtime-ui
|
||||
```
|
||||
|
||||
产品应用只负责 session 归属、workspace、业务对象 id、action callback 和页面路由;不要在产品应用里重建 turn 状态机、tool 状态机、Provider 参数拼装或 mock fallback。
|
||||
|
||||
## Error Handling
|
||||
|
||||
这个包不吞掉 transport error。调用方应按真实边界处理:
|
||||
|
||||
- bridge / network error:fail closed,提示运行时不可用。
|
||||
- App Server error:保留原始 request result 或抛错,不转换成 UI 成功态。
|
||||
- `exportEvidence` 未实现:按宿主能力缺失处理,不伪造空 evidence。
|
||||
- event source 未实现:`nextEvent` 抛错,宿主可改用 read model hydration。
|
||||
- action response 失败:保留 pending action,由宿主提示重试。
|
||||
|
||||
错误分类建议:
|
||||
|
||||
| Failure | UI expectation |
|
||||
| --- | --- |
|
||||
| bridge unavailable | runtime blocked / unavailable。 |
|
||||
| provider not ready | settings / setup action,由 App Server facts 表达。 |
|
||||
| stream interrupted | hydration `stale` / `repairing`,由 read model 修复。 |
|
||||
| action response failed | action 仍 pending,可重试。 |
|
||||
| evidence export missing | evidence panel 显示能力缺失,不伪造导出包。 |
|
||||
|
||||
## Conformance
|
||||
|
||||
最小 runtime client conformance:
|
||||
|
||||
- lifecycle:`startTurn -> readThread -> cancelTurn / respondAction` 均委托 gateway。
|
||||
- events:`subscribeEvents`、`dispatchEvent`、`nextEvent` 只处理 `agentSession/event`。
|
||||
- evidence:`exportEvidence` 有实现时委托,没有实现时 fail closed。
|
||||
- errors:transport error 原样传播,不切 mock。
|
||||
- bundle:`@limecloud/agent-runtime-client/sessionGateway` dist 不包含 Node builtin 或 `@limecloud/app-server-client` 依赖。
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm --prefix packages/app-server-client run build
|
||||
npm --prefix packages/agent-runtime-client run build
|
||||
npm --prefix packages/agent-runtime-client run test
|
||||
npm run test:contracts
|
||||
```
|
||||
|
||||
## Package Metadata
|
||||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| Runtime | Node `>=20`,ESM。 |
|
||||
| Root dependency | `@limecloud/app-server-client`。 |
|
||||
| Browser-safe export | `@limecloud/agent-runtime-client/sessionGateway`。 |
|
||||
| Side effects | `false`。 |
|
||||
| Public files | `dist`、`README.md`。 |
|
||||
| License | `MIT`。 |
|
||||
|
||||
## Do Not
|
||||
|
||||
- 不要在这里新增 JSON-RPC method name。
|
||||
- 不要在这里生成 `AgentUiProjectionState`。
|
||||
- 不要在这里导入 React、Electron bridge、renderer bridge helper 或测试 mock registry。
|
||||
- 不要把 Provider API key、Provider SDK client 或 direct HTTP 放进 runtime client。
|
||||
- 不要把旧 Agent App desktop facade 当成新 runtime owner。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@limecloud/agent-runtime-client",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"type": "module",
|
||||
"description": "Standard Agent Runtime client facade over the current App Server JSON-RPC runtime surface.",
|
||||
"license": "MIT",
|
||||
@@ -25,6 +25,10 @@
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./sessionGateway": {
|
||||
"types": "./dist/sessionGateway.d.ts",
|
||||
"import": "./dist/sessionGateway.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
@@ -38,7 +42,7 @@
|
||||
"test": "npm run build && node --test tests/*.test.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"app-server-client": "1.65.0"
|
||||
"@limecloud/app-server-client": "1.66.0"
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
|
||||
@@ -21,4 +21,10 @@ export {
|
||||
type AppServerRequestResult,
|
||||
type EvidenceExportParams,
|
||||
type EvidenceExportResponse,
|
||||
} from "app-server-client";
|
||||
} from "@limecloud/app-server-client";
|
||||
|
||||
export {
|
||||
createAgentRuntimeClientFromSessionGateway,
|
||||
type AgentRuntimeLifecycleClient,
|
||||
type AgentRuntimeSessionGateway,
|
||||
} from "./sessionGateway.js";
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
import type {
|
||||
AgentRuntimeClient,
|
||||
AgentRuntimeClientSubscription,
|
||||
AgentRuntimeEventListener,
|
||||
AgentSessionActionRespondParams,
|
||||
AgentSessionActionRespondResponse,
|
||||
AgentSessionEventNotification,
|
||||
AgentSessionReadParams,
|
||||
AgentSessionReadResponse,
|
||||
AgentSessionTurnCancelParams,
|
||||
AgentSessionTurnCancelResponse,
|
||||
AgentSessionTurnStartParams,
|
||||
AgentSessionTurnStartResponse,
|
||||
AppServerRequestOptions,
|
||||
AppServerRequestResult,
|
||||
EvidenceExportParams,
|
||||
EvidenceExportResponse,
|
||||
JsonRpcMessage,
|
||||
JsonRpcNotification,
|
||||
} from "@limecloud/app-server-client";
|
||||
|
||||
const METHOD_AGENT_SESSION_EVENT = "agentSession/event";
|
||||
|
||||
export type AgentRuntimeLifecycleClient = Pick<
|
||||
AgentRuntimeClient,
|
||||
"startTurn" | "readThread" | "cancelTurn" | "respondAction"
|
||||
>;
|
||||
|
||||
type AgentRuntimeGatewayMethod<Params, Result> = (
|
||||
params: Params,
|
||||
options?: AppServerRequestOptions,
|
||||
) => Promise<AppServerRequestResult<Result>>;
|
||||
|
||||
export type AgentRuntimeSessionGateway = {
|
||||
startTurn: AgentRuntimeGatewayMethod<
|
||||
AgentSessionTurnStartParams,
|
||||
AgentSessionTurnStartResponse
|
||||
>;
|
||||
readSession: AgentRuntimeGatewayMethod<
|
||||
AgentSessionReadParams,
|
||||
AgentSessionReadResponse
|
||||
>;
|
||||
cancelTurn: AgentRuntimeGatewayMethod<
|
||||
AgentSessionTurnCancelParams,
|
||||
AgentSessionTurnCancelResponse
|
||||
>;
|
||||
respondAction: AgentRuntimeGatewayMethod<
|
||||
AgentSessionActionRespondParams,
|
||||
AgentSessionActionRespondResponse
|
||||
>;
|
||||
exportEvidence?: AgentRuntimeGatewayMethod<
|
||||
EvidenceExportParams,
|
||||
EvidenceExportResponse
|
||||
>;
|
||||
nextEvent?(timeoutMs?: number): Promise<AgentSessionEventNotification>;
|
||||
drainEvents?(limit?: number): Promise<JsonRpcMessage[]>;
|
||||
};
|
||||
|
||||
export function createAgentRuntimeClientFromSessionGateway(
|
||||
gateway: AgentRuntimeSessionGateway,
|
||||
): AgentRuntimeClient {
|
||||
const eventRouter = new AgentRuntimeGatewayEventRouter();
|
||||
return {
|
||||
startTurn: (params, options) =>
|
||||
callAgentRuntimeSessionGateway(gateway.startTurn, params, options),
|
||||
readThread: (params, options) =>
|
||||
callAgentRuntimeSessionGateway(gateway.readSession, params, options),
|
||||
cancelTurn: (params, options) =>
|
||||
callAgentRuntimeSessionGateway(gateway.cancelTurn, params, options),
|
||||
respondAction: (params, options) =>
|
||||
callAgentRuntimeSessionGateway(gateway.respondAction, params, options),
|
||||
exportEvidence: (params, options) =>
|
||||
callOptionalAgentRuntimeSessionGateway(
|
||||
gateway.exportEvidence,
|
||||
"exportEvidence",
|
||||
params,
|
||||
options,
|
||||
),
|
||||
subscribeEvents(listener) {
|
||||
return eventRouter.subscribe(listener);
|
||||
},
|
||||
async dispatchEvent(message) {
|
||||
return await eventRouter.dispatch(message);
|
||||
},
|
||||
async nextEvent(timeoutMs) {
|
||||
if (gateway.nextEvent) {
|
||||
const notification = await gateway.nextEvent(timeoutMs);
|
||||
await eventRouter.dispatch(notification);
|
||||
return notification;
|
||||
}
|
||||
if (gateway.drainEvents) {
|
||||
return await nextDrainedAgentRuntimeEvent(
|
||||
gateway,
|
||||
eventRouter,
|
||||
timeoutMs,
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
"AgentRuntime session gateway does not expose agentSession/event subscription.",
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function callAgentRuntimeSessionGateway<Params, Result>(
|
||||
method: (
|
||||
params: Params,
|
||||
options?: AppServerRequestOptions,
|
||||
) => Promise<AppServerRequestResult<Result>>,
|
||||
params: Params,
|
||||
options?: AppServerRequestOptions,
|
||||
): Promise<AppServerRequestResult<Result>> {
|
||||
if (options === undefined) {
|
||||
return method(params);
|
||||
}
|
||||
return method(params, options);
|
||||
}
|
||||
|
||||
async function callOptionalAgentRuntimeSessionGateway<Params, Result>(
|
||||
method: AgentRuntimeGatewayMethod<Params, Result> | undefined,
|
||||
methodName: string,
|
||||
params: Params,
|
||||
options?: AppServerRequestOptions,
|
||||
): Promise<AppServerRequestResult<Result>> {
|
||||
if (!method) {
|
||||
throw new Error(
|
||||
`AgentRuntime session gateway does not expose ${methodName}.`,
|
||||
);
|
||||
}
|
||||
return callAgentRuntimeSessionGateway(method, params, options);
|
||||
}
|
||||
|
||||
async function nextDrainedAgentRuntimeEvent(
|
||||
gateway: AgentRuntimeSessionGateway,
|
||||
eventRouter: AgentRuntimeGatewayEventRouter,
|
||||
timeoutMs?: number,
|
||||
): Promise<AgentSessionEventNotification> {
|
||||
const messages = await gateway.drainEvents?.(1);
|
||||
for (const message of messages ?? []) {
|
||||
const notification = agentSessionEventNotificationFromMessage(message);
|
||||
if (notification) {
|
||||
await eventRouter.dispatch(notification);
|
||||
return notification;
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
timeoutMs === undefined
|
||||
? "AgentRuntime session gateway drainEvents did not return agentSession/event."
|
||||
: `AgentRuntime session gateway drainEvents did not return agentSession/event within ${timeoutMs}ms.`,
|
||||
);
|
||||
}
|
||||
|
||||
class AgentRuntimeGatewayEventRouter {
|
||||
readonly #listeners = new Set<AgentRuntimeEventListener>();
|
||||
|
||||
subscribe(listener: AgentRuntimeEventListener): AgentRuntimeClientSubscription {
|
||||
this.#listeners.add(listener);
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
this.#listeners.delete(listener);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async dispatch(message: JsonRpcMessage): Promise<boolean> {
|
||||
const notification = agentSessionEventNotificationFromMessage(message);
|
||||
if (!notification) {
|
||||
return false;
|
||||
}
|
||||
for (const listener of this.#listeners) {
|
||||
await listener(notification.params.event, notification);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function agentSessionEventNotificationFromMessage(
|
||||
message: JsonRpcMessage,
|
||||
): AgentSessionEventNotification | undefined {
|
||||
if (!isJsonRpcNotification(message)) {
|
||||
return undefined;
|
||||
}
|
||||
if (message.method !== METHOD_AGENT_SESSION_EVENT) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isRecord(message.params) || !("event" in message.params)) {
|
||||
return undefined;
|
||||
}
|
||||
return message as AgentSessionEventNotification;
|
||||
}
|
||||
|
||||
function isJsonRpcNotification(
|
||||
message: JsonRpcMessage,
|
||||
): message is JsonRpcNotification {
|
||||
return isRecord(message) && "method" in message && !("id" in message);
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
AppServerConnection,
|
||||
createAgentRuntimeClient,
|
||||
createAgentRuntimeClientFromSessionGateway,
|
||||
} from "../dist/index.js";
|
||||
|
||||
test("createAgentRuntimeClient is available from the standard runtime client package", async () => {
|
||||
@@ -36,3 +38,299 @@ test("createAgentRuntimeClient is available from the standard runtime client pac
|
||||
assert.equal(sent[0].method, "agentSession/turn/start");
|
||||
assert.equal(result.result.turn.turnId, "turn-1");
|
||||
});
|
||||
|
||||
test("createAgentRuntimeClientFromSessionGateway adapts an existing session gateway", async () => {
|
||||
const calls = [];
|
||||
const gateway = {
|
||||
async startTurn(params, options) {
|
||||
calls.push(["startTurn", params, options]);
|
||||
return {
|
||||
id: 1,
|
||||
result: {
|
||||
turn: {
|
||||
turnId: "turn-1",
|
||||
sessionId: params.sessionId,
|
||||
threadId: "thread-1",
|
||||
status: "accepted",
|
||||
},
|
||||
},
|
||||
response: { jsonrpc: "2.0", id: 1, result: {} },
|
||||
notifications: [],
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
async readSession(params, options) {
|
||||
calls.push(["readSession", params, options]);
|
||||
return {
|
||||
id: 2,
|
||||
result: {
|
||||
session: {
|
||||
sessionId: params.sessionId,
|
||||
threadId: "thread-1",
|
||||
status: "running",
|
||||
createdAt: "2026-05-15T00:00:00.000Z",
|
||||
updatedAt: "2026-05-15T00:00:01.000Z",
|
||||
},
|
||||
turns: [],
|
||||
},
|
||||
response: { jsonrpc: "2.0", id: 2, result: {} },
|
||||
notifications: [],
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
async cancelTurn(params, options) {
|
||||
calls.push(["cancelTurn", params, options]);
|
||||
return {
|
||||
id: 3,
|
||||
result: {},
|
||||
response: { jsonrpc: "2.0", id: 3, result: {} },
|
||||
notifications: [],
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
async respondAction(params, options) {
|
||||
calls.push(["respondAction", params, options]);
|
||||
return {
|
||||
id: 4,
|
||||
result: {},
|
||||
response: { jsonrpc: "2.0", id: 4, result: {} },
|
||||
notifications: [],
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
async exportEvidence(params, options) {
|
||||
calls.push(["exportEvidence", params, options]);
|
||||
return {
|
||||
id: 5,
|
||||
result: {
|
||||
session: {
|
||||
sessionId: params.sessionId,
|
||||
threadId: "thread-1",
|
||||
status: "completed",
|
||||
createdAt: "2026-05-15T00:00:00.000Z",
|
||||
updatedAt: "2026-05-15T00:00:02.000Z",
|
||||
},
|
||||
turns: [],
|
||||
events: [],
|
||||
artifacts: [],
|
||||
exportedAt: "2026-05-15T00:00:03.000Z",
|
||||
},
|
||||
response: { jsonrpc: "2.0", id: 5, result: {} },
|
||||
notifications: [],
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const runtime = createAgentRuntimeClientFromSessionGateway(gateway);
|
||||
const requestOptions = { timeoutMs: 120_000 };
|
||||
|
||||
await runtime.startTurn(
|
||||
{ sessionId: "session-1", input: { text: "生成草稿" } },
|
||||
requestOptions,
|
||||
);
|
||||
await runtime.readThread({ sessionId: "session-1" }, requestOptions);
|
||||
await runtime.cancelTurn(
|
||||
{ sessionId: "session-1", turnId: "turn-1" },
|
||||
requestOptions,
|
||||
);
|
||||
await runtime.respondAction(
|
||||
{
|
||||
sessionId: "session-1",
|
||||
requestId: "request-1",
|
||||
actionType: "ask_user",
|
||||
confirmed: true,
|
||||
response: "继续",
|
||||
},
|
||||
requestOptions,
|
||||
);
|
||||
await runtime.exportEvidence(
|
||||
{
|
||||
sessionId: "session-1",
|
||||
includeEvents: true,
|
||||
includeArtifacts: true,
|
||||
},
|
||||
requestOptions,
|
||||
);
|
||||
|
||||
assert.deepEqual(calls.map(([name]) => name), [
|
||||
"startTurn",
|
||||
"readSession",
|
||||
"cancelTurn",
|
||||
"respondAction",
|
||||
"exportEvidence",
|
||||
]);
|
||||
assert.equal(calls[1][1].sessionId, "session-1");
|
||||
assert.equal(calls[1][2].timeoutMs, 120_000);
|
||||
assert.equal(calls[4][1].includeEvents, true);
|
||||
});
|
||||
|
||||
test("session gateway client dispatches only App Server runtime events", async () => {
|
||||
const runtime = createAgentRuntimeClientFromSessionGateway(
|
||||
createMinimalSessionGateway(),
|
||||
);
|
||||
const received = [];
|
||||
const subscription = runtime.subscribeEvents((event, notification) => {
|
||||
received.push([event.type, notification.method]);
|
||||
});
|
||||
|
||||
const ignored = await runtime.dispatchEvent({
|
||||
method: "log/list",
|
||||
params: {},
|
||||
});
|
||||
const handled = await runtime.dispatchEvent(
|
||||
agentSessionEventNotification("turn.started"),
|
||||
);
|
||||
subscription.unsubscribe();
|
||||
await runtime.dispatchEvent(agentSessionEventNotification("turn.completed"));
|
||||
|
||||
assert.equal(ignored, false);
|
||||
assert.equal(handled, true);
|
||||
assert.deepEqual(received, [["turn.started", "agentSession/event"]]);
|
||||
});
|
||||
|
||||
test("session gateway client reads nextEvent from gateway event sources", async () => {
|
||||
const directRuntime = createAgentRuntimeClientFromSessionGateway({
|
||||
...createMinimalSessionGateway(),
|
||||
async nextEvent(timeoutMs) {
|
||||
assert.equal(timeoutMs, 500);
|
||||
return agentSessionEventNotification("tool.started");
|
||||
},
|
||||
});
|
||||
const directReceived = [];
|
||||
directRuntime.subscribeEvents((event) => {
|
||||
directReceived.push(event.type);
|
||||
});
|
||||
|
||||
const directEvent = await directRuntime.nextEvent(500);
|
||||
|
||||
assert.equal(directEvent.params.event.type, "tool.started");
|
||||
assert.deepEqual(directReceived, ["tool.started"]);
|
||||
|
||||
const drainedRuntime = createAgentRuntimeClientFromSessionGateway({
|
||||
...createMinimalSessionGateway(),
|
||||
async drainEvents(limit) {
|
||||
assert.equal(limit, 1);
|
||||
return [
|
||||
{ method: "log/list", params: {} },
|
||||
agentSessionEventNotification("action.required"),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
const drainedEvent = await drainedRuntime.nextEvent();
|
||||
|
||||
assert.equal(drainedEvent.params.event.type, "action.required");
|
||||
});
|
||||
|
||||
test("session gateway client fails closed when required runtime surfaces are missing", async () => {
|
||||
const runtime = createAgentRuntimeClientFromSessionGateway(
|
||||
createMinimalSessionGateway(),
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
runtime.exportEvidence({ sessionId: "session-1" }),
|
||||
/does not expose exportEvidence/,
|
||||
);
|
||||
await assert.rejects(runtime.nextEvent(), /does not expose agentSession\/event/);
|
||||
});
|
||||
|
||||
test("session gateway client propagates transport errors without fallback mocks", async () => {
|
||||
const runtime = createAgentRuntimeClientFromSessionGateway({
|
||||
...createMinimalSessionGateway(),
|
||||
async startTurn() {
|
||||
throw new Error("bridge offline");
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
runtime.startTurn({ sessionId: "session-1", input: { text: "生成草稿" } }),
|
||||
/bridge offline/,
|
||||
);
|
||||
});
|
||||
|
||||
test("sessionGateway subpath remains browser-safe", async () => {
|
||||
const source = await readFile(
|
||||
new URL("../dist/sessionGateway.js", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert.equal(source.includes("node:"), false);
|
||||
assert.equal(source.includes("app-server-client"), false);
|
||||
assert.equal(source.includes("@limecloud/app-server-client"), false);
|
||||
});
|
||||
|
||||
function createMinimalSessionGateway() {
|
||||
return {
|
||||
async startTurn(params) {
|
||||
return {
|
||||
id: 1,
|
||||
result: {
|
||||
turn: {
|
||||
turnId: "turn-1",
|
||||
sessionId: params.sessionId,
|
||||
threadId: "thread-1",
|
||||
status: "accepted",
|
||||
},
|
||||
},
|
||||
response: { jsonrpc: "2.0", id: 1, result: {} },
|
||||
notifications: [],
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
async readSession(params) {
|
||||
return {
|
||||
id: 2,
|
||||
result: {
|
||||
session: {
|
||||
sessionId: params.sessionId,
|
||||
threadId: "thread-1",
|
||||
status: "running",
|
||||
createdAt: "2026-05-15T00:00:00.000Z",
|
||||
updatedAt: "2026-05-15T00:00:01.000Z",
|
||||
},
|
||||
turns: [],
|
||||
},
|
||||
response: { jsonrpc: "2.0", id: 2, result: {} },
|
||||
notifications: [],
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
async cancelTurn() {
|
||||
return {
|
||||
id: 3,
|
||||
result: {},
|
||||
response: { jsonrpc: "2.0", id: 3, result: {} },
|
||||
notifications: [],
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
async respondAction() {
|
||||
return {
|
||||
id: 4,
|
||||
result: {},
|
||||
response: { jsonrpc: "2.0", id: 4, result: {} },
|
||||
notifications: [],
|
||||
messages: [],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function agentSessionEventNotification(type) {
|
||||
return {
|
||||
method: "agentSession/event",
|
||||
params: {
|
||||
event: {
|
||||
eventId: `event-${type}`,
|
||||
sequence: 1,
|
||||
sessionId: "session-1",
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
type,
|
||||
timestamp: "2026-05-15T00:00:02.000Z",
|
||||
payload: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
这个包只包含纯 TypeScript 类型和函数,不依赖 React,不调用 JSON-RPC,不访问 Electron IPC,也不绑定任何具体业务模块。
|
||||
|
||||
当前物理包名是 `@limecloud/agent-runtime-projection`。历史规划中出现过的 `@limecloud/agent-ui-projection` 只能作为未来 alias 或文档历史引用,不能写成 current owner。
|
||||
|
||||
## Standard Package Layering
|
||||
|
||||
Agent UI / Runtime 的标准链路由四个包共同组成:
|
||||
@@ -23,7 +25,7 @@ Agent UI / Runtime 的标准链路由四个包共同组成:
|
||||
- `@limecloud/agent-runtime-projection` 只做 headless projection 和 selectors,不接触宿主、网络、React 或业务对象。
|
||||
- `@limecloud/agent-runtime-ui` 只消费 projection state 渲染标准 React primitives,不订阅运行时。
|
||||
|
||||
宿主负责把 App Server 事件适配为标准 execution events,再按 `AgentRuntimeClient -> executionEvents -> projectAgentUiState -> AgentUiProjectionView` 或自己的 presentation adapter 渲染。宿主可以保留本地化、点击路由、业务卡片和页面布局;不得重新实现 scope 匹配、latest index、summary selector 或运行时事实解释。
|
||||
宿主负责把 App Server 事件适配为标准 execution events,再按 `AgentRuntimeClient -> projectAgentUiState -> AgentUiProjectionView` 或自己的 presentation adapter 渲染;如果需要展开细节,中间事件链路是 `AgentRuntimeClient -> executionEvents -> projectAgentUiState -> AgentUiProjectionView`。宿主可以保留本地化、点击路由、业务卡片和页面布局;不得重新实现 scope 匹配、latest index、summary selector 或运行时事实解释。
|
||||
|
||||
## Source Layout
|
||||
|
||||
@@ -31,6 +33,7 @@ Agent UI / Runtime 的标准链路由四个包共同组成:
|
||||
|
||||
```text
|
||||
src/actions.ts -> HITL action.required / action.resolved projection helpers
|
||||
src/appServerFacts.ts -> App Server agentSession/read、agentSession/event、evidence/export facts -> execution events replay
|
||||
src/artifactEvents.ts -> artifact_snapshot projection helpers
|
||||
src/contextEvents.ts -> context_trace / turn_context projection helpers
|
||||
src/conversationEvents.ts -> message / text delta / reasoning delta projection helpers
|
||||
@@ -48,6 +51,7 @@ src/refs.ts -> artifact refs 等跨宿主引用提取
|
||||
src/routing.ts -> routing run.status / decision payload projection helpers
|
||||
src/runtimeFacts.ts -> runtime entity/status/phase/topology 标准事实解释
|
||||
src/summary.ts -> summary / surface / lane selectors
|
||||
src/subagents.ts -> Subagents 标准模型 selector
|
||||
src/subagentStatusEvents.ts -> subagent_status_changed team / handoff / worker notification projection helpers
|
||||
src/threadItems.ts -> thread item -> AgentUiProjectionEvent builders / action item builders / subagent activity builders / task owner metadata parser
|
||||
src/toolEvents.ts -> tool_start / tool_end / tool_progress / tool delta -> AgentUiProjectionEvent builders
|
||||
@@ -72,8 +76,10 @@ npm install @limecloud/agent-ui-contracts @limecloud/agent-runtime-projection
|
||||
- 用 `action.resolved` 标记已处理 action。
|
||||
- 聚合 artifact refs、evidence refs、task refs、source count。
|
||||
- 给 UI 提供 `UIMessageParts`、`ProcessTimeline`、`ExecutionGraph`、`actions`、`tools` 和 `readModel` 等标准渲染对象。
|
||||
- 给 UI 提供 `subagents` 标准模型,统一表达子代理线程、委派调用、活动摘要和隔离摘要,避免 React 组件重新解释子代理事实。
|
||||
- 为 `AgentUiProjectionEvent` 提供 host-neutral 的索引、scope selector 和 latest selector,宿主 store 只负责持久化和订阅。
|
||||
- 为 `AgentUiProjectionEvent` 提供 host-neutral 的 summary selector,包括 action / task / artifact / evidence / diagnostics 计数、notable latest events、Team Workbench surface / lane 聚合和 artifact latest lookup。
|
||||
- 为 `AgentUiProjectionEvent` 提供 host-neutral 的 summary selector,包括 action / task / artifact / evidence / diagnostics 计数、notable latest events、Subagents surface 聚合和 artifact latest lookup。
|
||||
- 为 App Server `agentSession/read`、`agentSession/event`、`evidence/export` facts 提供标准 execution events replay adapter。
|
||||
- 为宿主事件 adapter 提供 host-neutral 的 action event builder、artifact snapshot builder、context trace / turn context builder、conversation event builder、diagnostic / cost metric builder、historical hydration builder、plan approval metadata parser / builder、runtime permission builder、queue event builder、routing status builder、runtime lifecycle / runtime team / model routing / task profile builder、subagent status changed builder、thread item builder、thread item action builder、subagent activity / worker notification builder、tool lifecycle builder、TaskUpdate owner metadata parser、event base 字段、sequence 编排、字段规整、artifact refs 提取、routing decision payload、runtime entity/status/phase/topology 和 worker usage 解释函数。
|
||||
|
||||
这个包不负责:
|
||||
@@ -86,6 +92,17 @@ npm install @limecloud/agent-ui-contracts @limecloud/agent-runtime-projection
|
||||
|
||||
## Usage
|
||||
|
||||
优先按输入来源选择入口:
|
||||
|
||||
| 输入来源 | 推荐入口 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 已有 normalized `AgentRuntimeExecutionEvent[]` | `projectAgentUiState` | 新 UI 默认入口,输出 message / timeline / graph / action / read model。 |
|
||||
| 只需要旧事实栏 | `projectAgentRuntimeReadModel` | 兼容入口,不适合作为新 surface 的唯一状态。 |
|
||||
| 既有产品已有 transcript messages + read model | `projectAgentUiStateFromSessionSnapshot` | 迁移期入口,把旧消息转成 `UIMessageParts`,同时复用 read model source events 生成 timeline / graph / refs / actions。 |
|
||||
| App Server `agentSession/read` + `agentSession/event` + `evidence/export` facts | `replayAppServerFacts` | 不创建 client,只把已取得 facts replay 成标准 projection。 |
|
||||
| contracts fixture | `replayAgentUiFixture` | 下游包和产品接入测试使用。 |
|
||||
| 宿主私有事件 shape | `buildAgentUi*Event` helpers | 在 adapter 边界转成标准 projection event。 |
|
||||
|
||||
```ts
|
||||
import {
|
||||
projectAgentUiState,
|
||||
@@ -124,6 +141,57 @@ const uiState = projectAgentUiState({
|
||||
console.log(uiState.messages, uiState.timeline, uiState.graph);
|
||||
```
|
||||
|
||||
最小产品接入通常长这样:
|
||||
|
||||
```ts
|
||||
import {
|
||||
projectAgentUiState,
|
||||
replayAppServerFacts,
|
||||
} from "@limecloud/agent-runtime-projection";
|
||||
|
||||
const replay = replayAppServerFacts({
|
||||
readModel: await runtimeClient.readThread({ sessionId }),
|
||||
events: drainedAgentSessionEvents,
|
||||
evidenceExport: exportedEvidence,
|
||||
});
|
||||
|
||||
const state = projectAgentUiState({
|
||||
executionEvents: replay.events,
|
||||
sourceCount: replay.state.readModel.sourceCount,
|
||||
});
|
||||
```
|
||||
|
||||
已有产品如果已经把消息 transcript 和 runtime read model 分开维护,不要在页面里继续拼 `AgentTimeline + RuntimeFactsPanel` 两套 surface。先在 adapter 边界合成标准 state,再交给 `AgentUiProjectionView`:
|
||||
|
||||
```ts
|
||||
import {
|
||||
projectAgentUiStateFromSessionSnapshot,
|
||||
} from "@limecloud/agent-runtime-projection";
|
||||
|
||||
const state = projectAgentUiStateFromSessionSnapshot({
|
||||
messages: session.messages.map((message) => ({
|
||||
id: message.id,
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
createdAt: message.createdAt,
|
||||
})),
|
||||
readModel: projectAgentRuntimeReadModel({
|
||||
executionEvents: session.executionEvents,
|
||||
sourceCount: session.sourceCount,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
如果宿主已经维护自己的 event store,应把 store 中的可见事件交给 selector,而不是在 UI 组件里重新筛选:
|
||||
|
||||
```ts
|
||||
const visibleEvents = selectAgentUiProjectionEventsForScopeFromStore(store, {
|
||||
sessionId,
|
||||
turnId,
|
||||
});
|
||||
const summary = summarizeAgentUiProjectionEvents(visibleEvents);
|
||||
```
|
||||
|
||||
## Event Shape
|
||||
|
||||
`AgentRuntimeExecutionEvent` 是跨应用的最小公共事件形状。业务应用可以使用自己的 session 类型,只要 execution event 字段兼容即可:
|
||||
@@ -157,11 +225,64 @@ interface AppSession {
|
||||
- `graph`: 面向任务、run、step、tool 和 action 的 `ExecutionGraph`。
|
||||
- `tools`: 工具事件组。
|
||||
- `actions`: 待用户处理的 action。
|
||||
- `subagents`: 面向 subagent / handoff / review 的标准模型,包括 `threads`、`delegationCalls`、`activities`、`activeThreadIds`、`completedThreadIds`、`failedThreadIds` 和轻量 `isolation` 摘要。
|
||||
- `readModel`: 兼容既有事实栏的 read model。
|
||||
- `runtime`: 当前运行状态。
|
||||
- `hydration`: 宿主应用判断快照 / 实时流状态的水位信息。
|
||||
|
||||
`projectAgentRuntimeReadModel` 仍然保留,用于只需要事实栏或旧组件的宿主;新 UI 默认使用 `projectAgentUiState`。
|
||||
`projectAgentRuntimeReadModel` 仍然保留,用于只需要事实栏或旧组件的宿主;新 UI 默认使用 `projectAgentUiState`。如果宿主正在从旧 transcript UI 迁移,使用 `projectAgentUiStateFromSessionSnapshot` 作为过渡入口;它只接受消息快照和 read model,不读取 Provider、不生成 runtime truth,也不替代 App Server facts。
|
||||
|
||||
`AgentUiProjectionState` 输出字段的使用边界:
|
||||
|
||||
- `messages`: 交给 `UIMessagePartsView` 或宿主消息流。
|
||||
- `timeline`: 交给 `ProcessTimelineView`,表达工具、action、artifact、diagnostic 的线性过程。
|
||||
- `graph`: 交给 `ExecutionGraphView`,表达 run / turn / task / tool / subagent 的结构关系。
|
||||
- `actions`: 交给 `ActionRequiredList`,只表达可响应的人类操作。
|
||||
- `subagents`: 交给 `SubagentsView` 或宿主自己的子代理 presentation adapter;业务组件不能从 assistant 正文或局部 UI state 推断 subagent / handoff / review 状态。
|
||||
- `runtime`: 顶部或侧栏运行状态,不作为 Provider 状态事实源。
|
||||
- `hydration`: 宿主判断 snapshot / realtime stream 是否 stale,不用于业务结果判断。
|
||||
|
||||
## Projection Lifecycle
|
||||
|
||||
Projection 的生命周期固定为 headless state machine,不绑定 React:
|
||||
|
||||
```text
|
||||
hydrate(read model / fixture / App Server facts)
|
||||
-> apply runtime events by id + sequence
|
||||
-> derive messages / timeline / graph / actions / tools
|
||||
-> derive subagents from runtime events
|
||||
-> expose hydration status
|
||||
```
|
||||
|
||||
标准行为:
|
||||
|
||||
| Step | Rule |
|
||||
| --- | --- |
|
||||
| Hydrate | `hydrate(input)` 用新的 event 集合重建 state,不复用 React local state。 |
|
||||
| Apply | `apply(event)` 按 event id 幂等追加,重复事件不改变 state。 |
|
||||
| Sequence | projection 按输入顺序保留 timeline,fixture validation 负责发现非法 sequence gap。 |
|
||||
| Stream merge | `model.delta` / `reasoning.*` 合并为稳定 `UIMessagePart`,final text 不重复追加。 |
|
||||
| Snapshot repair | snapshot / read model 只能修复可见状态,不能让 UI 猜测 tool/action/artifact 终态。 |
|
||||
| Subagents model | `subagents` 由 projection 构建;React 和产品应用不能再私有过滤 graph/read model。 |
|
||||
| Reset | `reset()` 清空 events、read model 和 ephemeral UI。 |
|
||||
|
||||
当前 `hydration.status` 最小支持 `idle` 和 `live`;`stale`、`repairing`、`degraded` 是 contracts 预留的标准状态,必须由 runtime/read model repair 事实驱动,不能由 UI timeout 猜测。
|
||||
|
||||
## Selectors And State API
|
||||
|
||||
推荐按用途选择 API:
|
||||
|
||||
| API | 用途 |
|
||||
| --- | --- |
|
||||
| `projectAgentUiState` | 一次性把 events 投影成完整 UI state。 |
|
||||
| `createAgentUiProjector` | 持有 headless projector,适合 event stream apply。 |
|
||||
| `projectAgentRuntimeReadModel` | 兼容事实栏或旧 surface。 |
|
||||
| `buildAgentUiSubagentsModel` | 从 execution events 构建标准 Subagents 模型。 |
|
||||
| `indexAgentUiProjectionEvents` | 为宿主外部 event store 建 latest / scope index。 |
|
||||
| `selectAgentUiProjectionEventsForScopeFromStore` | 按 session/thread/run/turn/task scope 取可见事件。 |
|
||||
| `summarizeAgentUiProjectionEvents` | 生成 host-neutral summary,不进 React。 |
|
||||
|
||||
这些 API 都是纯函数或纯 TypeScript 对象,不会发起 JSON-RPC,不会访问 Provider,也不会读写业务 store。
|
||||
|
||||
## Event Store Selectors
|
||||
|
||||
@@ -190,7 +311,7 @@ const visible = selectAgentUiProjectionEventsForScopeFromStore(state, {
|
||||
const summary = summarizeAgentUiProjectionEvents(visible);
|
||||
```
|
||||
|
||||
不要在业务宿主里重新实现 `session / thread / run / turn / task` scope 匹配、latest event 索引、artifact / evidence / action lookup、事件类型分组、Team Workbench surface / lane 聚合或 notable event summary。否则不同 App 会逐步长出不同的 Agent UI 解释口径。
|
||||
不要在业务宿主里重新实现 `session / thread / run / turn / task` scope 匹配、latest event 索引、artifact / evidence / action lookup、事件类型分组、Subagents 聚合或 notable event summary。否则不同 App 会逐步长出不同的 Agent UI 解释口径。
|
||||
|
||||
也不要在业务宿主里重新实现 `buildAgentUiActionRequiredEvent`、`buildAgentUiActionResolvedEvent`、`buildAgentUiArtifactSnapshotEvent`、`buildAgentUiContextTraceEvent`、`buildAgentUiTurnContextEvents`、`buildAgentUiMessageSnapshotEvent`、`buildAgentUiTextDeltaEvent`、`buildAgentUiReasoningDeltaEvent`、`buildAgentUiHistoricalHydrationEvents`、`buildAgentUiPlanApprovalRequiredEvent`、`buildAgentUiPlanApprovalResolvedEvent`、`extractAgentUiPlanApprovalProjection`、`extractAgentUiPlanApprovalResponseProjection`、`buildAgentUiRuntimePermissionChangedEvent`、`buildAgentUiQueueAddedEvents`、`buildAgentUiQueueLifecycleEvents`、`buildAgentUiRoutingStatusEvent`、`buildAgentUiThreadStartedEvent`、`buildAgentUiRunStartedEvent`、`buildAgentUiRunFinishedEvent`、`buildAgentUiRunFailedEvent`、`buildAgentUiRuntimeStatusEvent`、`buildAgentUiRuntimeTeamChangedEvent`、`buildAgentUiModelChangeEvent`、`buildAgentUiTaskProfileResolvedEvent`、`buildAgentUiSubagentStatusChangedEvents`、`buildAgentUiThreadItemEvent`、`buildAgentUiThreadItemActionEvent`、`buildAgentUiThreadItemSubagentActivityEvent`、`buildAgentUiThreadItemSubagentWorkerNotificationEvent`、`buildAgentUiThreadItemBase`、`buildAgentUiToolStartEvents`、`buildAgentUiToolEndEvent`、`buildAgentUiToolEndEvents`、`buildAgentUiToolProgressEvent`、`buildAgentUiToolOutputDeltaEvent`、`buildAgentUiToolInputDeltaEvent`、`extractAgentUiTaskOwnerChangeProjection`、`buildAgentUiProjectionBase`、`sequenceAgentUiProjectionEvents`、`definedString`、`truncateText`、`metadataKeys`、`extractArtifactRefs`、`buildRoutingDecisionPayload`、`buildWorkerUsageProjection`、runtime entity/status/phase/topology 这类通用事实解释。宿主 adapter 只把自己的事件 shape 映射到标准 projection event,通用规整与事实解释必须回到本包。
|
||||
|
||||
@@ -210,10 +331,75 @@ App Server JSON-RPC
|
||||
|
||||
不要让 UI 组件直接调用 JSON-RPC。这样主聊天、工作台和后续独立 App 可以共享同一套投影逻辑,同时保留自己的后端连接方式。
|
||||
|
||||
如果宿主已经拿到 App Server current facts,可以直接用纯 adapter 进入标准 projection:
|
||||
|
||||
```ts
|
||||
import { replayAppServerFacts } from "@limecloud/agent-runtime-projection";
|
||||
|
||||
const result = replayAppServerFacts({
|
||||
readModel: sessionReadResult,
|
||||
events: drainedAgentSessionEvents,
|
||||
evidenceExport: evidenceExportResult,
|
||||
});
|
||||
|
||||
console.log(result.state.messages, result.state.timeline, result.state.graph);
|
||||
```
|
||||
|
||||
`replayAppServerFacts` 只消费已取得的结构化 facts,不创建 `AppServerClient`,不订阅 JSON-RPC,也不直读数据库。`waitingAction` 会被表达为 `action.required` + waiting runtime state;artifact 和 evidence export 只通过 refs 暴露大输出,避免 UI 从 assistant 正文或 provider 原始响应猜测事实状态。
|
||||
|
||||
`replayAppServerFacts` 的输入建议:
|
||||
|
||||
- `readModel`: `AgentRuntimeClient.readThread(...)` 或 App Server `agentSession/read` 的 result。
|
||||
- `events`: App Server `agentSession/event` notification 中的 `params.event` 列表。
|
||||
- `evidenceExport`: App Server `evidence/export` 的 result,只提供 evidence pack refs 和 artifact refs。
|
||||
|
||||
不要把 JSON-RPC envelope、provider stream chunk 或 assistant Markdown 原文直接传给 projection;这些应该先在 runtime / host adapter 边界规整成 facts。
|
||||
|
||||
宿主应用如果把 App Server 的 `artifact.snapshot` 物化成本地业务产物,例如 Prompt 草稿、文章版本或素材交付物,应只把最终业务产物对应的 `artifact.changed` 传给投影层。上游快照可以保留在 provider 原始事件或诊断 payload 中,不要再作为第二个可见 artifact 事件传入,否则 UI 会把“中间快照”和“最终业务产物”渲染成两个并列产物。
|
||||
|
||||
共享投影层不会按 `artifactId` 或 `turnId` 自动合并多条 artifact 事件,因为多产物会话是合法场景。是否折叠、隐藏中间快照或提升最终产物,由宿主的业务适配层决定。
|
||||
|
||||
## Fixture Replay
|
||||
|
||||
下游包和产品接入测试应复用 contracts fixtures:
|
||||
|
||||
```ts
|
||||
import { getAgentUiFixture } from "@limecloud/agent-ui-contracts";
|
||||
import { replayAgentUiFixture } from "@limecloud/agent-runtime-projection";
|
||||
|
||||
const fixture = getAgentUiFixture("subagent-handoff");
|
||||
const replay = replayAgentUiFixture(fixture);
|
||||
|
||||
expect(replay.state.graph.length).toBeGreaterThan(0);
|
||||
expect(replay.state.subagents.threads.length).toBeGreaterThan(0);
|
||||
expect(replay.state.subagents.delegationCalls.length).toBeGreaterThan(0);
|
||||
```
|
||||
|
||||
fixture replay 的目的不是模拟后端,而是证明同一份标准 facts 在 projection 和 UI 层得到一致解释。
|
||||
|
||||
## Host Adapter Pattern
|
||||
|
||||
宿主私有事件应只在 adapter 边界被翻译一次:
|
||||
|
||||
```ts
|
||||
import { buildAgentUiToolStartEvents } from "@limecloud/agent-runtime-projection";
|
||||
|
||||
export function fromHostToolStarted(event: HostToolStarted) {
|
||||
return buildAgentUiToolStartEvents({
|
||||
id: event.id,
|
||||
sequence: event.sequence,
|
||||
sessionId: event.sessionId,
|
||||
threadId: event.threadId,
|
||||
runId: event.turnId,
|
||||
toolCallId: event.callId,
|
||||
toolName: event.name,
|
||||
createdAt: event.timestamp,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
adapter 可以读取宿主字段,但不能在组件里继续解释 tool lifecycle、subagent lineage、handoff status 或 action decision。
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
@@ -222,6 +408,14 @@ npm --prefix packages/agent-runtime-projection run test
|
||||
npm --prefix packages/agent-runtime-projection pack --dry-run
|
||||
```
|
||||
|
||||
## Do Not
|
||||
|
||||
- 不要在本包里创建 `AppServerClient`、调用 renderer bridge helper、订阅 Electron IPC 或发 HTTP。
|
||||
- 不要把 Provider stream chunk、API key、HTTP response、DB row 直接暴露给 UI。
|
||||
- 不要在产品应用里复制 `projectAgentUiState`、scope selector、summary selector 或 `runtimeFacts` 解释逻辑。
|
||||
- 不要恢复旧树形过程术语作为 projection 标准;标准输出是 `UIMessageParts`、`ProcessTimeline`、`ExecutionGraph`。
|
||||
- 不要把业务 artifact 折叠策略写进共享 projection;业务物化和展示优先级由宿主 adapter 决定。
|
||||
|
||||
## Publish
|
||||
|
||||
首次发布到 npmjs:
|
||||
|
||||
@@ -0,0 +1,641 @@
|
||||
import type {
|
||||
AgentRuntimeExecutionEvent,
|
||||
AgentRuntimeExecutionEventKind,
|
||||
AgentRuntimeExecutionEventStatus,
|
||||
AgentUiProjectionState,
|
||||
} from "@limecloud/agent-ui-contracts";
|
||||
|
||||
import {
|
||||
compactProjectionFields,
|
||||
definedString,
|
||||
readNumberField,
|
||||
readRecord,
|
||||
readStringArrayField,
|
||||
readStringField,
|
||||
} from "./normalization.js";
|
||||
import { projectAgentUiState } from "./uiState.js";
|
||||
|
||||
export interface AppServerAgentSessionFact {
|
||||
sessionId: string;
|
||||
threadId: string;
|
||||
appId?: string;
|
||||
workspaceId?: string;
|
||||
status?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface AppServerAgentTurnFact {
|
||||
turnId: string;
|
||||
sessionId?: string;
|
||||
threadId?: string;
|
||||
status?: string;
|
||||
startedAt?: string;
|
||||
completedAt?: string;
|
||||
}
|
||||
|
||||
export interface AppServerAgentEventFact {
|
||||
eventId: string;
|
||||
sequence?: number;
|
||||
sessionId?: string;
|
||||
threadId?: string;
|
||||
turnId?: string;
|
||||
type: string;
|
||||
timestamp?: string;
|
||||
payload?: unknown;
|
||||
}
|
||||
|
||||
export interface AppServerArtifactSummaryFact {
|
||||
artifactRef?: string;
|
||||
eventId?: string;
|
||||
sequence?: number;
|
||||
turnId?: string;
|
||||
artifactId?: string;
|
||||
path?: string;
|
||||
title?: string;
|
||||
kind?: string;
|
||||
status?: string;
|
||||
contentStatus?: string;
|
||||
metadata?: unknown;
|
||||
}
|
||||
|
||||
export interface AppServerEvidencePackSummaryFact {
|
||||
packRelativeRoot?: string;
|
||||
packAbsoluteRoot?: string;
|
||||
exportedAt?: string;
|
||||
threadStatus?: string;
|
||||
latestTurnStatus?: string;
|
||||
turnCount?: number;
|
||||
itemCount?: number;
|
||||
pendingRequestCount?: number;
|
||||
queuedTurnCount?: number;
|
||||
recentArtifactCount?: number;
|
||||
knownGaps?: string[];
|
||||
artifacts?: AppServerEvidencePackArtifactFact[];
|
||||
}
|
||||
|
||||
export interface AppServerEvidencePackArtifactFact {
|
||||
kind?: string;
|
||||
title?: string;
|
||||
relativePath?: string;
|
||||
absolutePath?: string;
|
||||
bytes?: number;
|
||||
}
|
||||
|
||||
export interface AppServerSessionReadFacts {
|
||||
session: AppServerAgentSessionFact;
|
||||
turns?: AppServerAgentTurnFact[];
|
||||
detail?: unknown;
|
||||
}
|
||||
|
||||
export interface AppServerEvidenceExportFacts {
|
||||
session: AppServerAgentSessionFact;
|
||||
turns?: AppServerAgentTurnFact[];
|
||||
events?: AppServerAgentEventFact[];
|
||||
artifacts?: AppServerArtifactSummaryFact[];
|
||||
exportedAt?: string;
|
||||
evidencePack?: AppServerEvidencePackSummaryFact;
|
||||
}
|
||||
|
||||
export interface AppServerFactsReplayInput {
|
||||
readModel?: AppServerSessionReadFacts;
|
||||
events?: AppServerAgentEventFact[];
|
||||
evidenceExport?: AppServerEvidenceExportFacts;
|
||||
sourceCount?: number;
|
||||
}
|
||||
|
||||
export interface AppServerFactsProjectionResult {
|
||||
events: AgentRuntimeExecutionEvent[];
|
||||
state: AgentUiProjectionState<AgentRuntimeExecutionEvent>;
|
||||
diagnostics: string[];
|
||||
}
|
||||
|
||||
export function projectAppServerEventsToExecutionEvents(
|
||||
events: readonly AppServerAgentEventFact[] | undefined,
|
||||
): AgentRuntimeExecutionEvent[] {
|
||||
return dedupeExecutionEvents(
|
||||
(events ?? []).map(projectAppServerEventToExecutionEvent),
|
||||
);
|
||||
}
|
||||
|
||||
export function projectAppServerSessionReadToExecutionEvents(
|
||||
readModel: AppServerSessionReadFacts,
|
||||
): AgentRuntimeExecutionEvent[] {
|
||||
const session = readModel.session;
|
||||
const turns = readModel.turns ?? [];
|
||||
const events: AgentRuntimeExecutionEvent[] = [];
|
||||
|
||||
events.push(
|
||||
compactProjectionFields({
|
||||
id: `appserver:session:${session.sessionId}:snapshot`,
|
||||
schemaVersion: "lime-runtime-event/v0.1",
|
||||
kind: "state",
|
||||
status: statusFromSessionStatus(session.status),
|
||||
eventClass: "snapshot.updated",
|
||||
runtimeId: "app-server",
|
||||
threadId: session.threadId,
|
||||
title: "Session snapshot updated",
|
||||
detail: session.status,
|
||||
payload: compactProjectionFields({
|
||||
sessionId: session.sessionId,
|
||||
appId: session.appId,
|
||||
workspaceId: session.workspaceId,
|
||||
status: session.status,
|
||||
turnCount: turns.length,
|
||||
}),
|
||||
createdAt:
|
||||
definedString(session.updatedAt) ??
|
||||
definedString(session.createdAt) ??
|
||||
defaultTimestamp(),
|
||||
} satisfies AgentRuntimeExecutionEvent),
|
||||
);
|
||||
|
||||
turns.forEach((turn, index) => {
|
||||
events.push(projectAppServerTurnToExecutionEvent(turn, session, index));
|
||||
});
|
||||
|
||||
return dedupeExecutionEvents(events);
|
||||
}
|
||||
|
||||
export function projectAppServerEvidenceExportToExecutionEvents(
|
||||
exportFacts: AppServerEvidenceExportFacts,
|
||||
): AgentRuntimeExecutionEvent[] {
|
||||
const events = [
|
||||
...projectAppServerEventsToExecutionEvents(exportFacts.events),
|
||||
...projectAppServerSessionReadToExecutionEvents({
|
||||
session: exportFacts.session,
|
||||
turns: exportFacts.turns,
|
||||
}),
|
||||
...projectAppServerArtifactsToExecutionEvents(
|
||||
exportFacts.artifacts,
|
||||
exportFacts.session,
|
||||
exportFacts.exportedAt,
|
||||
),
|
||||
...projectAppServerEvidencePackToExecutionEvents(
|
||||
exportFacts.evidencePack,
|
||||
exportFacts.session,
|
||||
exportFacts.exportedAt,
|
||||
),
|
||||
];
|
||||
|
||||
return dedupeExecutionEvents(events);
|
||||
}
|
||||
|
||||
export function replayAppServerFacts(
|
||||
input: AppServerFactsReplayInput,
|
||||
): AppServerFactsProjectionResult {
|
||||
const diagnostics: string[] = [];
|
||||
const events = [
|
||||
...(input.readModel
|
||||
? projectAppServerSessionReadToExecutionEvents(input.readModel)
|
||||
: []),
|
||||
...projectAppServerEventsToExecutionEvents(input.events),
|
||||
...(input.evidenceExport
|
||||
? projectAppServerEvidenceExportToExecutionEvents(input.evidenceExport)
|
||||
: []),
|
||||
];
|
||||
const dedupedEvents = dedupeExecutionEvents(events).sort(compareEvents);
|
||||
|
||||
if (dedupedEvents.length === 0) {
|
||||
diagnostics.push("app_server_facts_empty");
|
||||
}
|
||||
|
||||
return {
|
||||
events: dedupedEvents,
|
||||
state: projectAgentUiState({
|
||||
executionEvents: dedupedEvents,
|
||||
sourceCount: input.sourceCount,
|
||||
}),
|
||||
diagnostics,
|
||||
};
|
||||
}
|
||||
|
||||
function projectAppServerEventToExecutionEvent(
|
||||
event: AppServerAgentEventFact,
|
||||
): AgentRuntimeExecutionEvent {
|
||||
const payload = readRecord(event.payload) ?? {};
|
||||
const eventClass = normalizeEventClass(event.type);
|
||||
const text = readText(payload);
|
||||
const actionId = readStringField(payload, [
|
||||
"request_id",
|
||||
"requestId",
|
||||
"action_id",
|
||||
"actionId",
|
||||
"id",
|
||||
]);
|
||||
const toolCallId = readStringField(payload, [
|
||||
"toolCallId",
|
||||
"tool_call_id",
|
||||
"toolId",
|
||||
"tool_id",
|
||||
"id",
|
||||
]);
|
||||
const artifactRefs = normalizeRefs([
|
||||
...readStringArrayField(payload, ["artifactRefs", "artifact_refs"]),
|
||||
readStringField(payload, ["artifactRef", "artifact_ref"]),
|
||||
readStringField(payload, ["artifactId", "artifact_id"]),
|
||||
]);
|
||||
const evidenceRefs = normalizeRefs([
|
||||
...readStringArrayField(payload, ["evidenceRefs", "evidence_refs"]),
|
||||
readStringField(payload, ["evidenceRef", "evidence_ref"]),
|
||||
readStringField(payload, ["evidenceId", "evidence_id"]),
|
||||
]);
|
||||
|
||||
return compactProjectionFields({
|
||||
id: `appserver:${event.eventId}`,
|
||||
schemaVersion: "lime-runtime-event/v0.1",
|
||||
kind: kindForEventClass(eventClass),
|
||||
status: statusForEventClass(eventClass),
|
||||
eventClass,
|
||||
runtimeId: "app-server",
|
||||
threadId: event.threadId ?? event.sessionId,
|
||||
turnId: event.turnId,
|
||||
taskId: readStringField(payload, ["taskId", "task_id"]),
|
||||
subagentId: readStringField(payload, ["subagentId", "subagent_id"]),
|
||||
runId: readStringField(payload, ["runId", "run_id"]),
|
||||
stepId: readStringField(payload, ["stepId", "step_id"]),
|
||||
toolCallId: isToolEvent(eventClass) ? toolCallId : undefined,
|
||||
actionId: isActionEvent(eventClass) ? actionId : undefined,
|
||||
artifactId:
|
||||
readStringField(payload, ["artifactId", "artifact_id"]) ??
|
||||
artifactRefs[0],
|
||||
evidenceId:
|
||||
readStringField(payload, ["evidenceId", "evidence_id"]) ??
|
||||
evidenceRefs[0],
|
||||
sequence: event.sequence,
|
||||
phase: phaseForEventClass(eventClass),
|
||||
owner: ownerForEventClass(eventClass),
|
||||
title: titleForEvent(eventClass, payload),
|
||||
detail: detailForEvent(eventClass, payload, text),
|
||||
refIds: normalizeRefs(readStringArrayField(payload, ["refIds", "ref_ids"])),
|
||||
artifactRefs,
|
||||
evidenceRefs,
|
||||
payload: compactProjectionFields({
|
||||
...payload,
|
||||
sourceType: event.type,
|
||||
sessionId: event.sessionId,
|
||||
text,
|
||||
messageId: readMessageId(payload),
|
||||
actionKind: readStringField(payload, ["action_type", "actionType"]),
|
||||
controls: controlsForAction(payload),
|
||||
}),
|
||||
createdAt: event.timestamp ?? defaultTimestamp(),
|
||||
completedAt: isCompletedEvent(eventClass) ? event.timestamp : undefined,
|
||||
} satisfies AgentRuntimeExecutionEvent);
|
||||
}
|
||||
|
||||
function projectAppServerTurnToExecutionEvent(
|
||||
turn: AppServerAgentTurnFact,
|
||||
session: AppServerAgentSessionFact,
|
||||
index: number,
|
||||
): AgentRuntimeExecutionEvent {
|
||||
const eventClass = eventClassForTurnStatus(turn.status);
|
||||
return compactProjectionFields({
|
||||
id: `appserver:turn:${turn.turnId}:status:${turn.status ?? "unknown"}`,
|
||||
schemaVersion: "lime-runtime-event/v0.1",
|
||||
kind: "state",
|
||||
status: statusFromTurnStatus(turn.status),
|
||||
eventClass,
|
||||
runtimeId: "app-server",
|
||||
threadId: turn.threadId ?? session.threadId,
|
||||
turnId: turn.turnId,
|
||||
sequence: index + 1,
|
||||
phase: phaseForEventClass(eventClass),
|
||||
title: titleForTurnStatus(turn.status),
|
||||
detail: turn.status,
|
||||
payload: compactProjectionFields({
|
||||
sessionId: turn.sessionId ?? session.sessionId,
|
||||
nativeStatus: turn.status,
|
||||
}),
|
||||
createdAt:
|
||||
definedString(turn.completedAt) ??
|
||||
definedString(turn.startedAt) ??
|
||||
definedString(session.updatedAt) ??
|
||||
defaultTimestamp(),
|
||||
completedAt: turn.completedAt,
|
||||
} satisfies AgentRuntimeExecutionEvent);
|
||||
}
|
||||
|
||||
function projectAppServerArtifactsToExecutionEvents(
|
||||
artifacts: readonly AppServerArtifactSummaryFact[] | undefined,
|
||||
session: AppServerAgentSessionFact,
|
||||
exportedAt: string | undefined,
|
||||
): AgentRuntimeExecutionEvent[] {
|
||||
return (artifacts ?? [])
|
||||
.filter((artifact) => definedString(artifact.artifactRef))
|
||||
.map((artifact) =>
|
||||
compactProjectionFields({
|
||||
id: `appserver:artifact:${artifact.artifactRef}`,
|
||||
schemaVersion: "lime-runtime-event/v0.1",
|
||||
kind: "draft",
|
||||
owner: "artifact",
|
||||
status: artifact.status === "failed" ? "failed" : "completed",
|
||||
eventClass: "artifact.changed",
|
||||
runtimeId: "app-server",
|
||||
threadId: session.threadId,
|
||||
turnId: artifact.turnId,
|
||||
artifactId: artifact.artifactId ?? artifact.artifactRef,
|
||||
artifactRefs: normalizeRefs([artifact.artifactRef]),
|
||||
sequence: artifact.sequence,
|
||||
title: artifact.title ?? "Artifact changed",
|
||||
detail: artifact.path ?? artifact.kind,
|
||||
payload: compactProjectionFields({
|
||||
artifactRef: artifact.artifactRef,
|
||||
path: artifact.path,
|
||||
kind: artifact.kind,
|
||||
contentStatus: artifact.contentStatus,
|
||||
sourceEventId: artifact.eventId,
|
||||
metadata: artifact.metadata,
|
||||
}),
|
||||
createdAt: exportedAt ?? defaultTimestamp(),
|
||||
completedAt: exportedAt,
|
||||
} satisfies AgentRuntimeExecutionEvent),
|
||||
);
|
||||
}
|
||||
|
||||
function projectAppServerEvidencePackToExecutionEvents(
|
||||
evidencePack: AppServerEvidencePackSummaryFact | undefined,
|
||||
session: AppServerAgentSessionFact,
|
||||
exportedAt: string | undefined,
|
||||
): AgentRuntimeExecutionEvent[] {
|
||||
const packRoot = definedString(evidencePack?.packRelativeRoot);
|
||||
if (!packRoot) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
compactProjectionFields({
|
||||
id: `appserver:evidence:${packRoot}`,
|
||||
schemaVersion: "lime-runtime-event/v0.1",
|
||||
kind: "evidence",
|
||||
owner: "evidence",
|
||||
status: "completed",
|
||||
eventClass: "evidence.changed",
|
||||
runtimeId: "app-server",
|
||||
threadId: session.threadId,
|
||||
evidenceId: packRoot,
|
||||
evidenceRefs: [packRoot],
|
||||
title: "Evidence pack exported",
|
||||
detail: packRoot,
|
||||
payload: compactProjectionFields({
|
||||
packRelativeRoot: evidencePack?.packRelativeRoot,
|
||||
packAbsoluteRoot: evidencePack?.packAbsoluteRoot,
|
||||
exportedAt: evidencePack?.exportedAt ?? exportedAt,
|
||||
threadStatus: evidencePack?.threadStatus,
|
||||
latestTurnStatus: evidencePack?.latestTurnStatus,
|
||||
turnCount: evidencePack?.turnCount,
|
||||
itemCount: evidencePack?.itemCount,
|
||||
pendingRequestCount: evidencePack?.pendingRequestCount,
|
||||
queuedTurnCount: evidencePack?.queuedTurnCount,
|
||||
recentArtifactCount: evidencePack?.recentArtifactCount,
|
||||
knownGaps: evidencePack?.knownGaps,
|
||||
artifacts: evidencePack?.artifacts,
|
||||
}),
|
||||
createdAt: evidencePack?.exportedAt ?? exportedAt ?? defaultTimestamp(),
|
||||
completedAt: evidencePack?.exportedAt ?? exportedAt,
|
||||
} satisfies AgentRuntimeExecutionEvent),
|
||||
];
|
||||
}
|
||||
|
||||
function normalizeEventClass(type: string): string {
|
||||
if (type === "message.delta_batch" || type === "message.batch") {
|
||||
return "model.delta";
|
||||
}
|
||||
if (type === "message.delta") return "model.delta";
|
||||
if (
|
||||
type === "message" ||
|
||||
type === "message.completed" ||
|
||||
type === "item.completed"
|
||||
) {
|
||||
return "model.completed";
|
||||
}
|
||||
if (type === "thinking.delta") return "reasoning.delta";
|
||||
if (type === "artifact.snapshot") return "artifact.changed";
|
||||
if (type === "runtime.status") return "run.status";
|
||||
if (type === "turn.done" || type === "turn.final_done") {
|
||||
return "turn.completed";
|
||||
}
|
||||
if (type === "turn.cancelled") return "turn.canceled";
|
||||
return type;
|
||||
}
|
||||
|
||||
function kindForEventClass(
|
||||
eventClass: string,
|
||||
): AgentRuntimeExecutionEventKind {
|
||||
if (eventClass.startsWith("model.")) return "model";
|
||||
if (eventClass.startsWith("reasoning.")) return "note";
|
||||
if (eventClass.startsWith("tool.")) return "tool";
|
||||
if (eventClass.startsWith("action.")) return "action";
|
||||
if (eventClass.startsWith("artifact.")) return "draft";
|
||||
if (eventClass.startsWith("evidence.")) return "evidence";
|
||||
if (
|
||||
eventClass.startsWith("subagent.") ||
|
||||
eventClass.startsWith("handoff.") ||
|
||||
eventClass.startsWith("review.") ||
|
||||
eventClass.startsWith("task.")
|
||||
) {
|
||||
return "handoff";
|
||||
}
|
||||
return "state";
|
||||
}
|
||||
|
||||
function statusForEventClass(
|
||||
eventClass: string,
|
||||
): AgentRuntimeExecutionEventStatus {
|
||||
if (eventClass.endsWith(".failed") || eventClass === "runtime.error") {
|
||||
return "failed";
|
||||
}
|
||||
if (eventClass === "action.required" || eventClass === "handoff.requested") {
|
||||
return "pending";
|
||||
}
|
||||
if (
|
||||
eventClass.endsWith(".completed") ||
|
||||
eventClass.endsWith(".resolved") ||
|
||||
eventClass.endsWith(".result") ||
|
||||
eventClass.endsWith(".changed") ||
|
||||
eventClass.endsWith(".verdict")
|
||||
) {
|
||||
return "completed";
|
||||
}
|
||||
if (eventClass === "turn.canceled") return "failed";
|
||||
return "running";
|
||||
}
|
||||
|
||||
function statusFromSessionStatus(
|
||||
status: string | undefined,
|
||||
): AgentRuntimeExecutionEventStatus {
|
||||
if (status === "completed" || status === "idle") return "completed";
|
||||
if (status === "failed" || status === "canceled") return "failed";
|
||||
if (status === "waitingAction") return "blocked";
|
||||
if (status === "running") return "running";
|
||||
return "pending";
|
||||
}
|
||||
|
||||
function statusFromTurnStatus(
|
||||
status: string | undefined,
|
||||
): AgentRuntimeExecutionEventStatus {
|
||||
if (status === "completed") return "completed";
|
||||
if (status === "failed" || status === "canceled") return "failed";
|
||||
if (status === "waitingAction") return "pending";
|
||||
if (status === "running" || status === "accepted") return "running";
|
||||
return "pending";
|
||||
}
|
||||
|
||||
function eventClassForTurnStatus(status: string | undefined): string {
|
||||
if (status === "completed") return "turn.completed";
|
||||
if (status === "failed") return "turn.failed";
|
||||
if (status === "canceled") return "turn.canceled";
|
||||
if (status === "waitingAction") return "action.required";
|
||||
if (status === "running" || status === "accepted") return "turn.started";
|
||||
return "turn.submitted";
|
||||
}
|
||||
|
||||
function phaseForEventClass(eventClass: string): string {
|
||||
if (eventClass === "action.required") return "action_required";
|
||||
if (eventClass.endsWith(".failed") || eventClass === "runtime.error") {
|
||||
return "failed";
|
||||
}
|
||||
if (
|
||||
eventClass.endsWith(".completed") ||
|
||||
eventClass.endsWith(".resolved") ||
|
||||
eventClass.endsWith(".result") ||
|
||||
eventClass.endsWith(".changed")
|
||||
) {
|
||||
return "completed";
|
||||
}
|
||||
if (eventClass.startsWith("tool.")) return "tool_running";
|
||||
if (eventClass.startsWith("model.")) return "streaming";
|
||||
return "preparing";
|
||||
}
|
||||
|
||||
function ownerForEventClass(eventClass: string): string {
|
||||
if (eventClass.startsWith("tool.")) return "runtime";
|
||||
if (eventClass.startsWith("artifact.")) return "artifact";
|
||||
if (eventClass.startsWith("evidence.")) return "evidence";
|
||||
return "runtime";
|
||||
}
|
||||
|
||||
function titleForEvent(
|
||||
eventClass: string,
|
||||
payload: Record<string, unknown>,
|
||||
): string {
|
||||
return (
|
||||
readStringField(payload, ["title", "name", "toolName", "tool_name"]) ??
|
||||
eventClass
|
||||
);
|
||||
}
|
||||
|
||||
function titleForTurnStatus(status: string | undefined): string {
|
||||
if (status === "completed") return "Turn completed";
|
||||
if (status === "failed") return "Turn failed";
|
||||
if (status === "canceled") return "Turn canceled";
|
||||
if (status === "waitingAction") return "Action required";
|
||||
if (status === "queued") return "Turn queued";
|
||||
return "Turn started";
|
||||
}
|
||||
|
||||
function detailForEvent(
|
||||
eventClass: string,
|
||||
payload: Record<string, unknown>,
|
||||
text: string | undefined,
|
||||
): string | undefined {
|
||||
if (eventClass.startsWith("model.") || eventClass.startsWith("reasoning.")) {
|
||||
return text;
|
||||
}
|
||||
return readStringField(payload, ["detail", "message", "error", "reason"]);
|
||||
}
|
||||
|
||||
function readText(payload: Record<string, unknown>): string | undefined {
|
||||
const direct = readStringField(payload, ["text", "delta", "message"]);
|
||||
if (direct) return direct;
|
||||
|
||||
const message = readRecord(payload.message);
|
||||
const messageText = readStringField(message, ["text", "delta", "message"]);
|
||||
if (messageText) return messageText;
|
||||
|
||||
const content = Array.isArray(message?.content)
|
||||
? message.content
|
||||
: Array.isArray(payload.content)
|
||||
? payload.content
|
||||
: [];
|
||||
return content
|
||||
.map((part) => readStringField(readRecord(part), ["text", "content"]))
|
||||
.filter((value): value is string => Boolean(value))
|
||||
.join("");
|
||||
}
|
||||
|
||||
function readMessageId(payload: Record<string, unknown>): string | undefined {
|
||||
const message = readRecord(payload.message);
|
||||
return (
|
||||
readStringField(payload, ["messageId", "message_id"]) ??
|
||||
readStringField(message, ["id", "messageId", "message_id"])
|
||||
);
|
||||
}
|
||||
|
||||
function controlsForAction(payload: Record<string, unknown>): string[] {
|
||||
const controls = readStringArrayField(payload, ["controls"]);
|
||||
if (controls.length > 0) return controls;
|
||||
|
||||
const actionType = readStringField(payload, ["action_type", "actionType"]);
|
||||
if (actionType === "ask_user" || actionType === "elicitation") {
|
||||
return ["answer"];
|
||||
}
|
||||
if (actionType === "tool_confirmation") {
|
||||
return ["approve", "reject"];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function isToolEvent(eventClass: string): boolean {
|
||||
return eventClass.startsWith("tool.");
|
||||
}
|
||||
|
||||
function isActionEvent(eventClass: string): boolean {
|
||||
return eventClass.startsWith("action.");
|
||||
}
|
||||
|
||||
function isCompletedEvent(eventClass: string): boolean {
|
||||
return (
|
||||
statusForEventClass(eventClass) === "completed" ||
|
||||
statusForEventClass(eventClass) === "failed"
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeRefs(values: Array<string | undefined>): string[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
values
|
||||
.map((value) => definedString(value))
|
||||
.filter((value): value is string => Boolean(value)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function dedupeExecutionEvents(
|
||||
events: readonly AgentRuntimeExecutionEvent[],
|
||||
): AgentRuntimeExecutionEvent[] {
|
||||
const byId = new Map<string, AgentRuntimeExecutionEvent>();
|
||||
events.forEach((event) => {
|
||||
byId.set(event.id, event);
|
||||
});
|
||||
return Array.from(byId.values());
|
||||
}
|
||||
|
||||
function compareEvents(
|
||||
left: AgentRuntimeExecutionEvent,
|
||||
right: AgentRuntimeExecutionEvent,
|
||||
): number {
|
||||
const leftSequence = readNumberField({ sequence: left.sequence }, ["sequence"]);
|
||||
const rightSequence = readNumberField(
|
||||
{ sequence: right.sequence },
|
||||
["sequence"],
|
||||
);
|
||||
if (leftSequence !== undefined && rightSequence !== undefined) {
|
||||
return leftSequence - rightSequence;
|
||||
}
|
||||
return left.createdAt.localeCompare(right.createdAt);
|
||||
}
|
||||
|
||||
function defaultTimestamp(): string {
|
||||
return "1970-01-01T00:00:00.000Z";
|
||||
}
|
||||
@@ -15,7 +15,9 @@ export type {
|
||||
AgentRuntimeReadModel,
|
||||
AgentRuntimeSurface,
|
||||
AgentUiControl,
|
||||
AgentUiArtifactRefView,
|
||||
AgentUiDiagnosticView,
|
||||
AgentUiEvidenceRefView,
|
||||
AgentUiEventClass,
|
||||
AgentUiHydrationStatus,
|
||||
AgentUiOwner,
|
||||
@@ -32,6 +34,11 @@ export type {
|
||||
AgentUiRuntimeStatus,
|
||||
AgentUiRuntimeStatusView,
|
||||
AgentUiScope,
|
||||
AgentUiSubagentActivityView,
|
||||
AgentUiSubagentDelegationView,
|
||||
AgentUiSubagentIsolationView,
|
||||
AgentUiSubagentsModel,
|
||||
AgentUiSubagentThreadView,
|
||||
AgentUiSurface,
|
||||
AgentUiTopology,
|
||||
ExecutionGraph,
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
collectAgentUiFixtureValidationIssues,
|
||||
type AgentUiContractValidationIssue,
|
||||
type AgentUiFixture,
|
||||
type AgentUiProjectionState,
|
||||
type AgentRuntimeExecutionEvent,
|
||||
} from "@limecloud/agent-ui-contracts";
|
||||
import { projectAgentUiState } from "./uiState.js";
|
||||
|
||||
export interface AgentUiFixtureReplayResult<
|
||||
TEvent extends AgentRuntimeExecutionEvent = AgentRuntimeExecutionEvent,
|
||||
> {
|
||||
fixtureId: string;
|
||||
state: AgentUiProjectionState<TEvent>;
|
||||
validationIssues: AgentUiContractValidationIssue[];
|
||||
diagnostics: string[];
|
||||
passed: boolean;
|
||||
}
|
||||
|
||||
export function replayAgentUiFixture<
|
||||
TEvent extends AgentRuntimeExecutionEvent = AgentRuntimeExecutionEvent,
|
||||
>(
|
||||
fixture: AgentUiFixture<TEvent>,
|
||||
): AgentUiFixtureReplayResult<TEvent> {
|
||||
const validationIssues = collectAgentUiFixtureValidationIssues(fixture);
|
||||
const state = projectAgentUiState<TEvent>({
|
||||
executionEvents: fixture.events,
|
||||
});
|
||||
const diagnostics = collectReplayDiagnostics(fixture, state, validationIssues);
|
||||
|
||||
return {
|
||||
fixtureId: fixture.id,
|
||||
state,
|
||||
validationIssues,
|
||||
diagnostics,
|
||||
passed: validationIssues.length === 0 && diagnostics.length === 0,
|
||||
};
|
||||
}
|
||||
|
||||
function collectReplayDiagnostics<
|
||||
TEvent extends AgentRuntimeExecutionEvent,
|
||||
>(
|
||||
fixture: AgentUiFixture<TEvent>,
|
||||
state: AgentUiProjectionState<TEvent>,
|
||||
validationIssues: AgentUiContractValidationIssue[],
|
||||
): string[] {
|
||||
const diagnostics: string[] = validationIssues.map((issue) => issue.code);
|
||||
const expected = fixture.expected;
|
||||
|
||||
if (expected.messagePartCount !== undefined && state.messages.length < expected.messagePartCount) {
|
||||
diagnostics.push("message_parts_below_expected");
|
||||
}
|
||||
if (expected.timelineEntryCount !== undefined && state.timeline.length < expected.timelineEntryCount) {
|
||||
diagnostics.push("timeline_entries_below_expected");
|
||||
}
|
||||
if (expected.graphNodeCount !== undefined && state.graph.length < expected.graphNodeCount) {
|
||||
diagnostics.push("graph_nodes_below_expected");
|
||||
}
|
||||
if (expected.pendingActionCount !== undefined && state.readModel.pendingActions.length !== expected.pendingActionCount) {
|
||||
diagnostics.push("pending_actions_mismatch");
|
||||
}
|
||||
if (expected.artifactCount !== undefined && state.artifacts.length < expected.artifactCount) {
|
||||
diagnostics.push("artifacts_below_expected");
|
||||
}
|
||||
if (expected.evidenceCount !== undefined && state.evidence.length < expected.evidenceCount) {
|
||||
diagnostics.push("evidence_below_expected");
|
||||
}
|
||||
diagnostics.push(...collectSubagentsReplayDiagnostics(fixture, state));
|
||||
|
||||
return diagnostics.filter(
|
||||
(diagnostic) => !(expected.diagnostics ?? []).includes(diagnostic),
|
||||
);
|
||||
}
|
||||
|
||||
function collectSubagentsReplayDiagnostics<
|
||||
TEvent extends AgentRuntimeExecutionEvent,
|
||||
>(
|
||||
fixture: AgentUiFixture<TEvent>,
|
||||
state: AgentUiProjectionState<TEvent>,
|
||||
): string[] {
|
||||
const expected = fixture.expected.subagents;
|
||||
if (!expected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const diagnostics: string[] = [];
|
||||
const model = state.subagents;
|
||||
if (
|
||||
expected.hasSubagents !== undefined
|
||||
&& model.hasSubagents !== expected.hasSubagents
|
||||
) {
|
||||
diagnostics.push("subagents_surface_mismatch");
|
||||
}
|
||||
if (
|
||||
expected.threadCount !== undefined
|
||||
&& model.threads.length < expected.threadCount
|
||||
) {
|
||||
diagnostics.push("subagent_threads_below_expected");
|
||||
}
|
||||
if (
|
||||
expected.delegationCallCount !== undefined
|
||||
&& model.delegationCalls.length < expected.delegationCallCount
|
||||
) {
|
||||
diagnostics.push("subagent_delegation_calls_below_expected");
|
||||
}
|
||||
if (
|
||||
expected.activityCount !== undefined
|
||||
&& model.activities.length < expected.activityCount
|
||||
) {
|
||||
diagnostics.push("subagent_activities_below_expected");
|
||||
}
|
||||
if (
|
||||
expected.activeThreadCount !== undefined
|
||||
&& model.activeThreadIds.length !== expected.activeThreadCount
|
||||
) {
|
||||
diagnostics.push("subagent_active_threads_mismatch");
|
||||
}
|
||||
if (
|
||||
expected.completedThreadCount !== undefined
|
||||
&& model.completedThreadIds.length !== expected.completedThreadCount
|
||||
) {
|
||||
diagnostics.push("subagent_completed_threads_mismatch");
|
||||
}
|
||||
if (
|
||||
expected.failedThreadCount !== undefined
|
||||
&& model.failedThreadIds.length !== expected.failedThreadCount
|
||||
) {
|
||||
diagnostics.push("subagent_failed_threads_mismatch");
|
||||
}
|
||||
return diagnostics;
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
export type * from "./contracts.js";
|
||||
export * from "./actions.js";
|
||||
export * from "./appServerFacts.js";
|
||||
export * from "./artifactEvents.js";
|
||||
export * from "./contextEvents.js";
|
||||
export * from "./conversationEvents.js";
|
||||
export * from "./diagnosticEvents.js";
|
||||
export * from "./envelope.js";
|
||||
export * from "./eventStore.js";
|
||||
export * from "./fixtureReplay.js";
|
||||
export * from "./hydrationEvents.js";
|
||||
export * from "./lifecycle.js";
|
||||
export * from "./normalization.js";
|
||||
@@ -16,6 +18,7 @@ export * from "./refs.js";
|
||||
export * from "./routing.js";
|
||||
export * from "./runtimeFacts.js";
|
||||
export * from "./summary.js";
|
||||
export * from "./subagents.js";
|
||||
export * from "./subagentStatusEvents.js";
|
||||
export * from "./threadItems.js";
|
||||
export * from "./toolEvents.js";
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
export * from "./contracts.js";
|
||||
export * from "./actions.js";
|
||||
export * from "./appServerFacts.js";
|
||||
export * from "./artifactEvents.js";
|
||||
export * from "./contextEvents.js";
|
||||
export * from "./conversationEvents.js";
|
||||
export * from "./diagnosticEvents.js";
|
||||
export * from "./envelope.js";
|
||||
export * from "./eventStore.js";
|
||||
export * from "./fixtureReplay.js";
|
||||
export * from "./hydrationEvents.js";
|
||||
export * from "./lifecycle.js";
|
||||
export * from "./normalization.js";
|
||||
@@ -16,6 +18,7 @@ export * from "./refs.js";
|
||||
export * from "./routing.js";
|
||||
export * from "./runtimeFacts.js";
|
||||
export * from "./summary.js";
|
||||
export * from "./subagents.js";
|
||||
export * from "./subagentStatusEvents.js";
|
||||
export * from "./threadItems.js";
|
||||
export * from "./toolEvents.js";
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
export type * from "./contracts.js";
|
||||
export * from "./actions.js";
|
||||
export * from "./appServerFacts.js";
|
||||
export * from "./artifactEvents.js";
|
||||
export * from "./contextEvents.js";
|
||||
export * from "./conversationEvents.js";
|
||||
export * from "./diagnosticEvents.js";
|
||||
export * from "./envelope.js";
|
||||
export * from "./eventStore.js";
|
||||
export * from "./fixtureReplay.js";
|
||||
export * from "./hydrationEvents.js";
|
||||
export * from "./lifecycle.js";
|
||||
export * from "./normalization.js";
|
||||
@@ -16,6 +18,7 @@ export * from "./refs.js";
|
||||
export * from "./routing.js";
|
||||
export * from "./runtimeFacts.js";
|
||||
export * from "./summary.js";
|
||||
export * from "./subagents.js";
|
||||
export * from "./subagentStatusEvents.js";
|
||||
export * from "./threadItems.js";
|
||||
export * from "./toolEvents.js";
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
import type {
|
||||
AgentRuntimeExecutionEvent,
|
||||
AgentUiSubagentActivityView,
|
||||
AgentUiSubagentDelegationView,
|
||||
AgentUiSubagentIsolationView,
|
||||
AgentUiSubagentsModel,
|
||||
AgentUiSubagentThreadView,
|
||||
} from "@limecloud/agent-ui-contracts";
|
||||
|
||||
function isRecord(input: unknown): input is Record<string, unknown> {
|
||||
return typeof input === "object" && input !== null && !Array.isArray(input);
|
||||
}
|
||||
|
||||
function payloadString(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
...keys: string[]
|
||||
): string | undefined {
|
||||
const payload = event.payload;
|
||||
if (!isRecord(payload)) return undefined;
|
||||
for (const key of keys) {
|
||||
const value = payload[key];
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function payloadNumber(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
...keys: string[]
|
||||
): number | undefined {
|
||||
const payload = event.payload;
|
||||
if (!isRecord(payload)) return undefined;
|
||||
for (const key of keys) {
|
||||
const value = payload[key];
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function payloadBoolean(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
...keys: string[]
|
||||
): boolean | undefined {
|
||||
const payload = event.payload;
|
||||
if (!isRecord(payload)) return undefined;
|
||||
for (const key of keys) {
|
||||
const value = payload[key];
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function safePreview(value: string | undefined): string | undefined {
|
||||
if (!value) return undefined;
|
||||
return value.length > 280 ? `${value.slice(0, 277)}...` : value;
|
||||
}
|
||||
|
||||
function compact<T extends object>(input: T): T {
|
||||
return Object.fromEntries(
|
||||
Object.entries(input).filter(([, value]) => value !== undefined),
|
||||
) as T;
|
||||
}
|
||||
|
||||
function subagentThreadId(event: AgentRuntimeExecutionEvent): string | undefined {
|
||||
return (
|
||||
event.subagentId ??
|
||||
event.workerId ??
|
||||
payloadString(
|
||||
event,
|
||||
"subagentId",
|
||||
"agentThreadId",
|
||||
"childThreadId",
|
||||
"childSessionId",
|
||||
"session_id",
|
||||
"targetThreadId",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function parentThreadId(event: AgentRuntimeExecutionEvent): string | undefined {
|
||||
return (
|
||||
payloadString(
|
||||
event,
|
||||
"parentThreadId",
|
||||
"parent_thread_id",
|
||||
"parentSessionId",
|
||||
"parent_session_id",
|
||||
) ?? event.threadId
|
||||
);
|
||||
}
|
||||
|
||||
function isSubagentEvent(event: AgentRuntimeExecutionEvent): boolean {
|
||||
const eventClass = event.eventClass ?? "";
|
||||
return Boolean(
|
||||
subagentThreadId(event) ||
|
||||
eventClass.startsWith("subagent.") ||
|
||||
eventClass.startsWith("handoff.") ||
|
||||
eventClass === "agent.spawned" ||
|
||||
eventClass === "agent.completed",
|
||||
);
|
||||
}
|
||||
|
||||
function isDelegationEvent(event: AgentRuntimeExecutionEvent): boolean {
|
||||
const eventClass = event.eventClass ?? "";
|
||||
return (
|
||||
eventClass.startsWith("subagent.") ||
|
||||
eventClass.startsWith("handoff.") ||
|
||||
eventClass === "agent.spawned" ||
|
||||
eventClass === "agent.handoff"
|
||||
);
|
||||
}
|
||||
|
||||
function isTerminalStatus(status: string): boolean {
|
||||
return status === "completed" || status === "failed";
|
||||
}
|
||||
|
||||
function activityKind(event: AgentRuntimeExecutionEvent): string {
|
||||
const eventClass = event.eventClass ?? "";
|
||||
if (eventClass.includes("started") || eventClass === "agent.spawned") {
|
||||
return "started";
|
||||
}
|
||||
if (eventClass.includes("completed") || eventClass === "agent.completed") {
|
||||
return "completed";
|
||||
}
|
||||
if (eventClass.includes("failed")) return "failed";
|
||||
if (eventClass.startsWith("handoff.")) return "handoff";
|
||||
if (eventClass.startsWith("review.")) return "review";
|
||||
if (eventClass.startsWith("tool.") || eventClass.startsWith("model.")) {
|
||||
return "interacted";
|
||||
}
|
||||
return "updated";
|
||||
}
|
||||
|
||||
function delegationAction(event: AgentRuntimeExecutionEvent): string {
|
||||
const eventClass = event.eventClass ?? "";
|
||||
if (eventClass.startsWith("handoff.") || eventClass === "agent.handoff") {
|
||||
return "handoff";
|
||||
}
|
||||
if (eventClass.includes("wait")) return "wait";
|
||||
if (eventClass.includes("interrupt")) return "interrupt";
|
||||
if (eventClass.includes("close")) return "close";
|
||||
if (eventClass.includes("send") || eventClass.includes("input")) {
|
||||
return "send_input";
|
||||
}
|
||||
return "spawn";
|
||||
}
|
||||
|
||||
function isolationForEvent(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
): AgentUiSubagentIsolationView | undefined {
|
||||
const isolation = compact<AgentUiSubagentIsolationView>({
|
||||
runtimeProfileId: payloadString(event, "runtimeProfileId", "runtime_profile_id"),
|
||||
modelProfileId: payloadString(event, "modelProfileId", "model_profile_id"),
|
||||
isolationProfileId: payloadString(
|
||||
event,
|
||||
"isolationProfileId",
|
||||
"isolation_profile_id",
|
||||
),
|
||||
workspaceRef: payloadString(event, "workspaceRef", "workspace_ref", "cwdRef"),
|
||||
permissionProfile: payloadString(
|
||||
event,
|
||||
"permissionProfile",
|
||||
"permission_profile",
|
||||
),
|
||||
sandboxProfile: payloadString(event, "sandboxProfile", "sandbox_profile"),
|
||||
forkPolicy: payloadString(event, "forkPolicy", "fork_policy"),
|
||||
depth: payloadNumber(event, "depth", "subagentDepth"),
|
||||
canDelegate: payloadBoolean(event, "canDelegate", "can_delegate"),
|
||||
});
|
||||
return Object.keys(isolation).length ? isolation : undefined;
|
||||
}
|
||||
|
||||
function mergeThread(
|
||||
current: AgentUiSubagentThreadView | undefined,
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
threadId: string,
|
||||
): AgentUiSubagentThreadView {
|
||||
const sourceEventIds = new Set([
|
||||
...(current?.sourceEventIds ?? []),
|
||||
event.id,
|
||||
]);
|
||||
const artifactRefs = new Set([
|
||||
...(current?.artifactRefs ?? []),
|
||||
...(event.artifactRefs ?? []),
|
||||
]);
|
||||
const evidenceRefs = new Set([
|
||||
...(current?.evidenceRefs ?? []),
|
||||
...(event.evidenceRefs ?? []),
|
||||
]);
|
||||
return compact<AgentUiSubagentThreadView>({
|
||||
threadId,
|
||||
subagentId: event.subagentId ?? current?.subagentId ?? threadId,
|
||||
parentThreadId: current?.parentThreadId ?? parentThreadId(event),
|
||||
parentTaskId:
|
||||
current?.parentTaskId ??
|
||||
payloadString(event, "parentTaskId", "parent_task_id"),
|
||||
taskId: current?.taskId ?? event.taskId,
|
||||
taskPath:
|
||||
current?.taskPath ??
|
||||
payloadString(event, "taskPath", "task_path", "agentPath", "agent_path"),
|
||||
role:
|
||||
current?.role ??
|
||||
payloadString(event, "role", "agentRole", "agent_role"),
|
||||
nickname:
|
||||
current?.nickname ??
|
||||
payloadString(event, "nickname", "agentNickname", "agent_nickname"),
|
||||
status: event.status,
|
||||
title: current?.title ?? event.title,
|
||||
summary:
|
||||
payloadString(event, "summary", "resultSummary", "outputSummary") ??
|
||||
current?.summary,
|
||||
promptPreview:
|
||||
current?.promptPreview ??
|
||||
safePreview(
|
||||
payloadString(event, "promptPreview", "prompt_preview", "prompt", "input"),
|
||||
),
|
||||
lastActivityAt: event.completedAt ?? event.createdAt ?? current?.lastActivityAt,
|
||||
createdAt: current?.createdAt ?? event.createdAt,
|
||||
completedAt: event.completedAt ?? (isTerminalStatus(event.status) ? event.createdAt : current?.completedAt),
|
||||
artifactRefs: Array.from(artifactRefs),
|
||||
evidenceRefs: Array.from(evidenceRefs),
|
||||
sourceEventIds: Array.from(sourceEventIds),
|
||||
isolation: current?.isolation ?? isolationForEvent(event),
|
||||
});
|
||||
}
|
||||
|
||||
function delegationForEvent(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
threadId: string,
|
||||
): AgentUiSubagentDelegationView {
|
||||
const parentId = parentThreadId(event);
|
||||
return compact<AgentUiSubagentDelegationView>({
|
||||
callId:
|
||||
event.handoffId ??
|
||||
payloadString(event, "callId", "delegationCallId") ??
|
||||
`${parentId ?? "root"}:delegation:${threadId}`,
|
||||
sourceEventId: event.id,
|
||||
action: delegationAction(event),
|
||||
parentThreadId: parentId,
|
||||
targetThreadIds: [threadId],
|
||||
status: event.status,
|
||||
title: event.title,
|
||||
promptPreview: safePreview(
|
||||
payloadString(event, "promptPreview", "prompt_preview", "prompt", "input"),
|
||||
),
|
||||
createdAt: event.createdAt,
|
||||
completedAt: event.completedAt,
|
||||
});
|
||||
}
|
||||
|
||||
function activityForEvent(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
threadId: string,
|
||||
): AgentUiSubagentActivityView {
|
||||
return compact<AgentUiSubagentActivityView>({
|
||||
activityId: event.id,
|
||||
threadId,
|
||||
sourceEventId: event.id,
|
||||
kind: activityKind(event),
|
||||
status: event.status,
|
||||
title: event.title,
|
||||
createdAt: event.createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildAgentUiSubagentsModel(
|
||||
events: readonly AgentRuntimeExecutionEvent[] = [],
|
||||
): AgentUiSubagentsModel {
|
||||
const threads = new Map<string, AgentUiSubagentThreadView>();
|
||||
const delegationCalls = new Map<string, AgentUiSubagentDelegationView>();
|
||||
const activities: AgentUiSubagentActivityView[] = [];
|
||||
|
||||
events.forEach((event) => {
|
||||
if (!isSubagentEvent(event)) return;
|
||||
const threadId = subagentThreadId(event);
|
||||
if (!threadId) return;
|
||||
|
||||
threads.set(threadId, mergeThread(threads.get(threadId), event, threadId));
|
||||
activities.push(activityForEvent(event, threadId));
|
||||
|
||||
if (isDelegationEvent(event)) {
|
||||
const delegation = delegationForEvent(event, threadId);
|
||||
delegationCalls.set(delegation.callId, delegation);
|
||||
}
|
||||
});
|
||||
|
||||
const threadList = Array.from(threads.values());
|
||||
return {
|
||||
hasSubagents: threadList.length > 0,
|
||||
threads: threadList,
|
||||
delegationCalls: Array.from(delegationCalls.values()),
|
||||
activities,
|
||||
activeThreadIds: threadList
|
||||
.filter((thread) => thread.status === "pending" || thread.status === "running" || thread.status === "blocked")
|
||||
.map((thread) => thread.threadId),
|
||||
completedThreadIds: threadList
|
||||
.filter((thread) => thread.status === "completed")
|
||||
.map((thread) => thread.threadId),
|
||||
failedThreadIds: threadList
|
||||
.filter((thread) => thread.status === "failed")
|
||||
.map((thread) => thread.threadId),
|
||||
};
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export interface AgentUiProjectionSummary {
|
||||
latestNotableEvents: AgentUiProjectionEvent[];
|
||||
}
|
||||
|
||||
export interface AgentUiTeamWorkbenchProjectionSummary {
|
||||
export interface AgentUiSubagentsProjectionSummary {
|
||||
total: number;
|
||||
rosterCount: number;
|
||||
workBoardCount: number;
|
||||
@@ -30,27 +30,25 @@ export interface AgentUiTeamWorkbenchProjectionSummary {
|
||||
latestEvents: AgentUiProjectionEvent[];
|
||||
}
|
||||
|
||||
export interface AgentUiTeamWorkbenchSurfaceLaneDefinition {
|
||||
export interface AgentUiSubagentsSurfaceLaneDefinition {
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
surfaces: AgentUiSurface[];
|
||||
}
|
||||
|
||||
export interface AgentUiTeamWorkbenchSurfaceLaneSummary
|
||||
extends AgentUiTeamWorkbenchSurfaceLaneDefinition {
|
||||
export interface AgentUiSubagentsSurfaceLaneSummary extends AgentUiSubagentsSurfaceLaneDefinition {
|
||||
total: number;
|
||||
latestEvents: AgentUiProjectionEvent[];
|
||||
}
|
||||
|
||||
export interface AgentUiTeamWorkbenchSurfaceDefinition {
|
||||
export interface AgentUiSubagentsSurfaceDefinition {
|
||||
surface: AgentUiSurface;
|
||||
label: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface AgentUiTeamWorkbenchSurfaceSummary
|
||||
extends AgentUiTeamWorkbenchSurfaceDefinition {
|
||||
export interface AgentUiSubagentsSurfaceSummary extends AgentUiSubagentsSurfaceDefinition {
|
||||
total: number;
|
||||
latestEvents: AgentUiProjectionEvent[];
|
||||
}
|
||||
@@ -66,7 +64,7 @@ export const EMPTY_AGENT_UI_PROJECTION_SUMMARY: AgentUiProjectionSummary = {
|
||||
latestNotableEvents: [],
|
||||
};
|
||||
|
||||
export const EMPTY_AGENT_UI_TEAM_WORKBENCH_PROJECTION_SUMMARY: AgentUiTeamWorkbenchProjectionSummary =
|
||||
export const EMPTY_AGENT_UI_SUBAGENTS_PROJECTION_SUMMARY: AgentUiSubagentsProjectionSummary =
|
||||
{
|
||||
total: 0,
|
||||
rosterCount: 0,
|
||||
@@ -127,7 +125,7 @@ export const AGENT_UI_EVIDENCE_EVENT_TYPES = new Set<AgentUiEventClass>([
|
||||
"review.completed",
|
||||
]);
|
||||
|
||||
export const AGENT_UI_TEAM_WORKBENCH_SURFACES = new Set<AgentUiSurface>([
|
||||
export const AGENT_UI_SUBAGENTS_SURFACES = new Set<AgentUiSurface>([
|
||||
"team_roster",
|
||||
"work_board",
|
||||
"delegation_graph",
|
||||
@@ -140,7 +138,7 @@ export const AGENT_UI_TEAM_WORKBENCH_SURFACES = new Set<AgentUiSurface>([
|
||||
"team_policy",
|
||||
]);
|
||||
|
||||
export const AGENT_UI_TEAM_WORKBENCH_SURFACE_DEFINITIONS: AgentUiTeamWorkbenchSurfaceDefinition[] =
|
||||
export const AGENT_UI_SUBAGENTS_SURFACE_DEFINITIONS: AgentUiSubagentsSurfaceDefinition[] =
|
||||
[
|
||||
{
|
||||
surface: "team_roster",
|
||||
@@ -175,7 +173,7 @@ export const AGENT_UI_TEAM_WORKBENCH_SURFACE_DEFINITIONS: AgentUiTeamWorkbenchSu
|
||||
{
|
||||
surface: "teammate_transcript",
|
||||
label: "Transcript",
|
||||
description: "队友 transcript ref 与局部会话线索",
|
||||
description: "子代理 transcript ref 与局部会话线索",
|
||||
},
|
||||
{
|
||||
surface: "background_teammate",
|
||||
@@ -185,7 +183,7 @@ export const AGENT_UI_TEAM_WORKBENCH_SURFACE_DEFINITIONS: AgentUiTeamWorkbenchSu
|
||||
{
|
||||
surface: "remote_teammate",
|
||||
label: "Remote",
|
||||
description: "远端 teammate / external task 状态",
|
||||
description: "远端子代理 / external task 状态",
|
||||
},
|
||||
{
|
||||
surface: "team_policy",
|
||||
@@ -194,12 +192,12 @@ export const AGENT_UI_TEAM_WORKBENCH_SURFACE_DEFINITIONS: AgentUiTeamWorkbenchSu
|
||||
},
|
||||
];
|
||||
|
||||
export const AGENT_UI_TEAM_WORKBENCH_SURFACE_LANES: AgentUiTeamWorkbenchSurfaceLaneDefinition[] =
|
||||
export const AGENT_UI_SUBAGENTS_SURFACE_LANES: AgentUiSubagentsSurfaceLaneDefinition[] =
|
||||
[
|
||||
{
|
||||
id: "team-topology",
|
||||
label: "Team 拓扑",
|
||||
description: "成员、分派、任务板与策略事实",
|
||||
label: "Subagents 拓扑",
|
||||
description: "子代理、分派、任务板与策略事实",
|
||||
surfaces: [
|
||||
"team_roster",
|
||||
"delegation_graph",
|
||||
@@ -210,7 +208,7 @@ export const AGENT_UI_TEAM_WORKBENCH_SURFACE_LANES: AgentUiTeamWorkbenchSurfaceL
|
||||
{
|
||||
id: "worker-flow",
|
||||
label: "Worker 流",
|
||||
description: "执行通知、队友 transcript 与后台/远端伙伴",
|
||||
description: "执行通知、子代理 transcript 与后台/远端子代理",
|
||||
surfaces: [
|
||||
"worker_notifications",
|
||||
"teammate_transcript",
|
||||
@@ -226,7 +224,7 @@ export const AGENT_UI_TEAM_WORKBENCH_SURFACE_LANES: AgentUiTeamWorkbenchSurfaceL
|
||||
},
|
||||
];
|
||||
|
||||
export const AGENT_UI_TEAM_WORKBENCH_EVENT_TYPES = new Set<AgentUiEventClass>([
|
||||
export const AGENT_UI_SUBAGENTS_EVENT_TYPES = new Set<AgentUiEventClass>([
|
||||
"agent.changed",
|
||||
"agent.spawned",
|
||||
"agent.completed",
|
||||
@@ -293,56 +291,59 @@ export function summarizeAgentUiProjectionEvents(
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeAgentUiTeamWorkbenchProjectionEvents(
|
||||
export function summarizeAgentUiSubagentsProjectionEvents(
|
||||
events: AgentUiProjectionEvent[],
|
||||
): AgentUiTeamWorkbenchProjectionSummary {
|
||||
const teamEvents = events.filter(
|
||||
): AgentUiSubagentsProjectionSummary {
|
||||
const subagentEvents = events.filter(
|
||||
(event) =>
|
||||
AGENT_UI_TEAM_WORKBENCH_EVENT_TYPES.has(event.type) ||
|
||||
Boolean(
|
||||
event.surface && AGENT_UI_TEAM_WORKBENCH_SURFACES.has(event.surface),
|
||||
),
|
||||
AGENT_UI_SUBAGENTS_EVENT_TYPES.has(event.type) ||
|
||||
Boolean(event.surface && AGENT_UI_SUBAGENTS_SURFACES.has(event.surface)),
|
||||
);
|
||||
|
||||
if (teamEvents.length === 0) {
|
||||
return EMPTY_AGENT_UI_TEAM_WORKBENCH_PROJECTION_SUMMARY;
|
||||
if (subagentEvents.length === 0) {
|
||||
return EMPTY_AGENT_UI_SUBAGENTS_PROJECTION_SUMMARY;
|
||||
}
|
||||
|
||||
return {
|
||||
total: teamEvents.length,
|
||||
rosterCount: teamEvents.filter((event) => event.surface === "team_roster")
|
||||
.length,
|
||||
workBoardCount: teamEvents.filter((event) => event.surface === "work_board")
|
||||
.length,
|
||||
delegationCount: teamEvents.filter(
|
||||
total: subagentEvents.length,
|
||||
rosterCount: subagentEvents.filter(
|
||||
(event) => event.surface === "team_roster",
|
||||
).length,
|
||||
workBoardCount: subagentEvents.filter(
|
||||
(event) => event.surface === "work_board",
|
||||
).length,
|
||||
delegationCount: subagentEvents.filter(
|
||||
(event) => event.surface === "delegation_graph",
|
||||
).length,
|
||||
handoffCount: teamEvents.filter((event) => event.surface === "handoff_lane")
|
||||
.length,
|
||||
workerNotificationCount: teamEvents.filter(
|
||||
handoffCount: subagentEvents.filter(
|
||||
(event) => event.surface === "handoff_lane",
|
||||
).length,
|
||||
workerNotificationCount: subagentEvents.filter(
|
||||
(event) => event.surface === "worker_notifications",
|
||||
).length,
|
||||
reviewCount: teamEvents.filter((event) => event.surface === "review_lane")
|
||||
.length,
|
||||
transcriptCount: teamEvents.filter(
|
||||
reviewCount: subagentEvents.filter(
|
||||
(event) => event.surface === "review_lane",
|
||||
).length,
|
||||
transcriptCount: subagentEvents.filter(
|
||||
(event) => event.surface === "teammate_transcript",
|
||||
).length,
|
||||
backgroundCount: teamEvents.filter(
|
||||
backgroundCount: subagentEvents.filter(
|
||||
(event) => event.surface === "background_teammate",
|
||||
).length,
|
||||
remoteCount: teamEvents.filter(
|
||||
remoteCount: subagentEvents.filter(
|
||||
(event) => event.surface === "remote_teammate",
|
||||
).length,
|
||||
policyCount: teamEvents.filter((event) => event.surface === "team_policy")
|
||||
.length,
|
||||
latestEvents: teamEvents.slice().reverse().slice(0, 5),
|
||||
policyCount: subagentEvents.filter(
|
||||
(event) => event.surface === "team_policy",
|
||||
).length,
|
||||
latestEvents: subagentEvents.slice().reverse().slice(0, 5),
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeAgentUiTeamWorkbenchSurfaceLanes(
|
||||
export function summarizeAgentUiSubagentsSurfaceLanes(
|
||||
events: AgentUiProjectionEvent[],
|
||||
): AgentUiTeamWorkbenchSurfaceLaneSummary[] {
|
||||
return AGENT_UI_TEAM_WORKBENCH_SURFACE_LANES.map((lane) => {
|
||||
): AgentUiSubagentsSurfaceLaneSummary[] {
|
||||
return AGENT_UI_SUBAGENTS_SURFACE_LANES.map((lane) => {
|
||||
const surfaceSet = new Set(lane.surfaces);
|
||||
const laneEvents = events.filter(
|
||||
(event) => event.surface && surfaceSet.has(event.surface),
|
||||
@@ -356,12 +357,12 @@ export function summarizeAgentUiTeamWorkbenchSurfaceLanes(
|
||||
}).filter((lane) => lane.total > 0);
|
||||
}
|
||||
|
||||
export function summarizeAgentUiTeamWorkbenchSurfaces(
|
||||
export function summarizeAgentUiSubagentsSurfaces(
|
||||
events: AgentUiProjectionEvent[],
|
||||
options: { latestLimit?: number } = {},
|
||||
): AgentUiTeamWorkbenchSurfaceSummary[] {
|
||||
): AgentUiSubagentsSurfaceSummary[] {
|
||||
const latestLimit = Math.max(1, options.latestLimit ?? 3);
|
||||
return AGENT_UI_TEAM_WORKBENCH_SURFACE_DEFINITIONS.map((definition) => {
|
||||
return AGENT_UI_SUBAGENTS_SURFACE_DEFINITIONS.map((definition) => {
|
||||
const surfaceEvents = events.filter(
|
||||
(event) => event.surface === definition.surface,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import type {
|
||||
AgentRuntimeExecutionEvent,
|
||||
AgentRuntimeProjectionInput,
|
||||
AgentRuntimeReadModel,
|
||||
AgentUiArtifactRefView,
|
||||
AgentUiEvidenceRefView,
|
||||
AgentUiProjectionState,
|
||||
AgentUiProjector,
|
||||
AgentUiRefView,
|
||||
AgentUiRuntimeStatusView,
|
||||
ExecutionGraphNode,
|
||||
ExecutionGraphNodeType,
|
||||
@@ -11,6 +15,7 @@ import type {
|
||||
UIMessagePart,
|
||||
} from "@limecloud/agent-ui-contracts";
|
||||
import { agentEventSurface, projectAgentRuntimeReadModel } from "./readModel.js";
|
||||
import { buildAgentUiSubagentsModel } from "./subagents.js";
|
||||
|
||||
function eventRefs(event: AgentRuntimeExecutionEvent): string[] {
|
||||
return [
|
||||
@@ -20,6 +25,122 @@ function eventRefs(event: AgentRuntimeExecutionEvent): string[] {
|
||||
];
|
||||
}
|
||||
|
||||
function isRecord(input: unknown): input is Record<string, unknown> {
|
||||
return typeof input === "object" && input !== null && !Array.isArray(input);
|
||||
}
|
||||
|
||||
export interface AgentUiSessionSnapshotMessage {
|
||||
id: string;
|
||||
role?: "user" | "assistant" | "system" | string;
|
||||
content?: string;
|
||||
text?: string;
|
||||
createdAt?: string;
|
||||
refs?: string[];
|
||||
}
|
||||
|
||||
export interface AgentUiSessionSnapshotInput<
|
||||
TEvent extends AgentRuntimeExecutionEvent = AgentRuntimeExecutionEvent,
|
||||
> extends AgentRuntimeProjectionInput<TEvent> {
|
||||
messages?: readonly AgentUiSessionSnapshotMessage[];
|
||||
readModel?: AgentRuntimeReadModel<TEvent>;
|
||||
}
|
||||
|
||||
function payloadString(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
...keys: string[]
|
||||
): string | undefined {
|
||||
const payload = event.payload;
|
||||
if (!isRecord(payload)) return undefined;
|
||||
for (const key of keys) {
|
||||
const value = payload[key];
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function payloadRecord(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
key: string,
|
||||
): Record<string, unknown> | undefined {
|
||||
const payload = event.payload;
|
||||
if (!isRecord(payload)) return undefined;
|
||||
const value = payload[key];
|
||||
return isRecord(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function safeRefPath(value: string | undefined): string | undefined {
|
||||
if (!value) return undefined;
|
||||
if (/^(?:[A-Za-z]:[\\/]|\/|file:)/.test(value)) return undefined;
|
||||
if (/(?:api[-_]?key|authorization|password|secret|token)=/i.test(value)) {
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function safePreview(value: string | undefined): string | undefined {
|
||||
if (!value) return undefined;
|
||||
return value.length > 280 ? `${value.slice(0, 277)}...` : value;
|
||||
}
|
||||
|
||||
function compactRefView(ref: AgentUiRefView): AgentUiRefView {
|
||||
return Object.fromEntries(
|
||||
Object.entries(ref).filter(([, value]) => value !== undefined),
|
||||
) as AgentUiRefView;
|
||||
}
|
||||
|
||||
function refViewForEvent(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
refId: string,
|
||||
): AgentUiRefView {
|
||||
const payloadSourceEventId = payloadString(event, "sourceEventId", "eventId");
|
||||
const path = safeRefPath(
|
||||
payloadString(
|
||||
event,
|
||||
"path",
|
||||
"relativePath",
|
||||
"packRelativeRoot",
|
||||
"artifactPath",
|
||||
"evidencePath",
|
||||
),
|
||||
);
|
||||
return compactRefView({
|
||||
id: refId,
|
||||
sourceEventId: payloadSourceEventId ?? event.id,
|
||||
title: payloadString(event, "title", "name") ?? event.title,
|
||||
status: event.status,
|
||||
owner: event.owner,
|
||||
path,
|
||||
contentRef: payloadString(event, "contentRef", "uri", "url"),
|
||||
mimeType: payloadString(event, "mimeType", "mime"),
|
||||
preview: safePreview(payloadString(event, "preview", "summary", "text")),
|
||||
metadata: payloadRecord(event, "metadata"),
|
||||
});
|
||||
}
|
||||
|
||||
function collectRefViews(
|
||||
events: AgentRuntimeExecutionEvent[],
|
||||
readModelRefs: string[],
|
||||
kind: "artifact" | "evidence",
|
||||
): AgentUiRefView[] {
|
||||
const refs = new Map<string, AgentUiRefView>();
|
||||
events.forEach((event) => {
|
||||
const eventRefs = kind === "artifact"
|
||||
? event.artifactRefs ?? []
|
||||
: event.evidenceRefs ?? [];
|
||||
eventRefs.forEach((id) => {
|
||||
if (!refs.has(id)) refs.set(id, refViewForEvent(event, id));
|
||||
});
|
||||
});
|
||||
readModelRefs.forEach((id) => {
|
||||
if (!refs.has(id)) {
|
||||
refs.set(id, { id, sourceEventId: id });
|
||||
}
|
||||
});
|
||||
return Array.from(refs.values());
|
||||
}
|
||||
|
||||
function uiPartForEvent(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
): UIMessagePart | undefined {
|
||||
@@ -191,6 +312,38 @@ function collectMessageParts(
|
||||
return parts;
|
||||
}
|
||||
|
||||
function normalizeSnapshotMessageRole(
|
||||
role: AgentUiSessionSnapshotMessage["role"],
|
||||
): string {
|
||||
return role && role.trim() ? role : "assistant";
|
||||
}
|
||||
|
||||
function snapshotMessageText(message: AgentUiSessionSnapshotMessage): string {
|
||||
return message.content ?? message.text ?? "";
|
||||
}
|
||||
|
||||
function messagePartForSnapshotMessage(
|
||||
message: AgentUiSessionSnapshotMessage,
|
||||
): UIMessagePart {
|
||||
return {
|
||||
type: "text",
|
||||
partId: `message:${message.id}`,
|
||||
messageId: message.id,
|
||||
role: normalizeSnapshotMessageRole(message.role),
|
||||
text: snapshotMessageText(message),
|
||||
state: "final",
|
||||
sourceEventId: `message:${message.id}`,
|
||||
createdAt: message.createdAt,
|
||||
refs: message.refs,
|
||||
};
|
||||
}
|
||||
|
||||
function collectExecutionEventsFromReadModel<TEvent extends AgentRuntimeExecutionEvent>(
|
||||
readModel?: AgentRuntimeReadModel<TEvent>,
|
||||
): TEvent[] {
|
||||
return readModel?.events.map((event) => event.source) ?? [];
|
||||
}
|
||||
|
||||
function timelineKindForEvent(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
): ProcessTimelineEntryKind {
|
||||
@@ -230,6 +383,7 @@ function graphNodeIdForEvent(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
): string | undefined {
|
||||
return (
|
||||
event.subagentId ??
|
||||
event.toolCallId ??
|
||||
event.actionId ??
|
||||
event.stepId ??
|
||||
@@ -243,6 +397,7 @@ function graphNodeIdForEvent(
|
||||
function graphNodeTypeForEvent(
|
||||
event: AgentRuntimeExecutionEvent,
|
||||
): ExecutionGraphNodeType {
|
||||
if (event.subagentId) return "subagent";
|
||||
if (event.toolCallId) return "tool";
|
||||
if (event.actionId) return "action";
|
||||
if (event.stepId) return "step";
|
||||
@@ -268,7 +423,9 @@ function upsertGraphNode(
|
||||
nodes.set(nodeId, {
|
||||
nodeId,
|
||||
parentId:
|
||||
event.stepId && event.toolCallId
|
||||
event.subagentId && event.taskId
|
||||
? event.taskId
|
||||
: event.stepId && event.toolCallId
|
||||
? event.stepId
|
||||
: event.taskId && event.runId
|
||||
? event.taskId
|
||||
@@ -357,6 +514,7 @@ export function projectAgentUiState<
|
||||
const timeline = executionEvents.map(timelineEntryForEvent);
|
||||
const graphNodes = new Map<string, ExecutionGraphNode>();
|
||||
executionEvents.forEach((event) => upsertGraphNode(graphNodes, event));
|
||||
const graph = Array.from(graphNodes.values());
|
||||
const actions = readModel.events.filter(
|
||||
(event) => event.surface === "human-action",
|
||||
);
|
||||
@@ -379,18 +537,21 @@ export function projectAgentUiState<
|
||||
runtime: runtimeStatusForEvents(executionEvents),
|
||||
messages,
|
||||
timeline,
|
||||
graph: Array.from(graphNodes.values()),
|
||||
graph,
|
||||
tools,
|
||||
actions,
|
||||
artifacts: readModel.artifactRefs.map((id) => ({
|
||||
id,
|
||||
sourceEventId: id,
|
||||
})),
|
||||
evidence: readModel.evidenceRefs.map((id) => ({
|
||||
id,
|
||||
sourceEventId: id,
|
||||
})),
|
||||
artifacts: collectRefViews(
|
||||
executionEvents,
|
||||
readModel.artifactRefs,
|
||||
"artifact",
|
||||
) as AgentUiArtifactRefView[],
|
||||
evidence: collectRefViews(
|
||||
executionEvents,
|
||||
readModel.evidenceRefs,
|
||||
"evidence",
|
||||
) as AgentUiEvidenceRefView[],
|
||||
diagnostics,
|
||||
subagents: buildAgentUiSubagentsModel(executionEvents),
|
||||
readModel,
|
||||
hydration: {
|
||||
status: executionEvents.length ? "live" : "idle",
|
||||
@@ -400,6 +561,27 @@ export function projectAgentUiState<
|
||||
};
|
||||
}
|
||||
|
||||
export function projectAgentUiStateFromSessionSnapshot<
|
||||
TEvent extends AgentRuntimeExecutionEvent,
|
||||
>(
|
||||
input: AgentUiSessionSnapshotInput<TEvent> = {},
|
||||
): AgentUiProjectionState<TEvent> {
|
||||
const executionEvents =
|
||||
input.executionEvents ?? collectExecutionEventsFromReadModel(input.readModel);
|
||||
const state = projectAgentUiState<TEvent>({
|
||||
executionEvents,
|
||||
sourceCount: input.sourceCount ?? input.readModel?.sourceCount,
|
||||
});
|
||||
const messages = input.messages?.map(messagePartForSnapshotMessage) ?? [];
|
||||
if (!messages.length) {
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
messages,
|
||||
};
|
||||
}
|
||||
|
||||
export function createAgentUiProjector<
|
||||
TEvent extends AgentRuntimeExecutionEvent,
|
||||
>(initialInput?: AgentRuntimeProjectionInput<TEvent>): AgentUiProjector<TEvent> {
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
projectAppServerEventsToExecutionEvents,
|
||||
projectAppServerSessionReadToExecutionEvents,
|
||||
replayAppServerFacts,
|
||||
} from "../dist/index.js";
|
||||
|
||||
const timestamp = "2026-06-10T00:00:00.000Z";
|
||||
|
||||
test("App Server events replay into standard projection surfaces", () => {
|
||||
const result = replayAppServerFacts({
|
||||
events: [
|
||||
appServerEvent("evt-message", 1, "message.delta", {
|
||||
text: "你好,Lime。",
|
||||
messageId: "msg-1",
|
||||
}),
|
||||
appServerEvent("evt-tool", 2, "tool.started", {
|
||||
toolCallId: "tool-1",
|
||||
toolName: "search",
|
||||
}),
|
||||
appServerEvent("evt-action", 3, "action.required", {
|
||||
request_id: "request-1",
|
||||
action_type: "tool_confirmation",
|
||||
}),
|
||||
appServerEvent("evt-artifact", 4, "artifact.snapshot", {
|
||||
artifactRef: "artifact-1",
|
||||
artifactId: "artifact-1",
|
||||
path: "draft.md",
|
||||
}),
|
||||
appServerEvent("evt-evidence", 5, "evidence.changed", {
|
||||
evidenceRef: "evidence-1",
|
||||
}),
|
||||
appServerEvent("evt-completed", 6, "turn.completed", {
|
||||
text: "完成",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
assert.deepEqual(result.diagnostics, []);
|
||||
assert.equal(result.state.runtime.status, "completed");
|
||||
assert.equal(result.state.messages[0].text, "你好,Lime。");
|
||||
assert.equal(result.state.tools.length, 1);
|
||||
assert.equal(result.state.readModel.pendingActions.length, 1);
|
||||
assert.deepEqual(
|
||||
result.state.readModel.pendingActions[0].actions.map(
|
||||
(action) => action.decision,
|
||||
),
|
||||
["approve", "reject"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.state.artifacts.map((artifact) => artifact.id),
|
||||
["artifact-1"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.state.evidence.map((evidence) => evidence.id),
|
||||
["evidence-1"],
|
||||
);
|
||||
});
|
||||
|
||||
test("App Server read model hydrates snapshot and turn lifecycle facts", () => {
|
||||
const events = projectAppServerSessionReadToExecutionEvents({
|
||||
session: {
|
||||
sessionId: "session-1",
|
||||
threadId: "thread-1",
|
||||
appId: "content-studio",
|
||||
status: "running",
|
||||
createdAt: timestamp,
|
||||
updatedAt: "2026-06-10T00:00:02.000Z",
|
||||
},
|
||||
turns: [
|
||||
{
|
||||
turnId: "turn-1",
|
||||
sessionId: "session-1",
|
||||
threadId: "thread-1",
|
||||
status: "completed",
|
||||
startedAt: timestamp,
|
||||
completedAt: "2026-06-10T00:00:01.000Z",
|
||||
},
|
||||
{
|
||||
turnId: "turn-2",
|
||||
sessionId: "session-1",
|
||||
threadId: "thread-1",
|
||||
status: "waitingAction",
|
||||
startedAt: "2026-06-10T00:00:02.000Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.equal(events.length, 3);
|
||||
assert.equal(events[0].eventClass, "snapshot.updated");
|
||||
assert.equal(events[1].eventClass, "turn.completed");
|
||||
assert.equal(events[2].eventClass, "action.required");
|
||||
|
||||
const result = replayAppServerFacts({
|
||||
readModel: {
|
||||
session: {
|
||||
sessionId: "session-1",
|
||||
threadId: "thread-1",
|
||||
status: "running",
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
turns: [
|
||||
{
|
||||
turnId: "turn-2",
|
||||
status: "waitingAction",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(result.state.runtime.status, "waiting");
|
||||
assert.equal(result.state.readModel.pendingActions.length, 1);
|
||||
assert.equal(
|
||||
result.state.graph.some(
|
||||
(node) => node.nodeId === "turn-2" && node.nodeType === "turn",
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("App Server evidence export projects artifacts, evidence pack and deduped events", () => {
|
||||
const duplicatedEvent = appServerEvent("evt-message", 1, "message.delta", {
|
||||
text: "导出文本",
|
||||
});
|
||||
const result = replayAppServerFacts({
|
||||
events: [duplicatedEvent],
|
||||
evidenceExport: {
|
||||
session: {
|
||||
sessionId: "session-1",
|
||||
threadId: "thread-1",
|
||||
status: "completed",
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
turns: [
|
||||
{
|
||||
turnId: "turn-1",
|
||||
status: "completed",
|
||||
completedAt: timestamp,
|
||||
},
|
||||
],
|
||||
events: [duplicatedEvent],
|
||||
artifacts: [
|
||||
{
|
||||
artifactRef: "artifact-document:req-1",
|
||||
eventId: "evt-artifact",
|
||||
sequence: 8,
|
||||
turnId: "turn-1",
|
||||
artifactId: "artifact-1",
|
||||
path: "draft.md",
|
||||
title: "Draft",
|
||||
contentStatus: "notRequested",
|
||||
},
|
||||
],
|
||||
exportedAt: "2026-06-10T00:00:03.000Z",
|
||||
evidencePack: {
|
||||
packRelativeRoot: ".lime/harness/sessions/session-1",
|
||||
exportedAt: "2026-06-10T00:00:03.000Z",
|
||||
threadStatus: "completed",
|
||||
latestTurnStatus: "completed",
|
||||
turnCount: 1,
|
||||
itemCount: 2,
|
||||
pendingRequestCount: 0,
|
||||
queuedTurnCount: 0,
|
||||
recentArtifactCount: 1,
|
||||
knownGaps: [],
|
||||
artifacts: [
|
||||
{
|
||||
kind: "markdown",
|
||||
title: "Summary",
|
||||
relativePath: "summary.md",
|
||||
bytes: 128,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
result.events.filter((event) => event.id === "appserver:evt-message")
|
||||
.length,
|
||||
1,
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.state.artifacts.map((artifact) => artifact.id),
|
||||
["artifact-document:req-1"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.state.evidence.map((evidence) => evidence.id),
|
||||
[".lime/harness/sessions/session-1"],
|
||||
);
|
||||
assert.equal(result.state.hydration.eventCount, result.events.length);
|
||||
});
|
||||
|
||||
test("App Server event adapter keeps projection package runtime-client free", () => {
|
||||
const events = projectAppServerEventsToExecutionEvents([
|
||||
appServerEvent("evt-1", 1, "turn.failed", {
|
||||
message: "boom",
|
||||
}),
|
||||
]);
|
||||
|
||||
assert.equal(events[0].eventClass, "turn.failed");
|
||||
assert.equal(events[0].status, "failed");
|
||||
assert.equal(events[0].detail, "boom");
|
||||
});
|
||||
|
||||
function appServerEvent(eventId, sequence, type, payload = {}) {
|
||||
return {
|
||||
eventId,
|
||||
sequence,
|
||||
sessionId: "session-1",
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
type,
|
||||
timestamp,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
agentUiConformanceFixtures,
|
||||
getAgentUiFixture,
|
||||
} from "../../agent-ui-contracts/dist/index.js";
|
||||
import {
|
||||
createAgentUiProjector,
|
||||
replayAgentUiFixture,
|
||||
} from "../dist/index.js";
|
||||
|
||||
test("replayAgentUiFixture replays standard contract fixtures", () => {
|
||||
for (const fixture of agentUiConformanceFixtures) {
|
||||
const result = replayAgentUiFixture(fixture);
|
||||
assert.equal(result.validationIssues.length, 0, fixture.id);
|
||||
assert.equal(result.passed, true, fixture.id);
|
||||
assert.equal(result.state.hydration.eventCount, fixture.events.length);
|
||||
assert.equal(
|
||||
result.state.runtime.latestEventId,
|
||||
fixture.events.at(-1)?.id,
|
||||
fixture.id,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("replayAgentUiFixture proves key projection surfaces", () => {
|
||||
const textState = replayAgentUiFixture(getAgentUiFixture("text-basic")).state;
|
||||
assert.ok(textState.messages.length >= 1);
|
||||
assert.ok(textState.messages.some((part) => part.text === "你好,Lime。"));
|
||||
assert.equal(
|
||||
replayAgentUiFixture(getAgentUiFixture("tool-success")).state.tools.length,
|
||||
2,
|
||||
);
|
||||
assert.equal(
|
||||
replayAgentUiFixture(getAgentUiFixture("hitl-action")).state.actions.length,
|
||||
2,
|
||||
);
|
||||
assert.deepEqual(
|
||||
replayAgentUiFixture(getAgentUiFixture("artifact-evidence")).state.artifacts
|
||||
.map((artifact) => artifact.id),
|
||||
["artifact_fixture_1"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
replayAgentUiFixture(getAgentUiFixture("artifact-evidence")).state.evidence
|
||||
.map((evidence) => evidence.id),
|
||||
["evidence_fixture_1"],
|
||||
);
|
||||
|
||||
const handoffState = replayAgentUiFixture(getAgentUiFixture("subagent-handoff")).state;
|
||||
assert.equal(handoffState.subagents.hasSubagents, true);
|
||||
assert.deepEqual(
|
||||
handoffState.subagents.threads.map((thread) => thread.threadId),
|
||||
["subagent_fixture_researcher"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
handoffState.subagents.delegationCalls.map((delegation) => delegation.action),
|
||||
["spawn", "handoff"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
handoffState.subagents.activities.map((activity) => activity.kind),
|
||||
["started", "interacted", "handoff", "completed"],
|
||||
);
|
||||
});
|
||||
|
||||
test("projector apply remains idempotent for fixture replay", () => {
|
||||
const fixture = getAgentUiFixture("subagent-handoff");
|
||||
const projector = createAgentUiProjector();
|
||||
|
||||
for (const event of fixture.events) {
|
||||
projector.apply(event);
|
||||
projector.apply(event);
|
||||
}
|
||||
|
||||
const state = projector.getState();
|
||||
assert.equal(state.hydration.eventCount, fixture.events.length);
|
||||
assert.ok(
|
||||
state.graph.some(
|
||||
(node) =>
|
||||
node.nodeId === "subagent_fixture_researcher"
|
||||
&& node.nodeType === "subagent",
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
state.timeline.some((entry) => entry.sourceEventId === "evt_handoff_requested"),
|
||||
);
|
||||
});
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
normalizeRuntimeStatusFromRuntimePhase,
|
||||
normalizeSubagentRuntimeStatus,
|
||||
projectAgentUiState,
|
||||
projectAgentUiStateFromSessionSnapshot,
|
||||
projectAgentRuntimeReadModel,
|
||||
readNumberField,
|
||||
readRecord,
|
||||
@@ -72,9 +73,9 @@ import {
|
||||
buildRoutingDecisionPayload,
|
||||
extractArtifactRefs,
|
||||
summarizeAgentUiProjectionEvents,
|
||||
summarizeAgentUiTeamWorkbenchProjectionEvents,
|
||||
summarizeAgentUiTeamWorkbenchSurfaceLanes,
|
||||
summarizeAgentUiTeamWorkbenchSurfaces,
|
||||
summarizeAgentUiSubagentsProjectionEvents,
|
||||
summarizeAgentUiSubagentsSurfaceLanes,
|
||||
summarizeAgentUiSubagentsSurfaces,
|
||||
truncateText,
|
||||
} from "../dist/index.js";
|
||||
|
||||
@@ -247,11 +248,112 @@ test("projectAgentUiState exposes standard message, timeline and graph projectio
|
||||
);
|
||||
assert.equal(state.actions.length, 1);
|
||||
assert.deepEqual(state.artifacts, [
|
||||
{ id: "artifact-1", sourceEventId: "artifact-1" },
|
||||
{
|
||||
id: "artifact-1",
|
||||
sourceEventId: "evt-tool",
|
||||
title: "读取资料",
|
||||
status: "completed",
|
||||
},
|
||||
]);
|
||||
assert.equal(state.hydration.status, "live");
|
||||
});
|
||||
|
||||
test("projectAgentUiState builds structured artifact and evidence refs", () => {
|
||||
const state = projectAgentUiState({
|
||||
executionEvents: [
|
||||
{
|
||||
id: "evt-artifact",
|
||||
kind: "draft",
|
||||
status: "completed",
|
||||
owner: "artifact",
|
||||
eventClass: "artifact.changed",
|
||||
title: "草稿已保存",
|
||||
artifactRefs: ["prompt-draft:1"],
|
||||
payload: {
|
||||
sourceEventId: "provider-artifact-1",
|
||||
relativePath: "drafts/prompt-1.md",
|
||||
contentRef: "content://prompt-draft/1",
|
||||
mimeType: "text/markdown",
|
||||
preview: "可编辑 Prompt 草稿",
|
||||
metadata: { draftId: "1" },
|
||||
},
|
||||
sequence: 1,
|
||||
createdAt: "2026-06-07T00:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "evt-evidence",
|
||||
kind: "evidence",
|
||||
status: "completed",
|
||||
owner: "evidence",
|
||||
eventClass: "evidence.changed",
|
||||
title: "输入资料已引用",
|
||||
evidenceRefs: ["input-source:1"],
|
||||
payload: {
|
||||
sourceEventId: "provider-evidence-1",
|
||||
packRelativeRoot: "evidence/input-source-1",
|
||||
summary: "资料来源摘要",
|
||||
mime: "application/json",
|
||||
metadata: { sourceId: "1" },
|
||||
},
|
||||
sequence: 2,
|
||||
createdAt: "2026-06-07T00:00:01.000Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.deepEqual(state.artifacts, [
|
||||
{
|
||||
id: "prompt-draft:1",
|
||||
sourceEventId: "provider-artifact-1",
|
||||
title: "草稿已保存",
|
||||
status: "completed",
|
||||
owner: "artifact",
|
||||
path: "drafts/prompt-1.md",
|
||||
contentRef: "content://prompt-draft/1",
|
||||
mimeType: "text/markdown",
|
||||
preview: "可编辑 Prompt 草稿",
|
||||
metadata: { draftId: "1" },
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(state.evidence, [
|
||||
{
|
||||
id: "input-source:1",
|
||||
sourceEventId: "provider-evidence-1",
|
||||
title: "输入资料已引用",
|
||||
status: "completed",
|
||||
owner: "evidence",
|
||||
path: "evidence/input-source-1",
|
||||
mimeType: "application/json",
|
||||
preview: "资料来源摘要",
|
||||
metadata: { sourceId: "1" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("projectAgentUiState keeps unsafe ref paths out of the UI contract", () => {
|
||||
const state = projectAgentUiState({
|
||||
executionEvents: [
|
||||
{
|
||||
id: "evt-artifact",
|
||||
kind: "draft",
|
||||
status: "completed",
|
||||
eventClass: "artifact.changed",
|
||||
title: "草稿已保存",
|
||||
artifactRefs: ["prompt-draft:1"],
|
||||
payload: {
|
||||
path: "/Users/private/workspace/draft.md",
|
||||
preview: "x".repeat(320),
|
||||
},
|
||||
sequence: 1,
|
||||
createdAt: "2026-06-07T00:00:00.000Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.equal(state.artifacts[0].path, undefined);
|
||||
assert.equal(state.artifacts[0].preview.length, 280);
|
||||
});
|
||||
|
||||
test("projectAgentUiState merges streaming text and reasoning parts by scope", () => {
|
||||
const state = projectAgentUiState({
|
||||
executionEvents: [
|
||||
@@ -383,6 +485,81 @@ test("projectAgentUiState keeps unresolved actions in waiting status", () => {
|
||||
assert.equal(state.runtime.status, "waiting");
|
||||
});
|
||||
|
||||
test("projectAgentUiStateFromSessionSnapshot upgrades legacy transcript plus read model to full UI state", () => {
|
||||
const readModel = projectAgentRuntimeReadModel({
|
||||
sourceCount: 2,
|
||||
executionEvents: [
|
||||
{
|
||||
id: "evt-artifact",
|
||||
kind: "draft",
|
||||
status: "completed",
|
||||
eventClass: "artifact.changed",
|
||||
title: "草稿已保存",
|
||||
artifactRefs: ["artifact-1"],
|
||||
payload: {
|
||||
relativePath: "drafts/outline.md",
|
||||
preview: "文章大纲",
|
||||
},
|
||||
sequence: 1,
|
||||
createdAt: "2026-06-10T00:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "evt-evidence",
|
||||
kind: "evidence",
|
||||
status: "completed",
|
||||
eventClass: "evidence.changed",
|
||||
title: "证据已导出",
|
||||
evidenceRefs: ["evidence-1"],
|
||||
payload: {
|
||||
packRelativeRoot: "evidence/input-source",
|
||||
summary: "输入源摘要",
|
||||
},
|
||||
sequence: 2,
|
||||
createdAt: "2026-06-10T00:00:01.000Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const state = projectAgentUiStateFromSessionSnapshot({
|
||||
readModel,
|
||||
messages: [
|
||||
{
|
||||
id: "message-user",
|
||||
role: "user",
|
||||
content: "请生成小红书标题",
|
||||
createdAt: "2026-06-10T00:00:02.000Z",
|
||||
},
|
||||
{
|
||||
id: "message-assistant",
|
||||
role: "assistant",
|
||||
text: "已生成 5 个标题候选",
|
||||
refs: ["artifact-1", "evidence-1"],
|
||||
createdAt: "2026-06-10T00:00:03.000Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.equal(state.hydration.status, "live");
|
||||
assert.equal(state.hydration.eventCount, 2);
|
||||
assert.equal(state.readModel.sourceCount, 2);
|
||||
assert.deepEqual(
|
||||
state.messages.map((message) => [message.messageId, message.role, message.text]),
|
||||
[
|
||||
["message-user", "user", "请生成小红书标题"],
|
||||
["message-assistant", "assistant", "已生成 5 个标题候选"],
|
||||
],
|
||||
);
|
||||
assert.deepEqual(state.messages[1].refs, ["artifact-1", "evidence-1"]);
|
||||
assert.deepEqual(
|
||||
state.artifacts.map((artifact) => [artifact.id, artifact.path, artifact.preview]),
|
||||
[["artifact-1", "drafts/outline.md", "文章大纲"]],
|
||||
);
|
||||
assert.deepEqual(
|
||||
state.evidence.map((evidence) => [evidence.id, evidence.path, evidence.preview]),
|
||||
[["evidence-1", "evidence/input-source", "输入源摘要"]],
|
||||
);
|
||||
});
|
||||
|
||||
test("createAgentUiProjector applies events idempotently", () => {
|
||||
const projector = createAgentUiProjector();
|
||||
const event = {
|
||||
@@ -564,7 +741,7 @@ test("Agent UI projection summaries classify host-neutral event groups", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("Agent UI team workbench summaries group surfaces and lanes", () => {
|
||||
test("Agent UI subagents summaries group surfaces and lanes", () => {
|
||||
const events = [
|
||||
{
|
||||
type: "team.changed",
|
||||
@@ -624,9 +801,9 @@ test("Agent UI team workbench summaries group surfaces and lanes", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const summary = summarizeAgentUiTeamWorkbenchProjectionEvents(events);
|
||||
const lanes = summarizeAgentUiTeamWorkbenchSurfaceLanes(events);
|
||||
const surfaces = summarizeAgentUiTeamWorkbenchSurfaces(events, {
|
||||
const summary = summarizeAgentUiSubagentsProjectionEvents(events);
|
||||
const lanes = summarizeAgentUiSubagentsSurfaceLanes(events);
|
||||
const surfaces = summarizeAgentUiSubagentsSurfaces(events, {
|
||||
latestLimit: 1,
|
||||
});
|
||||
|
||||
|
||||
@@ -29,9 +29,11 @@ React 是 peer dependency:
|
||||
- 渲染 `UIMessageParts` 消息部件。
|
||||
- 渲染 `ProcessTimeline` 过程时间线。
|
||||
- 渲染 `ExecutionGraph` 执行图。
|
||||
- 渲染 `state.subagents` 标准子代理模型。
|
||||
- 渲染 Agent 消息时间线。
|
||||
- 渲染运行事实摘要。
|
||||
- 渲染 action、evidence、artifact、tool 等 runtime facts。
|
||||
- 渲染 `ArtifactRef` / `EvidenceRef` 轻量引用列表,并把打开意图交还宿主应用。
|
||||
- 渲染标准多 action controls,并通过 `data-action-decision` 暴露稳定 DOM contract。
|
||||
- 通过 callback 把用户处理 action 的意图交还给宿主应用。
|
||||
|
||||
@@ -45,6 +47,8 @@ React 是 peer dependency:
|
||||
|
||||
## Usage
|
||||
|
||||
最推荐的入口是 `AgentUiProjectionView`。宿主先用 projection 包得到 `AgentUiProjectionState`,再把 state 传给 UI 包:
|
||||
|
||||
```tsx
|
||||
import { AgentUiProjectionView } from "@limecloud/agent-runtime-ui";
|
||||
import { projectAgentUiState } from "@limecloud/agent-runtime-projection";
|
||||
@@ -73,22 +77,90 @@ export function AgentPanel() {
|
||||
}
|
||||
```
|
||||
|
||||
如果宿主已经通过 App Server facts replay 得到 state,也可以直接渲染:
|
||||
|
||||
```tsx
|
||||
import { AgentUiProjectionView } from "@limecloud/agent-runtime-ui";
|
||||
import { replayAppServerFacts } from "@limecloud/agent-runtime-projection";
|
||||
|
||||
const replay = replayAppServerFacts({
|
||||
readModel,
|
||||
events,
|
||||
evidenceExport,
|
||||
});
|
||||
|
||||
<AgentUiProjectionView
|
||||
state={replay.state}
|
||||
onResolveAction={(event, action) => {
|
||||
actionResponder.respond(event, action);
|
||||
}}
|
||||
/>;
|
||||
```
|
||||
|
||||
`onResolveAction` 只表达用户意图。真正的后端调用应回到宿主的 runtime client,例如 `AgentRuntimeClient.respondAction(...)`。
|
||||
|
||||
## React Surface Model
|
||||
|
||||
这个包采用 controlled component 模式:宿主传入 `AgentUiProjectionState` 和 command callbacks,组件只负责渲染、稳定 DOM contract 和用户意图回调。
|
||||
|
||||
| Surface | Standard input | Owner of business behavior |
|
||||
| --- | --- | --- |
|
||||
| Message parts | `state.messages` | 宿主提供消息列表区域布局和本地化 label。 |
|
||||
| Process timeline | `state.timeline` | projection 负责排序和 entry kind;宿主负责样式。 |
|
||||
| Execution graph | `state.graph` | projection 负责 parent/child edge;宿主负责布局密度。 |
|
||||
| Action required | `state.actions` | 宿主通过 runtime client 提交 action response。 |
|
||||
| Tool group | `state.tools` | projection 负责 tool lifecycle 解释。 |
|
||||
| Subagents | `state.subagents` | projection 负责子代理线程、委派调用、活动摘要和隔离摘要。 |
|
||||
| Artifact refs | `state.artifacts` | 宿主负责打开 artifact workspace 或详情页。 |
|
||||
| Evidence refs | `state.evidence` | 宿主负责打开 evidence pack、review 或 replay。 |
|
||||
| Runtime summary | `state.readModel` | projection/read model 负责计数;宿主负责显示文案。 |
|
||||
|
||||
当前包不提供 hooks。`useAgentRuntimeClient`、`useAgentRuntimeFacts`、`useAgentUiProjector` 等 hook 应留在产品应用或后续单独 adapter 包里;共享 React primitives 不订阅 runtime、不持有 session store、不读取 App Server。
|
||||
|
||||
## Components
|
||||
|
||||
- `AgentTimeline`
|
||||
- `AgentUiProjectionView`
|
||||
- `UIMessagePartsView`
|
||||
- `ProcessTimelineView`
|
||||
- `ExecutionGraphView`
|
||||
- `ToolGroup`
|
||||
- `ActionRequiredList`
|
||||
- `RuntimeFactsPanel`
|
||||
- `RuntimeFactsSummary`
|
||||
- `RuntimeEventList`
|
||||
- `RuntimeFactCard`
|
||||
- `ActionCard`
|
||||
- `EvidenceCard`
|
||||
- `ArtifactCard`
|
||||
| 组件 | 输入 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| `AgentUiProjectionView` | `AgentUiProjectionState` | 标准组合入口,渲染消息、过程、事实栏、action 和 graph。 |
|
||||
| `UIMessagePartsView` | `UIMessageParts` | 渲染标准消息部件。 |
|
||||
| `ProcessTimelineView` | `ProcessTimeline` | 渲染线性执行过程。 |
|
||||
| `ExecutionGraphView` | `ExecutionGraph` | 渲染 run / task / tool / subagent 结构。 |
|
||||
| `SubagentsView` | `AgentUiProjectionState` | 从 `state.subagents` 渲染子代理标准面。 |
|
||||
| `SubagentThreadList` | `AgentUiSubagentThreadView[]` | 渲染子代理线程、身份、状态、refs 摘要。 |
|
||||
| `SubagentDelegationList` | `AgentUiSubagentDelegationView[]` | 渲染 spawn / handoff / wait / interrupt 等委派调用。 |
|
||||
| `SubagentActivityList` | `AgentUiSubagentActivityView[]` | 渲染 started / handoff / review / completed / failed 等活动摘要。 |
|
||||
| `ArtifactRefList` | `AgentUiArtifactRefView[]` | 渲染 artifact refs,不读取大 payload。 |
|
||||
| `EvidenceRefList` | `AgentUiEvidenceRefView[]` | 渲染 evidence refs,不读取证据内容。 |
|
||||
| `AgentUiRefList` | `AgentUiRefView[]` | refs 通用 primitive。 |
|
||||
| `RuntimeFactsPanel` | `AgentRuntimeReadModel` | 兼容事实栏入口。 |
|
||||
| `RuntimeFactsSummary` | `AgentRuntimeReadModel` | 渲染 source / action / artifact / evidence 计数。 |
|
||||
| `RuntimeEventList` | `AgentRuntimeEventProjection[]` | 渲染一般 runtime event 列表。 |
|
||||
| `ToolGroup` | tool events | 渲染工具调用分组。 |
|
||||
| `ActionRequiredList` | action events | 渲染待处理 action。 |
|
||||
| `RuntimeFactCard` | runtime event | 渲染单个 runtime fact。 |
|
||||
| `ActionCard` / `EvidenceCard` / `ArtifactCard` | runtime event | 语义化 card alias,方便宿主样式分层。 |
|
||||
| `AgentTimeline` | host message list | 兼容传统消息列表,不是新 projection 默认入口。 |
|
||||
|
||||
## Labels And I18n
|
||||
|
||||
默认 label 只是 fallback。Lime 产品应用必须传入自己的 i18n label 和 formatter:
|
||||
|
||||
```tsx
|
||||
<AgentUiProjectionView
|
||||
state={state}
|
||||
labels={{
|
||||
messagePartsAriaLabel: t("agent.messages.aria"),
|
||||
processTimelineAriaLabel: t("agent.timeline.aria"),
|
||||
executionGraphAriaLabel: t("agent.graph.aria"),
|
||||
runtimeSummaryAriaLabel: t("agent.summary.aria"),
|
||||
actionRequiredAriaLabel: t("agent.actions.aria"),
|
||||
actionButtonLabel: (action) => t(`agent.action.${action.decision}`),
|
||||
eventStatusLabel: (event) => t(`agent.eventStatus.${event.status}`),
|
||||
}}
|
||||
/>;
|
||||
```
|
||||
|
||||
宿主需要覆盖 `zh-CN / zh-TW / en-US / ja-JP / ko-KR`,不要把本包 fallback 当成产品文案。
|
||||
|
||||
## Source Layout
|
||||
|
||||
@@ -100,7 +172,9 @@ src/labels.ts -> 默认 label、status 和 meta formatter
|
||||
src/messages.tsx -> AgentTimeline / UIMessagePartsView
|
||||
src/processTimeline.tsx -> ProcessTimelineView
|
||||
src/executionGraph.tsx -> ExecutionGraphView
|
||||
src/refs.tsx -> ArtifactRefList / EvidenceRefList / AgentUiRefList
|
||||
src/runtimeFacts.tsx -> RuntimeFactsPanel / RuntimeFactCard / action/tool lists
|
||||
src/subagents.tsx -> SubagentsView / SubagentThreadList / SubagentDelegationList / SubagentActivityList
|
||||
src/projectionView.tsx -> AgentUiProjectionView 标准组合入口
|
||||
src/index.ts -> barrel exports only
|
||||
```
|
||||
@@ -131,6 +205,18 @@ src/index.ts -> barrel exports only
|
||||
- `agent-execution-events`
|
||||
- `agent-tool-group`
|
||||
- `agent-action-required-list`
|
||||
- `agent-subagents`
|
||||
- `agent-subagent-threads`
|
||||
- `agent-subagent-thread`
|
||||
- `agent-subagent-delegations`
|
||||
- `agent-subagent-delegation`
|
||||
- `agent-subagent-activities`
|
||||
- `agent-subagent-activity`
|
||||
- `agent-ref-list`
|
||||
- `agent-ref-card`
|
||||
- `agent-ref-action`
|
||||
- `agent-artifact-refs`
|
||||
- `agent-evidence-refs`
|
||||
- `agent-execution-graph`
|
||||
- `agent-execution-node`
|
||||
- `agent-event-surface`
|
||||
@@ -143,6 +229,25 @@ src/index.ts -> barrel exports only
|
||||
|
||||
这些 class names 是包级 CSS contract。宿主应用可以用自己的设计系统实现样式,不应把样式文件或页面壳反向变成包依赖。
|
||||
|
||||
稳定 data attributes:
|
||||
|
||||
| Attribute | Surface | 用途 |
|
||||
| --- | --- | --- |
|
||||
| `data-runtime-status` | `AgentUiProjectionView` | 当前 runtime status。 |
|
||||
| `data-hydration-status` | `AgentUiProjectionView` | hydration / stale / repair 状态。 |
|
||||
| `data-action-decision` | action buttons | approve / reject / answer / retry 等 action intent。 |
|
||||
| `data-node-id` / `data-node-type` / `data-parent-id` | graph | graph node 和 lineage。 |
|
||||
| `data-subagent-count` / `data-delegation-count` / `data-activity-count` | Subagents | 子代理 smoke / snapshot 断言。 |
|
||||
| `data-thread-id` / `data-subagent-id` / `data-parent-thread-id` | Subagents | 子代理线程身份和父子关系。 |
|
||||
| `data-delegation-action` / `data-target-thread-ids` | Subagents | 委派调用类型和目标线程。 |
|
||||
| `data-activity-kind` / `data-source-event-id` | Subagents | 活动分类和来源事件。 |
|
||||
| `data-event-class` | runtime fact cards | `tool.*`、`handoff.*`、`review.*` 等事件族。 |
|
||||
| `data-ref-kind` / `data-ref-id` / `data-source-event-id` | artifact / evidence refs | 引用类型、引用 id 和来源事件。 |
|
||||
|
||||
## Accessibility
|
||||
|
||||
默认 aria label 只是 fallback。产品应用必须通过 `labels` 注入本地化 aria label,并确保可点击 action 使用真实 `<button>`。组件不会把图标、颜色或状态文本作为唯一语义;状态同时通过 class / data attributes 暴露给宿主样式和测试。
|
||||
|
||||
## Backend Integration
|
||||
|
||||
推荐数据流:
|
||||
@@ -174,6 +279,85 @@ App Server JSON-RPC
|
||||
|
||||
`RuntimeFactsPanel` 只渲染宿主传入的 read model,不会订阅 JSON-RPC,也不会判断某个 artifact 是否已经被业务层物化。宿主如果已经把 App Server `artifact.snapshot` 写成业务产物,应在进入 `@limecloud/agent-runtime-projection` 前去掉上游快照事件,只保留本地业务产物的 `artifact.changed`。这样右侧事实栏展示的是可继续编辑、可应用的交付物,而不是后端中间事件。
|
||||
|
||||
## Product App Pattern
|
||||
|
||||
产品应用通常这样接入:
|
||||
|
||||
```tsx
|
||||
function ProductAgentPanel({ sessionId }: { sessionId: string }) {
|
||||
const runtime = useAgentRuntimeClient();
|
||||
const facts = useAgentRuntimeFacts(sessionId);
|
||||
const state = projectAgentUiState({
|
||||
executionEvents: facts.executionEvents,
|
||||
sourceCount: facts.sourceCount,
|
||||
});
|
||||
|
||||
return (
|
||||
<AgentUiProjectionView
|
||||
state={state}
|
||||
artifact={<ProductArtifactWorkspace sessionId={sessionId} />}
|
||||
onResolveAction={(event, action) => {
|
||||
runtime.respondAction({
|
||||
sessionId,
|
||||
requestId: event.actionId ?? event.id,
|
||||
actionType: "ask_user",
|
||||
confirmed: action.decision !== "reject",
|
||||
response: action.decision,
|
||||
});
|
||||
}}
|
||||
labels={productAgentLabels}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
这段模式里,UI 包只负责渲染和回调。`useAgentRuntimeClient`、`useAgentRuntimeFacts`、业务 artifact workspace、权限判断和路由都属于宿主。
|
||||
|
||||
Subagents 的接入也必须走标准 projection state:
|
||||
|
||||
```tsx
|
||||
import { SubagentsView } from "@limecloud/agent-runtime-ui";
|
||||
|
||||
<SubagentsView
|
||||
state={state}
|
||||
labels={{
|
||||
subagentsAriaLabel: t("agent.subagents"),
|
||||
subagentThreadsAriaLabel: t("agent.subagents.threads"),
|
||||
subagentDelegationsAriaLabel: t("agent.subagents.delegations"),
|
||||
subagentActivitiesAriaLabel: t("agent.subagents.activities"),
|
||||
}}
|
||||
/>;
|
||||
```
|
||||
|
||||
`SubagentsView` 只读取 `state.subagents`。如果业务组件需要自定义子代理布局,也应消费同一个模型,而不是重新过滤 `state.graph` 或 `state.readModel.visibleEvents`。
|
||||
|
||||
## Testing
|
||||
|
||||
React surface 测试应使用 contracts fixture 和 projection replay,不要自己编私有状态:
|
||||
|
||||
```tsx
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { getAgentUiFixture } from "@limecloud/agent-ui-contracts";
|
||||
import { replayAgentUiFixture } from "@limecloud/agent-runtime-projection";
|
||||
import { AgentUiProjectionView } from "@limecloud/agent-runtime-ui";
|
||||
|
||||
const replay = replayAgentUiFixture(getAgentUiFixture("hitl-action"));
|
||||
const html = renderToStaticMarkup(
|
||||
<AgentUiProjectionView state={replay.state} />,
|
||||
);
|
||||
|
||||
expect(html).toContain("data-action-decision");
|
||||
```
|
||||
|
||||
组件测试只覆盖渲染、class / data attribute contract 和 callback 接线。复杂状态机、事件归并和 summary 归类应在 projection 包测试。
|
||||
|
||||
标准 fixture smoke 至少覆盖:
|
||||
|
||||
- `hitl-action` 渲染 `data-action-decision`。
|
||||
- `tool-success` / `tool-failure` 渲染 ToolGroup。
|
||||
- `artifact-evidence` 渲染 artifact / evidence refs。
|
||||
- `subagent-handoff` 渲染 `SubagentsView`、threads、delegation calls、activities。
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
@@ -183,6 +367,26 @@ npm --prefix packages/agent-runtime-ui run test
|
||||
npm --prefix packages/agent-runtime-ui pack --dry-run
|
||||
```
|
||||
|
||||
## Package Metadata
|
||||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| Runtime | Node `>=20`,ESM。 |
|
||||
| Peer deps | `react >=18 <20`、`react-dom >=18 <20`。 |
|
||||
| Dependencies | `@limecloud/agent-ui-contracts`。 |
|
||||
| Dev dependency | `@limecloud/agent-runtime-projection` 仅用于 fixture replay 测试。 |
|
||||
| Side effects | `false`,不注入全局 CSS。 |
|
||||
| Public files | `dist`、`README.md`。 |
|
||||
| License | `MIT`。 |
|
||||
|
||||
## Do Not
|
||||
|
||||
- 不要在本包里创建 runtime client、订阅 JSON-RPC、调用 Electron bridge 或访问 App Server。
|
||||
- 不要在本包里拼 Provider 参数、读取 API key、解释 tool lifecycle 或合成 evidence。
|
||||
- 不要把业务页面、workspace、Prompt 草稿、素材预览或审核流放进共享 UI 包。
|
||||
- 不要在 React 组件里重写 projection reducer、scope selector、summary selector 或 runtime facts 解释。
|
||||
- 不要让默认英文 fallback 进入 Lime 产品文案;产品应用必须注入本地化 label。
|
||||
|
||||
## Publish
|
||||
|
||||
首次发布到 npmjs:
|
||||
|
||||
@@ -2,5 +2,7 @@ export type * from "./types.js";
|
||||
export * from "./messages.js";
|
||||
export * from "./processTimeline.js";
|
||||
export * from "./executionGraph.js";
|
||||
export * from "./refs.js";
|
||||
export * from "./runtimeFacts.js";
|
||||
export * from "./subagents.js";
|
||||
export * from "./projectionView.js";
|
||||
|
||||
@@ -3,7 +3,9 @@ import type { AgentRuntimeExecutionEvent } from "@limecloud/agent-ui-contracts";
|
||||
import { ExecutionGraphView } from "./executionGraph.js";
|
||||
import { UIMessagePartsView } from "./messages.js";
|
||||
import { ProcessTimelineView } from "./processTimeline.js";
|
||||
import { ArtifactRefList, EvidenceRefList } from "./refs.js";
|
||||
import { ActionRequiredList, RuntimeEventList, RuntimeFactsSummary, ToolGroup } from "./runtimeFacts.js";
|
||||
import { SubagentsView } from "./subagents.js";
|
||||
import type { AgentUiProjectionViewProps } from "./types.js";
|
||||
|
||||
export function AgentUiProjectionView<TEvent extends AgentRuntimeExecutionEvent = AgentRuntimeExecutionEvent>({
|
||||
@@ -12,6 +14,9 @@ export function AgentUiProjectionView<TEvent extends AgentRuntimeExecutionEvent
|
||||
emptyMessages,
|
||||
labels,
|
||||
onResolveAction,
|
||||
onSelectArtifactRef,
|
||||
onSelectEvidenceRef,
|
||||
onOpenSubagentThread,
|
||||
}: AgentUiProjectionViewProps<TEvent>) {
|
||||
const otherEvents = state.readModel.visibleEvents.filter(
|
||||
(event) => event.surface !== "human-action" && event.surface !== "tool",
|
||||
@@ -64,6 +69,29 @@ export function AgentUiProjectionView<TEvent extends AgentRuntimeExecutionEvent
|
||||
actionButtonLabel={labels?.actionButtonLabel}
|
||||
eventStatusLabel={labels?.eventStatusLabel}
|
||||
/>
|
||||
<ArtifactRefList
|
||||
refs={state.artifacts}
|
||||
ariaLabel={labels?.artifactRefsAriaLabel}
|
||||
refTitle={labels?.artifactRefTitle}
|
||||
refMeta={labels?.artifactRefMeta}
|
||||
refPreview={labels?.artifactRefPreview}
|
||||
refActionLabel={labels?.artifactRefActionLabel}
|
||||
onSelectRef={onSelectArtifactRef}
|
||||
/>
|
||||
<EvidenceRefList
|
||||
refs={state.evidence}
|
||||
ariaLabel={labels?.evidenceRefsAriaLabel}
|
||||
refTitle={labels?.evidenceRefTitle}
|
||||
refMeta={labels?.evidenceRefMeta}
|
||||
refPreview={labels?.evidenceRefPreview}
|
||||
refActionLabel={labels?.evidenceRefActionLabel}
|
||||
onSelectRef={onSelectEvidenceRef}
|
||||
/>
|
||||
<SubagentsView
|
||||
state={state}
|
||||
labels={labels}
|
||||
onOpenThread={onOpenSubagentThread}
|
||||
/>
|
||||
{artifact ? <div className="agent-session-artifact">{artifact}</div> : null}
|
||||
<ExecutionGraphView
|
||||
nodes={state.graph}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import type {
|
||||
AgentUiArtifactRefView,
|
||||
AgentUiEvidenceRefView,
|
||||
AgentUiRefView,
|
||||
} from "@limecloud/agent-ui-contracts";
|
||||
|
||||
import type {
|
||||
AgentUiRefListProps,
|
||||
ArtifactRefListProps,
|
||||
EvidenceRefListProps,
|
||||
} from "./types.js";
|
||||
|
||||
function defaultRefTitle(ref: AgentUiRefView): ReactNode {
|
||||
return ref.title ?? ref.id;
|
||||
}
|
||||
|
||||
function defaultRefMeta(ref: AgentUiRefView): ReactNode {
|
||||
return ref.path ?? ref.contentRef ?? ref.sourceEventId;
|
||||
}
|
||||
|
||||
function defaultRefPreview(ref: AgentUiRefView): ReactNode {
|
||||
return ref.preview;
|
||||
}
|
||||
|
||||
export function AgentUiRefList<TRef extends AgentUiRefView = AgentUiRefView>({
|
||||
refs = [],
|
||||
empty,
|
||||
ariaLabel = "Runtime refs",
|
||||
className = "agent-ref-list",
|
||||
refKind = "ref",
|
||||
refTitle = defaultRefTitle,
|
||||
refMeta = defaultRefMeta,
|
||||
refPreview = defaultRefPreview,
|
||||
refActionLabel,
|
||||
onSelectRef,
|
||||
}: AgentUiRefListProps<TRef>) {
|
||||
if (!refs.length) return empty === undefined ? null : <div className={`${className}-empty`}>{empty}</div>;
|
||||
|
||||
return (
|
||||
<div className={className} aria-label={ariaLabel} data-ref-kind={refKind}>
|
||||
{refs.map((ref) => (
|
||||
<article
|
||||
key={ref.id}
|
||||
className="agent-ref-card"
|
||||
data-ref-kind={refKind}
|
||||
data-ref-id={ref.id}
|
||||
data-source-event-id={ref.sourceEventId}
|
||||
data-ref-status={ref.status}
|
||||
data-content-ref={ref.contentRef}
|
||||
>
|
||||
<div>
|
||||
<small>{refKind}</small>
|
||||
<strong>{refTitle(ref)}</strong>
|
||||
<em>{refMeta(ref)}</em>
|
||||
</div>
|
||||
{refPreview ? <p>{refPreview(ref)}</p> : null}
|
||||
{onSelectRef ? (
|
||||
<button type="button" className="agent-ref-action" onClick={() => onSelectRef(ref)}>
|
||||
{refActionLabel ? refActionLabel(ref) : "Open"}
|
||||
</button>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArtifactRefList({
|
||||
ariaLabel = "Artifact refs",
|
||||
className = "agent-artifact-refs",
|
||||
refKind = "artifact",
|
||||
...props
|
||||
}: ArtifactRefListProps) {
|
||||
return (
|
||||
<AgentUiRefList<AgentUiArtifactRefView>
|
||||
{...props}
|
||||
ariaLabel={ariaLabel}
|
||||
className={className}
|
||||
refKind={refKind}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function EvidenceRefList({
|
||||
ariaLabel = "Evidence refs",
|
||||
className = "agent-evidence-refs",
|
||||
refKind = "evidence",
|
||||
...props
|
||||
}: EvidenceRefListProps) {
|
||||
return (
|
||||
<AgentUiRefList<AgentUiEvidenceRefView>
|
||||
{...props}
|
||||
ariaLabel={ariaLabel}
|
||||
className={className}
|
||||
refKind={refKind}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -72,7 +72,7 @@ export function RuntimeFactCard<TEvent extends AgentRuntimeExecutionEvent = Agen
|
||||
: [];
|
||||
return (
|
||||
<article
|
||||
className={event.status}
|
||||
className={`agent-runtime-event ${event.status}`}
|
||||
data-event-id={event.id}
|
||||
data-event-class={event.source.eventClass}
|
||||
data-owner={event.source.owner}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import type {
|
||||
AgentRuntimeExecutionEvent,
|
||||
AgentUiSubagentActivityView,
|
||||
AgentUiSubagentDelegationView,
|
||||
AgentUiSubagentThreadView,
|
||||
} from "@limecloud/agent-ui-contracts";
|
||||
|
||||
import type {
|
||||
SubagentActivityListProps,
|
||||
SubagentDelegationListProps,
|
||||
SubagentsViewProps,
|
||||
SubagentThreadListProps,
|
||||
} from "./types.js";
|
||||
|
||||
function defaultThreadTitle(thread: AgentUiSubagentThreadView) {
|
||||
return thread.nickname ?? thread.role ?? thread.title;
|
||||
}
|
||||
|
||||
function defaultThreadMeta(thread: AgentUiSubagentThreadView) {
|
||||
return [thread.status, thread.taskPath ?? thread.threadId]
|
||||
.filter(Boolean)
|
||||
.join(" / ");
|
||||
}
|
||||
|
||||
function defaultThreadSummary(thread: AgentUiSubagentThreadView) {
|
||||
return thread.summary ?? thread.promptPreview;
|
||||
}
|
||||
|
||||
function defaultDelegationTitle(delegation: AgentUiSubagentDelegationView) {
|
||||
return `${delegation.action}: ${delegation.title}`;
|
||||
}
|
||||
|
||||
function defaultActivityTitle(activity: AgentUiSubagentActivityView) {
|
||||
return activity.title;
|
||||
}
|
||||
|
||||
function defaultActivityMeta(activity: AgentUiSubagentActivityView) {
|
||||
return `${activity.kind} / ${activity.status}`;
|
||||
}
|
||||
|
||||
export function SubagentThreadList({
|
||||
threads = [],
|
||||
empty,
|
||||
ariaLabel = "Subagent threads",
|
||||
threadTitle = defaultThreadTitle,
|
||||
threadMeta = defaultThreadMeta,
|
||||
threadSummary = defaultThreadSummary,
|
||||
threadActionLabel,
|
||||
onOpenThread,
|
||||
}: SubagentThreadListProps) {
|
||||
if (!threads.length) {
|
||||
return empty === undefined ? null : <div className="agent-subagent-threads-empty">{empty}</div>;
|
||||
}
|
||||
return (
|
||||
<div className="agent-subagent-threads" aria-label={ariaLabel}>
|
||||
{threads.map((thread) => (
|
||||
<article
|
||||
key={thread.threadId}
|
||||
className={`agent-subagent-thread ${thread.status}`}
|
||||
data-thread-id={thread.threadId}
|
||||
data-subagent-id={thread.subagentId}
|
||||
data-parent-thread-id={thread.parentThreadId}
|
||||
data-task-id={thread.taskId}
|
||||
data-subagent-status={thread.status}
|
||||
>
|
||||
<div>
|
||||
<small>{threadMeta(thread)}</small>
|
||||
<strong>{threadTitle(thread)}</strong>
|
||||
{threadSummary(thread) ? <p>{threadSummary(thread)}</p> : null}
|
||||
</div>
|
||||
{onOpenThread ? (
|
||||
<button type="button" onClick={() => onOpenThread(thread)}>
|
||||
{threadActionLabel ? threadActionLabel(thread) : "Open"}
|
||||
</button>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SubagentDelegationList({
|
||||
delegations = [],
|
||||
empty,
|
||||
ariaLabel = "Subagent delegations",
|
||||
delegationTitle = defaultDelegationTitle,
|
||||
}: SubagentDelegationListProps) {
|
||||
if (!delegations.length) {
|
||||
return empty === undefined ? null : <div className="agent-subagent-delegations-empty">{empty}</div>;
|
||||
}
|
||||
return (
|
||||
<div className="agent-subagent-delegations" aria-label={ariaLabel}>
|
||||
{delegations.map((delegation) => (
|
||||
<article
|
||||
key={delegation.callId}
|
||||
className={`agent-subagent-delegation ${delegation.status}`}
|
||||
data-delegation-call-id={delegation.callId}
|
||||
data-delegation-action={delegation.action}
|
||||
data-parent-thread-id={delegation.parentThreadId}
|
||||
data-target-thread-ids={delegation.targetThreadIds.join(" ")}
|
||||
>
|
||||
<small>{delegation.status}</small>
|
||||
<strong>{delegationTitle(delegation)}</strong>
|
||||
{delegation.promptPreview ? <p>{delegation.promptPreview}</p> : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SubagentActivityList({
|
||||
activities = [],
|
||||
empty,
|
||||
ariaLabel = "Subagent activities",
|
||||
activityTitle = defaultActivityTitle,
|
||||
activityMeta = defaultActivityMeta,
|
||||
}: SubagentActivityListProps) {
|
||||
if (!activities.length) {
|
||||
return empty === undefined ? null : <div className="agent-subagent-activities-empty">{empty}</div>;
|
||||
}
|
||||
return (
|
||||
<div className="agent-subagent-activities" aria-label={ariaLabel}>
|
||||
{activities.map((activity) => (
|
||||
<article
|
||||
key={activity.activityId}
|
||||
className={`agent-subagent-activity ${activity.status}`}
|
||||
data-activity-id={activity.activityId}
|
||||
data-thread-id={activity.threadId}
|
||||
data-activity-kind={activity.kind}
|
||||
data-source-event-id={activity.sourceEventId}
|
||||
>
|
||||
<small>{activityMeta(activity)}</small>
|
||||
<strong>{activityTitle(activity)}</strong>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SubagentsView<TEvent extends AgentRuntimeExecutionEvent = AgentRuntimeExecutionEvent>({
|
||||
state,
|
||||
emptyThreads,
|
||||
emptyDelegations,
|
||||
emptyActivities,
|
||||
labels,
|
||||
onOpenThread,
|
||||
}: SubagentsViewProps<TEvent>) {
|
||||
const model = state.subagents;
|
||||
|
||||
if (!model.hasSubagents) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="agent-subagents"
|
||||
aria-label={labels?.subagentsAriaLabel ?? "Subagents"}
|
||||
data-subagent-count={model.threads.length}
|
||||
data-delegation-count={model.delegationCalls.length}
|
||||
data-activity-count={model.activities.length}
|
||||
data-active-subagent-count={model.activeThreadIds.length}
|
||||
>
|
||||
<SubagentThreadList
|
||||
threads={model.threads}
|
||||
empty={emptyThreads}
|
||||
ariaLabel={labels?.subagentThreadsAriaLabel}
|
||||
threadTitle={labels?.subagentThreadTitle}
|
||||
threadMeta={labels?.subagentThreadMeta}
|
||||
threadSummary={labels?.subagentThreadSummary}
|
||||
onOpenThread={onOpenThread}
|
||||
/>
|
||||
<SubagentDelegationList
|
||||
delegations={model.delegationCalls}
|
||||
empty={emptyDelegations}
|
||||
ariaLabel={labels?.subagentDelegationsAriaLabel}
|
||||
delegationTitle={labels?.subagentDelegationTitle}
|
||||
/>
|
||||
<SubagentActivityList
|
||||
activities={model.activities}
|
||||
empty={emptyActivities}
|
||||
ariaLabel={labels?.subagentActivitiesAriaLabel}
|
||||
activityTitle={labels?.subagentActivityTitle}
|
||||
activityMeta={labels?.subagentActivityMeta}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,12 @@ import type { ReactNode } from "react";
|
||||
|
||||
import type {
|
||||
AgentUiProjectionState,
|
||||
AgentUiArtifactRefView,
|
||||
AgentUiEvidenceRefView,
|
||||
AgentUiRefView,
|
||||
AgentUiSubagentActivityView,
|
||||
AgentUiSubagentDelegationView,
|
||||
AgentUiSubagentThreadView,
|
||||
AgentRuntimeActionProjection,
|
||||
AgentRuntimeEventProjection,
|
||||
AgentRuntimeExecutionEvent,
|
||||
@@ -116,10 +122,33 @@ export interface ExecutionGraphViewProps {
|
||||
nodeMeta?: (node: ExecutionGraphNode) => ReactNode;
|
||||
}
|
||||
|
||||
export interface AgentUiRefListProps<TRef extends AgentUiRefView = AgentUiRefView> {
|
||||
refs?: readonly TRef[];
|
||||
empty?: ReactNode;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
refKind?: string;
|
||||
refTitle?: (ref: TRef) => ReactNode;
|
||||
refMeta?: (ref: TRef) => ReactNode;
|
||||
refPreview?: (ref: TRef) => ReactNode;
|
||||
refActionLabel?: (ref: TRef) => ReactNode;
|
||||
onSelectRef?: (ref: TRef) => void;
|
||||
}
|
||||
|
||||
export type ArtifactRefListProps = AgentUiRefListProps<AgentUiArtifactRefView>;
|
||||
|
||||
export type EvidenceRefListProps = AgentUiRefListProps<AgentUiEvidenceRefView>;
|
||||
|
||||
export interface AgentUiProjectionViewLabels<TEvent extends AgentRuntimeExecutionEvent = AgentRuntimeExecutionEvent> {
|
||||
messagePartsAriaLabel?: string;
|
||||
processTimelineAriaLabel?: string;
|
||||
executionGraphAriaLabel?: string;
|
||||
artifactRefsAriaLabel?: string;
|
||||
evidenceRefsAriaLabel?: string;
|
||||
subagentsAriaLabel?: string;
|
||||
subagentThreadsAriaLabel?: string;
|
||||
subagentDelegationsAriaLabel?: string;
|
||||
subagentActivitiesAriaLabel?: string;
|
||||
runtimeSummaryAriaLabel?: string;
|
||||
executionEventsAriaLabel?: string;
|
||||
toolGroupAriaLabel?: string;
|
||||
@@ -133,6 +162,20 @@ export interface AgentUiProjectionViewLabels<TEvent extends AgentRuntimeExecutio
|
||||
timelineEntryMeta?: ProcessTimelineViewProps["entryMeta"];
|
||||
graphNodeTitle?: ExecutionGraphViewProps["nodeTitle"];
|
||||
graphNodeMeta?: ExecutionGraphViewProps["nodeMeta"];
|
||||
artifactRefTitle?: ArtifactRefListProps["refTitle"];
|
||||
artifactRefMeta?: ArtifactRefListProps["refMeta"];
|
||||
artifactRefPreview?: ArtifactRefListProps["refPreview"];
|
||||
artifactRefActionLabel?: ArtifactRefListProps["refActionLabel"];
|
||||
evidenceRefTitle?: EvidenceRefListProps["refTitle"];
|
||||
evidenceRefMeta?: EvidenceRefListProps["refMeta"];
|
||||
evidenceRefPreview?: EvidenceRefListProps["refPreview"];
|
||||
evidenceRefActionLabel?: EvidenceRefListProps["refActionLabel"];
|
||||
subagentThreadTitle?: SubagentThreadListProps["threadTitle"];
|
||||
subagentThreadMeta?: SubagentThreadListProps["threadMeta"];
|
||||
subagentThreadSummary?: SubagentThreadListProps["threadSummary"];
|
||||
subagentDelegationTitle?: SubagentDelegationListProps["delegationTitle"];
|
||||
subagentActivityTitle?: SubagentActivityListProps["activityTitle"];
|
||||
subagentActivityMeta?: SubagentActivityListProps["activityMeta"];
|
||||
actionButtonLabel?: RuntimeFactCardProps<TEvent>["actionButtonLabel"];
|
||||
eventStatusLabel?: RuntimeFactCardProps<TEvent>["eventStatusLabel"];
|
||||
}
|
||||
@@ -143,4 +186,54 @@ export interface AgentUiProjectionViewProps<TEvent extends AgentRuntimeExecution
|
||||
emptyMessages?: ReactNode;
|
||||
labels?: AgentUiProjectionViewLabels<TEvent>;
|
||||
onResolveAction?: AgentRuntimeActionResolver<TEvent>;
|
||||
onSelectArtifactRef?: ArtifactRefListProps["onSelectRef"];
|
||||
onSelectEvidenceRef?: EvidenceRefListProps["onSelectRef"];
|
||||
onOpenSubagentThread?: SubagentsViewProps["onOpenThread"];
|
||||
}
|
||||
|
||||
export interface SubagentThreadListProps {
|
||||
threads?: readonly AgentUiSubagentThreadView[];
|
||||
empty?: ReactNode;
|
||||
ariaLabel?: string;
|
||||
threadTitle?: (thread: AgentUiSubagentThreadView) => ReactNode;
|
||||
threadMeta?: (thread: AgentUiSubagentThreadView) => ReactNode;
|
||||
threadSummary?: (thread: AgentUiSubagentThreadView) => ReactNode;
|
||||
threadActionLabel?: (thread: AgentUiSubagentThreadView) => ReactNode;
|
||||
onOpenThread?: (thread: AgentUiSubagentThreadView) => void;
|
||||
}
|
||||
|
||||
export interface SubagentDelegationListProps {
|
||||
delegations?: readonly AgentUiSubagentDelegationView[];
|
||||
empty?: ReactNode;
|
||||
ariaLabel?: string;
|
||||
delegationTitle?: (delegation: AgentUiSubagentDelegationView) => ReactNode;
|
||||
}
|
||||
|
||||
export interface SubagentActivityListProps {
|
||||
activities?: readonly AgentUiSubagentActivityView[];
|
||||
empty?: ReactNode;
|
||||
ariaLabel?: string;
|
||||
activityTitle?: (activity: AgentUiSubagentActivityView) => ReactNode;
|
||||
activityMeta?: (activity: AgentUiSubagentActivityView) => ReactNode;
|
||||
}
|
||||
|
||||
export interface SubagentsViewProps<TEvent extends AgentRuntimeExecutionEvent = AgentRuntimeExecutionEvent> {
|
||||
state: AgentUiProjectionState<TEvent>;
|
||||
emptyThreads?: ReactNode;
|
||||
emptyDelegations?: ReactNode;
|
||||
emptyActivities?: ReactNode;
|
||||
onOpenThread?: (thread: AgentUiSubagentThreadView) => void;
|
||||
labels?: Pick<
|
||||
AgentUiProjectionViewLabels<TEvent>,
|
||||
| "subagentsAriaLabel"
|
||||
| "subagentThreadsAriaLabel"
|
||||
| "subagentDelegationsAriaLabel"
|
||||
| "subagentActivitiesAriaLabel"
|
||||
| "subagentThreadTitle"
|
||||
| "subagentThreadMeta"
|
||||
| "subagentThreadSummary"
|
||||
| "subagentDelegationTitle"
|
||||
| "subagentActivityTitle"
|
||||
| "subagentActivityMeta"
|
||||
>;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,23 @@ import { renderToStaticMarkup } from "react-dom/server";
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { projectAgentUiState } from "@limecloud/agent-runtime-projection";
|
||||
import {
|
||||
agentUiConformanceFixtures,
|
||||
getAgentUiFixture,
|
||||
} from "@limecloud/agent-ui-contracts";
|
||||
import {
|
||||
projectAgentUiState,
|
||||
replayAgentUiFixture,
|
||||
} from "@limecloud/agent-runtime-projection";
|
||||
|
||||
import { AgentTimeline, AgentUiProjectionView, RuntimeFactsPanel } from "../dist/index.js";
|
||||
import {
|
||||
AgentTimeline,
|
||||
AgentUiProjectionView,
|
||||
ArtifactRefList,
|
||||
EvidenceRefList,
|
||||
RuntimeFactsPanel,
|
||||
SubagentsView,
|
||||
} from "../dist/index.js";
|
||||
|
||||
test("AgentTimeline renders user and assistant messages", () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
@@ -78,6 +92,7 @@ test("RuntimeFactsPanel renders action button and fact counts", () => {
|
||||
const markup = renderToStaticMarkup(React.createElement(RuntimeFactsPanel, { readModel, onResolveAction: () => {} }));
|
||||
|
||||
assert.match(markup, /Runtime facts summary/);
|
||||
assert.match(markup, /agent-runtime-event pending/);
|
||||
assert.match(markup, /Input sources/);
|
||||
assert.match(markup, /Actions/);
|
||||
assert.match(markup, /需要补充输入源/);
|
||||
@@ -269,3 +284,117 @@ test("AgentUiProjectionView accepts host-provided labels", () => {
|
||||
assert.match(markup, /打开模型设置/);
|
||||
assert.match(markup, /执行图/);
|
||||
});
|
||||
|
||||
test("AgentUiProjectionView renders standard fixture replay states", () => {
|
||||
for (const fixture of agentUiConformanceFixtures) {
|
||||
const replay = replayAgentUiFixture(fixture);
|
||||
const markup = renderToStaticMarkup(
|
||||
React.createElement(AgentUiProjectionView, {
|
||||
state: replay.state,
|
||||
onResolveAction: () => {},
|
||||
}),
|
||||
);
|
||||
|
||||
assert.equal(replay.passed, true, fixture.id);
|
||||
assert.match(markup, /agent-ui-projection/, fixture.id);
|
||||
assert.match(markup, /data-hydration-status="live"/, fixture.id);
|
||||
}
|
||||
});
|
||||
|
||||
test("AgentUiProjectionView renders subagent handoff fixture graph", () => {
|
||||
const replay = replayAgentUiFixture(getAgentUiFixture("subagent-handoff"));
|
||||
const markup = renderToStaticMarkup(
|
||||
React.createElement(AgentUiProjectionView, {
|
||||
state: replay.state,
|
||||
onResolveAction: () => {},
|
||||
}),
|
||||
);
|
||||
|
||||
assert.match(markup, /data-node-type="subagent"/);
|
||||
assert.match(markup, /data-parent-id="task_fixture"/);
|
||||
assert.match(markup, /agent-subagents/);
|
||||
assert.match(markup, /Subagent threads/);
|
||||
assert.match(markup, /Subagent delegations/);
|
||||
assert.match(markup, /Subagent activities/);
|
||||
assert.match(markup, /data-subagent-id="subagent_fixture_researcher"/);
|
||||
assert.match(markup, /data-delegation-action="handoff"/);
|
||||
assert.match(markup, /Research subagent started/);
|
||||
assert.match(markup, /data-source-event-id="evt_handoff_requested"/);
|
||||
});
|
||||
|
||||
test("AgentUiProjectionView renders artifact and evidence refs from projection state", () => {
|
||||
const replay = replayAgentUiFixture(getAgentUiFixture("artifact-evidence"));
|
||||
const markup = renderToStaticMarkup(
|
||||
React.createElement(AgentUiProjectionView, {
|
||||
state: replay.state,
|
||||
labels: {
|
||||
artifactRefsAriaLabel: "交付物引用",
|
||||
evidenceRefsAriaLabel: "证据引用",
|
||||
artifactRefActionLabel: () => "打开交付物",
|
||||
evidenceRefActionLabel: () => "打开证据",
|
||||
},
|
||||
onSelectArtifactRef: () => {},
|
||||
onSelectEvidenceRef: () => {},
|
||||
}),
|
||||
);
|
||||
|
||||
assert.match(markup, /交付物引用/);
|
||||
assert.match(markup, /证据引用/);
|
||||
assert.match(markup, /data-ref-kind="artifact"/);
|
||||
assert.match(markup, /data-ref-kind="evidence"/);
|
||||
assert.match(markup, /data-ref-id="artifact_fixture_1"/);
|
||||
assert.match(markup, /data-ref-id="evidence_fixture_1"/);
|
||||
assert.match(markup, /打开交付物/);
|
||||
assert.match(markup, /打开证据/);
|
||||
});
|
||||
|
||||
test("ArtifactRefList and EvidenceRefList expose stable DOM contracts", () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
React.createElement(
|
||||
React.Fragment,
|
||||
null,
|
||||
React.createElement(ArtifactRefList, {
|
||||
refs: [{ id: "artifact-1", sourceEventId: "evt-artifact" }],
|
||||
refTitle: (ref) => `Artifact ${ref.id}`,
|
||||
}),
|
||||
React.createElement(EvidenceRefList, {
|
||||
refs: [{ id: "evidence-1", sourceEventId: "evt-evidence" }],
|
||||
refTitle: (ref) => `Evidence ${ref.id}`,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
assert.match(markup, /agent-artifact-refs/);
|
||||
assert.match(markup, /agent-evidence-refs/);
|
||||
assert.match(markup, /data-source-event-id="evt-artifact"/);
|
||||
assert.match(markup, /data-source-event-id="evt-evidence"/);
|
||||
assert.match(markup, /Artifact artifact-1/);
|
||||
assert.match(markup, /Evidence evidence-1/);
|
||||
});
|
||||
|
||||
test("SubagentsView renders threads, delegations, and activities from projection state", () => {
|
||||
const replay = replayAgentUiFixture(getAgentUiFixture("subagent-handoff"));
|
||||
const markup = renderToStaticMarkup(
|
||||
React.createElement(SubagentsView, {
|
||||
state: replay.state,
|
||||
labels: {
|
||||
subagentsAriaLabel: "子代理",
|
||||
subagentThreadsAriaLabel: "子代理线程",
|
||||
subagentDelegationsAriaLabel: "委派调用",
|
||||
subagentActivitiesAriaLabel: "活动记录",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
assert.match(markup, /子代理/);
|
||||
assert.match(markup, /子代理线程/);
|
||||
assert.match(markup, /委派调用/);
|
||||
assert.match(markup, /活动记录/);
|
||||
assert.match(markup, /data-subagent-count="1"/);
|
||||
assert.match(markup, /data-delegation-count="2"/);
|
||||
assert.match(markup, /data-activity-count="4"/);
|
||||
assert.match(markup, /data-thread-id="subagent_fixture_researcher"/);
|
||||
assert.match(markup, /data-delegation-action="spawn"/);
|
||||
assert.match(markup, /data-delegation-action="handoff"/);
|
||||
assert.match(markup, /data-activity-kind="handoff"/);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# @limecloud/agent-ui-contracts
|
||||
|
||||
`@limecloud/agent-ui-contracts` 是 Lime Agent UI / Runtime 共享的类型契约事实源。它只导出 TypeScript 类型,不包含投影逻辑、React 组件、JSON-RPC client 或 Electron bridge。
|
||||
`@limecloud/agent-ui-contracts` 是 Lime Agent UI / Runtime 共享的契约事实源。它导出 TypeScript 类型、标准 conformance fixtures 和轻量 validation helpers,不包含投影逻辑、React 组件、JSON-RPC client 或 Electron bridge。
|
||||
|
||||
## Boundary
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
|
||||
- runtime execution event 最小公共形状。
|
||||
- 跨宿主 Agent UI adapter event 最小公共形状。
|
||||
- action、read model、message parts、process timeline、execution graph 和 projection state 类型。
|
||||
- action、read model、message parts、process timeline、execution graph、Subagents 和 projection state 类型。
|
||||
- `AgentUiProjector` 等纯接口定义。
|
||||
- 标准 conformance fixtures:text、tool、HITL、artifact/evidence、stream repair、subagent handoff。
|
||||
- validation helpers:runtime event、read model、projection state、fixture 的最小结构校验。
|
||||
|
||||
这个包不负责:
|
||||
|
||||
@@ -31,6 +33,16 @@
|
||||
-> React primitives for the projection state
|
||||
```
|
||||
|
||||
当前物理包名以 `@limecloud/agent-runtime-projection` 和 `@limecloud/agent-runtime-ui` 为准。`@limecloud/agent-ui-projection`、`@limecloud/agent-ui-react` 只允许作为历史规划名或未来 alias 讨论,不能写成 current owner。
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @limecloud/agent-ui-contracts
|
||||
```
|
||||
|
||||
这个包通常被另外三个包直接依赖。产品应用只有在需要声明自己的 adapter input / output 类型、编写 conformance fixture,或在边界处做最小结构校验时才直接导入它。
|
||||
|
||||
## Source Layout
|
||||
|
||||
实现必须按职责拆分,`src/index.ts` 只能做 type-only barrel exports:
|
||||
@@ -38,19 +50,224 @@
|
||||
```text
|
||||
src/events.ts -> Agent UI adapter event taxonomy
|
||||
src/runtime.ts -> runtime execution event / read model / action contracts
|
||||
src/projection.ts -> AgentUiProjectionState / projector contracts
|
||||
src/projection.ts -> AgentUiProjectionState / Subagents / projector contracts
|
||||
src/messages.ts -> UI message part contracts
|
||||
src/timeline.ts -> process timeline contracts
|
||||
src/graph.ts -> execution graph contracts
|
||||
src/index.ts -> barrel exports only
|
||||
src/fixtures.ts -> standard conformance fixtures
|
||||
src/validation.ts -> minimal contract validation helpers
|
||||
src/index.ts -> barrel exports
|
||||
```
|
||||
|
||||
新增类型必须落在对应职责文件;不得把事件、runtime、projection、message、timeline 和 graph 类型重新合并回 `src/index.ts`。
|
||||
|
||||
`AgentRuntimeEventProjection.action` 保留兼容单按钮读取,`AgentRuntimeEventProjection.actions` 是标准多 action controls 表达,用于 approve / reject / answer / retry / stop 等宿主 intent。
|
||||
|
||||
## Quick Start
|
||||
|
||||
定义运行时事件时,只声明结构化事实,不写 UI 文案推断和 provider 原始对象:
|
||||
|
||||
```ts
|
||||
import type { AgentRuntimeExecutionEvent } from "@limecloud/agent-ui-contracts";
|
||||
|
||||
export function toolStartedEvent(params: {
|
||||
turnId: string;
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
}): AgentRuntimeExecutionEvent {
|
||||
return {
|
||||
id: `tool:${params.toolCallId}:started`,
|
||||
schemaVersion: "lime-runtime-event/v0.1",
|
||||
runtimeId: "runtime-1",
|
||||
threadId: "thread-1",
|
||||
turnId: params.turnId,
|
||||
sequence: 1,
|
||||
kind: "tool",
|
||||
status: "running",
|
||||
eventClass: "tool.started",
|
||||
title: params.toolName,
|
||||
toolCallId: params.toolCallId,
|
||||
runId: params.turnId,
|
||||
createdAt: new Date().toISOString(),
|
||||
payload: {
|
||||
toolName: params.toolName,
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
定义 UI projection state 类型时只引用 contracts,不从 React 或 runtime client 反向导入:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
AgentUiProjectionState,
|
||||
AgentUiSubagentsModel,
|
||||
ProcessTimeline,
|
||||
UIMessageParts,
|
||||
} from "@limecloud/agent-ui-contracts";
|
||||
|
||||
export interface HostAgentPanelState {
|
||||
messages: UIMessageParts;
|
||||
timeline: ProcessTimeline;
|
||||
projection: AgentUiProjectionState;
|
||||
subagents: AgentUiSubagentsModel;
|
||||
}
|
||||
```
|
||||
|
||||
`AgentUiProjectionState.subagents` 是标准子代理模型。React 组件和产品应用不能再从 `graph`、`readModel.visibleEvents`、assistant 正文或本地 state 重新解释 subagent、handoff、review 事实;这些解释必须在 projection 层完成。
|
||||
|
||||
## Core Event Taxonomy
|
||||
|
||||
Contracts 层只定义事实 envelope 和稳定枚举,不规定传输方式。事件族按 `eventClass` 划分:
|
||||
|
||||
| Event family | Required scope | Standard UI surface |
|
||||
| --- | --- | --- |
|
||||
| `turn.*` / `run.*` | `threadId`、`turnId`、`runId` | runtime status、ProcessTimeline。 |
|
||||
| `model.*` / `reasoning.*` | message id 或 turn scope | `UIMessageParts`。 |
|
||||
| `tool.*` | `toolCallId` | ToolGroup、ProcessTimeline、ExecutionGraph。 |
|
||||
| `action.*` | `actionId` | ActionRequired。 |
|
||||
| `artifact.*` | `artifactId` 或 `artifactRefs` | ArtifactRef / artifact workspace。 |
|
||||
| `evidence.*` / `review.*` | `evidenceId` 或 `evidenceRefs` | EvidenceRef、review lane。 |
|
||||
| `task.*` / `subagent.*` / `handoff.*` | `taskId`、`subagentId`、`handoffId` | ExecutionGraph、Subagents。 |
|
||||
| `snapshot.*` / `stream.*` | sequence / cursor | hydration、repair diagnostics。 |
|
||||
|
||||
新增事件族时先补 contracts fixture 和 validation 文档,再让 projection / UI 包消费;不要先在产品组件里私有解释。
|
||||
|
||||
## Fixtures
|
||||
|
||||
`agentUiConformanceFixtures` 固定当前最小验收切片:
|
||||
|
||||
- `text-basic`
|
||||
- `tool-success`
|
||||
- `tool-failure`
|
||||
- `hitl-action`
|
||||
- `artifact-evidence`
|
||||
- `stream-repair`
|
||||
- `subagent-handoff`
|
||||
|
||||
这些 fixtures 只包含 normalized runtime facts 和 read model 摘要,不包含 React props、Provider SDK 对象或 App Server transport。
|
||||
|
||||
宿主或下游包新增投影能力时,优先扩展 fixture,再让 projection / UI 测试消费同一份 fixture:
|
||||
|
||||
```ts
|
||||
import {
|
||||
agentUiConformanceFixtures,
|
||||
getAgentUiFixture,
|
||||
} from "@limecloud/agent-ui-contracts";
|
||||
|
||||
for (const fixture of agentUiConformanceFixtures) {
|
||||
console.log(fixture.id, fixture.events.length);
|
||||
}
|
||||
|
||||
const hitl = getAgentUiFixture("hitl-action");
|
||||
console.log(hitl.expected.pendingActionCount);
|
||||
```
|
||||
|
||||
新增 fixture 的最低要求:
|
||||
|
||||
- `id` 稳定,能作为测试名和文档锚点。
|
||||
- `schemaVersion` 使用当前 `AGENT_UI_FIXTURE_SCHEMA_VERSION`。
|
||||
- `events` 只包含 normalized runtime facts。
|
||||
- `readModel` 只放结构化摘要,不放 React props。
|
||||
- `expected` 描述 UI 应见事实,不描述具体 DOM 样式。
|
||||
|
||||
## Validation
|
||||
|
||||
```ts
|
||||
import {
|
||||
validateAgentUiFixture,
|
||||
validateRuntimeEvent,
|
||||
} from "@limecloud/agent-ui-contracts";
|
||||
|
||||
validateRuntimeEvent(event);
|
||||
validateAgentUiFixture(fixture);
|
||||
```
|
||||
|
||||
Validation helpers 只做合同层最小检查:必需字段、scope id、sequence gap、secret-bearing key 和大 payload inline 风险。复杂 reducer 行为必须放在 projection 包验证。
|
||||
|
||||
`validateProjectionState` 要求 projection state 包含完整 `subagents` 模型:
|
||||
|
||||
```ts
|
||||
{
|
||||
hasSubagents: boolean;
|
||||
threads: AgentUiSubagentThreadView[];
|
||||
delegationCalls: AgentUiSubagentDelegationView[];
|
||||
activities: AgentUiSubagentActivityView[];
|
||||
activeThreadIds: string[];
|
||||
completedThreadIds: string[];
|
||||
failedThreadIds: string[];
|
||||
}
|
||||
```
|
||||
|
||||
缺少这些字段时,projection state 不符合 Lime AgentUI 标准。
|
||||
|
||||
边界层建议使用 `collect*ValidationIssues` 做非抛错检查,把问题汇入宿主诊断:
|
||||
|
||||
```ts
|
||||
import { collectRuntimeEventValidationIssues } from "@limecloud/agent-ui-contracts";
|
||||
|
||||
const issues = collectRuntimeEventValidationIssues(event);
|
||||
if (issues.length > 0) {
|
||||
reportContractIssues(issues);
|
||||
}
|
||||
```
|
||||
|
||||
## Product App Usage
|
||||
|
||||
产品应用直接使用本包时,通常只做三件事:
|
||||
|
||||
1. 为自己的 adapter 参数声明标准 event / projection 类型。
|
||||
2. 在接入测试中读取 `agentUiConformanceFixtures`。
|
||||
3. 在 App Server facts 进入 projection 前做最小合同校验。
|
||||
|
||||
推荐接入边界:
|
||||
|
||||
```text
|
||||
Product App runtime service
|
||||
-> AgentRuntimeExecutionEvent[]
|
||||
-> validateRuntimeEvent / validateAgentUiFixture
|
||||
-> @limecloud/agent-runtime-projection
|
||||
-> @limecloud/agent-runtime-ui 或产品自己的 presentation adapter
|
||||
```
|
||||
|
||||
## Conformance
|
||||
|
||||
一个 runtime provider 或产品 adapter 声称兼容 Lime AgentUI contracts 时,至少要证明:
|
||||
|
||||
| Slice | Required proof |
|
||||
| --- | --- |
|
||||
| Text turn | `text-basic` fixture validation 通过,streaming text 可 replay。 |
|
||||
| Tool lifecycle | `tool-success` / `tool-failure` 有 `toolCallId`,大输出走 refs。 |
|
||||
| HITL | `hitl-action` 有 `action.required` / `action.resolved` 和 `actionId`。 |
|
||||
| Artifact / evidence | refs 不内联大 payload,不泄露 secret-bearing key。 |
|
||||
| Stream repair | sequence gap 只在声明 repair diagnostics 的 fixture 中允许。 |
|
||||
| Subagents | `subagent-handoff` 能表达 task、subagent、handoff、review scope。 |
|
||||
| Subagents | projection state 包含完整 `subagents` 字段。 |
|
||||
|
||||
下游包应从 `agentUiConformanceFixtures` 读取标准样本,而不是在自己的测试里复制一套私有 fixture。
|
||||
|
||||
## Package Metadata
|
||||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| Runtime | Node `>=20`,ESM。 |
|
||||
| Side effects | `false`,只导出类型、fixtures 和 validation helpers。 |
|
||||
| Public files | `dist`、`README.md`。 |
|
||||
| License | `MIT`。 |
|
||||
| Versioning | 与 AgentUI 四包同步发布;contracts 破坏性变更必须走 major。 |
|
||||
|
||||
## Do Not
|
||||
|
||||
- 不要在本包里实现 reducer、selector、React 组件或 JSON-RPC client。
|
||||
- 不要把 Provider SDK 类型、API key、HTTP response 或 Electron bridge 类型放进 contracts。
|
||||
- 不要把业务 App 的私有 task、prompt、artifact schema 当成标准 contracts。
|
||||
- 不要恢复旧树形过程术语作为标准;过程 UI 标准是 `UIMessageParts`、`ProcessTimeline`、`ExecutionGraph`。
|
||||
- 不要在 `src/index.ts` 写实现逻辑;它只能做 barrel exports。
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm --prefix packages/agent-ui-contracts run build
|
||||
npm --prefix packages/agent-ui-contracts run test
|
||||
npm --prefix packages/agent-ui-contracts pack --dry-run
|
||||
```
|
||||
|
||||
@@ -0,0 +1,454 @@
|
||||
import type {
|
||||
AgentRuntimeExecutionEvent,
|
||||
AgentRuntimeReadModel,
|
||||
} from "./runtime";
|
||||
|
||||
export const AGENT_UI_FIXTURE_SCHEMA_VERSION = "agent-ui-fixture/v0.1";
|
||||
|
||||
export interface AgentUiFixtureExpectation {
|
||||
status: string;
|
||||
messagePartCount?: number;
|
||||
timelineEntryCount?: number;
|
||||
graphNodeCount?: number;
|
||||
pendingActionCount?: number;
|
||||
artifactCount?: number;
|
||||
evidenceCount?: number;
|
||||
subagents?: {
|
||||
hasSubagents?: boolean;
|
||||
threadCount?: number;
|
||||
delegationCallCount?: number;
|
||||
activityCount?: number;
|
||||
activeThreadCount?: number;
|
||||
completedThreadCount?: number;
|
||||
failedThreadCount?: number;
|
||||
};
|
||||
diagnostics?: string[];
|
||||
}
|
||||
|
||||
export interface AgentUiFixture<
|
||||
TEvent extends AgentRuntimeExecutionEvent = AgentRuntimeExecutionEvent,
|
||||
> {
|
||||
id: string;
|
||||
schemaVersion: string;
|
||||
title: string;
|
||||
events: TEvent[];
|
||||
initialReadModel?: AgentRuntimeReadModel<TEvent>;
|
||||
finalReadModel?: AgentRuntimeReadModel<TEvent>;
|
||||
expected: AgentUiFixtureExpectation;
|
||||
}
|
||||
|
||||
const runtimeId = "agent_ui_fixture_runtime";
|
||||
const threadId = "thread_fixture";
|
||||
const turnId = "turn_fixture";
|
||||
const taskId = "task_fixture";
|
||||
const createdAt = "2026-06-10T00:00:00.000Z";
|
||||
|
||||
function event(
|
||||
input: Omit<
|
||||
AgentRuntimeExecutionEvent,
|
||||
"createdAt" | "id" | "runtimeId" | "schemaVersion" | "threadId" | "turnId"
|
||||
> & { id: string },
|
||||
): AgentRuntimeExecutionEvent {
|
||||
return {
|
||||
schemaVersion: "lime-runtime-event/v0.1",
|
||||
runtimeId,
|
||||
threadId,
|
||||
turnId,
|
||||
createdAt,
|
||||
...input,
|
||||
};
|
||||
}
|
||||
|
||||
function readModel(
|
||||
events: AgentRuntimeExecutionEvent[],
|
||||
overrides: Partial<AgentRuntimeReadModel> = {},
|
||||
): AgentRuntimeReadModel {
|
||||
return {
|
||||
events: events.map((source) => projection(source)),
|
||||
visibleEvents: events.map((source) => projection(source)),
|
||||
pendingActions: [],
|
||||
inputSourceRecovery: false,
|
||||
sourceCount: 0,
|
||||
artifactRefs: events.flatMap((source) => source.artifactRefs ?? []),
|
||||
evidenceRefs: events.flatMap((source) => source.evidenceRefs ?? []),
|
||||
taskRefs: [taskId],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function projection(source: AgentRuntimeExecutionEvent) {
|
||||
return {
|
||||
id: `projection_${source.id}`,
|
||||
source,
|
||||
surface: "runtime-status" as const,
|
||||
title: source.title,
|
||||
detail: source.detail,
|
||||
status: source.status,
|
||||
displayStatusKey: `agent.status.${source.status}`,
|
||||
resolved: source.status !== "blocked" && source.status !== "pending",
|
||||
actionKind: source.actionId ? "runtime_action" : "none",
|
||||
targetModule: "agent-runtime",
|
||||
actionId: source.actionId,
|
||||
};
|
||||
}
|
||||
|
||||
const textBasicEvents = [
|
||||
event({
|
||||
id: "evt_text_submitted",
|
||||
eventClass: "turn.submitted",
|
||||
kind: "state",
|
||||
status: "pending",
|
||||
sequence: 1,
|
||||
title: "Turn submitted",
|
||||
}),
|
||||
event({
|
||||
id: "evt_text_started",
|
||||
eventClass: "turn.started",
|
||||
kind: "state",
|
||||
status: "running",
|
||||
sequence: 2,
|
||||
title: "Turn started",
|
||||
}),
|
||||
event({
|
||||
id: "evt_text_delta",
|
||||
eventClass: "model.delta",
|
||||
kind: "model",
|
||||
status: "running",
|
||||
sequence: 3,
|
||||
title: "Assistant text",
|
||||
payload: { delta: "你好,Lime。" },
|
||||
}),
|
||||
event({
|
||||
id: "evt_text_completed",
|
||||
eventClass: "model.completed",
|
||||
kind: "model",
|
||||
status: "completed",
|
||||
sequence: 4,
|
||||
title: "Assistant final text",
|
||||
payload: { text: "你好,Lime。" },
|
||||
}),
|
||||
event({
|
||||
id: "evt_text_turn_completed",
|
||||
eventClass: "turn.completed",
|
||||
kind: "state",
|
||||
status: "completed",
|
||||
sequence: 5,
|
||||
title: "Turn completed",
|
||||
completedAt: createdAt,
|
||||
}),
|
||||
event({
|
||||
id: "evt_text_snapshot",
|
||||
eventClass: "snapshot.updated",
|
||||
kind: "state",
|
||||
status: "completed",
|
||||
sequence: 6,
|
||||
title: "Snapshot updated",
|
||||
}),
|
||||
];
|
||||
|
||||
const toolSuccessEvents = [
|
||||
event({
|
||||
id: "evt_tool_started",
|
||||
eventClass: "tool.started",
|
||||
kind: "tool",
|
||||
status: "running",
|
||||
sequence: 1,
|
||||
taskId,
|
||||
toolCallId: "tool_fixture_search",
|
||||
title: "Search started",
|
||||
payload: { toolName: "search" },
|
||||
}),
|
||||
event({
|
||||
id: "evt_tool_result",
|
||||
eventClass: "tool.result",
|
||||
kind: "tool",
|
||||
status: "completed",
|
||||
sequence: 2,
|
||||
taskId,
|
||||
toolCallId: "tool_fixture_search",
|
||||
title: "Search completed",
|
||||
refIds: ["output_search_1"],
|
||||
}),
|
||||
event({
|
||||
id: "evt_tool_turn_completed",
|
||||
eventClass: "turn.completed",
|
||||
kind: "state",
|
||||
status: "completed",
|
||||
sequence: 3,
|
||||
taskId,
|
||||
title: "Turn completed",
|
||||
}),
|
||||
];
|
||||
|
||||
const toolFailureEvents = [
|
||||
event({
|
||||
id: "evt_tool_failed_start",
|
||||
eventClass: "tool.started",
|
||||
kind: "tool",
|
||||
status: "running",
|
||||
sequence: 1,
|
||||
taskId,
|
||||
toolCallId: "tool_fixture_write",
|
||||
title: "Write file started",
|
||||
}),
|
||||
event({
|
||||
id: "evt_tool_failed",
|
||||
eventClass: "tool.failed",
|
||||
kind: "tool",
|
||||
status: "failed",
|
||||
sequence: 2,
|
||||
taskId,
|
||||
toolCallId: "tool_fixture_write",
|
||||
title: "Write file failed",
|
||||
payload: { failureCategory: "permission_denied" },
|
||||
}),
|
||||
];
|
||||
|
||||
const hitlActionEvents = [
|
||||
event({
|
||||
id: "evt_action_required",
|
||||
eventClass: "action.required",
|
||||
kind: "action",
|
||||
status: "blocked",
|
||||
sequence: 1,
|
||||
taskId,
|
||||
actionId: "action_fixture_approval",
|
||||
title: "Approval required",
|
||||
}),
|
||||
event({
|
||||
id: "evt_action_resolved",
|
||||
eventClass: "action.resolved",
|
||||
kind: "action",
|
||||
status: "completed",
|
||||
sequence: 2,
|
||||
taskId,
|
||||
actionId: "action_fixture_approval",
|
||||
title: "Approval resolved",
|
||||
payload: { decision: "approved" },
|
||||
}),
|
||||
];
|
||||
|
||||
const artifactEvidenceEvents = [
|
||||
event({
|
||||
id: "evt_artifact_changed",
|
||||
eventClass: "artifact.changed",
|
||||
kind: "draft",
|
||||
owner: "artifact",
|
||||
status: "completed",
|
||||
sequence: 1,
|
||||
taskId,
|
||||
artifactId: "artifact_fixture_1",
|
||||
artifactRefs: ["artifact_fixture_1"],
|
||||
title: "Artifact changed",
|
||||
}),
|
||||
event({
|
||||
id: "evt_evidence_changed",
|
||||
eventClass: "evidence.changed",
|
||||
kind: "evidence",
|
||||
owner: "evidence",
|
||||
status: "completed",
|
||||
sequence: 2,
|
||||
taskId,
|
||||
evidenceId: "evidence_fixture_1",
|
||||
evidenceRefs: ["evidence_fixture_1"],
|
||||
title: "Evidence changed",
|
||||
}),
|
||||
];
|
||||
|
||||
const streamRepairEvents = [
|
||||
event({
|
||||
id: "evt_repair_delta",
|
||||
eventClass: "model.delta",
|
||||
kind: "model",
|
||||
status: "running",
|
||||
sequence: 1,
|
||||
title: "Assistant text before gap",
|
||||
payload: { delta: "first chunk" },
|
||||
}),
|
||||
event({
|
||||
id: "evt_repair_snapshot",
|
||||
eventClass: "snapshot.updated",
|
||||
kind: "state",
|
||||
status: "completed",
|
||||
sequence: 3,
|
||||
title: "Snapshot repaired stream",
|
||||
}),
|
||||
];
|
||||
|
||||
const subagentHandoffEvents = [
|
||||
event({
|
||||
id: "evt_parent_task_created",
|
||||
eventClass: "task.created",
|
||||
kind: "handoff",
|
||||
status: "running",
|
||||
sequence: 1,
|
||||
taskId,
|
||||
title: "Parent task created",
|
||||
}),
|
||||
event({
|
||||
id: "evt_subagent_started",
|
||||
eventClass: "subagent.started",
|
||||
kind: "handoff",
|
||||
status: "running",
|
||||
sequence: 2,
|
||||
taskId,
|
||||
subagentId: "subagent_fixture_researcher",
|
||||
title: "Research subagent started",
|
||||
payload: { role: "researcher", parentTaskId: taskId },
|
||||
}),
|
||||
event({
|
||||
id: "evt_subagent_tool_result",
|
||||
eventClass: "tool.result",
|
||||
kind: "tool",
|
||||
status: "completed",
|
||||
sequence: 3,
|
||||
taskId,
|
||||
subagentId: "subagent_fixture_researcher",
|
||||
toolCallId: "tool_fixture_research",
|
||||
title: "Research completed",
|
||||
}),
|
||||
event({
|
||||
id: "evt_handoff_requested",
|
||||
eventClass: "handoff.requested",
|
||||
kind: "handoff",
|
||||
status: "blocked",
|
||||
sequence: 4,
|
||||
taskId,
|
||||
subagentId: "subagent_fixture_researcher",
|
||||
handoffId: "handoff_fixture_1",
|
||||
title: "Handoff requested",
|
||||
}),
|
||||
event({
|
||||
id: "evt_review_verdict",
|
||||
eventClass: "review.verdict",
|
||||
kind: "evidence",
|
||||
status: "completed",
|
||||
sequence: 5,
|
||||
taskId,
|
||||
reviewId: "review_fixture_1",
|
||||
evidenceId: "evidence_fixture_review",
|
||||
evidenceRefs: ["evidence_fixture_review"],
|
||||
title: "Review completed",
|
||||
}),
|
||||
event({
|
||||
id: "evt_subagent_completed",
|
||||
eventClass: "subagent.completed",
|
||||
kind: "handoff",
|
||||
status: "completed",
|
||||
sequence: 6,
|
||||
taskId,
|
||||
subagentId: "subagent_fixture_researcher",
|
||||
title: "Research subagent completed",
|
||||
}),
|
||||
];
|
||||
|
||||
export const agentUiConformanceFixtures = [
|
||||
{
|
||||
id: "text-basic",
|
||||
schemaVersion: AGENT_UI_FIXTURE_SCHEMA_VERSION,
|
||||
title: "Text turn with final reconciliation",
|
||||
events: textBasicEvents,
|
||||
finalReadModel: readModel(textBasicEvents),
|
||||
expected: {
|
||||
status: "completed",
|
||||
messagePartCount: 1,
|
||||
timelineEntryCount: 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tool-success",
|
||||
schemaVersion: AGENT_UI_FIXTURE_SCHEMA_VERSION,
|
||||
title: "Tool call success with output reference",
|
||||
events: toolSuccessEvents,
|
||||
finalReadModel: readModel(toolSuccessEvents),
|
||||
expected: {
|
||||
status: "completed",
|
||||
timelineEntryCount: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tool-failure",
|
||||
schemaVersion: AGENT_UI_FIXTURE_SCHEMA_VERSION,
|
||||
title: "Tool failure with recovery category",
|
||||
events: toolFailureEvents,
|
||||
finalReadModel: readModel(toolFailureEvents),
|
||||
expected: {
|
||||
status: "failed",
|
||||
diagnostics: ["tool.failed"],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "hitl-action",
|
||||
schemaVersion: AGENT_UI_FIXTURE_SCHEMA_VERSION,
|
||||
title: "Human approval action lifecycle",
|
||||
events: hitlActionEvents,
|
||||
initialReadModel: readModel(hitlActionEvents.slice(0, 1), {
|
||||
pendingActions: [projection(hitlActionEvents[0])],
|
||||
}),
|
||||
finalReadModel: readModel(hitlActionEvents),
|
||||
expected: {
|
||||
status: "completed",
|
||||
pendingActionCount: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "artifact-evidence",
|
||||
schemaVersion: AGENT_UI_FIXTURE_SCHEMA_VERSION,
|
||||
title: "Artifact and evidence references",
|
||||
events: artifactEvidenceEvents,
|
||||
finalReadModel: readModel(artifactEvidenceEvents),
|
||||
expected: {
|
||||
status: "completed",
|
||||
artifactCount: 1,
|
||||
evidenceCount: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "stream-repair",
|
||||
schemaVersion: AGENT_UI_FIXTURE_SCHEMA_VERSION,
|
||||
title: "Interrupted stream repaired by snapshot",
|
||||
events: streamRepairEvents,
|
||||
finalReadModel: readModel(streamRepairEvents),
|
||||
expected: {
|
||||
status: "completed",
|
||||
diagnostics: ["sequence_gap"],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "subagent-handoff",
|
||||
schemaVersion: AGENT_UI_FIXTURE_SCHEMA_VERSION,
|
||||
title: "Subagent handoff and review",
|
||||
events: subagentHandoffEvents,
|
||||
finalReadModel: readModel(subagentHandoffEvents, {
|
||||
evidenceRefs: ["evidence_fixture_review"],
|
||||
taskRefs: [taskId, "subagent_fixture_researcher"],
|
||||
}),
|
||||
expected: {
|
||||
status: "completed",
|
||||
graphNodeCount: 2,
|
||||
evidenceCount: 1,
|
||||
subagents: {
|
||||
hasSubagents: true,
|
||||
threadCount: 1,
|
||||
delegationCallCount: 2,
|
||||
activityCount: 4,
|
||||
activeThreadCount: 0,
|
||||
completedThreadCount: 1,
|
||||
failedThreadCount: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
] satisfies AgentUiFixture[];
|
||||
|
||||
export type AgentUiConformanceFixtureId =
|
||||
(typeof agentUiConformanceFixtures)[number]["id"];
|
||||
|
||||
export function getAgentUiFixture(
|
||||
id: AgentUiConformanceFixtureId,
|
||||
): AgentUiFixture {
|
||||
const fixture = agentUiConformanceFixtures.find((item) => item.id === id);
|
||||
if (!fixture) {
|
||||
throw new Error(`Unknown Agent UI fixture: ${id}`);
|
||||
}
|
||||
return fixture;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
export type * from "./events";
|
||||
export * from "./fixtures.js";
|
||||
export type * from "./graph";
|
||||
export type * from "./messages";
|
||||
export type * from "./projection";
|
||||
export type * from "./runtime";
|
||||
export type * from "./timeline";
|
||||
export * from "./validation.js";
|
||||
|
||||
@@ -36,8 +36,20 @@ export interface AgentUiRuntimeStatusView {
|
||||
export interface AgentUiRefView {
|
||||
id: string;
|
||||
sourceEventId: string;
|
||||
title?: string;
|
||||
status?: AgentRuntimeExecutionEventStatus;
|
||||
owner?: "artifact" | "evidence" | "runtime" | "ui" | string;
|
||||
path?: string;
|
||||
contentRef?: string;
|
||||
mimeType?: string;
|
||||
preview?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type AgentUiArtifactRefView = AgentUiRefView;
|
||||
|
||||
export type AgentUiEvidenceRefView = AgentUiRefView;
|
||||
|
||||
export interface AgentUiDiagnosticView {
|
||||
id: string;
|
||||
sourceEventId: string;
|
||||
@@ -46,6 +58,90 @@ export interface AgentUiDiagnosticView {
|
||||
status: AgentRuntimeExecutionEventStatus;
|
||||
}
|
||||
|
||||
export type AgentUiSubagentCommand =
|
||||
| "open_detail"
|
||||
| "send_input"
|
||||
| "interrupt"
|
||||
| "close"
|
||||
| "wait"
|
||||
| string;
|
||||
|
||||
export interface AgentUiSubagentIsolationView {
|
||||
runtimeProfileId?: string;
|
||||
modelProfileId?: string;
|
||||
isolationProfileId?: string;
|
||||
workspaceRef?: string;
|
||||
permissionProfile?: string;
|
||||
sandboxProfile?: string;
|
||||
forkPolicy?: string;
|
||||
depth?: number;
|
||||
canDelegate?: boolean;
|
||||
}
|
||||
|
||||
export interface AgentUiSubagentThreadView {
|
||||
threadId: string;
|
||||
subagentId: string;
|
||||
parentThreadId?: string;
|
||||
parentTaskId?: string;
|
||||
taskId?: string;
|
||||
taskPath?: string;
|
||||
role?: string;
|
||||
nickname?: string;
|
||||
status: AgentRuntimeExecutionEventStatus;
|
||||
title: string;
|
||||
summary?: string;
|
||||
promptPreview?: string;
|
||||
lastActivityAt?: string;
|
||||
createdAt?: string;
|
||||
completedAt?: string;
|
||||
artifactRefs: string[];
|
||||
evidenceRefs: string[];
|
||||
sourceEventIds: string[];
|
||||
isolation?: AgentUiSubagentIsolationView;
|
||||
}
|
||||
|
||||
export interface AgentUiSubagentDelegationView {
|
||||
callId: string;
|
||||
sourceEventId: string;
|
||||
action: "spawn" | "handoff" | "send_input" | "wait" | "interrupt" | "close" | string;
|
||||
parentThreadId?: string;
|
||||
targetThreadIds: string[];
|
||||
status: AgentRuntimeExecutionEventStatus;
|
||||
title: string;
|
||||
promptPreview?: string;
|
||||
createdAt?: string;
|
||||
completedAt?: string;
|
||||
}
|
||||
|
||||
export interface AgentUiSubagentActivityView {
|
||||
activityId: string;
|
||||
threadId: string;
|
||||
sourceEventId: string;
|
||||
kind:
|
||||
| "started"
|
||||
| "updated"
|
||||
| "interacted"
|
||||
| "handoff"
|
||||
| "review"
|
||||
| "completed"
|
||||
| "interrupted"
|
||||
| "failed"
|
||||
| string;
|
||||
status: AgentRuntimeExecutionEventStatus;
|
||||
title: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface AgentUiSubagentsModel {
|
||||
hasSubagents: boolean;
|
||||
threads: AgentUiSubagentThreadView[];
|
||||
delegationCalls: AgentUiSubagentDelegationView[];
|
||||
activities: AgentUiSubagentActivityView[];
|
||||
activeThreadIds: string[];
|
||||
completedThreadIds: string[];
|
||||
failedThreadIds: string[];
|
||||
}
|
||||
|
||||
export interface AgentUiProjectionState<
|
||||
TEvent extends AgentRuntimeExecutionEvent = AgentRuntimeExecutionEvent,
|
||||
> {
|
||||
@@ -55,9 +151,10 @@ export interface AgentUiProjectionState<
|
||||
graph: ExecutionGraph;
|
||||
tools: AgentRuntimeEventProjection<TEvent>[];
|
||||
actions: AgentRuntimeEventProjection<TEvent>[];
|
||||
artifacts: AgentUiRefView[];
|
||||
evidence: AgentUiRefView[];
|
||||
artifacts: AgentUiArtifactRefView[];
|
||||
evidence: AgentUiEvidenceRefView[];
|
||||
diagnostics: AgentUiDiagnosticView[];
|
||||
subagents: AgentUiSubagentsModel;
|
||||
readModel: AgentRuntimeReadModel<TEvent>;
|
||||
hydration: {
|
||||
status: AgentUiHydrationStatus;
|
||||
|
||||
@@ -82,10 +82,15 @@ export interface AgentRuntimeExecutionEvent {
|
||||
threadId?: string;
|
||||
turnId?: string;
|
||||
taskId?: string;
|
||||
subagentId?: string;
|
||||
runId?: string;
|
||||
stepId?: string;
|
||||
toolCallId?: string;
|
||||
actionId?: string;
|
||||
workerId?: string;
|
||||
channelId?: string;
|
||||
handoffId?: string;
|
||||
reviewId?: string;
|
||||
traceId?: string;
|
||||
spanId?: string;
|
||||
attemptId?: string;
|
||||
|
||||
@@ -0,0 +1,935 @@
|
||||
import type { AgentUiFixture } from "./fixtures";
|
||||
import type { AgentUiProjectionState } from "./projection";
|
||||
import type {
|
||||
AgentRuntimeExecutionEvent,
|
||||
AgentRuntimeReadModel,
|
||||
} from "./runtime";
|
||||
|
||||
export type AgentUiContractValidationCode =
|
||||
| "schema_mismatch"
|
||||
| "missing_scope_id"
|
||||
| "sequence_gap"
|
||||
| "secret_leak_risk"
|
||||
| "large_payload_inline"
|
||||
| "unknown_event_type";
|
||||
|
||||
export interface AgentUiContractValidationIssue {
|
||||
code: AgentUiContractValidationCode;
|
||||
path: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export class AgentUiContractValidationError extends Error {
|
||||
readonly issues: AgentUiContractValidationIssue[];
|
||||
|
||||
constructor(issues: AgentUiContractValidationIssue[]) {
|
||||
super(issues.map((issue) => `${issue.path}: ${issue.message}`).join("; "));
|
||||
this.name = "AgentUiContractValidationError";
|
||||
this.issues = issues;
|
||||
}
|
||||
}
|
||||
|
||||
export function validateRuntimeEvent(
|
||||
input: unknown,
|
||||
): AgentRuntimeExecutionEvent {
|
||||
const issues = collectRuntimeEventValidationIssues(input);
|
||||
throwIfIssues(issues);
|
||||
return input as AgentRuntimeExecutionEvent;
|
||||
}
|
||||
|
||||
export function validateThreadReadModel(
|
||||
input: unknown,
|
||||
): AgentRuntimeReadModel {
|
||||
const issues = collectThreadReadModelValidationIssues(input);
|
||||
throwIfIssues(issues);
|
||||
return input as AgentRuntimeReadModel;
|
||||
}
|
||||
|
||||
export function validateProjectionState(
|
||||
input: unknown,
|
||||
): AgentUiProjectionState {
|
||||
const issues = collectProjectionStateValidationIssues(input);
|
||||
throwIfIssues(issues);
|
||||
return input as AgentUiProjectionState;
|
||||
}
|
||||
|
||||
export function validateAgentUiFixture(input: unknown): AgentUiFixture {
|
||||
const issues = collectAgentUiFixtureValidationIssues(input);
|
||||
throwIfIssues(issues);
|
||||
return input as AgentUiFixture;
|
||||
}
|
||||
|
||||
export function collectRuntimeEventValidationIssues(
|
||||
input: unknown,
|
||||
path = "$",
|
||||
): AgentUiContractValidationIssue[] {
|
||||
const issues: AgentUiContractValidationIssue[] = [];
|
||||
|
||||
if (!isRecord(input)) {
|
||||
return [
|
||||
issue("schema_mismatch", path, "Runtime event must be an object."),
|
||||
];
|
||||
}
|
||||
|
||||
requireString(input, "id", path, issues);
|
||||
requireString(input, "schemaVersion", path, issues);
|
||||
requireString(input, "runtimeId", path, issues);
|
||||
requireString(input, "kind", path, issues);
|
||||
requireString(input, "status", path, issues);
|
||||
requireString(input, "title", path, issues);
|
||||
requireString(input, "createdAt", path, issues);
|
||||
|
||||
if (!Number.isInteger(input.sequence)) {
|
||||
issues.push(
|
||||
issue(
|
||||
"schema_mismatch",
|
||||
`${path}.sequence`,
|
||||
"Runtime event sequence must be an integer.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof input.eventClass === "string") {
|
||||
collectScopeIssues(input, path, issues);
|
||||
}
|
||||
|
||||
collectPayloadIssues(input.payload, `${path}.payload`, issues);
|
||||
return issues;
|
||||
}
|
||||
|
||||
export function collectThreadReadModelValidationIssues(
|
||||
input: unknown,
|
||||
path = "$",
|
||||
): AgentUiContractValidationIssue[] {
|
||||
const issues: AgentUiContractValidationIssue[] = [];
|
||||
|
||||
if (!isRecord(input)) {
|
||||
return [
|
||||
issue("schema_mismatch", path, "Thread read model must be an object."),
|
||||
];
|
||||
}
|
||||
|
||||
requireArray(input, "events", path, issues);
|
||||
if (Array.isArray(input.events)) {
|
||||
input.events.forEach((item, index) => {
|
||||
collectRuntimeEventProjectionValidationIssues(
|
||||
item,
|
||||
`${path}.events[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
requireArray(input, "visibleEvents", path, issues);
|
||||
if (Array.isArray(input.visibleEvents)) {
|
||||
input.visibleEvents.forEach((item, index) => {
|
||||
collectRuntimeEventProjectionValidationIssues(
|
||||
item,
|
||||
`${path}.visibleEvents[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
requireArray(input, "pendingActions", path, issues);
|
||||
if (Array.isArray(input.pendingActions)) {
|
||||
input.pendingActions.forEach((item, index) => {
|
||||
collectRuntimeEventProjectionValidationIssues(
|
||||
item,
|
||||
`${path}.pendingActions[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
requireBoolean(input, "inputSourceRecovery", path, issues);
|
||||
requireNumber(input, "sourceCount", path, issues);
|
||||
requireStringArray(input, "artifactRefs", path, issues);
|
||||
requireStringArray(input, "evidenceRefs", path, issues);
|
||||
requireStringArray(input, "taskRefs", path, issues);
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
export function collectProjectionStateValidationIssues(
|
||||
input: unknown,
|
||||
path = "$",
|
||||
): AgentUiContractValidationIssue[] {
|
||||
const issues: AgentUiContractValidationIssue[] = [];
|
||||
|
||||
if (!isRecord(input)) {
|
||||
return [
|
||||
issue("schema_mismatch", path, "Projection state must be an object."),
|
||||
];
|
||||
}
|
||||
|
||||
requireRecord(input, "runtime", path, issues);
|
||||
requireArray(input, "messages", path, issues);
|
||||
if (Array.isArray(input.messages)) {
|
||||
input.messages.forEach((item, index) => {
|
||||
collectMessagePartValidationIssues(
|
||||
item,
|
||||
`${path}.messages[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
requireArray(input, "timeline", path, issues);
|
||||
if (Array.isArray(input.timeline)) {
|
||||
input.timeline.forEach((item, index) => {
|
||||
collectTimelineEntryValidationIssues(
|
||||
item,
|
||||
`${path}.timeline[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
requireArray(input, "graph", path, issues);
|
||||
if (Array.isArray(input.graph)) {
|
||||
input.graph.forEach((item, index) => {
|
||||
collectGraphNodeValidationIssues(item, `${path}.graph[${index}]`, issues);
|
||||
});
|
||||
}
|
||||
requireArray(input, "tools", path, issues);
|
||||
if (Array.isArray(input.tools)) {
|
||||
input.tools.forEach((item, index) => {
|
||||
collectRuntimeEventProjectionValidationIssues(
|
||||
item,
|
||||
`${path}.tools[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
requireArray(input, "actions", path, issues);
|
||||
if (Array.isArray(input.actions)) {
|
||||
input.actions.forEach((item, index) => {
|
||||
collectRuntimeEventProjectionValidationIssues(
|
||||
item,
|
||||
`${path}.actions[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
requireArray(input, "artifacts", path, issues);
|
||||
if (Array.isArray(input.artifacts)) {
|
||||
input.artifacts.forEach((item, index) => {
|
||||
collectRefValidationIssues(item, `${path}.artifacts[${index}]`, issues);
|
||||
});
|
||||
}
|
||||
requireArray(input, "evidence", path, issues);
|
||||
if (Array.isArray(input.evidence)) {
|
||||
input.evidence.forEach((item, index) => {
|
||||
collectRefValidationIssues(item, `${path}.evidence[${index}]`, issues);
|
||||
});
|
||||
}
|
||||
requireArray(input, "diagnostics", path, issues);
|
||||
if (Array.isArray(input.diagnostics)) {
|
||||
input.diagnostics.forEach((item, index) => {
|
||||
collectDiagnosticValidationIssues(
|
||||
item,
|
||||
`${path}.diagnostics[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
requireRecord(input, "subagents", path, issues);
|
||||
if (isRecord(input.subagents)) {
|
||||
collectSubagentsValidationIssues(
|
||||
input.subagents,
|
||||
`${path}.subagents`,
|
||||
issues,
|
||||
);
|
||||
}
|
||||
requireRecord(input, "readModel", path, issues);
|
||||
if (isRecord(input.readModel)) {
|
||||
issues.push(
|
||||
...collectThreadReadModelValidationIssues(
|
||||
input.readModel,
|
||||
`${path}.readModel`,
|
||||
),
|
||||
);
|
||||
}
|
||||
requireRecord(input, "hydration", path, issues);
|
||||
if (isRecord(input.hydration)) {
|
||||
requireString(input.hydration, "status", `${path}.hydration`, issues);
|
||||
requireNumber(input.hydration, "eventCount", `${path}.hydration`, issues);
|
||||
}
|
||||
requireRecord(input, "ephemeralUi", path, issues);
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
function collectMessagePartValidationIssues(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input)) {
|
||||
issues.push(issue("schema_mismatch", path, "Message part must be an object."));
|
||||
return;
|
||||
}
|
||||
requireString(input, "type", path, issues);
|
||||
requireString(input, "partId", path, issues);
|
||||
requireString(input, "sourceEventId", path, issues);
|
||||
optionalString(input, "messageId", path, issues);
|
||||
optionalString(input, "role", path, issues);
|
||||
optionalString(input, "text", path, issues);
|
||||
optionalString(input, "state", path, issues);
|
||||
optionalString(input, "toolCallId", path, issues);
|
||||
optionalString(input, "artifactId", path, issues);
|
||||
optionalString(input, "evidenceId", path, issues);
|
||||
optionalString(input, "diagnosticId", path, issues);
|
||||
optionalString(input, "createdAt", path, issues);
|
||||
requireStringArray(input, "refs", path, issues);
|
||||
}
|
||||
|
||||
function collectTimelineEntryValidationIssues(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input)) {
|
||||
issues.push(issue("schema_mismatch", path, "Timeline entry must be an object."));
|
||||
return;
|
||||
}
|
||||
requireString(input, "entryId", path, issues);
|
||||
requireString(input, "kind", path, issues);
|
||||
requireString(input, "status", path, issues);
|
||||
requireString(input, "title", path, issues);
|
||||
requireString(input, "sourceEventId", path, issues);
|
||||
requireString(input, "createdAt", path, issues);
|
||||
optionalString(input, "phase", path, issues);
|
||||
optionalString(input, "owner", path, issues);
|
||||
optionalString(input, "detail", path, issues);
|
||||
optionalString(input, "completedAt", path, issues);
|
||||
requireStringArray(input, "refs", path, issues);
|
||||
if ("sequence" in input && input.sequence !== undefined) {
|
||||
requireNumber(input, "sequence", path, issues);
|
||||
}
|
||||
}
|
||||
|
||||
function collectGraphNodeValidationIssues(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input)) {
|
||||
issues.push(issue("schema_mismatch", path, "Graph node must be an object."));
|
||||
return;
|
||||
}
|
||||
requireString(input, "nodeId", path, issues);
|
||||
requireString(input, "nodeType", path, issues);
|
||||
requireString(input, "status", path, issues);
|
||||
requireString(input, "title", path, issues);
|
||||
optionalString(input, "parentId", path, issues);
|
||||
optionalString(input, "createdAt", path, issues);
|
||||
optionalString(input, "completedAt", path, issues);
|
||||
requireStringArray(input, "refs", path, issues);
|
||||
requireStringArray(input, "sourceEventIds", path, issues);
|
||||
}
|
||||
|
||||
function collectRuntimeEventProjectionValidationIssues(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input)) {
|
||||
issues.push(issue("schema_mismatch", path, "Runtime projection event must be an object."));
|
||||
return;
|
||||
}
|
||||
requireString(input, "id", path, issues);
|
||||
requireRecord(input, "source", path, issues);
|
||||
requireString(input, "surface", path, issues);
|
||||
requireString(input, "title", path, issues);
|
||||
requireString(input, "status", path, issues);
|
||||
requireString(input, "displayStatusKey", path, issues);
|
||||
requireBoolean(input, "resolved", path, issues);
|
||||
requireString(input, "actionKind", path, issues);
|
||||
requireString(input, "targetModule", path, issues);
|
||||
optionalString(input, "detail", path, issues);
|
||||
optionalString(input, "actionId", path, issues);
|
||||
if (isRecord(input.source)) {
|
||||
issues.push(...collectRuntimeEventValidationIssues(input.source, `${path}.source`));
|
||||
}
|
||||
}
|
||||
|
||||
function collectRefValidationIssues(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input)) {
|
||||
issues.push(issue("schema_mismatch", path, "Runtime ref must be an object."));
|
||||
return;
|
||||
}
|
||||
requireString(input, "id", path, issues);
|
||||
requireString(input, "sourceEventId", path, issues);
|
||||
optionalString(input, "title", path, issues);
|
||||
optionalString(input, "status", path, issues);
|
||||
optionalString(input, "owner", path, issues);
|
||||
optionalString(input, "path", path, issues);
|
||||
optionalString(input, "contentRef", path, issues);
|
||||
optionalString(input, "mimeType", path, issues);
|
||||
optionalString(input, "preview", path, issues);
|
||||
if (typeof input.preview === "string" && input.preview.length > 512) {
|
||||
issues.push(
|
||||
issue(
|
||||
"large_payload_inline",
|
||||
`${path}.preview`,
|
||||
"Runtime ref preview must stay small; store large content behind contentRef.",
|
||||
),
|
||||
);
|
||||
}
|
||||
if ("metadata" in input && input.metadata !== undefined) {
|
||||
requireRecord(input, "metadata", path, issues);
|
||||
collectPayloadIssues(input.metadata, `${path}.metadata`, issues);
|
||||
}
|
||||
}
|
||||
|
||||
function collectDiagnosticValidationIssues(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input)) {
|
||||
issues.push(issue("schema_mismatch", path, "Diagnostic must be an object."));
|
||||
return;
|
||||
}
|
||||
requireString(input, "id", path, issues);
|
||||
requireString(input, "sourceEventId", path, issues);
|
||||
requireString(input, "title", path, issues);
|
||||
requireString(input, "status", path, issues);
|
||||
optionalString(input, "detail", path, issues);
|
||||
}
|
||||
|
||||
function collectSubagentsValidationIssues(
|
||||
input: Record<string, unknown>,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
requireBoolean(input, "hasSubagents", path, issues);
|
||||
requireArray(input, "threads", path, issues);
|
||||
requireArray(input, "delegationCalls", path, issues);
|
||||
requireArray(input, "activities", path, issues);
|
||||
requireStringArray(input, "activeThreadIds", path, issues);
|
||||
requireStringArray(input, "completedThreadIds", path, issues);
|
||||
requireStringArray(input, "failedThreadIds", path, issues);
|
||||
|
||||
if (Array.isArray(input.threads)) {
|
||||
input.threads.forEach((item, index) => {
|
||||
collectSubagentThreadValidationIssues(
|
||||
item,
|
||||
`${path}.threads[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
if (Array.isArray(input.delegationCalls)) {
|
||||
input.delegationCalls.forEach((item, index) => {
|
||||
collectSubagentDelegationValidationIssues(
|
||||
item,
|
||||
`${path}.delegationCalls[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
if (Array.isArray(input.activities)) {
|
||||
input.activities.forEach((item, index) => {
|
||||
collectSubagentActivityValidationIssues(
|
||||
item,
|
||||
`${path}.activities[${index}]`,
|
||||
issues,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function collectSubagentThreadValidationIssues(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input)) {
|
||||
issues.push(issue("schema_mismatch", path, "Subagent thread must be an object."));
|
||||
return;
|
||||
}
|
||||
requireString(input, "threadId", path, issues);
|
||||
requireString(input, "subagentId", path, issues);
|
||||
requireString(input, "status", path, issues);
|
||||
requireString(input, "title", path, issues);
|
||||
optionalString(input, "parentThreadId", path, issues);
|
||||
optionalString(input, "parentTaskId", path, issues);
|
||||
optionalString(input, "taskId", path, issues);
|
||||
optionalString(input, "taskPath", path, issues);
|
||||
optionalString(input, "role", path, issues);
|
||||
optionalString(input, "nickname", path, issues);
|
||||
optionalString(input, "summary", path, issues);
|
||||
optionalString(input, "promptPreview", path, issues);
|
||||
optionalString(input, "lastActivityAt", path, issues);
|
||||
optionalString(input, "createdAt", path, issues);
|
||||
optionalString(input, "completedAt", path, issues);
|
||||
requireStringArray(input, "artifactRefs", path, issues);
|
||||
requireStringArray(input, "evidenceRefs", path, issues);
|
||||
requireStringArray(input, "sourceEventIds", path, issues);
|
||||
if ("isolation" in input && input.isolation !== undefined) {
|
||||
requireRecord(input, "isolation", path, issues);
|
||||
if (isRecord(input.isolation)) {
|
||||
collectSubagentIsolationValidationIssues(
|
||||
input.isolation,
|
||||
`${path}.isolation`,
|
||||
issues,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectSubagentIsolationValidationIssues(
|
||||
input: Record<string, unknown>,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
optionalString(input, "runtimeProfileId", path, issues);
|
||||
optionalString(input, "modelProfileId", path, issues);
|
||||
optionalString(input, "isolationProfileId", path, issues);
|
||||
optionalString(input, "workspaceRef", path, issues);
|
||||
optionalString(input, "permissionProfile", path, issues);
|
||||
optionalString(input, "sandboxProfile", path, issues);
|
||||
optionalString(input, "forkPolicy", path, issues);
|
||||
if ("depth" in input && input.depth !== undefined) {
|
||||
requireNumber(input, "depth", path, issues);
|
||||
}
|
||||
if ("canDelegate" in input && input.canDelegate !== undefined) {
|
||||
requireBoolean(input, "canDelegate", path, issues);
|
||||
}
|
||||
}
|
||||
|
||||
function collectSubagentDelegationValidationIssues(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input)) {
|
||||
issues.push(issue("schema_mismatch", path, "Subagent delegation must be an object."));
|
||||
return;
|
||||
}
|
||||
requireString(input, "callId", path, issues);
|
||||
requireString(input, "sourceEventId", path, issues);
|
||||
requireString(input, "action", path, issues);
|
||||
requireString(input, "status", path, issues);
|
||||
requireString(input, "title", path, issues);
|
||||
optionalString(input, "parentThreadId", path, issues);
|
||||
optionalString(input, "promptPreview", path, issues);
|
||||
optionalString(input, "createdAt", path, issues);
|
||||
optionalString(input, "completedAt", path, issues);
|
||||
requireStringArray(input, "targetThreadIds", path, issues);
|
||||
}
|
||||
|
||||
function collectSubagentActivityValidationIssues(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input)) {
|
||||
issues.push(issue("schema_mismatch", path, "Subagent activity must be an object."));
|
||||
return;
|
||||
}
|
||||
requireString(input, "activityId", path, issues);
|
||||
requireString(input, "threadId", path, issues);
|
||||
requireString(input, "sourceEventId", path, issues);
|
||||
requireString(input, "kind", path, issues);
|
||||
requireString(input, "status", path, issues);
|
||||
requireString(input, "title", path, issues);
|
||||
optionalString(input, "createdAt", path, issues);
|
||||
}
|
||||
|
||||
export function collectAgentUiFixtureValidationIssues(
|
||||
input: unknown,
|
||||
path = "$",
|
||||
): AgentUiContractValidationIssue[] {
|
||||
const issues: AgentUiContractValidationIssue[] = [];
|
||||
|
||||
if (!isRecord(input)) {
|
||||
return [issue("schema_mismatch", path, "Fixture must be an object.")];
|
||||
}
|
||||
|
||||
requireString(input, "id", path, issues);
|
||||
requireString(input, "schemaVersion", path, issues);
|
||||
requireString(input, "title", path, issues);
|
||||
requireArray(input, "events", path, issues);
|
||||
requireRecord(input, "expected", path, issues);
|
||||
if (isRecord(input.expected)) {
|
||||
collectFixtureExpectationValidationIssues(
|
||||
input.expected,
|
||||
`${path}.expected`,
|
||||
issues,
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(input.events)) {
|
||||
input.events.forEach((event, index) => {
|
||||
issues.push(
|
||||
...collectRuntimeEventValidationIssues(event, `${path}.events[${index}]`),
|
||||
);
|
||||
});
|
||||
issues.push(...collectSequenceIssues(input, path));
|
||||
}
|
||||
|
||||
if ("initialReadModel" in input && input.initialReadModel !== undefined) {
|
||||
issues.push(
|
||||
...collectThreadReadModelValidationIssues(
|
||||
input.initialReadModel,
|
||||
`${path}.initialReadModel`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if ("finalReadModel" in input && input.finalReadModel !== undefined) {
|
||||
issues.push(
|
||||
...collectThreadReadModelValidationIssues(
|
||||
input.finalReadModel,
|
||||
`${path}.finalReadModel`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
function collectFixtureExpectationValidationIssues(
|
||||
input: Record<string, unknown>,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
requireString(input, "status", path, issues);
|
||||
if ("subagents" in input && input.subagents !== undefined) {
|
||||
requireRecord(input, "subagents", path, issues);
|
||||
if (isRecord(input.subagents)) {
|
||||
collectFixtureSubagentsExpectationValidationIssues(
|
||||
input.subagents,
|
||||
`${path}.subagents`,
|
||||
issues,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectFixtureSubagentsExpectationValidationIssues(
|
||||
input: Record<string, unknown>,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
const booleanFields = ["hasSubagents"];
|
||||
const numberFields = [
|
||||
"threadCount",
|
||||
"delegationCallCount",
|
||||
"activityCount",
|
||||
"activeThreadCount",
|
||||
"completedThreadCount",
|
||||
"failedThreadCount",
|
||||
];
|
||||
booleanFields.forEach((field) => {
|
||||
if (field in input && input[field] !== undefined) {
|
||||
requireBoolean(input, field, path, issues);
|
||||
}
|
||||
});
|
||||
numberFields.forEach((field) => {
|
||||
if (field in input && input[field] !== undefined) {
|
||||
requireNumber(input, field, path, issues);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function collectSequenceIssues(
|
||||
fixture: Record<string, unknown>,
|
||||
path: string,
|
||||
): AgentUiContractValidationIssue[] {
|
||||
if (!Array.isArray(fixture.events)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const expectedDiagnostics = isRecord(fixture.expected)
|
||||
&& Array.isArray(fixture.expected.diagnostics)
|
||||
? fixture.expected.diagnostics
|
||||
: [];
|
||||
const allowsSequenceGap = expectedDiagnostics.includes("sequence_gap");
|
||||
if (allowsSequenceGap) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const issues: AgentUiContractValidationIssue[] = [];
|
||||
let previousSequence: number | undefined;
|
||||
fixture.events.forEach((event, index) => {
|
||||
if (!isRecord(event) || !Number.isInteger(event.sequence)) {
|
||||
return;
|
||||
}
|
||||
const sequence = event.sequence as number;
|
||||
if (
|
||||
previousSequence !== undefined
|
||||
&& sequence !== previousSequence + 1
|
||||
) {
|
||||
issues.push(
|
||||
issue(
|
||||
"sequence_gap",
|
||||
`${path}.events[${index}].sequence`,
|
||||
"Runtime event sequence must be contiguous unless the fixture declares sequence_gap diagnostics.",
|
||||
),
|
||||
);
|
||||
}
|
||||
previousSequence = sequence;
|
||||
});
|
||||
return issues;
|
||||
}
|
||||
|
||||
function collectScopeIssues(
|
||||
event: Record<string, unknown>,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
const eventClass = String(event.eventClass);
|
||||
|
||||
if (eventClass.startsWith("tool.") && typeof event.toolCallId !== "string") {
|
||||
issues.push(
|
||||
issue(
|
||||
"missing_scope_id",
|
||||
`${path}.toolCallId`,
|
||||
"Tool events must include toolCallId.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
eventClass.startsWith("action.")
|
||||
&& typeof event.actionId !== "string"
|
||||
) {
|
||||
issues.push(
|
||||
issue(
|
||||
"missing_scope_id",
|
||||
`${path}.actionId`,
|
||||
"Action events must include actionId.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
eventClass.startsWith("artifact.")
|
||||
&& typeof event.artifactId !== "string"
|
||||
&& !hasNonEmptyStringArray(event.artifactRefs)
|
||||
) {
|
||||
issues.push(
|
||||
issue(
|
||||
"missing_scope_id",
|
||||
`${path}.artifactId`,
|
||||
"Artifact events must include artifactId or artifactRefs.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(eventClass.startsWith("evidence.") || eventClass.startsWith("review."))
|
||||
&& typeof event.evidenceId !== "string"
|
||||
&& !hasNonEmptyStringArray(event.evidenceRefs)
|
||||
) {
|
||||
issues.push(
|
||||
issue(
|
||||
"missing_scope_id",
|
||||
`${path}.evidenceId`,
|
||||
"Evidence and review events must include evidenceId or evidenceRefs.",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function collectPayloadIssues(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (input === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serialized = safeStringify(input);
|
||||
if (serialized.length > 32_000) {
|
||||
issues.push(
|
||||
issue(
|
||||
"large_payload_inline",
|
||||
path,
|
||||
"Large runtime payloads must be stored as refs instead of inline event payloads.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
scanSecretKeys(input, path, issues);
|
||||
}
|
||||
|
||||
function scanSecretKeys(
|
||||
input: unknown,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input) && !Array.isArray(input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.entries(input).forEach(([key, value]) => {
|
||||
const childPath = Array.isArray(input) ? `${path}[${key}]` : `${path}.${key}`;
|
||||
if (/(api[-_]?key|authorization|password|secret|token)/i.test(key)) {
|
||||
issues.push(
|
||||
issue(
|
||||
"secret_leak_risk",
|
||||
childPath,
|
||||
"Runtime payload appears to contain a secret-bearing field.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
scanSecretKeys(value, childPath, issues);
|
||||
});
|
||||
}
|
||||
|
||||
function requireString(
|
||||
input: Record<string, unknown>,
|
||||
field: string,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (typeof input[field] !== "string" || input[field] === "") {
|
||||
issues.push(
|
||||
issue(
|
||||
"schema_mismatch",
|
||||
`${path}.${field}`,
|
||||
`${field} must be a non-empty string.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function requireNumber(
|
||||
input: Record<string, unknown>,
|
||||
field: string,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (typeof input[field] !== "number" || !Number.isFinite(input[field])) {
|
||||
issues.push(
|
||||
issue("schema_mismatch", `${path}.${field}`, `${field} must be a number.`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function requireBoolean(
|
||||
input: Record<string, unknown>,
|
||||
field: string,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (typeof input[field] !== "boolean") {
|
||||
issues.push(
|
||||
issue(
|
||||
"schema_mismatch",
|
||||
`${path}.${field}`,
|
||||
`${field} must be a boolean.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function requireArray(
|
||||
input: Record<string, unknown>,
|
||||
field: string,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!Array.isArray(input[field])) {
|
||||
issues.push(
|
||||
issue("schema_mismatch", `${path}.${field}`, `${field} must be an array.`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function requireRecord(
|
||||
input: Record<string, unknown>,
|
||||
field: string,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!isRecord(input[field])) {
|
||||
issues.push(
|
||||
issue("schema_mismatch", `${path}.${field}`, `${field} must be an object.`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function optionalString(
|
||||
input: Record<string, unknown>,
|
||||
field: string,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (field in input && input[field] !== undefined && typeof input[field] !== "string") {
|
||||
issues.push(
|
||||
issue("schema_mismatch", `${path}.${field}`, `${field} must be a string.`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function requireStringArray(
|
||||
input: Record<string, unknown>,
|
||||
field: string,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
requireArray(input, field, path, issues);
|
||||
if (!Array.isArray(input[field])) return;
|
||||
input[field].forEach((item, index) => {
|
||||
if (typeof item !== "string") {
|
||||
issues.push(
|
||||
issue(
|
||||
"schema_mismatch",
|
||||
`${path}.${field}[${index}]`,
|
||||
`${field} entries must be strings.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function optionalStringArray(
|
||||
input: Record<string, unknown>,
|
||||
field: string,
|
||||
path: string,
|
||||
issues: AgentUiContractValidationIssue[],
|
||||
): void {
|
||||
if (!(field in input) || input[field] === undefined) return;
|
||||
requireStringArray(input, field, path, issues);
|
||||
}
|
||||
|
||||
function throwIfIssues(issues: AgentUiContractValidationIssue[]): void {
|
||||
if (issues.length > 0) {
|
||||
throw new AgentUiContractValidationError(issues);
|
||||
}
|
||||
}
|
||||
|
||||
function issue(
|
||||
code: AgentUiContractValidationCode,
|
||||
path: string,
|
||||
message: string,
|
||||
): AgentUiContractValidationIssue {
|
||||
return { code, path, message };
|
||||
}
|
||||
|
||||
function isRecord(input: unknown): input is Record<string, unknown> {
|
||||
return typeof input === "object" && input !== null && !Array.isArray(input);
|
||||
}
|
||||
|
||||
function hasNonEmptyStringArray(input: unknown): boolean {
|
||||
return (
|
||||
Array.isArray(input)
|
||||
&& input.some((item) => typeof item === "string" && item.length > 0)
|
||||
);
|
||||
}
|
||||
|
||||
function safeStringify(input: unknown): string {
|
||||
try {
|
||||
return JSON.stringify(input) ?? "";
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -2,45 +2,375 @@ import assert from "node:assert/strict";
|
||||
import fs from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
import {} from "../dist/index.js";
|
||||
import {
|
||||
AgentUiContractValidationError,
|
||||
agentUiConformanceFixtures,
|
||||
collectAgentUiFixtureValidationIssues,
|
||||
collectProjectionStateValidationIssues,
|
||||
collectRuntimeEventValidationIssues,
|
||||
getAgentUiFixture,
|
||||
validateAgentUiFixture,
|
||||
validateProjectionState,
|
||||
validateRuntimeEvent,
|
||||
} from "../dist/index.js";
|
||||
|
||||
test("agent ui contracts package builds a runtime-free module", () => {
|
||||
assert.equal(true, true);
|
||||
test("agent ui contracts package exports runtime-free validation and fixtures", () => {
|
||||
assert.equal(typeof validateRuntimeEvent, "function");
|
||||
assert.equal(typeof validateAgentUiFixture, "function");
|
||||
assert.equal(typeof getAgentUiFixture, "function");
|
||||
assert.equal(Array.isArray(agentUiConformanceFixtures), true);
|
||||
});
|
||||
|
||||
test("agent ui contracts publish adapter and runtime type declarations", async () => {
|
||||
const declarations = await import("../dist/index.js");
|
||||
|
||||
assert.deepEqual(Object.keys(declarations), []);
|
||||
assert.deepEqual(Object.keys(declarations).sort(), [
|
||||
"AGENT_UI_FIXTURE_SCHEMA_VERSION",
|
||||
"AgentUiContractValidationError",
|
||||
"agentUiConformanceFixtures",
|
||||
"collectAgentUiFixtureValidationIssues",
|
||||
"collectProjectionStateValidationIssues",
|
||||
"collectRuntimeEventValidationIssues",
|
||||
"collectThreadReadModelValidationIssues",
|
||||
"getAgentUiFixture",
|
||||
"validateAgentUiFixture",
|
||||
"validateProjectionState",
|
||||
"validateRuntimeEvent",
|
||||
"validateThreadReadModel",
|
||||
]);
|
||||
|
||||
const indexDeclarations = await readDeclaration("index");
|
||||
assert.equal(
|
||||
indexDeclarations,
|
||||
[
|
||||
'export type * from "./events";',
|
||||
'export * from "./fixtures.js";',
|
||||
'export type * from "./graph";',
|
||||
'export type * from "./messages";',
|
||||
'export type * from "./projection";',
|
||||
'export type * from "./runtime";',
|
||||
'export type * from "./timeline";',
|
||||
'export * from "./validation.js";',
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const typeDeclarations = [
|
||||
await readDeclaration("events"),
|
||||
await readDeclaration("fixtures"),
|
||||
await readDeclaration("runtime"),
|
||||
await readDeclaration("projection"),
|
||||
await readDeclaration("messages"),
|
||||
await readDeclaration("timeline"),
|
||||
await readDeclaration("graph"),
|
||||
await readDeclaration("validation"),
|
||||
].join("\n");
|
||||
|
||||
assert.match(typeDeclarations, /export type AgentUiEventClass/);
|
||||
assert.match(typeDeclarations, /export interface AgentUiProjectionEvent/);
|
||||
assert.match(typeDeclarations, /export interface AgentRuntimeExecutionEvent/);
|
||||
assert.match(typeDeclarations, /subagentId\?: string/);
|
||||
assert.match(typeDeclarations, /handoffId\?: string/);
|
||||
assert.match(typeDeclarations, /reviewId\?: string/);
|
||||
assert.match(typeDeclarations, /export type AgentUiArtifactRefView/);
|
||||
assert.match(typeDeclarations, /export type AgentUiEvidenceRefView/);
|
||||
assert.match(typeDeclarations, /export interface AgentUiSubagentsModel/);
|
||||
assert.match(typeDeclarations, /export interface AgentUiProjectionState/);
|
||||
assert.match(typeDeclarations, /artifacts: AgentUiArtifactRefView\[\]/);
|
||||
assert.match(typeDeclarations, /evidence: AgentUiEvidenceRefView\[\]/);
|
||||
assert.match(typeDeclarations, /subagents: AgentUiSubagentsModel/);
|
||||
assert.match(typeDeclarations, /actions\?: AgentRuntimeActionProjection\[\]/);
|
||||
assert.match(typeDeclarations, /export interface AgentUiFixture/);
|
||||
assert.match(typeDeclarations, /export declare function validateRuntimeEvent/);
|
||||
});
|
||||
|
||||
test("projection state validation requires Subagents model", () => {
|
||||
const projectionState = {
|
||||
runtime: { status: "idle" },
|
||||
messages: [],
|
||||
timeline: [],
|
||||
graph: [],
|
||||
tools: [],
|
||||
actions: [],
|
||||
artifacts: [],
|
||||
evidence: [],
|
||||
diagnostics: [],
|
||||
subagents: {
|
||||
hasSubagents: false,
|
||||
threads: [],
|
||||
delegationCalls: [],
|
||||
activities: [],
|
||||
activeThreadIds: [],
|
||||
completedThreadIds: [],
|
||||
failedThreadIds: [],
|
||||
},
|
||||
readModel: {
|
||||
events: [],
|
||||
visibleEvents: [],
|
||||
pendingActions: [],
|
||||
inputSourceRecovery: false,
|
||||
sourceCount: 0,
|
||||
artifactRefs: [],
|
||||
evidenceRefs: [],
|
||||
taskRefs: [],
|
||||
},
|
||||
hydration: { status: "idle", eventCount: 0 },
|
||||
ephemeralUi: {},
|
||||
};
|
||||
|
||||
assert.equal(validateProjectionState(projectionState), projectionState);
|
||||
|
||||
const withoutSubagents = { ...projectionState };
|
||||
delete withoutSubagents.subagents;
|
||||
assert.deepEqual(
|
||||
collectProjectionStateValidationIssues(withoutSubagents).map(
|
||||
(issue) => issue.path,
|
||||
),
|
||||
["$.subagents"],
|
||||
);
|
||||
|
||||
const withIncompleteSubagents = {
|
||||
...projectionState,
|
||||
subagents: {
|
||||
hasSubagents: false,
|
||||
threads: [],
|
||||
},
|
||||
};
|
||||
assert.deepEqual(
|
||||
collectProjectionStateValidationIssues(withIncompleteSubagents).map(
|
||||
(issue) => issue.path,
|
||||
),
|
||||
[
|
||||
"$.subagents.delegationCalls",
|
||||
"$.subagents.activities",
|
||||
"$.subagents.activeThreadIds",
|
||||
"$.subagents.completedThreadIds",
|
||||
"$.subagents.failedThreadIds",
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("projection state validation checks structured artifact and evidence refs", () => {
|
||||
const projectionState = {
|
||||
runtime: { status: "completed" },
|
||||
messages: [],
|
||||
timeline: [],
|
||||
graph: [],
|
||||
tools: [],
|
||||
actions: [],
|
||||
artifacts: [
|
||||
{
|
||||
id: "prompt-draft:1",
|
||||
sourceEventId: "evt-artifact",
|
||||
title: "Prompt draft",
|
||||
status: "completed",
|
||||
owner: "artifact",
|
||||
path: "drafts/prompt.md",
|
||||
contentRef: "content://prompt-draft/1",
|
||||
mimeType: "text/markdown",
|
||||
preview: "可编辑草稿",
|
||||
metadata: { draftId: "1" },
|
||||
},
|
||||
],
|
||||
evidence: [
|
||||
{
|
||||
id: "input-source:1",
|
||||
sourceEventId: "evt-evidence",
|
||||
title: "Input source",
|
||||
status: "completed",
|
||||
owner: "evidence",
|
||||
path: "evidence/input-source-1",
|
||||
mimeType: "application/json",
|
||||
preview: "证据摘要",
|
||||
metadata: { sourceId: "1" },
|
||||
},
|
||||
],
|
||||
diagnostics: [],
|
||||
subagents: {
|
||||
hasSubagents: false,
|
||||
threads: [],
|
||||
delegationCalls: [],
|
||||
activities: [],
|
||||
activeThreadIds: [],
|
||||
completedThreadIds: [],
|
||||
failedThreadIds: [],
|
||||
},
|
||||
readModel: {
|
||||
events: [],
|
||||
visibleEvents: [],
|
||||
pendingActions: [],
|
||||
inputSourceRecovery: false,
|
||||
sourceCount: 0,
|
||||
artifactRefs: ["prompt-draft:1"],
|
||||
evidenceRefs: ["input-source:1"],
|
||||
taskRefs: [],
|
||||
},
|
||||
hydration: { status: "live", eventCount: 2 },
|
||||
ephemeralUi: {},
|
||||
};
|
||||
|
||||
assert.equal(validateProjectionState(projectionState), projectionState);
|
||||
|
||||
const invalid = {
|
||||
...projectionState,
|
||||
artifacts: [
|
||||
{ id: "prompt-draft:1" },
|
||||
{
|
||||
id: "prompt-draft:2",
|
||||
sourceEventId: "evt-artifact-2",
|
||||
preview: "x".repeat(513),
|
||||
},
|
||||
],
|
||||
evidence: [
|
||||
{
|
||||
id: "input-source:1",
|
||||
sourceEventId: "evt-evidence",
|
||||
metadata: { token: "secret" },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
assert.deepEqual(
|
||||
collectProjectionStateValidationIssues(invalid).map((issue) => [
|
||||
issue.code,
|
||||
issue.path,
|
||||
]),
|
||||
[
|
||||
["schema_mismatch", "$.artifacts[0].sourceEventId"],
|
||||
["large_payload_inline", "$.artifacts[1].preview"],
|
||||
["secret_leak_risk", "$.evidence[0].metadata.token"],
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("agent ui conformance fixtures cover the standard runtime slices", () => {
|
||||
assert.deepEqual(
|
||||
agentUiConformanceFixtures.map((fixture) => fixture.id),
|
||||
[
|
||||
"text-basic",
|
||||
"tool-success",
|
||||
"tool-failure",
|
||||
"hitl-action",
|
||||
"artifact-evidence",
|
||||
"stream-repair",
|
||||
"subagent-handoff",
|
||||
],
|
||||
);
|
||||
|
||||
for (const fixture of agentUiConformanceFixtures) {
|
||||
assert.equal(collectAgentUiFixtureValidationIssues(fixture).length, 0);
|
||||
assert.equal(validateAgentUiFixture(fixture), fixture);
|
||||
}
|
||||
|
||||
const handoff = getAgentUiFixture("subagent-handoff");
|
||||
assert.deepEqual(handoff.expected.subagents, {
|
||||
hasSubagents: true,
|
||||
threadCount: 1,
|
||||
delegationCallCount: 2,
|
||||
activityCount: 4,
|
||||
activeThreadCount: 0,
|
||||
completedThreadCount: 1,
|
||||
failedThreadCount: 0,
|
||||
});
|
||||
assert.ok(
|
||||
handoff.events.some((event) => event.eventClass === "subagent.started"),
|
||||
);
|
||||
assert.ok(
|
||||
handoff.events.some((event) => event.eventClass === "handoff.requested"),
|
||||
);
|
||||
assert.ok(handoff.events.some((event) => event.eventClass === "review.verdict"));
|
||||
});
|
||||
|
||||
test("fixture validation checks Subagents expectation shape", () => {
|
||||
const fixture = {
|
||||
...getAgentUiFixture("subagent-handoff"),
|
||||
expected: {
|
||||
status: "completed",
|
||||
subagents: {
|
||||
hasSubagents: "yes",
|
||||
threadCount: "1",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
assert.deepEqual(
|
||||
collectAgentUiFixtureValidationIssues(fixture).map((issue) => issue.path),
|
||||
[
|
||||
"$.expected.subagents.hasSubagents",
|
||||
"$.expected.subagents.threadCount",
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("runtime event validation enforces scope ids and payload safety", () => {
|
||||
const validToolEvent = {
|
||||
id: "evt_tool",
|
||||
schemaVersion: "lime-runtime-event/v0.1",
|
||||
runtimeId: "runtime",
|
||||
threadId: "thread",
|
||||
turnId: "turn",
|
||||
sequence: 1,
|
||||
kind: "tool",
|
||||
status: "running",
|
||||
eventClass: "tool.started",
|
||||
toolCallId: "tool_1",
|
||||
title: "Tool started",
|
||||
createdAt: "2026-06-10T00:00:00.000Z",
|
||||
};
|
||||
|
||||
assert.equal(validateRuntimeEvent(validToolEvent), validToolEvent);
|
||||
|
||||
assert.deepEqual(
|
||||
collectRuntimeEventValidationIssues({
|
||||
...validToolEvent,
|
||||
toolCallId: undefined,
|
||||
}).map((issue) => issue.code),
|
||||
["missing_scope_id"],
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
collectRuntimeEventValidationIssues({
|
||||
...validToolEvent,
|
||||
payload: { apiKey: "secret" },
|
||||
}).map((issue) => issue.code),
|
||||
["secret_leak_risk"],
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
collectRuntimeEventValidationIssues({
|
||||
...validToolEvent,
|
||||
payload: { text: "x".repeat(33_000) },
|
||||
}).map((issue) => issue.code),
|
||||
["large_payload_inline"],
|
||||
);
|
||||
});
|
||||
|
||||
test("fixture validation reports sequence gaps unless the fixture declares repair", () => {
|
||||
const base = getAgentUiFixture("text-basic");
|
||||
const broken = {
|
||||
...base,
|
||||
events: base.events.map((event, index) =>
|
||||
index === 2 ? { ...event, sequence: 8 } : event,
|
||||
),
|
||||
};
|
||||
|
||||
assert.deepEqual(
|
||||
collectAgentUiFixtureValidationIssues(broken)
|
||||
.map((issue) => issue.code)
|
||||
.filter((code) => code === "sequence_gap"),
|
||||
["sequence_gap", "sequence_gap"],
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => validateAgentUiFixture(broken),
|
||||
AgentUiContractValidationError,
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
collectAgentUiFixtureValidationIssues(getAgentUiFixture("stream-repair"))
|
||||
.length,
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
async function readDeclaration(name) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# app-server-client
|
||||
# @limecloud/app-server-client
|
||||
|
||||
`app-server-client` is the TypeScript client surface for independent apps
|
||||
`@limecloud/app-server-client` is the TypeScript client surface for independent apps
|
||||
that talk to `app-server` over JSON-RPC.
|
||||
|
||||
Current scope:
|
||||
@@ -34,7 +34,7 @@ Electron main integration shape:
|
||||
import {
|
||||
AppServerAgentEventRouter,
|
||||
startPackagedAppServerSidecar,
|
||||
} from "app-server-client";
|
||||
} from "@limecloud/app-server-client";
|
||||
|
||||
const { connected, lifecycle } = await startPackagedAppServerSidecar(
|
||||
{
|
||||
@@ -87,7 +87,7 @@ void (async () => {
|
||||
Agent runtime SDK facade:
|
||||
|
||||
```ts
|
||||
import { createAgentRuntimeClient } from "app-server-client";
|
||||
import { createAgentRuntimeClient } from "@limecloud/app-server-client";
|
||||
|
||||
const runtime = createAgentRuntimeClient(connection, {
|
||||
request: { timeoutMs: 120_000 },
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
{
|
||||
"name": "app-server-client",
|
||||
"version": "1.65.0",
|
||||
"name": "@limecloud/app-server-client",
|
||||
"version": "1.66.0",
|
||||
"type": "module",
|
||||
"description": "App Server JSON-RPC client and sidecar launch helpers for independent apps.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/limecloud/lime.git",
|
||||
"directory": "packages/app-server-client"
|
||||
},
|
||||
"homepage": "https://github.com/limecloud/lime/tree/main/packages/app-server-client#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/limecloud/lime/issues"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
|
||||
@@ -243,6 +243,7 @@ import {
|
||||
METHOD_WORKSPACE_BY_PATH_READ,
|
||||
METHOD_WORKSPACE_DEFAULT_ENSURE,
|
||||
METHOD_WORKSPACE_DEFAULT_READ,
|
||||
METHOD_WORKSPACE_ENSURE,
|
||||
METHOD_WORKSPACE_ENSURE_READY,
|
||||
METHOD_WORKSPACE_LIST,
|
||||
METHOD_WORKSPACE_PROJECTS_ROOT_READ,
|
||||
@@ -593,6 +594,8 @@ import {
|
||||
type WechatRuntimeModelSetParams,
|
||||
type WechatRuntimeModelSetResponse,
|
||||
type WorkspaceEnsureParams,
|
||||
type WorkspaceEnsureProjectParams,
|
||||
type WorkspaceEnsureProjectResponse,
|
||||
type WorkspaceEnsureReadyResponse,
|
||||
type WorkspaceListResponse,
|
||||
type WorkspacePathReadParams,
|
||||
@@ -979,6 +982,10 @@ export class AppServerClient {
|
||||
return this.request(METHOD_WORKSPACE_READ, params);
|
||||
}
|
||||
|
||||
ensureWorkspace(params: WorkspaceEnsureProjectParams): JsonRpcRequest {
|
||||
return this.request(METHOD_WORKSPACE_ENSURE, params);
|
||||
}
|
||||
|
||||
readWorkspaceByPath(params: WorkspacePathReadParams): JsonRpcRequest {
|
||||
return this.request(METHOD_WORKSPACE_BY_PATH_READ, params);
|
||||
}
|
||||
@@ -2254,6 +2261,17 @@ export class AppServerConnection {
|
||||
);
|
||||
}
|
||||
|
||||
async ensureWorkspace(
|
||||
params: WorkspaceEnsureProjectParams,
|
||||
options: AppServerRequestOptions = {},
|
||||
): Promise<AppServerRequestResult<WorkspaceEnsureProjectResponse>> {
|
||||
return await this.request<WorkspaceEnsureProjectResponse>(
|
||||
this.client.ensureWorkspace(params),
|
||||
METHOD_WORKSPACE_ENSURE,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
async readWorkspaceByPath(
|
||||
params: WorkspacePathReadParams,
|
||||
options: AppServerRequestOptions = {},
|
||||
|
||||
@@ -60,6 +60,7 @@ export const METHOD_SESSION_FILE_DELETE = "sessionFile/delete";
|
||||
export const METHOD_SESSION_FILE_LIST = "sessionFile/list";
|
||||
export const METHOD_WORKSPACE_LIST = "workspace/list";
|
||||
export const METHOD_WORKSPACE_READ = "workspace/read";
|
||||
export const METHOD_WORKSPACE_ENSURE = "workspace/ensure";
|
||||
export const METHOD_WORKSPACE_BY_PATH_READ = "workspace/byPath/read";
|
||||
export const METHOD_WORKSPACE_DEFAULT_READ = "workspace/default/read";
|
||||
export const METHOD_WORKSPACE_DEFAULT_ENSURE = "workspace/default/ensure";
|
||||
@@ -359,6 +360,7 @@ export const APP_SERVER_METHODS = [
|
||||
{ method: METHOD_SESSION_FILE_LIST, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_LIST, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_READ, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_ENSURE, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_BY_PATH_READ, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_DEFAULT_READ, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_DEFAULT_ENSURE, kind: "request" },
|
||||
@@ -1446,6 +1448,12 @@ export type WorkspacePathReadParams = {
|
||||
rootPath: string;
|
||||
};
|
||||
|
||||
export type WorkspaceEnsureProjectParams = {
|
||||
name: string;
|
||||
rootPath: string;
|
||||
workspaceType?: string;
|
||||
};
|
||||
|
||||
export type WorkspaceProjectPathResolveParams = {
|
||||
name: string;
|
||||
parentRootPath?: string;
|
||||
@@ -1463,6 +1471,12 @@ export type WorkspaceReadResponse = {
|
||||
workspace?: unknown;
|
||||
};
|
||||
|
||||
export type WorkspaceEnsureProjectResponse = {
|
||||
workspace: unknown;
|
||||
created: boolean;
|
||||
rootCreated: boolean;
|
||||
};
|
||||
|
||||
export type WorkspaceProjectsRootReadResponse = {
|
||||
rootPath: string;
|
||||
};
|
||||
|
||||
@@ -269,6 +269,7 @@ const {
|
||||
METHOD_WORKSPACE_BY_PATH_READ,
|
||||
METHOD_WORKSPACE_DEFAULT_ENSURE,
|
||||
METHOD_WORKSPACE_DEFAULT_READ,
|
||||
METHOD_WORKSPACE_ENSURE,
|
||||
METHOD_WORKSPACE_ENSURE_READY,
|
||||
METHOD_WORKSPACE_LIST,
|
||||
METHOD_WORKSPACE_PROJECTS_ROOT_READ,
|
||||
@@ -418,6 +419,11 @@ test("builds workspace and skill read requests with current methods", () => {
|
||||
const workspaceByPath = client.readWorkspaceByPath({
|
||||
rootPath: "/workspace/project",
|
||||
});
|
||||
const ensuredWorkspace = client.ensureWorkspace({
|
||||
name: "content-studio",
|
||||
rootPath: "/workspace/content-studio",
|
||||
workspaceType: "general",
|
||||
});
|
||||
const defaultWorkspace = client.readDefaultWorkspace();
|
||||
const ensuredDefault = client.ensureDefaultWorkspace();
|
||||
const projectsRoot = client.readWorkspaceProjectsRoot();
|
||||
@@ -493,6 +499,12 @@ test("builds workspace and skill read requests with current methods", () => {
|
||||
assert.deepEqual(workspaceByPath.params, {
|
||||
rootPath: "/workspace/project",
|
||||
});
|
||||
assert.equal(ensuredWorkspace.method, METHOD_WORKSPACE_ENSURE);
|
||||
assert.deepEqual(ensuredWorkspace.params, {
|
||||
name: "content-studio",
|
||||
rootPath: "/workspace/content-studio",
|
||||
workspaceType: "general",
|
||||
});
|
||||
assert.equal(defaultWorkspace.method, METHOD_WORKSPACE_DEFAULT_READ);
|
||||
assert.deepEqual(defaultWorkspace.params, {});
|
||||
assert.equal(ensuredDefault.method, METHOD_WORKSPACE_DEFAULT_ENSURE);
|
||||
@@ -1773,6 +1785,7 @@ test("exports app-server method catalog with request and notification kinds", ()
|
||||
{ method: METHOD_SESSION_FILE_LIST, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_LIST, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_READ, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_ENSURE, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_BY_PATH_READ, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_DEFAULT_READ, kind: "request" },
|
||||
{ method: METHOD_WORKSPACE_DEFAULT_ENSURE, kind: "request" },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@limecloud/lime-cli",
|
||||
"version": "1.65.0",
|
||||
"version": "1.66.0",
|
||||
"description": "Lime 官方任务 CLI",
|
||||
"bin": {
|
||||
"lime": "scripts/run.js"
|
||||
|
||||
@@ -28,7 +28,6 @@ const APP_ID = "content-factory-sdk-fixture-app";
|
||||
const ENTRY_KEY = "dashboard";
|
||||
const ENTRY_ROUTE = "/dashboard";
|
||||
const RUNTIME_VERSION = "0.8.0";
|
||||
const SESSION_ID = "agent-app-electron-sdk-session";
|
||||
const TASK_ID = "agent-app-electron-sdk-task-1";
|
||||
const REQUEST_ID = "agent-app-electron-sdk-request-1";
|
||||
const TASK_KIND = "content_factory.sdk_write";
|
||||
@@ -50,6 +49,16 @@ const SDK_METHODS = [
|
||||
"submitHostResponse",
|
||||
"cancelTask",
|
||||
];
|
||||
const REQUIRED_AGENT_UI_PROJECTION_SELECTORS = [
|
||||
".agent-ui-projection",
|
||||
".agent-ui-main",
|
||||
".agent-ui-sidecar",
|
||||
".agent-message-parts",
|
||||
".agent-process-timeline",
|
||||
".agent-execution-graph",
|
||||
".agent-artifact-refs",
|
||||
".agent-evidence-refs",
|
||||
];
|
||||
const REQUIRED_HOST_ADAPTER_CAPABILITIES = [
|
||||
"lime.agent",
|
||||
"lime.storage",
|
||||
@@ -64,7 +73,7 @@ Agent App Runtime Electron SDK Fixture Smoke
|
||||
|
||||
用途:
|
||||
启动真实 Electron Desktop Host,种子临时 Agent App installed state,
|
||||
通过正式侧栏进入 Agent App runtime page,再由 iframe 内真实 SDK
|
||||
通过正式侧栏 Agent Apps 聚合入口进入 Agent App runtime page,再由 iframe 内真实 SDK
|
||||
createLimeHostBridgeCapabilityInvoker 调用 lime.agent start/get/respond/cancel。
|
||||
后端使用一次性 external backend fixture 记录 App Server RuntimeCore 请求。
|
||||
|
||||
@@ -596,7 +605,6 @@ function pageHtml() {
|
||||
|
||||
const APP_ID = ${JSON.stringify(APP_ID)};
|
||||
const ENTRY_KEY = ${JSON.stringify(ENTRY_KEY)};
|
||||
const SESSION_ID = ${JSON.stringify(SESSION_ID)};
|
||||
const TASK_ID = ${JSON.stringify(TASK_ID)};
|
||||
const REQUEST_ID = ${JSON.stringify(REQUEST_ID)};
|
||||
const TASK_KIND = ${JSON.stringify(TASK_KIND)};
|
||||
@@ -801,7 +809,6 @@ function pageHtml() {
|
||||
requestId: "sdk-fixture-start-task",
|
||||
args: {
|
||||
taskId: TASK_ID,
|
||||
sessionId: SESSION_ID,
|
||||
workspaceId: "workspace-agent-app-sdk-fixture",
|
||||
taskKind: TASK_KIND,
|
||||
title: "Agent App SDK task fixture",
|
||||
@@ -847,13 +854,20 @@ function pageHtml() {
|
||||
if (!runtimeTurnId) {
|
||||
throw new Error("startTask did not return runtime turnId");
|
||||
}
|
||||
const runtimeSessionId =
|
||||
typeof startTask?.sessionId === "string" && startTask.sessionId.length > 0
|
||||
? startTask.sessionId
|
||||
: null;
|
||||
if (!runtimeSessionId) {
|
||||
throw new Error("startTask did not return runtime sessionId");
|
||||
}
|
||||
const firstRead = unwrap("getTask blocked", await bridge.call({
|
||||
capability: "lime.agent",
|
||||
method: "getTask",
|
||||
requestId: "sdk-fixture-get-task-blocked",
|
||||
args: {
|
||||
taskId: TASK_ID,
|
||||
sessionId: SESSION_ID
|
||||
sessionId: runtimeSessionId
|
||||
}
|
||||
}));
|
||||
const hostResponse = unwrap("submitHostResponse", await bridge.call({
|
||||
@@ -871,7 +885,7 @@ function pageHtml() {
|
||||
},
|
||||
eventName: HOST_RESPONSE_EVENT_NAME,
|
||||
actionScope: {
|
||||
sessionId: SESSION_ID,
|
||||
sessionId: runtimeSessionId,
|
||||
turnId: runtimeTurnId
|
||||
}
|
||||
}
|
||||
@@ -882,19 +896,35 @@ function pageHtml() {
|
||||
requestId: "sdk-fixture-get-task-running",
|
||||
args: {
|
||||
taskId: TASK_ID,
|
||||
sessionId: SESSION_ID,
|
||||
sessionId: runtimeSessionId,
|
||||
turnId: runtimeTurnId
|
||||
}
|
||||
}));
|
||||
const artifactEvidence = readArtifactEvidenceFromTaskRecord(secondRead);
|
||||
const toolEvidence = readToolCallEvidenceFromTaskRecord(secondRead);
|
||||
const hostRun = unwrap("openAgentRun", await bridge.call({
|
||||
capability: "lime.ui",
|
||||
method: "openAgentRun",
|
||||
requestId: "sdk-fixture-open-agent-run",
|
||||
args: {
|
||||
taskId: TASK_ID,
|
||||
sessionId: runtimeSessionId,
|
||||
bridgeAction: "agent_app_sdk_fixture.standard_projection",
|
||||
title: "Agent App SDK Host Bridge current path",
|
||||
mode: "drawer",
|
||||
task: secondRead,
|
||||
snapshot: secondRead,
|
||||
runtimeProcess: secondRead?.runtimeProcess ?? secondRead?.process,
|
||||
events: readTaskEvents(secondRead)
|
||||
}
|
||||
}));
|
||||
const cancel = unwrap("cancelTask", await bridge.call({
|
||||
capability: "lime.agent",
|
||||
method: "cancelTask",
|
||||
requestId: "sdk-fixture-cancel-task",
|
||||
args: {
|
||||
taskId: TASK_ID,
|
||||
sessionId: SESSION_ID,
|
||||
sessionId: runtimeSessionId,
|
||||
turnId: runtimeTurnId
|
||||
}
|
||||
}));
|
||||
@@ -910,6 +940,7 @@ function pageHtml() {
|
||||
firstRead,
|
||||
hostResponse,
|
||||
secondRead,
|
||||
hostRun,
|
||||
cancel
|
||||
},
|
||||
artifactEvidence,
|
||||
@@ -1509,23 +1540,27 @@ async function clearInvokeBuffers(page) {
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForAgentAppSidebarEntry(page, options) {
|
||||
async function clickAgentAppsNavEntry(page, options) {
|
||||
const startedAt = Date.now();
|
||||
while (Date.now() - startedAt < options.timeoutMs) {
|
||||
const entry = await evaluatePageSnapshot(page, () => {
|
||||
const buttons = Array.from(document.querySelectorAll("button"));
|
||||
const sidebar =
|
||||
document.querySelector('[data-testid="app-sidebar"]') ?? document;
|
||||
const buttons = Array.from(sidebar.querySelectorAll("button"));
|
||||
const matched = buttons.find((button) => {
|
||||
const label = [button.getAttribute("title"), button.textContent]
|
||||
const label = [
|
||||
button.getAttribute("aria-label"),
|
||||
button.getAttribute("title"),
|
||||
button.textContent,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
return (
|
||||
label.includes("SDK 内容工厂") ||
|
||||
label.includes("content-factory-sdk-fixture-app")
|
||||
);
|
||||
return /Agent Apps|应用中心|App Center/i.test(label);
|
||||
});
|
||||
if (!matched) {
|
||||
return null;
|
||||
}
|
||||
matched.click();
|
||||
return {
|
||||
title: matched.getAttribute("title"),
|
||||
text: matched.textContent,
|
||||
@@ -1537,24 +1572,94 @@ async function waitForAgentAppSidebarEntry(page, options) {
|
||||
}
|
||||
await sleep(options.intervalMs);
|
||||
}
|
||||
throw new Error("未在正式侧栏观察到 SDK fixture Agent App 入口");
|
||||
throw new Error("未在正式侧栏观察到 Agent Apps 聚合入口");
|
||||
}
|
||||
|
||||
async function clickAgentAppSidebarEntry(page) {
|
||||
const button = page
|
||||
.locator("button")
|
||||
.filter({ hasText: /SDK 内容工厂|content-factory-sdk-fixture-app/ })
|
||||
async function waitForAgentAppsInstalledRow(page, options) {
|
||||
const rowSelector = `[data-testid="agent-apps-list-row-${APP_ID}"]`;
|
||||
const installedSelector = `[data-testid="agent-apps-installed-${APP_ID}"]`;
|
||||
const startedAt = Date.now();
|
||||
let lastSnapshot = null;
|
||||
while (Date.now() - startedAt < options.timeoutMs) {
|
||||
const snapshot = await evaluatePageSnapshot(
|
||||
page,
|
||||
({ rowSelector, installedSelector }) => {
|
||||
const pageRoot = document.querySelector(
|
||||
'[data-testid="agent-apps-page"]',
|
||||
);
|
||||
const list = document.querySelector('[data-testid="agent-apps-list"]');
|
||||
const row = document.querySelector(rowSelector);
|
||||
const installedMarker = document.querySelector(installedSelector);
|
||||
return {
|
||||
pageVisible: Boolean(pageRoot),
|
||||
listVisible: Boolean(list),
|
||||
rowVisible: Boolean(row),
|
||||
installedMarkerVisible: Boolean(installedMarker),
|
||||
url: window.location.href,
|
||||
bodyText: document.body?.innerText?.slice(0, 1_200) ?? "",
|
||||
};
|
||||
},
|
||||
{ rowSelector, installedSelector },
|
||||
);
|
||||
if (!snapshot) {
|
||||
await sleep(options.intervalMs);
|
||||
continue;
|
||||
}
|
||||
lastSnapshot = snapshot;
|
||||
if (
|
||||
snapshot.pageVisible &&
|
||||
snapshot.listVisible &&
|
||||
snapshot.rowVisible &&
|
||||
snapshot.installedMarkerVisible
|
||||
) {
|
||||
return {
|
||||
...snapshot,
|
||||
bodyText: sanitizeText(snapshot.bodyText),
|
||||
};
|
||||
}
|
||||
await sleep(options.intervalMs);
|
||||
}
|
||||
throw new Error(
|
||||
`Agent Apps 聚合页未显示 SDK fixture installed row: ${JSON.stringify(
|
||||
sanitizeJson(lastSnapshot),
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function openAgentAppRuntimeFromAgentAppsPage(page, options) {
|
||||
const navEntry = await clickAgentAppsNavEntry(page, options);
|
||||
await page.waitForSelector('[data-testid="agent-apps-page"]', {
|
||||
timeout: options.timeoutMs,
|
||||
});
|
||||
await page.waitForSelector('[data-testid="agent-apps-list"]', {
|
||||
timeout: options.timeoutMs,
|
||||
});
|
||||
const installedRow = await waitForAgentAppsInstalledRow(page, options);
|
||||
|
||||
const detailButton = page
|
||||
.locator(`[data-testid="agent-apps-open-detail-${APP_ID}"]`)
|
||||
.first();
|
||||
if ((await button.count()) > 0) {
|
||||
await button.click();
|
||||
return;
|
||||
}
|
||||
const titled = page.locator('button[title*="SDK 内容工厂"]').first();
|
||||
if ((await titled.count()) > 0) {
|
||||
await titled.click();
|
||||
return;
|
||||
}
|
||||
throw new Error("SDK fixture Agent App 侧栏按钮不可点击");
|
||||
await detailButton.click({ timeout: options.timeoutMs });
|
||||
await page.waitForSelector('[data-testid="agent-apps-detail"]', {
|
||||
timeout: options.timeoutMs,
|
||||
});
|
||||
const launchEntrySelector = `[data-testid="agent-apps-launch-entry-${ENTRY_KEY}"]`;
|
||||
await page.waitForSelector(launchEntrySelector, {
|
||||
timeout: options.timeoutMs,
|
||||
});
|
||||
await page.locator(launchEntrySelector).first().click({
|
||||
timeout: options.timeoutMs,
|
||||
});
|
||||
|
||||
return {
|
||||
navEntry,
|
||||
installedRow,
|
||||
detailVisible:
|
||||
(await page.locator('[data-testid="agent-apps-detail"]').count()) > 0,
|
||||
launchEntryKey: ENTRY_KEY,
|
||||
};
|
||||
}
|
||||
|
||||
async function waitForSdkFixtureResult(page, options) {
|
||||
@@ -1617,6 +1722,97 @@ async function captureRuntimePageEvidence(page) {
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForHostProjectionSurface(page, options) {
|
||||
const startedAt = Date.now();
|
||||
let lastSnapshot = null;
|
||||
while (Date.now() - startedAt < options.timeoutMs) {
|
||||
const snapshot = await evaluatePageSnapshot(
|
||||
page,
|
||||
(requiredSelectors) => {
|
||||
const dock = document.querySelector(
|
||||
'[data-testid="agent-app-host-agent-run-dock"]',
|
||||
);
|
||||
const drawer = document.querySelector(
|
||||
'[data-testid="agent-app-host-agent-run-drawer"]',
|
||||
);
|
||||
const missingSelectors = requiredSelectors.filter(
|
||||
(selector) => !document.querySelector(selector),
|
||||
);
|
||||
const projectionRoot = document.querySelector(".agent-ui-projection");
|
||||
const action = document.querySelector("[data-action-id]");
|
||||
const actionRequiredList = document.querySelector(
|
||||
".agent-action-required-list",
|
||||
);
|
||||
const artifact = document.querySelector(
|
||||
'.agent-artifact-refs [data-ref-kind="artifact"]',
|
||||
);
|
||||
const evidence = document.querySelector(
|
||||
'.agent-evidence-refs [data-ref-kind="evidence"]',
|
||||
);
|
||||
const toolEntry = document.querySelector(
|
||||
'.agent-process-entry[data-entry-kind="tool"]',
|
||||
);
|
||||
return {
|
||||
dockVisible: Boolean(dock),
|
||||
drawerVisible: Boolean(drawer),
|
||||
projectionVisible: Boolean(projectionRoot),
|
||||
runtimeStatus:
|
||||
projectionRoot instanceof HTMLElement
|
||||
? projectionRoot.dataset.runtimeStatus ?? null
|
||||
: null,
|
||||
hydrationStatus:
|
||||
projectionRoot instanceof HTMLElement
|
||||
? projectionRoot.dataset.hydrationStatus ?? null
|
||||
: null,
|
||||
missingSelectors,
|
||||
actionId:
|
||||
action instanceof HTMLElement ? action.dataset.actionId ?? null : null,
|
||||
actionRequiredListVisible: Boolean(actionRequiredList),
|
||||
artifactRefId:
|
||||
artifact instanceof HTMLElement ? artifact.dataset.refId ?? null : null,
|
||||
evidenceRefId:
|
||||
evidence instanceof HTMLElement ? evidence.dataset.refId ?? null : null,
|
||||
toolEntryVisible: Boolean(toolEntry),
|
||||
textPreview: document.body?.innerText?.slice(0, 1_000) ?? "",
|
||||
};
|
||||
},
|
||||
REQUIRED_AGENT_UI_PROJECTION_SELECTORS,
|
||||
);
|
||||
if (!snapshot) {
|
||||
await sleep(options.intervalMs);
|
||||
continue;
|
||||
}
|
||||
lastSnapshot = snapshot;
|
||||
if (snapshot.dockVisible && !snapshot.drawerVisible) {
|
||||
await page
|
||||
.locator('[data-testid="agent-app-host-agent-run-dock"]')
|
||||
.first()
|
||||
.click({ timeout: Math.min(options.intervalMs, 1_000) })
|
||||
.catch(() => {});
|
||||
await sleep(options.intervalMs);
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
snapshot.drawerVisible &&
|
||||
snapshot.projectionVisible &&
|
||||
snapshot.missingSelectors.length === 0
|
||||
) {
|
||||
return {
|
||||
...snapshot,
|
||||
textPreview: sanitizeText(snapshot.textPreview),
|
||||
};
|
||||
}
|
||||
await sleep(options.intervalMs);
|
||||
}
|
||||
throw new Error(
|
||||
`Host Agent Run 标准 AgentUI projection surface 未就绪: ${JSON.stringify(
|
||||
sanitizeJson(lastSnapshot),
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function stopRuntimeFromPage(page) {
|
||||
return await page.evaluate(
|
||||
async ({ appId }) => {
|
||||
@@ -1663,8 +1859,9 @@ function assertSdkLifecycleResult(result, backendSummary) {
|
||||
"SDK startTask taskId 不正确",
|
||||
);
|
||||
assert(
|
||||
result.taskLifecycle?.startTask?.sessionId === SESSION_ID,
|
||||
"SDK startTask sessionId 不正确",
|
||||
typeof result.taskLifecycle?.startTask?.sessionId === "string" &&
|
||||
result.taskLifecycle.startTask.sessionId.length > 0,
|
||||
"SDK startTask 未返回有效 sessionId",
|
||||
);
|
||||
assert(
|
||||
typeof result.taskLifecycle?.startTask?.turnId === "string" &&
|
||||
@@ -1675,6 +1872,10 @@ function assertSdkLifecycleResult(result, backendSummary) {
|
||||
result.ids?.turnId === result.taskLifecycle?.startTask?.turnId,
|
||||
"SDK ids.turnId 未使用 startTask 返回的 runtime turnId",
|
||||
);
|
||||
assert(
|
||||
result.ids?.sessionId === result.taskLifecycle?.startTask?.sessionId,
|
||||
"SDK ids.sessionId 未使用 startTask 返回的 runtime sessionId",
|
||||
);
|
||||
assert(
|
||||
result.taskLifecycle?.firstRead?.status === "running",
|
||||
`首次 SDK getTask status 应为 running,实际 ${result.taskLifecycle?.firstRead?.status}`,
|
||||
@@ -1687,6 +1888,14 @@ function assertSdkLifecycleResult(result, backendSummary) {
|
||||
result.taskLifecycle?.secondRead?.status === "running",
|
||||
`submitHostResponse 后 SDK getTask status 应为 running,实际 ${result.taskLifecycle?.secondRead?.status}`,
|
||||
);
|
||||
assert(
|
||||
result.taskLifecycle?.hostRun?.surface === "host_agent_run",
|
||||
"SDK lime.ui.openAgentRun 未返回 host_agent_run surface",
|
||||
);
|
||||
assert(
|
||||
result.taskLifecycle?.hostRun?.opened === true,
|
||||
"SDK lime.ui.openAgentRun 未打开 Host Agent Run 面板",
|
||||
);
|
||||
assert(
|
||||
result.taskLifecycle?.cancel?.status === "cancelled",
|
||||
`SDK cancelTask status 应为 cancelled,实际 ${result.taskLifecycle?.cancel?.status}`,
|
||||
@@ -1772,8 +1981,8 @@ function assertSdkLifecycleResult(result, backendSummary) {
|
||||
"turn_config provider_config.model 未抵达 backend",
|
||||
);
|
||||
assert(
|
||||
backendSummary.startSessionId === SESSION_ID,
|
||||
"external backend turnStart sessionId 不正确",
|
||||
backendSummary.startSessionId === result.taskLifecycle?.startTask?.sessionId,
|
||||
"external backend turnStart sessionId 未与 SDK startTask 返回值一致",
|
||||
);
|
||||
assert(
|
||||
backendSummary.startTurnId === result.taskLifecycle?.startTask?.turnId,
|
||||
@@ -1839,7 +2048,7 @@ async function run() {
|
||||
checkedAt: new Date().toISOString(),
|
||||
appId: APP_ID,
|
||||
entryKey: ENTRY_KEY,
|
||||
sessionId: SESSION_ID,
|
||||
sessionId: null,
|
||||
taskId: TASK_ID,
|
||||
turnId: null,
|
||||
requestId: REQUEST_ID,
|
||||
@@ -1856,6 +2065,9 @@ async function run() {
|
||||
backendLogPath: options.keepTemp ? runtimeEnv.backendLogPath : null,
|
||||
electronPreloadBridge: false,
|
||||
sidebarEntryVisible: false,
|
||||
agentAppsPageVisible: false,
|
||||
agentAppsInstalledRowVisible: false,
|
||||
agentAppsLaunchEntryVisible: false,
|
||||
runtimeSurfaceVisible: false,
|
||||
runtimeFrameVisible: false,
|
||||
frameSrc: null,
|
||||
@@ -1915,17 +2127,26 @@ async function run() {
|
||||
rendererSnapshot.electron && rendererSnapshot.hasInvokeBridge;
|
||||
await clearInvokeBuffers(page);
|
||||
|
||||
logStage("wait-sidebar-entry");
|
||||
const sidebarEntry = await waitForAgentAppSidebarEntry(page, options);
|
||||
logStage("open-agent-apps-runtime-page");
|
||||
const agentAppsLaunch = await openAgentAppRuntimeFromAgentAppsPage(
|
||||
page,
|
||||
options,
|
||||
);
|
||||
summary.sidebarEntryVisible = true;
|
||||
summary.sidebarEntry = sanitizeJson(sidebarEntry);
|
||||
|
||||
logStage("open-runtime-page");
|
||||
await clickAgentAppSidebarEntry(page);
|
||||
summary.sidebarEntry = sanitizeJson(agentAppsLaunch.navEntry);
|
||||
summary.agentAppsPageVisible = true;
|
||||
summary.agentAppsInstalledRowVisible = true;
|
||||
summary.agentAppsLaunchEntryVisible = true;
|
||||
summary.agentAppsLaunch = sanitizeJson(agentAppsLaunch);
|
||||
|
||||
logStage("wait-sdk-result");
|
||||
const sdkEvidence = await waitForSdkFixtureResult(page, options);
|
||||
const runtimeEvidence = await captureRuntimePageEvidence(page);
|
||||
logStage("wait-host-agentui-projection");
|
||||
const hostProjectionEvidence = await waitForHostProjectionSurface(
|
||||
page,
|
||||
options,
|
||||
);
|
||||
const traceEntries = invokeTraceEntriesFromStorage(
|
||||
runtimeEvidence?.traceRaw,
|
||||
);
|
||||
@@ -1934,6 +2155,7 @@ async function run() {
|
||||
const backendSummary = summarizeBackendLog(backendEntries);
|
||||
|
||||
Object.assign(summary, {
|
||||
sessionId: sdkEvidence.result?.taskLifecycle?.startTask?.sessionId ?? null,
|
||||
turnId: sdkEvidence.result?.taskLifecycle?.startTask?.turnId ?? null,
|
||||
runtimeSurfaceVisible: Boolean(runtimeEvidence?.surfaceVisible),
|
||||
runtimeFrameVisible: Boolean(runtimeEvidence?.frameVisible),
|
||||
@@ -1943,6 +2165,7 @@ async function run() {
|
||||
sdkEvidence.result?.artifactEvidence ?? null,
|
||||
),
|
||||
toolEvidence: sanitizeJson(sdkEvidence.result?.toolEvidence ?? null),
|
||||
hostProjectionEvidence: sanitizeJson(hostProjectionEvidence),
|
||||
frameContent: sanitizeJson({
|
||||
url: sdkEvidence.url,
|
||||
bodyTextPreview: sdkEvidence.bodyTextPreview,
|
||||
@@ -2005,6 +2228,22 @@ async function run() {
|
||||
summary.appServerHandleJsonLinesSeen,
|
||||
"未观察到 app_server_handle_json_lines",
|
||||
);
|
||||
assert(
|
||||
summary.hostProjectionEvidence?.projectionVisible === true,
|
||||
"未观察到 Host Agent Run 标准 AgentUI projection surface",
|
||||
);
|
||||
assert(
|
||||
summary.hostProjectionEvidence?.artifactRefId,
|
||||
"Host Agent Run 标准 ArtifactRef surface 未渲染",
|
||||
);
|
||||
assert(
|
||||
summary.hostProjectionEvidence?.evidenceRefId,
|
||||
"Host Agent Run 标准 EvidenceRef surface 未渲染",
|
||||
);
|
||||
assert(
|
||||
summary.hostProjectionEvidence?.toolEntryVisible === true,
|
||||
"Host Agent Run 标准 ProcessTimeline 未渲染 tool entry",
|
||||
);
|
||||
assert(
|
||||
consoleErrors.length === 0,
|
||||
`观察到 console error: ${consoleErrors.join(" | ")}`,
|
||||
|
||||
@@ -22,8 +22,13 @@ describe("agent app runtime Electron SDK fixture smoke guard", () => {
|
||||
expect(content).toContain(
|
||||
'typeof window.electronAPI?.invoke === "function"',
|
||||
);
|
||||
expect(content).toContain("waitForAgentAppSidebarEntry(");
|
||||
expect(content).toContain("clickAgentAppSidebarEntry(");
|
||||
expect(content).toContain("clickAgentAppsNavEntry(");
|
||||
expect(content).toContain("openAgentAppRuntimeFromAgentAppsPage(");
|
||||
expect(content).toContain('[data-testid="agent-apps-page"]');
|
||||
expect(content).toContain('[data-testid="agent-apps-list"]');
|
||||
expect(content).toContain("agent-apps-list-row-${APP_ID}");
|
||||
expect(content).toContain("agent-apps-open-detail-${APP_ID}");
|
||||
expect(content).toContain("agent-apps-launch-entry-${ENTRY_KEY}");
|
||||
expect(content).toContain('[data-testid="agent-app-runtime-surface"]');
|
||||
expect(content).toContain('[data-testid="agent-app-runtime-frame"]');
|
||||
expect(content).toContain("__agentAppSdkFixtureResult");
|
||||
@@ -71,14 +76,22 @@ describe("agent app runtime Electron SDK fixture smoke guard", () => {
|
||||
expect(content).not.toContain("?? TURN_ID");
|
||||
expect(content).not.toContain("agent-app-electron-sdk-turn-1");
|
||||
expect(content).toContain("const runtimeTurnId =");
|
||||
expect(content).toContain("const runtimeSessionId =");
|
||||
expect(content).toContain("startTask did not return runtime turnId");
|
||||
expect(content).toContain("startTask did not return runtime sessionId");
|
||||
expect(content).toMatch(
|
||||
/method: "submitHostResponse"[\s\S]*actionScope: \{[\s\S]*sessionId: SESSION_ID,[\s\S]*turnId: runtimeTurnId[\s\S]*\}/,
|
||||
/method: "submitHostResponse"[\s\S]*actionScope: \{[\s\S]*sessionId: runtimeSessionId,[\s\S]*turnId: runtimeTurnId[\s\S]*\}/,
|
||||
);
|
||||
expect(content).toMatch(
|
||||
/method: "cancelTask"[\s\S]*args: \{[\s\S]*taskId: TASK_ID,[\s\S]*sessionId: SESSION_ID,[\s\S]*turnId: runtimeTurnId[\s\S]*\}/,
|
||||
/method: "cancelTask"[\s\S]*args: \{[\s\S]*taskId: TASK_ID,[\s\S]*sessionId: runtimeSessionId,[\s\S]*turnId: runtimeTurnId[\s\S]*\}/,
|
||||
);
|
||||
expect(content).not.toContain("const SESSION_ID");
|
||||
expect(content).not.toContain("sessionId: SESSION_ID");
|
||||
expect(content).toContain("SDK startTask 未返回有效 turnId");
|
||||
expect(content).toContain("SDK startTask 未返回有效 sessionId");
|
||||
expect(content).toContain(
|
||||
"external backend turnStart sessionId 未与 SDK startTask 返回值一致",
|
||||
);
|
||||
expect(content).toContain(
|
||||
"external backend turnStart turnId 未与 SDK startTask 返回值一致",
|
||||
);
|
||||
@@ -88,6 +101,9 @@ describe("agent app runtime Electron SDK fixture smoke guard", () => {
|
||||
expect(content).toContain(
|
||||
"turnId: sdkEvidence.result?.taskLifecycle?.startTask?.turnId ?? null",
|
||||
);
|
||||
expect(content).toContain(
|
||||
"sessionId: sdkEvidence.result?.taskLifecycle?.startTask?.sessionId ?? null",
|
||||
);
|
||||
expect(content).not.toContain("backendSummary.startTurnId === TURN_ID");
|
||||
expect(content).not.toContain("backendSummary.cancelTurnId === TURN_ID");
|
||||
});
|
||||
@@ -118,6 +134,29 @@ describe("agent app runtime Electron SDK fixture smoke guard", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("proves the Host Agent Run standard projection without requiring stale pending actions", () => {
|
||||
const content = readSmokeScript();
|
||||
|
||||
const selectorDeclaration = content.match(
|
||||
/const REQUIRED_AGENT_UI_PROJECTION_SELECTORS = \[[\s\S]*?\];/,
|
||||
)?.[0] ?? "";
|
||||
expect(selectorDeclaration).toContain('".agent-ui-projection"');
|
||||
expect(selectorDeclaration).toContain('".agent-ui-main"');
|
||||
expect(selectorDeclaration).toContain('".agent-ui-sidecar"');
|
||||
expect(selectorDeclaration).toContain('".agent-message-parts"');
|
||||
expect(selectorDeclaration).toContain('".agent-process-timeline"');
|
||||
expect(selectorDeclaration).toContain('".agent-execution-graph"');
|
||||
expect(selectorDeclaration).toContain('".agent-artifact-refs"');
|
||||
expect(selectorDeclaration).toContain('".agent-evidence-refs"');
|
||||
expect(selectorDeclaration).not.toContain(".agent-action-required-list");
|
||||
expect(content).toContain("actionRequiredListVisible");
|
||||
expect(content).toContain("Host Agent Run 标准 EvidenceRef surface 未渲染");
|
||||
expect(content).toContain("Host Agent Run 标准 ProcessTimeline 未渲染 tool entry");
|
||||
expect(content).not.toContain(
|
||||
"Host Agent Run 标准 ActionRequired surface 未关联 fixture requestId",
|
||||
);
|
||||
});
|
||||
|
||||
it("replays App Server tool call facts into the SDK task record", () => {
|
||||
const content = readSmokeScript();
|
||||
|
||||
|
||||
@@ -2182,12 +2182,12 @@ function buildRuntimeSummaryCheckScript() {
|
||||
text.includes("拒绝并回滚"),
|
||||
hasWebSearchReady: text.includes("WebSearch 已接通"),
|
||||
hasSubagentReady: text.includes("子任务核心 tools 已接通"),
|
||||
hasTeamReady: text.includes("Team current tools 已接通"),
|
||||
hasTeamReady: text.includes("Subagents 协作 tools 已接通"),
|
||||
hasTaskReady: text.includes("Task current tools 已接通"),
|
||||
hasReadyBanner: text.includes("当前 runtime current surface 已覆盖 WebSearch、子任务、Team 与 Task 主链。"),
|
||||
hasReadyBanner: text.includes("当前 runtime current surface 已覆盖 WebSearch、子任务、Subagents 协作与 Task 主链。"),
|
||||
hasWebSearchGap: text.includes("WebSearch 未接通"),
|
||||
hasSubagentGap: text.includes("子任务核心 tools 缺"),
|
||||
hasTeamGap: text.includes("Team current tools 缺"),
|
||||
hasTeamGap: text.includes("Subagents 协作 tools 缺"),
|
||||
hasTaskGap: text.includes("Task current tools 缺"),
|
||||
hasGapBanner: text.includes("当前 runtime current surface 仍有缺口"),
|
||||
hasLegacyWebSearchWarning: text.includes(${JSON.stringify(FORBIDDEN_PAGE_WARNINGS[0])}),
|
||||
|
||||
@@ -1241,6 +1241,7 @@ const checks = [
|
||||
"METHOD_WORKSPACE_LIST",
|
||||
"METHOD_WORKSPACE_READ",
|
||||
"METHOD_WORKSPACE_BY_PATH_READ",
|
||||
"METHOD_WORKSPACE_ENSURE",
|
||||
"METHOD_WORKSPACE_DEFAULT_READ",
|
||||
"METHOD_WORKSPACE_DEFAULT_ENSURE",
|
||||
"METHOD_WORKSPACE_PROJECTS_ROOT_READ",
|
||||
@@ -1255,13 +1256,16 @@ const checks = [
|
||||
"METHOD_WORKSPACE_PROJECT_PATH_RESOLVE",
|
||||
"METHOD_WORKSPACE_LIST",
|
||||
"METHOD_WORKSPACE_DEFAULT_READ",
|
||||
"METHOD_WORKSPACE_ENSURE",
|
||||
"METHOD_WORKSPACE_ENSURE_READY",
|
||||
"METHOD_WORKSPACE_DEFAULT_ENSURE",
|
||||
"METHOD_WORKSPACE_BY_PATH_READ",
|
||||
"METHOD_WORKSPACE_READ",
|
||||
"workspace id is required to ensure App Server workspace",
|
||||
"workspace rootPath is required to ensure project",
|
||||
"App Server workspace/projectsRoot/read did not return rootPath",
|
||||
"App Server workspace/projectPath/resolve did not return rootPath",
|
||||
"App Server workspace/ensure did not return workspace",
|
||||
"App Server workspace/ensureReady did not return result",
|
||||
"App Server workspace/default/ensure did not return workspace",
|
||||
],
|
||||
@@ -1286,6 +1290,7 @@ const checks = [
|
||||
"应该通过 App Server 获取 workspace 根目录",
|
||||
"应该通过 App Server 解析项目目录",
|
||||
"应该通过 App Server 按路径获取项目",
|
||||
"应该通过 App Server ensure 本地项目工作区",
|
||||
"应该通过 App Server 获取并标准化默认项目",
|
||||
"应该通过 App Server 确保工作区目录就绪",
|
||||
"确保默认工作区目录就绪时应先 ensure 默认 workspace 再 ensureReady",
|
||||
@@ -1294,6 +1299,7 @@ const checks = [
|
||||
'"workspace/projectsRoot/read"',
|
||||
'"workspace/projectPath/resolve"',
|
||||
'"workspace/byPath/read"',
|
||||
'"workspace/ensure"',
|
||||
'"workspace/default/read"',
|
||||
'"workspace/default/ensure"',
|
||||
'"workspace/ensureReady"',
|
||||
@@ -2284,7 +2290,7 @@ const checks = [
|
||||
"toolEvidence: sanitizeJson(sdkEvidence.result?.toolEvidence ?? null)",
|
||||
"SDK secondRead 未从 App Server read model replay task:toolCall",
|
||||
"SDK secondRead result thread_read.tool_calls missing App Server tool call",
|
||||
"external backend turnStart sessionId 不正确",
|
||||
"external backend turnStart sessionId 未与 SDK startTask 返回值一致",
|
||||
"external backend actionRespond requestId 不正确",
|
||||
"external backend turnCancel turnId 未使用 SDK startTask 返回值",
|
||||
],
|
||||
@@ -4232,11 +4238,11 @@ const checks = [
|
||||
file: "src/components/agent/chat/projection/agentUiProjectionSummary.ts",
|
||||
snippets: [
|
||||
'from "@limecloud/agent-runtime-projection"',
|
||||
"summarizeAgentUiTeamWorkbenchSurfaceLanesBase",
|
||||
"summarizeAgentUiTeamWorkbenchSurfacesBase",
|
||||
"summarizeAgentUiSubagentsSurfaceLanesBase",
|
||||
"summarizeAgentUiSubagentsSurfacesBase",
|
||||
"findLatestAgentUiProjectionEventForArtifact",
|
||||
"summarizeAgentUiProjectionEvents",
|
||||
"summarizeAgentUiTeamWorkbenchProjectionEvents",
|
||||
"summarizeAgentUiSubagentsProjectionEvents",
|
||||
],
|
||||
absentSnippets: [
|
||||
"export const AGENT_UI_ACTION_EVENT_TYPES = new Set",
|
||||
@@ -4244,10 +4250,10 @@ const checks = [
|
||||
"export const AGENT_UI_ARTIFACT_EVENT_TYPES = new Set",
|
||||
"export const AGENT_UI_DIAGNOSTIC_EVENT_TYPES = new Set",
|
||||
"export const AGENT_UI_EVIDENCE_EVENT_TYPES = new Set",
|
||||
"export const AGENT_UI_TEAM_WORKBENCH_SURFACES = new Set",
|
||||
"export const AGENT_UI_SUBAGENTS_SURFACES = new Set",
|
||||
"export const AGENT_UI_NOTABLE_EVENT_TYPES = new Set",
|
||||
"export function summarizeAgentUiProjectionEvents(",
|
||||
"export function summarizeAgentUiTeamWorkbenchProjectionEvents(",
|
||||
"export function summarizeAgentUiSubagentsProjectionEvents(",
|
||||
"export function findLatestAgentUiProjectionEventForArtifact(",
|
||||
"function normalizeLookupKey",
|
||||
],
|
||||
@@ -5157,7 +5163,8 @@ const checks = [
|
||||
'import { projectAgentUiState } from "@limecloud/agent-runtime-projection"',
|
||||
"buildAgentAppStandardRuntimeEvents",
|
||||
"return projectAgentUiState({ executionEvents })",
|
||||
"buildAgentRunStandardProjectionStateFromState(run)",
|
||||
"buildSharedProjectionInput(run, t)",
|
||||
"buildAgentRunStandardProjectionStateFromState(sharedProjectionInput)",
|
||||
"standardState: AgentUiProjectionState",
|
||||
'data-testid="agent-run-standard-projection"',
|
||||
"<AgentUiProjectionView",
|
||||
@@ -5511,6 +5518,166 @@ const checks = [
|
||||
"invokeMockOnly",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Agent App runtime standard client adapter uses App Server AgentRuntimeClient current methods",
|
||||
files: [
|
||||
"src/features/agent-app/runtime/agentRuntimeCapabilityApi.ts",
|
||||
"src/features/agent-app/runtime/agentRuntimeClientApi.ts",
|
||||
"src/features/agent-app/runtime/agentRuntimeClientApi.test.ts",
|
||||
],
|
||||
snippets: [
|
||||
'@limecloud/agent-runtime-client',
|
||||
"createFailClosedAgentAppRuntimeCapabilityApi",
|
||||
"createAgentAppRuntimeCapabilityApiFromClient",
|
||||
'"startTurn" | "readThread" | "cancelTurn" | "respondAction"',
|
||||
"runtimeClient.startTurn(startParams)",
|
||||
"runtimeClient.readThread({",
|
||||
"runtimeClient.cancelTurn({",
|
||||
"runtimeClient.respondAction(",
|
||||
"AgentRuntimeClient adapter requires an existing sessionId",
|
||||
"hostOptions: {",
|
||||
"asterChatRequest",
|
||||
"turn_config: turnConfig",
|
||||
"threadReadFromAgentSessionRead(response)",
|
||||
"detail?.thread_read",
|
||||
"detail?.threadRead",
|
||||
"requires a standard AgentRuntimeClient or explicit compat api",
|
||||
"把 Agent App startTask 投影到标准 AgentRuntimeClient.startTurn",
|
||||
"没有 sessionId 时 fail closed,不伪造独立 task 协议",
|
||||
],
|
||||
absentSnippets: [
|
||||
"defaultAgentAppRuntimeCapabilityApi",
|
||||
"safeInvoke",
|
||||
"agent_app_runtime_start_task",
|
||||
"agent_app_runtime_get_task",
|
||||
"agent_app_runtime_cancel_task",
|
||||
"agent_app_runtime_submit_host_response",
|
||||
"agent_runtime_submit_turn",
|
||||
"agent_runtime_get_thread_read",
|
||||
"agent_runtime_interrupt_turn",
|
||||
"agent_runtime_respond_action",
|
||||
"mockPriorityCommands",
|
||||
"defaultMocks",
|
||||
"invokeMockOnly",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Agent runtime client package exposes session gateway adapter",
|
||||
files: [
|
||||
"packages/agent-runtime-client/package.json",
|
||||
"packages/agent-runtime-client/README.md",
|
||||
"packages/agent-runtime-client/src/index.ts",
|
||||
"packages/agent-runtime-client/src/sessionGateway.ts",
|
||||
"packages/agent-runtime-client/tests/client.test.mjs",
|
||||
"tsconfig.json",
|
||||
"vite.config.ts",
|
||||
],
|
||||
snippets: [
|
||||
'"./sessionGateway"',
|
||||
"@limecloud/agent-runtime-client/sessionGateway",
|
||||
"./packages/agent-runtime-client/src/sessionGateway.ts",
|
||||
"browser-safe 子路径",
|
||||
"createAgentRuntimeClientFromSessionGateway",
|
||||
"type AgentRuntimeLifecycleClient",
|
||||
"type AgentRuntimeSessionGateway",
|
||||
'"startTurn" | "readThread" | "cancelTurn" | "respondAction"',
|
||||
"readSession(",
|
||||
"callAgentRuntimeSessionGateway(gateway.readSession, params, options)",
|
||||
"if (options === undefined)",
|
||||
"createAgentRuntimeClientFromSessionGateway adapts an existing session gateway",
|
||||
"readSession",
|
||||
"timeoutMs: 120_000",
|
||||
],
|
||||
absentSnippets: [
|
||||
"safeInvoke",
|
||||
"mockPriorityCommands",
|
||||
"defaultMocks",
|
||||
"invokeMockOnly",
|
||||
"agent_app_runtime_",
|
||||
"agent_runtime_submit_turn",
|
||||
"fetch(",
|
||||
"XMLHttpRequest",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Agent App capability host accepts standard AgentRuntimeClient injection",
|
||||
files: [
|
||||
"src/features/agent-app/types.ts",
|
||||
"src/features/agent-app/types.d.ts",
|
||||
"src/features/agent-app/runtime/agentRuntimeCapabilityApi.ts",
|
||||
"src/features/agent-app/runtime/agentRuntimeCapabilityHost.ts",
|
||||
"src/features/agent-app/runtime/agentRuntimeCapabilityHost.test.ts",
|
||||
],
|
||||
snippets: [
|
||||
'import type { AgentRuntimeClient } from "@limecloud/agent-runtime-client"',
|
||||
"runtimeClient?: Pick<",
|
||||
'"startTurn" | "readThread" | "cancelTurn" | "respondAction"',
|
||||
"createAgentAppRuntimeCapabilityApiFromClient(options.runtimeClient",
|
||||
"createFailClosedAgentAppRuntimeCapabilityApi()",
|
||||
"requires a standard AgentRuntimeClient or explicit compat api",
|
||||
"sessionId?: string;",
|
||||
"workspaceId?: string;",
|
||||
"没有标准 runtime client 或显式 compat api 时不再隐式回退旧 facade",
|
||||
"可以直接注入标准 AgentRuntimeClient 驱动 lime.agent task",
|
||||
'sessionId: "session-standard"',
|
||||
"provider_config: {",
|
||||
'model_name: "claude-sonnet-4"',
|
||||
"artifact:created",
|
||||
],
|
||||
absentSnippets: [
|
||||
"defaultAgentAppRuntimeCapabilityApi",
|
||||
"AGENT_RUNTIME_COMMANDS.submitTurn",
|
||||
"agent_runtime_submit_turn",
|
||||
"agent_runtime_get_thread_read",
|
||||
"mockPriorityCommands",
|
||||
"defaultMocks",
|
||||
"invokeMockOnly",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Agent App runtime page defaults to App Server backed standard runtime client",
|
||||
files: [
|
||||
"src/features/agent-app/runtime/agentRuntimeAppServerClient.ts",
|
||||
"src/features/agent-app/runtime/agentRuntimeAppServerClient.test.ts",
|
||||
"src/features/agent-app/ui/AgentAppRuntimePage.tsx",
|
||||
"src/features/agent-app/ui/AgentAppRuntimePage.hostBridge.test.tsx",
|
||||
"src/features/agent-app/ui/AgentAppRuntimePage.testFixtures.tsx",
|
||||
],
|
||||
snippets: [
|
||||
"@limecloud/agent-runtime-client/sessionGateway",
|
||||
"createAgentRuntimeClientFromSessionGateway",
|
||||
"type AgentRuntimeLifecycleClient",
|
||||
"type AgentRuntimeSessionGateway",
|
||||
"createAgentAppRuntimeClientFromAppServer",
|
||||
"createAgentAppRuntimeSessionResolver",
|
||||
"createDefaultAgentAppRuntimeHostOptions",
|
||||
"appServerClient.startSession({",
|
||||
'kind: "agent_app.task"',
|
||||
"createAgentRuntimeClientFromSessionGateway as",
|
||||
"runtimeClient: createAgentAppRuntimeClientFromAppServer(appServerClient)",
|
||||
"ensureSession: createAgentAppRuntimeSessionResolver(appServerClient)",
|
||||
"...createDefaultAgentAppRuntimeHostOptions()",
|
||||
"app_server_agent_runtime_client",
|
||||
"appServerClientMocks.startSession",
|
||||
"appServerClientMocks.startTurn",
|
||||
"appServerClientMocks.readSession",
|
||||
"appServerClientMocks.respondAction",
|
||||
"runtimeApiMocks.startAgentAppRuntimeTask).not.toHaveBeenCalled()",
|
||||
"runtimeApiMocks.getAgentAppRuntimeTask).not.toHaveBeenCalled()",
|
||||
"通过 agentSession/start 为 Agent App 默认宿主创建 current session",
|
||||
"默认 Host options 同时提供 runtime client 与 session resolver,且 session 失败时 fail closed",
|
||||
],
|
||||
absentSnippets: [
|
||||
"safeInvoke",
|
||||
"agent_app_runtime_start_task",
|
||||
"agent_app_runtime_get_task",
|
||||
"agent_app_runtime_cancel_task",
|
||||
"agent_app_runtime_submit_host_response",
|
||||
"mockPriorityCommands",
|
||||
"defaultMocks",
|
||||
"invokeMockOnly",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Agent App runtime task record replays App Server read model tool calls",
|
||||
files: [
|
||||
|
||||
@@ -769,6 +769,7 @@ const currentElectronHostRequiredCommands = new Set([
|
||||
"project_memory_get",
|
||||
"save_experimental_config",
|
||||
"start_oem_cloud_oauth_callback_bridge",
|
||||
"workspace_ensure",
|
||||
"workspace_ensure_default_ready",
|
||||
"workspace_ensure_ready",
|
||||
"workspace_get",
|
||||
@@ -4638,6 +4639,7 @@ function main() {
|
||||
const registeredCommands = collectElectronHostCommands();
|
||||
const mockPriorityCommands = collectMockPriorityCommands();
|
||||
const bridgeTruthCommands = collectBridgeTruthCommands();
|
||||
const noMockFallbackCompatCommands = collectNoMockFallbackCompatCommands();
|
||||
const agentCommandCatalog = readAgentCommandCatalog();
|
||||
const retiredMemoryRuntimeCatalogSectionLeaks = Object.keys(
|
||||
agentCommandCatalog,
|
||||
@@ -4755,6 +4757,21 @@ function main() {
|
||||
(command) => !bridgeTruthCommands.has(command),
|
||||
),
|
||||
);
|
||||
const agentAppRuntimeDevBridgeTruthLeaks = new Set(
|
||||
[...currentAgentAppRuntimeDesktopHostCommands].filter((command) =>
|
||||
bridgeTruthCommands.has(command),
|
||||
),
|
||||
);
|
||||
const missingAgentAppRuntimeNoMockCompatCommands = new Set(
|
||||
[...currentAgentAppRuntimeDesktopHostCommands].filter(
|
||||
(command) => !noMockFallbackCompatCommands.has(command),
|
||||
),
|
||||
);
|
||||
const agentAppRuntimeMockPriorityLeaks = new Set(
|
||||
[...currentAgentAppRuntimeDesktopHostCommands].filter((command) =>
|
||||
mockPriorityCommands.has(command),
|
||||
),
|
||||
);
|
||||
const deprecatedCommandsStillUsed = new Set(
|
||||
[...frontendCommands].filter((command) => deprecatedCommands.has(command)),
|
||||
);
|
||||
@@ -5322,6 +5339,30 @@ function main() {
|
||||
);
|
||||
}
|
||||
|
||||
if (agentAppRuntimeDevBridgeTruthLeaks.size > 0) {
|
||||
hasError = true;
|
||||
printCommandGroup(
|
||||
"Agent App runtime task compat 命令不能回到 DevBridge truth surface",
|
||||
agentAppRuntimeDevBridgeTruthLeaks,
|
||||
);
|
||||
}
|
||||
|
||||
if (missingAgentAppRuntimeNoMockCompatCommands.size > 0) {
|
||||
hasError = true;
|
||||
printCommandGroup(
|
||||
"Agent App runtime task compat 命令必须保留 no-mock fail-closed 分类",
|
||||
missingAgentAppRuntimeNoMockCompatCommands,
|
||||
);
|
||||
}
|
||||
|
||||
if (agentAppRuntimeMockPriorityLeaks.size > 0) {
|
||||
hasError = true;
|
||||
printCommandGroup(
|
||||
"Agent App runtime task compat 命令不能回到 mock priority surface",
|
||||
agentAppRuntimeMockPriorityLeaks,
|
||||
);
|
||||
}
|
||||
|
||||
if (productionBridgeGuardFailures.length > 0) {
|
||||
hasError = true;
|
||||
printGuardFailures(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user