This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-MVP/activities/quote-intake/src/index.ts
T
JCAI Main AgentandClaude Opus 5 c6cdb4c310 M5(完成): @activity-sdk 与首个活动包 quote-intake
@activity-sdk(§8.2 / ADR-004):
- 基座六件接口(契约 / 交互 / 记忆 / 数据 / 工具 / LLM)—— 活动看到的全部世界
- defineActivity() 的 ABI;本地开发宿主(mock 工具表 + 模拟工位 + 录制夹具),
  宿主自身就守三规矩:裸调用拒发、视图含脚本拒推、监听无时限拒登记

activities/quote-intake —— 统一报价 A1 · RFQ 需求分析(flow):
- 契约七件 / 步序声明五步(LLM 抽取 → 条件跳步补全 → 查历史价 → DMN 打标 → 验收放行)
- 三分类规则 P-03 落 DMN 决策表(C 优先于 A,命中行留痕)
- 台账声明两对象、监听时限必配、SLA 落 Harness

depcruise 实测拦下活动包越界 import(activities → apps 双向隔离),
测试遂按边界拆分:活动侧只用 @sdk/@contracts/@sfeel

358 个测试通过,门禁五道全绿

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-27 02:09:43 +08:00

64 lines
2.4 KiB
TypeScript

/**
* 统一报价 A1 · RFQ 需求分析 —— 活动包代码入口(ADR-004:默认导出 defineActivity 的返回值)。
*
* 这是 **flow** 活动:下一步由步序声明给定,引擎照谱推进;
* 本文件承担声明装不下的那部分 —— 视图流控与纯工具(打标)。
* **零环境能力**:一切能力经基座六件注入。
*/
import { defineActivity } from '@sdk';
import type { ActivityBase, ActivityOutcome } from '@sdk';
const INTAKE = 'quote_intake.record';
const TYPE_TABLE = 'quote_intake.type_table';
export default defineActivity({
name: 'quote-intake',
paradigm: 'flow',
async run(base: ActivityBase): Promise<ActivityOutcome> {
const records = await base.ledger.read(INTAKE, { customer_code: String(base.contract.data['customerCode'] ?? '') });
const typed = await base.ledger.read(TYPE_TABLE);
// 视图流控:按局面推结构化块 —— 内容归活动,容器归系统
await base.interaction.pushView([
{
t: 'kv',
title: '受理概览',
items: [
{ k: '客户', v: String(base.contract.data['customerCode'] ?? '') },
{ k: '已归一', v: `${records.length} 条` },
{ k: '已打标', v: `${typed.length} 条` },
],
},
{
t: 'table',
title: '统一受理记录',
cols: ['PN', '厂牌', 'EAU', 'Type'],
rows: records.map((r) => [
String(r['pn'] ?? ''),
String(r['brand'] ?? ''),
String(r['eau'] ?? ''),
String(r['type_tag'] ?? '未打标'),
]),
},
]);
const untyped = records.filter((r) => r['type_tag'] === undefined || r['type_tag'] === null);
if (untyped.length > 0) {
await base.interaction.say(`还有 ${untyped.length} 条未打标,继续按三分类规则 P-03 判定。`, 'conclusion');
return { kind: 'await', reason: '等待打标步完成' };
}
await base.interaction.say(
`${records.length} 条已全部打标:` +
`A ${count(records, 'A')} 条直通定价,B ${count(records, 'B')} 条转寻源,C ${count(records, 'C')} 条转选型。`,
'criterion',
);
return { kind: 'submit', deliverable: 'intake-record', rationale: '逐条已打标,必交付物齐备' };
},
});
function count(rows: ReadonlyArray<Record<string, unknown>>, tag: string): number {
return rows.filter((r) => r['type_tag'] === tag).length;
}