Files
AgentLab/frontend/src/api/apps.ts
T
ThearchyHelios 7739340607 普通用户建应用集成收尾:
- 问数据和个人应用的图放行绑定只读查询等合规工具的调用工具节点,只读和遮罩在这条路径上生效;被治理成写入的工具在只读运行里直接拒;知识库范围在工具层固定(图级默认值、参数名纠错、Agent 都改不了)
- 个人应用的刷新和个人蓝图的试运行只有拥有者能读:运行、事件、证据、工件、文件、运行与审批列表、对比、可验证报告、质询、假设分析、提取工作流一并收紧;个人蓝图的试运行不论谁发起都按个人应用约束
- 共享蓝图只给拥有者、对应应用的管理者、管理员,看不到的一律 404;审核副本归审核人;管理员看别人的个人应用只有元数据、可以归档
- 记录表跨版本汇总(只读挂载旧版本、按当前列 UNION ALL、各版本各自固定高水位,缺的版本照实写进印章);停用前发起的运行可以继续写旧版本;更正沿用原提交人、更正人记审计
- 约定收口:待办种类 review、事件 file.extracted、表单错误 file、模板定义带示例与预置;前端类型一次补齐、去掉临时的局部类型;审核待办卡不再给打不开的链接;读附件事件有中文说法;检查脚本补断言
2026-10-15 13:55:19 +08:00

