@xdxer/dingtalk-agent 0.1.5-beta.7 → 0.1.5-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.en.md +1 -0
- package/README.md +1 -0
- package/dist/bin/dingtalk-agent.js +118 -15
- package/dist/bin/dingtalk-agent.js.map +1 -1
- package/dist/src/agent-audit.js +108 -9
- package/dist/src/agent-audit.js.map +1 -1
- package/dist/src/development-workspace.js +37 -1
- package/dist/src/development-workspace.js.map +1 -1
- package/dist/src/dws.js +9 -0
- package/dist/src/dws.js.map +1 -1
- package/dist/src/multica-deploy.js +347 -105
- package/dist/src/multica-deploy.js.map +1 -1
- package/dist/src/multica-provider.js +151 -27
- package/dist/src/multica-provider.js.map +1 -1
- package/dist/src/multica-runtime-vocabulary.js +110 -0
- package/dist/src/multica-runtime-vocabulary.js.map +1 -0
- package/dist/src/promotion.js +2 -1
- package/dist/src/promotion.js.map +1 -1
- package/dist/src/schedule-plan.js +192 -24
- package/dist/src/schedule-plan.js.map +1 -1
- package/dist/src/skill-manager.js +76 -9
- package/dist/src/skill-manager.js.map +1 -1
- package/docs/AGENT-IN-PRODUCTION.md +255 -0
- package/docs/ARCHITECTURE.md +5 -1
- package/docs/PLATFORM-GUARDRAILS.md +188 -0
- package/docs/schemas/multica-deployment-plan.schema.json +3 -1
- package/docs/schemas/multica-deployment-receipt.schema.json +15 -1
- package/docs/schemas/multica-deployment-status.schema.json +6 -2
- package/docs/schemas/multica-workspace-inspection.schema.json +16 -0
- package/docs/schemas/multica-workspace-status.schema.json +2 -0
- package/docs/schemas/workspace-scaffold.schema.json +1 -0
- package/lab/project-workspace/fake-multica-provider.mjs +97 -7
- package/lab/project-workspace/multica-deploy.fixture.json +2 -2
- package/lab/project-workspace/multica-readonly.fixture.json +2 -2
- package/lab/project-workspace/project.fixture.json +1 -1
- package/lab/robot-eval/suite.json +1 -1
- package/package.json +5 -2
- package/skills/core/dta-agent-compose/SKILL.md +1 -1
- package/skills/core/dta-agent-compose/references/drive-and-schedules.md +22 -2
- package/skills/core/dta-basic-behavior/SKILL.md +1 -1
- package/skills/core/dta-basic-behavior/references/memory-and-evolution.md +1 -1
- package/skills/core/dta-people-group-memory/SKILL.md +4 -4
- package/skills/core/dta-people-group-memory/references/model.md +1 -1
- package/skills/platforms/multica-dingtalk/PLATFORM.md +2 -2
- package/skills/platforms/multica-dingtalk/dta-deploy-multica/SKILL.md +6 -5
- package/skills/platforms/multica-dingtalk/dta-deploy-multica/references/multica-deployment-contract.md +14 -4
- package/skills/platforms/multica-dingtalk/dta-ops-multica/SKILL.md +33 -7
- package/skills/platforms/multica-dingtalk/dta-ops-multica/scripts/multica_ext.py +118 -12
- package/dist/src/map.js +0 -157
- package/dist/src/map.js.map +0 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# dta 平台护栏清单:哪些坑必须由工具层兜住
|
|
2
|
+
|
|
3
|
+
> 2026-07-24 一夜盯一个真实生产 Agent(FDE 教练)跑,踩出六个坑。本文回答一个问题:
|
|
4
|
+
> **每个坑该由谁兜——原语 / CLI 闸门 / Skill 纪律 / 文档?**
|
|
5
|
+
>
|
|
6
|
+
> 一条贯穿全文的判据:**凡是「dta 文档已经写过、业务方仍然逐个踩」的坑,就是该从文档下沉到 CLI 的信号。**
|
|
7
|
+
>
|
|
8
|
+
> ⚠️ **诚实说清本文的定位(经两轮 review 收窄)**:本文是**事故复盘 + 该由哪层兜的分析**,
|
|
9
|
+
> 不是"已封死"的宣告。本次 CR 真正落到**代码**的只有【节律静态检查(同刻并发按真实 occurrence 判、
|
|
10
|
+
> 完成判据存在性、计量标注)】;【AI 表格静默截断】和【完成判据分叉】这两个最核心的坑,
|
|
11
|
+
> 本次只做了**文档 + Skill 纪律 + 存在性检查**,**没有**做到类型层/注册表层的硬封印——那需要
|
|
12
|
+
> `completeness: proven\|unproven` 原语、`predicateRef` 注册表、no-op Receipt 合同,均已列为 follow-up issue。
|
|
13
|
+
> 换句话说:**别把本文当"每个 Agent 都自动免疫了",它是"我们知道坑在哪、部分下沉了、其余明确记账"。**
|
|
14
|
+
>
|
|
15
|
+
> 证据等级:**【实测】**本机只读复跑、**【读码】**对到 file:line、**【下界】**单读表格数可能偏小。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 0. dta 今天的护栏落在哪三层
|
|
20
|
+
|
|
21
|
+
`README.md` / `AGENTS.md` 的口径是 **Skill 劝 · CLI 拦 · DWS 做**。【读码】实际分布:
|
|
22
|
+
|
|
23
|
+
| 层 | 位置 | 今天真拦住了什么 |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Skill 劝 | `skills/core/dta-basic-behavior/SKILL.md` | 响应资格、意图、`nothing-to-save` 合法性、没 Receipt 不用完成措辞 |
|
|
26
|
+
| CLI 拦 | `src/dws.ts` 六条纪律、`records()` fail-closed、`src/actions.ts` 发消息后独立回读、`src/memory/operational.ts` 按 recordId 回读 + 命中多行拒绝 | 目标冻结、四原语、写后回读、幂等 |
|
|
27
|
+
| DWS 做 | `src/dws.ts`(全仓唯一 spawn `dws` 处) | — |
|
|
28
|
+
| **文档层**(既不劝也不拦) | `DWS-FIELD-NOTES.md`、`AGENT-IN-PRODUCTION.md` | **什么都拦不住** |
|
|
29
|
+
|
|
30
|
+
**最硬的一个判据**:`AGENT-IN-PRODUCTION.md` 早就写了"写后回读读的是同一个错误对象"、"同一个语义只留一个谓词"、"改 mode 就是在改计量口径"——**文档劝过了,业务方还是踩了**。这三条都该下沉。
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 1. 坑一 · AI 表格静默截断 → **必须是原语,不能只写进 Skill**
|
|
35
|
+
|
|
36
|
+
**【实测】** 同一 `--all --page-limit 0` 全表连读 6 次得 `400/300/1300/500/1800/700`,
|
|
37
|
+
pages `5/4/14/6/19/8`,**6 次全 `hasMore=false`、`partial` 键根本不存在**。单次最低 300 是并集下界的 17%。
|
|
38
|
+
成功信封的全部键只有 `records/hasMore/pages`——**没有 `totalCount`,服务端不提供任何完整性凭据**。
|
|
39
|
+
|
|
40
|
+
**【读码】dta 今天的状态**:
|
|
41
|
+
|
|
42
|
+
- `src/dws.ts records()` 已 fail-closed 检查 `hasMore/partial`——**和 FDE 引擎 `query()` 同一道闸门、同一个漏洞**。平台谎报 `hasMore:false`,两道闸门同时被绕过。
|
|
43
|
+
- **更糟**:`src/dws.ts queryAiTableRecords()`(dta 自己的 Operational Memory 读路径)**不带 `--all`、不带 `--page-limit 0`,只 `--limit 100`**——比 FDE 引擎还弱一档。
|
|
44
|
+
- `DWS-FIELD-NOTES.md` 现有对策「`--all` 必配 `--page-limit 0`」**今晚实测证伪**:加了照样截断。
|
|
45
|
+
|
|
46
|
+
**为什么不能留给业务方**(不是偏好):
|
|
47
|
+
1. 这是**介质属性**不是岗位知识。FDE 已严格按文档写对参数,仍被骗 → 这题在 Skill/文档层**无解**。
|
|
48
|
+
2. 判据需要**跨调用状态**(多读、计数账本、水位),Skill 是无状态语义层。`AGENTS.md` 自己就写「幂等、回读、硬拒绝 → CLI/Gate,不得只写在 Prompt」。
|
|
49
|
+
3. `dta-people-group-memory/references/storage-port.md` 自立原则「模型层不知道『截断』这个词」——既然模型层不该知道,就必须有人替它知道。
|
|
50
|
+
|
|
51
|
+
**该做什么**:
|
|
52
|
+
|
|
53
|
+
| 落层 | 改动 |
|
|
54
|
+
|---|---|
|
|
55
|
+
| **原语** `src/dws.ts` | `records()` 不再把 `hasMore=false` 当完整性证据。返回 `{records, completeness: 'proven'\|'unproven'}`,全量扫一律 `unproven`,调用方**编译期**绕不过去 |
|
|
56
|
+
| **原语** `src/dws.ts` | 把混为一谈的读拆成三个、在类型上分开可靠性:`getByRecordIds()`(≤100 定点,【实测】7/8 正确且失败 fail-loud)/`queryByKey(filters)`(服务端筛,**仍 unproven**)/`scanAll()`(**标记不可信**) |
|
|
57
|
+
| **原语** `src/memory/` | **count ledger**:写侧每次 `+1` 到 meta 表,读侧全量枚举时两数相比——**唯一**能把"读全了"变可证明的机制(服务端无 `totalCount` oracle)。诚实边界:只覆盖 dta 自己写的行,给"不少于";对单写者表等号成立 |
|
|
58
|
+
| **CLI 闸门** | 连读 K 次取 max **只提高发现截断的概率、不能证明完整**(【实测】并集到第 5 次才碰到 1800,仍是下界)。所以 K-读只作 `unproven→suspected-truncated` 降级信号,**不许当完整性证明**——写进代码注释,否则会被误当对策 |
|
|
59
|
+
| **文档** | 更正 `DWS-FIELD-NOTES.md`:`--all --page-limit 0` **不足以**防截断,附今晚数据 |
|
|
60
|
+
| **回归** | `evals/` 加 core 合同:fake dws stub 返回"页对齐前缀 + `hasMore:false`",断言 CLI **拒绝**当完整 |
|
|
61
|
+
|
|
62
|
+
**⚠️ 已推翻的一个说法**(复核纠正):曾以为"`--all` 的空返回有一种 fail-loud 安全形态"。**错**——
|
|
63
|
+
`--all` 路径上的空返回是 `{"hasMore":false,"pages":1,"records":null}`,`records` 键在、值为 `null`,
|
|
64
|
+
两道闸门(`dws.ts` 的 `'records' in x` 和 `records || []`)都**静默**放过。`--all` 路径上**两种 0 都不安全**。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 2. 坑二 · 写后回读读到同一个错对象 → **Action Gate 该管,今天管不到**
|
|
69
|
+
|
|
70
|
+
**背景事故**:FDE 的 `write-ai` 盲写 `rows[0]`、回读也取 `rows[0]` → 永远自洽 → 写错行永远发现不了。
|
|
71
|
+
根因是「业务主键不唯一 + 定位靠读」,回读只是发现手段。
|
|
72
|
+
|
|
73
|
+
**【读码】dta 今天**:这条合同**已存在**——`src/memory/operational.ts` 按 recordId 独立回读、key 命中多行直接拒绝、
|
|
74
|
+
创建后没拿到 recordId 就按 key 回查。`ARCHITECTURE.md` 还拿它当"为什么这必须是 CLI 命令"的范例。
|
|
75
|
+
**但覆盖面几乎为零**:只作用于 `dta memory operational upsert` 一条命令;进 Action Gate 的 typed 副作用
|
|
76
|
+
**只有回复与表情两种**,AI 表格写入根本不进 Gate。FDE 的 write-ai 是业务方自己写的,所以这条事故 dta 一点忙没帮上。
|
|
77
|
+
|
|
78
|
+
**该落哪**:
|
|
79
|
+
- **CLI 闸门(新增窄命令)**:按 `ARCHITECTURE.md` 的六条判据,业务表写入命中第 3(幂等且不能盲重试)+ 第 4(写后必须独立回读)→ **符合进 CLI 的标准**。但不做通用 CRUD(`ARCHITECTURE.md` 明禁复制 DWS 命令面)。折中:
|
|
80
|
+
`dta aitable upsert --key-field <f> --key <v> --required-nonempty <f,…>`,只做四件——按业务键定位 → 键唯一性断言 → 写 → 按 recordId 回读,其余回 DWS。
|
|
81
|
+
- **为什么不留给业务方**:这个错误 **100% 静默且自洽**(写 rows[0]、回读也取 rows[0] → 永远自证成功),数据上看不出来,只有 backlog 三十小时不降才反推得到。"结构性不可观测"的错误定义上就不该由每个业务方各自发现一次。
|
|
82
|
+
|
|
83
|
+
**⚠️ 一个未复现的推测**(复核纠正):曾推测 dta 自己的 `operational.ts` 也有同族"截断到 0 → 判不存在 → create 重复"的洞。
|
|
84
|
+
**在真实代码路径上未复现**:`operational.ts` 走 `--limit 100`(非 `--all`),【实测】连读 30 次里 2 次空返回都是
|
|
85
|
+
带 `error` 的信封 → 进 `dws.ts` 的 `error.code` 判断 → **fail-loud(die)**,不是静默造重复。
|
|
86
|
+
所以 dta 内核这条路是安全的——但**新增的 `dta aitable upsert` 若走 `--all` 定位就会引入这个洞**,设计时必须用 `getByRecordIds`/`queryByKey` 而非 `scanAll`。
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 3. 坑三 · 两个完成判据分叉 → **注册表 + 唯一入口,不是静态检查**
|
|
91
|
+
|
|
92
|
+
**背景事故**:FDE 的 `duty_runlog()` 完成判据只看答案原文、`_settled()` 还要问题原话,两谓词分叉 →
|
|
93
|
+
心跳先问 done、done 就不跑补录 → 补救路径 30 小时没被触发、没有任何一层报警。
|
|
94
|
+
|
|
95
|
+
**【读码】现状**:`AGENT-IN-PRODUCTION.md` 已把结论写成「同一个语义只留一个谓词,两个调用方共用」;
|
|
96
|
+
`AGENTS.md` 有「每条不变量只选一个 owner」的表,但管的是**内容写在哪个文件**、不是**代码谓词的唯一性**。**没有机器检查。**
|
|
97
|
+
|
|
98
|
+
**能做**:
|
|
99
|
+
- **装配层**:`dta-agent-compose` 的节律声明里,每条 schedule 带 `doneWhen: predicate:<id>`。
|
|
100
|
+
- **CLI 闸门**:`dta schedule plan` 校验每个 predicate 在 Agent 侧可执行——`src/schedule-plan.ts` 已有
|
|
101
|
+
"平台不提供该能力就 supported:false 记缺口、绝不静默降级"的 fail-closed 骨架,**直接复用到 predicate**。
|
|
102
|
+
- **回归**:同一 duty 的两个调用点必须解析到同一 predicate id,不同就红。
|
|
103
|
+
|
|
104
|
+
**不能做(诚实标注)**:跨语言(FDE 引擎是 Python)**没法静态证明两个函数语义相同**。约束形式只能是「注册 + 唯一入口 + 谁绕过谁没 receipt」,不是分析。
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 4. 坑四 · 解析失败被折叠成"内容为空" → **解析失败必须留痕,拦在写入前**
|
|
109
|
+
|
|
110
|
+
**背景事故**:平台改中继模板标题 → FDE 的 `_RE_ASKED` 100% 不命中 → 抠不到问题原话 → **静默写空**。
|
|
111
|
+
通用形状 = **解析失败与"本来就空"不可区分**,和 `DWS-FIELD-NOTES.md` 的「读错层级 → undefined → 当成空」同族。
|
|
112
|
+
|
|
113
|
+
**该落哪**:
|
|
114
|
+
- **原语/Skill 纪律**:凡"从外部信封解析出字段再写入"的路径,解析失败必须产出一个**可区分于空**的标记(如 `parse-miss`),
|
|
115
|
+
而不是落一个空字符串。写入前若关键字段是 `parse-miss` → 记欠账、不写、可报警。
|
|
116
|
+
- **对应 `dta-people-group-memory` 的第 3 条不变式「读失败 ≠ 不存在」**——但那条现在只针对**读**,
|
|
117
|
+
要扩到**解析**:解析失败 ≠ 字段为空。
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 5. 坑五 · ephemeral 不产 issue → 不可计量 → **观测缺口,ARCHITECTURE §8 自己承认**
|
|
122
|
+
|
|
123
|
+
**【读码】** 常驻节律多是 `ephemeral`(`run_only`),平台不为它建 issue → `/api/tasks/{id}/usage` 拿不到 →
|
|
124
|
+
心跳的真实 Token 拿不到。`ARCHITECTURE.md §8` 自己说观测是"最薄的一段"。
|
|
125
|
+
FDE 侧实测:可见 IM 成本只是冰山一角,心跳那半平台不记账。
|
|
126
|
+
|
|
127
|
+
**该落哪**:
|
|
128
|
+
- **文档已对(`AGENT-IN-PRODUCTION.md` §6 写了盲区),但 dta 无计量能力**。这条不该硬造——
|
|
129
|
+
但 dta 至少该在 `dta schedule plan` 输出里**标注每条节律的 `mode` 与可计量性**,让 operator 在排期时就知道哪块看不见,
|
|
130
|
+
而不是等业务方各自发现。「改 `mode` 就是改计量口径」这句该从文档变成 `schedule plan` 的一条显式告警。
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 6. 坑六 · "无操作"不可观测 → **`nothing-to-save` 也要留痕**
|
|
135
|
+
|
|
136
|
+
**背景事故**:FDE 的单聊沉淀判 `nothing-to-save` 是合法结果,但**判了 nothing-to-save 和压根没跑,在外部完全不可区分**——
|
|
137
|
+
沉淀这步没跑不留痕、跑了判空也不留痕。结果是"这个能力到底在不在工作"永远回答不了。
|
|
138
|
+
|
|
139
|
+
**该落哪**:
|
|
140
|
+
- **Skill 纪律(`dta-basic-behavior` + `dta-people-group-memory`)**:把「无操作也是一次可观测的操作」立成通用合同——
|
|
141
|
+
判 `nothing-to-save` / `skip` / `no-op` 时**留一行结构化痕迹**(带原因),而不是静默返回。
|
|
142
|
+
- 这条和 `dta-basic-behavior` 的"没 Receipt 不用完成措辞"是一体两面:完成要有证据,**不完成也要有证据**。
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 7. 对 dta-people-group-memory 四条不变式的复核
|
|
147
|
+
|
|
148
|
+
这个 Skill(v0.3.0)刚进默认套装,四条不变式在**静默截断**下逐条评估:
|
|
149
|
+
|
|
150
|
+
| 不变式 | 扛得住截断吗 | 说明 |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| ① 原始层只增不删,派生层可重建 | ✅ | 与截断无关 |
|
|
153
|
+
| ② 写后必回读,success 不算数 | ⚠️ **不够** | 回读若用 `scanAll` 会被截断骗;必须限定为**按 recordId 回读**(坑二)。「回读」要在契约里写死是哪种读 |
|
|
154
|
+
| ③ **读失败 ≠ 不存在** | 🔴 **最需要加强** | 现在只针对**报错的读**。而截断读**不报错**——它成功返回一个偏小的前缀。这条不变式的前提"读失败会被识别"在 `--all` 上**不成立**。必须补一句:**"成功但可能不完整"也算读失败**,全量枚举一律 `unproven`,绝不据此判"不存在"或新建重复 |
|
|
155
|
+
| ④ 先写事实、后推水位 | ✅ | 与截断无关,且正确 |
|
|
156
|
+
|
|
157
|
+
**核心修订**:第 ③ 条要从「读失败 ≠ 不存在」升级为「**读不完整 ≠ 不存在**,而全量枚举永远无法自证完整」。
|
|
158
|
+
这正是坑一 G1 的原语要兜的——把"完整性未证明"变成一个类型层的显式状态,Skill 的第 ③ 条才有东西可依靠。
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 附:落地状态(2026-07-24 本次 CR,两轮 review 后收窄)
|
|
163
|
+
|
|
164
|
+
分三档如实标:**代码闸门**(机器强制)、**文档/Skill**(劝、不强制)、**follow-up**(真封印,本次没做)。
|
|
165
|
+
|
|
166
|
+
**已落代码闸门(机器强制、CI 守)**
|
|
167
|
+
| 坑 | 落点 | 状态 |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| 坑三 判据缺失 | `schedule-plan.ts` 校验每条节律有 `completion`,缺失 `schedule show/plan` **exit 2**(json+人类都设,不 fail-open) | ✅ 代码 |
|
|
170
|
+
| 坑五 计量盲区 | `schedule-plan.ts` 每条节律标 `metered`(ephemeral 不记账显式提示) | ✅ 代码 |
|
|
171
|
+
| 撞车误报/漏报 | `firesCollide` 改**按真实 occurrence 判**(robfig dom/dow OR、名字字面量、闰年枚举);判不准的(跨时区/扩展语法)进 `unverifiedPairs`,**和确认撞车一样 exit 2 挡住**——`0 9 * * *`@Asia/Shanghai 与 `0 1 * * *`@UTC 是同一时刻,证不出不撞就不许部署 | ✅ 代码 + 回归 |
|
|
172
|
+
|
|
173
|
+
**已落文档/Skill 纪律(劝,不强制——这几条本次【没有】做成硬封印)**
|
|
174
|
+
| 坑 | 落点 | ⚠️ 局限 |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| 坑一 截断 | `DWS-FIELD-NOTES` 重写 + `dws.ts` 注释 + people-memory 不变式③「读不完整≠不存在」 | `records()` 仍会接受 `hasMore:false` 的部分前缀、把 `records:null` 折成 `[]`——**没有 completeness 类型封印**(见 follow-up) |
|
|
177
|
+
| 坑三 判据分叉 | `completion` 存在性检查 | **只证明"写了一句话"**,不保证运行时两个谓词引用同一个——原事故那种"各有一套判据"仍可能复发(见 follow-up) |
|
|
178
|
+
| 坑四 解析失败折叠成空 | `DWS-FIELD-NOTES`「解析失败≠空」+ `parse-miss` | 是文案,没有强制的 typed action |
|
|
179
|
+
| 坑六 无操作不可观测 | `dta-basic-behavior`:`nothing-to-save` 也要留痕 | 是文案,**没有** host 签发的 no-op Receipt 合同(见 follow-up) |
|
|
180
|
+
| 不变式② | people-memory:写后按 recordId 回读 | ✅ 纪律清晰 |
|
|
181
|
+
|
|
182
|
+
**明确 follow-up(真封印,本次没做,各开 issue)**
|
|
183
|
+
1. `completeness: proven\|unproven` 读原语 + 页对齐前缀 fixture——把"未证明完整"变类型层显式状态。
|
|
184
|
+
2. `predicateRef` 注册表 + 运行时引用校验——让"完成判据分叉"在编译期/运行期查得出来。
|
|
185
|
+
3. host 签发的 no-op / nothing-to-save Receipt 合同——让"跑过判空"可与"没跑"区分。
|
|
186
|
+
4. cron 同刻判定复用 provider(robfig)occurrence 预览,替掉本地模拟。
|
|
187
|
+
|
|
188
|
+
**明确没做(避免一味增加)**:不新增 `dta aitable upsert` CLI 命令面(`ARCHITECTURE.md` 明禁复制 DWS 命令面,且无现有调用方);`records:null` 不硬判死(dws 版本相关,硬判深耦合);计量不进 core。
|
|
@@ -40,9 +40,11 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"definitionHash": { "$ref": "#/$defs/hash" },
|
|
43
|
-
"instructionsHash": { "$ref": "#/$defs/hash" }
|
|
43
|
+
"instructionsHash": { "$ref": "#/$defs/hash" },
|
|
44
|
+
"previousRuntimeId": { "type": "string" }
|
|
44
45
|
}
|
|
45
46
|
},
|
|
47
|
+
"rebindRuntime": { "type": "boolean" },
|
|
46
48
|
"operations": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
47
49
|
"writeBudget": {
|
|
48
50
|
"type": "object",
|
|
@@ -59,7 +59,13 @@
|
|
|
59
59
|
"loadedSkills": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/name" } },
|
|
60
60
|
"toolTracePassed": { "type": "boolean" }, "responsePassed": { "type": "boolean" },
|
|
61
61
|
"responseHash": { "type": "string" },
|
|
62
|
-
"failures": { "type": "array", "items": { "type": "string" } }
|
|
62
|
+
"failures": { "type": "array", "items": { "type": "string" } },
|
|
63
|
+
"runtimeProvider": { "type": "string" },
|
|
64
|
+
"vocabularyIds": { "type": "array", "items": { "type": "string" } },
|
|
65
|
+
"toolNames": { "type": "array", "items": { "type": "string" } },
|
|
66
|
+
"unrecognizedTools": { "type": "array", "items": { "type": "string" } },
|
|
67
|
+
"classification": { "enum": ["", "skills-not-visible"] },
|
|
68
|
+
"failureDetails": { "$ref": "multica-workspace-inspection.schema.json#/$defs/failureDetails" }
|
|
63
69
|
}
|
|
64
70
|
},
|
|
65
71
|
"rollback": {
|
|
@@ -72,6 +78,14 @@
|
|
|
72
78
|
}
|
|
73
79
|
},
|
|
74
80
|
"failures": { "type": "array", "items": { "type": "string" } },
|
|
81
|
+
"failureDetails": { "$ref": "multica-workspace-inspection.schema.json#/$defs/failureDetails" },
|
|
82
|
+
"rebind": {
|
|
83
|
+
"type": "object", "additionalProperties": false,
|
|
84
|
+
"required": ["fromRuntimeId", "toRuntimeId"],
|
|
85
|
+
"properties": {
|
|
86
|
+
"fromRuntimeId": { "type": "string" }, "toRuntimeId": { "type": "string" }
|
|
87
|
+
}
|
|
88
|
+
},
|
|
75
89
|
"evidencePath": { "type": "string", "minLength": 1 },
|
|
76
90
|
"remoteRead": { "const": true }, "remoteWrite": { "type": "boolean" },
|
|
77
91
|
"triggerWrite": { "const": false },
|
|
@@ -14,10 +14,14 @@
|
|
|
14
14
|
"passed": { "type": "boolean" }, "providerReady": { "type": "boolean" },
|
|
15
15
|
"operationIntegrity": { "type": "boolean" }, "receiptIntegrity": { "type": "boolean" },
|
|
16
16
|
"reconciled": { "type": "boolean" },
|
|
17
|
+
"receiptFrozen": { "type": "boolean" },
|
|
18
|
+
"smokeStale": { "type": "boolean" },
|
|
19
|
+
"nextAction": { "type": "string" },
|
|
17
20
|
"failures": { "type": "array", "items": { "type": "string" } },
|
|
18
21
|
"receipt": { "oneOf": [{ "type": "null" }, { "$ref": "multica-deployment-receipt.schema.json" }] },
|
|
19
|
-
"remoteRead": { "type": "boolean" }, "remoteWrite": { "
|
|
20
|
-
"triggerWrite": { "const": false },
|
|
22
|
+
"remoteRead": { "type": "boolean" }, "remoteWrite": { "type": "boolean" },
|
|
23
|
+
"triggerWrite": { "const": false },
|
|
24
|
+
"sideEffect": { "enum": [false, "multica-read", "multica-read-write"] },
|
|
21
25
|
"dingtalkSideEffect": { "const": false }
|
|
22
26
|
}
|
|
23
27
|
}
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
"resources": { "$ref": "#/$defs/resources" },
|
|
37
37
|
"diff": { "type": "array", "items": { "type": "string" } },
|
|
38
38
|
"failures": { "type": "array", "items": { "type": "string" } },
|
|
39
|
+
"failureDetails": { "$ref": "#/$defs/failureDetails" },
|
|
40
|
+
"envSanitized": { "type": "array", "items": { "type": "string" } },
|
|
39
41
|
"calls": {
|
|
40
42
|
"type": "array",
|
|
41
43
|
"items": {
|
|
@@ -60,6 +62,20 @@
|
|
|
60
62
|
},
|
|
61
63
|
"$defs": {
|
|
62
64
|
"hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
65
|
+
"failureDetails": {
|
|
66
|
+
"type": "array",
|
|
67
|
+
"items": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"additionalProperties": false,
|
|
70
|
+
"required": ["code"],
|
|
71
|
+
"properties": {
|
|
72
|
+
"code": { "type": "string", "minLength": 1 },
|
|
73
|
+
"expected": { "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] },
|
|
74
|
+
"actual": { "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] },
|
|
75
|
+
"hint": { "type": "string" }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
63
79
|
"resources": {
|
|
64
80
|
"type": "object",
|
|
65
81
|
"additionalProperties": false,
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"observedHash": { "type": "string" },
|
|
27
27
|
"diff": { "type": "array", "items": { "type": "string" } },
|
|
28
28
|
"failures": { "type": "array", "items": { "type": "string" } },
|
|
29
|
+
"failureDetails": { "$ref": "multica-workspace-inspection.schema.json#/$defs/failureDetails" },
|
|
30
|
+
"observedAgentRuntimeId": { "type": "string" },
|
|
29
31
|
"remoteRead": { "const": false },
|
|
30
32
|
"remoteWrite": { "const": false },
|
|
31
33
|
"sideEffect": { "const": false },
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"alreadyDeclared": { "type": "boolean" },
|
|
20
20
|
"manifest": { "const": "dingtalk-agent.json" },
|
|
21
21
|
"block": { "type": "object" },
|
|
22
|
+
"createdPaths": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
22
23
|
"envRefs": {
|
|
23
24
|
"type": "object",
|
|
24
25
|
"additionalProperties": false,
|
|
@@ -82,7 +82,9 @@ if (commandKey === 'config show') {
|
|
|
82
82
|
process.exit(0)
|
|
83
83
|
}
|
|
84
84
|
if (commandKey === 'auth status') {
|
|
85
|
-
|
|
85
|
+
// 复刻 multica Go CLI 的保留语义:环境里存在 MULTICA_AGENT_ID 即切 Agent 身份鉴权,
|
|
86
|
+
// 用户 token 判无效(B11)。dta 剔除该变量后本分支不应再命中。
|
|
87
|
+
if (!state.authenticated || process.env.MULTICA_AGENT_ID) {
|
|
86
88
|
console.error("Not authenticated. Run 'multica login' to authenticate.")
|
|
87
89
|
process.exit(0)
|
|
88
90
|
}
|
|
@@ -93,7 +95,11 @@ if (commandKey === 'auth status') {
|
|
|
93
95
|
}
|
|
94
96
|
if (commandKey === 'workspace list') finish([state.workspace])
|
|
95
97
|
if (commandKey === 'workspace get') finish(state.workspace)
|
|
96
|
-
if (commandKey === 'runtime list') {
|
|
98
|
+
if (commandKey === 'runtime list') {
|
|
99
|
+
requireScope()
|
|
100
|
+
const provider = process.env.DTA_FAKE_MULTICA_RUNTIME_PROVIDER || state.runtime.provider
|
|
101
|
+
finish([{ ...state.runtime, provider }, ...(state.extraRuntimes || [])])
|
|
102
|
+
}
|
|
97
103
|
if (commandKey === 'agent list') { requireScope(); finish(state.agents) }
|
|
98
104
|
if (commandKey === 'skill list') { requireScope(); finish(state.skills) }
|
|
99
105
|
|
|
@@ -216,10 +222,34 @@ const makeIssueRun = (issueId, agentId, description) => {
|
|
|
216
222
|
const agent = state.agents.find((item) => item.id === agentId)
|
|
217
223
|
if (!agent) process.exit(94)
|
|
218
224
|
const marker = description.match(/^marker=(.+)$/m)?.[1] || ''
|
|
225
|
+
// 递增的确定性 created_at:readSmoke 取最新 run 判定,重试产生的新 run 必须排在旧
|
|
226
|
+
// 失败 run 之后(真实平台的时间戳天然递增)。
|
|
227
|
+
const sequence = state.sequence.task
|
|
219
228
|
const taskId = id('task')
|
|
229
|
+
const createdAt = new Date(sequence * 60_000).toISOString()
|
|
220
230
|
const loaded = (agent.skill_ids || []).map((skillId) => state.skills.find((item) => item.id === skillId)?.name)
|
|
221
231
|
.filter(Boolean).sort()
|
|
232
|
+
// 冷沙箱竞态模拟:前 N 个 smoke run 看不到刚推送的 Skill(B7)。
|
|
233
|
+
const notFoundBudget = Number(process.env.DTA_FAKE_MULTICA_SKILL_NOT_FOUND_RUNS || 0)
|
|
234
|
+
const notFoundUsed = state.fired.filter((item) => item === 'skill-not-found').length
|
|
235
|
+
if (marker && notFoundBudget > notFoundUsed) {
|
|
236
|
+
state.fired.push('skill-not-found')
|
|
237
|
+
const missing = loaded[0] || 'dta-basic-behavior'
|
|
238
|
+
const messages = [
|
|
239
|
+
{ type: 'tool_use', tool: 'read_file', input: { text: `Loading skill '${missing}' (SKILL.md)` } },
|
|
240
|
+
{ type: 'tool_result', tool: 'read_file', content: `Skill view failed: Skill '${missing}' not found` },
|
|
241
|
+
{ type: 'text', content: 'Only the dws skill is available in this sandbox; cannot execute the verification.' },
|
|
242
|
+
].map((item, index) => ({ task_id: taskId, issue_id: issueId, seq: index + 1, ...item }))
|
|
243
|
+
return {
|
|
244
|
+
id: taskId, issue_id: issueId, agent_id: agentId, status: 'failed',
|
|
245
|
+
created_at: createdAt, messages,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
222
248
|
const reply = JSON.stringify({ schema: 'dta-multica-load-smoke@1', marker, loaded })
|
|
249
|
+
if (marker && process.env.DTA_FAKE_MULTICA_SMOKE_NO_COMMENT !== '1') {
|
|
250
|
+
state.comments ||= {}
|
|
251
|
+
;(state.comments[issueId] ||= []).push({ id: id('file'), issue_id: issueId, content: reply })
|
|
252
|
+
}
|
|
223
253
|
const resultMode = process.env.DTA_FAKE_MULTICA_SMOKE_RESULT_FILE || ''
|
|
224
254
|
const resultName = resultMode === 'json-force' ? 'result.json' : 'result.md'
|
|
225
255
|
const resultPath = `/home/user/multica_workspaces/ws-contract-deploy/${taskId}/workdir/${resultName}`
|
|
@@ -256,7 +286,25 @@ const makeIssueRun = (issueId, agentId, description) => {
|
|
|
256
286
|
].join('\n') } },
|
|
257
287
|
{ type: 'tool_use', tool: 'bash', input: { command: `multica issue status ${issueId} in_review` } },
|
|
258
288
|
]
|
|
259
|
-
|
|
289
|
+
// 云 runtime 词汇(B8 实测轨迹):skill 装载走 read_file + tool_result 的
|
|
290
|
+
// **Skill loaded**,命令走 terminal 的 `$ ` 前缀文本;穿插无害噪声命令。
|
|
291
|
+
const cloudSmokeMessages = [
|
|
292
|
+
{ type: 'tool_use', tool: 'terminal', input: { text: `$ multica issue get ${issueId} --output json` } },
|
|
293
|
+
...loaded.flatMap((name) => [
|
|
294
|
+
{ type: 'tool_use', tool: 'read_file', input: { text: `Loading skill '${name}' (SKILL.md)` } },
|
|
295
|
+
{ type: 'tool_result', tool: 'read_file', content: `**Skill loaded** - Name: ${name}` },
|
|
296
|
+
]),
|
|
297
|
+
{ type: 'tool_use', tool: 'terminal', input: { text: '$ ls -la' } },
|
|
298
|
+
...(process.env.DTA_FAKE_MULTICA_SMOKE_UNSAFE === '1'
|
|
299
|
+
? [{ type: 'tool_use', tool: 'dws', input: { command: 'contact current-user' } }] : []),
|
|
300
|
+
{ type: 'text', content: 'Required skills loaded; posting the structured result.' },
|
|
301
|
+
{ type: 'tool_use', tool: 'write', input: { filePath: resultPath, content: reply } },
|
|
302
|
+
{ type: 'tool_use', tool: 'terminal', input: { text: `$ multica issue comment add ${issueId} --content-file result.json` } },
|
|
303
|
+
{ type: 'tool_result', tool: 'terminal', content: `Comment added to issue ${issueId}.` },
|
|
304
|
+
{ type: 'tool_use', tool: 'terminal', input: { text: `$ multica issue status ${issueId} in_review` } },
|
|
305
|
+
{ type: 'text', content: 'Load smoke result posted to the current issue.' },
|
|
306
|
+
]
|
|
307
|
+
const hostSmokeMessages = [
|
|
260
308
|
{ type: 'tool_use', tool: 'bash', input: { command: `multica issue get ${issueId} --output json` } },
|
|
261
309
|
{ type: 'tool_use', tool: 'bash', input: { command: `multica issue metadata list ${issueId} --output json` } },
|
|
262
310
|
{ type: 'tool_use', tool: 'bash', input: { command: `multica issue comment list ${issueId} --recent 10 --output json` } },
|
|
@@ -268,6 +316,20 @@ const makeIssueRun = (issueId, agentId, description) => {
|
|
|
268
316
|
...responseTools,
|
|
269
317
|
{ type: 'text', content: 'Load smoke result posted to the current issue.' },
|
|
270
318
|
]
|
|
319
|
+
// 伪造轨迹:banner 只出现在 terminal echo 的 tool_result 里(模型可指定的内容),
|
|
320
|
+
// 没有任何 read_file 装载结果——判定必须拒绝把它当装载证据。
|
|
321
|
+
const cloudSpoofMessages = [
|
|
322
|
+
...loaded.flatMap((name) => [
|
|
323
|
+
{ type: 'tool_use', tool: 'terminal', input: { text: `$ echo "**Skill loaded** - Name: ${name}"` } },
|
|
324
|
+
{ type: 'tool_result', tool: 'terminal', content: `**Skill loaded** - Name: ${name}` },
|
|
325
|
+
]),
|
|
326
|
+
{ type: 'text', content: 'Required skills loaded; posting the structured result.' },
|
|
327
|
+
{ type: 'tool_use', tool: 'write', input: { filePath: resultPath, content: reply } },
|
|
328
|
+
{ type: 'tool_use', tool: 'terminal', input: { text: `$ multica issue comment add ${issueId} --content-file result.json` } },
|
|
329
|
+
]
|
|
330
|
+
const vocabMode = process.env.DTA_FAKE_MULTICA_TRACE_VOCAB || ''
|
|
331
|
+
const smokeMessages = vocabMode === 'cloud' ? cloudSmokeMessages
|
|
332
|
+
: vocabMode === 'cloud-spoof' ? cloudSpoofMessages : hostSmokeMessages
|
|
271
333
|
const conversationMessages = [
|
|
272
334
|
{ type: 'tool_use', tool: 'skill', input: { name: 'release-manager' } },
|
|
273
335
|
{ type: 'text', content: 'Multica contract answer from the assigned Agent.' },
|
|
@@ -277,7 +339,7 @@ const makeIssueRun = (issueId, agentId, description) => {
|
|
|
277
339
|
}))
|
|
278
340
|
return {
|
|
279
341
|
id: taskId, issue_id: issueId, agent_id: agentId, status: 'completed',
|
|
280
|
-
created_at:
|
|
342
|
+
created_at: createdAt, messages,
|
|
281
343
|
}
|
|
282
344
|
}
|
|
283
345
|
|
|
@@ -303,19 +365,47 @@ if (commandKey === 'issue comment add') {
|
|
|
303
365
|
if (!issue) process.exit(94)
|
|
304
366
|
const content = flag('--content')
|
|
305
367
|
if (!content.includes(`mention://agent/${issue.assignee_id}`)) process.exit(95)
|
|
306
|
-
|
|
368
|
+
const newRun = makeIssueRun(issue.id, issue.assignee_id, issue.description || '')
|
|
369
|
+
// 复刻真实平台:mention 触发的 run 是异步创建的,前 N 次 issue runs 读还看不到它。
|
|
370
|
+
const delayReads = Number(process.env.DTA_FAKE_MULTICA_RUN_DELAY_READS || 0)
|
|
371
|
+
if (delayReads > 0) {
|
|
372
|
+
state.delayedRuns ||= {}
|
|
373
|
+
state.delayedRuns[issue.id] = { remaining: delayReads, run: newRun }
|
|
374
|
+
} else {
|
|
375
|
+
issue.runs.push(newRun)
|
|
376
|
+
}
|
|
307
377
|
issue.status = 'done'
|
|
308
|
-
|
|
378
|
+
state.comments ||= {}
|
|
379
|
+
const comment = { id: id('file'), issue_id: issue.id, content }
|
|
380
|
+
;(state.comments[issue.id] ||= []).push(comment)
|
|
381
|
+
finish(comment, true)
|
|
382
|
+
}
|
|
383
|
+
if (commandKey === 'issue comment list') {
|
|
384
|
+
requireScope()
|
|
385
|
+
const issue = state.issues.find((item) => item.id === core[3])
|
|
386
|
+
if (!issue) process.exit(94)
|
|
387
|
+
finish((state.comments || {})[issue.id] || [])
|
|
309
388
|
}
|
|
310
389
|
if (commandKey === 'issue runs') {
|
|
311
390
|
requireScope()
|
|
312
391
|
const issue = state.issues.find((item) => item.id === core[2])
|
|
313
392
|
if (!issue) process.exit(94)
|
|
393
|
+
// 延迟中的 run 每被读一次倒计数一次,归零才可见(模拟异步 run 创建)。
|
|
394
|
+
const delayed = (state.delayedRuns || {})[issue.id]
|
|
395
|
+
let materialized = false
|
|
396
|
+
if (delayed) {
|
|
397
|
+
delayed.remaining -= 1
|
|
398
|
+
if (delayed.remaining <= 0) {
|
|
399
|
+
issue.runs.push(delayed.run)
|
|
400
|
+
delete state.delayedRuns[issue.id]
|
|
401
|
+
}
|
|
402
|
+
materialized = true
|
|
403
|
+
}
|
|
314
404
|
// Simulate an overrunning run: report the task as still running so the poll loop times out.
|
|
315
405
|
// The persisted run stays terminal, so a later `--status --run-id` resume sees it completed.
|
|
316
406
|
const forceRunning = process.env.DTA_FAKE_MULTICA_RUN_FORCE_RUNNING === '1'
|
|
317
407
|
finish(issue.runs.map(({ messages, ...run }) =>
|
|
318
|
-
forceRunning ? { ...run, status: 'running' } : run))
|
|
408
|
+
forceRunning ? { ...run, status: 'running' } : run), materialized)
|
|
319
409
|
}
|
|
320
410
|
if (commandKey === 'issue run-messages') {
|
|
321
411
|
requireScope()
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"runtime": {
|
|
10
10
|
"id": "runtime-contract-deploy",
|
|
11
11
|
"workspace_id": "ws-contract-deploy",
|
|
12
|
-
"name": "Contract
|
|
13
|
-
"provider": "
|
|
12
|
+
"name": "Contract OpenCode Runtime",
|
|
13
|
+
"provider": "opencode",
|
|
14
14
|
"status": "online"
|
|
15
15
|
},
|
|
16
16
|
"authenticated": true,
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"runtime": {
|
|
10
10
|
"id": "runtime-contract-codex",
|
|
11
11
|
"workspace_id": "ws-contract-release",
|
|
12
|
-
"name": "Contract
|
|
13
|
-
"provider": "
|
|
12
|
+
"name": "Contract OpenCode Runtime",
|
|
13
|
+
"provider": "opencode",
|
|
14
14
|
"status": "online"
|
|
15
15
|
},
|
|
16
16
|
"agent": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"profile": "dev",
|
|
30
30
|
"workspaceIdFrom": "env:MULTICA_WORKSPACE_ID",
|
|
31
31
|
"runtimeIdFrom": "env:MULTICA_RUNTIME_ID",
|
|
32
|
-
"agentIdFrom": "env:
|
|
32
|
+
"agentIdFrom": "env:DTA_MULTICA_DEV_AGENT_ID"
|
|
33
33
|
},
|
|
34
34
|
"authority": {
|
|
35
35
|
"dwsProfileFrom": "env:DTA_DWS_PROFILE",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"capability": "basic-skill",
|
|
13
13
|
"prompt": "这是基础行为加载探针。只依据已经进入当前上下文的正文,严格输出 skill=<name>@<metadata.version>;如果正文不可见,只输出 skill=not-loaded。禁止猜测,不要补充解释。",
|
|
14
14
|
"expectations": [
|
|
15
|
-
{ "id": "exact-basic-version", "type": "equals", "value": "skill=dta-basic-behavior@0.13.
|
|
15
|
+
{ "id": "exact-basic-version", "type": "equals", "value": "skill=dta-basic-behavior@0.13.1" }
|
|
16
16
|
],
|
|
17
17
|
"manualChecks": []
|
|
18
18
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xdxer/dingtalk-agent",
|
|
3
|
-
"version": "0.1.5-beta.
|
|
3
|
+
"version": "0.1.5-beta.9",
|
|
4
4
|
"description": "把 Agent 装配成钉钉数字员工:Skill 决策,CLI 固定事务边界,Agent 本体运行在哪个 Managed Agent Platform 上是可替换的。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dingtalk",
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"evals/baselines/2026-07-17/personal-event-live-readiness-summary.json",
|
|
40
40
|
"evals/baselines/2026-07-17/agent-enhance-opencode-dogfood-summary.json",
|
|
41
41
|
"lab",
|
|
42
|
+
"docs/AGENT-IN-PRODUCTION.md",
|
|
43
|
+
"docs/PLATFORM-GUARDRAILS.md",
|
|
42
44
|
"docs/ARCHITECTURE.md",
|
|
43
45
|
"docs/INSTALLATION.md",
|
|
44
46
|
"docs/PRIOR-ART.md",
|
|
@@ -59,9 +61,10 @@
|
|
|
59
61
|
"eval:behavior": "npm run build && node evals/run-shadow-evals.mjs",
|
|
60
62
|
"eval:opencode": "npm run build && node dist/bin/dingtalk-agent.js lab eval --engine opencode --workspace lab/robot-eval/workspace --suite lab/robot-eval/suite.json --lanes stateless --execute --yes --json",
|
|
61
63
|
"prepack": "npm run build",
|
|
62
|
-
"release:check": "npm run build && node scripts/check-skill-versions.mjs && node scripts/check-docs.mjs && node evals/run-contract-evals.mjs --full && node scripts/release-readiness.mjs --json",
|
|
64
|
+
"release:check": "npm run build && node scripts/check-skill-versions.mjs && node scripts/check-docs.mjs && node scripts/check-schedule.mjs && node evals/run-contract-evals.mjs --full && node scripts/release-readiness.mjs --json",
|
|
63
65
|
"check:skill-versions": "node scripts/check-skill-versions.mjs",
|
|
64
66
|
"check:docs": "node scripts/check-docs.mjs",
|
|
67
|
+
"check:schedule": "node scripts/check-schedule.mjs",
|
|
65
68
|
"eval:contract:full": "npm run build && node evals/run-contract-evals.mjs --full"
|
|
66
69
|
},
|
|
67
70
|
"devDependencies": {
|
|
@@ -3,7 +3,7 @@ name: dta-agent-compose
|
|
|
3
3
|
description: 当用户要创建、新建、装配一个 Agent 或钉钉数字员工——包括把 GitHub 仓库、本地文件夹或钉钉文档定义成 Agent,或要审计、补齐、优化 Agent 的 AGENTS.md、本体职责、岗位 Skills、记忆/知识/产物存储、DWS 权限绑定与「谁叫醒它」的驱动节律时使用。即使尚未 init Workspace,也按 dingtalk-agent 的 AgentDefinition 范式给出可运行的最小装配方案;声明驱动节律但不实现触发器本身。
|
|
4
4
|
compatibility: Requires dingtalk-agent on PATH; remote DingTalk documents require authenticated dws.
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.15.
|
|
6
|
+
version: "0.15.2"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# 装配一个可工作的钉钉数字员工 Agent
|
|
@@ -49,6 +49,22 @@
|
|
|
49
49
|
|
|
50
50
|
**② 是心脏。** 判断标准:如果你写不出"完成 = 数据里某个可查的状态",那你要的不是可维护的定时任务,而是一根迟早断的接力链——退回去重新设计判据,别加定时器。
|
|
51
51
|
|
|
52
|
+
### 唤醒提示词带策略,Skill 只带能力
|
|
53
|
+
|
|
54
|
+
①里那句"唤醒只带类别、不带工作清单",还有一层同样重要的推论:**投递策略属于这条节律,不属于 Skill。**
|
|
55
|
+
|
|
56
|
+
分界线只有一句:**换个场景还成立的是能力,换个场景就得改的是策略。**
|
|
57
|
+
|
|
58
|
+
| | 归 Skill(原子能力) | 归唤醒提示词(这条节律的策略) |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| 日报 | 怎么采数、怎么归纳、怎么出图出文、降级链 | 发到**哪个群**、几点发、只发群还是也发单聊 |
|
|
61
|
+
| 巡检 | 怎么判健康、异常长什么样 | 卡住了**报给谁** |
|
|
62
|
+
| 交付 | 怎么生成、怎么回读确认送达 | **收件人**、窗口、频次 |
|
|
63
|
+
|
|
64
|
+
Skill 里仍然写死通用红线(如"默认只发主人单聊;发群必须本次明确指定"),而那个"明确指定"由唤醒提示词提供——提示词来自版本化的 `schedules` 声明,本身就是可信来源,不是从消息正文猜来的。
|
|
65
|
+
|
|
66
|
+
**把群 ID 焊进 `SKILL.md` 的代价**:换个群、加个收件人、挪个时间,都要改 Skill 并重新 `dta deploy` 一次能力包;同一个能力也没法同时服务多个投递场景(早上发 A 群、晚上发 B 群就得复制一份 Skill)。策略留在 schedule 里,改调度不动能力。
|
|
67
|
+
|
|
52
68
|
---
|
|
53
69
|
|
|
54
70
|
## 在仓库里声明:`dingtalk-agent.json#schedules`
|
|
@@ -94,9 +110,11 @@
|
|
|
94
110
|
## 三条与平台无关的硬约束
|
|
95
111
|
|
|
96
112
|
- **定时不可靠是常态**:平台会塌缩漏掉的 fire、跳过迟到过久的(Multica 阈值 5 分钟)。判据必须写成「它今天/本周发生了没有」,不是「那一拍跑了没」。
|
|
97
|
-
-
|
|
113
|
+
- **外发类节律没有锁**:外发的留痕写在动作之后,两条节律**同一时刻**并发会把同一条消息发两遍给第三方。**多节律必须错开 fire 时刻**(分钟×小时都不相交才算错开——`0 0-6` 与 `0 23` 同分钟但不同刻,不算撞;`dta schedule show` 现在按真 fire 时刻判,不再只看分钟),并在 Skill 里写死"一拍内同一件外发绝不执行第二次"。**所有节律写同一个 timezone**:跨时区的 `0 9 * * *`@Asia/Shanghai 与 `0 1 * * *`@UTC 是同一时刻,本地算不出来,一律当撞车挡住部署。
|
|
98
114
|
- **Agent 不自建定时器、不自改节律**:节律是可审计的部署产物。在线 Run 只能把新节律整理成候选交给 operator——和"在线 Run 不能热改身份/权限/Skill"是同一条边界。
|
|
99
115
|
|
|
116
|
+
**再两条排布经验**(真实生产换来,详见 [AGENT-IN-PRODUCTION.md](../../../../docs/AGENT-IN-PRODUCTION.md) §1):**有时效的轻活排前,别让重活饿死它**——把每次都超时的重活排在一拍最前,后面的对账/收件会整轮轮不到(真事故:留痕对账排夜拍第 7 位、夜间重建每拍超时,结果整夜没跑到);正解是轻活/有时效的排前,或给重活单独一条节律、独立预算。**入口是脉冲、出口是均匀**——用户常集中在某时段涌入,产能要按峰值窗口算,按日均算会在高峰欠账。
|
|
117
|
+
|
|
100
118
|
---
|
|
101
119
|
|
|
102
120
|
## 谁来设定:三角色
|
|
@@ -119,6 +137,8 @@
|
|
|
119
137
|
- [ ] ③ 是增量 delta 不是重建;单拍在预算内、能收敛
|
|
120
138
|
- [ ] ④ 完成靠效果为证(write/receipt/水位),不认自述、不认空读
|
|
121
139
|
- [ ] ⑤ 有一条监督节律盯收敛、卡住报人
|
|
122
|
-
- [ ]
|
|
140
|
+
- [ ] 多条节律错开 fire 时刻、且统一一个 timezone(`dta schedule show` exit 0);外发类有"一拍内不重复"约束
|
|
141
|
+
- [ ] 有时效的轻活排在重活前面(或重活单独一条节律);别让排最前的超时重活饿死尾部轻活;产能按峰值窗口算不按日均
|
|
142
|
+
- [ ] 每条 `mode` 的可计量性心里有数(`schedule show` 标 metered;ephemeral 不记账,成本要另建观测层——见 AGENT-IN-PRODUCTION.md)
|
|
123
143
|
- [ ] 目标平台 `PLATFORM.md` 确认支持;不支持记为缺口,不静默降级
|
|
124
144
|
- [ ] `dta info --json` 回读校验通过(5 段 cron、合法 mode、name 唯一)
|
|
@@ -3,7 +3,7 @@ name: dta-basic-behavior
|
|
|
3
3
|
description: 当 Agent 作为钉钉里的真实员工处理消息、@、单聊、群聊、任务、确认、记忆或协作事项时使用;即使当前目录没有初始化 Workspace、没有 CONTEXT.md,也先用本 Skill 判断响应资格、意图、作用域、风险、授权、隐私与完成状态。Prepared Run 中只通过 dingtalk-agent 原子动作,普通会话的钉钉产品操作按需使用 dws。
|
|
4
4
|
compatibility: Requires dingtalk-agent on PATH; DingTalk side effects require dws.
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.13.
|
|
6
|
+
version: "0.13.1"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# 钉钉数字员工基础行为
|
|
@@ -34,7 +34,7 @@ Provider 只改变“内容放在哪里”,不改变下面的筛选规则。
|
|
|
34
34
|
|
|
35
35
|
在线 Run 只采证并提出局部候选;离线流程把真实纠正加入回归集,执行 baseline/candidate 对照,生成候选和报告。通过 gate 也不自动扩大权限或热替换当前 Run。
|
|
36
36
|
|
|
37
|
-
长期写入至少携带:`source`、`scope`、`recorded_at`、`confidence` 和 `supersedes`。没有值得保存的内容时,`nothing-to-save`
|
|
37
|
+
长期写入至少携带:`source`、`scope`、`recorded_at`、`confidence` 和 `supersedes`。没有值得保存的内容时,`nothing-to-save` 是正确结果——但**这一步跑过、判了没什么可存,也要留一条痕迹**(哪怕就是 `nothing-to-save`)。否则「判过、无可存」和「压根没跑」从外面分不出来,这条记忆能力到底在不在工作就永远查不清。完成要有证据,不完成也要有证据。
|
|
38
38
|
|
|
39
39
|
任务 checkpoint 与长期记忆必须分开:完成后可从任务证据中提炼稳定事实或 Skill 候选,但不能把进度、等待状态和临时计划整体写进 `MEMORY.md`。
|
|
40
40
|
|