用户强调补进规范(§9.3 整节重立,三条硬规矩):
① **关键表单逐张配 HTML 报告模版** —— 模版是活动自己持有的资产
(assets.viewTemplates,随包版本冻结),不是系统临场生成;执行与验收都要配,
人办工单看到的是已排好版的报告(AI 拟值已填、basedOn 的过程成果嵌在正文里),
不是一堆裸字段;
② **从待处理工单到工作成果报告是同一张模版**:在办 = 报告 + 指导/判据;
办结后指导与判据卸下,只剩报告原样进「工作成果」归档。不许两张模版 ——
迟早长歪,人回头对不上账(语义出处:PRD ViewRegion 头注);
③ 一个局面一张模版,不在一张模版里堆条件分支。
同步三处:§4.2 assets 区硬规矩栏、§9.2 执行事项行、§11.2 自测清单
(关键表单逐张有模版、执行/验收都配、待处理与成果同模版)。
纯文档改动,五闸绿。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
450 lines
34 KiB
Markdown
450 lines
34 KiB
Markdown
# 活动开发规范
|
||
|
||
> 2026-07-30 定稿。给 **AI 开发活动时查阅**的规范:开发一个活动要写什么、能用什么、
|
||
> 什么会被拒、跑起来是什么样。基于代码现状如实记述 —— 每条「已就位」的断言都带
|
||
> `file:line`;产品要求但现状还不满足的,标 **⚠️待实现** 并汇总在[第 12 章](#12-待实现清单)。
|
||
>
|
||
> 相关文档:`docs/编码规范.md` §8(活动包平台契约)、各模块 `CONTRACT.md`(模块级不变量)、
|
||
> `docs/产品设计.md`(三面一体)。本文与它们冲突时,**以代码现状为准**,并把冲突记进第 12 章。
|
||
|
||
---
|
||
|
||
## 1. 读者与用法
|
||
|
||
- **读者**:开发活动(`activities/<id>/`)的 AI 或人。你不需要读 apps/api 的源码 ——
|
||
活动的全部世界是 SDK 基座六件(第 3 章)与清单六区(第 4 章)。
|
||
- **用法**:开发前通读 2–4 章(边界与结构);写案例模型看第 8 章;写交互看第 9 章;
|
||
提交前对着第 11 章自测清单过一遍。
|
||
- **标注约定**:✅ = 现状已就位(带 file:line);⚠️ = 产品已定、代码未实现(汇总第 12 章)。
|
||
|
||
## 2. 总纲与铁律
|
||
|
||
**活动 = 任务的定义。** 编排引用一个活动,运行时就长出一个任务(恰一名负责人)。
|
||
活动在系统外开发、经安装通道入库(`registry.install`),版本恒新增、同版重装 no-op、
|
||
在途任务按启动时版本跑完(`apps/api/src/modules/activity-registry/CONTRACT.md`)。
|
||
|
||
**统一建模**(语义权威:`JCAI_PRD/workbench/src/app/(proto)/_data/model.ts` 头注,2026-07-25):
|
||
|
||
> 一个活动**完全由 AI Loop 自主完成**;Loop 按**阶段**推进(**阶段可重复**),阶段产出
|
||
> **过程成果**(Deliverable:草稿 → 待验收 → 已放行 / 已驳回)。一个任务可能有一或多个
|
||
> 过程成果,**每个成果都要调人类工具放行**。
|
||
|
||
活动不再分范式(2026-07-29 用户定):业务模型一律**案例模型**(CMMN + 说明书 + DMN),
|
||
业务分类是活动库唯一的一维(`packages/contracts/src/manifest.ts:20-23`)。
|
||
|
||
### 四条铁律
|
||
|
||
| # | 铁律 | 机制依据 |
|
||
|---|---|---|
|
||
| ① | **只读世界**:活动只能读台账、调读类工具、问模型、加工表单 | 基座六件即全部能力(§3);环境能力静态审拒装(§11) |
|
||
| ② | **写只有草稿与意向**:写台账 = 草稿层(不产生数据事件);外部动作(发邮件等)= 登记动作意向(入参冻结 + 幂等键),**登记那一刻世界上什么都没变** | `ledger.write` 只有草稿写(`base.ts:55-60`);写类工具自动转意向(`apps/api/src/app/tool-bus.ts:206-209`) |
|
||
| ③ | **终局唯放行**:任务完成的唯一出口是**人类验收通过** —— 那一刻才草稿合并、意向执行、输出口发射;验收不过 = 不完成 | `raiseAcceptance` 是唯一提交口(`turn-runner.ts:207-242`);`land()` 六步(§7.3) |
|
||
| ④ | **活动不自转**:`run(base)` 是**一个回合**的推进,由引擎驱动;不自设步数、不产生终局迁移;预算与时限由 Harness 在环外强制 | `define.ts:22-25`;迁移拓扑表把 Completed/Terminated 设为死胡同(`loop-engine/internal/domain/lifecycle.ts:29-38`) |
|
||
|
||
## 3. 能力边界:基座六件就是全部世界
|
||
|
||
`packages/activity-sdk/src/base.ts`(权威,100 行,逐行有注释)。
|
||
「基座即笼子:**内容归活动,笼子归系统**。活动代码无环境能力,一切能力经调用契约注入,
|
||
越权在结构上不可能。」
|
||
|
||
| 件 | 句柄 | API | 要点 |
|
||
|---|---|---|---|
|
||
| ① 契约 | `base.contract` | `activityId / version / data / message / outputPorts{mandatory,selective} / prohibitions / discretion` | **只读**,启动前冻结;`message` 是委托消息,不得携带扩大授权的效力 |
|
||
| ② 交互 | `base.interaction` | `pushView(blocks)` / `pushHtml(html)` / `say(text, tag?)` / `issueWorkOrder(order)` / `issueCard(card)` | 视图模版过净化门(含脚本即拒);工单两模式、对话卡三族(§9) |
|
||
| ③ 记忆 | `base.memory` | `window(size)` / `recall({layers?, limit?})` | 任务记忆窗口是活动侧唯一记忆接口;活动记忆渐进披露,语义引擎不读 |
|
||
| ④ 台账 | `base.ledger` | `read(address, where?)` / `write(address, key, patch)` | 读 = 任务视图(正式版 + 本任务草稿层,读己之写);写 = **只有草稿写**,不产生数据事件 |
|
||
| ⑤ 工具 | `base.tools` | `invoke(tool, input)` / `intend(tool, input, label)` / `listen({eventType, keyFilter, deadline})` | AI 对世界只有**触达**(动作)与**等待**(监听)两种姿势;**监听时限必配** |
|
||
| ⑥ 模型 | `base.llm` | `generate({model, prompt, system?, outputSchema?})` | `model` 是**自定义模型名**(逻辑名),不触厂商与密钥;给 outputSchema 即要求结构化出参并校验 |
|
||
|
||
**outcome 三态**(`define.ts:10-16`):`{kind:'submit', deliverable, rationale}`(提交放行)/
|
||
`{kind:'await', reason}`(等人或等事件)/ `{kind:'blocked', reason}`(推不动,如实报)。
|
||
|
||
**禁区**(安装静态审黑名单,`activity-registry/internal/domain/static-audit.ts:144-159`):
|
||
`process.env`、`require(`、`node:fs`、`node:net`、`node:child_process`、`globalThis.fetch`、
|
||
`fetch(`、`XMLHttpRequest`、`eval(`、`new Function(` —— 产物文本扫描命中即拒装
|
||
(`environment-capability` / `dynamic-eval`)。另有 depcruise 双向隔离(activities 不得
|
||
import apps,反向也不得)与 eslint 禁 `fetch`/`process` 全局。
|
||
|
||
**SDK 三道守卫**(本地 mock 宿主即可测出,`packages/activity-sdk/src/host.ts`):
|
||
|
||
1. 视图模版**无脚本**(`host.ts:64`):`<script` 即抛 —— 容器无脚本执行权;
|
||
2. **裸调用拒发**(`host.ts:71`):`issueWorkOrder` 的 `basis` 为空即抛 —— 调用必附上下文;
|
||
3. **监听时限必配**(`host.ts:116`):`deadline` 空串即抛 —— 无死等。
|
||
|
||
> 如实说明:SDK 面是**本地开发面**(线上 `.js` 产物只当数据存,不 import 不 eval,
|
||
> ADR-004)。线上真正的闸是工具总线 + `ToolInvoker` 白名单 + `card-rules` 机检 ——
|
||
> 与 SDK 守卫同一套规矩,两头都得过。
|
||
|
||
## 4. 包结构与清单六区
|
||
|
||
### 4.1 目录树(照 `activities/quote-intake`,六包结构一致)
|
||
|
||
```
|
||
activities/<activity-id>/
|
||
├── package.json # @activities/<id>,private,type:module,exports "." → ./src/index.ts
|
||
├── src/
|
||
│ ├── index.ts # 代码入口:export default defineActivity({...})
|
||
│ ├── manifest.ts # 清单六区 + 导出 DMN 常量(供测试直接求值)
|
||
│ ├── case-model.ts # 导出 CMMN_XML 与 NARRATIVE(双表示,同版本发布)
|
||
│ ├── skills.ts # 导出 Skill[](id 用 skillIdSchema.parse 品牌化)
|
||
│ └── views/report.ts # HTML 视图模版常量(viewShell/kvCard/tableCard/noteCard 拼装)
|
||
├── __tests__/activity.test.ts # 自测四段(§11.2)
|
||
└── dist/{manifest.json,index.js} # 构建产物(⚠️ 现为过期占位,债 M5-3)
|
||
```
|
||
|
||
**入口写法**(`activities/quote-intake/src/index.ts`):只 import `@sdk`;台账地址提成
|
||
顶部常量;`export default defineActivity({ name, async run(base) {...} })`;典型 run 体 =
|
||
`ledger.read` 取数 → `pushView` 推块 → 分支 `say` + `return {kind:'await'}` 或
|
||
`return {kind:'submit', deliverable, rationale}`;本地纯函数放文件末尾。
|
||
⚠️ `defineActivity` 目前仍要求 `paradigm` 参数 —— 陈旧残留,统一填 `'loop'`,待清理(第 12 章⑥)。
|
||
|
||
### 4.2 清单六区(`packages/contracts/src/manifest.ts:163-172`)
|
||
|
||
| 区 | 内容 | 硬规矩 |
|
||
|---|---|---|
|
||
| ① identity | `activityId / name / packageVersion / category / sdkVersion / checksum / signature?` | **无 paradigm**;`sdkVersion` 与 SDK 精确相等否则拒装;**改了包内容必须升 packageVersion**(同版重装 no-op,改了不升版 = 静默丢失) |
|
||
| ② contract | 七件契约(§4.3) | |
|
||
| ③ declarations | `models[] / tools[] / ledger[] / listeners[] / harness` | 运行时**只读这里**的工具与模型声明;监听时限必配否则 `dangling-listener` 拒装 |
|
||
| ④ assets | `businessModel{cmmnXml, narrative, dmn} / skills / viewTemplates / cardSchemas / distillHooks` | CMMN 与说明书**同版本发布不允许分叉**(空任一即拒装);**关键表单逐张配 HTML 报告模版**(执行与验收都要,待处理与成果同模版,§9.3) |
|
||
| ⑤ samples | `{name, input, message?, expect:{ports[], payloadShape?}}[]` | **至少 1 条**;首装无历史时以静态审 + 样例集替代回放审 |
|
||
| ⑥ entry | `{file, checksum}` | 指 .js 产物 |
|
||
|
||
`declarations.harness`(`manifest.ts:33-45`):`memoryWindow`(默认 20)/ `stallAfter`
|
||
(ISO-8601 时长,默认 PT30M —— 停滞熔断的判据)/ `nudge[]` / `retry`。
|
||
|
||
**台账声明**(`packages/contracts/src/ledger.ts:59-76`):`{domain, object, label?, fields[]}`,
|
||
字段 `{name, type, label?, primaryKey, masked}`;标识符**只准小写字母开头、字母数字下划线**。
|
||
域的归属纪律:活动自持域用活动名;**跨活动的事实只有两条路 —— 输出口载荷,或共享域
|
||
`shared`**。物化三律(`ledger/internal/domain/schema-merge.ts`):最大化合并(同名字段
|
||
masked/primaryKey 取或)、最宽类型(integer→number→text 等加宽链,json 与标量互斥即冲突)、
|
||
只增不破坏(只产出建表/加列/加宽,**新增主键即冲突** → `ledger-type-conflict` 拒装)。
|
||
|
||
### 4.3 契约七件(`packages/contracts/src/contract7.ts:129-140`)
|
||
|
||
| 件 | 写什么 | 硬规矩 |
|
||
|---|---|---|
|
||
| ① input | `data`(JSON Schema)+ `messageAllowed / messageHint` | `data` 即**启动 Schema**(§5);message 不得携带扩大授权的效力 |
|
||
| ② output | `mandatory[]` + `selective[]` | mandatory 每次放行必发(AND);selective 每组**至少 2 口、恰发一口**,选口即分支结论(§7.4) |
|
||
| ③ requiredDeliverables | `{key, label, schema?}[]` | 缺一不得提交放行;验收工单「成果清单」的来源 |
|
||
| ④ acceptanceCriteria | `{key, label, level: L1-L5, predicate?, modelRef?}[]` | **L1/L2 必带 predicate(S-FEEL)或 modelRef**,否则 `acceptance-level-misreported` 拒装 —— 验收等级不得谎报 |
|
||
| ⑤ prohibitions | `{key, statement}[]` | 短而硬;负面清单优于正面枚举 |
|
||
| ⑥ authorization | `discretion[] / preauthorizedActions[] / humanCallBudget` | humanCallBudget = 调人预算(环外强制);⚠️ preauthorizedActions 现为死代码,**暂不使用**(第 12 章⑨) |
|
||
| ⑦ resources | `tools[] / skills[] / models[] / roles[] / budget{steps, tokens, activeMinutes, humanCalls}` | 预算四项由基座环外强制,**不得由活动代码自查自守**;models 是自定义模型名 |
|
||
|
||
> 坑位:`contract.resources.tools` 与 `declarations.tools` 是**双份声明**,运行时只读
|
||
> declarations(`engines.ts:443-447`)——两处要写一致,安装审只查 declarations。
|
||
> **出口字段要有台账列着落**:出口载荷从放行落地的事实推导(§7.5),声明一个没有任何
|
||
> 台账列承载的标量出口字段,下游网关会因取不到值而死路(`route-dead-end`)。
|
||
|
||
## 5. 启动与补全(活动输入无强制类型)
|
||
|
||
### 5.1 编排层确认结论(✅ 全部成立)
|
||
|
||
用户语义「活动间流转数据没有强制类型、活动输入可接受任何类型」在编排层**已成立**:
|
||
|
||
- **起单宽进**:`process.start` 不按调用接口拒缺项(`process-service.ts:270-315`,
|
||
注释写死「门口不设缺项闸,收敛点在活动」;`start-guard.test.ts:109-115` 守着)。
|
||
- **数据按字段名流动**:`flatScope` 铺平作用域(起单入参垫底 → 各输出口字段依次盖上 →
|
||
刚发射的载荷最后盖,后发生的赢,零类型检查,`routing.ts:32-41`);节点入参 =
|
||
**整个铺平作用域**,下游按自己的契约取,无契约裁剪(`self-hosted-runtime.ts:256`)。
|
||
- **闭合校验不查字段**:只剩结构闭合(dangling-node / multi-default-edge)与活动装载
|
||
(unknown-activity)四条(`closure.ts:33-81`)。字段够不够是活动自己的事。
|
||
|
||
### 5.2 机械门:required 缺项 → 补全卡(✅)
|
||
|
||
任务启动时(`task-service.ts:88-134`):
|
||
|
||
1. `missingRequired(inputSchema, data)`:**只查启动 Schema 顶层 `required`**,
|
||
「缺」= `undefined | null | ''`(`0`/`false`/`[]` 都算有);标签取
|
||
`properties[key].title ?? label ?? key`(给人看「客户码」不是 `customerCode`)。
|
||
**不做类型校验** —— 这是机械门的全部(语义级判断归 Agent,§5.4)。
|
||
2. 缺项**照建单**,任务落 `awaiting-human`,引擎照常装载;
|
||
3. 发**补全卡**(elicitation 对话卡,`engines.ts:715-736`):字段 = 缺的 `{key, label}`;
|
||
**不占活动调人预算**(补全是受理的事,不是活动的打扰);挂 P2D 人事件监听。
|
||
|
||
**答卡合并**(`task-service.ts:262-299`)四条硬规矩:
|
||
|
||
- 只收**当前还缺的键**(白名单),契约外的键、已填过的键静默丢弃;
|
||
- 只收**非空字符串** —— 补全卡回传一律按字符串,数字/布尔会被静默丢弃(设计表单时注意);
|
||
- 补齐(或「没法填」)→ 置 `running` 并立即 tick;答一半 → 只再派一张**只含剩余项**的卡,引擎一步不走;
|
||
- **「没法填」**:回传 `reason` 非空即放行**带缺口开跑** —— 收敛责任从此归活动内部。
|
||
两条路必走其一(答值或给理由),空答被 `card-rules` 拒收。
|
||
|
||
### 5.3 前端呈现(✅)
|
||
|
||
补全卡渲染在对话区(`desk-task/internal/chat-bubbles.tsx:214-251`,题头「请你补全 · N 项」);
|
||
「没法填?告诉 AI 原因」是显式按钮;**有未答的补全/决策卡时输入框锁定 ——「先办卡,再说话」**。
|
||
|
||
### 5.4 Agent 案内随时可补(✅ 机制在,活动要自己用)
|
||
|
||
开跑后发现「字段在但内容不像话」(机械门查不出的语义级不满足),活动应:
|
||
|
||
- **小信息**:`interaction.issueCard({k:'elicitation', ask, fields, basis})` —— 对话区就地要几个小信息;
|
||
- **要人干活**:`interaction.issueWorkOrder({mode:'execution', ...})` —— 执行工单
|
||
(PRD 统一建模里「补全」并入执行事项的那条路,§9.2)。
|
||
|
||
> ⚠️ 边界隐患(第 12 章④):走过「没法填」的任务,若后续验收被驳回(runtime 回到
|
||
> awaiting-human),到达的 human 事件会被受理分支误吞(它不看 cardId)。在修复落地前,
|
||
> 活动拿到「带缺口开跑」的单,应尽早在环内把缺项补齐或显式置值,别让缺口一路挂到验收。
|
||
|
||
## 6. 工具调用:完全声明式
|
||
|
||
### 6.1 设计口径
|
||
|
||
设计活动时**只考虑需要什么工具**:工具名 + 输入 + 输出(+ 给人看的指导段⚠️)。
|
||
不关心系统里有没有实现 —— 声明的工具进 `declarations.tools`(只有名,版本字段现无消费者)。
|
||
|
||
已注册工具的契约形状(`toolbox/internal/domain/tool-contract.ts:13-39`):
|
||
`name / displayName(中文名)/ version / description(**面向 AI** 的适用说明,≥8 字)/
|
||
input / output(JSON Schema)/ effect: 'read'|'write' / errors[] / idempotent(写类必须 true)/
|
||
timeoutMs / retries / connectionKeys[](只声明键,值在治理台)/ capabilities.validate? /
|
||
pure? / ownerActivityId?`。
|
||
⚠️ 增加 `guide` 字段(**面向人**的作业指导段):工具未命中转人时,执行卡上的作业指导书
|
||
从这里来 —— 声明式、可审、不临场生成(第 12 章②)。
|
||
|
||
### 6.2 运行时五分叉
|
||
|
||
| 情形 | 目标行为 | 现状 |
|
||
|---|---|---|
|
||
| 命中 · 读类 | 直接执行 | ✅ `tool-invoker.ts` 执行(超时 race;入参不合 schema 回 `{ok:false, code:'input-invalid'}` 不抛) |
|
||
| 命中 · 写类 | **登记动作意向**,放行后由系统执行(附幂等键) | ✅ 登记(`app/tool-bus.ts:206-209`「现在世界上什么都没变」);⚠️ 但落地链路断着 —— 意向永停 drafted,放行时不执行(§7.3、第 12 章①) |
|
||
| **未注册**(系统里没有) | **生成人类执行卡 + 作业指导书**:卡面 = 工具 guide 段 + 本次输入数据 + 按**输出 Schema** 生成的回填表单;人填回的表单值**就是这次工具调用的出参** | ⚠️ 现状抛 `ToolNotFoundError`(`tool-invoker.ts:43`),单拍路径 catch 后记忆留痕「行动被拒」回合不崩 —— **转人待实现**(第 12 章②) |
|
||
| 已注册 · **未白名单授权** | **报错**(越权是治理配置问题,不静默转人) | ✅ `ToolNotAllowedError`(`tool-invoker.ts:45-51`,文案「越权在结构上不可能」) |
|
||
| 已停用 | 失败路径(活动按失败处理) | ✅ `ToolDisabledError` |
|
||
|
||
配套现状(如实记):
|
||
|
||
- 工具总线 = **声明 × 整张目录**,编译时未命中收进 `missing[]` 如实回报、写观察进任务
|
||
记忆、**不阻断**(`compileToolBus`,`tool-bus.ts:184-213`;`engines.ts:449-455`)——
|
||
这半边已经是「声明式」的形状,缺的是运行时把 miss 转人;
|
||
- ⚠️ 安装静态审现把 `tool-contract-missing` 判 **error 拒装**(`static-audit.ts:108-111`)——
|
||
与「声明式工具、系统没有就转人」正面冲突,需降级为 warn(第 12 章②);
|
||
- ⚠️ 白名单授权现状只有种子常量表(`seed-mvp.ts:109-117`),无治理台授权口(第 12 章⑤);
|
||
- 缺省单拍 Agent 路径**不经工具总线**(工具名是自由字符串,唯一闸门是 ToolInvoker);
|
||
`JCAI_AGENT_OPEN_LOOP=1` 的开环路径才吃总线的声明×目录编译(第 12 章⑧)。
|
||
|
||
### 6.3 系统自带的工具(不用声明/只需声明模型)
|
||
|
||
- **human.ask 恒在**(`tool-bus.ts:52-75`):调人执行卡,零声明即得,不进目录不进白名单 ——
|
||
Harness 给的能力;每发一卡计 `humanCalls: 1`。
|
||
- **LLM 五件**(`tool-bus.ts:84-159`):`llm.extract / classify / summarize / judge / rewrite`,
|
||
仅当 `declarations.models` 非空才上总线(没声明模型却问模型是越权,用量无从归因);
|
||
提示词归系统,活动不写也改不了;`llm.judge` 只作参考,**不代替人的放行裁决**。
|
||
|
||
## 7. 写动作与验收(所有写必须经人类验收)
|
||
|
||
### 7.1 写三分(✅,`ledger/contract.ts:85-114`)
|
||
|
||
| 写法 | 谁用 | 事件 |
|
||
|---|---|---|
|
||
| `systemWrite` | 系统(受理落件等) | 即时抛数据事件 |
|
||
| `draftWrite` | **活动过程写的唯一形态**(`base.ledger.write`) | **不抛事件**;写时记基线值 |
|
||
| `releaseWrite` | 放行那一刻(系统调) | 先查冲突(草稿基线 ≠ 当前正式版即 `DraftConflictError`)→ 事务合并 → 读回落地行 → **此刻才抛事件** → 清草稿 |
|
||
|
||
### 7.2 动作意向(✅ 登记,⚠️ 执行断链)
|
||
|
||
写类工具调用(`base.tools.intend`,或 invoke 写类工具被自动转意向)→ 登记
|
||
`sys.action_intent`:入参冻结 + 幂等键 + 状态机 `drafted → prechecked → landed/failed/abandoned`。
|
||
**登记那一刻世界上什么都没变** —— 这是铁律②的机制承载。
|
||
|
||
### 7.3 放行落地六步(`release-service.ts:144-244`)
|
||
|
||
① `releaseWrite` 草稿合并(原子、过冲突检查)→ ② 记痕「放行写合并 N 行」→
|
||
③ **只取 `prechecked` 的意向**逐个执行(附幂等键 `${taskId}:${intentId}`,已成动作不回滚)→
|
||
④ 裁定(有失败即 blocked)→ ⑤ 受阻:发落地失败卡、口一个不发、任务落 `blocked` 终态 →
|
||
⑥ 收敛:恰选一口校验(兜底)→ 发射输出口。
|
||
|
||
> ⚠️ **重大现状洞(第 12 章①)**:把意向从 `drafted` 推到 `prechecked` 的
|
||
> `submitForRelease`(预检 + 已验证动作清单上卡)**没有任何调用方** —— 活路径
|
||
> (`turn-runner.raiseAcceptance`)绕过了它。后果:**写类动作登记后永不执行**、
|
||
> 验收卡的 `actions` 恒空、`criteria` 恒为硬编码单条 L5、幂等键登记/落地两套不一致。
|
||
> 实现工具调用逻辑时这是第一优先级。
|
||
|
||
### 7.4 验收工单与恰选一口(✅)
|
||
|
||
验收工单 schema(`card.ts:328-350`):`deliverable / criteria(L1-L5 逐条,pass 标记)/
|
||
actions?(通过即执行的动作清单)/ fields(报告可修订)/ portChoice?(选择组)/ passLabel?`。
|
||
回传(`card.ts:473-491`):`verdict: pass | redo | revise` + `values` + `comment?` + `selectedPorts`。
|
||
|
||
机检三规矩(`card-rules.ts:101-150`):驳回**必附意见**;**恰选一口**在 pass 时强制
|
||
(未选 / 选组外即拒收,主闸在回传收单,落地 `validateSelection` 兜底);工单只增不改。
|
||
`producesCompletion` 仅 `acceptance + pass` —— **验收通过是任务完成的唯一来源**。
|
||
|
||
选中的那一口**就是这单的分支结论**:放行注入 `facts.flat.outcome = 选中口名`
|
||
(`engines.ts:642-644`),下游异或网关的条件读的就是它(`outcome = "won"` 等)。
|
||
|
||
> ⚠️ 前端链两端断(第 12 章③):验收工单的 `fields` 前端不渲染、`values` 恒传 `{}`
|
||
> (人工修订报告落不下来);驳回 UI 只有 redo,引擎侧把 redo/revise 都塌成 reject。
|
||
|
||
### 7.5 出口载荷从落地事实推导(✅,契约设计的硬约束)
|
||
|
||
`outputPortsOf(manifest, landed)`(`engines.ts:535-574`):数组字段 ← `landed.rows` 整体;
|
||
标量字段 ← `landed.flat[field]`(驼峰/下划线两认);`landed.flat` 还会全量兜底铺进每个口。
|
||
**推论**:出口声明的标量字段必须有台账列承载(活动放行前写进草稿的行),否则下游取不到 ——
|
||
这是活动契约与台账声明之间必须对齐的一条线(历史事故:两枚去向旗没有列,G1 全流死路)。
|
||
|
||
## 8. 案例模型(CMMN + 说明书 + DMN)
|
||
|
||
### 8.1 CMMN 子集封顶(`loop-engine/internal/domain/cmmn-parse.ts`)
|
||
|
||
计划项 **8 型**:`humanTask / decisionTask / processTask / caseTask / stage / milestone /
|
||
timerEventListener / userEventListener`;结构标签 23 个(sentry/onPart/ifPart/planningTable/
|
||
discretionaryItem/itemControl/…)。**表外构件直接拒**(`assertSubset` 抛错,不静默忽略)——
|
||
子集封顶要能被机器证明。
|
||
|
||
| 记号 | 语义 | 纪律 |
|
||
|---|---|---|
|
||
| `manualActivationRule` | Enabled ≠ Active,要按一下才动;**Agent 发 `start` 来按** | **触达世界与关键交付的项必须标**(AM 4.1 原理一) |
|
||
| `repetitionRule` | 项可重复(阶段可多轮) | 撒网/追问这类常来回的事要标 |
|
||
| `planningTable / discretionaryItem` | 裁量项:Agent 临场规划入场 | 「看情况做」的事放这里 |
|
||
| `entryCriterion / exitCriterion` | sentry = onPart(听任务数据/项迁移)+ ifPart(S-FEEL) | 条件读任务数据字段 |
|
||
| `ifPart` 前缀 `semantic:` | **语义性前提**:引擎不机算,交 Agent 裁量 | 「回价是否可信」这类判断这样写 |
|
||
| `autoComplete` | 齐了即收(必做的都完成、没有进行中的)| 案计划级与阶段级都可标;齐了不问 Agent 直接提验收 |
|
||
| `timerEventListener` | 时限(ISO-8601 时长) | 催办/过半提醒 |
|
||
| `userEventListener` | 人事件(客户改需求等) | 外部变化的入口 |
|
||
|
||
**AI 不得产生终局迁移**:迁移拓扑表把 `Completed`/`Terminated` 设为死胡同,
|
||
`Available → Completed` 不在表内 —— Agent 想跳步会吃 `IllegalTransitionError`,
|
||
被回合循环捕获成「行动被拒」留痕,不崩(`turn-runner.ts:363-372`)。
|
||
|
||
### 8.2 说明书(narrative)
|
||
|
||
与 CMMN **同版本发布、不允许分叉**(空任一即拒装)。写法照 `activities/quote-intake/src/case-model.ts`:
|
||
`§0 开案`(什么事、成品是什么)→ `S1..Sn`(各阶段怎么办、判断口径、宁可空着不许臆造这类
|
||
纪律)→ `§Z 结案`(什么算办完、交出去什么)。说明书是 Agent 每一拍叙事面的第一段 ——
|
||
**写给 AI 执行看的,不是给人读的产品文案**;判断口径要写死(「吃不准降 B,不许臆断 A」)。
|
||
|
||
### 8.3 DMN(能算的不问 LLM)
|
||
|
||
`{drd[], tables[]}`;表 = `hitPolicy(默认 FIRST)/ inputs(name+expression)/ outputs / rules`。
|
||
单元格谓词 = S-FEEL:`'-'` 或空串任意匹配、`'"A","B"'` 析取、`'<90'` 比较、`'[0..100]'` 区间。
|
||
求值(`dmn.ts:33-76`):任一输入缺 → `incomplete`(带 missing);零命中 → `no-match`;
|
||
命中留痕 `DecisionTrace{inputs, hitRules, outputs}`。决策表在 `decisionTask` 上以
|
||
`decisionRef` 引用;**同输入必同结论、零 token** —— 规则能写死的判断一律进 DMN。
|
||
|
||
## 9. 工作台视图逻辑(PRD 为语义权威)
|
||
|
||
语义权威:`JCAI_PRD/workbench/src/app/(proto)/_data/model.ts`(统一建模)与
|
||
`_components/{ViewRegion,TaskChat}.tsx`;WOS 平移件:`apps/web/src/features/desk-task/internal/
|
||
{ViewRegion,TaskChat,chat-bubbles,work-order-report}.tsx`。卡族 schema 前后端同源:
|
||
`packages/contracts/src/card.ts`。
|
||
|
||
### 9.1 三栏分工(结构性定调)
|
||
|
||
| 栏 | 承载 | 铁律 |
|
||
|---|---|---|
|
||
| **视图区** | **全部结构化交互**:HTML 交互界面(表单读写 + 图形展示)+ 工作事项 + 过程成果 | 待处理**翻页不排队**(队列是活动内部的事);五页签:待处理/沟通记录/工作成果/任务详情/工作日志 |
|
||
| **对话区** | **只承载非结构化消息** + 决策选择题;系统事件 = 灰头像 | **气泡二元制**:纯文本气泡或完整表单卡,不允许中间态;补全/决策卡吐出期间 composer 锁定 ——「先办卡,再说话」 |
|
||
| **任务日志** | 全部任务记忆的结构化呈现 | 归侧四类(机器产出/人产出/人裁决/世界等待)= 度量口径,零埋点 |
|
||
|
||
**三类同级唤醒事件**:①对话输入 ②外部事件(数据/时间/控制)③页面交互(含工单回传)。
|
||
|
||
### 9.2 工作事项(WorkItem):人类工作清单
|
||
|
||
**AI 决定、只增不减**,只有两类(决策不是工作事项):
|
||
|
||
| 类 | 字段(PRD `model.ts:106-133` ↔ WOS `card.ts:279-350`) | 用途 |
|
||
|---|---|---|
|
||
| **执行** | `ask`(工作要求)/ `steps[]`(作业指导书分步要点)/ `standard?`(完成标准)/ `basedOn?`(报告正文取自哪份过程成果 —— 人要在数据上作判断,就得把数据摆在表单旁)/ `blocks?` / `fields[]`(回填表单,字段可带 **ai 拟值**)/ `submitLabel?` | 做 AI 的工作要求 —— **原「补全」并入此类**;回填报告用活动持有的 HTML 模版(§9.3);⚠️ 工具未命中转人也走这张卡(第 12 章②) |
|
||
| **验收** | `deliverable / criteria(L1-L5)/ actions?(待落地动作意向,读/写)/ portChoice? / passLabel?` | 收 AI 的成果:放行 / 驳回(必附意见) |
|
||
|
||
对话区另有三张卡:**决策**(选择题,recommend + 后果说明)、**fill 补全卡**(就地要几个
|
||
小信息;「没法填的理由」是另一条必走之路)、**rate 评价卡 —— 暂不做**(schema 在,不接)。
|
||
|
||
### 9.3 HTML 报告模版:活动持有,一单一模版贯穿始终
|
||
|
||
**活动的每一张关键表单,都必须有对应的 HTML 报告模版** —— 模版是**活动自己持有的资产**
|
||
(`assets.viewTemplates`,随包版本冻结),不是系统临场生成的。三条硬规矩:
|
||
|
||
1. **执行与验收都要配**:执行工单的回填报告、验收工单的成果报告,各有其模版 ——
|
||
人办工单时看到的不是一堆裸字段,是一份**已排好版的报告**(AI 拟值已填、可编辑,
|
||
数据摆在表单旁边,`basedOn` 引用的过程成果就嵌在报告正文里);
|
||
2. **从待处理工单到工作成果报告是同一张模版**:工单在办时,卡面 = 报告模版 +
|
||
作业指导 / 验收判据;办结后**指导与判据卸下,只剩报告** —— 它原样进「工作成果」
|
||
页签成为归档件。不许为「在办」与「归档」各做一张模版:两张迟早长歪,
|
||
人回头核对时对不上账。语义出处:PRD ViewRegion 头注「一张工单 → 卡组一
|
||
(HTML 报告 · AI 已填好、可编辑)+ 卡组二(作业指导 / 验收判据);办结后只剩
|
||
报告卡 —— 指导与判据是办理时的参照,不进归档件」;
|
||
3. **一个局面一张模版**(受理中/已分类/待放行……):局面变了换模版,
|
||
不在一张模版里堆条件分支。
|
||
|
||
推送两条路:`pushView(blocks)`(结构化块,11 型:callout/kv/badges/table/bars/donut/
|
||
mail/cards/waiting/files,`card.ts:103-201`)与 `pushHtml(html)`(HTML 模版)。
|
||
模版纪律(`activity-sdk/src/view.ts`):容器是 `sandbox=""` iframe,**无脚本执行权、
|
||
样式必须自带**;数据由基座推送时填(`data-slot` 取值、`data-rows` 逐行展开);
|
||
SDK 给了 `viewShell / kvCard / tableCard / noteCard / stagesCard` 拼装件,照
|
||
`activities/quote-intake/src/views/report.ts` 用。
|
||
|
||
### 9.4 运行时的卡怎么到人(✅ 链路)
|
||
|
||
活动 `issueWorkOrder`/`issueCard` → `card-rules` 机检(裸调用拒发/字段齐备)→ 调人预算
|
||
环外扣账 → 落 `sys.work_order` / `sys.chat_card` → 到达面两路:收件人是负责人 →
|
||
**直接长在任务对话流**;他人 → 系统消息要素。人答卡 → 校验(§7.4)→ human 事件 →
|
||
任务 `deliver` → 引擎下一拍收裁决。**在途验收先于一切**:有未裁决的验收卡时,
|
||
回合原地等,不重发卡、不问模型(`turn-runner.ts:274-287`)。
|
||
|
||
## 10. 记忆与技能
|
||
|
||
- **任务记忆**:只增不减;`memory.window(n)` 取最近轨迹(n = `declarations.harness.memoryWindow`);
|
||
终态封卷后不能再追加;蒸馏钩子(`distillHooks`:on terminal / on milestone + prompt)
|
||
在封卷后跑,失败不掀翻终态。
|
||
- **活动记忆**:跨任务沉淀,四层;`memory.recall({layers?, limit?})` 选摘;**语义引擎不读**
|
||
(它只进叙事面)。
|
||
- **技能**(`manifest.ts:117-131`):`{id, label, applicability, method, crystallized[],
|
||
knowledgeRefs[], version}`。**渐进披露**:常驻上下文只有目录(`id label:applicability`),
|
||
正文 Agent 说要才加载 —— 一次塞全部正文会把局面淹掉。**载方法不载权力**:技能里的
|
||
结晶片段调工具,仍过活动自己的白名单,技能不带来新授权。
|
||
|
||
## 11. 开发流程与自测
|
||
|
||
### 11.1 本地开发(mock host)
|
||
|
||
`createMockHost({contract, tools?, models?, ledger?, memory?})` → `{base, recording, run}`。
|
||
`recording` 十通道:`views / htmls / says / workOrders / cards / toolCalls / intents /
|
||
listeners / llmCalls / writes` —— 断言活动这一回合做了什么。工具未 mock 回
|
||
`{ok:false, code:'not-mocked'}`(活动要能按失败路径走);模型未 mock 直接抛。
|
||
|
||
### 11.2 自测四段(照 `activities/quote-intake/__tests__/activity.test.ts`)
|
||
|
||
1. **清单合规**:过 `activityManifestSchema`;监听时限已配;L1/L2 判据可机检;禁区非空 + 调人预算 > 0;
|
||
2. **案例模型与模版**:CMMN 双表示齐备;触达世界的项全标 manualActivationRule;末尾有收口验收项;**关键表单逐张有报告模版**(执行/验收都配、无脚本、待处理与成果同模版);
|
||
3. **DMN**:逐规则求值断言(同输入必同结论、优先级、任意格);
|
||
4. **入口**:mock host 跑 run —— 各局面的 outcome、推了什么视图、说了什么话、守卫真的拦
|
||
(无脚本/裸调用/时限必配)。
|
||
|
||
### 11.3 安装两审与版本纪律
|
||
|
||
- **静态审 11 code**(`static-audit.ts`):signature-invalid / sdk-incompatible / schema-invalid /
|
||
tool-contract-missing(⚠️ 待降级)/ model-name-unbound / dangling-listener /
|
||
template-has-script / environment-capability / dynamic-eval / ledger-type-conflict /
|
||
acceptance-level-misreported。任一 error 即拒装。
|
||
- **回放审**:历史成功样本现在必须仍过(regressions 硬门);首装无历史以样例集替代。
|
||
- **版本纪律**:安装恒新增、同版重装 no-op(**改了内容必须升 packageVersion**,否则静默
|
||
丢失 —— 实测踩过);卸载只置状态不删行;资产(业务模型/DMN/技能)播下一版不覆盖,
|
||
覆盖只能是显式治理回滚。
|
||
- 白名单授权:装入 ≠ 能调 —— 授权是另一步(⚠️ 现只有种子常量表,第 12 章⑤)。
|
||
|
||
## 12. 待实现清单
|
||
|
||
按对四条产品语义的阻塞度排序。实现时逐条销账,销一条更新本表。
|
||
|
||
| # | 事项 | 现状(证据) | 目标 |
|
||
|---|---|---|---|
|
||
| ① | **写类意向落地断链**(阻塞语义①) | `submitForRelease`/`precheck` 无调用方(`release-service.ts:119-141` 全仓无引用);活路径 `raiseAcceptance` 绕过预检;`land()` 只执行 `prechecked`(`:156`)→ 意向永停 `drafted`,写类动作永不执行;验收卡 `actions` 恒空、`criteria` 恒为硬编码单条 L5(`engines.ts:301`);幂等键两套(`engines.ts:360` vs `precheck.ts:93-95`) | 提交放行走 `submitForRelease`(预检 → 意向置 prechecked → 已验证动作清单与契约 acceptanceCriteria 上卡);幂等键统一为 `${taskId}:${intentId}` |
|
||
| ② | **工具未命中转人执行卡**(阻塞语义③) | 未注册抛 `ToolNotFoundError`(`tool-invoker.ts:43`);静态审 `tool-contract-missing` = error 拒装(`static-audit.ts:108-111`);工具契约无面向人的指导段 | 未注册 → 生成执行工单(卡面 = 工具 `guide` 段 + 输入数据 + 按输出 Schema 生成的回填表单,回传值即工具出参,幂等键沿用);静态审降级为 warn;工具契约加 `guide` 字段。未授权仍报错不转人 |
|
||
| ③ | **验收报告修订链两端断** | 验收工单 `fields` 前端不渲染(`work-order-report.tsx:75`)、`values` 恒传 `{}`(`chat-bubbles.tsx:500,519`);驳回 UI 只有 redo,引擎把 redo/revise 塌成 reject(`turn-runner.ts:432`) | fields 渲染 + values 回传(AI 拟值可改);驳回两式贯通到引擎 |
|
||
| ④ | **受理分支误吞验收裁决** | `deliver` 的受理分支判 `awaiting-human && kind==='human'` 不看 cardId(`task-service.ts:262-264`);「没法填」带缺口开跑的单再遇验收驳回,human 事件被误判成补全 | 受理分支按补全卡 cardId 匹配 |
|
||
| ⑤ | **治理台白名单授权口** | 唯一入口是种子常量表(`seed-mvp.ts:109-117`),无 API 无 UI;安装不授权 | 治理台按活动配工具白名单(读走 read、改走 govern) |
|
||
| ⑥ | `defineActivity` 去 paradigm | schema 已删,SDK 仍要求(`define.ts:21`);六包里还有 `'flow'` 字样 | SDK 签名去掉;六包清理 |
|
||
| ⑦ | dist 构建产物真编译 | `dist/manifest.json` 是过期 flow 形状、`index.js` 是源码文本拷贝(债 M5-3) | build-activities 真编译 + manifest 同步 |
|
||
| ⑧ | 缺省单拍路径无工具总线 | 只有 `JCAI_AGENT_OPEN_LOOP=1` 走总线(`engines.ts:464-491`);缺省 tool 是自由字符串 | 工具调用逻辑实现时统一走总线(开环转正或单拍接总线) |
|
||
| ⑨ | `preauthorizedActions` 死代码 | `dispositionOf`/`ToolInvoker.disposition` 无调用方;两包 manifest 里声明了但不生效 | 暂不启用:manifest 里**不要写**;将来要么接线要么删字段 |
|
||
| ⑩ | `SchemaValidator` 双死依赖 | task 与 process 都声明了 `schemas` 端口但不再调(宽进后退役) | 清理端口与注入 |
|
||
| ⑪ | **评价卡(rate)暂不做** | schema 在(`card.ts:424-438`),链路未接 | 用户定:暂不做 —— 不设计、不接线 |
|
||
|
||
其余记录在案的小不一致(不阻塞,修到顺手时清):`declarations.tools[].version` 无消费者;
|
||
`contract.resources.tools` 双份声明;replay 模式跳过白名单检查(`tool-invoker.ts:58-61`
|
||
在 loadContract 之前);注册路由造不出纯工具;工具 `retries` 声明了不重试;
|
||
`activity-registry/contract.ts:54`「两维管理」注释陈旧;`chat-bubbles.tsx:6` /
|
||
`work-order-report.tsx:6`「当前未挂载」注释已过期(实际都已挂载)。
|