BossAgents SCSAI 回写优化 — 实现方案文档

BossAgents SCSAI 回写优化 — 实现方案文档

版本: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,且前端能自动启动创建流程。

#### 当前问题分析

  1. executeGenerate() 输出格式不标准:当前 generated_content 来自规则 action_script 的自由格式输出,不保证包含 item_type/properties/item_properties/relationships 四层结构
  2. 前端无法自动消费:useCapabilityCreate.js 期望接收 { item_type, properties, item_properties, relationships } 格式,但 generate 返回的是自由文本或非标准 JSON
  3. 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 进行格式标准化处理:

// 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 是标准格式:

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 输出并自动启动创建流程:

/**
 * ★ 从 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 输出标准化格式

interface GeneratedContent {
  item_type: string                    // 必填,SCSAI ItemType 名称
  properties: Record<string, any>      // 必填,普通属性键值对
  item_properties?: Record<string, {   // 可选,Item 类型属性
    action: 'add' | 'get'
    item_type: string
    properties?: Record<string, any>
    id?: string
    keyed_name?: string
  }>
  relationships?: Array<{             // 可选,关系定义
    relationship_type: string
    related_item_type: string
    related_items: Array<{
      properties?: Record<string, any>
      related_id?: string | object
      relItemProps?: Record<string, any>
    }>
  }>
  _unparseable?: boolean              // 内部标记:原始内容无法解析
}

#### 依赖关系

  • 依赖 useCapabilityCreate.jscreateObject() 方法(已存在)
  • 依赖 AmlBuilder.jsbuildAML() 方法(已存在)
  • 依赖 rule-engine.jsexecuteGenerate() 方法(已存在)

1.3.2 P0-5.2: AMLGenerator降级路径item_properties补齐

#### 需求概述

AMLGenerator._buildAML() 当前不支持 item_properties 字段的嵌套输出,item/foreign 类型字段的对象值被直接输出为字符串(裸 GUID),需与前端 AmlBuilder.buildItemElement() 输出一致。

#### 当前问题分析

  1. _buildAML() 缺少 item_properties 处理:当前代码(第347-383行)仅处理 properties 和 relationships,无 item_properties 嵌套输出
  2. item/foreign 类型字段对象值被裸输出:第359行 return \ <${key}>${String(value)}\; 将对象值直接 String() 化,输出 [object Object]
  3. 与 AmlBuilder 输出不一致:AmlBuilder.buildItemElement() 支持 item_properties 嵌套创建和 action='get' 引用

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server/core/aml-generator.js | 修改 | _buildAML() 方法添加 item_properties 嵌套输出 |

#### 核心类/函数设计

1. aml-generator.js — 修改 _buildAML() 方法

_buildAML() 方法中,将 item/foreign 类型字段的对象值按 item_properties 格式输出,并在 properties XML 后、relationships XML 前插入 item_properties 嵌套输出:

_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}><![CDATA[${escaped}]]></${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 = `<AML>
  <Item type="${itemType}" action="${action}"${idAttr}>
${propertiesXml}${itemPropsXml}${relationshipsXml}
  </Item>
</AML>`;

    return aml;
}

/**
 * ★ 新增:构建 item_properties 嵌套 XML
 * 与 AmlBuilder.buildItemElement() 的 item_properties 处理逻辑一致
 * 
 * 支持两种格式:
 * 1. 嵌套创建:{ action: 'add', item_type: 'WBS Element', properties: { name: 'xxx' } }
 * 2. 引用已有:{ action: 'get', item_type: 'Method', id: 'xxx', keyed_name: 'yyy' }
 */
