mslxdff 0.1.225 → 0.1.227
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/docs/cli_help_mini.md +4 -1
- package/package.json +1 -1
- package/src/cli/commands/provider/index.js +2 -0
- package/src/cli/commands/provider/workbuddy-quota.js +154 -0
- package/src/peers.js +345 -307
- package/src/providers/workbuddy/activity.js +204 -0
- package/src/providers/workbuddy/desktop-report.js +286 -0
- package/src/providers/workbuddy/expert-tasks.js +205 -0
- package/src/providers/workbuddy/growth-api.js +66 -32
- package/src/providers/workbuddy/growth-plans.js +32 -21
- package/src/providers/workbuddy/growth.js +83 -17
- package/src/runtime/keep-warm.js +135 -120
- package/src/runtime/workbuddy-growth.js +25 -3
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { compatFetch, timeoutSignal } from "../../compat.js";
|
|
3
|
+
import { growthRequest } from "./growth-api.js";
|
|
4
|
+
// WorkBuddy 每日活跃上报 + 连登管家(兑换档位 + 抽奖)。
|
|
5
|
+
//
|
|
6
|
+
// 为什么不靠签到:实测(2026-10-20,本机三号)签到每天成功(code 0)但
|
|
7
|
+
// GET /activity/growth/streak 的 days 恒为 0 —— 连登只认 /v2/report 的
|
|
8
|
+
// chat_request_send 事件,签到不点亮。连登档位 7/14/28 天的积分、补签卡、
|
|
9
|
+
// 抽奖次数全部拿不到,属于「不报错但持续漏领」。
|
|
10
|
+
//
|
|
11
|
+
// 上报形状照抄客户端(字段宁全勿简,防上游加严);userId 必填,缺失服务端
|
|
12
|
+
// 200 但静默丢弃。每号每天 1 次即可(按天去重,重复上报无额外收益)。
|
|
13
|
+
|
|
14
|
+
const REPORT_BASES = ["https://www.codebuddy.cn", "https://copilot.tencent.com"];
|
|
15
|
+
|
|
16
|
+
function reportHeaders({ at, uid, domain, enterpriseId }) {
|
|
17
|
+
return {
|
|
18
|
+
Authorization: `Bearer ${at}`,
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
Accept: "application/json",
|
|
21
|
+
"X-User-Id": uid,
|
|
22
|
+
"X-Domain": domain || "www.codebuddy.cn",
|
|
23
|
+
"X-Product": "SaaS",
|
|
24
|
+
"User-Agent": "CLI/2.115.0 WorkBuddy/2.115.0",
|
|
25
|
+
Origin: "https://www.codebuddy.cn",
|
|
26
|
+
Referer: "https://www.codebuddy.cn/",
|
|
27
|
+
...(enterpriseId ? { "X-Enterprise-Id": enterpriseId } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function chatActivityEvent({ uid, conversationId, requestId, model = "deepseek-v4-flash", modelName = "DeepSeek V4 Flash" }) {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
return {
|
|
34
|
+
eventCode: "chat_request_send",
|
|
35
|
+
timestamp: now,
|
|
36
|
+
reportDelay: 0,
|
|
37
|
+
mode: "craft",
|
|
38
|
+
conversationId,
|
|
39
|
+
requestId,
|
|
40
|
+
inputLength: 12,
|
|
41
|
+
requestModelId: model,
|
|
42
|
+
requestModelName: modelName,
|
|
43
|
+
isPlan: false,
|
|
44
|
+
isAutoExecuteTerminal: false,
|
|
45
|
+
isAutoModify: false,
|
|
46
|
+
codebaseEnable: false,
|
|
47
|
+
maxToken: 0,
|
|
48
|
+
maxSteps: 0,
|
|
49
|
+
temperature: 0,
|
|
50
|
+
maxRetries: 0,
|
|
51
|
+
mentionContexts: [],
|
|
52
|
+
knowledgeId: [],
|
|
53
|
+
knowledgeName: [],
|
|
54
|
+
codebaseId: "",
|
|
55
|
+
mentionContextCount: 0,
|
|
56
|
+
command: "",
|
|
57
|
+
expertId: "",
|
|
58
|
+
recommendId: "",
|
|
59
|
+
skillId: "",
|
|
60
|
+
skillCount: 0,
|
|
61
|
+
totalCount: 0,
|
|
62
|
+
fileUri: "",
|
|
63
|
+
presentAt: now,
|
|
64
|
+
traceId: "",
|
|
65
|
+
rootRequestId: requestId,
|
|
66
|
+
parentConversationId: conversationId,
|
|
67
|
+
agentName: "default",
|
|
68
|
+
agentType: "conversation",
|
|
69
|
+
userId: uid,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 一条对话活跃上报:点亮当日连登 + 解锁 first_buddy(领养前置)。双域容灾,不抛异常。
|
|
74
|
+
export async function reportChatActivity({ uid, at, domain, enterpriseId, conversationId, requestId, model, modelName, fetchImpl = compatFetch, timeoutMs = 15000 } = {}) {
|
|
75
|
+
if (!uid || !at) return { ok: false, msg: "缺少 uid / accessToken" };
|
|
76
|
+
const conv = conversationId || `mslxdff-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
77
|
+
const req = requestId || conv;
|
|
78
|
+
const body = JSON.stringify([chatActivityEvent({ uid, conversationId: conv, requestId: req, model, modelName })]);
|
|
79
|
+
let last = { ok: false, msg: "无可用域" };
|
|
80
|
+
for (const base of REPORT_BASES) {
|
|
81
|
+
let res;
|
|
82
|
+
try {
|
|
83
|
+
res = await fetchImpl(`${base}/v2/report`, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: reportHeaders({ at, uid, domain, enterpriseId }),
|
|
86
|
+
body,
|
|
87
|
+
signal: timeoutSignal(timeoutMs),
|
|
88
|
+
});
|
|
89
|
+
} catch (e) {
|
|
90
|
+
last = { ok: false, status: 0, msg: `网络失败: ${String(e?.message || e).slice(0, 160)}` };
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
let j = null;
|
|
94
|
+
try { j = await res.json(); } catch {}
|
|
95
|
+
const out = { ok: res.status === 200 && j?.code === 0, status: res.status, code: j?.code, msg: j?.msg || "", base };
|
|
96
|
+
if (out.ok) return out;
|
|
97
|
+
// 401/403 是登录态问题,换域也没用
|
|
98
|
+
if (res.status === 401 || res.status === 403) return { ...out, needRefresh: true };
|
|
99
|
+
last = out;
|
|
100
|
+
}
|
|
101
|
+
return last;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const TIER_KEYS = ["tier_7d", "tier_14d", "tier_28d"];
|
|
105
|
+
|
|
106
|
+
// 连登完整状态(days / 档位 / 补签卡)。
|
|
107
|
+
export async function growthStreak(api = {}) {
|
|
108
|
+
const r = await growthRequest({ ...api, method: "GET", path: "/activity/growth/streak" });
|
|
109
|
+
if (!r.ok) return { ok: false, msg: r.msg || `HTTP ${r.status}`, days: 0, tiers: [], cards: 0 };
|
|
110
|
+
const data = r.data || {};
|
|
111
|
+
const rs = data.redemption_status || {};
|
|
112
|
+
const tiers = TIER_KEYS
|
|
113
|
+
.map((k) => ({ tier: k.slice(5), status: rs[`${k}_status`] || "", days: Number(rs[`${k}_count`] || 0) }))
|
|
114
|
+
.filter((t) => t.status);
|
|
115
|
+
return {
|
|
116
|
+
ok: true,
|
|
117
|
+
days: Number(data.streak?.days || 0),
|
|
118
|
+
monthTotalDays: Number(data.streak?.month_total_days || 0),
|
|
119
|
+
nextTier: data.streak?.next_tier || "",
|
|
120
|
+
nextTierRemaining: Number(data.streak?.next_tier_remaining || 0),
|
|
121
|
+
tiers,
|
|
122
|
+
cards: Number(data.makeup_cards?.balance || 0),
|
|
123
|
+
rewards: Array.isArray(rs.tiers) ? rs.tiers : [],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 兑换一个已解锁档位(tier: "7d"|"14d"|"28d");未解锁上游 403,调用方按 status 跳过即可。
|
|
128
|
+
export async function redeemStreakTier(api = {}, tier) {
|
|
129
|
+
const r = await growthRequest({
|
|
130
|
+
...api,
|
|
131
|
+
method: "POST",
|
|
132
|
+
path: "/activity/growth/redeem",
|
|
133
|
+
body: { tier, client_token: randomUUID() },
|
|
134
|
+
});
|
|
135
|
+
return { ok: r.ok, status: r.status, code: r.code, msg: r.msg || "", data: r.data || {} };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function lotteryChances(api = {}) {
|
|
139
|
+
const r = await growthRequest({ ...api, method: "GET", path: "/activity/growth/lottery/summary" });
|
|
140
|
+
if (!r.ok) return { ok: false, chances: 0, enabled: false, msg: r.msg || `HTTP ${r.status}` };
|
|
141
|
+
return { ok: true, chances: Number(r.data?.chances || 0), enabled: r.data?.module?.enabled !== false };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function lotteryDraw(api = {}) {
|
|
145
|
+
const r = await growthRequest({
|
|
146
|
+
...api,
|
|
147
|
+
method: "POST",
|
|
148
|
+
path: "/activity/growth/lottery/draw",
|
|
149
|
+
body: { client_token: randomUUID() },
|
|
150
|
+
});
|
|
151
|
+
return { ok: r.ok, status: r.status, msg: r.msg || "", prize: r.data || null };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 连登管家闭环:兑换所有已解锁未领取的档位 → 抽完全部次数。幂等,未解锁静默跳过。
|
|
155
|
+
export async function runStreakBonus({ uid, at, domain, enterpriseId, fetchImpl = compatFetch, onStep = null, maxDraws = 20 } = {}) {
|
|
156
|
+
const api = { uid, at, domain, enterpriseId, fetchImpl };
|
|
157
|
+
const step = (e) => { try { onStep?.(e); } catch {} };
|
|
158
|
+
const out = { uid, redeemed: [], drawn: 0, credit: 0, energy: 0, prizes: [], errors: [] };
|
|
159
|
+
|
|
160
|
+
const st = await growthStreak(api);
|
|
161
|
+
if (!st.ok) {
|
|
162
|
+
out.errors.push(`读连登状态失败:${st.msg}`);
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
out.days = st.days;
|
|
166
|
+
|
|
167
|
+
for (const t of st.tiers) {
|
|
168
|
+
// locked = 天数不够;claimed = 已领过。其余状态视为可兑换。
|
|
169
|
+
if (!["locked", "claimed"].includes(String(t.status))) {
|
|
170
|
+
const r = await redeemStreakTier(api, t.tier);
|
|
171
|
+
if (r.ok) {
|
|
172
|
+
const credit = Number(r.data?.credit || 0);
|
|
173
|
+
const energy = Number(r.data?.energy || 0);
|
|
174
|
+
out.credit += credit;
|
|
175
|
+
out.energy += energy;
|
|
176
|
+
out.redeemed.push({ tier: t.tier, credit, energy });
|
|
177
|
+
step({ phase: "redeem", ok: true, tier: t.tier, credit, energy });
|
|
178
|
+
} else {
|
|
179
|
+
out.errors.push(`兑换 ${t.tier} 失败:${r.msg || `HTTP ${r.status}`}`);
|
|
180
|
+
step({ phase: "redeem", ok: false, tier: t.tier, msg: r.msg });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 每轮重读次数(兑换与抽奖都可能再发次数),抽到 0 或达上限为止。
|
|
186
|
+
for (;;) {
|
|
187
|
+
if (out.drawn >= maxDraws) break;
|
|
188
|
+
const left = (await lotteryChances(api)).chances;
|
|
189
|
+
if (left <= 0) break;
|
|
190
|
+
const r = await lotteryDraw(api);
|
|
191
|
+
if (!r.ok) {
|
|
192
|
+
out.errors.push(`抽奖失败:${r.msg || `HTTP ${r.status}`}`);
|
|
193
|
+
step({ phase: "draw", ok: false, msg: r.msg });
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
out.drawn += 1;
|
|
197
|
+
const name = r.prize?.prize_name || r.prize?.name || r.prize?.title || "";
|
|
198
|
+
const credit = Number(r.prize?.credit || 0);
|
|
199
|
+
out.credit += credit;
|
|
200
|
+
out.prizes.push({ name, credit });
|
|
201
|
+
step({ phase: "draw", ok: true, remaining: left - 1, prize: name, credit });
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { compatFetch, timeoutSignal } from "../../compat.js";
|
|
3
|
+
// WorkBuddy 桌面客户端行为指纹上报(/v2/report)。
|
|
4
|
+
//
|
|
5
|
+
// 为什么需要:桌面端类任务(RichMeow_Chat / Buddy_App / create_canvas / template_5 /
|
|
6
|
+
// playbook_prompt / Library_read)上游按**客户端指纹**判据,CLI 域的 growthEvent
|
|
7
|
+
// 发了也不计数。判据是同一 POST /v2/report 通道上不同的事件链 + 桌面 UA。
|
|
8
|
+
// 来源:workbuddy2api-panel internal/upstream/desktop.go 三账号实测点亮记录。
|
|
9
|
+
//
|
|
10
|
+
// 幂等:行为事件按天去重,重复推进不会重复扣资源;已达标任务由调用方跳过。
|
|
11
|
+
|
|
12
|
+
const DESKTOP_BASE = "https://copilot.tencent.com";
|
|
13
|
+
const DESKTOP_UA = "WorkBuddy/5.5.6 WorkBuddy/5.5.6 CLI/2.137.1";
|
|
14
|
+
const WEB_BASE = "https://www.workbuddy.cn";
|
|
15
|
+
const WEB_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36";
|
|
16
|
+
|
|
17
|
+
// 稳定派生设备标识:同一账号每次同值,模拟固定设备。
|
|
18
|
+
function deriveId(uid, salt) {
|
|
19
|
+
return createHash("sha256").update(`${salt}:${uid}`).digest("hex").slice(0, 36);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function desktopFingerprint(uid, nickname = "") {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
return {
|
|
25
|
+
timezone: "Asia/Shanghai",
|
|
26
|
+
reportDelay: 2000,
|
|
27
|
+
userId: uid,
|
|
28
|
+
username: nickname,
|
|
29
|
+
userNickname: nickname,
|
|
30
|
+
product: "SaaS",
|
|
31
|
+
releaseDate: 1789036585355,
|
|
32
|
+
commit: "5f9692923c93033111c51ad7b003eb80204a9b75",
|
|
33
|
+
ideName: "WorkBuddy",
|
|
34
|
+
ideType: "WorkBuddy",
|
|
35
|
+
ideVersion: "5.5.6",
|
|
36
|
+
machineId: deriveId(uid, "machine"),
|
|
37
|
+
sessionId: deriveId(uid, "session"),
|
|
38
|
+
extName: "workbuddy-desktop",
|
|
39
|
+
extVersion: "5.5.6",
|
|
40
|
+
os: "win32",
|
|
41
|
+
arch: "x64",
|
|
42
|
+
osVersion: "10.0.26220",
|
|
43
|
+
cpuCores: 20,
|
|
44
|
+
memorySize: 24,
|
|
45
|
+
timestamp: now,
|
|
46
|
+
presentAt: now,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 以桌面指纹上报一批事件(业务字段覆盖公共指纹)。
|
|
51
|
+
export async function reportDesktopEvents({ uid, at, nickname, events = [], fetchImpl = compatFetch, timeoutMs = 20000 } = {}) {
|
|
52
|
+
if (!events.length) return { ok: false, msg: "无事件" };
|
|
53
|
+
const fp = desktopFingerprint(uid, nickname);
|
|
54
|
+
const body = JSON.stringify(events.map((ev) => ({ ...fp, ...ev })));
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetchImpl(`${DESKTOP_BASE}/v2/report`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Bearer ${at}`,
|
|
60
|
+
Accept: "application/json, text/plain, */*",
|
|
61
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
62
|
+
"User-Agent": DESKTOP_UA,
|
|
63
|
+
"X-Domain": DESKTOP_BASE,
|
|
64
|
+
"X-Product": "SaaS",
|
|
65
|
+
"X-Request-ID": `${deriveId(uid, "req")}${Date.now() % 1e6}`,
|
|
66
|
+
"X-User-Id": uid,
|
|
67
|
+
},
|
|
68
|
+
body,
|
|
69
|
+
signal: timeoutSignal(timeoutMs),
|
|
70
|
+
});
|
|
71
|
+
let j = null;
|
|
72
|
+
try { j = await res.json(); } catch {}
|
|
73
|
+
return { ok: res.status === 200 && (j?.code === undefined || j?.code === 0), status: res.status, code: j?.code, msg: j?.msg || "" };
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return { ok: false, status: 0, msg: `网络失败: ${String(e?.message || e).slice(0, 160)}` };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// web 域单事件上报(Library_read 这类页面行为任务)。
|
|
80
|
+
export async function reportWebEvent({ uid, at, enterpriseId, eventCode, pageURL, elementId = "", elementName = "", fetchImpl = compatFetch, timeoutMs = 20000 } = {}) {
|
|
81
|
+
const ev = {
|
|
82
|
+
eventCode,
|
|
83
|
+
timestamp: Date.now(),
|
|
84
|
+
reportDelay: 0,
|
|
85
|
+
pageURL,
|
|
86
|
+
elementId,
|
|
87
|
+
elementName,
|
|
88
|
+
os: "Win32",
|
|
89
|
+
arch: "",
|
|
90
|
+
osVersion: "10.0",
|
|
91
|
+
userAgent: WEB_UA,
|
|
92
|
+
machineId: deriveId(uid, "webmachine"),
|
|
93
|
+
userId: uid,
|
|
94
|
+
enterpriseId: enterpriseId || "",
|
|
95
|
+
};
|
|
96
|
+
try {
|
|
97
|
+
const res = await fetchImpl(`${WEB_BASE}/v2/report`, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${at}`,
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
Accept: "application/json",
|
|
103
|
+
"x-client-platform": "web",
|
|
104
|
+
Origin: WEB_BASE,
|
|
105
|
+
Referer: pageURL,
|
|
106
|
+
"User-Agent": WEB_UA,
|
|
107
|
+
"X-User-Id": uid,
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify([ev]),
|
|
110
|
+
signal: timeoutSignal(timeoutMs),
|
|
111
|
+
});
|
|
112
|
+
let j = null;
|
|
113
|
+
try { j = await res.json(); } catch {}
|
|
114
|
+
return { ok: res.status === 200 && (j?.code === undefined || j?.code === 0), status: res.status, msg: j?.msg || "" };
|
|
115
|
+
} catch (e) {
|
|
116
|
+
return { ok: false, status: 0, msg: `网络失败: ${String(e?.message || e).slice(0, 160)}` };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 一次「桌面端成功对话」六事件链(点亮 RichMeow_Chat;也是模板/画布/灵感链的前缀)。
|
|
121
|
+
export function chatSequence({ conversationId, requestId, messageId }) {
|
|
122
|
+
const mk = (code, extra) => ({ eventCode: code, ...extra });
|
|
123
|
+
return [
|
|
124
|
+
mk("agent_task_created", {
|
|
125
|
+
source: "LOCAL", name: "working", task_target: "local", mode: "craft",
|
|
126
|
+
requestModelId: "fast-model", requestModelName: "fast-model",
|
|
127
|
+
has_repo: false, repo_type: "none", workspace_type: "empty",
|
|
128
|
+
has_connector: false, connector_types: [], has_mention: false, mention_types: [],
|
|
129
|
+
has_template: false, action: "", template_name: "",
|
|
130
|
+
has_expert: false, expert_id: "", expert_name: "", expert_industry_id: "",
|
|
131
|
+
has_skill: false, skill_names: [],
|
|
132
|
+
conversationId, messageId, buddyId: "", buddyName: "",
|
|
133
|
+
}),
|
|
134
|
+
mk("chat_message_send", {
|
|
135
|
+
messageId: `${messageId}-assistant`, historyCount: 0, isContextTruncated: false,
|
|
136
|
+
currentStepCount: 1, traceId: requestId, rootRequestId: requestId,
|
|
137
|
+
parentConversationId: conversationId, agentName: "cli", agentType: "main",
|
|
138
|
+
}),
|
|
139
|
+
mk("chat_request_send", {
|
|
140
|
+
inputLength: 24, isPlan: false, isAutoExecuteTerminal: false, isAutoModify: false,
|
|
141
|
+
codebaseEnable: false, maxToken: 0, maxSteps: 500, temperature: 0, maxRetries: 0,
|
|
142
|
+
mentionContexts: [], knowledgeId: [], knowledgeName: [], codebaseId: "",
|
|
143
|
+
mentionContextCount: 0, command: "", recommendId: "", skillId: "", skillCount: 0, totalCount: 0,
|
|
144
|
+
traceId: requestId, rootRequestId: requestId, parentConversationId: conversationId,
|
|
145
|
+
agentName: "cli", agentType: "main",
|
|
146
|
+
"codebuddy.session_id": conversationId, "codebuddy.conversation_request_id": requestId,
|
|
147
|
+
}),
|
|
148
|
+
mk("chat_message_response", {
|
|
149
|
+
messageId: `${messageId}-assistant`, responseModelId: "fast-model",
|
|
150
|
+
inputToken: 120, outputToken: 80, totalToken: 200,
|
|
151
|
+
cachedTokens: 0, cachedWriteTokens: 0, cachedMissTokens: 0,
|
|
152
|
+
isSuccessful: true, messageErrorCode: "", finishReason: "stop",
|
|
153
|
+
firstTokenAt: Date.now(), traceId: requestId, conversationId,
|
|
154
|
+
rootRequestId: requestId, parentConversationId: conversationId, agentName: "cli", agentType: "main",
|
|
155
|
+
"codebuddy.session_id": conversationId, "codebuddy.conversation_request_id": requestId,
|
|
156
|
+
}),
|
|
157
|
+
mk("chat_message_status", {
|
|
158
|
+
messageId: `${messageId}-assistant`, messageErrorCode: "0", traceId: requestId,
|
|
159
|
+
rootRequestId: requestId, parentConversationId: conversationId, agentName: "cli", agentType: "main",
|
|
160
|
+
}),
|
|
161
|
+
mk("chat_request_response", {
|
|
162
|
+
mode: "craft", toolCallCount: 0, inputToken: 120, outputToken: 80, totalToken: 200,
|
|
163
|
+
cachedTokens: 0, cachedWriteTokens: 0, cachedMissTokens: 0,
|
|
164
|
+
isSuccessful: true, messageErrorCode: "", finishReason: "stop",
|
|
165
|
+
rootRequestId: requestId, parentConversationId: conversationId,
|
|
166
|
+
}),
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 企鹅教师助手(Buddy_App_QQ 判据应用;同时满足 Buddy_App「进入任一应用」)。
|
|
171
|
+
const BUDDY_APP = { id: "cb_y5Dy46tPQGGWtueMxXbe", name: "企鹅教师助手" };
|
|
172
|
+
|
|
173
|
+
function buddyAppSequence() {
|
|
174
|
+
const mk = (code, extra = {}) => ({ eventCode: code, mode: "LOCAL", buddyId: BUDDY_APP.id, buddyName: BUDDY_APP.name, ...extra });
|
|
175
|
+
return [
|
|
176
|
+
mk("buddyapp_discover_click"),
|
|
177
|
+
mk("buddyapp_show", { elementId: BUDDY_APP.id, elementName: BUDDY_APP.name, position: 2 }),
|
|
178
|
+
mk("buddyapp_enter_click", { elementId: BUDDY_APP.id, elementName: BUDDY_APP.name, position: 2, isFirstPage: "1" }),
|
|
179
|
+
mk("buddyapp_auth_confirm_click", { elementId: BUDDY_APP.id, elementName: BUDDY_APP.name }),
|
|
180
|
+
mk("buddyapp_bindaccount_skip_click", { elementId: BUDDY_APP.id, elementName: BUDDY_APP.name }),
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const TEMPLATES = [
|
|
185
|
+
"代码审查", "单元测试生成", "重构建议", "技术文档撰写", "接口设计",
|
|
186
|
+
"SQL 优化", "缺陷分诊", "性能分析", "安全审计", "日志排查",
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
// 模板 id 只用于去重计数:上游不校验 id 是否真实存在(实测任意不同 id 都计数),
|
|
190
|
+
// 但**必须每次都不同**——固定 id 打到第 5 次就再也不涨。
|
|
191
|
+
function freshTemplateId(used) {
|
|
192
|
+
for (let i = 0; i < 50; i++) {
|
|
193
|
+
const id = `tpl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
194
|
+
if (!used.includes(id)) return id;
|
|
195
|
+
}
|
|
196
|
+
return `tpl-${Date.now()}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 每个任务构造一批事件。round 用于需要重复的任务(模板 ×5)区分不同对象。
|
|
200
|
+
function buildEvents(code, round, ids, used = []) {
|
|
201
|
+
const { conversationId, requestId, messageId } = ids;
|
|
202
|
+
const chat = () => chatSequence({ conversationId, requestId, messageId });
|
|
203
|
+
switch (code) {
|
|
204
|
+
case "RichMeow_Chat":
|
|
205
|
+
return { events: chat(), via: "desktop" };
|
|
206
|
+
case "Buddy_App":
|
|
207
|
+
case "Buddy_App_QQ":
|
|
208
|
+
return { events: buddyAppSequence(), via: "desktop" };
|
|
209
|
+
case "create_canvas":
|
|
210
|
+
return {
|
|
211
|
+
events: [
|
|
212
|
+
...chat(),
|
|
213
|
+
{ eventCode: "wbx_design_canvas_task_create", conversationId, requestId, source: "summon_keyword", cost: 12000, isSuccessful: true },
|
|
214
|
+
{ eventCode: "wbx_design_canvas_open", conversationId, requestId, id: `ardot-file-${requestId.slice(-8)}`, source: "summon_keyword", type: "page", cost: 13000, isSuccessful: true },
|
|
215
|
+
],
|
|
216
|
+
via: "desktop",
|
|
217
|
+
};
|
|
218
|
+
case "template_5": {
|
|
219
|
+
const id = freshTemplateId(used);
|
|
220
|
+
const name = TEMPLATES[used.length % TEMPLATES.length];
|
|
221
|
+
return {
|
|
222
|
+
events: [
|
|
223
|
+
...chat(),
|
|
224
|
+
{ eventCode: "agent_task_created_with_template", mode: "working", isCustomModel: false, id, name, requestId },
|
|
225
|
+
{ eventCode: "template_used", template_id: id, task_mode: "working" },
|
|
226
|
+
],
|
|
227
|
+
via: "desktop",
|
|
228
|
+
obj: id,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
case "playbook_prompt": {
|
|
232
|
+
// 同理:案例 id 只需每次不同,用真实案例名做展示字段。
|
|
233
|
+
const id = `pb-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
234
|
+
const name = TEMPLATES[used.length % TEMPLATES.length];
|
|
235
|
+
const payload = { id, name, type: "document", categoryId: "", categoryName: "" };
|
|
236
|
+
return {
|
|
237
|
+
events: [
|
|
238
|
+
...chat(),
|
|
239
|
+
{ eventCode: "web_element_click", pageName: "playbook_detail", elementId: "playbook_ctaClick", elementName: name, source: "discover" },
|
|
240
|
+
{ eventCode: "playbook_cta_click", source: "discover", position: 0, ...payload },
|
|
241
|
+
{ eventCode: "playbook_prompt_send", conversationId, requestId, ...payload },
|
|
242
|
+
],
|
|
243
|
+
via: "desktop",
|
|
244
|
+
obj: id,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
case "Library_read":
|
|
248
|
+
return {
|
|
249
|
+
via: "web",
|
|
250
|
+
event: {
|
|
251
|
+
eventCode: "library_doc_intro_click",
|
|
252
|
+
pageURL: "https://www.workbuddy.cn/library",
|
|
253
|
+
elementId: "library_doc",
|
|
254
|
+
elementName: "资料库",
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
default:
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 桌面/网页类任务执行器:按 needTimes 重复上报,任一失败即中断并回传原因。
|
|
263
|
+
export async function runDesktopTask(api = {}, { code, needTimes = 1, round = 0, sleepFn, onStep } = {}) {
|
|
264
|
+
const { uid, at, enterpriseId, fetchImpl = compatFetch } = api;
|
|
265
|
+
const step = (e) => { try { onStep?.(e); } catch {} };
|
|
266
|
+
let done = 0;
|
|
267
|
+
const used = [];
|
|
268
|
+
for (let i = 0; i < needTimes; i++) {
|
|
269
|
+
const stamp = `${Date.now()}-${i}-${Math.random().toString(36).slice(2, 8)}`;
|
|
270
|
+
const built = buildEvents(code, round + i, {
|
|
271
|
+
conversationId: `mslxdff-conv-${stamp}`,
|
|
272
|
+
requestId: `mslxdff-req-${stamp}`,
|
|
273
|
+
messageId: `mslxdff-msg-${stamp}`,
|
|
274
|
+
}, used);
|
|
275
|
+
if (!built) return { ok: false, done, msg: `任务 ${code} 没有对应的事件构造器` };
|
|
276
|
+
const r = built.via === "web"
|
|
277
|
+
? await reportWebEvent({ uid, at, enterpriseId, fetchImpl, ...built.event })
|
|
278
|
+
: await reportDesktopEvents({ uid, at, fetchImpl, events: built.events });
|
|
279
|
+
step({ phase: "fire", task_code: code, i: i + 1, times: needTimes, ok: r.ok, msg: r.msg });
|
|
280
|
+
if (!r.ok) return { ok: false, done, msg: r.msg || `HTTP ${r.status}` };
|
|
281
|
+
done += 1;
|
|
282
|
+
if (built.obj) used.push(built.obj);
|
|
283
|
+
if (i + 1 < needTimes) await sleepFn?.(1200);
|
|
284
|
+
}
|
|
285
|
+
return { ok: true, done };
|
|
286
|
+
}
|