@xdxer/dingtalk-agent 0.1.1 → 0.1.2
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/README.md +81 -80
- package/dist/bin/dingtalk-agent.js +685 -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/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/skill-manager.js +273 -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 +207 -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 +293 -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 +1031 -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
|
@@ -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"]}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Field 是稳定的协作/权限/知识边界(TypeScript source);Session 和沙箱都只是它下面的运行实例。
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { digest, stableStringify } from './events.js';
|
|
5
|
+
export function resolveField(root, cfg, event, explicitId = '') {
|
|
6
|
+
const fields = (cfg.fields || []).map((entry) => loadField(root, entry));
|
|
7
|
+
if (!fields.length)
|
|
8
|
+
throw new Error('没有配置 Field;运行 `dingtalk-agent init` 补齐基础框架');
|
|
9
|
+
if (explicitId) {
|
|
10
|
+
const hit = fields.find((x) => x.entry.id === explicitId || x.value.id === explicitId);
|
|
11
|
+
if (!hit)
|
|
12
|
+
throw new Error(`找不到 Field: ${explicitId}`);
|
|
13
|
+
return hit;
|
|
14
|
+
}
|
|
15
|
+
const matched = fields.filter((x) => !x.entry.default && matches(x.value.match || {}, event));
|
|
16
|
+
if (matched.length) {
|
|
17
|
+
const max = Math.max(...matched.map((x) => Number(x.entry.priority || 0)));
|
|
18
|
+
const winners = matched.filter((x) => Number(x.entry.priority || 0) === max);
|
|
19
|
+
if (winners.length !== 1) {
|
|
20
|
+
throw new Error(`Field 匹配歧义: ${winners.map((x) => x.value.id).join(', ')};请提高优先级或收紧 selector`);
|
|
21
|
+
}
|
|
22
|
+
return winners[0];
|
|
23
|
+
}
|
|
24
|
+
const defaults = fields.filter((x) => x.entry.default);
|
|
25
|
+
if (defaults.length !== 1)
|
|
26
|
+
throw new Error(`必须且只能有一个默认 Field,当前 ${defaults.length} 个`);
|
|
27
|
+
return defaults[0];
|
|
28
|
+
}
|
|
29
|
+
export function loadBehavior(root, field) {
|
|
30
|
+
const packs = field.behavior?.packs || [];
|
|
31
|
+
if (!Array.isArray(packs) || !packs.length)
|
|
32
|
+
throw new Error(`Field ${field.id} 没有 behavior.packs`);
|
|
33
|
+
const out = {
|
|
34
|
+
$schema: 'dingtalk-agent/behavior-snapshot@1',
|
|
35
|
+
id: '', version: '', description: '', handles: {},
|
|
36
|
+
stateMachine: { initial: 'ready', transitions: {}, terminal: [] },
|
|
37
|
+
limits: {}, invariants: [], packs: [], hash: '',
|
|
38
|
+
};
|
|
39
|
+
for (const rel of packs) {
|
|
40
|
+
const path = resolve(root, rel);
|
|
41
|
+
const pack = readJson(path, `Behavior pack ${rel}`);
|
|
42
|
+
out.id = pack.id || out.id;
|
|
43
|
+
out.version = pack.version || out.version;
|
|
44
|
+
out.description = pack.description || out.description;
|
|
45
|
+
for (const [kind, rule] of Object.entries(pack.handles || {})) {
|
|
46
|
+
out.handles[kind] = {
|
|
47
|
+
...(out.handles[kind] || {}),
|
|
48
|
+
...rule,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
out.stateMachine.initial = pack.stateMachine?.initial || out.stateMachine.initial;
|
|
52
|
+
out.stateMachine.transitions = mergeTransitions(out.stateMachine.transitions, pack.stateMachine?.transitions || {});
|
|
53
|
+
out.stateMachine.terminal = [...new Set([
|
|
54
|
+
...out.stateMachine.terminal, ...(pack.stateMachine?.terminal || []),
|
|
55
|
+
])];
|
|
56
|
+
Object.assign(out.limits, pack.limits || {});
|
|
57
|
+
out.invariants.push(...(pack.invariants || []));
|
|
58
|
+
out.packs.push({ path: rel, id: pack.id, version: pack.version,
|
|
59
|
+
hash: digest(stableStringify(pack)) });
|
|
60
|
+
}
|
|
61
|
+
out.invariants = [...new Set(out.invariants)];
|
|
62
|
+
out.hash = digest(stableStringify(out));
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
function loadField(root, entry) {
|
|
66
|
+
if (!entry?.id || !entry?.path)
|
|
67
|
+
throw new Error('Field 配置项必须有 id 和 path');
|
|
68
|
+
const path = resolve(root, entry.path);
|
|
69
|
+
const value = normalizeField(readJson(path, `Field ${entry.id}`), entry.path);
|
|
70
|
+
if (!value.id)
|
|
71
|
+
throw new Error(`${entry.path} 缺少 id`);
|
|
72
|
+
if (value.id !== entry.id)
|
|
73
|
+
throw new Error(`Field id 不一致: config=${entry.id}, file=${value.id}`);
|
|
74
|
+
validateMatch(value.match, entry.path);
|
|
75
|
+
return { entry, value, path, hash: digest(stableStringify(value)) };
|
|
76
|
+
}
|
|
77
|
+
function normalizeField(input, label) {
|
|
78
|
+
const mode = input.transport?.mode || 'personal-event';
|
|
79
|
+
const egressOwner = input.transport?.egressOwner ||
|
|
80
|
+
(mode === 'robot-connect' ? 'connector' : 'dingtalk-agent');
|
|
81
|
+
const allowed = new Set(['personal-event:dingtalk-agent', 'robot-connect:connector']);
|
|
82
|
+
if (!allowed.has(`${mode}:${egressOwner}`)) {
|
|
83
|
+
throw new Error(`${label} 的 transport 非法:${mode}/${egressOwner};出口必须且只能有一个 owner`);
|
|
84
|
+
}
|
|
85
|
+
return { ...input, transport: { mode, egressOwner } };
|
|
86
|
+
}
|
|
87
|
+
function validateMatch(match, label) {
|
|
88
|
+
if (match === undefined)
|
|
89
|
+
return;
|
|
90
|
+
if (!match || typeof match !== 'object' || Array.isArray(match)) {
|
|
91
|
+
throw new Error(`${label} 的 match 必须是对象`);
|
|
92
|
+
}
|
|
93
|
+
const allowedKeys = new Set(['tenantIds', 'conversationIds', 'actorIds', 'eventKinds']);
|
|
94
|
+
for (const [key, value] of Object.entries(match)) {
|
|
95
|
+
if (!allowedKeys.has(key))
|
|
96
|
+
throw new Error(`${label} 的 match 含未知 selector: ${key}`);
|
|
97
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
|
|
98
|
+
throw new Error(`${label} 的 match.${key} 必须是 string[]`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function readJson(path, label) {
|
|
103
|
+
if (!existsSync(path))
|
|
104
|
+
throw new Error(`${label} 不存在: ${path};运行 dingtalk-agent init 可补齐默认资产`);
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
throw new Error(`${label} 不是合法 JSON: ${e.message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function matches(match, event) {
|
|
113
|
+
const actorIds = new Set([
|
|
114
|
+
event.actor?.openDingTalkId, event.actor?.userId, event.actor?.id,
|
|
115
|
+
].filter(Boolean));
|
|
116
|
+
const checks = {
|
|
117
|
+
tenantIds: (allowed) => allowed.includes(event.tenant.id),
|
|
118
|
+
conversationIds: (allowed) => allowed.includes(event.conversation.id),
|
|
119
|
+
actorIds: (allowed) => allowed.some((id) => actorIds.has(id)),
|
|
120
|
+
eventKinds: (allowed) => allowed.includes(event.kind),
|
|
121
|
+
};
|
|
122
|
+
const active = Object.entries(match);
|
|
123
|
+
return active.length > 0 && active.every(([key, allowed]) => checks[key](allowed));
|
|
124
|
+
}
|
|
125
|
+
function mergeTransitions(base, extra) {
|
|
126
|
+
const out = { ...base };
|
|
127
|
+
for (const [state, transitions] of Object.entries(extra)) {
|
|
128
|
+
out[state] = { ...(out[state] || {}), ...transitions };
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.js","sourceRoot":"","sources":["../../src/fields.ts"],"names":[],"mappings":"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","sourcesContent":["// Field 是稳定的协作/权限/知识边界(TypeScript source);Session 和沙箱都只是它下面的运行实例。\n\nimport { readFileSync, existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { digest, stableStringify } from './events.js'\nimport type {\n AgentConfig, BehaviorSnapshot, FieldDefinition, FieldEntry, NormalizedEvent,\n} from './types.js'\n\nexport interface ResolvedField {\n entry: FieldEntry\n value: FieldDefinition\n path: string\n hash: string\n}\n\nexport function resolveField(\n root: string,\n cfg: AgentConfig,\n event: NormalizedEvent,\n explicitId = '',\n): ResolvedField {\n const fields = (cfg.fields || []).map((entry) => loadField(root, entry))\n if (!fields.length) throw new Error('没有配置 Field;运行 `dingtalk-agent init` 补齐基础框架')\n\n if (explicitId) {\n const hit = fields.find((x) => x.entry.id === explicitId || x.value.id === explicitId)\n if (!hit) throw new Error(`找不到 Field: ${explicitId}`)\n return hit\n }\n\n const matched = fields.filter((x) => !x.entry.default && matches(x.value.match || {}, event))\n if (matched.length) {\n const max = Math.max(...matched.map((x) => Number(x.entry.priority || 0)))\n const winners = matched.filter((x) => Number(x.entry.priority || 0) === max)\n if (winners.length !== 1) {\n throw new Error(`Field 匹配歧义: ${winners.map((x) => x.value.id).join(', ')};请提高优先级或收紧 selector`)\n }\n return winners[0]\n }\n\n const defaults = fields.filter((x) => x.entry.default)\n if (defaults.length !== 1) throw new Error(`必须且只能有一个默认 Field,当前 ${defaults.length} 个`)\n return defaults[0]\n}\n\nexport function loadBehavior(root: string, field: FieldDefinition): BehaviorSnapshot {\n const packs = field.behavior?.packs || []\n if (!Array.isArray(packs) || !packs.length) throw new Error(`Field ${field.id} 没有 behavior.packs`)\n const out: BehaviorSnapshot = {\n $schema: 'dingtalk-agent/behavior-snapshot@1',\n id: '', version: '', description: '', handles: {},\n stateMachine: { initial: 'ready', transitions: {}, terminal: [] },\n limits: {}, invariants: [], packs: [], hash: '',\n }\n for (const rel of packs) {\n const path = resolve(root, rel)\n const pack = readJson(path, `Behavior pack ${rel}`)\n out.id = pack.id || out.id\n out.version = pack.version || out.version\n out.description = pack.description || out.description\n for (const [kind, rule] of Object.entries(pack.handles || {})) {\n out.handles[kind] = {\n ...(out.handles[kind] || {}),\n ...(rule as Record<string, any>),\n } as any\n }\n out.stateMachine.initial = pack.stateMachine?.initial || out.stateMachine.initial\n out.stateMachine.transitions = mergeTransitions(\n out.stateMachine.transitions, pack.stateMachine?.transitions || {})\n out.stateMachine.terminal = [...new Set([\n ...out.stateMachine.terminal, ...(pack.stateMachine?.terminal || []),\n ])]\n Object.assign(out.limits, pack.limits || {})\n out.invariants.push(...(pack.invariants || []))\n out.packs.push({ path: rel, id: pack.id, version: pack.version,\n hash: digest(stableStringify(pack)) })\n }\n out.invariants = [...new Set(out.invariants)]\n out.hash = digest(stableStringify(out))\n return out\n}\n\nfunction loadField(root: string, entry: FieldEntry): ResolvedField {\n if (!entry?.id || !entry?.path) throw new Error('Field 配置项必须有 id 和 path')\n const path = resolve(root, entry.path)\n const value = normalizeField(readJson(path, `Field ${entry.id}`), entry.path)\n if (!value.id) throw new Error(`${entry.path} 缺少 id`)\n if (value.id !== entry.id) throw new Error(`Field id 不一致: config=${entry.id}, file=${value.id}`)\n validateMatch(value.match, entry.path)\n return { entry, value, path, hash: digest(stableStringify(value)) }\n}\n\nfunction normalizeField(input: Record<string, any>, label: string): FieldDefinition {\n const mode = input.transport?.mode || 'personal-event'\n const egressOwner = input.transport?.egressOwner ||\n (mode === 'robot-connect' ? 'connector' : 'dingtalk-agent')\n const allowed = new Set(['personal-event:dingtalk-agent', 'robot-connect:connector'])\n if (!allowed.has(`${mode}:${egressOwner}`)) {\n throw new Error(`${label} 的 transport 非法:${mode}/${egressOwner};出口必须且只能有一个 owner`)\n }\n return { ...input, transport: { mode, egressOwner } } as FieldDefinition\n}\n\nfunction validateMatch(match: FieldDefinition['match'], label: string): void {\n if (match === undefined) return\n if (!match || typeof match !== 'object' || Array.isArray(match)) {\n throw new Error(`${label} 的 match 必须是对象`)\n }\n const allowedKeys = new Set(['tenantIds', 'conversationIds', 'actorIds', 'eventKinds'])\n for (const [key, value] of Object.entries(match)) {\n if (!allowedKeys.has(key)) throw new Error(`${label} 的 match 含未知 selector: ${key}`)\n if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {\n throw new Error(`${label} 的 match.${key} 必须是 string[]`)\n }\n }\n}\n\nfunction readJson(path: string, label: string): Record<string, any> {\n if (!existsSync(path)) throw new Error(`${label} 不存在: ${path};运行 dingtalk-agent init 可补齐默认资产`)\n try { return JSON.parse(readFileSync(path, 'utf8')) }\n catch (e) { throw new Error(`${label} 不是合法 JSON: ${e.message}`) }\n}\n\nfunction matches(match: NonNullable<FieldDefinition['match']>, event: NormalizedEvent): boolean {\n const actorIds = new Set([\n event.actor?.openDingTalkId, event.actor?.userId, event.actor?.id,\n ].filter(Boolean))\n const checks: Record<string, (allowed: string[]) => boolean> = {\n tenantIds: (allowed) => allowed.includes(event.tenant.id),\n conversationIds: (allowed) => allowed.includes(event.conversation.id),\n actorIds: (allowed) => allowed.some((id) => actorIds.has(id)),\n eventKinds: (allowed) => allowed.includes(event.kind),\n }\n const active = Object.entries(match)\n return active.length > 0 && active.every(([key, allowed]) =>\n checks[key](allowed as string[]))\n}\n\nfunction mergeTransitions(\n base: BehaviorSnapshot['stateMachine']['transitions'],\n extra: BehaviorSnapshot['stateMachine']['transitions'],\n): BehaviorSnapshot['stateMachine']['transitions'] {\n const out = { ...base }\n for (const [state, transitions] of Object.entries(extra)) {\n out[state] = { ...(out[state] || {}), ...transitions }\n }\n return out\n}\n"]}
|
package/dist/src/init.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// init 只为“需要持久 Workspace / Prepared Run runtime”的目录建立最小骨架。
|
|
2
|
+
// 普通 Claude Code/Codex Session 只需全局 Skill + bootstrap,不需要 init。
|
|
3
|
+
import { cpSync, existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import * as cfg from './config.js';
|
|
6
|
+
import { resolvePackageRoot } from './package-root.js';
|
|
7
|
+
import { ensureWorkspace, workspacePath, } from './workspace.js';
|
|
8
|
+
const PKG = resolvePackageRoot(import.meta.url);
|
|
9
|
+
export function init(root, options = {}) {
|
|
10
|
+
const wasInitialized = existsSync(workspacePath(root));
|
|
11
|
+
const existingConfig = cfg.load(root);
|
|
12
|
+
const workspace = ensureWorkspace(root, options);
|
|
13
|
+
// Runtime 的机器策略仍需本地存在;只补缺失项,不复制全局 Skill、Ontology,
|
|
14
|
+
// 也不修改项目的 AGENTS.md / CLAUDE.md / .gitignore。
|
|
15
|
+
copyMissing(join(PKG, 'templates', 'behaviors'), join(root, 'behaviors'));
|
|
16
|
+
copyMissing(join(PKG, 'templates', 'fields'), join(root, 'fields'));
|
|
17
|
+
for (const dir of [
|
|
18
|
+
'.dingtalk-agent/sessions', '.dingtalk-agent/event-index',
|
|
19
|
+
'.dingtalk-agent/waits', '.dingtalk-agent/cache',
|
|
20
|
+
])
|
|
21
|
+
mkdirSync(join(root, dir), { recursive: true });
|
|
22
|
+
if (!existingConfig)
|
|
23
|
+
cfg.save(root, cfg.DEFAULT);
|
|
24
|
+
console.log(`${wasInitialized ? '✅ Workspace 已存在;检查完成,未重新初始化' : '✅ 已初始化最小 Agent Workspace'}
|
|
25
|
+
|
|
26
|
+
Context: ${workspace.contextId}(未显式指定时由目录名稳定派生)
|
|
27
|
+
配置: .dingtalk-agent/workspace.json
|
|
28
|
+
Storage: ${workspace.mounts.memory} / ${workspace.mounts.knowledge}
|
|
29
|
+
Skill: ${workspace.skills.join(', ')}(按名称解析;不复制进当前项目)
|
|
30
|
+
|
|
31
|
+
会话水合: dingtalk-agent bootstrap
|
|
32
|
+
事件运行时: dingtalk-agent prepare --event-file <event.json>
|
|
33
|
+
本地验收: dingtalk-agent smoke`);
|
|
34
|
+
return workspace;
|
|
35
|
+
}
|
|
36
|
+
function copyMissing(from, to) {
|
|
37
|
+
if (!existsSync(from))
|
|
38
|
+
return;
|
|
39
|
+
cpSync(from, to, { recursive: true, force: false, errorOnExist: false });
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/init.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,gEAAgE;AAEhE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,KAAK,GAAG,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EACL,eAAe,EAAE,aAAa,GAC/B,MAAM,gBAAgB,CAAA;AAEvB,MAAM,GAAG,GAAG,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE/C,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,UAAgC,EAAE;IACnE,MAAM,cAAc,GAAG,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAA;IACtD,MAAM,cAAc,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrC,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAEhD,kDAAkD;IAClD,8CAA8C;IAC9C,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAA;IACzE,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAA;IACnE,KAAK,MAAM,GAAG,IAAI;QAChB,0BAA0B,EAAE,6BAA6B;QACzD,uBAAuB,EAAE,uBAAuB;KACjD;QAAE,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAClD,IAAI,CAAC,cAAc;QAAE,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IAEhD,OAAO,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,0BAA0B;;aAE/E,SAAS,CAAC,SAAS;;aAEnB,SAAS,CAAC,MAAM,CAAC,MAAM,MAAM,SAAS,CAAC,MAAM,CAAC,SAAS;aACvD,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;;;;6BAIX,CAAC,CAAA;IAC5B,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,EAAU;IAC3C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAM;IAC7B,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAA;AAC1E,CAAC","sourcesContent":["// init 只为“需要持久 Workspace / Prepared Run runtime”的目录建立最小骨架。\n// 普通 Claude Code/Codex Session 只需全局 Skill + bootstrap,不需要 init。\n\nimport { cpSync, existsSync, mkdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport * as cfg from './config.js'\nimport { resolvePackageRoot } from './package-root.js'\nimport {\n ensureWorkspace, workspacePath, type WorkspaceInitOptions,\n} from './workspace.js'\n\nconst PKG = resolvePackageRoot(import.meta.url)\n\nexport function init(root: string, options: WorkspaceInitOptions = {}) {\n const wasInitialized = existsSync(workspacePath(root))\n const existingConfig = cfg.load(root)\n const workspace = ensureWorkspace(root, options)\n\n // Runtime 的机器策略仍需本地存在;只补缺失项,不复制全局 Skill、Ontology,\n // 也不修改项目的 AGENTS.md / CLAUDE.md / .gitignore。\n copyMissing(join(PKG, 'templates', 'behaviors'), join(root, 'behaviors'))\n copyMissing(join(PKG, 'templates', 'fields'), join(root, 'fields'))\n for (const dir of [\n '.dingtalk-agent/sessions', '.dingtalk-agent/event-index',\n '.dingtalk-agent/waits', '.dingtalk-agent/cache',\n ]) mkdirSync(join(root, dir), { recursive: true })\n if (!existingConfig) cfg.save(root, cfg.DEFAULT)\n\n console.log(`${wasInitialized ? '✅ Workspace 已存在;检查完成,未重新初始化' : '✅ 已初始化最小 Agent Workspace'}\n\n Context: ${workspace.contextId}(未显式指定时由目录名稳定派生)\n 配置: .dingtalk-agent/workspace.json\n Storage: ${workspace.mounts.memory} / ${workspace.mounts.knowledge}\n Skill: ${workspace.skills.join(', ')}(按名称解析;不复制进当前项目)\n\n 会话水合: dingtalk-agent bootstrap\n 事件运行时: dingtalk-agent prepare --event-file <event.json>\n 本地验收: dingtalk-agent smoke`)\n return workspace\n}\n\nfunction copyMissing(from: string, to: string): void {\n if (!existsSync(from)) return\n cpSync(from, to, { recursive: true, force: false, errorOnExist: false })\n}\n"]}
|