版本:v1.0 | 日期:2026-06-25 | 基于 spec-SCSAI回写优化.md 需求规格 v2.0
1. 实现模型
1.1 上下文视图
1.1.1 系统上下文
SCSAI 回写优化在 BossAgents 整体架构中的定位——修复前端创建闭环、后端降级路径、关系格式转换、后端重试回滚、create_post 可靠性、前后端同步六大关键路径缺陷:
``
┌─────────────────────────────────────────────────────────────────────────┐
│ 外部系统 │
│ ┌───────────────┐ ┌───────────────┐ ┌────────────────────────────┐ │
│ │ 业务用户 │ │ SCSAI agent│ │ SmartLLMRouter │ │
│ │ (Web/小程序) │ │ (AML操作) │ │ (5级降级链路) │ │
│ └───────┬───────┘ └───────┬───────┘ └─────────────┬──────────────┘ │
└──────────┼──────────────────┼─────────────────────────┼─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ BossAgents 核心层 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 前端创建路径(权威实现) │ │
│ │ useCapabilityCreate.js → AmlBuilder.js → /SCSAI-api/ApplyItem │ │
│ │ ★ 5.1: generate输出对接前端创建流程 │ │
│ │ ★ 5.5: create_post规则执行结果可感知 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 后端创建路径(镜像实现) │ │
│ │ CapabilityRuntime.create() → rule-engine.js createItem() │ │
│ │ → AMLGenerator / aml-builder.js → SCSAIClient.sendAML() │ │
│ │ ★ 5.2: AMLGenerator item_properties补齐 │ │
│ │ ★ 5.3: 关系发现格式转换 │ │
│ │ ★ 5.4: 后端创建路径重试与回滚 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 同步维护层 │ │
│ │ ★ 5.6: 前后端创建逻辑同步维护 │ │
│ │ aml-builder.js @mirror-of AmlBuilder.js │ │
│ │ unified-create.js @mirror-of useCapabilityCreate.js │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
`
1.1.2 需求与模块映射
| 需求ID | 优先级 | 需求名称 | 主要修改模块 | 修改类型 |
|---|---|---|---|---|
| 5.1 | P0 | generate能力输出与前端创建流程对接 | capability-runtime.js, rule-engine.js, useCapabilityCreate.js | 修改 |
| 5.2 | P0 | AMLGenerator降级路径item_properties补齐 | aml-generator.js | 修改 |
| 5.3 | P0 | 关系发现格式转换 | capability-runtime.js, relationship-resolver.js | 修改+新增 |
| 5.4 | P1 | 后端创建路径重试与回滚 | SCSAI-client.js, rule-engine.js, unified-create.js | 修改 |
| 5.5 | P1 | 前端create_post规则执行可靠性 | rule-engine.js, server.js, useCapabilityCreate.js | 修改+新增 |
| 5.6 | P2 | 前后端创建逻辑同步维护 | aml-builder.js, unified-create.js, 测试文件 | 修改+新增 |
1.2 服务/组件总体架构
1.2.1 优化后的回写数据流
`
┌─────────────────────────┐
│ 用户触发 generate 能力 │
└───────────┬─────────────┘
│
┌───────────▼─────────────┐
│ CapabilityRuntime.generate() │
│ → executeGenerate() │
│ 返回 generated_content │
│ ★ 标准化输出格式: │
│ { item_type, properties, │
│ item_properties, │
│ relationships } │
└───────────┬─────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌───────────▼──────────┐ ┌─────▼──────────┐ ┌──────▼──────────┐
│ 前端权威创建路径 │ │ 后端镜像创建路径 │ │ 后端降级路径 │
│ useCapabilityCreate │ │ rule-engine.js │ │ AMLGenerator │
│ → AmlBuilder.js │ │ → aml-builder.js│ │ ★ item_props │
│ → /SCSAI-api/ApplyItem │ │ → SCSAIClient │ │ 补齐 │
│ ★ 自动消费 generate │ │ ★ 重试与回滚 │ │ │
│ 输出 │ │ ★ 关系格式转换 │ │ │
└───────────┬──────────┘ └─────┬──────────┘ └──────┬──────────┘
│ │ │
│ ┌────────▼────────┐ │
│ │ SCSAIClient │ │
│ │ ★ SOAP Fault解析 │ │
│ │ ★ 超时结构化错误 │ │
│ │ ★ 重试策略 │ │
│ └────────┬────────┘ │
│ │ │
┌───────────▼────────────────────▼─────────────────────▼──────────┐
│ SCSAI agent │
└───────────────────────────────┬──────────────────────────────────┘
│
┌───────────────────▼───────────────────────┐
│ create_post 规则执行 │
│ ★ 前端可感知执行结果(非 fire-and-forget) │
│ ★ 重试机制(最多2次,指数退避) │
│ ★ 超时处理(30秒) │
│ ★ 结果持久化(前端断线可补发) │
└───────────────────────────────────────────┘
`
1.2.2 关系发现格式转换流
`
RelationshipResolver.discoverRelations()
│
▼ 返回 source_index/target_index 格式
[{ parent_index: 0, child_index: 1, relation_type: 'Part BOM', properties: {...} }]
│
▼ ★ 格式转换器(新增 convertDiscoveryRelations)
[{ relationship_type: 'Part BOM', related_item_type: 'Part',
related_items: [{ properties: {...} }] }]
│
▼ 传入 AmlBuilder / AMLGenerator
buildAML({ relationships: 转换后数据 })
`
1.3 实现设计文档
1.3.1 P0-5.1: generate能力输出与前端创建流程对接
#### 需求概述
executeGenerate() 返回的 generated_content 需要输出为前端 useCapabilityCreate 可直接消费的结构化 JSON,且前端能自动启动创建流程。
#### 当前问题分析
- executeGenerate() 输出格式不标准:当前 generated_content
来自规则 action_script 的自由格式输出,不保证包含item_type/properties/item_properties/relationships四层结构 - 前端无法自动消费:useCapabilityCreate.js 期望接收 { item_type, properties, item_properties, relationships }
格式,但 generate 返回的是自由文本或非标准 JSON - LLM 降级路径输出不标准:LLM Router 返回的内容可能是 {"content": "..."}
而非创建所需的结构化数据
#### 修改文件
| 文件路径 | 修改类型 | 说明 |
|---------|---------|------|
| server/core/rule-engine.js | 修改 | executeGenerate() 返回标准化 generated_content |
| server/core/capability-runtime.js | 修改 | generate() 方法确保输出格式标准化 |
| src/composables/useCapabilityCreate.js | 修改 | 新增 createFromGenerate() 方法消费 generate 输出 |
#### 核心类/函数设计
1. rule-engine.js — executeGenerate() 输出标准化
在 executeGenerate() 方法(第1683行)中,对 generated_content 进行格式标准化处理:
`javascript
// executeGenerate() 返回前,标准化 generated_content
_standardizeGeneratedContent(rawContent, item_type) {
if (!rawContent) return null;
// 如果已是标准格式(含 item_type + properties),直接返回
if (typeof rawContent === 'object' && rawContent.item_type && rawContent.properties) {
return rawContent;
}
// 如果是字符串,尝试解析 JSON
let parsed = rawContent;
if (typeof rawContent === 'string') {
try {
const jsonMatch = rawContent.match(/\{[\s\S]*\}/);
if (jsonMatch) parsed = JSON.parse(jsonMatch[0]);
} catch (e) {
// 无法解析,返回原始文本包装
return { item_type, properties: { description: rawContent }, _unparseable: true };
}
}
// 如果解析后的对象有 properties 字段,提取标准格式
if (parsed && typeof parsed === 'object') {
// 情况1:已经是标准格式
if (parsed.item_type && parsed.properties) return parsed;
// 情况2:LLM 返回 { properties: {...}, relationships: [...] } 但无 item_type
if (parsed.properties && typeof parsed.properties === 'object') {
return {
item_type: item_type || parsed.item_type || 'Part',
properties: parsed.properties,
item_properties: parsed.item_properties || {},
relationships: parsed.relationships || []
};
}
// 情况3:LLM 返回扁平属性对象(最常见)
// 将非标准字段(relationships, item_properties)提取出来
const { relationships, item_properties, ...properties } = parsed;
return {
item_type: item_type || parsed.item_type || 'Part',
properties,
item_properties: item_properties || {},
relationships: relationships || []
};
}
// 无法标准化
return { item_type, properties: { description: String(rawContent) }, _unparseable: true };
}
`
2. capability-runtime.js — generate() 方法增强
在 generate() 方法(第774行)中,确保返回的 generated_content 是标准格式:
`javascript
async generate(context, options = {}) {
const { item_type, data, template_id, autoCreate } = context;
const engine = await this._initRuleEngine();
const startTime = Date.now();
let result = null;
if (engine) {
const engineResult = await engine.executeGenerate(
{ item_type, data, template_id },
this._buildEngineOptions()
);
if (engineResult && engineResult.executed) {
this._recordRuleEngineHit();
// ★ 标准化 generated_content
engineResult.generated_content = this._standardizeGeneratedContent(
engineResult.generated_content, item_type
);
result = this._formatResult('generate', engineResult, {
source: engineResult.source || 'rule_engine',
durationMs: Date.now() - startTime
});
}
}
if (!result) {
// LLM 降级:使用创建专用 prompt
const systemPrompt = this._buildCreatePrompt(item_type, null, data);
const llmResult = await this._callLLMViaRouter({
prompt: JSON.stringify(data),
systemPrompt,
taskType: 'generate',
capability: 'generate',
context: { item_type, data },
});
if (llmResult) {
// ★ 标准化 LLM 输出
const standardized = this._standardizeGeneratedContent(
llmResult.data || llmResult.raw, item_type
);
result = this._formatResult('generate', {
executed: true,
generated_content: standardized,
scope: 'generate',
item_type
}, { source: 'llm_router', durationMs: Date.now() - startTime });
}
}
if (!result) {
return { capability: 'generate', success: false, error: '规则引擎和 LLM 均不可用' };
}
// ★ autoCreate 标记:指示前端是否自动启动创建流程
result.autoCreate = autoCreate !== false;
return result;
}
/**
- ★ 标准化 generate 输出为前端可消费格式
*/
_standardizeGeneratedContent(rawContent, item_type) {
if (!rawContent) return null;
if (typeof rawContent === 'object' && rawContent.item_type && rawContent.properties) {
return rawContent;
}
let parsed = rawContent;
if (typeof rawContent === 'string') {
try {
const jsonMatch = rawContent.match(/\{[\s\S]*\}/);
if (jsonMatch) parsed = JSON.parse(jsonMatch[0]);
} catch (e) {
return { item_type, properties: { description: rawContent }, _unparseable: true };
}
}
if (parsed && typeof parsed === 'object') {
if (parsed.item_type && parsed.properties) return parsed;
if (parsed.properties && typeof parsed.properties === 'object') {
return {
item_type: item_type || parsed.item_type || 'Part',
properties: parsed.properties,
item_properties: parsed.item_properties || {},
relationships: parsed.relationships || []
};
}
const { relationships, item_properties, ...properties } = parsed;
return {
item_type: item_type || parsed.item_type || 'Part',
properties,
item_properties: item_properties || {},
relationships: relationships || []
};
}
return { item_type, properties: { description: String(rawContent) }, _unparseable: true };
}
`
3. useCapabilityCreate.js — 新增 createFromGenerate() 方法
在前端 useCapabilityCreate.js 中新增方法,消费 generate 输出并自动启动创建流程:
`javascript
/**
- ★ 从 generate 输出创建对象
- @param {Object} generatedContent - generate 返回的结构化数据
- { item_type, properties, item_properties, relationships }
- @param {Object} options - { autoCreate: true/false }
- @returns {Object} 创建结果
*/
async function createFromGenerate(generatedContent, options = {}) {
const { autoCreate = true } = options;
// 1. 格式校验
if (!generatedContent || !generatedContent.item_type) {
error.value = '生成内容无法自动创建,请手动填写'
return { success: false, reason: 'unparseable' }
}
// 2. 保留生成内容(失败不丢失)
result.value = { generated_content: generatedContent }
if (!autoCreate) {
// 仅展示,不自动创建
return { success: true, autoCreate: false, generatedContent }
}
// 3. 启动创建流程(复用 useCapabilityCreate 的 10 步流程)
try {
const createResult = await createObject({
item_type: generatedContent.item_type,
properties: generatedContent.properties || {},
item_properties: generatedContent.item_properties || {},
relationships: generatedContent.relationships || []
})
return createResult
} catch (e) {
// 创建失败,保留 generated_content 供用户手动修正
error.value = 创建失败: ${e.message},生成内容已保留
return {
success: false,
reason: 'create_failed',
generatedContent,
error: e.message
}
}
}
`
#### 数据模型
generate 输出标准化格式:
`typescript
interface GeneratedContent {
item_type: string // 必填,SCSAI ItemType 名称
properties: Record
item_properties?: Record action: 'add' | 'get' item_type: string properties?: Record id?: string keyed_name?: string }> relationships?: Array<{ // 可选,关系定义 relationship_type: string related_item_type: string related_items: Array<{ properties?: Record related_id?: string | object relItemProps?: Record }> }> _unparseable?: boolean // 内部标记:原始内容无法解析 } ` #### 依赖关系 #### 需求概述 AMLGenerator._buildAML() 当前不支持 item_properties 字段的嵌套输出,item/foreign 类型字段的对象值被直接输出为字符串(裸 GUID),需与前端 AmlBuilder.buildItemElement() 输出一致。 #### 当前问题分析 #### 修改文件 | 文件路径 | 修改类型 | 说明 | |---------|---------|------| | server/core/aml-generator.js #### 核心类/函数设计 1. aml-generator.js — 修改 _buildAML() 在 _buildAML() ` _buildAML(data, schema, options) { const { itemType } = schema; const action = options.action || 'add'; const { properties } = schema; const skipKeys = ['relationships', '_relationships', 'id']; // ★ 收集 item_properties(从 data 中提取或从 schema 推断) const itemProps = data.item_properties || data._item_properties || {}; // 构建属性 XML(★ 修改:item/foreign 类型对象值不直接输出,转入 itemProps) const propertiesXml = Object.entries(data) .filter(([key, value]) => value !== undefined && value !== null && !skipKeys.includes(key) && key !== 'item_properties' && key !== '_item_properties') .map(([key, value]) => { const prop = properties[key]; const dataType = prop?.type || 'string'; // ★ item/foreign 类型且值为对象 → 转入 item_properties 处理 if ((dataType === 'item' || dataType === 'foreign') && typeof value === 'object' && value !== null) { itemProps[key] = value; return null; // 不在 properties XML 中输出 } // item/foreign 类型且值为字符串(GUID)→ 直接输出 if (dataType === 'item' || dataType === 'foreign') { return <${key}>${String(value)}${key}> } if (['list', 'color list', 'date', 'integer', 'float', 'decimal', 'number', 'boolean'].includes(dataType)) { return <${key}>${this._escapeXml(String(value))}${key}> } const escaped = this._escapeXml(String(value)); return <${key}>${key}> }) .filter(Boolean) // ★ 过滤掉 null(被转入 item_properties 的字段) .join('\n'); // ★ 构建 item_properties 嵌套 XML(与 AmlBuilder.buildItemElement 一致) const itemPropsXml = this._buildItemPropertiesXml(itemProps); // 构建 Relationships XML const relationships = data.relationships || data._relationships || []; const relationshipsXml = this._buildRelationshipsXml(relationships); const idAttr = (action !== 'add' && data.id) ? id="${data.id}" const aml = ${propertiesXml}${itemPropsXml}${relationshipsXml} return aml; } /** * */ _buildItemPropertiesXml(itemProperties) { if (!itemProperties || typeof itemProperties !== 'object') return ''; let xml = ''; for (const [propName, propDef] of Object.entries(itemProperties)) { if (!propDef || typeof propDef !== 'object') continue; if (propDef.action === 'add' && propDef.item_type) { // 嵌套创建:递归构建子 Item const subProps = propDef.properties || {}; const subPropsXml = Object.entries(subProps) .filter(([, v]) => v !== undefined && v !== null) .map(([k, v]) => { if (typeof v === 'object') return null; // 不支持更深层嵌套 return <${k}>${this._escapeXml(String(v))}${k}> }) .filter(Boolean) .join('\n'); xml += \n <${propName}> xml += \n xml += subPropsXml ? '\n' + subPropsXml : ''; xml += \n xml += \n ${propName}> } else if (propDef.action === 'get' && propDef.id && propDef.item_type) { // 引用已有对象 const keyedName = propDef.keyed_name || ''; xml += \n <${propName}> xml += xml += ${propName}> } } return xml; } ` #### 依赖关系 #### 需求概述 RelationshipResolver.discoverRelations() 返回 source_index/target_index 格式的关系数据,需转换为 AmlBuilder 标准的 relationship_type/related_item_type/related_items 格式后传入 AML 构建。 #### 当前问题分析 #### 修改文件 | 文件路径 | 修改类型 | 说明 | |---------|---------|------| | server/core/capability-runtime.js | server/core/relationship-resolver.js #### 核心类/函数设计 1. relationship-resolver.js — 新增格式转换方法 在 RelationshipResolver 类中新增静态方法: ` /** * */ static convertDiscoveryRelations(discoveredRelations, batchItems, mainItemType) { if (!Array.isArray(discoveredRelations) || discoveredRelations.length === 0) return []; if (!Array.isArray(batchItems) || batchItems.length === 0) return []; // 按 relation_type 分组 const grouped = {}; for (const rel of discoveredRelations) { const relType = rel.relation_type || rel.relationship_type; if (!relType) { console.warn('[RelationshipResolver] 关系类型为空,跳过'); continue; } const parentIdx = rel.parent_index ?? rel.source_index; const childIdx = rel.child_index ?? rel.target_index; // 索引越界检查 if (parentIdx === undefined || childIdx === undefined) continue; if (parentIdx < 0 || parentIdx >= batchItems.length || childIdx < 0 || childIdx >= batchItems.length) { console.warn([RelationshipResolver] 关系索引越界: parent=${parentIdx}, child=${childIdx}, batch_size=${batchItems.length} continue; } if (!grouped[relType]) { grouped[relType] = { relationship_type: relType, related_items: [] }; } const childItem = batchItems[childIdx]; const relatedItemType = childItem.item_type || mainItemType; // 设置 related_item_type(同一关系类型下取第一个子对象的类型) if (!grouped[relType].related_item_type) { grouped[relType].related_item_type = relatedItemType; } // 构建相关项 const relatedItem = { properties: { ...childItem } }; // 关系级别属性(如 quantity, sort_order) if (rel.properties && Object.keys(rel.properties).length > 0) { const relItemProps = {}; for (const [k, v] of Object.entries(rel.properties)) { if (v !== null && v !== undefined && k !== 'SCSAI_relation_id' && k !== 'direction') { relatedItem[k] = v; // 放在 related_item 层级 } } } grouped[relType].related_items.push(relatedItem); } // 校验转换结果 const result = []; for (const group of Object.values(grouped)) { if (!group.relationship_type || !group.related_item_type || !group.related_items || group.related_items.length === 0) { console.warn('[RelationshipResolver] 转换结果校验失败,跳过:', JSON.stringify(group).substring(0, 100)); continue; } result.push(group); } return result; } ` 2. capability-runtime.js — 修改 create() 方法 在 create() ` // ===== 关系感知:批量关系发现 ===== let discoveredRelations = []; if (resolver && Array.isArray(context.batch_items) && context.batch_items.length > 1) { try { const discovery = await resolver.discoverRelations(item_type, context.batch_items); discoveredRelations = discovery.relations || []; if (discoveredRelations.length > 0) { this._log('create', item_type, '自动发现关系', ${discoveredRelations.length} 条 // ★ 格式转换:source_index/target_index → relationship_type/related_item_type/related_items const { RelationshipResolver } = require('./relationship-resolver'); const convertedRelations = RelationshipResolver.convertDiscoveryRelations( discoveredRelations, context.batch_items, item_type ); // 合并到 relationships(转换后的标准格式) for (const rel of convertedRelations) { relationships.push(rel); } } } catch (e) { this._log('create', item_type, '关系发现失败(非阻塞)', e.message); } } ` #### 依赖关系 #### 需求概述 后端 createItem()(unified-create.js / rule-engine.js)中 applyAML 失败后需实施重试,SCSAIClient.sendAML() 需解析 SOAP Fault 返回结构化错误,多关系对象部分失败需回滚。 #### 当前问题分析 #### 修改文件 | 文件路径 | 修改类型 | 说明 | |---------|---------|------| | server/utils/SCSAI-client.js | server/core/rule-engine.js | server/routes/unified-create.js #### 核心类/函数设计 1. SCSAI-client.js — sendAML() 超时返回结构化错误 修改 _sendRequest() ` _sendRequest(url, options, postData) { return new Promise((resolve, reject) => { // ... 现有代码 ... // 超时处理(★ 修改:resolve 而非 reject,返回结构化错误) req.setTimeout(this.timeout, () => { req.destroy(); // ★ 不再 reject,改为 resolve 结构化错误 resolve({ statusCode: 0, headers: {}, data: '', timeoutError: true, timeoutMs: this.timeout }); }); // ... 现有代码 ... }); } ` 修改 sendAML() ` async sendAML(aml, opts = {}) { // ... 现有代码 ... const response = await this._sendRequest(url, options, soapEnvelope); // ★ 超时处理:返回结构化错误 if (response.timeoutError) { return { success: false, items: [], fault: { code: 'TIMEOUT', string: SCSAI请求超时 (${response.timeoutMs}ms) actor: '', detail: timeout after ${response.timeoutMs}ms }, rawXml: '', count: 0 }; } // 检查 HTTP 状态码 if (response.statusCode >= 400) { return { success: false, items: [], fault: { code: 'HTTP_ERROR', string: HTTP ${response.statusCode} actor: '', detail: response.data }, rawXml: response.data, count: 0 }; } return this._parseResponse(response.data); } ` 2. rule-engine.js — createItem() 添加重试与回滚 在 createItem() ` /** */ async _applyAMLWithRetry(applyAML, aml, context) { const maxRetries = 3; let currentAml = aml; const retryReasons = []; for (let attempt = 0; attempt <= maxRetries; attempt++) { const result = await applyAML(currentAml); // 成功 if (result && (result.items?.length > 0 || result.Item)) { return { result, retryCount: attempt, retryReasons }; } // 解析错误 const fault = result?.fault || {}; const faultCode = fault.code || ''; const faultString = fault.string || ''; // 权限错误 → 重试1次(重新获取 token 后重试) if (/no default permission|permission_id|access denied/i.test(faultString) && attempt === 0) { retryReasons.push({ attempt, reason: 'permission_error', faultCode, faultString }); // 权限重试:注入 World 权限 const worldAml = try { const worldRes = await applyAML(worldAml); const worldId = worldRes?.items?.[0]?.id; if (worldId) { currentAml = currentAml.replace( / ); } } catch (e) { / 忽略 / } continue; } // 唯一性冲突 → 追加后缀重试(最多3次) if (/not unique|PropertiesAreNotUnique/i.test(faultString)) { retryReasons.push({ attempt, reason: 'uniqueness_conflict', faultCode, faultString }); const suffix = -${attempt + 1} // 在 item_number 或 name 后追加后缀 currentAml = currentAml.replace( / (match, val) => ); if (attempt < 2) continue; // 最多追加3次后缀 } // 超时 → 重试1次 if (faultCode === 'TIMEOUT' && attempt === 0) { retryReasons.push({ attempt, reason: 'timeout', faultCode, faultString }); continue; } // 其他错误 → 指数退避重试 if (attempt < maxRetries) { retryReasons.push({ attempt, reason: 'other_error', faultCode, faultString }); await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); continue; } // 全部重试失败 return { result, retryCount: attempt, retryReasons, finalFault: fault }; } } /** */ async _rollbackCreatedItems(applyAML, createdIds) { const rolledBackIds = []; const orphanIds = []; for (const { item_type, item_id } of createdIds) { try { const deleteAml = const result = await applyAML(deleteAml); if (result?.items?.length > 0 || result?.Item) { rolledBackIds.push(item_id); // ★ 审计日志 console.log('[AUDIT] rollback_delete', JSON.stringify({ action: 'rollback_delete', item_type, item_id, reason: '后续步骤失败,回滚已创建对象', timestamp: new Date().toISOString() })); } else { orphanIds.push(item_id); console.error('[CRITICAL] 回滚失败,孤儿对象:', item_id); } } catch (e) { orphanIds.push(item_id); console.error('[CRITICAL] 回滚异常,孤儿对象:', item_id, e.message); } } return { rolled_back: orphanIds.length === 0, rolled_back_ids: rolledBackIds, orphan_ids: orphanIds }; } ` 3. rule-engine.js — createItem() 集成重试与回滚 修改 createItem() ` // 3. 构建AML并提交(★ 使用带重试的提交) let itemId = null, amlError = null; try { const cleanProps = {}; for (const [k, v] of Object.entries(context.properties || {})) { if (v === null || v === undefined || v === '') continue; if (typeof v === 'object') continue; cleanProps[k] = v; } const { buildAML } = require('../utils/aml-builder'); const aml = buildAML({ item_type, action: 'add', properties: cleanProps, item_properties: context.item_properties, relationships: context.relationships }); if (applyAML) { // ★ 带重试的 AML 提交 const { result: applyResult, retryCount, finalFault } = await this._applyAMLWithRetry(applyAML, aml, context); // 提取 itemId if (applyResult?.Item?.id) itemId = applyResult.Item.id; if (!itemId && applyResult?.items?.[0]?.id) itemId = applyResult.items[0].id; if (!itemId && applyResult?.id) itemId = applyResult.id; if (!itemId) { // ★ 嵌套 AML 失败 → 分步降级 const hasNested = Object.keys(context.item_properties || {}).length > 0 || (context.relationships || []).length > 0; if (hasNested && retryCount > 0) { // 分步降级:先创建主对象(仅 properties),再逐个创建子对象和关系 const stepDownResult = await this._stepDownCreate(applyAML, item_type, cleanProps, context.item_properties, context.relationships); if (stepDownResult.success) { itemId = stepDownResult.item_id; } else { return { success: false, errors: ['SCSAI提交失败(含分步降级)'], warnings, rule_results: allResults, aml, retry_count: retryCount, final_fault: finalFault }; } } else { return { success: false, errors: ['SCSAI提交失败'], warnings, rule_results: allResults, aml, retry_count: retryCount, final_fault: finalFault }; } } } else { return { success: true, aml, rule_results: allResults, warnings, item_id: null }; } } catch (e) { amlError = e.message; errors.push('AML创建失败: ' + e.message); } ` #### 依赖关系 #### 需求概述 前端创建成功后触发的 create_post 规则执行结果需能被前端感知,不允许 fire-and-forget 导致规则执行丢失。 #### 当前问题分析 #### 修改文件 | 文件路径 | 修改类型 | 说明 | |---------|---------|------| | server.js | server/core/rule-engine.js | src/composables/useCapabilityCreate.js #### 核心类/函数设计 1. rule-engine.js — 新增 executeCreatePost() 方法 ` /** * */ async executeCreatePost(context, options = {}) { const { item_type, item_id } = context; const maxRetries = options.maxRetries ?? 2; const timeoutMs = options.timeout ?? 30000; if (!item_type || !item_id) { return { success: false, error: '缺少 item_type 或 item_id', rule_results: [] }; } const postContext = { ...context, item_id }; let lastError = null; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { // 超时控制 const result = await Promise.race([ this.execute('create_post', postContext, { conflict_strategy: 'merge' }), new Promise((_, reject) => setTimeout(() => reject(new Error('create_post 执行超时')), timeoutMs) ) ]); // 审计日志 console.log('[AUDIT] create_post', JSON.stringify({ action: 'create_post_execute', item_type, item_id, attempt, success: true, rule_count: result.results?.length || 0, timestamp: new Date().toISOString() })); return { success: true, rule_results: result.results || [], retry_count: attempt, elapsed: result.elapsed || 0 }; } catch (e) { lastError = e.message; // 审计日志(失败) console.log('[AUDIT] create_post_failed', JSON.stringify({ action: 'create_post_execute', item_type, item_id, attempt, success: false, error: lastError, timestamp: new Date().toISOString() })); // 指数退避重试 if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); } } } return { success: false, error: lastError, rule_results: [], retry_count: maxRetries }; } ` 2. server.js — 新增 create_post 端点 在 server.js 的路由处理中新增端点: ` // POST /api/rule-engine/execute-create-post // 前端创建成功后调用,执行 create_post 规则 if (pathname === '/api/rule-engine/execute-create-post') { const { item_type, item_id, properties, relationships } = body || {}; if (!item_type || !item_id) { return { success: false, message: '请提供 item_type 和 item_id' }; } try { const result = await engine.executeCreatePost( { item_type, item_id, properties: properties || {}, relationships: relationships || [] }, { maxRetries: 2, timeout: 30000 } ); return { success: result.success, data: result }; } catch (e) { return { success: false, message: e.message }; } } ` 3. useCapabilityCreate.js — 创建成功后调用 create_post 在前端创建成功后,调用 create_post 端点获取规则执行结果: ` // ★ 创建成功后执行 create_post 规则(替代 fire-and-forget) async function executeCreatePost(itemType, itemId, properties, relationships) { try { const response = await fetch('/api/rule-engine/execute-create-post', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ item_type: itemType, item_id: itemId, properties, relationships }) }) if (!response.ok) { console.warn('[useCapabilityCreate] create_post 请求失败:', response.status) return { success: false, error: HTTP ${response.status} } const result = await response.json() if (result.success) { console.log('[useCapabilityCreate] create_post 执行成功:', result.data?.rule_results?.length || 0, '条规则') } else { console.warn('[useCapabilityCreate] create_post 执行失败:', result.data?.error) // ★ 不回滚主对象,仅通知用户 error.value = 对象创建成功,但后续处理失败:${result.data?.error || '未知原因'} } return result } catch (e) { console.warn('[useCapabilityCreate] create_post 请求异常:', e.message) return { success: false, error: e.message } } } ` #### 数据模型 create_post 结果持久化(用于前端断线后补发): ` CREATE TABLE IF NOT EXISTS create_post_results ( id INTEGER PRIMARY KEY AUTOINCREMENT, item_type TEXT NOT NULL, item_id TEXT NOT NULL, success INTEGER NOT NULL DEFAULT 0, rule_results TEXT, -- JSON error TEXT, retry_count INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now','localtime')), delivered INTEGER DEFAULT 0, -- 是否已推送到前端 delivered_at TEXT ); CREATE INDEX IF NOT EXISTS idx_post_results_item ON create_post_results(item_type, item_id); CREATE INDEX IF NOT EXISTS idx_post_results_undelivered ON create_post_results(delivered) WHERE delivered = 0; ` #### 需求概述 后端 aml-builder.js 与前端 AmlBuilder.js、后端 unified-create.js 与前端 useCapabilityCreate.js 的逻辑差异需通过自动化测试检测,后端镜像代码需标注前端对应文件和同步状态。 #### 当前问题分析 #### 修改文件 | 文件路径 | 修改类型 | 说明 | |---------|---------|------| | server/utils/aml-builder.js | server/routes/unified-create.js | tests/aml-builder-consistency.test.js | tests/create-flow-consistency.test.js #### 核心类/函数设计 1. 文件头 @mirror-of 标注 在 server/utils/aml-builder.js ` /** * * * */ ` 在 server/routes/unified-create.js ` /** * * * */ ` 2. 自动化一致性测试 ` // tests/aml-builder-consistency.test.js /** * */ const { buildAML: serverBuildAML } = require('../server/utils/aml-builder') // 测试用例:标准三层数据结构 const testCases = [ { name: '普通属性', data: { item_type: 'Part', action: 'add', properties: { name: '测试零件', item_number: 'P-001' } } }, { name: '含 item_properties(嵌套创建)', data: { item_type: 'Project', action: 'add', properties: { name: '测试项目' }, item_properties: { wbs_id: { action: 'add', item_type: 'WBS Element', properties: { name: '测试 WBS' } } } } }, { name: '含 item_properties(引用已有)', data: { item_type: 'Project', action: 'add', properties: { name: '测试项目' }, item_properties: { scheduling_method: { action: 'get', item_type: 'Method', id: 'ABC123', keyed_name: 'Standard' } } } }, { name: '含 relationships', data: { item_type: 'Part', action: 'add', properties: { name: '父零件' }, relationships: [{ relationship_type: 'Part BOM', related_item_type: 'Part', related_items: [{ properties: { name: '子零件', item_number: 'P-002' } }] }] } }, { name: '完整三层结构', data: { item_type: 'Project', action: 'add', properties: { name: '完整项目' }, item_properties: { wbs_id: { action: 'add', item_type: 'WBS Element', properties: { name: '项目 WBS' } } }, relationships: [{ relationship_type: 'Project Tree', related_item_type: 'Project', related_items: [{ properties: { name: '子项目' } }] }] } } ] describe('AmlBuilder 前后端一致性', () => { testCases.forEach(({ name, data }) => { test(name, () => { const serverAml = serverBuildAML(data) // 基本校验:输出非空 expect(serverAml).toBeTruthy() // 结构校验:包含必要的 XML 元素 expect(serverAml).toContain(' expect(serverAml).toContain('') expect(serverAml).toContain(type="${data.item_type}" expect(serverAml).toContain(action="${data.action || 'add'}" // item_properties 校验 if (data.item_properties) { for (const [propName, propDef] of Object.entries(data.item_properties)) { expect(serverAml).toContain(<${propName}> expect(serverAml).toContain(${propName}> if (propDef.action === 'add') { expect(serverAml).toContain(type="${propDef.item_type}" } else if (propDef.action === 'get') { expect(serverAml).toContain(id="${propDef.id}" } } } // relationships 校验 if (data.relationships?.length > 0) { expect(serverAml).toContain(' expect(serverAml).toContain('') for (const rel of data.relationships) { expect(serverAml).toContain(type="${rel.relationship_type}" } } }) }) }) ` #### 依赖关系 SCSAI 回写优化涉及的接口变更遵循以下原则: | 接口 | 方法 | 路径 | 说明 | |------|------|------|------| | create_post 执行 | POST | /api/rule-engine/execute-create-post POST /api/rule-engine/execute-create-post 请求体: ` { "item_type": "Project", "item_id": "ABC123DEF456", "properties": { "name": "测试项目" }, "relationships": [] } ` 响应体(成功): ` { "success": true, "data": { "success": true, "rule_results": [ { "rule_id": "builtin-project-create-post-001", "rule_name": "Project: 子任务编号补全", "result": { "modified": true } } ], "retry_count": 0, "elapsed": 150 } } ` 响应体(失败): ` { "success": false, "data": { "success": false, "error": "create_post 执行超时", "rule_results": [], "retry_count": 2 } } ` ` // generate 能力输出标准化格式 interface GeneratedContent { // 必填 item_type: string; // SCSAI ItemType 名称 properties: Record // 可选 item_properties?: Record action: 'add' | 'get'; item_type: string; properties?: Record id?: string; // action='get' 时必填 keyed_name?: string; // action='get' 时推荐 }>; relationships?: AmlBuilderRelation[]; // 关系定义 // 内部标记 _unparseable?: boolean; // 原始内容无法解析 _source?: 'rule_engine' | 'llm_router'; // 生成来源 } ` ` // AmlBuilder 标准关系格式(转换后) interface AmlBuilderRelation { relationship_type: string; // SCSAI RelationshipType 名称 related_item_type: string; // 关联对象 ItemType related_items: AmlBuilderRelatedItem[]; // 关联对象列表 } interface AmlBuilderRelatedItem { properties?: Record item_properties?: Record related_id?: string | object; // 引用 ID relItemProps?: Record id?: string; // 已有对象 ID item_number?: string; // 已有对象编号 } ` ` // discoverRelations() 返回格式(转换前) interface DiscoveryRelation { parent_index: number; // 源对象在 batch_items 中的索引 child_index: number; // 目标对象在 batch_items 中的索引 relation_type: string; // SCSAI RelationshipType 名称 properties?: Record field_hint?: string; // 关系发现来源提示 parent_ref?: string; // 父对象引用 } ` ` CREATE TABLE IF NOT EXISTS create_post_results ( id INTEGER PRIMARY KEY AUTOINCREMENT, item_type TEXT NOT NULL, item_id TEXT NOT NULL, success INTEGER NOT NULL DEFAULT 0, rule_results TEXT, -- JSON 格式 error TEXT, retry_count INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now','localtime')), delivered INTEGER DEFAULT 0, -- 是否已推送到前端 delivered_at TEXT ); CREATE INDEX IF NOT EXISTS idx_post_results_item ON create_post_results(item_type, item_id); CREATE INDEX IF NOT EXISTS idx_post_results_undelivered ON create_post_results(delivered) WHERE delivered = 0; ` ` SCSAI_retry: max_retries: 3 permission_retry: 1 uniqueness_retry: 3 timeout_retry: 1 backoff_base_ms: 1000 create_post: max_retries: 2 timeout_ms: 30000 backoff_base_ms: 1000 ` ` // 审计日志记录格式 interface RollbackAuditLog { action: 'rollback_delete'; item_type: string; item_id: string; reason: string; timestamp: string; // ISO 8601 operator?: string; // 操作人(可选) } interface CreatePostAuditLog { action: 'create_post_execute' | 'create_post_failed'; item_type: string; item_id: string; attempt: number; success: boolean; error?: string; rule_count?: number; timestamp: string; } `` 的 createObject() 方法(已存在) 的 buildAML() 方法(已存在) 的 executeGenerate() 方法(已存在)1.3.2 P0-5.2: AMLGenerator降级路径item_properties补齐
<${key}>${String(value)}${key}>\; 将对象值直接 String() 化,输出 [object Object] | 修改 | _buildAML() 方法添加 item_properties 嵌套输出 | 方法 方法中,将 item/foreign 类型字段的对象值按 item_properties 格式输出,并在 properties XML 后、relationships XML 前插入 item_properties 嵌套输出:javascript;;; : '';;;;;;;;;; 的 buildItemElement() 作为格式参考(唯一真相源)1.3.3 P0-5.3: 关系发现格式转换
/target_index 字段/related_item_type/related_items 格式 | 修改 | create() 方法中关系发现结果格式转换 | | 新增 | convertDiscoveryRelations() 静态方法 |javascript); 方法(第871-893行)中,将关系发现结果转换为 AmlBuilder 标准格式:javascript); 的 discoverRelations() 方法(已存在) 的输入格式要求(已存在)1.3.4 P1-5.4: 后端创建路径重试与回滚
,调用方需 try/catch 方法已存在(第137-153行),但 sendAML() 的错误路径未统一使用 | 修改 | sendAML() 超时返回结构化错误而非抛异常 | | 修改 | createItem() 添加重试与回滚逻辑 | | 修改 | 7步创建流程添加回滚逻辑 | 方法,超时时 resolve 而非 reject:javascript 方法,处理超时响应:javascript,, 方法中,对 applyAML 调用添加重试逻辑:javascript;;; 方法中 applyAML 调用部分(第2538行附近):javascript 的 sendAML() 方法(已存在,需修改超时处理) 的 buildAML() 方法(已存在)1.3.5 P1-5.5: 前端create_post规则执行可靠性
中,但前端通过 /SCSAI-api/ApplyItem 直接提交时,不经过 createItem() 代理提交 AML,绕过了后端规则引擎的 create_post 执行 | 修改 | 新增 /api/rule-engine/execute-create-post 端点 | | 修改 | 新增 executeCreatePost() 方法(带重试和超时) | | 修改 | 创建成功后调用 create_post 端点 |javascriptjavascriptjavascript }sql1.3.6 P2-5.6: 前后端创建逻辑同步维护
方法(前端有 list 值匹配、日期格式化等),esc() 方法实现略有不同 | 修改 | 添加文件头 @mirror-of 标注 | | 修改 | 添加文件头 @mirror-of 标注 | | 新增 | 前后端 AmlBuilder 输出一致性测试 | | 新增 | 前后端创建流程逻辑一致性测试 | 文件头部添加:javascript 文件头部添加:javascriptjavascript))))))) 和 AmlBuilder.js 的输出格式(已存在)2. 接口设计
2.1 总体设计
代理保持不变2.2 接口清单
2.2.1 新增接口
| 前端创建成功后调用,执行 create_post 规则并返回结果 |jsonjsonjson2.2.2 变更接口
接口 变更类型 说明 CapabilityRuntime.generate() 返回值变更 generated_content 标准化为 { item_type, properties, item_properties, relationships } 格式 SCSAIClient.sendAML() 行为变更 超时不再抛异常,返回 { success: false, fault: { code: 'TIMEOUT' } } rule-engine.js createItem() 行为变更 applyAML 失败后实施重试,支持分步降级和回滚 4. 数据模型
4.1 设计目标
接口,确保 generate 输出可被前端消费 接口,统一关系数据格式 表,支持前端断线后补发4.2 模型实现
4.2.1 GeneratedContent 接口
typescript4.2.2 AmlBuilderRelation 接口
typescript4.2.3 DiscoveryRelation 接口(转换前)
typescript4.2.4 create_post_results 表
sql4.2.5 重试策略配置
yamlconfig.yaml 新增配置段
4.2.6 回滚审计日志格式
typescript
BossAgents