Archived
三面拆分⑫:设计台九区一级目录 —— 一区一内容,测试台收成单例仿真
设计中心从「蓝图 + 四库」摊成九区,顺序即设计的次序: 蓝图 → 触发器 → 流程包 → 活动库 → 工具箱 → 数据域 → 知识库 → 大模型 → 测试台。 触发器与大模型从「并进流程包 / 塞在系统区」里拿出来独立成区 —— 前者与流程包平级 (不入包结构、不随包版本),后者是设计者天天要碰的东西;知识库先立个诚实的空占位。 一区一内容,于是区内页签栏整条删掉(壳层在只剩一格时收起): · 流程包:总览的三枚计数并进货架顶端 · 测试台:入站邮件演练移到触发器区就地验(它验的正是那一区定的规则) · 大模型:从系统区拆出,系统区只剩通道实例 测试台一次只跑一条流。起单前先销毁上一单,刷新页面能认回在跑的那一单, 「停止仿真」把工作流连同它派出的任务、工单、实例、留痕一并拔掉 —— 不留半单挂在别人工作台上。实测:起单 → 1 单落到 AI 工作台 → 停止 → 库里零残留。 功能工具补中文名(migration 0018,读回退到逻辑名),治理台一律主显示中文名。 顺带清掉几处说教式文案(「这些是工具,不是模型 —— 模型是它们跑在上面的发动机…」那类)。 五闸:typecheck / lint / depcruise / test 绿;api:check 快照随本提交入库。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -1919,6 +1919,7 @@ export declare const registerToolRoute: {
|
|||||||
readonly body: z.ZodObject<{
|
readonly body: z.ZodObject<{
|
||||||
contract: z.ZodObject<{
|
contract: z.ZodObject<{
|
||||||
name: z.ZodString;
|
name: z.ZodString;
|
||||||
|
displayName: z.ZodOptional<z.ZodString>;
|
||||||
version: z.ZodString;
|
version: z.ZodString;
|
||||||
description: z.ZodString;
|
description: z.ZodString;
|
||||||
input: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
input: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
||||||
@@ -2006,6 +2007,7 @@ export declare const listToolsRoute: {
|
|||||||
readonly output: z.ZodObject<{
|
readonly output: z.ZodObject<{
|
||||||
tools: z.ZodArray<z.ZodObject<{
|
tools: z.ZodArray<z.ZodObject<{
|
||||||
name: z.ZodString;
|
name: z.ZodString;
|
||||||
|
displayName: z.ZodString;
|
||||||
version: z.ZodString;
|
version: z.ZodString;
|
||||||
description: z.ZodString;
|
description: z.ZodString;
|
||||||
effect: z.ZodEnum<{
|
effect: z.ZodEnum<{
|
||||||
@@ -2130,6 +2132,30 @@ export declare const testRunRoute: {
|
|||||||
}>;
|
}>;
|
||||||
}, z.core.$strip>;
|
}, z.core.$strip>;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 停止仿真 —— **把这一单连根拔掉**:工作流、它的活动任务、工单、实例、trace 全部销毁。
|
||||||
|
* 仿真不是生产:试跑留下的东西不该在库里、在别人工作台上过夜。
|
||||||
|
*/
|
||||||
|
export declare const testStopRoute: {
|
||||||
|
readonly method: "POST";
|
||||||
|
readonly path: "/api/gov/testbed/stop";
|
||||||
|
readonly body: z.ZodObject<{
|
||||||
|
workflowId: z.ZodString;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
destroyed: z.ZodBoolean;
|
||||||
|
tasks: z.ZodNumber;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
|
/** 当前在跑的仿真(测试台一次只跑一条)—— 刷新页面也认得回来 */
|
||||||
|
export declare const testCurrentRoute: {
|
||||||
|
readonly method: "GET";
|
||||||
|
readonly path: "/api/gov/testbed/current";
|
||||||
|
readonly output: z.ZodObject<{
|
||||||
|
workflowId: z.ZodOptional<z.ZodString>;
|
||||||
|
subject: z.ZodOptional<z.ZodString>;
|
||||||
|
}, z.core.$strip>;
|
||||||
|
};
|
||||||
/** 全链 Trace:一条工作流的 span 树(节点 → 步骤 / 工具 / 模型 / 人卡) */
|
/** 全链 Trace:一条工作流的 span 树(节点 → 步骤 / 工具 / 模型 / 人卡) */
|
||||||
export declare const traceOfWorkflowRoute: {
|
export declare const traceOfWorkflowRoute: {
|
||||||
readonly method: "GET";
|
readonly method: "GET";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
* 「治理台九功能**不设**后端 governance 模块:每个功能是对应模块的管理用例 + 路由组装。」
|
* 「治理台九功能**不设**后端 governance 模块:每个功能是对应模块的管理用例 + 路由组装。」
|
||||||
* 「治理动作本身留痕入事件流 —— 治理也被治理。」
|
* 「治理动作本身留痕入事件流 —— 治理也被治理。」
|
||||||
*/
|
*/
|
||||||
|
import { sql } from 'drizzle-orm';
|
||||||
import type { Hono } from 'hono';
|
import type { Hono } from 'hono';
|
||||||
import type { ApiError, GovernanceFunction, GrantAction } from '@contracts';
|
import type { ApiError, GovernanceFunction, GrantAction } from '@contracts';
|
||||||
import {
|
import {
|
||||||
@@ -25,7 +26,9 @@ import {
|
|||||||
listTriggersRoute,
|
listTriggersRoute,
|
||||||
registerToolRoute,
|
registerToolRoute,
|
||||||
saveProcessRoute,
|
saveProcessRoute,
|
||||||
|
testCurrentRoute,
|
||||||
testRunRoute,
|
testRunRoute,
|
||||||
|
testStopRoute,
|
||||||
traceOfWorkflowRoute,
|
traceOfWorkflowRoute,
|
||||||
setProcessStateRoute,
|
setProcessStateRoute,
|
||||||
reassignTaskRoute,
|
reassignTaskRoute,
|
||||||
@@ -42,6 +45,53 @@ import { NotClosedError, PinnedVersionError, StaleVersionError } from '@modules/
|
|||||||
import type { Container } from '../container.ts';
|
import type { Container } from '../container.ts';
|
||||||
import type { AppEnv } from '../http-env.ts';
|
import type { AppEnv } from '../http-env.ts';
|
||||||
|
|
||||||
|
/** `db.execute` 的行读取 —— Drizzle 的返回型随驱动变,窄化收在这一处 */
|
||||||
|
function rowsOf<T>(result: unknown): T[] {
|
||||||
|
const bag = result as { rows?: T[] } | T[];
|
||||||
|
return Array.isArray(bag) ? bag : (bag.rows ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁仿真单 —— 把测试台起的那一条(或指定的一条)**连根拔掉**。
|
||||||
|
*
|
||||||
|
* 顺序按外键的反方向来:先拆挂在任务上的东西(工单、卡、记忆、运行时、意向、监听),
|
||||||
|
* 再拆任务与节点实例,最后拆工作流本身;trace 与事件按工作流清。
|
||||||
|
* 用 `source->>'ref' = 'testbed'` 认门:**只动测试台自己起的单**,
|
||||||
|
* 生产单一个字都不碰(它们的来源是触发器或纪要登记)。
|
||||||
|
*/
|
||||||
|
async function destroySimulations(container: Container, workflowId?: string): Promise<number> {
|
||||||
|
const scope = workflowId === undefined ? sql`` : sql` and id = ${workflowId}`;
|
||||||
|
const wfs = await container.db.execute<{ id: string }>(
|
||||||
|
sql`select id from sys.workflow where source->>'ref' = 'testbed'${scope}`,
|
||||||
|
);
|
||||||
|
const ids = rowsOf<{ id: string }>(wfs).map((r) => r.id);
|
||||||
|
if (ids.length === 0) return 0;
|
||||||
|
|
||||||
|
let taskCount = 0;
|
||||||
|
for (const id of ids) {
|
||||||
|
const tasks = await container.db.execute<{ id: string }>(sql`select id from sys.task where workflow_id = ${id}`);
|
||||||
|
const taskIds = rowsOf<{ id: string }>(tasks).map((r) => r.id);
|
||||||
|
taskCount += taskIds.length;
|
||||||
|
for (const taskId of taskIds) {
|
||||||
|
await container.db.execute(sql`delete from sys.work_order where task_id = ${taskId}`);
|
||||||
|
await container.db.execute(sql`delete from sys.chat_card where task_id = ${taskId}`);
|
||||||
|
await container.db.execute(sql`delete from sys.chat_message where task_id = ${taskId}`);
|
||||||
|
await container.db.execute(sql`delete from sys.task_memory where task_id = ${taskId}`);
|
||||||
|
await container.db.execute(sql`delete from sys.task_memory_seal where task_id = ${taskId}`);
|
||||||
|
await container.db.execute(sql`delete from sys.task_view where task_id = ${taskId}`);
|
||||||
|
await container.db.execute(sql`delete from sys.flow_state where task_id = ${taskId}`);
|
||||||
|
await container.db.execute(sql`delete from sys.loop_state where task_id = ${taskId}`);
|
||||||
|
await container.db.execute(sql`delete from sys.action_intent where task_id = ${taskId}`);
|
||||||
|
await container.db.execute(sql`delete from sys.listener where task_id = ${taskId}`);
|
||||||
|
}
|
||||||
|
await container.db.execute(sql`delete from sys.task where workflow_id = ${id}`);
|
||||||
|
await container.db.execute(sql`delete from sys.node_instance where workflow_id = ${id}`);
|
||||||
|
await container.db.execute(sql`delete from sys.trace_span where workflow_id = ${id}`);
|
||||||
|
await container.db.execute(sql`delete from sys.workflow where id = ${id}`);
|
||||||
|
}
|
||||||
|
return taskCount;
|
||||||
|
}
|
||||||
|
|
||||||
export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Container): void {
|
export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Container): void {
|
||||||
/**
|
/**
|
||||||
* 岗位授权:客体 = 治理功能,动作按路由分级 —— **看是 `read`,动才是 `govern`**。
|
* 岗位授权:客体 = 治理功能,动作按路由分级 —— **看是 `read`,动才是 `govern`**。
|
||||||
@@ -230,6 +280,8 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
|
|||||||
return c.json(body, 400);
|
return c.json(body, 400);
|
||||||
}
|
}
|
||||||
const me = await container.org.findByAccount(c.get('account'));
|
const me = await container.org.findByAccount(c.get('account'));
|
||||||
|
// **一次只跑一条**:再起一单就先把上一单连根拔掉 —— 仿真台上不留过夜的东西
|
||||||
|
await destroySimulations(container);
|
||||||
try {
|
try {
|
||||||
const record = await container.process.start({
|
const record = await container.process.start({
|
||||||
packageId: parsed.data.packageId,
|
packageId: parsed.data.packageId,
|
||||||
@@ -247,6 +299,33 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止仿真 —— **连根拔掉**:工作流、活动任务、工单、节点实例、trace 一并销毁。
|
||||||
|
* 仿真不是生产:试跑留下的任务不该在别人工作台上过夜,也不该混进在途口径。
|
||||||
|
*/
|
||||||
|
app.post(testStopRoute.path, async (c) => {
|
||||||
|
const denied = await governProcess(c.get('account'));
|
||||||
|
if (denied) return c.json(denied, denyStatus(denied));
|
||||||
|
const parsed = testStopRoute.body.safeParse(await c.req.json());
|
||||||
|
if (!parsed.success) {
|
||||||
|
const body: ApiError = { code: 'validation-failed', message: '停止仿真入参不合协议' };
|
||||||
|
return c.json(body, 400);
|
||||||
|
}
|
||||||
|
const tasks = await destroySimulations(container, parsed.data.workflowId);
|
||||||
|
return c.json({ destroyed: true, tasks });
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 当前在跑的仿真 —— 刷新页面也认得回来(测试台一次只跑一条) */
|
||||||
|
app.get(testCurrentRoute.path, async (c) => {
|
||||||
|
const denied = await governProcess(c.get('account'));
|
||||||
|
if (denied) return c.json(denied, denyStatus(denied));
|
||||||
|
const rows = await container.db.execute<{ id: string; subject: string }>(
|
||||||
|
sql`select id, subject from sys.workflow where source->>'ref' = 'testbed' order by started_at desc limit 1`,
|
||||||
|
);
|
||||||
|
const hit = rowsOf<{ id: string; subject: string }>(rows)[0];
|
||||||
|
return c.json(hit === undefined ? {} : { workflowId: hit.id, subject: hit.subject });
|
||||||
|
});
|
||||||
|
|
||||||
/** 全链 Trace:一条工作流的 span 树 —— 节点 → 步骤 / 工具 / 模型 / 人卡 */
|
/** 全链 Trace:一条工作流的 span 树 —— 节点 → 步骤 / 工具 / 模型 / 人卡 */
|
||||||
app.get(traceOfWorkflowRoute.path, async (c) => {
|
app.get(traceOfWorkflowRoute.path, async (c) => {
|
||||||
const denied = await governProcess(c.get('account'));
|
const denied = await governProcess(c.get('account'));
|
||||||
@@ -545,7 +624,8 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
|
|||||||
}
|
}
|
||||||
const { contract, samples, connectionValues } = parsed.data;
|
const { contract, samples, connectionValues } = parsed.data;
|
||||||
const verdict = await container.toolbox.register({
|
const verdict = await container.toolbox.register({
|
||||||
contract,
|
// 没给中文名就以逻辑名兜底 —— 主名不能空
|
||||||
|
contract: { ...contract, displayName: contract.displayName ?? contract.name },
|
||||||
// 期望形状统一按「出参是个对象」收:细的形状由契约 output schema 自己说
|
// 期望形状统一按「出参是个对象」收:细的形状由契约 output schema 自己说
|
||||||
samples: samples.map((s) => ({ input: s.input, expectShape: { type: 'object' } })),
|
samples: samples.map((s) => ({ input: s.input, expectShape: { type: 'object' } })),
|
||||||
...(connectionValues === undefined ? {} : { connectionValues }),
|
...(connectionValues === undefined ? {} : { connectionValues }),
|
||||||
@@ -563,6 +643,7 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
|
|||||||
const tools = await container.toolbox.list();
|
const tools = await container.toolbox.list();
|
||||||
return c.json({
|
return c.json({
|
||||||
tools: tools.map((t) => ({
|
tools: tools.map((t) => ({
|
||||||
|
displayName: t.displayName,
|
||||||
name: t.name,
|
name: t.name,
|
||||||
version: t.version,
|
version: t.version,
|
||||||
description: t.description,
|
description: t.description,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { seedMvpFlow } from './seed-mvp-flow.ts';
|
|||||||
/** 第一方功能工具:查历史价 —— MVP 用录制回放替身代真 CRM(README:mock 只在外界) */
|
/** 第一方功能工具:查历史价 —— MVP 用录制回放替身代真 CRM(README:mock 只在外界) */
|
||||||
const HISTORY_TOOL = {
|
const HISTORY_TOOL = {
|
||||||
name: 'crm.history-price-read',
|
name: 'crm.history-price-read',
|
||||||
|
displayName: '历史价查询',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: '读取历史价格情报:某 PN 最近一次报价距今天数、行情是否平稳、生命周期状态',
|
description: '读取历史价格情报:某 PN 最近一次报价距今天数、行情是否平稳、生命周期状态',
|
||||||
input: { type: 'object', required: ['pn'], properties: { pn: { type: 'string' } } },
|
input: { type: 'object', required: ['pn'], properties: { pn: { type: 'string' } } },
|
||||||
@@ -39,6 +40,7 @@ const HISTORY_TOOL = {
|
|||||||
const MAIL_TOOL = {
|
const MAIL_TOOL = {
|
||||||
...HISTORY_TOOL,
|
...HISTORY_TOOL,
|
||||||
name: 'mail.send',
|
name: 'mail.send',
|
||||||
|
displayName: '发送邮件',
|
||||||
description: '发出邮件(报价单 / 询价函 / 催办):写类动作,放行时才真正触达世界',
|
description: '发出邮件(报价单 / 询价函 / 催办):写类动作,放行时才真正触达世界',
|
||||||
input: {
|
input: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -61,6 +63,7 @@ const MAIL_TOOL = {
|
|||||||
*/
|
*/
|
||||||
const PLACEHOLDER_TOOL = {
|
const PLACEHOLDER_TOOL = {
|
||||||
name: 'slot.placeholder',
|
name: 'slot.placeholder',
|
||||||
|
displayName: '槽位占位产出',
|
||||||
version: '0.1.0',
|
version: '0.1.0',
|
||||||
description: '活动槽位占位产出:把契约要求的字段落成一行(不做业务判断;真实现后摘除)',
|
description: '活动槽位占位产出:把契约要求的字段落成一行(不做业务判断;真实现后摘除)',
|
||||||
input: {
|
input: {
|
||||||
|
|||||||
@@ -29,6 +29,10 @@
|
|||||||
`human`(**人类工具**,系统内化、零声明即得 —— 调人是 Harness 给的能力)、
|
`human`(**人类工具**,系统内化、零声明即得 —— 调人是 Harness 给的能力)、
|
||||||
`llm`(**LLM 五件**:抽取/归类/摘要/裁断/改写,经模型网关一缝;
|
`llm`(**LLM 五件**:抽取/归类/摘要/裁断/改写,经模型网关一缝;
|
||||||
仅当活动声明了模型用途名才上总线 —— 没声明却会问模型是越权,用量也无从归因)
|
仅当活动声明了模型用途名才上总线 —— 没声明却会问模型是越权,用量也无从归因)
|
||||||
|
- **一名两用**(2026-07-29):`name` 是**机器名**(全局唯一逻辑名,活动按它声明白名单、
|
||||||
|
调用按它寻址),`displayName` 是**人名**(治理台一律主显示中文名,逻辑名退居副标)。
|
||||||
|
`crm.history-price-read` 对机器精确、对人不友好,一屏几十件工具时尤甚;
|
||||||
|
旧工具未填时读回退到 `name`(加法,非破坏)
|
||||||
- **一切工具执行经 `ToolInvoker`** —— 绕过它,回放审就对它失明(§12.5#10)
|
- **一切工具执行经 `ToolInvoker`** —— 绕过它,回放审就对它失明(§12.5#10)
|
||||||
- `replay` 模式**绝不回退实调**:缺录即 `ToolReplayMissError`
|
- `replay` 模式**绝不回退实调**:缺录即 `ToolReplayMissError`
|
||||||
- **白名单在结构上拦截**:功能工具按活动白名单,纯工具按 `ownerActivityId` 私有作用域;
|
- **白名单在结构上拦截**:功能工具按活动白名单,纯工具按 `ownerActivityId` 私有作用域;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { ToolContract } from '../internal/domain/tool-contract.ts';
|
|||||||
|
|
||||||
const CONTRACT: ToolContract = {
|
const CONTRACT: ToolContract = {
|
||||||
name: 'erp.stock-read',
|
name: 'erp.stock-read',
|
||||||
|
displayName: 'ERP 库存查询',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: '读取 ERP 现货库存:PN → 库存量',
|
description: '读取 ERP 现货库存:PN → 库存量',
|
||||||
input: { type: 'object', required: ['pn'], properties: { pn: { type: 'string' } } },
|
input: { type: 'object', required: ['pn'], properties: { pn: { type: 'string' } } },
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { auditPassed, staticAudit } from '../internal/domain/registration-audit.
|
|||||||
|
|
||||||
const contract = (over: Partial<ToolContract> = {}): ToolContract => ({
|
const contract = (over: Partial<ToolContract> = {}): ToolContract => ({
|
||||||
name: 'crm.customer-read',
|
name: 'crm.customer-read',
|
||||||
|
displayName: '客户档案查询',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: '读取客户资料库中的客户档案,用于取客户等级与付款条款',
|
description: '读取客户资料库中的客户档案,用于取客户等级与付款条款',
|
||||||
input: { type: 'object', properties: { customerCode: { type: 'string' } }, required: ['customerCode'] },
|
input: { type: 'object', properties: { customerCode: { type: 'string' } }, required: ['customerCode'] },
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { sys } from '@shared/db.ts';
|
|||||||
export const toolTable = sys.table(
|
export const toolTable = sys.table(
|
||||||
'tool',
|
'tool',
|
||||||
{
|
{
|
||||||
|
/** 中文名 —— 人读的主名(治理台主显示);存量行以逻辑名兜底 */
|
||||||
|
displayName: text('display_name'),
|
||||||
name: text('name').primaryKey(),
|
name: text('name').primaryKey(),
|
||||||
version: text('version').notNull(),
|
version: text('version').notNull(),
|
||||||
description: text('description').notNull(),
|
description: text('description').notNull(),
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ type Row = typeof toolTable.$inferSelect;
|
|||||||
function toContract(row: Row): ToolContract {
|
function toContract(row: Row): ToolContract {
|
||||||
return {
|
return {
|
||||||
name: row.name,
|
name: row.name,
|
||||||
|
// 存量行没中文名就以逻辑名兜底 —— 界面上宁可显示机器名,不显示空白
|
||||||
|
displayName: row.displayName ?? row.name,
|
||||||
version: row.version,
|
version: row.version,
|
||||||
description: row.description,
|
description: row.description,
|
||||||
input: row.input as Record<string, unknown>,
|
input: row.input as Record<string, unknown>,
|
||||||
@@ -50,6 +52,7 @@ export function createToolRegistry(db: Db): ToolRegistry {
|
|||||||
name: contract.name,
|
name: contract.name,
|
||||||
version: contract.version,
|
version: contract.version,
|
||||||
description: contract.description,
|
description: contract.description,
|
||||||
|
displayName: contract.displayName,
|
||||||
input: contract.input,
|
input: contract.input,
|
||||||
output: contract.output,
|
output: contract.output,
|
||||||
effect: contract.effect,
|
effect: contract.effect,
|
||||||
@@ -70,6 +73,7 @@ export function createToolRegistry(db: Db): ToolRegistry {
|
|||||||
target: toolTable.name,
|
target: toolTable.name,
|
||||||
set: {
|
set: {
|
||||||
version: values.version,
|
version: values.version,
|
||||||
|
displayName: values.displayName,
|
||||||
description: values.description,
|
description: values.description,
|
||||||
input: values.input,
|
input: values.input,
|
||||||
output: values.output,
|
output: values.output,
|
||||||
|
|||||||
@@ -11,8 +11,13 @@ export type EffectLevel = (typeof EFFECT_LEVELS)[number];
|
|||||||
export const EFFECT_LEVEL_LABEL: Readonly<Record<EffectLevel, string>> = { read: '读', write: '写' };
|
export const EFFECT_LEVEL_LABEL: Readonly<Record<EffectLevel, string>> = { read: '读', write: '写' };
|
||||||
|
|
||||||
export type ToolContract = {
|
export type ToolContract = {
|
||||||
/** 全局唯一逻辑名 */
|
/** 全局唯一逻辑名 —— 机器用:活动按它声明白名单、调用按它寻址 */
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
|
/**
|
||||||
|
* 中文名 —— **人用**:治理台一律主显示它,逻辑名退居副标。
|
||||||
|
* 「crm.history-price-read」对机器精确,对人不友好;一屏几十件工具时尤其。
|
||||||
|
*/
|
||||||
|
readonly displayName: string;
|
||||||
readonly version: string;
|
readonly version: string;
|
||||||
/** 面向 AI 的适用说明:何时用、语义、边界 */
|
/** 面向 AI 的适用说明:何时用、语义、边界 */
|
||||||
readonly description: string;
|
readonly description: string;
|
||||||
|
|||||||
@@ -27,13 +27,14 @@ import { BlueprintHome } from '@features/gov-blueprint';
|
|||||||
import { TasksHome, TaskLogPage, TaskPage, WfPage } from '@features/gov-task';
|
import { TasksHome, TaskLogPage, TaskPage, WfPage } from '@features/gov-task';
|
||||||
import { EmpPage, PeopleHome, RolePage } from '@features/gov-staff';
|
import { EmpPage, PeopleHome, RolePage } from '@features/gov-staff';
|
||||||
import { PkgPage, WorkflowsHome } from '@features/gov-process';
|
import { PkgPage, WorkflowsHome } from '@features/gov-process';
|
||||||
import { TriggerPage } from '@features/gov-trigger';
|
import { TriggerPage, TriggerRulesHome } from '@features/gov-trigger';
|
||||||
import { ActPage, LibraryHome } from '@features/gov-activity';
|
import { ActPage, LibraryHome } from '@features/gov-activity';
|
||||||
import { ToolPage, ToolsHome } from '@features/gov-tool';
|
import { ToolPage, ToolsHome } from '@features/gov-tool';
|
||||||
import { DataHome, LedgersHome, ObjPage } from '@features/gov-data';
|
import { DataHome, LedgersHome, ObjPage } from '@features/gov-data';
|
||||||
|
import { ArchiveHome } from '@features/gov-archive';
|
||||||
import { TestbedHome } from '@features/gov-testbed';
|
import { TestbedHome } from '@features/gov-testbed';
|
||||||
import { CockpitHome, KnowledgeHome, MemPage } from '@features/gov-memory';
|
import { CockpitHome, KnowledgeHome, MemPage } from '@features/gov-memory';
|
||||||
import { ChanPage, LlmPage, SystemHome } from '@features/gov-system';
|
import { ChanPage, LlmPage, ModelsHome, SystemHome } from '@features/gov-system';
|
||||||
|
|
||||||
/* ── URL 编解码:`#/<surface>/<zone>/<tab>[/<k>:<id>…]` ────────────────── */
|
/* ── URL 编解码:`#/<surface>/<zone>/<tab>[/<k>:<id>…]` ────────────────── */
|
||||||
|
|
||||||
@@ -235,7 +236,12 @@ export function GovShell({ surface }: { surface: Surface }) {
|
|||||||
<p>{meta.desc}</p>
|
<p>{meta.desc}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="g-tabs">
|
{/*
|
||||||
|
* 页签栏只在**真有得选**时出现:一区一内容的区(拆分后大多如此)
|
||||||
|
* 摆一个只有一颗按钮的导航条,是在给人做无谓的选择。
|
||||||
|
* 台账例外 —— 它的页签是随发布生长的数据域,数量本来就不定。
|
||||||
|
*/}
|
||||||
|
<div className={'g-tabs' + (zone !== 'ledgers' && TABS[zone].length <= 1 ? ' hidden' : '')}>
|
||||||
{zone === 'ledgers'
|
{zone === 'ledgers'
|
||||||
? // 台账的页签是**数据域**(短名 2-3 字),随发布生长 —— 设计产生运营的形状,落在页签上
|
? // 台账的页签是**数据域**(短名 2-3 字),随发布生长 —— 设计产生运营的形状,落在页签上
|
||||||
domains.map((d) => (
|
domains.map((d) => (
|
||||||
@@ -277,10 +283,13 @@ function ZoneHome({ zone, tab, nav }: { zone: GovZone; tab: string; nav: Nav })
|
|||||||
const P = {
|
const P = {
|
||||||
// 设计中心
|
// 设计中心
|
||||||
blueprint: BlueprintHome,
|
blueprint: BlueprintHome,
|
||||||
|
triggers: TriggerRulesHome,
|
||||||
workflows: WorkflowsHome,
|
workflows: WorkflowsHome,
|
||||||
library: LibraryHome,
|
library: LibraryHome,
|
||||||
tools: ToolsHome,
|
tools: ToolsHome,
|
||||||
data: DataHome,
|
data: DataHome,
|
||||||
|
archive: ArchiveHome,
|
||||||
|
models: ModelsHome,
|
||||||
testbed: TestbedHome,
|
testbed: TestbedHome,
|
||||||
// 运营中心
|
// 运营中心
|
||||||
cockpit: CockpitHome,
|
cockpit: CockpitHome,
|
||||||
|
|||||||
@@ -84,6 +84,7 @@
|
|||||||
.g-head-t p { margin: 5px 0 0; font-size: 12.5px; line-height: 1.7; color: var(--ink-3); max-width: 108ch; }
|
.g-head-t p { margin: 5px 0 0; font-size: 12.5px; line-height: 1.7; color: var(--ink-3); max-width: 108ch; }
|
||||||
|
|
||||||
/* 区内页签:一个页签 = 一个维度 */
|
/* 区内页签:一个页签 = 一个维度 */
|
||||||
|
.g-tabs.hidden { display: none; }
|
||||||
.g-tabs { display: flex; flex-wrap: wrap; gap: 3px; padding: 4px; border-radius: 12px; background: #e3e8ef; align-self: flex-start; }
|
.g-tabs { display: flex; flex-wrap: wrap; gap: 3px; padding: 4px; border-radius: 12px; background: #e3e8ef; align-self: flex-start; }
|
||||||
.g-tab { display: inline-flex; align-items: center; gap: 7px; height: 32px; padding: 0 14px; border: 0; border-radius: 9px; background: transparent; color: var(--ink-3); font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; transition: background var(--t-fast), color var(--t-fast); }
|
.g-tab { display: inline-flex; align-items: center; gap: 7px; height: 32px; padding: 0 14px; border: 0; border-radius: 9px; background: transparent; color: var(--ink-3); font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; transition: background var(--t-fast), color var(--t-fast); }
|
||||||
.g-tab svg { color: var(--ink-4); }
|
.g-tab svg { color: var(--ink-4); }
|
||||||
@@ -680,12 +681,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 模式选择:不是第二条页签栏 —— 前置 mono 墨签点明这是个**控件**,方角、黄铜底线定当前 */
|
/* 模式选择:不是第二条页签栏 —— 前置 mono 墨签点明这是个**控件**,方角、黄铜底线定当前 */
|
||||||
.g-tbmode { display: flex; align-items: center; gap: 8px; }
|
|
||||||
.g-tbmode > em { font: 600 9.5px/1 var(--sh-mono, var(--mono)); font-style: normal; letter-spacing: .14em; text-transform: uppercase; color: #6a7686; }
|
|
||||||
.g-tbmode-b { display: inline-flex; align-items: center; gap: 6px; height: 27px; padding: 0 12px; border: 1px solid #d8dee6; border-bottom-width: 2px; background: #f7f9fb; color: #6a7686; font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; transition: color var(--t-fast), border-color var(--t-fast), background var(--t-fast); }
|
|
||||||
.g-tbmode-b:hover { color: #46586a; background: #fff; }
|
|
||||||
.g-tbmode-b.on { background: #fff; color: #161b22; font-weight: 700; border-color: #c6cfd9; border-bottom-color: #8a5f1c; }
|
|
||||||
.g-tbmode-b:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
|
|
||||||
|
|
||||||
/* ── 图纸卡:方角白纸 + mono 三字码题头(与流程区的 VER — / FIG — 同一idiom)── */
|
/* ── 图纸卡:方角白纸 + mono 三字码题头(与流程区的 VER — / FIG — 同一idiom)── */
|
||||||
.g-sheet { display: flex; flex-direction: column; min-width: 0; background: #fff; border: 1px solid var(--sh-line); box-shadow: var(--cshadow); }
|
.g-sheet { display: flex; flex-direction: column; min-width: 0; background: #fff; border: 1px solid var(--sh-line); box-shadow: var(--cshadow); }
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* feature `gov-archive` 唯一出口(编码规范 §9.1)。
|
||||||
|
* 知识库 —— 企业的资料与文档。**待建**:占位页如实说明,不假装有内容。
|
||||||
|
*/
|
||||||
|
export { ArchiveHome } from './internal/panel.tsx';
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 知识库 —— 待建。
|
||||||
|
*
|
||||||
|
* 占位页只做一件事:**如实说它还没有**。不摆假数据、不摆点不动的按钮 ——
|
||||||
|
* 空着比装着强(与死信卡、沟通记录同一条规矩)。
|
||||||
|
*/
|
||||||
|
import { Card, Clear } from '@web-shared/gov/primitives.tsx';
|
||||||
|
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||||
|
|
||||||
|
export function ArchiveHome({ nav }: { tab: string; nav: Nav }) {
|
||||||
|
void nav;
|
||||||
|
return (
|
||||||
|
<Card ico="library" title="知识库" extra="待建">
|
||||||
|
<Clear>
|
||||||
|
这里将放企业的资料与文档 —— 规范、图纸、样例、外部材料,供活动按需引用。
|
||||||
|
<br />
|
||||||
|
目前还没有建:没有存储、没有检索、没有引用机制。等要用到的第一条流出现时再动手。
|
||||||
|
</Clear>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* feature `gov-process` —— 流程包(设计态):**一条流一个包,版本收在包内部**。
|
* feature `gov-process` —— 流程包(设计态):**一条流一个包,版本收在包内部**。
|
||||||
*
|
*
|
||||||
* 台面三页签:总览 · 流程包 · 触发器。
|
* 一区一内容:三枚计数摆在货架顶端,底下就是包货架(触发器已独立成区)。
|
||||||
* 货架一流程一卡(不按版本平铺);进详情后左 1/3 是版本列表、右 2/3 是流程图,
|
* 货架一流程一卡(不按版本平铺);进详情后左 1/3 是版本列表、右 2/3 是流程图,
|
||||||
* 图下是节点属性,再往下才是声明式的契约细节(调用接口 · 边映射 · 触发器)。
|
* 图下是节点属性,再往下才是声明式的契约细节(调用接口 · 边映射 · 触发器)。
|
||||||
*
|
*
|
||||||
@@ -41,7 +41,6 @@ import { FlowFigureBlock } from '@web-shared/gov/flow/FlowFigure.tsx';
|
|||||||
import { packageToGraph } from '@web-shared/gov/flow/pkg-to-graph.ts';
|
import { packageToGraph } from '@web-shared/gov/flow/pkg-to-graph.ts';
|
||||||
import { mergeGraphIntoPackage } from '@web-shared/gov/flow/graph-to-pkg.ts';
|
import { mergeGraphIntoPackage } from '@web-shared/gov/flow/graph-to-pkg.ts';
|
||||||
import type { FlowEdge, FlowGraph, FlowNode } from '@web-shared/gov/flow/flow-layout.ts';
|
import type { FlowEdge, FlowGraph, FlowNode } from '@web-shared/gov/flow/flow-layout.ts';
|
||||||
import { TriggerRulesHome } from '@features/gov-trigger';
|
|
||||||
|
|
||||||
export type HomeProps = { tab: string; nav: Nav };
|
export type HomeProps = { tab: string; nav: Nav };
|
||||||
|
|
||||||
@@ -206,11 +205,17 @@ function EdgeProps({ edge, onPatch }: { edge: FlowEdge; onPatch: (f: (e: FlowEdg
|
|||||||
|
|
||||||
/* ---------------- 台面:总览 · 流程包 · 触发器 ---------------- */
|
/* ---------------- 台面:总览 · 流程包 · 触发器 ---------------- */
|
||||||
|
|
||||||
export function WorkflowsHome({ tab, nav }: HomeProps) {
|
/**
|
||||||
// 触发器的**定义面**:谁调起这条流,与流程包一处维护(启停在运营中心)
|
* 流程包一区一内容:总览的三枚计数并进货架顶端 —— 一个页签的导航条不必存在,
|
||||||
if (tab === 'rules') return <TriggerRulesHome nav={nav} />;
|
* 「先点总览再点流程包」也不是人想做的事。触发器已独立成区。
|
||||||
if (tab === 'pkgs') return <FlowShelf nav={nav} />;
|
*/
|
||||||
return <OverviewHome />;
|
export function WorkflowsHome({ nav }: HomeProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<OverviewHome />
|
||||||
|
<FlowShelf nav={nav} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OverviewHome() {
|
function OverviewHome() {
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
* feature `gov-system` 唯一出口(编码规范 §9.1)。
|
* feature `gov-system` 唯一出口(编码规范 §9.1)。
|
||||||
* 系统管理:LLM 实例四元绑定、通道实例
|
* 系统管理:LLM 实例四元绑定、通道实例
|
||||||
*/
|
*/
|
||||||
export { SystemHome, LlmPage, ChanPage } from './internal/panel.tsx';
|
export { SystemHome, ModelsHome, LlmPage, ChanPage } from './internal/panel.tsx';
|
||||||
|
|||||||
@@ -14,6 +14,15 @@ import {
|
|||||||
|
|
||||||
export type HomeProps = { tab: string; nav: Nav };
|
export type HomeProps = { tab: string; nav: Nav };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大模型区 —— 模型实例的四元绑定(设计中心一级目录)。
|
||||||
|
* 从系统区拆出来:模型是**设计者天天要碰**的东西(活动声明用途名、换档改绑定),
|
||||||
|
* 不该和通道实例挤在「系统管理」里。
|
||||||
|
*/
|
||||||
|
export function ModelsHome({ nav }: HomeProps) {
|
||||||
|
return <SystemHome tab="llm" nav={nav} />;
|
||||||
|
}
|
||||||
|
|
||||||
export function SystemHome({ tab, nav }: HomeProps) {
|
export function SystemHome({ tab, nav }: HomeProps) {
|
||||||
const llm = useGovLlm();
|
const llm = useGovLlm();
|
||||||
const channels = useGovChannels();
|
const channels = useGovChannels();
|
||||||
|
|||||||
@@ -2,49 +2,17 @@
|
|||||||
* 联调测试台 —— 把定义**试着跑一遍**。
|
* 联调测试台 —— 把定义**试着跑一遍**。
|
||||||
*
|
*
|
||||||
* 测试台是**后端检测活动与流程**的地方,不是代人干活的地方:在这儿装配并起单,
|
* 测试台是**后端检测活动与流程**的地方,不是代人干活的地方:在这儿装配并起单,
|
||||||
* 真实的任务照旧落到被指派人的工作台上。所以它只管两件事:
|
* 真实的任务照旧落到被指派人的工作台上。
|
||||||
* **把流跑起来** + **看穿它怎么跑的**。
|
|
||||||
*
|
*
|
||||||
* 一条流有两种起法,对应两个模式 —— 两条路都得能试:
|
* **一次只跑一条**:测试台上的工作流生命周期完全跟着仿真状态 —— 起一单就是它,
|
||||||
* · 仿真试跑:直接调流程包的**调用接口**起单(触发器起单走的也是这个口),带节点指派;
|
* 停止仿真,这条流连同它派出去的活动任务一起销毁,不留半单在别人工作台上挂着。
|
||||||
* · 入站邮件演练:从**外界**推一封信进来,看归属解析怎么判、哪个触发器接、有没有起单。
|
|
||||||
*
|
*
|
||||||
* 模式不做成区页签(区页签由壳层的 `TABS` 定,测试台只占一格),做成本屏的一个控件 ——
|
* (外界来信那条路在**触发器**区就地演练 —— 它验的是规则,归那边。)
|
||||||
* 它选的是「用哪种刺激」,不是「看哪个维度」。
|
|
||||||
*/
|
*/
|
||||||
import { useState } from 'react';
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { Ico } from '@web-shared/Ico.tsx';
|
|
||||||
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||||
import { InboundDrill } from './inbound.tsx';
|
|
||||||
import { SimulateRun } from './simulate.tsx';
|
import { SimulateRun } from './simulate.tsx';
|
||||||
|
|
||||||
type Mode = 'run' | 'mail';
|
|
||||||
|
|
||||||
const MODES: ReadonlyArray<{ k: Mode; label: string; ico: string }> = [
|
|
||||||
{ k: 'run', label: '仿真试跑', ico: 'flask-conical' },
|
|
||||||
{ k: 'mail', label: '入站邮件演练', ico: 'mail' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function TestbedHome({ nav }: { tab: string; nav: Nav }): ReactNode {
|
export function TestbedHome({ nav }: { tab: string; nav: Nav }): ReactNode {
|
||||||
const [mode, setMode] = useState<Mode>('run');
|
return <SimulateRun nav={nav} />;
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="g-tbmode">
|
|
||||||
<em>试法</em>
|
|
||||||
{MODES.map((m) => (
|
|
||||||
<button
|
|
||||||
key={m.k}
|
|
||||||
className={'g-tbmode-b' + (mode === m.k ? ' on' : '')}
|
|
||||||
aria-pressed={mode === m.k}
|
|
||||||
onClick={() => setMode(m.k)}
|
|
||||||
>
|
|
||||||
<Ico name={m.ico} s={12} />
|
|
||||||
{m.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{mode === 'run' ? <SimulateRun nav={nav} /> : <InboundDrill nav={nav} />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
*/
|
*/
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import type { ProcessNode, ProcessPackage } from '@contracts';
|
import type { ProcessNode, ProcessPackage } from '@contracts';
|
||||||
import { TERMINAL_STATE_LABEL, WORKFLOW_STATE_LABEL, testRunRoute } from '@contracts';
|
import { TERMINAL_STATE_LABEL, WORKFLOW_STATE_LABEL, testCurrentRoute, testRunRoute, testStopRoute } from '@contracts';
|
||||||
import { Ico } from '@web-shared/Ico.tsx';
|
import { Ico } from '@web-shared/Ico.tsx';
|
||||||
import { call } from '@web-shared/typed-fetch.ts';
|
import { call } from '@web-shared/typed-fetch.ts';
|
||||||
import { useGovPackages, useGovStaff } from '@web-shared/gov/gov-api.ts';
|
import { useGovPackages, useGovStaff } from '@web-shared/gov/gov-api.ts';
|
||||||
@@ -76,7 +76,28 @@ export function SimulateRun({ nav }: { nav: Nav }): ReactNode {
|
|||||||
const [subject, setSubject] = useState('测试台试跑');
|
const [subject, setSubject] = useState('测试台试跑');
|
||||||
const [values, setValues] = useState<CallValues>({});
|
const [values, setValues] = useState<CallValues>({});
|
||||||
const [assignments, setAssignments] = useState<Readonly<Record<string, string>>>({});
|
const [assignments, setAssignments] = useState<Readonly<Record<string, string>>>({});
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const [workflowId, setWorkflowId] = useState<string | undefined>(undefined);
|
const [workflowId, setWorkflowId] = useState<string | undefined>(undefined);
|
||||||
|
/*
|
||||||
|
* **一次只跑一条**:刷新页面也认得回来 —— 不然仿真在后台跑着,界面上却像没起过,
|
||||||
|
* 人会再起一单(而上一单的任务还在别人工作台上挂着)。
|
||||||
|
*/
|
||||||
|
const current = useQuery({
|
||||||
|
queryKey: ['gov', 'testbed', 'current'],
|
||||||
|
queryFn: () => call<{ workflowId?: string; subject?: string }>(testCurrentRoute),
|
||||||
|
});
|
||||||
|
const running = workflowId ?? current.data?.workflowId;
|
||||||
|
|
||||||
|
/** 停止仿真 = **销毁**:工作流、活动任务、工单、实例、trace 一并拔掉 */
|
||||||
|
const stop = useMutation({
|
||||||
|
mutationFn: (id: string) => call<{ destroyed: boolean; tasks: number }>(testStopRoute, { body: { workflowId: id } }),
|
||||||
|
onSuccess: () => {
|
||||||
|
setWorkflowId(undefined);
|
||||||
|
run.reset();
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ['gov', 'testbed'] });
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ['desk'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
const [error, setError] = useState<string | undefined>(undefined);
|
const [error, setError] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
// 没选就落到第一个有生效版的包 —— 常见情形只有一个包,不该逼人先点一下
|
// 没选就落到第一个有生效版的包 —— 常见情形只有一个包,不该逼人先点一下
|
||||||
@@ -107,16 +128,14 @@ export function SimulateRun({ nav }: { nav: Nav }): ReactNode {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (workflowId !== undefined) {
|
if (running !== undefined) {
|
||||||
return (
|
return (
|
||||||
<Observe
|
<Observe
|
||||||
id={workflowId}
|
id={running}
|
||||||
staffName={staffName}
|
staffName={staffName}
|
||||||
nav={nav}
|
nav={nav}
|
||||||
onBack={() => {
|
stopping={stop.isPending}
|
||||||
setWorkflowId(undefined);
|
onBack={() => stop.mutate(running)}
|
||||||
run.reset();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -312,11 +331,13 @@ function Observe({
|
|||||||
staffName,
|
staffName,
|
||||||
nav,
|
nav,
|
||||||
onBack,
|
onBack,
|
||||||
|
stopping,
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
staffName: (sid: string) => string;
|
staffName: (sid: string) => string;
|
||||||
nav: Nav;
|
nav: Nav;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
|
stopping: boolean;
|
||||||
}): ReactNode {
|
}): ReactNode {
|
||||||
const wf = useRunWorkflow(id);
|
const wf = useRunWorkflow(id);
|
||||||
const state = wf.data?.workflow.state;
|
const state = wf.data?.workflow.state;
|
||||||
@@ -368,7 +389,7 @@ function Observe({
|
|||||||
}
|
}
|
||||||
acts={[
|
acts={[
|
||||||
{ label: '刷新', ico: 'refresh-cw', onClick: refresh },
|
{ label: '刷新', ico: 'refresh-cw', onClick: refresh },
|
||||||
{ label: '再装一单', ico: 'rotate-ccw', primary: true, onClick: onBack },
|
{ label: stopping ? '销毁中…' : '停止仿真', ico: 'square', primary: true, onClick: onBack },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* feature `gov-tool` —— 工具箱:**系统能对世界做什么**,一屏四段。
|
* feature `gov-tool` —— 工具箱:**系统能对世界做什么**,一屏四段。
|
||||||
*
|
*
|
||||||
* 功能工具 · 已注册的调用契约(读类自由调,写类登记意向)
|
* 功能工具 · 已注册的调用契约(读类自由调,写类登记意向)
|
||||||
* LLM 工具 · 大模型驱动的系统内通用工具(**不是模型**;模型在系统区绑)
|
* LLM 工具 · 大模型驱动的系统内通用工具(模型实例在**大模型**区绑)
|
||||||
* 人类工具 · 工单两模式 + 对话卡三族(调人也是调工具,只是被调的是人)
|
* 人类工具 · 工单两模式 + 对话卡三族(调人也是调工具,只是被调的是人)
|
||||||
* 接入 · 导入自定义工具(与第一方同门两审)/ 连接 MCP
|
* 接入 · 导入自定义工具(与第一方同门两审)/ 连接 MCP
|
||||||
*
|
*
|
||||||
@@ -75,11 +75,10 @@ const HUMAN_TOOLS = [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LLM 工具目录 —— 系统内**由大模型驱动**的通用工具(不是模型本身)。
|
* LLM 工具目录 —— 系统内**由大模型驱动**的通用工具。
|
||||||
*
|
*
|
||||||
* 模型是发动机,这些是装在发动机上的件:每一件都有明确的入、出与算账口径。
|
|
||||||
* 它们不在工具目录里注册 —— 与人类工具一样,是基座给的能力,不是谁装上来的。
|
* 它们不在工具目录里注册 —— 与人类工具一样,是基座给的能力,不是谁装上来的。
|
||||||
* 跑在哪个模型上由系统区的四元绑定决定(活动只见逻辑名,不触厂商与密钥)。
|
* 跑在哪个模型上由**大模型**区的四元绑定决定(活动只见逻辑名,不触厂商与密钥)。
|
||||||
*/
|
*/
|
||||||
const LLM_TOOLS = [
|
const LLM_TOOLS = [
|
||||||
{
|
{
|
||||||
@@ -150,8 +149,9 @@ export function ToolsHome({ nav }: HomeProps) {
|
|||||||
<Ico name="wrench" s={16} />
|
<Ico name="wrench" s={16} />
|
||||||
</span>
|
</span>
|
||||||
<span className="g-tile-t">
|
<span className="g-tile-t">
|
||||||
<b>{t.name}</b>
|
{/* 主名是人读的中文名,逻辑名退居副标 —— 一屏几十件工具时尤其要紧 */}
|
||||||
<em>v{t.version}</em>
|
<b>{t.displayName}</b>
|
||||||
|
<em>{t.name} · v{t.version}</em>
|
||||||
</span>
|
</span>
|
||||||
<St tone={write ? 'warn' : 'ok'} ico={write ? 'pen-line' : 'search'}>
|
<St tone={write ? 'warn' : 'ok'} ico={write ? 'pen-line' : 'search'}>
|
||||||
{write ? '写' : '读'}
|
{write ? '写' : '读'}
|
||||||
@@ -204,11 +204,9 @@ export function ToolsHome({ nav }: HomeProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Note>
|
<Note>
|
||||||
这些是<b>工具</b>,不是模型 —— 模型是它们跑在上面的发动机。
|
已绑 {(llm.data?.instances ?? []).length} 个模型实例:
|
||||||
当前绑了 {(llm.data?.instances ?? []).length} 个模型实例(
|
{(llm.data?.instances ?? []).map((m) => m.name).join(' · ') || '尚未绑定'}。
|
||||||
{(llm.data?.instances ?? []).map((m) => m.name).join(' · ') || '尚未绑定'});
|
换厂商或型号去<button className="g-link" onClick={() => nav.jump('models', 'llm')}>大模型</button>改绑定。
|
||||||
换厂商、换型号只改<button className="g-link" onClick={() => nav.jump('system', 'llm')}>系统区的四元绑定</button>,
|
|
||||||
这些工具与活动一行都不动。
|
|
||||||
</Note>
|
</Note>
|
||||||
</Sec>
|
</Sec>
|
||||||
|
|
||||||
@@ -450,7 +448,7 @@ export function ToolPage({ id }: { id: string }) {
|
|||||||
ico="wrench"
|
ico="wrench"
|
||||||
tone={write ? 'danger' : 'brand'}
|
tone={write ? 'danger' : 'brand'}
|
||||||
kicker={`工具 · ${t.name}`}
|
kicker={`工具 · ${t.name}`}
|
||||||
title={t.name}
|
title={t.displayName}
|
||||||
chips={
|
chips={
|
||||||
<>
|
<>
|
||||||
<Chip tone={write ? 'danger' : 'brand'}>{write ? '写类' : '读类'}</Chip>
|
<Chip tone={write ? 'danger' : 'brand'}>{write ? '写类' : '读类'}</Chip>
|
||||||
|
|||||||
+19
-28
@@ -1,5 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* 联调测试台 · 入站邮件演练 —— 从**外界**推一封信进来,看谁接。
|
* 触发器 · 入站邮件演练 —— 从**外界**推一封信进来,看哪条规则接住它。
|
||||||
|
*
|
||||||
|
* 摆在触发器区里,是因为它验的正是这一区定的东西:规则写下来了,来一封真信试试对不对。
|
||||||
*
|
*
|
||||||
* 造一封入站邮件(可带附件)投进通道,走的是**与真通道同一条**入站归一链路
|
* 造一封入站邮件(可带附件)投进通道,走的是**与真通道同一条**入站归一链路
|
||||||
* (`POST /api/ingest/channel/:id/mail` → 归属解析 → 统一事件流 → 三级消费)。
|
* (`POST /api/ingest/channel/:id/mail` → 归属解析 → 统一事件流 → 三级消费)。
|
||||||
@@ -17,7 +19,7 @@ import type { IngestMailOutput, InboundMail } from '@contracts';
|
|||||||
import { ingestMailRoute } from '@contracts';
|
import { ingestMailRoute } from '@contracts';
|
||||||
import { call } from '@web-shared/typed-fetch.ts';
|
import { call } from '@web-shared/typed-fetch.ts';
|
||||||
import { useGovChannels, useGovStaff } from '@web-shared/gov/gov-api.ts';
|
import { useGovChannels, useGovStaff } from '@web-shared/gov/gov-api.ts';
|
||||||
import { Acts, Card, Field, Note, PageHead, Rows, Sec, St } from '@web-shared/gov/primitives.tsx';
|
import { Acts, Band, Card, Field, Note, Rows, Sec, St } from '@web-shared/gov/primitives.tsx';
|
||||||
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -138,32 +140,9 @@ export function InboundDrill({ nav }: { nav: Nav }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHead
|
<Band ico="flask-conical" extra="定完就地验:规则写下来了,来一封真信试试对不对">
|
||||||
ico="flask-conical"
|
投一封信试试
|
||||||
tone="brand"
|
</Band>
|
||||||
kicker="联调测试台 · 事件触发"
|
|
||||||
title="入站邮件演练"
|
|
||||||
desc="试跑测的是「调用接口起单」那条路,这一屏测的是另一条:外界来了封信,触发器接不接、起不起单。"
|
|
||||||
chips={
|
|
||||||
<>
|
|
||||||
<St tone={allGreen ? 'ok' : 'warn'} ico={allGreen ? 'circle-check' : 'alert-circle'}>
|
|
||||||
{allGreen ? '起单条件齐' : `差 ${preflight.filter((p) => !p.ok).length} 条`}
|
|
||||||
</St>
|
|
||||||
<St tone="mute" ico="paperclip">
|
|
||||||
附件 {attachments.length}
|
|
||||||
</St>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
acts={[
|
|
||||||
{
|
|
||||||
label: send.isPending ? '投递中…' : '投递这封信',
|
|
||||||
ico: 'send',
|
|
||||||
primary: true,
|
|
||||||
disabled: send.isPending || effectiveChannel === '',
|
|
||||||
onClick: fire,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
走的是与真通道同一条入站链路:通道归一 → 归属解析 → 统一事件流 → 三级消费(任务监听 /
|
走的是与真通道同一条入站链路:通道归一 → 归属解析 → 统一事件流 → 三级消费(任务监听 /
|
||||||
@@ -256,6 +235,18 @@ export function InboundDrill({ nav }: { nav: Nav }) {
|
|||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
<Acts
|
||||||
|
align="end"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: send.isPending ? '投递中…' : allGreen ? '投递这封信' : `还差 ${preflight.filter((p) => !p.ok).length} 条,仍要投`,
|
||||||
|
ico: 'send',
|
||||||
|
primary: true,
|
||||||
|
disabled: send.isPending || effectiveChannel === '',
|
||||||
|
onClick: fire,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Sec>
|
</Sec>
|
||||||
|
|
||||||
{error !== undefined && (
|
{error !== undefined && (
|
||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
St,
|
St,
|
||||||
} from '@web-shared/gov/primitives.tsx';
|
} from '@web-shared/gov/primitives.tsx';
|
||||||
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||||
|
import { InboundDrill } from './inbound.tsx';
|
||||||
import { useGovObjects, useGovPackages, useGovTriggers } from '@web-shared/gov/gov-api.ts';
|
import { useGovObjects, useGovPackages, useGovTriggers } from '@web-shared/gov/gov-api.ts';
|
||||||
import { domainShort } from '@web-shared/gov/domains.ts';
|
import { domainShort } from '@web-shared/gov/domains.ts';
|
||||||
|
|
||||||
@@ -133,6 +134,8 @@ export function TriggerRulesHome({ nav }: { nav: Nav }) {
|
|||||||
)}
|
)}
|
||||||
</NList>
|
</NList>
|
||||||
)}
|
)}
|
||||||
|
{/* 定完就地验:来一封真信,看这些规则里哪条接住 */}
|
||||||
|
<InboundDrill nav={nav} />
|
||||||
</>
|
</>
|
||||||
</QueryGate>
|
</QueryGate>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -77,11 +77,20 @@ describe('legacyGovTarget —— 旧 #/gov 深链一条不死', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('已删的区落到对应新家', () => {
|
it('已删的区落到对应新家', () => {
|
||||||
expect(legacyGovTarget('#/gov/triggers/rules')).toBe('/design#/design/workflows/rules');
|
|
||||||
expect(legacyGovTarget('#/gov/memory/metrics')).toBe('/ops#/ops/cockpit/dash');
|
expect(legacyGovTarget('#/gov/memory/metrics')).toBe('/ops#/ops/cockpit/dash');
|
||||||
expect(legacyGovTarget('#/gov/memory/layers')).toBe('/ops#/ops/knowledge/layers');
|
expect(legacyGovTarget('#/gov/memory/layers')).toBe('/ops#/ops/knowledge/layers');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 触发器与大模型**又独立成区**了(2026-07-29):触发器一度并进流程包页签、
|
||||||
|
* 模型一度住在系统区 —— 那两段旧链接要各回各家,不能停在中途那一站。
|
||||||
|
*/
|
||||||
|
it('搬回自己区的:触发器与大模型', () => {
|
||||||
|
expect(legacyGovTarget('#/gov/triggers/rules')).toBe('/design#/design/triggers/rules');
|
||||||
|
expect(legacyGovTarget('#/gov/workflows/rules')).toBe('/design#/design/triggers/rules');
|
||||||
|
expect(legacyGovTarget('#/gov/system/llm')).toBe('/design#/design/models/llm');
|
||||||
|
});
|
||||||
|
|
||||||
it('裸 #/gov 落运营首页;非 gov 哈希不管', () => {
|
it('裸 #/gov 落运营首页;非 gov 哈希不管', () => {
|
||||||
expect(legacyGovTarget('#/gov')).toBe('/ops#/ops/cockpit/dash');
|
expect(legacyGovTarget('#/gov')).toBe('/ops#/ops/cockpit/dash');
|
||||||
expect(legacyGovTarget('')).toBeUndefined();
|
expect(legacyGovTarget('')).toBeUndefined();
|
||||||
|
|||||||
@@ -28,12 +28,20 @@ export type Route =
|
|||||||
* 记忆一分为二:度量升为 cockpit 首页,活动记忆归 knowledge。
|
* 记忆一分为二:度量升为 cockpit 首页,活动记忆归 knowledge。
|
||||||
*/
|
*/
|
||||||
export const GOV_ZONES = [
|
export const GOV_ZONES = [
|
||||||
// 设计中心
|
/*
|
||||||
|
* 设计中心九区,顺序即设计的次序:
|
||||||
|
* 蓝图(全局) → 触发器(谁调起) → 流程包(怎么流转) → 活动库(每件事怎么干)
|
||||||
|
* → 工具箱(能对世界做什么) → 数据域(运营什么) → 知识库(资料,待建)
|
||||||
|
* → 大模型(模型实例绑在哪) → 测试台(跑一遍看看)
|
||||||
|
*/
|
||||||
'blueprint',
|
'blueprint',
|
||||||
|
'triggers',
|
||||||
'workflows',
|
'workflows',
|
||||||
'library',
|
'library',
|
||||||
'tools',
|
'tools',
|
||||||
'data',
|
'data',
|
||||||
|
'archive',
|
||||||
|
'models',
|
||||||
'testbed',
|
'testbed',
|
||||||
// 运营中心
|
// 运营中心
|
||||||
'cockpit',
|
'cockpit',
|
||||||
@@ -47,10 +55,13 @@ export type GovZone = (typeof GOV_ZONES)[number];
|
|||||||
|
|
||||||
export const ZONES: ReadonlyArray<{ key: GovZone; label: string; icon: string }> = [
|
export const ZONES: ReadonlyArray<{ key: GovZone; label: string; icon: string }> = [
|
||||||
{ key: 'blueprint', label: '蓝图', icon: 'route' },
|
{ key: 'blueprint', label: '蓝图', icon: 'route' },
|
||||||
|
{ key: 'triggers', label: '触发器', icon: 'zap' },
|
||||||
{ key: 'workflows', label: '流程包', icon: 'workflow' },
|
{ key: 'workflows', label: '流程包', icon: 'workflow' },
|
||||||
{ key: 'library', label: '活动库', icon: 'boxes' },
|
{ key: 'library', label: '活动库', icon: 'boxes' },
|
||||||
{ key: 'tools', label: '工具箱', icon: 'wrench' },
|
{ key: 'tools', label: '工具箱', icon: 'wrench' },
|
||||||
{ key: 'data', label: '数据域', icon: 'database' },
|
{ key: 'data', label: '数据域', icon: 'database' },
|
||||||
|
{ key: 'archive', label: '知识库', icon: 'library' },
|
||||||
|
{ key: 'models', label: '大模型', icon: 'cpu' },
|
||||||
{ key: 'testbed', label: '测试台', icon: 'flask-conical' },
|
{ key: 'testbed', label: '测试台', icon: 'flask-conical' },
|
||||||
{ key: 'cockpit', label: '驾驶舱', icon: 'gauge' },
|
{ key: 'cockpit', label: '驾驶舱', icon: 'gauge' },
|
||||||
{ key: 'tasks', label: '任务', icon: 'activity' },
|
{ key: 'tasks', label: '任务', icon: 'activity' },
|
||||||
@@ -126,29 +137,46 @@ export const ZM: Readonly<Record<GovZone, { title: string; desc: string; accent:
|
|||||||
title: '联调测试台',
|
title: '联调测试台',
|
||||||
icon: 'flask-conical',
|
icon: 'flask-conical',
|
||||||
accent: 'linear-gradient(140deg,#e8833a,#c25f1a)',
|
accent: 'linear-gradient(140deg,#e8833a,#c25f1a)',
|
||||||
desc: '把定义试着跑一遍。两种试法:仿真试跑 —— 装一次调用、指派每个节点归谁、起单看穿全程(真实任务照旧落到被指派人的工作台,测试台不代答);入站邮件演练 —— 造一封信投进通道,看归属解析怎么判、哪个触发器命中。模拟的只有外界那一侧,系统内部机制一律真跑。',
|
desc: '把定义试着跑一遍:装一次调用、指派每个节点归谁、起单看穿全程。真实任务照旧落到被指派人的工作台,测试台不代答。一次只跑一条流,生命周期跟着仿真状态——停止仿真,这条流连同它派出的任务一起销毁。',
|
||||||
|
},
|
||||||
|
triggers: {
|
||||||
|
title: '触发器',
|
||||||
|
icon: 'zap',
|
||||||
|
accent: 'linear-gradient(140deg,#f0a020,#c47a0a)',
|
||||||
|
desc: '谁调起流程:事件(通道来件命中规则)、登记(人在表单上起一单)、周期(到点自己起)。规则在这里定,定完就地投一封信验哪条接住;起单开关在运营中心。',
|
||||||
|
},
|
||||||
|
archive: {
|
||||||
|
title: '知识库',
|
||||||
|
icon: 'library',
|
||||||
|
accent: 'linear-gradient(140deg,#8a5f1c,#5e3f10)',
|
||||||
|
desc: '企业的资料与文档 —— 规范、图纸、样例、外部材料。待建。',
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
title: '大模型',
|
||||||
|
icon: 'cpu',
|
||||||
|
accent: 'linear-gradient(140deg,#4b4a8c,#2f2e63)',
|
||||||
|
desc: '模型实例的四元绑定(Provider / URL / Key / Model)。活动只声明用途名,换厂商换型号改这里,活动一行不动;密钥只存这儿,不入包、不入留痕。',
|
||||||
},
|
},
|
||||||
system: {
|
system: {
|
||||||
title: '系统管理',
|
title: '系统管理',
|
||||||
icon: 'settings',
|
icon: 'settings',
|
||||||
accent: 'linear-gradient(140deg,#46586a,#2b3644)',
|
accent: 'linear-gradient(140deg,#46586a,#2b3644)',
|
||||||
desc: '系统边界的配置面:LLM 实例(名称间接层)、通道实例(归属键规则与默认认领岗位)、系统用途模型绑定。',
|
desc: '系统边界的配置面:通道实例——归属键规则与默认认领岗位。模型实例已独立成设计中心的「大模型」区。',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 区内页签:一页一个维度 —— 不把四种维度摞在同一屏。
|
* 区内页签 —— **多数区只有一格**:一区一内容,页签栏此时由壳层收起,不占一行。
|
||||||
*
|
* 还留着多格的只有运营侧的任务与员工(它们确实是几个不同维度),
|
||||||
* `ledgers` 的页签是数据域,随发布动态生成(壳层运行时铺开),故此表留空。
|
* 以及 `ledgers`:它的页签是数据域,随发布动态生成(壳层运行时铺开),故此表留空。
|
||||||
*/
|
*/
|
||||||
export const TABS: Readonly<Record<GovZone, ReadonlyArray<{ key: string; label: string; ico: string }>>> = {
|
export const TABS: Readonly<Record<GovZone, ReadonlyArray<{ key: string; label: string; ico: string }>>> = {
|
||||||
blueprint: [{ key: 'map', label: '企业蓝图', ico: 'route' }],
|
blueprint: [{ key: 'map', label: '企业蓝图', ico: 'route' }],
|
||||||
testbed: [{ key: 'inbound', label: '联调试跑', ico: 'flask-conical' }],
|
testbed: [{ key: 'run', label: '仿真试跑', ico: 'flask-conical' }],
|
||||||
workflows: [
|
triggers: [{ key: 'rules', label: '调起规则', ico: 'zap' }],
|
||||||
{ key: 'overview', label: '总览', ico: 'gauge' },
|
archive: [{ key: 'docs', label: '资料', ico: 'library' }],
|
||||||
{ key: 'pkgs', label: '流程包', ico: 'boxes' },
|
models: [{ key: 'llm', label: '模型实例', ico: 'cpu' }],
|
||||||
{ key: 'rules', label: '触发器', ico: 'zap' },
|
workflows: [{ key: 'pkgs', label: '流程包', ico: 'boxes' }],
|
||||||
],
|
|
||||||
library: [{ key: 'shelf', label: '活动货架', ico: 'boxes' }],
|
library: [{ key: 'shelf', label: '活动货架', ico: 'boxes' }],
|
||||||
tools: [{ key: 'catalog', label: '能力目录', ico: 'wrench' }],
|
tools: [{ key: 'catalog', label: '能力目录', ico: 'wrench' }],
|
||||||
data: [{ key: 'domains', label: '数据对象', ico: 'database' }],
|
data: [{ key: 'domains', label: '数据对象', ico: 'database' }],
|
||||||
@@ -167,10 +195,7 @@ export const TABS: Readonly<Record<GovZone, ReadonlyArray<{ key: string; label:
|
|||||||
],
|
],
|
||||||
ledgers: [], // 动态:页签即数据域(短名呈现),由壳层按已发布域生成
|
ledgers: [], // 动态:页签即数据域(短名呈现),由壳层按已发布域生成
|
||||||
knowledge: [{ key: 'layers', label: '活动记忆', ico: 'layers' }],
|
knowledge: [{ key: 'layers', label: '活动记忆', ico: 'layers' }],
|
||||||
system: [
|
system: [{ key: 'chan', label: '通道实例', ico: 'mail' }],
|
||||||
{ key: 'llm', label: 'LLM 实例', ico: 'cpu' },
|
|
||||||
{ key: 'chan', label: '通道实例', ico: 'mail' },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function routeLabel(r: Route): string {
|
export function routeLabel(r: Route): string {
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ export const SURFACE_META: Readonly<Record<Surface, { path: string; brand: strin
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 区归属:设计五区 + 运营六区,恰好铺满 `GOV_ZONES`(由测试守住) */
|
/** 区归属:设计九区 + 运营六区,恰好铺满 `GOV_ZONES`(由测试守住) */
|
||||||
export const SURFACE_ZONES: Readonly<Record<Surface, readonly GovZone[]>> = {
|
export const SURFACE_ZONES: Readonly<Record<Surface, readonly GovZone[]>> = {
|
||||||
design: ['blueprint', 'workflows', 'library', 'tools', 'data', 'testbed'],
|
design: ['blueprint', 'triggers', 'workflows', 'library', 'tools', 'data', 'archive', 'models', 'testbed'],
|
||||||
ops: ['cockpit', 'tasks', 'people', 'ledgers', 'knowledge', 'system'],
|
ops: ['cockpit', 'tasks', 'people', 'ledgers', 'knowledge', 'system'],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ export const SURFACE_ZONES: Readonly<Record<Surface, readonly GovZone[]>> = {
|
|||||||
* 数据**结构归设计、行数据归运营**。同一功能的两种动作各归其位,不拆授权。
|
* 数据**结构归设计、行数据归运营**。同一功能的两种动作各归其位,不拆授权。
|
||||||
*/
|
*/
|
||||||
export const SURFACE_FNS: Readonly<Record<Surface, readonly GovernanceFunction[]>> = {
|
export const SURFACE_FNS: Readonly<Record<Surface, readonly GovernanceFunction[]>> = {
|
||||||
design: ['process', 'activity', 'tool', 'data', 'trigger'],
|
design: ['process', 'activity', 'tool', 'data', 'trigger', 'system'],
|
||||||
ops: ['task', 'staff', 'memory', 'system', 'trigger', 'data'],
|
ops: ['task', 'staff', 'memory', 'system', 'trigger', 'data'],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,7 +66,9 @@ function isGovZone(value: string): value is GovZone {
|
|||||||
* 精确到页签的先匹配 —— 记忆的两个旧页签各归其位(度量升驾驶舱,活动记忆归知识库)。
|
* 精确到页签的先匹配 —— 记忆的两个旧页签各归其位(度量升驾驶舱,活动记忆归知识库)。
|
||||||
*/
|
*/
|
||||||
const LEGACY_MOVES: Readonly<Record<string, string>> = {
|
const LEGACY_MOVES: Readonly<Record<string, string>> = {
|
||||||
triggers: 'design/workflows/rules',
|
'workflows/rules': 'design/triggers/rules',
|
||||||
|
'testbed/inbound': 'design/testbed/run',
|
||||||
|
'system/llm': 'design/models/llm',
|
||||||
memory: 'ops/cockpit/dash',
|
memory: 'ops/cockpit/dash',
|
||||||
'memory/layers': 'ops/knowledge/layers',
|
'memory/layers': 'ops/knowledge/layers',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- 工具中文名:治理台一律主显示它,逻辑名(crm.history-price-read)退居副标。
|
||||||
|
-- 存量行先用逻辑名兜底,注册新版本时按契约填真名。
|
||||||
|
alter table sys.tool add column display_name text;
|
||||||
|
update sys.tool set display_name = name where display_name is null;
|
||||||
@@ -127,6 +127,13 @@
|
|||||||
"when": 1785130113172,
|
"when": 1785130113172,
|
||||||
"tag": "0017_trace_span",
|
"tag": "0017_trace_span",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 18,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1785130113173,
|
||||||
|
"tag": "0018_tool_display_name",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-9
@@ -33,7 +33,13 @@
|
|||||||
|
|
||||||
## 二、设计中心 `/design`
|
## 二、设计中心 `/design`
|
||||||
|
|
||||||
**左轨 = 企业蓝图(首页)+ 四库。**
|
**左轨 = 九区,顺序即设计的次序。** 一区一内容 —— 区内不再有页签导航,
|
||||||
|
点进一区看到的就是那一件事(唯一的例外是运营侧的任务与员工,它们确实是几个不同维度)。
|
||||||
|
|
||||||
|
```
|
||||||
|
蓝图 → 触发器 → 流程包 → 活动库 → 工具箱 → 数据域 → 知识库 → 大模型 → 测试台
|
||||||
|
全局 谁调起 怎么流转 怎么干 能干什么 运营什么 资料 跑在哪 试一遍
|
||||||
|
```
|
||||||
|
|
||||||
### 企业蓝图(首页)
|
### 企业蓝图(首页)
|
||||||
|
|
||||||
@@ -51,14 +57,19 @@
|
|||||||
全部聚在这里,点进即改。设计者的"需处置"不散落在四库里
|
全部聚在这里,点进即改。设计者的"需处置"不散落在四库里
|
||||||
(为此流程包区原有的「需处置」页签已删除:同一件事两处显示,就是散落)。
|
(为此流程包区原有的「需处置」页签已删除:同一件事两处显示,就是散落)。
|
||||||
|
|
||||||
### 四库
|
### 九区
|
||||||
|
|
||||||
| 库 | 定位 | 内容 |
|
| 区 | 定位 | 内容 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **流程包** | 战略 —— 企业以哪些流转运转 | 现工作流区整体迁入:编排工作区(画布 + 属性卡)、边映射校验、版本谱系、导出依赖文件。**触发器的定义并入本库**:包详情"调起它的触发器"从只读变可编辑(规则 / 输入装配 / 目标版本),另设「调起规则」页签横览全部触发器。触发器不再是独立一区 |
|
| **蓝图** | 全局 —— 定义完了没有 | 四库因果链 + 待补清单(见上) |
|
||||||
| **活动库** | 方法 —— 每件事怎么干 | 现活动库区平移:货架、契约面、范式、资产版本谱系、安装两审 |
|
| **触发器** | 谁调起流程 | 事件 / 登记 / 周期三类调起规则的定义;定完就地投一封信演练,看哪条规则接住(归属解析 → 三级消费的真链路)。**启停开关在运营中心** |
|
||||||
| **工具箱** | 能力 —— 系统能对世界做什么 | 现工具区平移:读/写目录、白名单授权、幂等口径、引用反查 |
|
| **流程包** | 战略 —— 企业以哪些流转运转 | 编排工作区(画布 + 属性卡)、边映射校验、版本谱系、导出依赖文件。总览的三枚计数并进货架顶端 |
|
||||||
| **数据域** | 对象 —— 企业运营什么 | 现数据区的**定义面**:域与对象 schema、字段、放行规则、被谁引用。**行数据不在这里** —— 那是运营的事 |
|
| **活动库** | 方法 —— 每件事怎么干 | 货架、契约面、范式、资产版本谱系、安装两审 |
|
||||||
|
| **工具箱** | 能力 —— 系统能对世界做什么 | 功能 / LLM / 人类三类工具目录、白名单授权、幂等口径、引用反查。**一律主显示中文名**,逻辑名退居副标 |
|
||||||
|
| **数据域** | 对象 —— 企业运营什么 | 域与对象 schema、字段、放行规则、被谁引用。**行数据不在这里** —— 那是运营的事 |
|
||||||
|
| **知识库** | 企业的资料与文档 | 规范、图纸、样例、外部材料。**待建**,现在是个占位区 |
|
||||||
|
| **大模型** | 模型实例绑在哪 | Provider / URL / Key / Model 四元绑定。活动只声明用途名,换厂商换型号改这里;密钥只存这儿,不入包、不入留痕 |
|
||||||
|
| **测试台** | 试着跑一遍 | 装一次调用、指派每个节点归谁、起单看穿全程(全链 Trace)。真实任务照旧落到被指派人的工作台,测试台不代答。**一次只跑一条流**:生命周期跟着仿真状态,停止仿真即销毁这条流连同它派出的任务 |
|
||||||
|
|
||||||
### 一条纪律
|
### 一条纪律
|
||||||
|
|
||||||
@@ -203,11 +214,11 @@ shared
|
|||||||
| 决策 | 选择 | 理由 |
|
| 决策 | 选择 | 理由 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 拆分形态 | 两个独立台面(+工作台成三面) | 产品叙事最干净:定义 / 监控 / 干活是三种心智;权限天然分界 |
|
| 拆分形态 | 两个独立台面(+工作台成三面) | 产品叙事最干净:定义 / 监控 / 干活是三种心智;权限天然分界 |
|
||||||
| 触发器归属 | 定义归设计(流程包内),处置归运营 | 同一对象两种动作各归其位;避免两边各放一份 |
|
| 触发器归属 | 定义**独立成设计中心一区**,启停归运营 | 同一对象两种动作各归其位;起初塞在流程包内,但它与流程包平级(不入包结构、不随包版本),塞进去反而讲不清 |
|
||||||
| 度量归属 | 升为运营首页(驾驶舱) | "看企业今天跑得怎样"需要开屏答案;与活动记忆分开是两种心智 |
|
| 度量归属 | 升为运营首页(驾驶舱) | "看企业今天跑得怎样"需要开屏答案;与活动记忆分开是两种心智 |
|
||||||
| 动态位形态 | 域 = 导航项,随发布长 | "设计产生运营的形状"要在导航上直接可见;对象级太碎 |
|
| 动态位形态 | 域 = 导航项,随发布长 | "设计产生运营的形状"要在导航上直接可见;对象级太碎 |
|
||||||
| 权限粒度 | 保留九功能粒度,入口按读权亮灯 | grants 零迁移,细粒度保住;≤50 人不需要两层模型 |
|
| 权限粒度 | 保留九功能粒度,入口按读权亮灯 | grants 零迁移,细粒度保住;≤50 人不需要两层模型 |
|
||||||
| 设计中心 IA | 蓝图首页 + 四库平铺 | 因果链要有一屏能讲;四库直达的稳定导航同时保住 |
|
| 设计中心 IA | 蓝图首页 + 九区一级平铺,一区一内容 | 因果链要有一屏能讲;区内页签退化成一格后就是多余的一行,索性删掉 —— 点进一区看到的就是那一件事 |
|
||||||
| 交互基调(2026-07-28) | 设计台**卡片式**,运营台**列表式** | 设计是「看清一件东西的全貌」——卡片一眼见轮廓;运营是「查一堆记录里的那一条」——列表可扫可比 |
|
| 交互基调(2026-07-28) | 设计台**卡片式**,运营台**列表式** | 设计是「看清一件东西的全貌」——卡片一眼见轮廓;运营是「查一堆记录里的那一条」——列表可扫可比 |
|
||||||
| 数据四域(2026-07-28) | project / customer / intel / supplier | **流转单据归项目**(每一单的受理记录→比价→草案→报价单),**沉淀资产按内容归**客户/情报/供应商;按活动名分域会让「同一个客户」散在七处 |
|
| 数据四域(2026-07-28) | project / customer / intel / supplier | **流转单据归项目**(每一单的受理记录→比价→草案→报价单),**沉淀资产按内容归**客户/情报/供应商;按活动名分域会让「同一个客户」散在七处 |
|
||||||
| 活动与工具解耦(2026-07-28) | 槽位只声明工具**名** | 注册、连接值、白名单全在治理侧 —— 活动代码里看不到授权表,也就绕不开它 |
|
| 活动与工具解耦(2026-07-28) | 槽位只声明工具**名** | 注册、连接值、白名单全在治理侧 —— 活动代码里看不到授权表,也就绕不开它 |
|
||||||
|
|||||||
@@ -339,6 +339,8 @@ export const registerToolRoute = {
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
contract: z.object({
|
contract: z.object({
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
|
/** 中文名 —— 治理台主显示;不给就以逻辑名兜底(界面宁可显示机器名,不显示空白) */
|
||||||
|
displayName: z.string().min(1).optional(),
|
||||||
version: z.string().min(1),
|
version: z.string().min(1),
|
||||||
description: z.string().min(1),
|
description: z.string().min(1),
|
||||||
input: z.record(z.string(), z.unknown()),
|
input: z.record(z.string(), z.unknown()),
|
||||||
@@ -407,6 +409,8 @@ export const listToolsRoute = {
|
|||||||
tools: z.array(
|
tools: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
/** 中文名 —— 治理台主显示;逻辑名退居副标 */
|
||||||
|
displayName: z.string(),
|
||||||
version: z.string(),
|
version: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
effect: z.enum(['read', 'write']),
|
effect: z.enum(['read', 'write']),
|
||||||
@@ -527,6 +531,24 @@ export const testRunRoute = {
|
|||||||
output: z.object({ workflowId: z.string(), state: workflowStateSchema }),
|
output: z.object({ workflowId: z.string(), state: workflowStateSchema }),
|
||||||
} as const satisfies RouteContract;
|
} as const satisfies RouteContract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止仿真 —— **把这一单连根拔掉**:工作流、它的活动任务、工单、实例、trace 全部销毁。
|
||||||
|
* 仿真不是生产:试跑留下的东西不该在库里、在别人工作台上过夜。
|
||||||
|
*/
|
||||||
|
export const testStopRoute = {
|
||||||
|
method: 'POST',
|
||||||
|
path: '/api/gov/testbed/stop',
|
||||||
|
body: z.object({ workflowId: z.string() }),
|
||||||
|
output: z.object({ destroyed: z.boolean(), tasks: z.number().int() }),
|
||||||
|
} as const satisfies RouteContract;
|
||||||
|
|
||||||
|
/** 当前在跑的仿真(测试台一次只跑一条)—— 刷新页面也认得回来 */
|
||||||
|
export const testCurrentRoute = {
|
||||||
|
method: 'GET',
|
||||||
|
path: '/api/gov/testbed/current',
|
||||||
|
output: z.object({ workflowId: z.string().optional(), subject: z.string().optional() }),
|
||||||
|
} as const satisfies RouteContract;
|
||||||
|
|
||||||
/** 全链 Trace:一条工作流的 span 树(节点 → 步骤 / 工具 / 模型 / 人卡) */
|
/** 全链 Trace:一条工作流的 span 树(节点 → 步骤 / 工具 / 模型 / 人卡) */
|
||||||
export const traceOfWorkflowRoute = {
|
export const traceOfWorkflowRoute = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
Reference in New Issue
Block a user