@xgjktech/xg_cwork_im 1.18.9 → 1.19.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/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -14
- package/dist/index.js.map +1 -1
- package/dist/src/action-store-lifecycle.test.js +0 -2
- package/dist/src/action-store-lifecycle.test.js.map +1 -1
- package/dist/src/block-streaming-config.test.js.map +1 -1
- package/dist/src/dispatch-mentioned-reply.d.ts.map +1 -1
- package/dist/src/dispatch-mentioned-reply.js +16 -2
- package/dist/src/dispatch-mentioned-reply.js.map +1 -1
- package/dist/src/plan-core-adapter.d.ts +46 -0
- package/dist/src/plan-core-adapter.d.ts.map +1 -0
- package/dist/src/plan-core-adapter.js +103 -0
- package/dist/src/plan-core-adapter.js.map +1 -0
- package/dist/src/plan-core-adapter.test.d.ts +2 -0
- package/dist/src/plan-core-adapter.test.d.ts.map +1 -0
- package/dist/src/plan-core-adapter.test.js +164 -0
- package/dist/src/plan-core-adapter.test.js.map +1 -0
- package/dist/src/plan-core-db.d.ts +22 -0
- package/dist/src/plan-core-db.d.ts.map +1 -0
- package/dist/src/plan-core-db.js +91 -0
- package/dist/src/plan-core-db.js.map +1 -0
- package/dist/src/plan-core-db.test.d.ts +2 -0
- package/dist/src/plan-core-db.test.d.ts.map +1 -0
- package/dist/src/plan-core-db.test.js +115 -0
- package/dist/src/plan-core-db.test.js.map +1 -0
- package/dist/src/plan-core-snapshot.d.ts +23 -0
- package/dist/src/plan-core-snapshot.d.ts.map +1 -0
- package/dist/src/plan-core-snapshot.js +65 -0
- package/dist/src/plan-core-snapshot.js.map +1 -0
- package/dist/src/plan-core-snapshot.test.d.ts +2 -0
- package/dist/src/plan-core-snapshot.test.d.ts.map +1 -0
- package/dist/src/plan-core-snapshot.test.js +155 -0
- package/dist/src/plan-core-snapshot.test.js.map +1 -0
- package/dist/src/plan-stat-event.d.ts +4 -19
- package/dist/src/plan-stat-event.d.ts.map +1 -1
- package/dist/src/plan-stat-event.js +4 -88
- package/dist/src/plan-stat-event.js.map +1 -1
- package/dist/src/plan-stat-event.test.js +1 -245
- package/dist/src/plan-stat-event.test.js.map +1 -1
- package/dist/src/plan-tool-middleware.d.ts +2 -10
- package/dist/src/plan-tool-middleware.d.ts.map +1 -1
- package/dist/src/plan-tool-middleware.js +11 -60
- package/dist/src/plan-tool-middleware.js.map +1 -1
- package/dist/src/plan-tool-middleware.test.js +83 -603
- package/dist/src/plan-tool-middleware.test.js.map +1 -1
- package/dist/src/plugin-dispose.d.ts +1 -1
- package/dist/src/plugin-dispose.d.ts.map +1 -1
- package/dist/src/plugin-dispose.js +3 -5
- package/dist/src/plugin-dispose.js.map +1 -1
- package/dist/src/turn-agent-event-subscription.d.ts +1 -1
- package/dist/src/turn-agent-event-subscription.js +1 -1
- package/package.json +9 -5
|
@@ -1,623 +1,103 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
|
-
import { createPlanToolResultMiddleware } from "./plan-tool-middleware.js";
|
|
4
|
-
|
|
5
|
-
function makePlan(overrides = {}) {
|
|
3
|
+
import { createPlanToolResultMiddleware, } from "./plan-tool-middleware.js";
|
|
4
|
+
function makeAction(eventId, overrides = {}) {
|
|
6
5
|
return {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
eventId,
|
|
7
|
+
event: "action_dispatch",
|
|
8
|
+
action: "form_fill",
|
|
9
|
+
target: "page://order-detail",
|
|
10
|
+
version: 1,
|
|
11
|
+
status: "completed",
|
|
12
|
+
scopeControl: { activeSessionOnly: true },
|
|
13
|
+
payload: { data: { id: eventId } },
|
|
14
|
+
timestamp: "2026-09-07T17:00:00.000+08:00",
|
|
15
|
+
origin: { channel: "xg_cwork_im", to: "g-1" },
|
|
12
16
|
...overrides,
|
|
13
17
|
};
|
|
14
18
|
}
|
|
15
|
-
function
|
|
16
|
-
const content = overrides.text !== undefined
|
|
17
|
-
? [{ type: "text", text: overrides.text }]
|
|
18
|
-
: [];
|
|
19
|
+
function makeToolEvent(toolName, overrides = {}) {
|
|
19
20
|
return {
|
|
20
|
-
toolCallId:
|
|
21
|
-
toolName
|
|
21
|
+
toolCallId: "call-1",
|
|
22
|
+
toolName,
|
|
22
23
|
args: {},
|
|
23
|
-
result: {
|
|
24
|
-
|
|
25
|
-
content,
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
function makeSuccessPayload(plan, withEvent = false) {
|
|
30
|
-
return {
|
|
31
|
-
ok: true,
|
|
32
|
-
planId: plan.planId,
|
|
33
|
-
plan,
|
|
34
|
-
...(withEvent ? { event: { emitted: false, reason: "no runId" } } : {}),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function makeDraftSuccessPayload(planId) {
|
|
38
|
-
return {
|
|
39
|
-
ok: true,
|
|
40
|
-
planId,
|
|
41
|
-
view: `goal: 草稿计划\nT0 [ ] 任务`,
|
|
24
|
+
result: { details: undefined, content: [] },
|
|
25
|
+
...overrides,
|
|
42
26
|
};
|
|
43
27
|
}
|
|
44
|
-
class
|
|
45
|
-
|
|
46
|
-
planStatCalls = [];
|
|
47
|
-
sendPlanStatImpl = (params) => {
|
|
48
|
-
this.planStatCalls.push(params);
|
|
49
|
-
};
|
|
50
|
-
getPlanById = (planId) => {
|
|
51
|
-
return this.planById.get(planId);
|
|
52
|
-
};
|
|
53
|
-
sendPlanStat = (params) => {
|
|
54
|
-
this.sendPlanStatImpl(params);
|
|
55
|
-
};
|
|
28
|
+
class Probe {
|
|
29
|
+
sent = [];
|
|
56
30
|
actionById = new Map();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
console.warn = (...args) => {
|
|
68
|
-
const line = args.map(String).join(" ");
|
|
69
|
-
if (line.includes("[plan-telemetry]")) {
|
|
70
|
-
lines.push(line);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
try {
|
|
74
|
-
fn();
|
|
75
|
-
}
|
|
76
|
-
finally {
|
|
77
|
-
console.warn = original;
|
|
78
|
-
}
|
|
79
|
-
return lines;
|
|
80
|
-
}
|
|
81
|
-
function captureWarnLines(fn) {
|
|
82
|
-
const original = console.warn;
|
|
83
|
-
const lines = [];
|
|
84
|
-
console.warn = (...args) => {
|
|
85
|
-
lines.push(args.map(String).join(" "));
|
|
86
|
-
};
|
|
87
|
-
try {
|
|
88
|
-
fn();
|
|
89
|
-
}
|
|
90
|
-
finally {
|
|
91
|
-
console.warn = original;
|
|
31
|
+
deps;
|
|
32
|
+
handler;
|
|
33
|
+
constructor() {
|
|
34
|
+
this.deps = {
|
|
35
|
+
getActionById: (eventId) => this.actionById.get(eventId),
|
|
36
|
+
sendActionRunEvent: (action) => {
|
|
37
|
+
this.sent.push(action);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
this.handler = createPlanToolResultMiddleware(this.deps);
|
|
92
41
|
}
|
|
93
|
-
return lines;
|
|
94
42
|
}
|
|
95
|
-
describe("createPlanToolResultMiddleware:
|
|
96
|
-
it("
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
});
|
|
112
|
-
assert.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
});
|
|
139
|
-
deps.planById.set(authoritativePlan.planId, authoritativePlan);
|
|
140
|
-
const event = makeEvent({
|
|
141
|
-
toolCallId: "call-1",
|
|
142
|
-
toolName: "xg_plan_write",
|
|
143
|
-
details: makeDraftSuccessPayload(authoritativePlan.planId),
|
|
144
|
-
});
|
|
145
|
-
const lines = captureTelemetryLines(() => {
|
|
146
|
-
const result = handler(event, FAKE_CTX);
|
|
147
|
-
assert.equal(result, undefined);
|
|
148
|
-
});
|
|
149
|
-
assert.equal(lines.length, 1);
|
|
150
|
-
assert.ok(lines[0].includes("备份旧配置"));
|
|
151
|
-
assert.ok(lines[0].includes("缺少权限"));
|
|
152
|
-
assert.deepEqual(deps.planStatCalls, [{ plan: authoritativePlan, source: "tool" }]);
|
|
153
|
-
});
|
|
154
|
-
it("反查得到 cancelled 计划 → PLAN_STAT 照推,blocked 遥测跳过", () => {
|
|
155
|
-
const deps = new RecordingDeps();
|
|
156
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
157
|
-
const cancelledPlan = makePlan({
|
|
158
|
-
planId: "plan-cancelled",
|
|
159
|
-
status: "cancelled",
|
|
160
|
-
cancelledAt: "2026-07-31T01:02:03.000Z",
|
|
161
|
-
tasks: [{ title: "旧阻塞", status: "blocked", note: "取消后不应告警" }],
|
|
162
|
-
});
|
|
163
|
-
deps.planById.set(cancelledPlan.planId, cancelledPlan);
|
|
164
|
-
const event = makeEvent({
|
|
165
|
-
toolCallId: "call-cancelled",
|
|
166
|
-
details: {
|
|
167
|
-
ok: true,
|
|
168
|
-
planId: cancelledPlan.planId,
|
|
169
|
-
view: "goal: 【已取消】迁移配置到新版本\nT0 [!] 旧阻塞",
|
|
170
|
-
cancelled: true,
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
const lines = captureTelemetryLines(() => {
|
|
174
|
-
assert.equal(handler(event, FAKE_CTX), undefined);
|
|
175
|
-
});
|
|
176
|
-
assert.deepEqual(lines, []);
|
|
177
|
-
assert.deepEqual(deps.planStatCalls, [{ plan: cancelledPlan, source: "tool" }]);
|
|
178
|
-
});
|
|
179
|
-
it("0.3.0 新形状反查未命中 → 记 warn 后放弃,不推送且不抛", () => {
|
|
180
|
-
const deps = new RecordingDeps();
|
|
181
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
182
|
-
const event = makeEvent({
|
|
183
|
-
toolCallId: "call-draft-miss",
|
|
184
|
-
details: makeDraftSuccessPayload("plan-draft-miss"),
|
|
185
|
-
});
|
|
186
|
-
const lines = captureWarnLines(() => {
|
|
187
|
-
assert.doesNotThrow(() => handler(event, FAKE_CTX));
|
|
188
|
-
});
|
|
189
|
-
assert.equal(deps.planStatCalls.length, 0);
|
|
190
|
-
assert.equal(lines.length, 1);
|
|
191
|
-
assert.ok(lines[0].includes("反查不到 plan"));
|
|
192
|
-
assert.ok(lines[0].includes("plan-draft-miss"));
|
|
193
|
-
});
|
|
194
|
-
it("0.2.0 旧形状反查未命中 → 回退使用返回中的完整 plan 推送", () => {
|
|
195
|
-
const deps = new RecordingDeps();
|
|
196
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
197
|
-
const legacyPlan = makePlan({
|
|
198
|
-
planId: "plan-legacy-fallback",
|
|
199
|
-
tasks: [{ title: "旧版任务", status: "pending" }],
|
|
200
|
-
});
|
|
201
|
-
const event = makeEvent({
|
|
202
|
-
toolCallId: "call-legacy-fallback",
|
|
203
|
-
details: makeSuccessPayload(legacyPlan, true),
|
|
204
|
-
});
|
|
205
|
-
handler(event, FAKE_CTX);
|
|
206
|
-
assert.deepEqual(deps.planStatCalls, [{ plan: legacyPlan, source: "tool" }]);
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
describe("createPlanToolResultMiddleware: text 回退路径", () => {
|
|
210
|
-
it("falls back to content[0].text when details is undefined", () => {
|
|
211
|
-
const deps = new RecordingDeps();
|
|
212
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
213
|
-
const plan = makePlan({
|
|
214
|
-
planId: "plan-2",
|
|
215
|
-
tasks: [{ title: "校验数据", status: "blocked", note: "依赖未就绪" }],
|
|
216
|
-
});
|
|
217
|
-
const rawBody = JSON.stringify(makeSuccessPayload(plan, true), null, 2);
|
|
218
|
-
const event = makeEvent({
|
|
219
|
-
toolCallId: "call-2",
|
|
220
|
-
toolName: "xg_plan_write",
|
|
221
|
-
details: undefined,
|
|
222
|
-
text: rawBody,
|
|
223
|
-
});
|
|
224
|
-
const lines = captureTelemetryLines(() => {
|
|
225
|
-
const result = handler(event, FAKE_CTX);
|
|
226
|
-
assert.equal(result, undefined);
|
|
227
|
-
});
|
|
228
|
-
assert.equal(lines.length, 1);
|
|
229
|
-
assert.ok(lines[0].includes("校验数据"));
|
|
230
|
-
});
|
|
231
|
-
it("uses getPlanById authoritative state when details is missing (D5/RULING-1 §3)", () => {
|
|
232
|
-
const deps = new RecordingDeps();
|
|
233
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
234
|
-
// text 路径拿到的 plan 里没有 blocked task,但权威反查里有
|
|
235
|
-
const textPlan = makePlan({ planId: "plan-3", tasks: [{ title: "t1", status: "pending" }] });
|
|
236
|
-
const authoritativePlan = makePlan({
|
|
237
|
-
planId: "plan-3",
|
|
238
|
-
tasks: [{ title: "t1", status: "blocked", note: "authoritative 才有的阻塞" }],
|
|
239
|
-
});
|
|
240
|
-
deps.planById.set("plan-3", authoritativePlan);
|
|
241
|
-
const rawBody = JSON.stringify(makeSuccessPayload(textPlan), null, 2);
|
|
242
|
-
const event = makeEvent({
|
|
243
|
-
toolCallId: "call-3",
|
|
244
|
-
toolName: "xg_plan_write",
|
|
245
|
-
details: undefined,
|
|
246
|
-
text: rawBody,
|
|
247
|
-
});
|
|
248
|
-
const lines = captureTelemetryLines(() => {
|
|
249
|
-
handler(event, FAKE_CTX);
|
|
250
|
-
});
|
|
251
|
-
assert.equal(lines.length, 1);
|
|
252
|
-
assert.ok(lines[0].includes("authoritative 才有的阻塞"));
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
describe("createPlanToolResultMiddleware: 守卫拒绝畸形 payload", () => {
|
|
256
|
-
it("does not emit telemetry for ok:false", () => {
|
|
257
|
-
const deps = new RecordingDeps();
|
|
258
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
259
|
-
const event = makeEvent({
|
|
260
|
-
toolCallId: "call-4",
|
|
261
|
-
toolName: "xg_plan_write",
|
|
262
|
-
details: { ok: false, error: { code: "PLAN_NOT_FOUND" } },
|
|
263
|
-
});
|
|
264
|
-
const lines = captureTelemetryLines(() => {
|
|
265
|
-
assert.equal(handler(event, FAKE_CTX), undefined);
|
|
266
|
-
});
|
|
267
|
-
assert.equal(lines.length, 0);
|
|
268
|
-
});
|
|
269
|
-
it("does not emit telemetry when planId is missing", () => {
|
|
270
|
-
const deps = new RecordingDeps();
|
|
271
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
272
|
-
const event = makeEvent({
|
|
273
|
-
toolCallId: "call-5",
|
|
274
|
-
toolName: "xg_plan_write",
|
|
275
|
-
details: { ok: true, plan: makePlan() },
|
|
276
|
-
});
|
|
277
|
-
const lines = captureTelemetryLines(() => {
|
|
278
|
-
assert.equal(handler(event, FAKE_CTX), undefined);
|
|
279
|
-
});
|
|
280
|
-
assert.equal(lines.length, 0);
|
|
281
|
-
});
|
|
282
|
-
it("does not emit telemetry when tasks is not an array", () => {
|
|
283
|
-
const deps = new RecordingDeps();
|
|
284
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
285
|
-
const event = makeEvent({
|
|
286
|
-
toolCallId: "call-6",
|
|
287
|
-
toolName: "xg_plan_write",
|
|
288
|
-
details: {
|
|
289
|
-
ok: true,
|
|
290
|
-
planId: "plan-6",
|
|
291
|
-
plan: { planId: "plan-6", goal: "g", createdAt: "t", updatedAt: "t", tasks: "not-an-array" },
|
|
292
|
-
},
|
|
293
|
-
});
|
|
294
|
-
const lines = captureTelemetryLines(() => {
|
|
295
|
-
assert.equal(handler(event, FAKE_CTX), undefined);
|
|
296
|
-
});
|
|
297
|
-
assert.equal(lines.length, 0);
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
describe("createPlanToolResultMiddleware: handler 内部出错不外抛", () => {
|
|
301
|
-
it("resolves normally even when getPlanById throws", () => {
|
|
302
|
-
const deps = new RecordingDeps();
|
|
303
|
-
deps.getPlanById = () => {
|
|
43
|
+
describe("createPlanToolResultMiddleware: 仅 xg_action_dispatch 分支(plan 已移除)", () => {
|
|
44
|
+
it("非 action 工具直接放行:不反查、不发", () => {
|
|
45
|
+
const probe = new Probe();
|
|
46
|
+
const event = makeToolEvent("xg_plan_write", {
|
|
47
|
+
result: { details: { ok: true, planId: "p-1" }, content: [] },
|
|
48
|
+
});
|
|
49
|
+
assert.equal(probe.handler(event, undefined), undefined);
|
|
50
|
+
assert.equal(probe.actionById.size, 0);
|
|
51
|
+
assert.deepEqual(probe.sent, []);
|
|
52
|
+
});
|
|
53
|
+
it("details 命中 eventId → 反查并发送", () => {
|
|
54
|
+
const probe = new Probe();
|
|
55
|
+
const action = makeAction("e-1");
|
|
56
|
+
probe.actionById.set("e-1", action);
|
|
57
|
+
probe.handler(makeToolEvent("xg_action_dispatch", {
|
|
58
|
+
result: { details: { ok: true, eventId: "e-1" }, content: [] },
|
|
59
|
+
}), undefined);
|
|
60
|
+
assert.deepEqual(probe.sent, [action]);
|
|
61
|
+
});
|
|
62
|
+
it("回退解析 content[0].text JSON 里的 eventId", () => {
|
|
63
|
+
const probe = new Probe();
|
|
64
|
+
const action = makeAction("e-2");
|
|
65
|
+
probe.actionById.set("e-2", action);
|
|
66
|
+
probe.handler(makeToolEvent("xg_action_dispatch", {
|
|
67
|
+
result: { details: undefined, content: [{ type: "text", text: '{"ok":true,"eventId":"e-2"}' }] },
|
|
68
|
+
}), undefined);
|
|
69
|
+
assert.deepEqual(probe.sent, [action]);
|
|
70
|
+
});
|
|
71
|
+
it("解析不到 eventId → 跳过,不抛", () => {
|
|
72
|
+
const probe = new Probe();
|
|
73
|
+
assert.equal(probe.handler(makeToolEvent("xg_action_dispatch", { result: { details: { ok: false }, content: [] } }), undefined), undefined);
|
|
74
|
+
assert.deepEqual(probe.sent, []);
|
|
75
|
+
});
|
|
76
|
+
it("反查不到 action → 跳过,不抛", () => {
|
|
77
|
+
const probe = new Probe();
|
|
78
|
+
assert.equal(probe.handler(makeToolEvent("xg_action_dispatch", {
|
|
79
|
+
result: { details: { ok: true, eventId: "missing" }, content: [] },
|
|
80
|
+
}), undefined), undefined);
|
|
81
|
+
assert.deepEqual(probe.sent, []);
|
|
82
|
+
});
|
|
83
|
+
it("getActionById 抛异常 → handler 不外抛", () => {
|
|
84
|
+
const probe = new Probe();
|
|
85
|
+
probe.deps.getActionById = () => {
|
|
304
86
|
throw new Error("boom");
|
|
305
87
|
};
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
});
|
|
315
|
-
assert.doesNotThrow(() => handler(event, FAKE_CTX));
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
describe("createPlanToolResultMiddleware: 恒返回 void(同步性断言)", () => {
|
|
319
|
-
// 本任务 handler 已无异步出口(推群没了)。TASK-2x-4 引入 sendPlanStat 的
|
|
320
|
-
// fire-and-forget 之后,这条要恢复成"even when 内部 promise 永不 resolve 仍立即返回"的真实用例。
|
|
321
|
-
it("handler's return value is undefined and the call itself is synchronous", () => {
|
|
322
|
-
const deps = new RecordingDeps();
|
|
323
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
324
|
-
const plan = makePlan({ tasks: [{ title: "t1", status: "blocked", note: "n" }] });
|
|
325
|
-
const event = makeEvent({
|
|
326
|
-
toolCallId: "call-9",
|
|
327
|
-
toolName: "xg_plan_write",
|
|
328
|
-
details: makeSuccessPayload(plan),
|
|
329
|
-
});
|
|
330
|
-
const result = handler(event, FAKE_CTX);
|
|
331
|
-
assert.equal(result, undefined);
|
|
332
|
-
});
|
|
333
|
-
it("0.3.0 新形状反查后不修改 event.result", () => {
|
|
334
|
-
const deps = new RecordingDeps();
|
|
335
|
-
const plan = makePlan({ planId: "plan-no-mutate" });
|
|
336
|
-
deps.planById.set(plan.planId, plan);
|
|
337
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
338
|
-
const event = makeEvent({
|
|
339
|
-
toolCallId: "call-no-mutate",
|
|
340
|
-
details: makeDraftSuccessPayload(plan.planId),
|
|
341
|
-
text: JSON.stringify(makeDraftSuccessPayload(plan.planId)),
|
|
342
|
-
});
|
|
343
|
-
const originalResult = structuredClone(event.result);
|
|
344
|
-
handler(event, FAKE_CTX);
|
|
345
|
-
assert.deepEqual(event.result, originalResult);
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
describe("createPlanToolResultMiddleware: [plan-telemetry] 遥测行为(D2)", () => {
|
|
349
|
-
it("plan 含 1 个 blocked task → 恰好打 1 行,内容含 planId/taskIndex/taskTitle/note/goal", () => {
|
|
350
|
-
const deps = new RecordingDeps();
|
|
351
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
352
|
-
const plan = makePlan({
|
|
353
|
-
planId: "plan-telemetry-1",
|
|
354
|
-
goal: "迁移配置到新版本",
|
|
355
|
-
tasks: [{ title: "备份旧配置", status: "blocked", note: "缺少权限" }],
|
|
356
|
-
});
|
|
357
|
-
const event = makeEvent({
|
|
358
|
-
toolCallId: "call-t1",
|
|
359
|
-
toolName: "xg_plan_write",
|
|
360
|
-
details: makeSuccessPayload(plan),
|
|
361
|
-
});
|
|
362
|
-
const lines = captureTelemetryLines(() => {
|
|
363
|
-
handler(event, FAKE_CTX);
|
|
364
|
-
});
|
|
365
|
-
assert.equal(lines.length, 1);
|
|
366
|
-
const jsonPart = lines[0].slice(lines[0].indexOf("{"));
|
|
367
|
-
const parsed = JSON.parse(jsonPart);
|
|
368
|
-
assert.equal(parsed.planId, "plan-telemetry-1");
|
|
369
|
-
assert.equal(parsed.taskIndex, 0);
|
|
370
|
-
assert.equal(parsed.taskTitle, "备份旧配置");
|
|
371
|
-
assert.equal(parsed.note, "缺少权限");
|
|
372
|
-
assert.equal(parsed.goal, "迁移配置到新版本");
|
|
373
|
-
assert.equal(parsed.toolName, "xg_plan_write");
|
|
374
|
-
});
|
|
375
|
-
it("plan 含 2 个 blocked task → 打 2 行,顺序与 tasks 下标一致", () => {
|
|
376
|
-
const deps = new RecordingDeps();
|
|
377
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
378
|
-
const plan = makePlan({
|
|
379
|
-
tasks: [
|
|
380
|
-
{ title: "备份旧配置", status: "blocked", note: "缺少权限" },
|
|
381
|
-
{ title: "校验数据", status: "pending" },
|
|
382
|
-
{ title: "切换流量", status: "blocked", note: "依赖未就绪" },
|
|
383
|
-
],
|
|
384
|
-
});
|
|
385
|
-
const event = makeEvent({
|
|
386
|
-
toolCallId: "call-t2",
|
|
387
|
-
toolName: "xg_plan_write",
|
|
388
|
-
details: makeSuccessPayload(plan),
|
|
389
|
-
});
|
|
390
|
-
const lines = captureTelemetryLines(() => {
|
|
391
|
-
handler(event, FAKE_CTX);
|
|
392
|
-
});
|
|
393
|
-
assert.equal(lines.length, 2);
|
|
394
|
-
const first = JSON.parse(lines[0].slice(lines[0].indexOf("{")));
|
|
395
|
-
const second = JSON.parse(lines[1].slice(lines[1].indexOf("{")));
|
|
396
|
-
assert.equal(first.taskIndex, 0);
|
|
397
|
-
assert.equal(second.taskIndex, 2);
|
|
398
|
-
});
|
|
399
|
-
it("无 blocked task → 不打任何遥测行", () => {
|
|
400
|
-
const deps = new RecordingDeps();
|
|
401
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
402
|
-
const plan = makePlan({
|
|
403
|
-
tasks: [{ title: "t1", status: "pending" }, { title: "t2", status: "completed" }],
|
|
404
|
-
});
|
|
405
|
-
const event = makeEvent({
|
|
406
|
-
toolCallId: "call-t3",
|
|
407
|
-
toolName: "xg_plan_write",
|
|
408
|
-
details: makeSuccessPayload(plan),
|
|
409
|
-
});
|
|
410
|
-
const lines = captureTelemetryLines(() => {
|
|
411
|
-
handler(event, FAKE_CTX);
|
|
412
|
-
});
|
|
413
|
-
assert.equal(lines.length, 0);
|
|
414
|
-
});
|
|
415
|
-
it("🔴 同一 plan、同一 blocked、连续两次 middleware 事件 → 打 2 行(钉住不去重)", () => {
|
|
416
|
-
const deps = new RecordingDeps();
|
|
417
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
418
|
-
const plan = makePlan({
|
|
419
|
-
planId: "plan-no-dedup",
|
|
420
|
-
tasks: [{ title: "备份旧配置", status: "blocked", note: "缺少权限" }],
|
|
421
|
-
});
|
|
422
|
-
const event = makeEvent({
|
|
423
|
-
toolCallId: "call-t4",
|
|
424
|
-
toolName: "xg_plan_write",
|
|
425
|
-
details: makeSuccessPayload(plan),
|
|
426
|
-
});
|
|
427
|
-
const lines = captureTelemetryLines(() => {
|
|
428
|
-
handler(event, FAKE_CTX);
|
|
429
|
-
handler(event, FAKE_CTX);
|
|
430
|
-
});
|
|
431
|
-
assert.equal(lines.length, 2, "不去重:同一 blocked 连续两次事件应各打一行");
|
|
432
|
-
});
|
|
433
|
-
it("plan 带 origin → 日志行含 channel/to/sessionKey", () => {
|
|
434
|
-
const deps = new RecordingDeps();
|
|
435
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
436
|
-
const plan = makePlan({
|
|
437
|
-
tasks: [{ title: "t1", status: "blocked", note: "n" }],
|
|
438
|
-
origin: { channel: "xg_cwork_im", to: "g-123", sessionKey: "sess-1" },
|
|
439
|
-
});
|
|
440
|
-
const event = makeEvent({
|
|
441
|
-
toolCallId: "call-t5",
|
|
442
|
-
toolName: "xg_plan_write",
|
|
443
|
-
details: makeSuccessPayload(plan),
|
|
444
|
-
});
|
|
445
|
-
const lines = captureTelemetryLines(() => {
|
|
446
|
-
handler(event, FAKE_CTX);
|
|
447
|
-
});
|
|
448
|
-
assert.equal(lines.length, 1);
|
|
449
|
-
const parsed = JSON.parse(lines[0].slice(lines[0].indexOf("{")));
|
|
450
|
-
assert.equal(parsed.channel, "xg_cwork_im");
|
|
451
|
-
assert.equal(parsed.to, "g-123");
|
|
452
|
-
assert.equal(parsed.sessionKey, "sess-1");
|
|
453
|
-
});
|
|
454
|
-
it("🔴 plan 无 origin(旧数据)→ 日志行不含这几个键,且照常打日志、不抛、不跳过", () => {
|
|
455
|
-
const deps = new RecordingDeps();
|
|
456
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
457
|
-
const plan = makePlan({
|
|
458
|
-
tasks: [{ title: "t1", status: "blocked", note: "n" }],
|
|
459
|
-
});
|
|
460
|
-
const event = makeEvent({
|
|
461
|
-
toolCallId: "call-t6",
|
|
462
|
-
toolName: "xg_plan_write",
|
|
463
|
-
details: makeSuccessPayload(plan),
|
|
464
|
-
});
|
|
465
|
-
const lines = captureTelemetryLines(() => {
|
|
466
|
-
assert.doesNotThrow(() => handler(event, FAKE_CTX));
|
|
467
|
-
});
|
|
468
|
-
assert.equal(lines.length, 1);
|
|
469
|
-
const parsed = JSON.parse(lines[0].slice(lines[0].indexOf("{")));
|
|
470
|
-
assert.equal("channel" in parsed, false);
|
|
471
|
-
assert.equal("to" in parsed, false);
|
|
472
|
-
assert.equal("sessionKey" in parsed, false);
|
|
473
|
-
});
|
|
474
|
-
it("blocked task 的 note 为空/空白 → 该条不产出遥测", () => {
|
|
475
|
-
const deps = new RecordingDeps();
|
|
476
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
477
|
-
const plan = makePlan({
|
|
478
|
-
tasks: [
|
|
479
|
-
{ title: "无 note", status: "blocked" },
|
|
480
|
-
{ title: "空白 note", status: "blocked", note: " " },
|
|
481
|
-
],
|
|
482
|
-
});
|
|
483
|
-
const event = makeEvent({
|
|
484
|
-
toolCallId: "call-t7",
|
|
485
|
-
toolName: "xg_plan_write",
|
|
486
|
-
details: makeSuccessPayload(plan),
|
|
487
|
-
});
|
|
488
|
-
const lines = captureTelemetryLines(() => {
|
|
489
|
-
handler(event, FAKE_CTX);
|
|
490
|
-
});
|
|
491
|
-
assert.equal(lines.length, 0);
|
|
492
|
-
});
|
|
493
|
-
});
|
|
494
|
-
describe("createPlanToolResultMiddleware: PLAN_STAT 增量推送(D3)", () => {
|
|
495
|
-
it("plan_write 成功 → 发 1 条 PLAN_STAT,source === \"tool\"", () => {
|
|
496
|
-
const deps = new RecordingDeps();
|
|
497
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
498
|
-
const plan = makePlan({ tasks: [{ title: "t1", status: "pending" }] });
|
|
499
|
-
const event = makeEvent({
|
|
500
|
-
toolCallId: "call-ps1",
|
|
501
|
-
toolName: "xg_plan_write",
|
|
502
|
-
details: makeSuccessPayload(plan),
|
|
503
|
-
});
|
|
504
|
-
handler(event, FAKE_CTX);
|
|
505
|
-
assert.equal(deps.planStatCalls.length, 1);
|
|
506
|
-
assert.equal(deps.planStatCalls[0].source, "tool");
|
|
507
|
-
assert.equal(deps.planStatCalls[0].plan.planId, plan.planId);
|
|
508
|
-
});
|
|
509
|
-
it("xg_plan_execute 的单计划/列表返回均直接忽略,不解析、不反查、不推送", () => {
|
|
510
|
-
const deps = new RecordingDeps();
|
|
511
|
-
let getPlanByIdCalls = 0;
|
|
512
|
-
deps.getPlanById = () => {
|
|
513
|
-
getPlanByIdCalls += 1;
|
|
514
|
-
return undefined;
|
|
515
|
-
};
|
|
516
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
517
|
-
const events = [
|
|
518
|
-
makeEvent({
|
|
519
|
-
toolCallId: "call-ps2",
|
|
520
|
-
toolName: "xg_plan_execute",
|
|
521
|
-
details: makeDraftSuccessPayload("plan-read"),
|
|
522
|
-
}),
|
|
523
|
-
makeEvent({
|
|
524
|
-
toolCallId: "call-ps3",
|
|
525
|
-
toolName: "xg_plan_execute",
|
|
526
|
-
details: {
|
|
527
|
-
ok: true,
|
|
528
|
-
plans: [{
|
|
529
|
-
planId: "plan-list",
|
|
530
|
-
goal: "最近计划",
|
|
531
|
-
updatedAt: "2026-07-29T00:00:00.000Z",
|
|
532
|
-
taskCounts: { total: 1, completed: 0, blocked: 0, inProgress: 0 },
|
|
533
|
-
}],
|
|
534
|
-
},
|
|
535
|
-
}),
|
|
536
|
-
];
|
|
537
|
-
const lines = captureWarnLines(() => {
|
|
538
|
-
for (const event of events) {
|
|
539
|
-
handler(event, FAKE_CTX);
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
assert.equal(getPlanByIdCalls, 0);
|
|
543
|
-
assert.equal(deps.planStatCalls.length, 0);
|
|
544
|
-
assert.equal(lines.length, 0);
|
|
545
|
-
});
|
|
546
|
-
it("🔴 工具返回 ok:false → 不发 PLAN_STAT(失败不发)", () => {
|
|
547
|
-
const deps = new RecordingDeps();
|
|
548
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
549
|
-
const event = makeEvent({
|
|
550
|
-
toolCallId: "call-ps5",
|
|
551
|
-
toolName: "xg_plan_write",
|
|
552
|
-
details: { ok: false, error: { code: "PLAN_NOT_FOUND" } },
|
|
553
|
-
});
|
|
554
|
-
handler(event, FAKE_CTX);
|
|
555
|
-
assert.equal(deps.planStatCalls.length, 0);
|
|
556
|
-
});
|
|
557
|
-
it("🔴 非 plan 工具 → 不发 PLAN_STAT", () => {
|
|
558
|
-
const deps = new RecordingDeps();
|
|
559
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
560
|
-
const plan = makePlan({ tasks: [{ title: "t1", status: "pending" }] });
|
|
561
|
-
const event = makeEvent({
|
|
562
|
-
toolCallId: "call-ps6",
|
|
563
|
-
toolName: "some_other_tool",
|
|
564
|
-
details: makeSuccessPayload(plan),
|
|
565
|
-
});
|
|
566
|
-
handler(event, FAKE_CTX);
|
|
567
|
-
assert.equal(deps.planStatCalls.length, 0);
|
|
568
|
-
});
|
|
569
|
-
it("🔴 sendPlanStat 内部抛异常 → handler 不外抛、仍返回 void(红线回归)", () => {
|
|
570
|
-
const deps = new RecordingDeps();
|
|
571
|
-
deps.sendPlanStatImpl = () => {
|
|
88
|
+
assert.equal(probe.handler(makeToolEvent("xg_action_dispatch", {
|
|
89
|
+
result: { details: { ok: true, eventId: "e-1" }, content: [] },
|
|
90
|
+
}), undefined), undefined);
|
|
91
|
+
});
|
|
92
|
+
it("sendActionRunEvent 抛异常 → handler 不外抛", () => {
|
|
93
|
+
const probe = new Probe();
|
|
94
|
+
probe.actionById.set("e-1", makeAction("e-1"));
|
|
95
|
+
probe.deps.sendActionRunEvent = () => {
|
|
572
96
|
throw new Error("boom");
|
|
573
97
|
};
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
toolCallId: "call-ps7",
|
|
578
|
-
toolName: "xg_plan_write",
|
|
579
|
-
details: makeSuccessPayload(plan),
|
|
580
|
-
});
|
|
581
|
-
assert.doesNotThrow(() => {
|
|
582
|
-
const result = handler(event, FAKE_CTX);
|
|
583
|
-
assert.equal(result, undefined);
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
|
-
it("🔴 跨调用序列:同一 plan 连续 3 次成功调用 → 发 3 条(全量幂等,不去重)", () => {
|
|
587
|
-
const deps = new RecordingDeps();
|
|
588
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
589
|
-
const plan = makePlan({ planId: "plan-stat-no-dedup", tasks: [{ title: "t1", status: "pending" }] });
|
|
590
|
-
const event = makeEvent({
|
|
591
|
-
toolCallId: "call-ps8",
|
|
592
|
-
toolName: "xg_plan_write",
|
|
593
|
-
details: makeSuccessPayload(plan),
|
|
594
|
-
});
|
|
595
|
-
handler(event, FAKE_CTX);
|
|
596
|
-
handler(event, FAKE_CTX);
|
|
597
|
-
handler(event, FAKE_CTX);
|
|
598
|
-
assert.equal(deps.planStatCalls.length, 3, "不去重:同一 plan 连续 3 次成功调用应各发一条");
|
|
599
|
-
});
|
|
600
|
-
});
|
|
601
|
-
describe("createPlanToolResultMiddleware: xg_action_dispatch → RUN_EVENT", () => {
|
|
602
|
-
it("成功结果按 eventId 反查权威 action 并推送", () => {
|
|
603
|
-
const deps = new RecordingDeps();
|
|
604
|
-
const action = {
|
|
605
|
-
eventId: "evt-1", event: "action_dispatch", action: "ui_refresh", target: "order_detail",
|
|
606
|
-
version: 1, status: "completed", payload: { data: { id: "ORD-1" } }, timestamp: "2026-08-13T00:00:00.000Z",
|
|
607
|
-
origin: { channel: "xg_cwork_im", to: "g-1" },
|
|
608
|
-
};
|
|
609
|
-
deps.actionById.set(action.eventId, action);
|
|
610
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
611
|
-
handler(makeEvent({ toolName: "xg_action_dispatch", details: { ok: true, eventId: action.eventId } }), FAKE_CTX);
|
|
612
|
-
assert.deepEqual(deps.actionRunEventCalls, [action]);
|
|
613
|
-
assert.equal(deps.planStatCalls.length, 0);
|
|
614
|
-
});
|
|
615
|
-
it("失败结果或缺失权威 action 均不推送", () => {
|
|
616
|
-
const deps = new RecordingDeps();
|
|
617
|
-
const handler = createPlanToolResultMiddleware(deps);
|
|
618
|
-
handler(makeEvent({ toolName: "xg_action_dispatch", details: { ok: false } }), FAKE_CTX);
|
|
619
|
-
handler(makeEvent({ toolName: "xg_action_dispatch", details: { ok: true, eventId: "missing" } }), FAKE_CTX);
|
|
620
|
-
assert.equal(deps.actionRunEventCalls.length, 0);
|
|
98
|
+
assert.equal(probe.handler(makeToolEvent("xg_action_dispatch", {
|
|
99
|
+
result: { details: { ok: true, eventId: "e-1" }, content: [] },
|
|
100
|
+
}), undefined), undefined);
|
|
621
101
|
});
|
|
622
102
|
});
|
|
623
103
|
//# sourceMappingURL=plan-tool-middleware.test.js.map
|