Archived
2026-08-31 用户实测三连 + 「设计只是头上加了动画,操作感没变」: ① 类型「自己给上了」:data-by 行缺键落回外层同名字段(debt I3),查名 pill 摆出 案卷锚(原 PN)的类型 —— 这一行根本没有的值。守卫认 selectCurrent 按行作用域写的 option[selected]:真选上才摆名,没选摆「待选择」。守卫必须单层 :has —— :has() 里嵌 :has() 是非法选择器,整条被浏览器静默丢弃(第一版栽过,预览截图逮回)。 ② 「品牌…点保存根本不会保存」:块① 行源是 sub_eval.part,那张案卷表没有主数据列, renderTemplate 只认行作用域 —— 保存真落草稿,显示与孪生件永远空白(四步重写后的 潜伏缺陷:e2e 走 API、浏览器走查在 swap 版上,两条验证线都照不到,真人一填才现形)。 行源改 mine:parts.profile 本身(S1 ④ prune:true 恒有两行),行里天生有全部列。 ③ 「改回同一类型,下面还是两个类型」:②③ 自投影为绕「已落地行撤不掉」的死锁 (2026-08-30 e2e 案例 2)特意不带 prune,没落地的陈旧行也跟着积在屏上。 治在平台:draftDrop 加配方专用旗 skipLanded(跳过并出声「N 行已随数据工单入库、 这一笔不清它」,不炸;模型那一路的硬拒一个字不变),②③ 恢复 prune:true —— 死锁与积行两头都收,debt「prune 与确认即入库的架构性冲突」销账。 quiet 拍仍不撤行(F3),清理落在每次确认工单那一拍。 ④ 设计:块① 从 11 列横滑表推翻成电路板工作台 —— 深色板底上两张可就地编辑的 芯片档案卡(SOIC 封装 SVG + 角色/在库徽 + 类型行 + 双栏主数据格),编辑态整卡 变表单;三级占位(必填红/建议橘)上卡与参数值表;参数值表所属料号列加角色徽。 嵌套的角色/在库查表全只读(嵌套行里有 data-col 会被 collect 并成一条,测钉死)。 契约:loop-engine port draftDrop +skipLanded/+landed(纯新增,CONTRACT.md 已记, api-report 已刷)。测:107 包内(三锚变异验证)+ auto-runner skipLanded(变异验证) + 2520 全仓;2.7.0 装库 SQL 核过(档案卡宿主/守卫/胶囊/S1 四笔 prune)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
448 lines
19 KiB
TypeScript
448 lines
19 KiB
TypeScript
/**
|
|
* module: loop-engine —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。
|
|
*/
|
|
import type { LoopModel, StaffId, TaskId } from '@contracts';
|
|
import type { CaseModel } from './internal/domain/cmmn-parse.ts';
|
|
import type { CaseRuntime, EnabledSet } from './internal/domain/lifecycle.ts';
|
|
import type { AgentTurn } from './contract.ts';
|
|
export type Clock = {
|
|
now(): Date;
|
|
};
|
|
/** 运行时实例状态 —— loop 的模型定位与就绪集(OS 09 三份持久状态之一) */
|
|
export type LoopStateStore = {
|
|
save(taskId: TaskId, model: LoopModel, parsed: CaseModel, runtime: CaseRuntime, pending?: PendingAcceptance | null): Promise<void>;
|
|
load(taskId: TaskId): Promise<{
|
|
model: LoopModel;
|
|
parsed: CaseModel;
|
|
runtime: CaseRuntime;
|
|
pending?: PendingAcceptance;
|
|
} | undefined>;
|
|
};
|
|
/** S-FEEL —— app 注入 `@sfeel`;引擎零 LLM */
|
|
export type Sfeel = {
|
|
condition(expression: string, scope: Readonly<Record<string, unknown>>): boolean;
|
|
expression(expression: string, scope: Readonly<Record<string, unknown>>): unknown;
|
|
unaryTest(cell: string, value: unknown, scope: Readonly<Record<string, unknown>>): boolean;
|
|
};
|
|
/** 任务数据 —— app 注入 `ledger`(L1):引擎对**任务视图**求值,不依赖事件 */
|
|
export type TaskDataGateway = {
|
|
view(taskId: TaskId): Promise<Record<string, unknown>>;
|
|
/**
|
|
* 本活动**声明过**的台账对象(域.对象 → 列名与中文标)。
|
|
*
|
|
* 两用:喂给 Agent(不知道能写哪儿就只会瞎猜列名),以及校验 `write` 行动 ——
|
|
* 没声明过的地址一律拒写。越域写与越权调工具同罪,都得在结构上拦住。
|
|
*/
|
|
writable(taskId: TaskId): Promise<Array<{
|
|
domain: string;
|
|
object: string;
|
|
label: string;
|
|
keys: string[];
|
|
/**
|
|
* 单标识列(`declarations.ledger[].caseKey`)—— **摆给人看的行标识要去掉这一段**
|
|
* (2026-08-25 柴浩:「COV-…/CU-0142-01 只保留斜杠右边」):人正在这一单里看,
|
|
* 单号是脚下的地面,不是区分行的信息。基座那一侧同款做法见 `rowLabel`。
|
|
*/
|
|
caseKey?: string | undefined;
|
|
fields: Array<{
|
|
name: string;
|
|
label: string;
|
|
type?: string;
|
|
}>;
|
|
}>>;
|
|
/**
|
|
* 写草稿,并回报**这一笔到底有没有改变什么**。
|
|
*
|
|
* 值和上次一模一样的写是空转。不回报,Agent 就看不出自己在原地打转 ——
|
|
* 实测:同一行 verdict 被反复重写十几拍,每拍烧一次模型,预算就这么淌完。
|
|
*/
|
|
draftWrite(input: {
|
|
taskId: TaskId;
|
|
domain: string;
|
|
object: string;
|
|
key: Record<string, string | number>;
|
|
patch: Record<string, unknown>;
|
|
/**
|
|
* **值没变就别落笔**(2026-08-29 终审 I2)—— 配方每一拍都把同一张表重投一遍,
|
|
* 而草稿层的 upsert 尾巴上有 `landed_at = null`(「再写一笔就不再是已落地」)。
|
|
* 照写的话:数据工单确认入库之后,下一拍配方一重跑,那些行的「已落地」记号就被**悄悄擦掉**,
|
|
* 整单放行时会被再写一遍正式表、再抛一遍 data 事件 —— 而承诺是「整单放行不再写它、不抛事件」。
|
|
* 模型那一路不传这个旗:它的「空转写」要被看见(如实回 `changed:false`),行为一个字不变。
|
|
*/
|
|
skipUnchanged?: boolean | undefined;
|
|
/**
|
|
* **「只建不改」的表碰到已在库的键:跳过,不拒**(2026-08-29 终审 I2)。
|
|
* 那句拒回是写给模型看的(「别再写它,用 drop 把骨架撤掉」)—— 摆到人面前毫无意义,
|
|
* 而配方抛出去就是永久停单。对配方来说「这颗料已经在库了」是**常态**,不是错。
|
|
*/
|
|
skipExisting?: boolean | undefined;
|
|
}): Promise<{
|
|
changed: boolean;
|
|
skipped?: boolean;
|
|
}>;
|
|
/**
|
|
* **撤掉本单草稿层的一行** —— 写错了得有退路。
|
|
*
|
|
* 回 `false` = 草稿层里根本没有这一行(多半是主键写法对不上,
|
|
* `DAG…` 与 `DAG…-R0` 是两行)。如实说比静默成功有用:静默成功会让模型
|
|
* 以为撤掉了,下一拍判据照旧不过,它又去撤一次。
|
|
*/
|
|
draftDrop(input: {
|
|
taskId: TaskId;
|
|
domain: string;
|
|
object: string;
|
|
key: Record<string, string | number>;
|
|
/**
|
|
* **已落地的行:跳过,不拒**(2026-08-31,配方专用 —— 与 `draftWrite` 的两个旗同族)。
|
|
* 「数据工单确认即入库」把行落进正式表后,草稿层那一笔只是「本单写过它」的记号;
|
|
* 撤记号正式表一个字不动,读面反而少一行 —— 所以对人/模型那一路它是硬拒(拒回是
|
|
* 行动指南)。而配方 `prune` 的语义是「陈旧的投影行清掉」:已落地 = 已经是世界的
|
|
* 一部分,不是陈旧草稿 —— 对配方来说跳过是常态,抛错就是整单停摆(2026-08-30
|
|
* e2e 案例 2 的死锁:人确认入库后改了类型选择,prune 想撤已落地的旧类型行,当场炸;
|
|
* 当时以「②③ 不带 prune」绕开,代价是**没落地的**陈旧行也跟着不清了 ——
|
|
* 2026-08-31 用户实测点名的「改回同一类型,下面还是两个类型」正是这半截代价)。
|
|
* 模型那一路不传这个旗:行为一个字不变。
|
|
*/
|
|
skipLanded?: boolean | undefined;
|
|
}): Promise<{
|
|
dropped: boolean;
|
|
landed?: boolean;
|
|
}>;
|
|
};
|
|
/**
|
|
* Agent 回路 —— app 注入(Mastra Agent 在 adapter,§6.1)。
|
|
* **回合由 usecase 驱动,不放开自由 maxSteps,预算在环外由 Harness 强制。**
|
|
*/
|
|
export type AgentPort = {
|
|
decide(input: {
|
|
taskId: TaskId;
|
|
/** 叙事面:说明书 + 活动记忆选摘 + 任务记忆窗口 */
|
|
narrative: string;
|
|
enabled: EnabledSet;
|
|
scope: Record<string, unknown>;
|
|
/**
|
|
* 这一单能调什么 —— **声明 × 目录**编译出来的工具清单(`compileToolBus`)。
|
|
*
|
|
* 不给它这份清单,它就只知道说明书里提过的工具名,连入参长什么样都得猜 ——
|
|
* 实测的后果是:该调工具的那一步改成了发一张卡问人(「请为以下三个PN查询历史价格」),
|
|
* 明明系统里就有这件工具。工具是声明式的,清单得摆在它眼前。
|
|
*/
|
|
tools: Array<{
|
|
id: string;
|
|
description: string;
|
|
input: Record<string, unknown>;
|
|
effect: 'read' | 'write';
|
|
}>;
|
|
/** 本活动可写的台账地址与列名 —— 不给它字典,它只会照想象编列名 */
|
|
writable: Array<{
|
|
domain: string;
|
|
object: string;
|
|
label: string;
|
|
keys: string[];
|
|
fields: Array<{
|
|
name: string;
|
|
label: string;
|
|
type?: string;
|
|
}>;
|
|
}>;
|
|
}): Promise<AgentTurn>;
|
|
};
|
|
/** 工具 / 人类工具 / 监听 —— 三型行动的落点 */
|
|
export type ToolGateway = {
|
|
invoke(input: {
|
|
tool: string;
|
|
input: Record<string, unknown>;
|
|
activityId: string;
|
|
taskId: TaskId;
|
|
}): Promise<{
|
|
ok: boolean;
|
|
output?: Record<string, unknown>;
|
|
error?: {
|
|
message: string;
|
|
};
|
|
/**
|
|
* 已转人代办,等人回填(§6.2)——「人是工具的一种实现」。
|
|
*
|
|
* 系统里没有这件工具时,不是失败:基座已开了一张执行卡并立了监听。
|
|
* 这一拍该**挂起等人**,不该把计划项判 Failed —— 判 Failed 等于告诉 Agent
|
|
* 「这条路走不通,换一条」,可这条路明明正在走,只是走的人换成了人。
|
|
*/
|
|
awaitingHuman?: {
|
|
readonly workOrderId: string;
|
|
};
|
|
}>;
|
|
isWriteEffect(tool: string): Promise<boolean>;
|
|
registerIntent(input: {
|
|
taskId: TaskId;
|
|
tool: string;
|
|
input: Record<string, unknown>;
|
|
label: string;
|
|
}): Promise<void>;
|
|
/** 这一单能调什么(声明 × 目录)—— 单拍路径与开环路径吃同一份清单 */
|
|
busOf(activityId: string): Promise<Array<{
|
|
id: string;
|
|
description: string;
|
|
input: Record<string, unknown>;
|
|
effect: 'read' | 'write';
|
|
}>>;
|
|
};
|
|
/**
|
|
* 在途验收(提交放行的持久痕):卡发出即记,裁决到达再记 verdict ——
|
|
* 判齐重入不再发第二张卡。恢复的那一拍按 verdict 分岔:
|
|
* `pass` 才是终局(released);**非 pass = 打回重做,不是终局** ——
|
|
* 意见(comment)用人话进轨迹、在途验收清掉,同一拍照常问模型,
|
|
* 模型改完重新提交,新发的验收卡才是下一次裁决的对象(2026-08-22,
|
|
* 从前非 pass 只把任务重新挂成 awaiting-human,人说了「重做」却再没有
|
|
* 任何事件叫醒它,一单就此卡死 —— 柴浩实测)。
|
|
*/
|
|
export type PendingAcceptance = {
|
|
readonly acceptanceCardId: string;
|
|
readonly verdict?: 'pass' | 'reject';
|
|
/** 打回重做时人写的意见 —— 空着就是点了固定按钮没写话 */
|
|
readonly comment?: string;
|
|
readonly selectedPorts?: Record<string, string>;
|
|
};
|
|
export type CardGateway = {
|
|
/** 数据录入卡 —— 人确认那一下由基座直接写草稿层,不回到 Agent 手里 */
|
|
entry(input: {
|
|
taskId: TaskId;
|
|
ownerId: StaffId;
|
|
humanCallCap: number;
|
|
ask: string;
|
|
ledger: string;
|
|
key: Readonly<Record<string, string>>;
|
|
fields: ReadonlyArray<{
|
|
key: string;
|
|
label: string;
|
|
value?: string;
|
|
required?: boolean;
|
|
}>;
|
|
/** 同一次确认还要落的另几张表 —— 建一位新客户 = 公司档 + 联系人档 */
|
|
also?: ReadonlyArray<{
|
|
title: string;
|
|
ledger: string;
|
|
key: Readonly<Record<string, string>>;
|
|
fields: ReadonlyArray<{
|
|
key: string;
|
|
label: string;
|
|
value?: string;
|
|
required?: boolean;
|
|
}>;
|
|
}>;
|
|
basis?: ReadonlyArray<{
|
|
k: string;
|
|
v: string;
|
|
}>;
|
|
}): Promise<{
|
|
id: string;
|
|
}>;
|
|
issue(input: {
|
|
taskId: TaskId;
|
|
ownerId: StaffId;
|
|
humanCallCap: number;
|
|
workOrder: 'execution' | 'acceptance';
|
|
/** 这张卡挂在哪个计划项上 —— 引擎按它查剧本声明(如 `cardVariant`),不靠模型抄卡骨架;引擎自己发的卡(验收/兜底)没有项 */
|
|
itemId?: string | undefined;
|
|
card: Record<string, unknown>;
|
|
}): Promise<{
|
|
id: string;
|
|
}>;
|
|
/**
|
|
* 发**补全卡**(对话卡族)—— 缺口清单摆在对话区、带一张表单,人就地填齐。
|
|
*
|
|
* 与执行工单分开是因为它们是两件事:工单是「派一件活给你办」,
|
|
* 派一张工单去问几个字段,收卡的人得进待处理、办完再回来,重得不成比例。
|
|
* SOP 上画着「补全卡」的那一步(A1-S2),要的就是这一件。
|
|
*/
|
|
ask(input: {
|
|
taskId: TaskId;
|
|
ownerId: StaffId;
|
|
humanCallCap: number;
|
|
ask: string;
|
|
fields: ReadonlyArray<{
|
|
key: string;
|
|
label: string;
|
|
hint?: string;
|
|
}>;
|
|
basis?: ReadonlyArray<{
|
|
k: string;
|
|
v: string;
|
|
}>;
|
|
}): Promise<{
|
|
id: string;
|
|
}>;
|
|
/**
|
|
* 发**决策卡**(对话卡族)—— 岔路口摆给人**点一个**,不是让人敲字。
|
|
*
|
|
* 与补全卡的分工:补全卡问事实(缺哪几个字段,你填);决策卡问处置(往哪走,你选)。
|
|
* 两条路都合法、选哪条是业务判断的时候,该发的是这一件。
|
|
*/
|
|
decide(input: {
|
|
taskId: TaskId;
|
|
ownerId: StaffId;
|
|
humanCallCap: number;
|
|
ask: string;
|
|
options: ReadonlyArray<{
|
|
value: string;
|
|
label: string;
|
|
detail?: string;
|
|
consequence?: string;
|
|
recommended?: boolean;
|
|
}>;
|
|
basis?: ReadonlyArray<{
|
|
k: string;
|
|
v: string;
|
|
}>;
|
|
allowCustom?: boolean;
|
|
/** 这张卡挂在哪个计划项上 —— 组装层按它查剧本声明(`cardVariant: 'review'` = 摆成验收工单) */
|
|
itemId?: string | undefined;
|
|
/**
|
|
* **这张卡穿哪副面孔,引擎已经按剧本定好**(2026-08-27):模型没给 itemId、或给的项挂不上时,
|
|
* 组装层按 itemId 查剧本就查不到,卡就退成「需要你定」—— 而这一步的剧本明明写着 `cardVariant: 'review'`。
|
|
* 引擎知道当下在办哪一步,由它把面孔定下来递过去;组装层只在这里没给时才自己按 itemId 查。
|
|
*/
|
|
variant?: 'review' | undefined;
|
|
/** 人工验收清单(剧本 `acceptanceChecklist`)—— 审核卡上一条一枚「达标 / 不达标」开关 */
|
|
checklist?: ReadonlyArray<string> | undefined;
|
|
}): Promise<{
|
|
id: string;
|
|
}>;
|
|
/**
|
|
* **这一单还有哪几张卡在等人交回**(2026-08-27,用户:「你叫了人我都没填你就直接推进了」)。
|
|
* 每拍开头问一次:有卡没答,这一拍不问模型 —— 叫了人就等人;
|
|
* 任何多余的唤醒(迟到的回传事件、另一台后端的推进器)都推不动它。
|
|
*/
|
|
unanswered(taskId: TaskId): Promise<ReadonlyArray<{
|
|
id: string;
|
|
title: string;
|
|
}>>;
|
|
};
|
|
export type ListenerRegistrar = {
|
|
register(input: {
|
|
taskId: TaskId;
|
|
eventType: 'data' | 'time' | 'human';
|
|
keyFilter: Record<string, string>;
|
|
deadline: string;
|
|
}): Promise<{
|
|
id: string;
|
|
}>;
|
|
/**
|
|
* **这个活动声明过哪几种监听** —— 立监听的授权面(2026-08-23 加)。
|
|
*
|
|
* 与「写的授权面是台账声明」「调工具的授权面是白名单」同一条:没声明过的监听立不得。
|
|
* 起因是实测的一次永久挂起(E04):模型在依赖断点上临场立了个 data 监听等回执,
|
|
* 而那个活动 `declarations.listeners` 是空的、这一单也不会有任何 data 事件进来 ——
|
|
* 单子就此挂在 `awaiting-event` 一动不动。**立了没人叫醒的监听不是等待,是失联。**
|
|
*/
|
|
declared(taskId: TaskId): Promise<ReadonlyArray<{
|
|
eventType: string;
|
|
keyFilter: Record<string, string>;
|
|
}>>;
|
|
};
|
|
/**
|
|
* 视图流控 —— app 注入 `interaction`(L3)。
|
|
* 「loop:AI 按定位与局面临场组织视图流」(AM 07)。MVP 每回合推一次**案例全景**:
|
|
* 阶段与计划项的生命周期态、当下可为、目标缺口 —— 这些是语义引擎算出来的确定事实,
|
|
* 零 LLM 即可成屏;模型只决定「下一步做什么」,不负责画面。
|
|
*/
|
|
export type ViewGateway = {
|
|
/**
|
|
* 推一帧画面。**回报推没推上** —— 从前是 `Promise<void>` 且模版查不到就静默 return:
|
|
* 引擎每拍推一个硬编码的 `case-overview`,而 A1 声明的四张模版一个都不叫这个名字,
|
|
* 于是**每一帧都被悄悄丢掉**,工作区从头到尾空白,日志里也没有一个字。
|
|
* 静默失败比报错坏得多:它让「没画面」看起来像「活动还没产出」。
|
|
*/
|
|
push(input: {
|
|
taskId: TaskId;
|
|
activityId: string;
|
|
template: string;
|
|
scope: Record<string, unknown>;
|
|
}): Promise<{
|
|
pushed: boolean;
|
|
templates: string[];
|
|
}>;
|
|
};
|
|
/**
|
|
* 技能 —— app 注入 `skill-engine`(L4)。
|
|
* 「渐进披露:常驻上下文**仅有技能目录**,按事加载正文、按需下钻引用」(AM 08)。
|
|
*/
|
|
export type SkillGateway = {
|
|
/** 目录:id + 何时适用 —— 只给这些,正文等 AI 说要才加载 */
|
|
index(activityId: string): Promise<ReadonlyArray<{
|
|
id: string;
|
|
label: string;
|
|
applicability: string;
|
|
}>>;
|
|
/** 装配入信封:这一回合装配了哪些技能及其版本,记入任务记忆 */
|
|
stamp(activityId: string, skillIds: readonly string[]): Promise<ReadonlyArray<{
|
|
id: string;
|
|
version: string;
|
|
}>>;
|
|
};
|
|
export type MemoryRecorder = {
|
|
append(input: {
|
|
taskId: TaskId;
|
|
kind: 'engine-transition' | 'evaluation' | 'thought' | 'action' | 'result' | 'observation' | 'narration';
|
|
title: string;
|
|
detail?: Record<string, unknown>;
|
|
}): Promise<void>;
|
|
/** 装载:按定位选摘活动记忆入 Agent 上下文(渐进披露) */
|
|
loadActivityMemory(activityId: string, limit: number): Promise<Array<{
|
|
layer: string;
|
|
statement: string;
|
|
}>>;
|
|
/**
|
|
* 本任务自己的最近轨迹 —— 喂回叙事面,Agent 每拍不失忆。
|
|
*
|
|
* 不给 `size` 就用活动声明的 `memoryWindow`(叙事面按它给;窗口越大越贵)。
|
|
* **熔断检测要另开一个更大的窗**:它数的是「同一堵墙撞了几次」,
|
|
* 而一次提交被拒会连带写下交代、评估、推画面好几条 —— 20 条的叙事窗里
|
|
* 至多装得下三四次撞墙,于是撞十次也数不到 5(实测:同一条判据拒回 10 次没熔断,
|
|
* 另一单拒回 33 次,40 万 token 烧到 exhausted 才停)。
|
|
*/
|
|
window(taskId: TaskId, size?: number): Promise<Array<{
|
|
kind: string;
|
|
title: string;
|
|
detail?: Record<string, unknown> | undefined;
|
|
}>>;
|
|
};
|
|
export type TaskContext = {
|
|
of(taskId: TaskId): Promise<{
|
|
activityId: string;
|
|
ownerId: StaffId;
|
|
humanCallCap: number;
|
|
}>;
|
|
/**
|
|
* 这一单的**机检判据名**(归一化后的 key 与 label 都在里头)。
|
|
*
|
|
* 给 `judge` 行动当门用:L1/L2 是拿 S-FEEL 对台账求值的,Agent 裁不动它 ——
|
|
* 判据投影压根不读 Agent 对机检项的裁定。可从前引擎照收不误,还往轨迹里
|
|
* 写一句「语义性前提评估:XXX → 满足」:模型于是以为这条已经过了,
|
|
* 转头就 submit,被门以同一条判据拒回;再裁、再拒,连拒五拍熔断(实测 A2 死于此)。
|
|
* 轨迹里那句「满足」是**假话** —— 界面不撒谎,对 Agent 也一样。
|
|
*/
|
|
machineCriteria(taskId: TaskId): Promise<ReadonlySet<string>>;
|
|
};
|
|
/**
|
|
* **一次结构化 AI 调用** —— 配方的 `analyze` 动词(spec §5.2)。
|
|
*
|
|
* 与 `AgentPort` 的分工是死的:`AgentPort` 是「问它下一步干什么」,它能调工具、能写台账;
|
|
* 这一件**只是一个纯函数**:系统组装好入参,它出一份结构化判断,系统把结果落表。
|
|
* **不给工具、不给记忆、不多轮。** 出参不合 schema 由实现方就地重问一次,再不成就抛。
|
|
*/
|
|
export type AnalyzeGateway = {
|
|
run(input: {
|
|
readonly taskId: TaskId;
|
|
/** 活动声明过的模型名 */
|
|
readonly model: string;
|
|
readonly instruction: string;
|
|
/** 系统组装好的入参 —— 一行一条 */
|
|
readonly payload: readonly unknown[];
|
|
readonly outputSchema: Readonly<Record<string, unknown>>;
|
|
/** 归因:`<步名>#<第几笔>` */
|
|
readonly label: string;
|
|
}): Promise<{
|
|
readonly object: Readonly<Record<string, unknown>>;
|
|
}>;
|
|
};
|