@xdxer/dingtalk-agent 0.1.1 → 0.1.4-beta.0
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/LICENSE +21 -0
- package/README.md +247 -76
- package/dist/bin/dingtalk-agent.js +763 -0
- package/dist/bin/dingtalk-agent.js.map +1 -0
- package/dist/src/actions.js +562 -0
- package/dist/src/actions.js.map +1 -0
- package/dist/src/boot.js +70 -0
- package/dist/src/boot.js.map +1 -0
- package/dist/src/bootstrap.js +144 -0
- package/dist/src/bootstrap.js.map +1 -0
- package/dist/src/config.js +86 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/doctor.js +166 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/driver.js +45 -0
- package/dist/src/driver.js.map +1 -0
- package/{src → dist/src}/duty.js +42 -44
- package/dist/src/duty.js.map +1 -0
- package/dist/src/dws.js +270 -0
- package/dist/src/dws.js.map +1 -0
- package/dist/src/events.js +233 -0
- package/dist/src/events.js.map +1 -0
- package/dist/src/fields.js +132 -0
- package/dist/src/fields.js.map +1 -0
- package/dist/src/init.js +41 -0
- package/dist/src/init.js.map +1 -0
- package/dist/src/kb.js +240 -0
- package/dist/src/kb.js.map +1 -0
- package/dist/src/package-root.js +17 -0
- package/dist/src/package-root.js.map +1 -0
- package/dist/src/runs.js +79 -0
- package/dist/src/runs.js.map +1 -0
- package/dist/src/sessions.js +668 -0
- package/dist/src/sessions.js.map +1 -0
- package/dist/src/setup.js +101 -0
- package/dist/src/setup.js.map +1 -0
- package/dist/src/skill-manager.js +288 -0
- package/dist/src/skill-manager.js.map +1 -0
- package/dist/src/skills.js +200 -0
- package/dist/src/skills.js.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/waits.js +945 -0
- package/dist/src/waits.js.map +1 -0
- package/dist/src/workspace.js +173 -0
- package/dist/src/workspace.js.map +1 -0
- package/docs/ARCHITECTURE.md +217 -0
- package/docs/INSTALLATION.md +135 -0
- package/docs/MINIMAL-WORKSPACE-V1.md +172 -0
- package/docs/OPEN-SOURCE-REFERENCES.md +107 -0
- package/docs/SELF-TEST.md +252 -0
- package/docs/architecture/dingtalk-agent-blueprint.png +0 -0
- package/docs/architecture/dingtalk-agent-blueprint.svg +144 -0
- package/docs/architecture/durable-async-agent-runtime.png +0 -0
- package/docs/architecture/durable-async-agent-runtime.svg +234 -0
- package/docs//345/206/205/347/275/221/345/256/236/347/233/270.md +77 -0
- package/evals/baselines/2026-07-14/behavior-summary.json +28 -0
- package/evals/baselines/2026-07-14/contract-summary.json +18 -0
- package/evals/baselines/2026-07-14/live-canary-summary.json +25 -0
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/SKILL.md +72 -0
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/action-contract.md +31 -0
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/event-to-behavior.md +22 -0
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/memory-and-evolution.md +25 -0
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/runtime-modes.md +34 -0
- package/evals/baselines/2026-07-15/task-lifecycle-summary.json +50 -0
- package/evals/evals.json +316 -0
- package/evals/fixtures/dm-ambiguous-send.json +4 -0
- package/evals/fixtures/dm-blocked.json +4 -0
- package/evals/fixtures/dm-clear.json +4 -0
- package/evals/fixtures/dm-discussion.json +4 -0
- package/evals/fixtures/dm-doc-write-no-tool.json +4 -0
- package/evals/fixtures/dm-long-task-ack.json +4 -0
- package/evals/fixtures/dm-nonblocking-gap.json +4 -0
- package/evals/fixtures/dm-structured-task.json +4 -0
- package/evals/fixtures/group.json +10 -0
- package/evals/fixtures/mentioned.json +3 -0
- package/evals/run-contract-evals.mjs +1106 -0
- package/evals/run-shadow-evals.mjs +267 -0
- package/evals/runners/README.md +66 -0
- package/evals/runners/claude-shadow.mjs +533 -0
- package/evals/schemas/action-request.schema.json +77 -0
- package/evals/shadow-evals.json +133 -0
- package/package.json +28 -6
- package/skills/AGENTS.md +21 -3
- package/skills/dingtalk-basic-behavior/SKILL.md +86 -0
- package/skills/dingtalk-basic-behavior/assets/task-checkpoint.md +37 -0
- package/skills/dingtalk-basic-behavior/references/action-contract.md +31 -0
- package/skills/dingtalk-basic-behavior/references/event-to-behavior.md +24 -0
- package/skills/dingtalk-basic-behavior/references/memory-and-evolution.md +27 -0
- package/skills/dingtalk-basic-behavior/references/runtime-modes.md +34 -0
- package/skills/dingtalk-basic-behavior/references/task-lifecycle.md +108 -0
- package/skills//345/237/272/347/241/200/350/241/214/344/270/272.md +44 -0
- package/skills//345/277/203/350/267/263.md +11 -0
- package/skills//346/266/210/346/201/257.md +14 -14
- package/skills//350/257/204/346/265/213.md +14 -1
- package/skills//351/222/211/351/222/211.md +3 -2
- package/templates/behaviors/basic.json +68 -0
- package/templates/fields/default/field.json +25 -0
- package/bin/dingtalk-agent.js +0 -289
- package/src/boot.js +0 -65
- package/src/config.js +0 -42
- package/src/dws.js +0 -192
- package/src/init.js +0 -84
- package/src/kb.js +0 -221
- package/src/runs.js +0 -77
package/dist/src/dws.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// dws 硬化包装 —— 这一层是「拦」(TypeScript source)。
|
|
2
|
+
//
|
|
3
|
+
// **上面的每个模块都必须走它,不许自己 spawn dws。**
|
|
4
|
+
//
|
|
5
|
+
// ════════════════════════════════════════════════════════════════
|
|
6
|
+
// 【六条工程纪律】—— 每一条都是踩出来的,违反必出错,而且是【静默】出错
|
|
7
|
+
// ════════════════════════════════════════════════════════════════
|
|
8
|
+
//
|
|
9
|
+
// 1. **拿不到就响亮地死,绝不当成"是空的"。**
|
|
10
|
+
// 不同接口把数据藏在不同层级。读错层级 → 拿到 undefined → 当成"表是空的"
|
|
11
|
+
// → **一整天的数据可以这么无声丢掉,而且不报错。**
|
|
12
|
+
// 「查到 0 条」和「读错地方了」**必须分得开**。分不开就 die。
|
|
13
|
+
//
|
|
14
|
+
// 2. **分页会静默截断。** 默认页大小有上限,超了就悄悄少给你,
|
|
15
|
+
// **而且不一定有信号告诉你被截断了。**
|
|
16
|
+
// **拿不到分页信号 ≠ 没有下一页,那叫「没有信号」。**
|
|
17
|
+
// **半张表算出来的结果是错的,不是"差不多"。**
|
|
18
|
+
//
|
|
19
|
+
// 3. **创建类:报错了也可能其实建成功了。**
|
|
20
|
+
// **绝不看返回信封** —— 按名回查 + 内容回读定成败。
|
|
21
|
+
// 推论:**创建类永不盲重试**(会造重复件),失败先按幂等键回查。
|
|
22
|
+
//
|
|
23
|
+
// 4. **写操作报错 ≠ 失败。** 回读实态才定成败。返回 success 也不算验收。
|
|
24
|
+
//
|
|
25
|
+
// 5. **字段读写不对称。** 写进去的形状 ≠ 读回来的形状。**写完必回读。**
|
|
26
|
+
//
|
|
27
|
+
// 6. **markdown 会被重排版。重排版不是漂移。**
|
|
28
|
+
// 判漂移必须剥掉 markdown 符号只看文字流 —— 用行级 diff 会每次都误报,
|
|
29
|
+
// **然后你就会开始无脑 --force,真漂移也一起覆盖掉。**
|
|
30
|
+
//
|
|
31
|
+
// 一句话:**所有这些坑的形状都一样 —— 它不报错,它给你一个看起来很正常的答案。**
|
|
32
|
+
// 这一层做的就是:**把「看起来正常」和「真的正常」分开。**
|
|
33
|
+
import { spawnSync } from 'node:child_process';
|
|
34
|
+
export class Blocked extends Error {
|
|
35
|
+
}
|
|
36
|
+
export function die(msg) {
|
|
37
|
+
console.error('[FAIL] ' + msg);
|
|
38
|
+
throw new Blocked(msg);
|
|
39
|
+
}
|
|
40
|
+
const sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
41
|
+
/** 跑一条 dws。限流退避;超时不当成"空";坏响应响亮地死。 */
|
|
42
|
+
export function run(args, { allowFail = false, timeout = 180_000, retries = 2 } = {}) {
|
|
43
|
+
let last = '';
|
|
44
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
45
|
+
const p = spawnSync('dws', [...args, '--format', 'json'], { encoding: 'utf8', timeout, maxBuffer: 64 * 1024 * 1024 });
|
|
46
|
+
if (p.error) {
|
|
47
|
+
if (p.error.code === 'ENOENT') {
|
|
48
|
+
if (allowFail)
|
|
49
|
+
return null;
|
|
50
|
+
die('dws 跑不起来 —— 先 export PATH="$HOME/.local/bin:$PATH"');
|
|
51
|
+
}
|
|
52
|
+
last = String(p.error);
|
|
53
|
+
sleep(2000 * (attempt + 1));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const out = p.stdout || p.stderr || '';
|
|
57
|
+
last = out.slice(0, 300);
|
|
58
|
+
const i = out.indexOf('{');
|
|
59
|
+
if (i < 0) {
|
|
60
|
+
sleep(2000 * (attempt + 1));
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
let d;
|
|
64
|
+
try {
|
|
65
|
+
d = JSON.parse(out.slice(i, lastBrace(out) + 1));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
sleep(2000 * (attempt + 1));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const err = d.error || {};
|
|
72
|
+
if (err && (err.code || err.message)) {
|
|
73
|
+
const s = JSON.stringify(err).toLowerCase();
|
|
74
|
+
if (s.includes('limit') || s.includes('frequen') || s.includes('flow')) {
|
|
75
|
+
sleep(3000 * (attempt + 1));
|
|
76
|
+
continue; // 限流 → 退避
|
|
77
|
+
}
|
|
78
|
+
if (allowFail)
|
|
79
|
+
return null;
|
|
80
|
+
die(`dws 失败 ${args.slice(0, 4).join(' ')}\n ${JSON.stringify(err).slice(0, 300)}`);
|
|
81
|
+
}
|
|
82
|
+
if (p.status !== 0) {
|
|
83
|
+
if (allowFail)
|
|
84
|
+
return null;
|
|
85
|
+
die(`dws 以退出码 ${p.status} 结束,但没有结构化 error: ${args.slice(0, 4).join(' ')}\n ${last}`);
|
|
86
|
+
}
|
|
87
|
+
return d;
|
|
88
|
+
}
|
|
89
|
+
if (allowFail)
|
|
90
|
+
return null;
|
|
91
|
+
die(`dws 无响应(重试 ${retries} 次) ${args.slice(0, 4).join(' ')}\n ${last}`);
|
|
92
|
+
}
|
|
93
|
+
function lastBrace(s) {
|
|
94
|
+
// dws 有时在 JSON 后面粘一行 WARN / INFO —— 从最后一个 } 截断
|
|
95
|
+
for (let i = s.length - 1; i >= 0; i--)
|
|
96
|
+
if (s[i] === '}')
|
|
97
|
+
return i;
|
|
98
|
+
return s.length - 1;
|
|
99
|
+
}
|
|
100
|
+
// ──────────────── 三个层级,各读各的 ────────────────
|
|
101
|
+
const inner = (d) => (d && typeof d.data === 'object' && d.data !== null ? d.data : d);
|
|
102
|
+
/** 列表类查询的记录。拿不到就 die —— 绝不当成"表是空的"。 */
|
|
103
|
+
export function records(d) {
|
|
104
|
+
const x = inner(d);
|
|
105
|
+
if (!('records' in x)) {
|
|
106
|
+
die(`返回里没有 records(顶层键=${Object.keys(d)}) —— 接口形状可能变了。` +
|
|
107
|
+
`**绝不把它当成【表是空的】。**`);
|
|
108
|
+
}
|
|
109
|
+
if (x.hasMore || x.partial) {
|
|
110
|
+
die('分页未收敛 —— **部分结果不许当完整结果用。** 半张表算出来的是错的。');
|
|
111
|
+
}
|
|
112
|
+
return x.records || [];
|
|
113
|
+
}
|
|
114
|
+
/** 文档正文。**层级和列表类查询不一样** —— 两种形状都兜住,拿不到就 die。 */
|
|
115
|
+
export function markdown(d) {
|
|
116
|
+
const md = inner(d).markdown ?? d.markdown;
|
|
117
|
+
if (md === undefined || md === null) {
|
|
118
|
+
die(`拿不到文档正文(顶层键=${Object.keys(d)}) —— **绝不当成【这篇是空的】。**`);
|
|
119
|
+
}
|
|
120
|
+
return md;
|
|
121
|
+
}
|
|
122
|
+
/** 目录节点。 */
|
|
123
|
+
export const nodes = (d) => inner(d).nodes || [];
|
|
124
|
+
/**
|
|
125
|
+
* 待办翻页 —— **这是唯一有分页信号的读法。**
|
|
126
|
+
*
|
|
127
|
+
* ⚠️ **不要把页开大**。超过这个大小,分页信号会消失,
|
|
128
|
+
* 你拿到 undefined、当成 false,然后**静默漏掉剩下的**。
|
|
129
|
+
* 心跳的收件靠它 —— 漏了循环件就是「静默收工且不自知」。
|
|
130
|
+
*/
|
|
131
|
+
export function todosPage(status = 'false', page = 1) {
|
|
132
|
+
const d = run(['todo', 'task', 'list', '--status', status, '--size', '20', '--page', String(page)]);
|
|
133
|
+
const r = d.result || {};
|
|
134
|
+
if (!('todoCards' in r)) {
|
|
135
|
+
die(`待办返回里没有 todoCards(result 键=${Object.keys(r)}) —— 绝不当成【没有待办】。`);
|
|
136
|
+
}
|
|
137
|
+
return { cards: r.todoCards || [], more: Boolean(r.hasMore) };
|
|
138
|
+
}
|
|
139
|
+
/** 把待办【拉全】。只看第一页会漏掉循环件。 */
|
|
140
|
+
export function todosAll(status = 'false', maxPages = 20) {
|
|
141
|
+
const out = [];
|
|
142
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
143
|
+
const { cards, more } = todosPage(status, page);
|
|
144
|
+
out.push(...cards);
|
|
145
|
+
if (!more)
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
die(`待办翻页超过 ${maxPages} 页还没收敛 —— 不许把部分结果当完整。`);
|
|
149
|
+
}
|
|
150
|
+
// ──────────────── 创建类:报错也可能成功 → 按名回查 ────────────────
|
|
151
|
+
/** 目录列表可能把扩展名单列,比对时要拼回去。 */
|
|
152
|
+
export function findChild(folder, name) {
|
|
153
|
+
for (const n of nodes(run(['doc', 'list', '--folder', folder, '--page-size', '50']))) {
|
|
154
|
+
const nm = n.name || '', ext = n.extension || '';
|
|
155
|
+
if (nm === name || (ext && `${nm}.${ext}` === name))
|
|
156
|
+
return n.nodeId;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
/** 建文档。**不看返回信封** —— 一律按名回查定成败。 */
|
|
161
|
+
export function createDoc(folder, name, file) {
|
|
162
|
+
const exist = findChild(folder, name);
|
|
163
|
+
if (exist)
|
|
164
|
+
return exist;
|
|
165
|
+
run(['doc', 'create', '--name', name, '--folder', folder,
|
|
166
|
+
'--content-file', file, '--content-format', 'markdown', '--yes'], { allowFail: true }); // ← **报错也可能其实成功了**
|
|
167
|
+
sleep(1200);
|
|
168
|
+
const nid = findChild(folder, name);
|
|
169
|
+
if (!nid)
|
|
170
|
+
die(`建不出文档 ${name} —— 别把它当成【建成功了】。`);
|
|
171
|
+
return nid;
|
|
172
|
+
}
|
|
173
|
+
export const updateDoc = (node, file) => run(['doc', 'update', '--node', node, '--content-file', file,
|
|
174
|
+
'--content-format', 'markdown', '--mode', 'overwrite', '--yes']);
|
|
175
|
+
export const readDoc = (node, profile = '') => markdown(run([
|
|
176
|
+
'doc', 'read', '--node', node, ...profileArgs(profile),
|
|
177
|
+
]));
|
|
178
|
+
export const docInfo = (node, profile = '') => inner(run([
|
|
179
|
+
'doc', 'info', '--node', node, ...profileArgs(profile),
|
|
180
|
+
]));
|
|
181
|
+
// ──────────────── 漂移判定:重排版【不是】漂移 ────────────────
|
|
182
|
+
/**
|
|
183
|
+
* 剥掉 markdown 语法和转义装饰,只留【文字本身】。
|
|
184
|
+
*
|
|
185
|
+
* 钉钉会重排版你推上去的 markdown。**重排版不是漂移。**
|
|
186
|
+
* 用行级 diff 判漂移 → 每次 sync 都误报 →
|
|
187
|
+
* **然后你就会开始无脑 --force,真正的漂移也一起覆盖掉了。**
|
|
188
|
+
*/
|
|
189
|
+
export function words(s) {
|
|
190
|
+
let t = String(s ?? '');
|
|
191
|
+
for (let i = 0; i < 3; i++)
|
|
192
|
+
t = unescapeHtml(t); // 可能被多次转义
|
|
193
|
+
t = t.replace(/&#(\d+);/g, (_, n) => String.fromCharCode(+n));
|
|
194
|
+
t = t.replace(/\[打开\]\([^)]*\)/g, ''); // 钉钉自动加的链接装饰
|
|
195
|
+
return t.replace(/[`*_>#|\\~\-\s]+/g, '');
|
|
196
|
+
}
|
|
197
|
+
function unescapeHtml(s) {
|
|
198
|
+
return s.replace(/&(amp|lt|gt|quot|#39);/g, (m, e) => ({ amp: '&', lt: '<', gt: '>', quot: '"', '#39': "'" })[e]);
|
|
199
|
+
}
|
|
200
|
+
export const same = (a, b) => words(a) === words(b);
|
|
201
|
+
// ──────────────── 数字员工的消息原子动作(typed,目标不可自由拼装) ────────────────
|
|
202
|
+
function profileArgs(profile) {
|
|
203
|
+
return profile ? ['--profile', profile] : [];
|
|
204
|
+
}
|
|
205
|
+
function assertReplyTarget(target) {
|
|
206
|
+
const missing = [];
|
|
207
|
+
if (!target?.conversationId)
|
|
208
|
+
missing.push('conversationId');
|
|
209
|
+
if (!target?.messageId)
|
|
210
|
+
missing.push('messageId');
|
|
211
|
+
if (!target?.senderOpenDingTalkId)
|
|
212
|
+
missing.push('senderOpenDingTalkId');
|
|
213
|
+
if (missing.length)
|
|
214
|
+
die(`固定回复目标缺少 ${missing.join(' / ')}`);
|
|
215
|
+
}
|
|
216
|
+
/** ack 没有服务端 UUID,因此绝不自动重试。 */
|
|
217
|
+
export function ackMessage(target, { emoji = '收到', profile = '' } = {}) {
|
|
218
|
+
assertReplyTarget(target);
|
|
219
|
+
return run([
|
|
220
|
+
'chat', 'message', 'add-emoji',
|
|
221
|
+
'--conversation-id', target.conversationId,
|
|
222
|
+
'--msg-id', target.messageId,
|
|
223
|
+
'--emoji', emoji,
|
|
224
|
+
...profileArgs(profile),
|
|
225
|
+
], { retries: 0 });
|
|
226
|
+
}
|
|
227
|
+
/** reply/ask 共用同一传输;uuid 在调用前已持久化,重试也必须复用。 */
|
|
228
|
+
export function replyMessage(target, text, { uuid = '', profile = '' } = {}) {
|
|
229
|
+
assertReplyTarget(target);
|
|
230
|
+
if (!String(text || '').trim())
|
|
231
|
+
die('回复正文不能为空');
|
|
232
|
+
if (!uuid)
|
|
233
|
+
die('回复前必须先持久化 uuid 幂等键');
|
|
234
|
+
return run([
|
|
235
|
+
'chat', 'message', 'reply',
|
|
236
|
+
'--conversation-id', target.conversationId,
|
|
237
|
+
'--ref-msg-id', target.messageId,
|
|
238
|
+
'--ref-sender', target.senderOpenDingTalkId,
|
|
239
|
+
'--text', text,
|
|
240
|
+
'--uuid', uuid,
|
|
241
|
+
'--ai-tag=true',
|
|
242
|
+
...profileArgs(profile),
|
|
243
|
+
'--yes',
|
|
244
|
+
], { retries: 1 });
|
|
245
|
+
}
|
|
246
|
+
export function readMessageEmotions(messageId, profile = '') {
|
|
247
|
+
return run([
|
|
248
|
+
'chat', 'message', 'list-emotion-replies', '--msg-ids', messageId,
|
|
249
|
+
...profileArgs(profile),
|
|
250
|
+
], { allowFail: true, retries: 0 });
|
|
251
|
+
}
|
|
252
|
+
export function querySendStatus(openTaskId, profile = '') {
|
|
253
|
+
return run([
|
|
254
|
+
'chat', 'message', 'query-send-status', '--open-task-id', openTaskId,
|
|
255
|
+
...profileArgs(profile),
|
|
256
|
+
], { allowFail: true, retries: 1 });
|
|
257
|
+
}
|
|
258
|
+
export function readMessagesByIds(messageId, profile = '') {
|
|
259
|
+
return run([
|
|
260
|
+
'chat', 'message', 'list-by-ids', '--msg-ids', messageId,
|
|
261
|
+
...profileArgs(profile),
|
|
262
|
+
], { allowFail: true, retries: 1 });
|
|
263
|
+
}
|
|
264
|
+
/** 外发前核对 profile 对应的真实执行身份。 */
|
|
265
|
+
export function getSelf(profile = '') {
|
|
266
|
+
return run([
|
|
267
|
+
'contact', 'user', 'get-self', ...profileArgs(profile),
|
|
268
|
+
], { retries: 1 });
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=dws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dws.js","sourceRoot":"","sources":["../../src/dws.ts"],"names":[],"mappings":"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","sourcesContent":["// dws 硬化包装 —— 这一层是「拦」(TypeScript source)。\n//\n// **上面的每个模块都必须走它,不许自己 spawn dws。**\n//\n// ════════════════════════════════════════════════════════════════\n// 【六条工程纪律】—— 每一条都是踩出来的,违反必出错,而且是【静默】出错\n// ════════════════════════════════════════════════════════════════\n//\n// 1. **拿不到就响亮地死,绝不当成\"是空的\"。**\n// 不同接口把数据藏在不同层级。读错层级 → 拿到 undefined → 当成\"表是空的\"\n// → **一整天的数据可以这么无声丢掉,而且不报错。**\n// 「查到 0 条」和「读错地方了」**必须分得开**。分不开就 die。\n//\n// 2. **分页会静默截断。** 默认页大小有上限,超了就悄悄少给你,\n// **而且不一定有信号告诉你被截断了。**\n// **拿不到分页信号 ≠ 没有下一页,那叫「没有信号」。**\n// **半张表算出来的结果是错的,不是\"差不多\"。**\n//\n// 3. **创建类:报错了也可能其实建成功了。**\n// **绝不看返回信封** —— 按名回查 + 内容回读定成败。\n// 推论:**创建类永不盲重试**(会造重复件),失败先按幂等键回查。\n//\n// 4. **写操作报错 ≠ 失败。** 回读实态才定成败。返回 success 也不算验收。\n//\n// 5. **字段读写不对称。** 写进去的形状 ≠ 读回来的形状。**写完必回读。**\n//\n// 6. **markdown 会被重排版。重排版不是漂移。**\n// 判漂移必须剥掉 markdown 符号只看文字流 —— 用行级 diff 会每次都误报,\n// **然后你就会开始无脑 --force,真漂移也一起覆盖掉。**\n//\n// 一句话:**所有这些坑的形状都一样 —— 它不报错,它给你一个看起来很正常的答案。**\n// 这一层做的就是:**把「看起来正常」和「真的正常」分开。**\n\nimport { spawnSync } from 'node:child_process'\n\nexport class Blocked extends Error {}\n\nexport function die(msg): never {\n console.error('[FAIL] ' + msg)\n throw new Blocked(msg)\n}\n\nconst sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)\n\n/** 跑一条 dws。限流退避;超时不当成\"空\";坏响应响亮地死。 */\nexport function run(args, { allowFail = false, timeout = 180_000, retries = 2 } = {}) {\n let last = ''\n for (let attempt = 0; attempt <= retries; attempt++) {\n const p = spawnSync('dws', [...args, '--format', 'json'],\n { encoding: 'utf8', timeout, maxBuffer: 64 * 1024 * 1024 })\n if (p.error) {\n if ((p.error as NodeJS.ErrnoException).code === 'ENOENT') {\n if (allowFail) return null\n die('dws 跑不起来 —— 先 export PATH=\"$HOME/.local/bin:$PATH\"')\n }\n last = String(p.error); sleep(2000 * (attempt + 1)); continue\n }\n const out = p.stdout || p.stderr || ''\n last = out.slice(0, 300)\n const i = out.indexOf('{')\n if (i < 0) { sleep(2000 * (attempt + 1)); continue }\n let d\n try { d = JSON.parse(out.slice(i, lastBrace(out) + 1)) }\n catch { sleep(2000 * (attempt + 1)); continue }\n const err = d.error || {}\n if (err && (err.code || err.message)) {\n const s = JSON.stringify(err).toLowerCase()\n if (s.includes('limit') || s.includes('frequen') || s.includes('flow')) {\n sleep(3000 * (attempt + 1)); continue // 限流 → 退避\n }\n if (allowFail) return null\n die(`dws 失败 ${args.slice(0, 4).join(' ')}\\n ${JSON.stringify(err).slice(0, 300)}`)\n }\n if (p.status !== 0) {\n if (allowFail) return null\n die(`dws 以退出码 ${p.status} 结束,但没有结构化 error: ${args.slice(0, 4).join(' ')}\\n ${last}`)\n }\n return d\n }\n if (allowFail) return null\n die(`dws 无响应(重试 ${retries} 次) ${args.slice(0, 4).join(' ')}\\n ${last}`)\n}\n\nfunction lastBrace(s) {\n // dws 有时在 JSON 后面粘一行 WARN / INFO —— 从最后一个 } 截断\n for (let i = s.length - 1; i >= 0; i--) if (s[i] === '}') return i\n return s.length - 1\n}\n\n// ──────────────── 三个层级,各读各的 ────────────────\nconst inner = (d) => (d && typeof d.data === 'object' && d.data !== null ? d.data : d)\n\n/** 列表类查询的记录。拿不到就 die —— 绝不当成\"表是空的\"。 */\nexport function records(d) {\n const x = inner(d)\n if (!('records' in x)) {\n die(`返回里没有 records(顶层键=${Object.keys(d)}) —— 接口形状可能变了。` +\n `**绝不把它当成【表是空的】。**`)\n }\n if (x.hasMore || x.partial) {\n die('分页未收敛 —— **部分结果不许当完整结果用。** 半张表算出来的是错的。')\n }\n return x.records || []\n}\n\n/** 文档正文。**层级和列表类查询不一样** —— 两种形状都兜住,拿不到就 die。 */\nexport function markdown(d) {\n const md = inner(d).markdown ?? d.markdown\n if (md === undefined || md === null) {\n die(`拿不到文档正文(顶层键=${Object.keys(d)}) —— **绝不当成【这篇是空的】。**`)\n }\n return md\n}\n\n/** 目录节点。 */\nexport const nodes = (d) => inner(d).nodes || []\n\n/**\n * 待办翻页 —— **这是唯一有分页信号的读法。**\n *\n * ⚠️ **不要把页开大**。超过这个大小,分页信号会消失,\n * 你拿到 undefined、当成 false,然后**静默漏掉剩下的**。\n * 心跳的收件靠它 —— 漏了循环件就是「静默收工且不自知」。\n */\nexport function todosPage(status = 'false', page = 1) {\n const d = run(['todo', 'task', 'list', '--status', status, '--size', '20', '--page', String(page)])\n const r = d.result || {}\n if (!('todoCards' in r)) {\n die(`待办返回里没有 todoCards(result 键=${Object.keys(r)}) —— 绝不当成【没有待办】。`)\n }\n return { cards: r.todoCards || [], more: Boolean(r.hasMore) }\n}\n\n/** 把待办【拉全】。只看第一页会漏掉循环件。 */\nexport function todosAll(status = 'false', maxPages = 20) {\n const out: any[] = []\n for (let page = 1; page <= maxPages; page++) {\n const { cards, more } = todosPage(status, page)\n out.push(...cards)\n if (!more) return out\n }\n die(`待办翻页超过 ${maxPages} 页还没收敛 —— 不许把部分结果当完整。`)\n}\n\n// ──────────────── 创建类:报错也可能成功 → 按名回查 ────────────────\n/** 目录列表可能把扩展名单列,比对时要拼回去。 */\nexport function findChild(folder, name) {\n for (const n of nodes(run(['doc', 'list', '--folder', folder, '--page-size', '50']))) {\n const nm = n.name || '', ext = n.extension || ''\n if (nm === name || (ext && `${nm}.${ext}` === name)) return n.nodeId\n }\n return null\n}\n\n/** 建文档。**不看返回信封** —— 一律按名回查定成败。 */\nexport function createDoc(folder, name, file) {\n const exist = findChild(folder, name)\n if (exist) return exist\n run(['doc', 'create', '--name', name, '--folder', folder,\n '--content-file', file, '--content-format', 'markdown', '--yes'],\n { allowFail: true }) // ← **报错也可能其实成功了**\n sleep(1200)\n const nid = findChild(folder, name)\n if (!nid) die(`建不出文档 ${name} —— 别把它当成【建成功了】。`)\n return nid\n}\n\nexport const updateDoc = (node, file) =>\n run(['doc', 'update', '--node', node, '--content-file', file,\n '--content-format', 'markdown', '--mode', 'overwrite', '--yes'])\n\nexport const readDoc = (node, profile = '') => markdown(run([\n 'doc', 'read', '--node', node, ...profileArgs(profile),\n]))\nexport const docInfo = (node, profile = '') => inner(run([\n 'doc', 'info', '--node', node, ...profileArgs(profile),\n]))\n\n// ──────────────── 漂移判定:重排版【不是】漂移 ────────────────\n/**\n * 剥掉 markdown 语法和转义装饰,只留【文字本身】。\n *\n * 钉钉会重排版你推上去的 markdown。**重排版不是漂移。**\n * 用行级 diff 判漂移 → 每次 sync 都误报 →\n * **然后你就会开始无脑 --force,真正的漂移也一起覆盖掉了。**\n */\nexport function words(s) {\n let t = String(s ?? '')\n for (let i = 0; i < 3; i++) t = unescapeHtml(t) // 可能被多次转义\n t = t.replace(/&#(\\d+);/g, (_, n) => String.fromCharCode(+n))\n t = t.replace(/\\[打开\\]\\([^)]*\\)/g, '') // 钉钉自动加的链接装饰\n return t.replace(/[`*_>#|\\\\~\\-\\s]+/g, '')\n}\n\nfunction unescapeHtml(s) {\n return s.replace(/&(amp|lt|gt|quot|#39);/g, (m, e) =>\n ({ amp: '&', lt: '<', gt: '>', quot: '\"', '#39': \"'\" })[e])\n}\n\nexport const same = (a, b) => words(a) === words(b)\n\n// ──────────────── 数字员工的消息原子动作(typed,目标不可自由拼装) ────────────────\nfunction profileArgs(profile) {\n return profile ? ['--profile', profile] : []\n}\n\nfunction assertReplyTarget(target) {\n const missing: string[] = []\n if (!target?.conversationId) missing.push('conversationId')\n if (!target?.messageId) missing.push('messageId')\n if (!target?.senderOpenDingTalkId) missing.push('senderOpenDingTalkId')\n if (missing.length) die(`固定回复目标缺少 ${missing.join(' / ')}`)\n}\n\n/** ack 没有服务端 UUID,因此绝不自动重试。 */\nexport function ackMessage(target, { emoji = '收到', profile = '' } = {}) {\n assertReplyTarget(target)\n return run([\n 'chat', 'message', 'add-emoji',\n '--conversation-id', target.conversationId,\n '--msg-id', target.messageId,\n '--emoji', emoji,\n ...profileArgs(profile),\n ], { retries: 0 })\n}\n\n/** reply/ask 共用同一传输;uuid 在调用前已持久化,重试也必须复用。 */\nexport function replyMessage(\n target,\n text,\n { uuid = '', profile = '' }: { uuid?: string; profile?: string } = {},\n) {\n assertReplyTarget(target)\n if (!String(text || '').trim()) die('回复正文不能为空')\n if (!uuid) die('回复前必须先持久化 uuid 幂等键')\n return run([\n 'chat', 'message', 'reply',\n '--conversation-id', target.conversationId,\n '--ref-msg-id', target.messageId,\n '--ref-sender', target.senderOpenDingTalkId,\n '--text', text,\n '--uuid', uuid,\n '--ai-tag=true',\n ...profileArgs(profile),\n '--yes',\n ], { retries: 1 })\n}\n\nexport function readMessageEmotions(messageId, profile = '') {\n return run([\n 'chat', 'message', 'list-emotion-replies', '--msg-ids', messageId,\n ...profileArgs(profile),\n ], { allowFail: true, retries: 0 })\n}\n\nexport function querySendStatus(openTaskId, profile = '') {\n return run([\n 'chat', 'message', 'query-send-status', '--open-task-id', openTaskId,\n ...profileArgs(profile),\n ], { allowFail: true, retries: 1 })\n}\n\nexport function readMessagesByIds(messageId, profile = '') {\n return run([\n 'chat', 'message', 'list-by-ids', '--msg-ids', messageId,\n ...profileArgs(profile),\n ], { allowFail: true, retries: 1 })\n}\n\n/** 外发前核对 profile 对应的真实执行身份。 */\nexport function getSelf(profile = '') {\n return run([\n 'contact', 'user', 'get-self', ...profileArgs(profile),\n ], { retries: 1 })\n}\n"]}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// 钉钉原始事件 → 与平台细节解耦的内部 Event Contract(TypeScript source)。
|
|
2
|
+
// 消息正文始终是 data;只有 envelope 中的 ID 才能决定作用域和回复目标。
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
const KIND = {
|
|
5
|
+
user_im_message_receive_at: 'im.message.mentioned',
|
|
6
|
+
user_im_message_receive_o2o: 'im.message.dm',
|
|
7
|
+
user_im_message_receive_group: 'im.message.group',
|
|
8
|
+
heartbeat: 'agent.heartbeat',
|
|
9
|
+
'agent.heartbeat': 'agent.heartbeat',
|
|
10
|
+
};
|
|
11
|
+
export function digest(value) {
|
|
12
|
+
return createHash('sha256').update(String(value)).digest('hex');
|
|
13
|
+
}
|
|
14
|
+
export function stableStringify(value) {
|
|
15
|
+
if (value === null || typeof value !== 'object')
|
|
16
|
+
return JSON.stringify(value);
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
19
|
+
return `{${Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(',')}}`;
|
|
20
|
+
}
|
|
21
|
+
export function parseEventInput(value) {
|
|
22
|
+
if (typeof value !== 'string')
|
|
23
|
+
return value;
|
|
24
|
+
const text = value.trim();
|
|
25
|
+
if (!text)
|
|
26
|
+
throw new Error('事件输入是空的');
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(text);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
throw new Error(`事件不是合法 JSON: ${e.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* dws event consume 的 stdout 是 envelope,业务字段在 data JSON 字符串内。
|
|
36
|
+
* 同时兼容 schema 已展开的测试 fixture 和更底层的 payload.body 形状。
|
|
37
|
+
*/
|
|
38
|
+
export function normalizeEvent(input) {
|
|
39
|
+
const envelope = parseEventInput(input);
|
|
40
|
+
if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope)) {
|
|
41
|
+
throw new Error('事件必须是 JSON object');
|
|
42
|
+
}
|
|
43
|
+
const envelopeRecord = envelope;
|
|
44
|
+
const body = unwrapData(envelopeRecord);
|
|
45
|
+
const rawType = string(first(body, [
|
|
46
|
+
'type', 'event_key', 'eventKey', 'event_type', 'payload.type',
|
|
47
|
+
]) || first(envelopeRecord, ['type', 'event_key', 'eventKey']));
|
|
48
|
+
const kind = string(first(body, ['kind'])) || KIND[rawType] || rawType;
|
|
49
|
+
if (!kind)
|
|
50
|
+
throw new Error('事件缺少 type/kind,无法选择 Basic Behavior');
|
|
51
|
+
const messageId = string(first(body, [
|
|
52
|
+
'message_id', 'messageId', 'openMessageId', 'payload.body.openMessageId',
|
|
53
|
+
]));
|
|
54
|
+
const conversationId = string(first(body, [
|
|
55
|
+
'conversation_id', 'conversationId', 'openConversationId',
|
|
56
|
+
'payload.body.openConversationId',
|
|
57
|
+
]));
|
|
58
|
+
const senderOpenId = string(first(body, [
|
|
59
|
+
'sender_open_dingtalk_id', 'senderOpenDingTalkId', 'actor.openDingTalkId',
|
|
60
|
+
'payload.body.senderOpenDingTalkId',
|
|
61
|
+
]));
|
|
62
|
+
const senderUserId = string(first(body, [
|
|
63
|
+
'sender_user_id', 'senderUserId', 'actor.userId', 'payload.body.senderUserId',
|
|
64
|
+
]));
|
|
65
|
+
const senderName = string(first(body, [
|
|
66
|
+
'sender', 'sender_name', 'senderName', 'actor.displayName', 'payload.body.sender',
|
|
67
|
+
]));
|
|
68
|
+
const text = string(first(body, [
|
|
69
|
+
'content', 'text', 'message.text', 'payload.body.content',
|
|
70
|
+
]), true);
|
|
71
|
+
const threadId = string(first(body, [
|
|
72
|
+
'thread_id', 'threadId', 'openConvThreadId', 'payload.body.openConvThreadId',
|
|
73
|
+
]));
|
|
74
|
+
const rootMessageId = string(first(body, [
|
|
75
|
+
'root_message_id', 'rootMessageId', 'payload.body.rootMessageId',
|
|
76
|
+
]));
|
|
77
|
+
const taskId = string(first(body, ['task_id', 'taskId', 'issue_id', 'issueId']));
|
|
78
|
+
const suppliedCorrelation = string(first(body, [
|
|
79
|
+
'correlation_id', 'correlationId', 'session_key', 'sessionKey',
|
|
80
|
+
]));
|
|
81
|
+
const occurredAt = asTime(first(body, [
|
|
82
|
+
'event_time', 'eventTime', 'timestamp', 'occurredAtMs', 'create_time',
|
|
83
|
+
'createTime', 'payload.event_time', 'payload.body.createTime',
|
|
84
|
+
]));
|
|
85
|
+
const tenantId = string(first(body, [
|
|
86
|
+
'tenant_id', 'tenantId', 'corp_id', 'corpId', 'event_corp_id', 'eventCorpId',
|
|
87
|
+
'payload.corpId', 'payload.body.corpId',
|
|
88
|
+
]) || first(envelopeRecord, ['tenant_id', 'tenantId', 'corp_id', 'corpId', 'event_corp_id']));
|
|
89
|
+
const rawEventId = string(first(body, [
|
|
90
|
+
'event_id', 'eventId', 'id', 'payload.event_id',
|
|
91
|
+
]) || first(envelopeRecord, ['event_id', 'eventId', 'id']));
|
|
92
|
+
// 即使源事件漏了时间,同一份原始输入也必须得到同一个派生 ID。
|
|
93
|
+
const derivedSeed = stableStringify(envelopeRecord);
|
|
94
|
+
const eventId = rawEventId || `derived_${digest(derivedSeed).slice(0, 24)}`;
|
|
95
|
+
const isMessage = String(kind).startsWith('im.message.');
|
|
96
|
+
if (isMessage) {
|
|
97
|
+
const missing = [];
|
|
98
|
+
if (!conversationId)
|
|
99
|
+
missing.push('conversation_id');
|
|
100
|
+
if (!messageId)
|
|
101
|
+
missing.push('message_id');
|
|
102
|
+
if (!senderOpenId)
|
|
103
|
+
missing.push('sender_open_dingtalk_id');
|
|
104
|
+
if (missing.length) {
|
|
105
|
+
throw new Error(`消息事件缺少 ${missing.join(' / ')};不能安全固定回复目标`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const correlation = suppliedCorrelation
|
|
109
|
+
? { source: 'correlation', key: suppliedCorrelation }
|
|
110
|
+
: taskId
|
|
111
|
+
? { source: 'task', key: taskId }
|
|
112
|
+
: threadId
|
|
113
|
+
? { source: 'thread', key: threadId }
|
|
114
|
+
: rootMessageId
|
|
115
|
+
? { source: 'message-topic', key: rootMessageId }
|
|
116
|
+
: messageId
|
|
117
|
+
? { source: 'message-topic', key: messageId }
|
|
118
|
+
: { source: 'event', key: eventId };
|
|
119
|
+
return {
|
|
120
|
+
$schema: 'dingtalk-agent/event@1',
|
|
121
|
+
id: eventId,
|
|
122
|
+
idSource: rawEventId ? 'source' : 'derived',
|
|
123
|
+
kind,
|
|
124
|
+
occurredAt,
|
|
125
|
+
source: {
|
|
126
|
+
adapter: rawType.startsWith('user_im_') ? 'dws-event' : 'custom',
|
|
127
|
+
type: rawType || kind,
|
|
128
|
+
subscribeId: string(first(body, ['subscribe_id', 'subscribeId', 'subId'])),
|
|
129
|
+
},
|
|
130
|
+
tenant: { id: tenantId },
|
|
131
|
+
actor: {
|
|
132
|
+
id: senderOpenId || senderUserId || 'system',
|
|
133
|
+
openDingTalkId: senderOpenId,
|
|
134
|
+
userId: senderUserId,
|
|
135
|
+
displayName: senderName,
|
|
136
|
+
},
|
|
137
|
+
conversation: {
|
|
138
|
+
id: conversationId,
|
|
139
|
+
type: conversationType(kind, first(body, ['conversation_type', 'conversationType'])),
|
|
140
|
+
},
|
|
141
|
+
message: {
|
|
142
|
+
id: messageId,
|
|
143
|
+
text,
|
|
144
|
+
threadId,
|
|
145
|
+
rootMessageId,
|
|
146
|
+
},
|
|
147
|
+
correlation,
|
|
148
|
+
replyTarget: isMessage ? {
|
|
149
|
+
tenantId,
|
|
150
|
+
conversationId,
|
|
151
|
+
messageId,
|
|
152
|
+
senderOpenDingTalkId: senderOpenId,
|
|
153
|
+
} : null,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/** Agent/Driver 共用的极简语义事件;原始平台 envelope 另行留档。 */
|
|
157
|
+
export function compactEvent(event, contextId) {
|
|
158
|
+
return {
|
|
159
|
+
$schema: 'dingtalk-agent/trigger@1',
|
|
160
|
+
id: event.id,
|
|
161
|
+
type: event.kind,
|
|
162
|
+
contextId,
|
|
163
|
+
occurredAt: event.occurredAt,
|
|
164
|
+
conversationId: event.conversation.id,
|
|
165
|
+
messageId: event.message.id,
|
|
166
|
+
actorId: event.actor.openDingTalkId || event.actor.userId || event.actor.id,
|
|
167
|
+
actorName: event.actor.displayName,
|
|
168
|
+
text: event.message.text,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function unwrapData(envelope) {
|
|
172
|
+
if (typeof envelope.data !== 'string')
|
|
173
|
+
return envelope.data && typeof envelope.data === 'object'
|
|
174
|
+
? envelope.data
|
|
175
|
+
: envelope;
|
|
176
|
+
try {
|
|
177
|
+
const parsed = JSON.parse(envelope.data);
|
|
178
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
179
|
+
? parsed
|
|
180
|
+
: envelope;
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
throw new Error(`事件 data 不是合法 JSON 字符串: ${e.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function first(obj, paths) {
|
|
187
|
+
for (const path of paths) {
|
|
188
|
+
let value = obj;
|
|
189
|
+
for (const key of path.split('.')) {
|
|
190
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
191
|
+
value = undefined;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
value = value[key];
|
|
195
|
+
}
|
|
196
|
+
if (value !== undefined && value !== null && value !== '')
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
function string(value, keepEmpty = false) {
|
|
202
|
+
if (value === undefined || value === null)
|
|
203
|
+
return '';
|
|
204
|
+
if (typeof value === 'object')
|
|
205
|
+
return '';
|
|
206
|
+
const out = String(value);
|
|
207
|
+
return keepEmpty ? out : out.trim();
|
|
208
|
+
}
|
|
209
|
+
function asTime(value) {
|
|
210
|
+
if (value === undefined || value === null || value === '')
|
|
211
|
+
return null;
|
|
212
|
+
const n = Number(value);
|
|
213
|
+
if (Number.isFinite(n) && String(value).trim() !== '') {
|
|
214
|
+
const ms = n < 10_000_000_000 ? n * 1000 : n;
|
|
215
|
+
const d = new Date(ms);
|
|
216
|
+
if (!Number.isNaN(d.getTime()))
|
|
217
|
+
return d.toISOString();
|
|
218
|
+
}
|
|
219
|
+
const d = new Date(String(value));
|
|
220
|
+
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
221
|
+
}
|
|
222
|
+
function conversationType(kind, supplied) {
|
|
223
|
+
if (supplied)
|
|
224
|
+
return String(supplied);
|
|
225
|
+
if (kind === 'im.message.dm')
|
|
226
|
+
return 'dm';
|
|
227
|
+
if (kind === 'im.message.group')
|
|
228
|
+
return 'group';
|
|
229
|
+
if (kind === 'im.message.mentioned')
|
|
230
|
+
return 'mentioned-context';
|
|
231
|
+
return 'none';
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/events.ts"],"names":[],"mappings":"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","sourcesContent":["// 钉钉原始事件 → 与平台细节解耦的内部 Event Contract(TypeScript source)。\n// 消息正文始终是 data;只有 envelope 中的 ID 才能决定作用域和回复目标。\n\nimport { createHash } from 'node:crypto'\nimport type { CompactEvent, EventKind, NormalizedEvent } from './types.js'\n\nconst KIND: Record<string, EventKind> = {\n user_im_message_receive_at: 'im.message.mentioned',\n user_im_message_receive_o2o: 'im.message.dm',\n user_im_message_receive_group: 'im.message.group',\n heartbeat: 'agent.heartbeat',\n 'agent.heartbeat': 'agent.heartbeat',\n}\n\nexport function digest(value: unknown): string {\n return createHash('sha256').update(String(value)).digest('hex')\n}\n\nexport function stableStringify(value: any): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value)\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`\n return `{${Object.keys(value).sort().map((k) =>\n `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(',')}}`\n}\n\nexport function parseEventInput(value: unknown): unknown {\n if (typeof value !== 'string') return value\n const text = value.trim()\n if (!text) throw new Error('事件输入是空的')\n try { return JSON.parse(text) }\n catch (e) { throw new Error(`事件不是合法 JSON: ${e.message}`) }\n}\n\n/**\n * dws event consume 的 stdout 是 envelope,业务字段在 data JSON 字符串内。\n * 同时兼容 schema 已展开的测试 fixture 和更底层的 payload.body 形状。\n */\nexport function normalizeEvent(input: unknown): NormalizedEvent {\n const envelope = parseEventInput(input)\n if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope)) {\n throw new Error('事件必须是 JSON object')\n }\n const envelopeRecord = envelope as Record<string, any>\n const body = unwrapData(envelopeRecord)\n const rawType = string(first(body, [\n 'type', 'event_key', 'eventKey', 'event_type', 'payload.type',\n ]) || first(envelopeRecord, ['type', 'event_key', 'eventKey']))\n const kind = string(first(body, ['kind'])) || KIND[rawType] || rawType\n if (!kind) throw new Error('事件缺少 type/kind,无法选择 Basic Behavior')\n\n const messageId = string(first(body, [\n 'message_id', 'messageId', 'openMessageId', 'payload.body.openMessageId',\n ]))\n const conversationId = string(first(body, [\n 'conversation_id', 'conversationId', 'openConversationId',\n 'payload.body.openConversationId',\n ]))\n const senderOpenId = string(first(body, [\n 'sender_open_dingtalk_id', 'senderOpenDingTalkId', 'actor.openDingTalkId',\n 'payload.body.senderOpenDingTalkId',\n ]))\n const senderUserId = string(first(body, [\n 'sender_user_id', 'senderUserId', 'actor.userId', 'payload.body.senderUserId',\n ]))\n const senderName = string(first(body, [\n 'sender', 'sender_name', 'senderName', 'actor.displayName', 'payload.body.sender',\n ]))\n const text = string(first(body, [\n 'content', 'text', 'message.text', 'payload.body.content',\n ]), true)\n const threadId = string(first(body, [\n 'thread_id', 'threadId', 'openConvThreadId', 'payload.body.openConvThreadId',\n ]))\n const rootMessageId = string(first(body, [\n 'root_message_id', 'rootMessageId', 'payload.body.rootMessageId',\n ]))\n const taskId = string(first(body, ['task_id', 'taskId', 'issue_id', 'issueId']))\n const suppliedCorrelation = string(first(body, [\n 'correlation_id', 'correlationId', 'session_key', 'sessionKey',\n ]))\n const occurredAt = asTime(first(body, [\n 'event_time', 'eventTime', 'timestamp', 'occurredAtMs', 'create_time',\n 'createTime', 'payload.event_time', 'payload.body.createTime',\n ]))\n const tenantId = string(first(body, [\n 'tenant_id', 'tenantId', 'corp_id', 'corpId', 'event_corp_id', 'eventCorpId',\n 'payload.corpId', 'payload.body.corpId',\n ]) || first(envelopeRecord, ['tenant_id', 'tenantId', 'corp_id', 'corpId', 'event_corp_id']))\n\n const rawEventId = string(first(body, [\n 'event_id', 'eventId', 'id', 'payload.event_id',\n ]) || first(envelopeRecord, ['event_id', 'eventId', 'id']))\n // 即使源事件漏了时间,同一份原始输入也必须得到同一个派生 ID。\n const derivedSeed = stableStringify(envelopeRecord)\n const eventId = rawEventId || `derived_${digest(derivedSeed).slice(0, 24)}`\n\n const isMessage = String(kind).startsWith('im.message.')\n if (isMessage) {\n const missing: string[] = []\n if (!conversationId) missing.push('conversation_id')\n if (!messageId) missing.push('message_id')\n if (!senderOpenId) missing.push('sender_open_dingtalk_id')\n if (missing.length) {\n throw new Error(`消息事件缺少 ${missing.join(' / ')};不能安全固定回复目标`)\n }\n }\n\n const correlation = suppliedCorrelation\n ? { source: 'correlation', key: suppliedCorrelation }\n : taskId\n ? { source: 'task', key: taskId }\n : threadId\n ? { source: 'thread', key: threadId }\n : rootMessageId\n ? { source: 'message-topic', key: rootMessageId }\n : messageId\n ? { source: 'message-topic', key: messageId }\n : { source: 'event', key: eventId }\n\n return {\n $schema: 'dingtalk-agent/event@1',\n id: eventId,\n idSource: rawEventId ? 'source' : 'derived',\n kind,\n occurredAt,\n source: {\n adapter: rawType.startsWith('user_im_') ? 'dws-event' : 'custom',\n type: rawType || kind,\n subscribeId: string(first(body, ['subscribe_id', 'subscribeId', 'subId'])),\n },\n tenant: { id: tenantId },\n actor: {\n id: senderOpenId || senderUserId || 'system',\n openDingTalkId: senderOpenId,\n userId: senderUserId,\n displayName: senderName,\n },\n conversation: {\n id: conversationId,\n type: conversationType(kind, first(body, ['conversation_type', 'conversationType'])),\n },\n message: {\n id: messageId,\n text,\n threadId,\n rootMessageId,\n },\n correlation,\n replyTarget: isMessage ? {\n tenantId,\n conversationId,\n messageId,\n senderOpenDingTalkId: senderOpenId,\n } : null,\n }\n}\n\n/** Agent/Driver 共用的极简语义事件;原始平台 envelope 另行留档。 */\nexport function compactEvent(event: NormalizedEvent, contextId: string): CompactEvent {\n return {\n $schema: 'dingtalk-agent/trigger@1',\n id: event.id,\n type: event.kind,\n contextId,\n occurredAt: event.occurredAt,\n conversationId: event.conversation.id,\n messageId: event.message.id,\n actorId: event.actor.openDingTalkId || event.actor.userId || event.actor.id,\n actorName: event.actor.displayName,\n text: event.message.text,\n }\n}\n\nfunction unwrapData(envelope: Record<string, any>): Record<string, any> {\n if (typeof envelope.data !== 'string') return envelope.data && typeof envelope.data === 'object'\n ? envelope.data as Record<string, any>\n : envelope\n try {\n const parsed = JSON.parse(envelope.data)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? parsed as Record<string, any>\n : envelope\n } catch (e) {\n throw new Error(`事件 data 不是合法 JSON 字符串: ${e.message}`)\n }\n}\n\nfunction first(obj: Record<string, any>, paths: string[]): unknown {\n for (const path of paths) {\n let value: any = obj\n for (const key of path.split('.')) {\n if (value === null || value === undefined || typeof value !== 'object') {\n value = undefined\n break\n }\n value = value[key]\n }\n if (value !== undefined && value !== null && value !== '') return value\n }\n return undefined\n}\n\nfunction string(value: unknown, keepEmpty = false): string {\n if (value === undefined || value === null) return ''\n if (typeof value === 'object') return ''\n const out = String(value)\n return keepEmpty ? out : out.trim()\n}\n\nfunction asTime(value: unknown): string | null {\n if (value === undefined || value === null || value === '') return null\n const n = Number(value)\n if (Number.isFinite(n) && String(value).trim() !== '') {\n const ms = n < 10_000_000_000 ? n * 1000 : n\n const d = new Date(ms)\n if (!Number.isNaN(d.getTime())) return d.toISOString()\n }\n const d = new Date(String(value))\n return Number.isNaN(d.getTime()) ? null : d.toISOString()\n}\n\nfunction conversationType(kind: EventKind, supplied: unknown): string {\n if (supplied) return String(supplied)\n if (kind === 'im.message.dm') return 'dm'\n if (kind === 'im.message.group') return 'group'\n if (kind === 'im.message.mentioned') return 'mentioned-context'\n return 'none'\n}\n"]}
|