101 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 应用的接口(/api/apps、/api/ledger),挂在 api.apps 上。形状见 typesApp.ts(应用平台的接口约定)。
*
* 请求函数从 ./http 导入,不要从 ./client 导入(循环导入,见 http.ts 开头)。
* 入口提交校验失败回 422,ApiError 的 body 里带 {errors: FormError[]}。
*/
import type {
AppBindings, AppDetail, AppGrant, AppHookRow, AppList, AppSpec, AppSummary, AppVersionRow, BackfillPlan, BackfillResult,
ComponentCatalog, Disposition, InstanceRow, LintIssue, PublishResult, ViewModel,
} from '../typesApp'
import type { ConversationDetail, EvidenceSegmentDetail } from '../types'
import { del, get, patch, post, put, qs, type RequestOptions } from './http'
const at = (id: string) => `/apps/${encodeURIComponent(id)}`
const hookAt = (id: string, name: string) => `${at(id)}/hooks/${encodeURIComponent(name)}`
export const apps = {
catalog: (opts?: RequestOptions) => get<ComponentCatalog>('/apps/catalog', opts),
list: (opts?: RequestOptions) => get<AppList>('/apps', opts),
get: (id: string, opts?: RequestOptions) => get<AppDetail>(at(id), opts),
create: (body: { name: string; description?: string; spec?: AppSpec }) => post<AppDetail>('/apps', body),
/** 从工作流版本确定性派生一份应用草稿(不落库),编辑器拿去预览 / 新建 */
derive: (body: { workflow_id: string; version: number; name?: string }) =>
post<{ spec: AppSpec }>('/apps/derive', body),
/** 改草稿:spec、岗位绑定、名称等,只带改了的字段 */
update: (id: string, body: { name?: string; description?: string; icon?: string; draft?: AppSpec;
draft_bindings?: AppBindings }) => patch<AppDetail>(at(id), body),
archive: (id: string) => post<AppDetail>(`${at(id)}/archive`),
lint: (id: string) => post<{ issues: LintIssue[]; level: string | null }>(`${at(id)}/lint`),
publish: (id: string, body: { note?: string; dispositions?: Record<string, Disposition> }) =>
post<PublishResult>(`${at(id)}/publish`, body),
versions: (id: string) => get<{ items: AppVersionRow[] }>(`${at(id)}/versions`),
version: (id: string, v: number) => get<AppVersionRow & { spec: AppSpec; bindings: AppBindings }>(`${at(id)}/versions/${v}`),
grants: (id: string) => get<{ items: AppGrant[] }>(`${at(id)}/grants`),
setGrants: (id: string, items: AppGrant[]) => put<{ items: AppGrant[] }>(`${at(id)}/grants`, { items }),
/** 页面解析:服务端解析好的视图模型(不可见的组件不下发,遮罩值不出服务端) */
page: (id: string, page: string,
params?: { instance?: string; params?: Record<string, unknown>; draft?: boolean }, opts?: RequestOptions) =>
get<ViewModel>(`${at(id)}/pages/${encodeURIComponent(page)}${qs({
instance: params?.instance, params: params?.params ? JSON.stringify(params.params) : undefined,
// 编辑器预览:按草稿解析(不发起运行,视图取钉住版本已有的封存运行)
draft: params?.draft ? 1 : undefined,
})}`, opts),
/** 发起一个实例 */
start: (id: string, entry: string, values: Record<string, unknown>) =>
post<{ run_id: string; title: string }>(`${at(id)}/entries/${encodeURIComponent(entry)}`, { values }),
instances: (id: string, params?: { who?: 'mine' | 'all'; entry?: string; status?: string; limit?: number }) =>
get<{ items: InstanceRow[] }>(`${at(id)}/instances${qs(params)}`),
instance: (id: string, runId: string) => get<InstanceRow & { view: ViewModel }>(`${at(id)}/instances/${encodeURIComponent(runId)}`),
/**
* 应用页上点开一个指标块的出处(§3.6):指标 → 输入 → 查询快照的出处链,形状同报告片段接口的 chain / seal / redacted,
* 证据面板照样画。单元格和报告沿用现有的证据接口
*/
metricEvidence: (runId: string, node: string, metric: string, opts?: RequestOptions) =>
get<EvidenceSegmentDetail>(
`/runs/${encodeURIComponent(runId)}/evidence/metrics/${encodeURIComponent(node)}/${encodeURIComponent(metric)}`, opts),
/** 按需视图:发起一次运行(或命中缓存) */
refreshView: (id: string, view: string, params?: Record<string, unknown>) =>
post<{ run_id: string; cached: boolean }>(`${at(id)}/views/${encodeURIComponent(view)}/refresh`, { params: params ?? {} }),
/**
* 按新口径补算(D-f,manage):这个视图的固定版本相对上一个应用版本升了版、处置为按新口径重算时,取旧版本在同一来源上
* 最近几次封存的正式运行的输入,用新版本逐个发起正式运行(来源记为按新口径补算),同一输入补算过的跳过
*/
backfill: (id: string, view: string) =>
post<BackfillResult>(`${at(id)}/views/${encodeURIComponent(view)}/backfill`),
/** 补算会发起几次、跳过几次(不发起运行):确认框按它写明次数 */
backfillPlan: (id: string, view: string) => get<BackfillPlan>(`${at(id)}/views/${encodeURIComponent(view)}/backfill`),
/** 钩子(D-g):草稿声明 ∪ 已有的行,按名字 */
hooks: async (id: string): Promise<AppHookRow[]> => (await get<{ items: AppHookRow[] }>(`${at(id)}/hooks`)).items,
/** 生成或轮换签名密钥:secret 只在这一次交回;url_path 是收请求的地址 */
rotateHook: (id: string, name: string) => post<AppHookRow>(`${hookAt(id, name)}/rotate`),
setHookEnabled: (id: string, name: string, enabled: boolean) => patch<AppHookRow>(hookAt(id, name), { enabled }),
/** 删掉这个名字的密钥行:外部系统再用旧密钥签的请求一律拒收 */
deleteHook: (id: string, name: string) => del(hookAt(id, name)),
/**
* 应用编辑器里助手的对话(D-e,manage):取这个应用的那段对话,没有就建(kind=app、归属这个应用)。带着历史轮次;
* 老服务端只回对话本身时由调用方再按 id 取一次
*/
assistantConversation: (id: string): Promise<AssistantConversation> => {
// 同一个应用正在取的合并成一次:开发模式下 effect 执行两次,两个请求同时到服务端各建一段对话,历史就拆开了
const pending = assistantInflight.get(id)
if (pending) return pending
const request = get<AssistantConversation>(`${at(id)}/assistant-conversation`)
.finally(() => assistantInflight.delete(id))
assistantInflight.set(id, request)
return request
},
}
type AssistantConversation =
ConversationDetail | (Omit<ConversationDetail, 'turns'> & { turns?: ConversationDetail['turns'] })
/** 正在取的助手对话,按应用 id(见 apps.assistantConversation) */
const assistantInflight = new Map<string, Promise<AssistantConversation>>()
export const ledger = {
/** 一个工作流版本被哪些应用钉住、生效记录数、正式运行数 */
version: (workflowId: string, version: number) =>
get<{ apps: { app_id: string; name: string; app_version: number }[]; effects: number; formal_runs: number }>(
`/ledger/workflow-versions/${encodeURIComponent(workflowId)}/${version}`),
}