_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      <Item type="${this._escapeXml(propDef.item_type)}" action="add">`;
            xml += subPropsXml ? '\n' + subPropsXml : '';
            xml += `\n      </Item>`;
            xml += `\n    </${propName}>`;
            
        } else if (propDef.action === 'get' && propDef.id && propDef.item_type) {
            // 引用已有对象
            const keyedName = propDef.keyed_name || '';
            xml += `\n    <${propName}>`;
            xml += `<Item type="${this._escapeXml(propDef.item_type)}" id="${this._escapeXml(propDef.id)}" keyed_name="${this._escapeXml(keyedName)}" action="get"/>`;
            xml += `</${propName}>`;
        }
    }
    
    return xml;
}

#### 依赖关系

  • 依赖 server/utils/aml-builder.jsbuildItemElement() 作为格式参考(唯一真相源)
  • 不引入新依赖

1.3.3 P0-5.3: 关系发现格式转换

#### 需求概述

RelationshipResolver.discoverRelations() 返回 source_index/target_index 格式的关系数据,需转换为 AmlBuilder 标准的 relationship_type/related_item_type/related_items 格式后传入 AML 构建。

#### 当前问题分析

  1. capability-runtime.js create() 直接推送 source_index 格式:第880-888行,discoveredRelations 被直接 push 到 relationships 数组,保留了 source_index/target_index 字段
  2. AmlBuilder 无法处理 source_index 格式:AmlBuilder.buildRelationshipElement() 期望 relationship_type/related_item_type/related_items 格式
  3. 缺少格式转换器:系统中无 source_index → AmlBuilder 标准格式的转换逻辑

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server/core/capability-runtime.js | 修改 | create() 方法中关系发现结果格式转换 |

| server/core/relationship-resolver.js | 新增 | convertDiscoveryRelations() 静态方法 |

#### 核心类/函数设计

1. relationship-resolver.js — 新增格式转换方法

在 RelationshipResolver 类中新增静态方法:

/**
 * ★ 将 discoverRelations() 返回的 source_index/target_index 格式
 * 转换为 AmlBuilder 标准的 relationship_type/related_item_type/related_items 格式
 * 
 * @param {Array} discoveredRelations - discoverRelations() 返回的关系数组
 *   [{ parent_index, child_index, relation_type, properties, field_hint }]
 * @param {Array} batchItems - 原始对象数组
 * @param {string} mainItemType - 主对象类型
 * @returns {Array} AmlBuilder 标准格式的关系数组
 */
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() 方法(第871-893行)中,将关系发现结果转换为 AmlBuilder 标准格式:

// ===== 关系感知:批量关系发现 =====
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);
  }
}

#### 依赖关系

  • 依赖 relationship-resolver.jsdiscoverRelations() 方法(已存在)
  • 依赖 AmlBuilder.buildRelationshipElement() 的输入格式要求(已存在)

1.3.4 P1-5.4: 后端创建路径重试与回滚

#### 需求概述

后端 createItem()(unified-create.js / rule-engine.js)中 applyAML 失败后需实施重试,SCSAIClient.sendAML() 需解析 SOAP Fault 返回结构化错误,多关系对象部分失败需回滚。

#### 当前问题分析

  1. SCSAI-client.js sendAML() 超时抛异常:第121-123行,超时时 reject(new Error(...)),调用方需 try/catch
  2. rule-engine.js createItem() 无重试:第2538-2550行,applyAML 失败后直接返回 { success: false }
  3. unified-create.js 无回滚:创建步骤失败后不回滚已创建的对象
  4. SOAP Fault 解析已有但不完善_parseFault() 方法已存在(第137-153行),但 sendAML() 的错误路径未统一使用

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server/utils/SCSAI-client.js | 修改 | sendAML() 超时返回结构化错误而非抛异常 |

| server/core/rule-engine.js | 修改 | createItem() 添加重试与回滚逻辑 |

| server/routes/unified-create.js | 修改 | 7步创建流程添加回滚逻辑 |

#### 核心类/函数设计

1. SCSAI-client.js — sendAML() 超时返回结构化错误

修改 _sendRequest() 方法,超时时 resolve 而非 reject:

_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() 方法中,对 applyAML 调用添加重试逻辑:

/**
 * ★ 带重试的 AML 提交
 * @param {Function} applyAML - AML 提交函数
 * @param {string} aml - AML 字符串
 * @param {Object} context - 上下文(用于重试时修改 AML)
 * @returns {Object} { result, retryCount, retryReasons }
 */
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 = `<AML><Item type="Permission" action="get"><name>World</name></Item></AML>`;
      try {
        const worldRes = await applyAML(worldAml);
        const worldId = worldRes?.items?.[0]?.id;
        if (worldId) {
          currentAml = currentAml.replace(
            /<Item type="([^"]+)" action="add">/,
            `<Item type="$1" action="add" permission_id="${worldId}">`
          );
        }
      } 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(
        /<item_number>([^<]+)<\/item_number>/,
        (match, val) => `<item_number>${val}${suffix}</item_number>`
      );
      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 
    };
  }
}

/**
 * ★ 回滚已创建的对象
 * @param {Function} applyAML - AML 提交函数
 * @param {Array} createdIds - 已创建对象的 ID 列表 [{ item_type, item_id }]
 * @returns {Object} { rolled_back, rolled_back_ids, orphan_ids }
 */
async _rollbackCreatedItems(applyAML, createdIds) {
  const rolledBackIds = [];
  const orphanIds = [];
  
  for (const { item_type, item_id } of createdIds) {
    try {
      const deleteAml = `<AML><Item type="${item_type}" action="delete" id="${item_id}" /></AML>`;
      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() 方法中 applyAML 调用部分(第2538行附近):

// 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); }

#### 依赖关系

  • 依赖 SCSAI-client.jssendAML() 方法(已存在,需修改超时处理)
  • 依赖 aml-builder.jsbuildAML() 方法(已存在)

1.3.5 P1-5.5: 前端create_post规则执行可靠性

#### 需求概述

前端创建成功后触发的 create_post 规则执行结果需能被前端感知,不允许 fire-and-forget 导致规则执行丢失。

#### 当前问题分析

  1. create_post 是 fire-and-forget:rule-engine.js createItem() 第2566-2570行,create_post 执行结果仅记录在 allResults.create_post 中,但前端通过 /SCSAI-api/ApplyItem 直接提交时,不经过 createItem()
  2. 前端直接提交不触发 create_post:前端通过 /SCSAI-api/ApplyItem 代理提交 AML,绕过了后端规则引擎的 create_post 执行
  3. 无 create_post 结果通知机制:即使后端执行了 create_post,前端也无法获取结果

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server.js | 修改 | 新增 /api/rule-engine/execute-create-post 端点 |

| server/core/rule-engine.js | 修改 | 新增 executeCreatePost() 方法(带重试和超时) |

| src/composables/useCapabilityCreate.js | 修改 | 创建成功后调用 create_post 端点 |

#### 核心类/函数设计

1. rule-engine.js — 新增 executeCreatePost() 方法

/**
 * ★ 执行 create_post 规则(带重试和超时)
 * 供前端创建成功后调用,替代 fire-and-forget 模式
 * 
 * @param {Object} context - { item_type, item_id, properties, relationships }
 * @param {Object} options - { maxRetries: 2, timeout: 30000 }
 * @returns {Object} { success, rule_results, retry_count, error }
 */
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;

1.3.6 P2-5.6: 前后端创建逻辑同步维护

#### 需求概述

后端 aml-builder.js 与前端 AmlBuilder.js、后端 unified-create.js 与前端 useCapabilityCreate.js 的逻辑差异需通过自动化测试检测,后端镜像代码需标注前端对应文件和同步状态。

#### 当前问题分析

  1. aml-builder.js 与 AmlBuilder.js 已有差异:后端 aml-builder.js 无 _formatValue() 方法(前端有 list 值匹配、日期格式化等),esc() 方法实现略有不同
  2. unified-create.js 与 useCapabilityCreate.js 差异更大:unified-create.js 是简化版7步流程,useCapabilityCreate.js 是完整10步流程(含 AI 纠错闭环)
  3. 无自动化一致性测试:CI 中无前后端输出对比测试

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server/utils/aml-builder.js | 修改 | 添加文件头 @mirror-of 标注 |

| server/routes/unified-create.js | 修改 | 添加文件头 @mirror-of 标注 |

| tests/aml-builder-consistency.test.js | 新增 | 前后端 AmlBuilder 输出一致性测试 |

| tests/create-flow-consistency.test.js | 新增 | 前后端创建流程逻辑一致性测试 |

#### 核心类/函数设计

1. 文件头 @mirror-of 标注

server/utils/aml-builder.js 文件头部添加:

/**
 * 服务端 AML 构建器 - 将标准 JSON 格式转换为 SCSAI AML
 * 
 * @mirror-of src/utils/AmlBuilder.js
 * @last-sync 2026-06-25
 * @sync-status drifted
 * 
 * ⚠️ 此文件逻辑必须与前端 src/utils/AmlBuilder.js 保持完全一致
 * 前端逻辑经过反复测试(十几种复杂业务对象),后端不得自创逻辑
 * 
 * 已知差异(drifted 项):
 * 1. 前端有 _formatValue() 方法(list值匹配、日期格式化),后端无
 * 2. 前端 esc() 不转义单引号,后端转义
 * 3. 前端支持 default_permission 注入,后端支持
 * 4. 前端 buildRelationshipElement 支持 isSameType 分支,后端无
 */

server/routes/unified-create.js 文件头部添加:

/**
 * unified-create.js — 服务端统一对象创建 API
 * 
 * @mirror-of src/composables/useCapabilityCreate.js
 * @last-sync 2026-06-25
 * @sync-status drifted
 * 
 * 镜像 src/composables/useCapabilityCreate.js 的创建流程
 * 
 * 已知差异(drifted 项):
 * 1. 前端10步完整流程(含AI纠错3次重试),后端7步简化流程
 * 2. 前端有 _dualDbDedupCheck 双库查重,后端无
 * 3. 前端有 precreateNestedItem 预创建,后端有简化版
 * 4. 前端有权限重试(World权限),后端有
 * 5. 前端有去重重试(追加后缀),后端有
 */

2. 自动化一致性测试

// tests/aml-builder-consistency.test.js
/**
 * 前后端 AmlBuilder 输出一致性测试
 * 
 * 目的:检测后端 aml-builder.js 与前端 AmlBuilder.js 的输出差异
 * 在 CI 中运行,差异时构建失败
 */

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('<AML>')
      expect(serverAml).toContain('</AML>')
      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('<Relationships>')
        expect(serverAml).toContain('</Relationships>')
        for (const rel of data.relationships) {
          expect(serverAml).toContain(`type="${rel.relationship_type}"`)
        }
      }
    })
  })
})

#### 依赖关系

  • 依赖 CI/CD 流水线(需配置测试步骤)
  • 依赖 aml-builder.jsAmlBuilder.js 的输出格式(已存在)

2. 接口设计

2.1 总体设计

SCSAI 回写优化涉及的接口变更遵循以下原则:

  1. 前端直接提交模式不变/SCSAI-api/ApplyItem 代理保持不变
  2. 新增 create_post 端点:前端创建成功后主动调用
  3. 后端内部接口增强:重试、回滚、格式转换为内部逻辑,不新增外部 API

2.2 接口清单

2.2.1 新增接口

| 接口 | 方法 | 路径 | 说明 |

|------|------|------|------|

| create_post 执行 | POST | /api/rule-engine/execute-create-post | 前端创建成功后调用,执行 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
  }
}

2.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 设计目标

  1. generate 输出标准化:定义 GeneratedContent 接口,确保 generate 输出可被前端消费
  2. 关系格式统一:定义 AmlBuilderRelation 接口,统一关系数据格式
  3. create_post 结果持久化:新增 create_post_results 表,支持前端断线后补发
  4. 重试策略配置化:重试策略参数可配置,不硬编码

4.2 模型实现

4.2.1 GeneratedContent 接口

// generate 能力输出标准化格式
interface GeneratedContent {
  // 必填
  item_type: string;                          // SCSAI ItemType 名称
  properties: Record<string, any>;            // 普通属性键值对
  
  // 可选
  item_properties?: Record<string, {          // Item 类型属性
    action: 'add' | 'get';
    item_type: string;
    properties?: Record<string, any>;
    id?: string;                              // action='get' 时必填
    keyed_name?: string;                      // action='get' 时推荐
  }>;
  
  relationships?: AmlBuilderRelation[];       // 关系定义
  
  // 内部标记
  _unparseable?: boolean;                     // 原始内容无法解析
  _source?: 'rule_engine' | 'llm_router';    // 生成来源
}

4.2.2 AmlBuilderRelation 接口

// AmlBuilder 标准关系格式(转换后)
interface AmlBuilderRelation {
  relationship_type: string;                  // SCSAI RelationshipType 名称
  related_item_type: string;                  // 关联对象 ItemType
  related_items: AmlBuilderRelatedItem[];     // 关联对象列表
}

interface AmlBuilderRelatedItem {
  properties?: Record<string, any>;           // 子对象属性
  item_properties?: Record<string, any>;      // 子对象 Item 类型属性
  related_id?: string | object;               // 引用 ID
  relItemProps?: Record<string, any>;         // 关系级别附加属性
  id?: string;                                // 已有对象 ID
  item_number?: string;                       // 已有对象编号
}

4.2.3 DiscoveryRelation 接口(转换前)

// discoverRelations() 返回格式(转换前)
interface DiscoveryRelation {
  parent_index: number;                       // 源对象在 batch_items 中的索引
  child_index: number;                        // 目标对象在 batch_items 中的索引
  relation_type: string;                      // SCSAI RelationshipType 名称
  properties?: Record<string, any>;           // 关系级别属性
  field_hint?: string;                        // 关系发现来源提示
  parent_ref?: string;                        // 父对象引用
}

4.2.4 create_post_results 表

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;

4.2.5 重试策略配置

# config.yaml 新增配置段
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

4.2.6 回滚审计日志格式

// 审计日志记录格式
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;
}
← 返回案例列表
分享:
🤖 Try Now →
🤖
🎁