管理操作
选择现有实例绑定操作,通过可信调用上下文及显式 agent 或 oRPC 入口执行有限调用。
@lenso/manage@0.2.0 是选择和调用现有 Engine Operations 的可选包。它把一组明确选择的操作绑定到精确 plugin 实例,提供运行中 app adapter、独立于 SDK 的 agent tools 和需要显式挂载的 oRPC router。Core 插件无需 Manage 声明或 Web 仍可使用。
本页核对已发布 0.2.0 包与源码 549b987。对应 Core/Engine/Auth 为 0.2.0,可选 oRPC server peer 精确为 2.0.0-beta.42。混合版本前阅读安装与兼容性。
选择声明,不增加 handler
Engine Operation 已描述服务自身 method、共享 Standard Schema input 与语义 metadata。defineManage({ plugin, operations, views?, extensions? }) 将这些精确声明的一个子集组合起来。服务可以是原插件,也可以是声明明确 requires 的普通 sidecar;descriptor 不携带业务 handler。
现有 Notes companion factory 返回 { plugin, operations, manage },Manage 选择 list、read、remove;create、update 仍是普通 CLI 声明。本地 Files 有独立的 metadata/delete selection。Tasks factory 选择 submit、query、cancel、retry;report 只在 CLI 暴露。
在应用装配内使用现有 Notes 工厂结果:
import { describeManage, selectManageOperations } from "@lenso/manage";
// entry 来自现有 createNotesOperations(...) 工厂。const cliRead = selectManageOperations(entry.manage, ["list", "read"]);const agentRead = selectManageOperations(entry.manage, ["read"]);const descriptor = describeManage(entry.manage);// descriptor.schemaVersion === 1;不执行 setup 或运行时健康检查。Selection 返回原始 Operation 对象。重复选择、未声明 method、来自另一个 plugin 对象的操作均失败。重新创建同 ID 对象不能满足实例绑定。describeManage 返回冻结、脱敏的 plain JSON metadata 快照,不解析配置、获取资源或验证身份。
各入口独立选择暴露范围:
| 入口 | 显式选择 |
|---|---|
| CLI | Config 的 named operations export;上下文调用使用 operationBinding |
| 本地 MCP | Named mcpOperations,另加宿主 allow 与启动 binding |
| 运行中 agent adapter | 自己选择的 operations 与调用者策略 |
| oRPC | 自己选择的 operations、当前请求 evidence 与调用者策略 |
缺少 mcpOperations 时,MCP 保留 operations fallback;显式空数组禁用 MCP 声明。Manage metadata 本身不开放路由、tool 或 worker,也没有额外 lenso manage 子命令。
绑定真实 Notes 应用
下列有限入口放在审查后 checkout 的 examples/notes/src/manage-read.ts。先准备现有 Notes 数据库/迁移、登录配置和真实 NOTES_SESSION。入口导入真实 app 声明,只选读取,借用精确运行实例:
import { startApp } from "@lenso/core";import { createManageAdapter, selectManageOperations } from "@lenso/manage";import application, { definition, manage } from "../lenso.config";import { notesAudiences } from "./notes";
const selected = selectManageOperations(manage[0]!, ["list", "read"]);const credential = process.env.NOTES_SESSION ?? null; // 可信有限入口的 evidenceconst running = await startApp(application);try { const authentication = running.get(definition.authentication); const adapter = createManageAdapter({ running, plugins: application.plugins, operations: selected, binding: () => ({ context: { evidence: credential } }), async canList(operation) { if (operation.method !== "list" && operation.method !== "read") return false; const scope = authentication.for(notesAudiences[operation.method]); const actor = await scope.required(credential); return actor.kind === "user"; }, }); const [listEntry] = await adapter.catalog(); if (listEntry) { const result = await adapter.invokeEntry(listEntry.key, {}); // 向已授权调用者展示 result;不把私有 Notes 写入遥测。 }} finally { await running.stop(); // app 归入口所有,adapter 不会 stop}在 examples/notes 使用 bun src/manage-read.ts 运行准备好的入口。它不 provision store、签发凭据或启动 HTTP listener。无效/撤销 evidence 由 Auth 拒绝;这里的读取权限允许真实用户 list/read 自己的 Notes,服务仍检查对应 audience 和实际 owner。
createManageAdapter 不会 start/stop app,构造时确认每个选中 plugin 都是 running.get 可访问的精确实例。宿主负责 admission、并发、drain 和 shutdown。所有调用结束前保持资源存活,不借用已停止 app。
Context 与业务输入分开
Notes operation 声明 context: true,真实第二参数是 { evidence, signal? } 的 NotesOperationContext。示例 config 创建服务时不提供启动凭据 fallback。上下文操作缺少可信 context 会在运行方法前得到 missing-context-binding;传入 context 但 evidence 缺失/撤销时,则由 Auth 拒绝。
每次接纳调用后,raw Standard Schema input 只验证一次,随后运行 binding(operation, validatedInput)。它提供 context 和可选 confirm/approve callback,不替换 handler。Context 类型由真实 method 第二参数推导;bindManageOperation(operation, { context }) 为异构操作的单独 binding 保留检查。
业务 JSON 不接受 actor、credential 或 approval;Notes/Tasks strict schema 拒绝这些额外字段。可信入口使用验证过的 evidence 或当前 Auth 产生的 actor,服务继续重新验证。并发请求不能共用可变“当前 actor”。单输入 method 仍支持;当前 Tasks 使用可信启动身份,不是逐请求 context method。
canList(operation) 必须提供,既过滤 catalog,也在 invocation 时复查,包括早先创建的 tool。只有当前入口身份确实可用此操作时才返回精确 true;false 隐藏条目,并在调用时产生 forbidden-operation。抛出的 Auth/策略错误使请求失败。目录权限不授权具体 note、file 或 job,owner/tenant/realm/audience 规则仍在普通服务内。
oRPC 使用当前请求身份
长期 Notes 宿主使用上面的 running、application、definition 与只读 selected,在持有 app 的入口创建 router:
import { bearerEvidence } from "@lenso/auth/fetch";import { createManageRouter } from "@lenso/manage/orpc";import { RPCHandler } from "@orpc/server/fetch";
const authentication = running.get(definition.authentication);const router = createManageRouter({ running, plugins: application.plugins, operations: selected, evidence: bearerEvidence, binding: (_operation, _input, evidence) => ({ context: { evidence: evidence.evidence, signal: evidence.signal }, }), async canList(operation, evidence) { if (operation.method !== "list" && operation.method !== "read") return false; const scope = authentication.for(notesAudiences[operation.method]); const actor = await scope.required(evidence.evidence, { signal: evidence.signal }); return actor.kind === "user"; },});const handler = new RPCHandler(router);
// 在宿主已有 Fetch handler 内:const { matched, response } = await handler.handle(request, { prefix: "/manage", context: { request },});return matched ? response : new Response("Not found", { status: 404 });每次 catalog/invoke 从自身请求提取 evidence,并创建借用同一运行 app 的 adapter。宿主显式挂载 /manage,负责 ingress/TLS/CORS。这是 oRPC v2,不是自动 REST 路由。Signal 只启用 Auth/service/provider 实际实现的协作行为,不取消已提交写入。
使用 catalog 返回的 key 调用 invoke({ key, input })。另一形式 { pluginId, method, input } 供知道原始 identifier 的可信调用者使用。两种 envelope 都不允许额外 actor/approval 字段。身份或操作失败返回 MANAGE_FAILED 和安全版本化 diagnostic,非法 envelope 由正常 oRPC input validation 拒绝。
Agent tools 复用同一业务路径
createAgentTools(adapter) 可从 root 或 @lenso/manage/agent 导入,为过滤后的 catalog 返回独立于 SDK 的 { name, description, inputSchema, invoke }。Runtime agent/SDK 自己接入;它不启动 MCP 或提供身份 provider。
Agent input 要求可转换的对象 JSON Schema。只有 runtime validation 仍可用于普通 invocation,但无法描述安全 tool;scalar 或不可用 schema 产生 unsupported-input-schema。调用再次检查 canList、输入与服务授权。outputDescription 不会推导成 output schema。
Catalog entry 包含 schemaVersion: 1 与 adapter 范围内 opaque key,当前格式为 operation_<index>。Display ID 可能脱敏,invokeEntry 通过原始声明 dispatch。Key 不是跨部署持久 operation ID、job ID 或 receipt;选择变更后重新发现。
Confirmation 与 approval 是独立门槛
Operation 可以声明 confirmation: "required"、approval: "required" 或两者。可信 confirm()/approve() 必须通过真实用户确认流程和配置的 approval owner 验证这次调用。缺失/false 时拒绝;未经验证便返回 true 是应用缺陷。JSON confirmed/approved 或 destructive metadata 不能满足要求。
选择、操作权限、confirmation、approval 与对象授权分别决定。当前 Notes/Tasks 声明不会因删除/取消便自动要求这些门槛;产品需要时显式声明并接入真实可信流程。
输出、呈现与平台限制
共用 Engine invocation 保留服务 this、遥测与未知错误的安全遮蔽。Result 必须为 finite plain JSON;stream 与 plain AsyncIterable 也被拒绝。成功输出和 catalog 默认预算 1 MiB,可通过 maxOutputBytes 调整;oRPC diagnostic 独立预算 4 KiB 并有安全 fallback。预算不是服务 CPU/内存 quota,输出失败也可能发生在效果已提交后。
View hints 只包括 key、title/group/order、columns 和已声明 detail/action 引用。Key 唯一,引用 method 必须包含在选择内;namespaced extensions 是 plain JSON。两者不携带 React component、module URL 或 handler,也不改变 dispatch/Auth。该包不交付 Console frontend/runtime。Redaction 去掉 schema defaults/examples 与常见敏感数据,任意 key secret/自由文本仍由应用负责。
当前 Manage 集成测试运行于 Bun。oRPC adapter 基于 Fetch,但没有 Manage-on-workerd 示例/测试建立 Workers 部署契约。迁移装配前审查真实 package graph 与宿主能力;Fetch 签名不能证明 Bun/Node import、binding 或平台 shutdown 已验证,也不意味着浏览器直接访问服务/secret。
Manage 执行有限 query/command,不提供 scheduling、自动 retry、rollback、durable receipt/journal 恢复、持久 audit、配置 read-all/write-all、hot update 或 restart。Tasks submit 返回授权 job ID,持久取消/重试仍是独立授权业务操作。
定位真正失败的边界
| Diagnostic 或症状 | 下一步 |
|---|---|
invalid-manage | 精确运行 plugin、所选声明、必要 callback 与输出预算 |
unknown-operation / duplicate-operation | Selection 与 method 重复,不从 service 自动推断 |
missing-context-binding | context: true 所需可信 binding |
forbidden-operation | 当前 canList,包括发现 tool 后权限变化 |
UNAUTHORIZED / FORBIDDEN | 真实 evidence、正确 audience、实际 owner/tenant |
confirmation-required / approval-required | 真实入口流程/owner callback,不是 input flag |
unsupported-input-schema | Agent tool 要求对象 schema converter |
output-too-large / serialization-failed | Result/catalog 形状与字节预算,重放前检查业务状态 |
Manage 测试覆盖精确引用、一次验证、gates、脱敏 key dispatch、有界输出、并发 evidence、撤销与显式 v2 Fetch 挂载。Notes 测试验证真实共享服务;Tasks 测试验证独立选择与资源 setup 前拒绝非法输入。各入口操作与生命周期见 CLI、Auth、Agents与测试。