@ynhcj/xiaoyi-channel 0.0.75-beta → 0.0.75-next

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.
Files changed (103) hide show
  1. package/dist/index.d.ts +6 -9
  2. package/dist/index.js +29 -23
  3. package/dist/src/bot.js +27 -3
  4. package/dist/src/channel.js +11 -23
  5. package/dist/src/cspl/call-api.js +14 -11
  6. package/dist/src/cspl/config.js +3 -3
  7. package/dist/src/cspl/constants.d.ts +2 -0
  8. package/dist/src/cspl/constants.js +12 -0
  9. package/dist/src/cspl/utils.js +4 -2
  10. package/dist/src/file-download.js +3 -6
  11. package/dist/src/file-upload.js +52 -5
  12. package/dist/src/login-token-handler.d.ts +8 -0
  13. package/dist/src/login-token-handler.js +60 -0
  14. package/dist/src/message-queue.d.ts +17 -0
  15. package/dist/src/message-queue.js +51 -0
  16. package/dist/src/monitor.js +54 -3
  17. package/dist/src/outbound.js +2 -7
  18. package/dist/src/provider.d.ts +2 -1
  19. package/dist/src/provider.js +486 -33
  20. package/dist/src/reply-dispatcher.js +6 -0
  21. package/dist/src/runtime.d.ts +3 -11
  22. package/dist/src/runtime.js +6 -18
  23. package/dist/src/self-evolution-handler.d.ts +7 -0
  24. package/dist/src/self-evolution-handler.js +140 -0
  25. package/dist/src/self-evolution-keyword.d.ts +9 -0
  26. package/dist/src/self-evolution-keyword.js +147 -0
  27. package/dist/src/self-evolution-tool-result-nudge.d.ts +3 -0
  28. package/dist/src/self-evolution-tool-result-nudge.js +96 -0
  29. package/dist/src/skill-retriever/config.d.ts +4 -0
  30. package/dist/src/skill-retriever/config.js +23 -0
  31. package/dist/src/skill-retriever/hooks.d.ts +22 -0
  32. package/dist/src/skill-retriever/hooks.js +82 -0
  33. package/dist/src/skill-retriever/tool-search.d.ts +16 -0
  34. package/dist/src/skill-retriever/tool-search.js +172 -0
  35. package/dist/src/skill-retriever/types.d.ts +36 -0
  36. package/dist/src/skill-retriever/types.js +1 -0
  37. package/dist/src/task-manager.d.ts +4 -0
  38. package/dist/src/task-manager.js +6 -0
  39. package/dist/src/tools/call-device-tool.d.ts +5 -0
  40. package/dist/src/tools/call-device-tool.js +130 -0
  41. package/dist/src/tools/create-alarm-tool.js +5 -16
  42. package/dist/src/tools/delete-alarm-tool.js +1 -4
  43. package/dist/src/tools/device-tool-map.js +5 -4
  44. package/dist/src/tools/find-pc-devices-tool.d.ts +5 -0
  45. package/dist/src/tools/find-pc-devices-tool.js +98 -0
  46. package/dist/src/tools/get-alarm-tool-schema.d.ts +16 -0
  47. package/dist/src/tools/get-alarm-tool-schema.js +11 -0
  48. package/dist/src/tools/get-calendar-tool-schema.d.ts +16 -0
  49. package/dist/src/tools/get-calendar-tool-schema.js +9 -0
  50. package/dist/src/tools/get-collection-tool-schema.d.ts +16 -0
  51. package/dist/src/tools/get-collection-tool-schema.js +10 -0
  52. package/dist/src/tools/get-contact-tool-schema.d.ts +16 -0
  53. package/dist/src/tools/get-contact-tool-schema.js +11 -0
  54. package/dist/src/tools/get-device-file-tool-schema.d.ts +16 -0
  55. package/dist/src/tools/get-device-file-tool-schema.js +10 -0
  56. package/dist/src/tools/get-email-tool-schema.d.ts +16 -0
  57. package/dist/src/tools/get-email-tool-schema.js +9 -0
  58. package/dist/src/tools/get-note-tool-schema.d.ts +16 -0
  59. package/dist/src/tools/get-note-tool-schema.js +10 -0
  60. package/dist/src/tools/get-photo-tool-schema.d.ts +16 -0
  61. package/dist/src/tools/get-photo-tool-schema.js +10 -0
  62. package/dist/src/tools/image-reading-tool.js +4 -7
  63. package/dist/src/tools/login-token-tool.d.ts +5 -0
  64. package/dist/src/tools/login-token-tool.js +136 -0
  65. package/dist/src/tools/modify-alarm-tool.js +10 -23
  66. package/dist/src/tools/query-app-message-tool.d.ts +4 -0
  67. package/dist/src/tools/query-app-message-tool.js +138 -0
  68. package/dist/src/tools/query-memory-data-tool.d.ts +4 -0
  69. package/dist/src/tools/query-memory-data-tool.js +154 -0
  70. package/dist/src/tools/query-todo-task-tool.d.ts +4 -0
  71. package/dist/src/tools/query-todo-task-tool.js +133 -0
  72. package/dist/src/tools/save-file-to-phone-tool.d.ts +5 -0
  73. package/dist/src/tools/save-file-to-phone-tool.js +166 -0
  74. package/dist/src/tools/save-media-to-gallery-tool.js +3 -7
  75. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +1 -0
  76. package/dist/src/tools/save-self-evolution-skill-tool.js +412 -0
  77. package/dist/src/tools/schema-tool-factory.d.ts +27 -0
  78. package/dist/src/tools/schema-tool-factory.js +32 -0
  79. package/dist/src/tools/search-alarm-tool.js +6 -13
  80. package/dist/src/tools/search-calendar-tool.js +2 -0
  81. package/dist/src/tools/search-email-tool.d.ts +5 -0
  82. package/dist/src/tools/search-email-tool.js +137 -0
  83. package/dist/src/tools/search-file-tool.js +4 -4
  84. package/dist/src/tools/search-message-tool.js +1 -0
  85. package/dist/src/tools/search-photo-gallery-tool.js +2 -2
  86. package/dist/src/tools/send-email-tool.d.ts +4 -0
  87. package/dist/src/tools/send-email-tool.js +134 -0
  88. package/dist/src/tools/send-file-to-user-tool.js +3 -5
  89. package/dist/src/tools/session-manager.js +2 -0
  90. package/dist/src/tools/upload-file-tool.js +4 -4
  91. package/dist/src/tools/upload-photo-tool.js +2 -2
  92. package/dist/src/tools/xiaoyi-add-collection-tool.js +23 -4
  93. package/dist/src/tools/xiaoyi-collection-tool.js +2 -1
  94. package/dist/src/tools/xiaoyi-delete-collection-tool.js +1 -1
  95. package/dist/src/utils/runtime-manager.js +24 -2
  96. package/dist/src/utils/self-evolution-manager.d.ts +10 -0
  97. package/dist/src/utils/self-evolution-manager.js +68 -0
  98. package/dist/src/utils/tool-call-nudge-manager.d.ts +16 -0
  99. package/dist/src/utils/tool-call-nudge-manager.js +47 -0
  100. package/dist/src/websocket.d.ts +3 -0
  101. package/dist/src/websocket.js +69 -0
  102. package/openclaw.plugin.json +21 -0
  103. package/package.json +3 -3
@@ -0,0 +1,412 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { getCurrentSessionContext } from "./session-manager.js";
5
+ import { selfEvolutionManager } from "../utils/self-evolution-manager.js";
6
+ const SELF_EVOLVED_SKILL_ROOT = "/home/sandbox/.agents/skills";
7
+ const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/u;
8
+ function slugifyTitle(title) {
9
+ return title
10
+ .trim()
11
+ .toLowerCase()
12
+ .replace(/[^a-z0-9]+/g, "-")
13
+ .replace(/^-+|-+$/g, "")
14
+ .slice(0, 80);
15
+ }
16
+ function normalizeStringArray(value) {
17
+ if (Array.isArray(value)) {
18
+ return value
19
+ .map((item) => (typeof item === "string" ? item.trim() : ""))
20
+ .filter(Boolean);
21
+ }
22
+ if (typeof value === "string" && value.trim()) {
23
+ return [value.trim()];
24
+ }
25
+ return [];
26
+ }
27
+ function normalizeWhitespace(text) {
28
+ return text.replace(/\s+/gu, " ").trim();
29
+ }
30
+ function normalizeForFingerprint(text) {
31
+ return normalizeWhitespace(text)
32
+ .toLowerCase()
33
+ .replace(/[`"'()[\]{}:;,.!?]/gu, "")
34
+ .replace(/\s+/gu, " ")
35
+ .trim();
36
+ }
37
+ function normalizeForComparison(items) {
38
+ return items
39
+ .map((item) => normalizeForFingerprint(item))
40
+ .filter(Boolean)
41
+ .sort();
42
+ }
43
+ function sanitizeLine(text) {
44
+ let value = text;
45
+ let changed = false;
46
+ const replacements = [
47
+ [/(bearer\s+)[a-z0-9._=-]{12,}/giu, "$1[REDACTED_TOKEN]"],
48
+ [/((?:api[_ -]?key|access[_ -]?token|refresh[_ -]?token|password|secret)\s*[:=]\s*)([^\s,;]+)/giu, "$1[REDACTED_SECRET]"],
49
+ [/(-----BEGIN [A-Z ]*PRIVATE KEY-----)[\s\S]*?(-----END [A-Z ]*PRIVATE KEY-----)/gu, "$1\n[REDACTED_PRIVATE_KEY]\n$2"],
50
+ [/\b(?:[a-zA-Z]:\\(?:[^\\\r\n]+\\)*[^\\\r\n\s]+|\/(?:home|Users|tmp|var|private|etc)\/[^\s"'`<>]+)/gu, "[REDACTED_PATH]"],
51
+ [/\b(sk-[a-zA-Z0-9]{16,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z\-_]{20,})\b/gu, "[REDACTED_SECRET]"],
52
+ ];
53
+ for (const [pattern, replacement] of replacements) {
54
+ const next = value.replace(pattern, replacement);
55
+ if (next !== value) {
56
+ value = next;
57
+ changed = true;
58
+ }
59
+ }
60
+ return { value, changed };
61
+ }
62
+ function sanitizeStringArray(values) {
63
+ let changed = false;
64
+ const sanitized = values.map((value) => {
65
+ const result = sanitizeLine(value);
66
+ changed = changed || result.changed;
67
+ return result.value;
68
+ });
69
+ return { values: sanitized, changed };
70
+ }
71
+ function sanitizeSkillContent(params) {
72
+ const titleResult = sanitizeLine(params.title);
73
+ const summaryResult = sanitizeLine(params.summary);
74
+ const whenToUseResult = sanitizeLine(params.whenToUse);
75
+ const supplementResult = sanitizeLine(params.supplement);
76
+ const rulesResult = sanitizeStringArray(params.rules);
77
+ const examplesResult = sanitizeStringArray(params.examples);
78
+ const tagsResult = sanitizeStringArray(params.tags);
79
+ return {
80
+ title: titleResult.value,
81
+ summary: summaryResult.value,
82
+ whenToUse: whenToUseResult.value,
83
+ supplement: supplementResult.value,
84
+ rules: rulesResult.values,
85
+ examples: examplesResult.values,
86
+ tags: tagsResult.values,
87
+ changed: titleResult.changed ||
88
+ summaryResult.changed ||
89
+ whenToUseResult.changed ||
90
+ supplementResult.changed ||
91
+ rulesResult.changed ||
92
+ examplesResult.changed ||
93
+ tagsResult.changed,
94
+ };
95
+ }
96
+ function containsHighlySensitiveContent(text) {
97
+ const highRiskPatterns = [
98
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----/u,
99
+ /bearer\s+[a-z0-9._=-]{12,}/iu,
100
+ /\b(?:sk-[a-zA-Z0-9]{16,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z\-_]{20,})\b/u,
101
+ /(?:api[_ -]?key|access[_ -]?token|refresh[_ -]?token|password|secret)\s*[:=]\s*[^\s,;]{8,}/iu,
102
+ ];
103
+ return highRiskPatterns.some((pattern) => pattern.test(text));
104
+ }
105
+ function buildSkillFingerprint(params) {
106
+ const normalized = {
107
+ title: normalizeForFingerprint(params.title),
108
+ summary: normalizeForFingerprint(params.summary),
109
+ whenToUse: normalizeForFingerprint(params.whenToUse),
110
+ supplement: normalizeForFingerprint(params.supplement),
111
+ rules: normalizeForComparison(params.rules),
112
+ examples: normalizeForComparison(params.examples),
113
+ tags: normalizeForComparison(params.tags),
114
+ };
115
+ return createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
116
+ }
117
+ function parseFrontmatterValue(content, key) {
118
+ const match = content.match(new RegExp(`^${key}:\\s*"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"`, "m"));
119
+ if (match) {
120
+ return match[1].replace(/\\"/g, '"').replace(/\\n/g, "\n");
121
+ }
122
+ return null;
123
+ }
124
+ function parseTimestampFromExistingSkill(content, key) {
125
+ const value = parseFrontmatterValue(content, key);
126
+ if (!value) {
127
+ return null;
128
+ }
129
+ return ISO_DATE_PATTERN.test(value) ? value : null;
130
+ }
131
+ async function findDuplicateSkillByFingerprint(targetFingerprint) {
132
+ try {
133
+ const entries = await fs.readdir(SELF_EVOLVED_SKILL_ROOT, { withFileTypes: true });
134
+ for (const entry of entries) {
135
+ if (!entry.isDirectory() || !entry.name.startsWith("evolving-")) {
136
+ continue;
137
+ }
138
+ const skillFilePath = path.join(SELF_EVOLVED_SKILL_ROOT, entry.name, "SKILL.md");
139
+ try {
140
+ const existingContent = await fs.readFile(skillFilePath, "utf-8");
141
+ const fingerprint = parseFrontmatterValue(existingContent, "fingerprint");
142
+ if (fingerprint && fingerprint === targetFingerprint) {
143
+ return {
144
+ path: skillFilePath,
145
+ slug: entry.name.replace(/^evolving-/u, ""),
146
+ };
147
+ }
148
+ }
149
+ catch (error) {
150
+ if (error?.code !== "ENOENT") {
151
+ throw error;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ catch (error) {
157
+ if (error?.code !== "ENOENT") {
158
+ throw error;
159
+ }
160
+ }
161
+ return null;
162
+ }
163
+ function buildSkillMarkdown(params) {
164
+ const description = `${params.summary}\n\nWhen to use: ${params.whenToUse}`
165
+ .replace(/"/g, '\\"')
166
+ .replace(/\r?\n/g, "\\n");
167
+ const lines = [
168
+ "---",
169
+ `name: "${params.title.replace(/"/g, '\\"')}"`,
170
+ `description: "${description}"`,
171
+ `fingerprint: "${params.fingerprint}"`,
172
+ `created_at: "${params.createdAt}"`,
173
+ ];
174
+ if (params.updatedAt) {
175
+ lines.push(`updated_at: "${params.updatedAt}"`);
176
+ }
177
+ lines.push("---", "", `# ${params.title}`, "", "## Metadata", `- Created At: ${params.createdAt}`);
178
+ if (params.updatedAt) {
179
+ lines.push(`- Updated At: ${params.updatedAt}`);
180
+ }
181
+ lines.push("", "## Rules");
182
+ for (const rule of params.rules) {
183
+ lines.push(`- ${rule}`);
184
+ }
185
+ if (params.examples.length > 0) {
186
+ lines.push("", "## Examples");
187
+ for (const example of params.examples) {
188
+ lines.push(`- ${example}`);
189
+ }
190
+ }
191
+ if (params.supplement) {
192
+ lines.push("", "## Supplement", params.supplement);
193
+ }
194
+ if (params.tags.length > 0) {
195
+ lines.push("", "## Tags", params.tags.map((tag) => `- ${tag}`).join("\n"));
196
+ }
197
+ lines.push("");
198
+ return lines.join("\n");
199
+ }
200
+ export const saveSelfEvolutionSkillTool = {
201
+ name: "save_self_evolution_skill",
202
+ label: "Save Self Evolution Skill",
203
+ description: "将可复用的经验/脚本/教训等保存为skill技能,供下次执行类似任务时参考。仅用于通用、可复用的场景。仅当自进化开启时可调用本工具。",
204
+ parameters: {
205
+ type: "object",
206
+ properties: {
207
+ title: {
208
+ type: "string",
209
+ description: "所学技能的简短标题。**必须为小写字母/数字/中划线。**",
210
+ },
211
+ summary: {
212
+ type: "string",
213
+ description: "技能的概括性总结,不要太长。",
214
+ },
215
+ when_to_use: {
216
+ type: "string",
217
+ description: "描述在未来任务中什么情况/哪些条件下使用此技能,描述尽量精准。",
218
+ },
219
+ rules: {
220
+ type: "array",
221
+ items: { type: "string" },
222
+ description: "具体、可复用的规则或checklist。",
223
+ },
224
+ examples: {
225
+ type: "array",
226
+ items: { type: "string" },
227
+ description: "陷阱示例或正确模式示例,可选",
228
+ },
229
+ tags: {
230
+ type: "array",
231
+ items: { type: "string" },
232
+ description: "用于未来发现的标签,可选。",
233
+ },
234
+ supplement: {
235
+ type: "string",
236
+ description: "补充说明。将其他想补充但不属于固定字段的内容放在这里。可选。",
237
+ },
238
+ },
239
+ required: ["title", "summary", "when_to_use", "rules"],
240
+ },
241
+ async execute(_toolCallId, params) {
242
+ if (!(await selfEvolutionManager.isEnabled())) {
243
+ throw new Error("Self-evolution is currently disabled by the user.");
244
+ }
245
+ const sessionContext = getCurrentSessionContext();
246
+ if (!sessionContext) {
247
+ throw new Error("No active XY session found. This tool can only run during an active conversation.");
248
+ }
249
+ const title = typeof params.title === "string" ? params.title.trim() : "";
250
+ const summary = typeof params.summary === "string" ? params.summary.trim() : "";
251
+ const whenToUse = typeof params.when_to_use === "string" ? params.when_to_use.trim() : "";
252
+ const supplement = typeof params.supplement === "string" ? params.supplement.trim() : "";
253
+ const rawRules = normalizeStringArray(params.rules);
254
+ const rawExamples = normalizeStringArray(params.examples);
255
+ const rawTags = normalizeStringArray(params.tags);
256
+ if (!title || !summary || !whenToUse || rawRules.length === 0) {
257
+ throw new Error("Missing required fields. title, summary, when_to_use, and at least one rule are required.");
258
+ }
259
+ if (title.length < 6 || summary.length < 10 || whenToUse.length < 10) {
260
+ throw new Error("Skill content is too short. Provide a reusable title, summary, and usage guidance.");
261
+ }
262
+ const sanitized = sanitizeSkillContent({
263
+ title,
264
+ summary,
265
+ whenToUse,
266
+ supplement,
267
+ rules: rawRules,
268
+ examples: rawExamples,
269
+ tags: rawTags,
270
+ });
271
+ const combinedText = [
272
+ sanitized.title,
273
+ sanitized.summary,
274
+ sanitized.whenToUse,
275
+ sanitized.supplement,
276
+ ...sanitized.rules,
277
+ ...sanitized.examples,
278
+ ...sanitized.tags,
279
+ ].join("\n");
280
+ if (containsHighlySensitiveContent(combinedText)) {
281
+ throw new Error("Skill content appears to contain sensitive or environment-specific data and was rejected.");
282
+ }
283
+ const slug = slugifyTitle(sanitized.title);
284
+ if (!slug) {
285
+ throw new Error("Title could not be normalized into a valid skill name.");
286
+ }
287
+ const skillDir = path.join(SELF_EVOLVED_SKILL_ROOT, `evolving-${slug}`);
288
+ const skillFilePath = path.join(skillDir, "SKILL.md");
289
+ const fingerprint = buildSkillFingerprint({
290
+ title: sanitized.title,
291
+ summary: sanitized.summary,
292
+ whenToUse: sanitized.whenToUse,
293
+ supplement: sanitized.supplement,
294
+ rules: sanitized.rules,
295
+ examples: sanitized.examples,
296
+ tags: sanitized.tags,
297
+ });
298
+ const duplicateSkill = await findDuplicateSkillByFingerprint(fingerprint);
299
+ if (duplicateSkill && duplicateSkill.path !== skillFilePath) {
300
+ return {
301
+ content: [
302
+ {
303
+ type: "text",
304
+ text: JSON.stringify({
305
+ success: true,
306
+ deduped: true,
307
+ sanitized: sanitized.changed,
308
+ skillName: duplicateSkill.slug,
309
+ path: duplicateSkill.path,
310
+ message: "A semantically identical self-evolved skill already exists.",
311
+ }),
312
+ },
313
+ ],
314
+ };
315
+ }
316
+ const nowIso = new Date().toISOString();
317
+ let createdAt = nowIso;
318
+ let updatedAt;
319
+ try {
320
+ const existingContent = await fs.readFile(skillFilePath, "utf-8");
321
+ const existingFingerprint = parseFrontmatterValue(existingContent, "fingerprint");
322
+ const existingCreatedAt = parseTimestampFromExistingSkill(existingContent, "created_at");
323
+ createdAt = existingCreatedAt ?? nowIso;
324
+ if (existingFingerprint === fingerprint) {
325
+ return {
326
+ content: [
327
+ {
328
+ type: "text",
329
+ text: JSON.stringify({
330
+ success: true,
331
+ deduped: true,
332
+ sanitized: sanitized.changed,
333
+ skillName: slug,
334
+ path: skillFilePath,
335
+ createdAt,
336
+ message: "An identical self-evolved skill already exists.",
337
+ }),
338
+ },
339
+ ],
340
+ };
341
+ }
342
+ updatedAt = nowIso;
343
+ }
344
+ catch (error) {
345
+ if (error?.code !== "ENOENT") {
346
+ throw error;
347
+ }
348
+ }
349
+ const nextContent = buildSkillMarkdown({
350
+ title: sanitized.title,
351
+ summary: sanitized.summary,
352
+ whenToUse: sanitized.whenToUse,
353
+ supplement: sanitized.supplement,
354
+ rules: sanitized.rules,
355
+ examples: sanitized.examples,
356
+ tags: sanitized.tags,
357
+ fingerprint,
358
+ createdAt,
359
+ updatedAt,
360
+ });
361
+ const nextHash = createHash("sha256").update(nextContent).digest("hex");
362
+ await fs.mkdir(skillDir, { recursive: true });
363
+ try {
364
+ const existingContent = await fs.readFile(skillFilePath, "utf-8");
365
+ const existingHash = createHash("sha256").update(existingContent).digest("hex");
366
+ if (existingHash === nextHash) {
367
+ return {
368
+ content: [
369
+ {
370
+ type: "text",
371
+ text: JSON.stringify({
372
+ success: true,
373
+ deduped: true,
374
+ sanitized: sanitized.changed,
375
+ skillName: slug,
376
+ path: skillFilePath,
377
+ createdAt,
378
+ message: "An identical self-evolved skill already exists.",
379
+ }),
380
+ },
381
+ ],
382
+ };
383
+ }
384
+ }
385
+ catch (error) {
386
+ if (error?.code !== "ENOENT") {
387
+ throw error;
388
+ }
389
+ }
390
+ await fs.writeFile(skillFilePath, nextContent, "utf-8");
391
+ return {
392
+ content: [
393
+ {
394
+ type: "text",
395
+ text: JSON.stringify({
396
+ success: true,
397
+ deduped: false,
398
+ sanitized: sanitized.changed,
399
+ skillName: slug,
400
+ path: skillFilePath,
401
+ sessionId: sessionContext.sessionId,
402
+ createdAt,
403
+ updatedAt,
404
+ message: updatedAt
405
+ ? "Self-evolved skill updated successfully."
406
+ : "Self-evolved skill saved successfully.",
407
+ }),
408
+ },
409
+ ],
410
+ };
411
+ },
412
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * schema-tool-factory: 创建 get_xxx_tool_schema 工具。
3
+ * 每个工具在被 LLM 调用时,返回其所包含的子工具的 name / description / parameters schema。
4
+ */
5
+ interface SchemaToolOptions {
6
+ name: string;
7
+ label: string;
8
+ description: string;
9
+ tools: any[];
10
+ }
11
+ export declare function createSchemaTool(options: SchemaToolOptions): {
12
+ name: string;
13
+ label: string;
14
+ description: string;
15
+ parameters: {
16
+ type: "object";
17
+ properties: {};
18
+ required: string[];
19
+ };
20
+ execute(_toolCallId: string, _params: any): Promise<{
21
+ content: {
22
+ type: "text";
23
+ text: string;
24
+ }[];
25
+ }>;
26
+ };
27
+ export {};
@@ -0,0 +1,32 @@
1
+ /**
2
+ * schema-tool-factory: 创建 get_xxx_tool_schema 工具。
3
+ * 每个工具在被 LLM 调用时,返回其所包含的子工具的 name / description / parameters schema。
4
+ */
5
+ export function createSchemaTool(options) {
6
+ const { name, label, description, tools } = options;
7
+ return {
8
+ name,
9
+ label,
10
+ description,
11
+ parameters: {
12
+ type: "object",
13
+ properties: {},
14
+ required: [],
15
+ },
16
+ async execute(_toolCallId, _params) {
17
+ const schemas = tools.map((t) => ({
18
+ name: t.name,
19
+ description: t.description,
20
+ parameters: t.parameters,
21
+ }));
22
+ return {
23
+ content: [
24
+ {
25
+ type: "text",
26
+ text: JSON.stringify(schemas, null, 2),
27
+ },
28
+ ],
29
+ };
30
+ },
31
+ };
32
+ }
@@ -15,14 +15,7 @@ const DAYS_OF_WAKE_TYPE_VALUES = [0, 1, 2, 3, 4];
15
15
  export const searchAlarmTool = {
16
16
  name: "search_alarm",
17
17
  label: "Search Alarm",
18
- description: `检索用户设备上的闹钟。支持多种检索条件,至少需要提供一个检索条件,多个条件可以组合使用。
19
-
20
- 检索条件(至少提供一个):
21
- - rangeType: 查询范围,枚举值:all=查询所有闹钟,next=查找下一个响铃闹钟,current=一小时内最近一次增查改的闹钟
22
- - alarmState: 闹钟开启状态,0=关闭,1=开启
23
- - daysOfWakeType: 闹钟响铃类型,0=单次响铃,1=法定节假日,2=每天,3=自定义时间,4=法定工作日
24
- - startTime: 时间间隔开始,格式 YYYYMMDD hhmmss(例如:20240315 000000),需要与 endTime 一起使用
25
- - endTime: 时间间隔结束,格式 YYYYMMDD hhmmss(例如:20240315 235959),需要与 startTime 一起使用
18
+ description: `检索用户设备上的闹钟。至少需要提供一个检索条件,多个条件可以组合使用。
26
19
 
27
20
  使用示例:
28
21
  - 查询所有闹钟:{"rangeType": "all"}
@@ -41,25 +34,25 @@ b. 使用该工具之前需获取当前真实时间
41
34
  rangeType: {
42
35
  type: "string",
43
36
  enum: ["all", "next", "current"],
44
- description: "查询范围:all=所有闹钟,next=下一个响铃闹钟,current=一小时内最近修改的闹钟",
37
+ description: "(检索条件之一)查询范围,枚举值:all=查询所有闹钟,next=查找下一个响铃闹钟,current=一小时内最近一次增查改的闹钟",
45
38
  },
46
39
  alarmState: {
47
40
  type: "number",
48
41
  enum: [0, 1],
49
- description: "闹钟开启状态:0=关闭,1=开启",
42
+ description: "(检索条件之一)闹钟开启状态,枚举值:0=关闭,1=开启",
50
43
  },
51
44
  daysOfWakeType: {
52
45
  type: "number",
53
46
  enum: [0, 1, 2, 3, 4],
54
- description: "闹钟响铃类型:0=单次,1=法定节假日,2=每天,3=自定义,4=法定工作日",
47
+ description: "(检索条件之一)闹钟响铃类型,枚举值:0=单次响铃,1=法定节假日,2=每天,3=自定义时间,4=法定工作日",
55
48
  },
56
49
  startTime: {
57
50
  type: "string",
58
- description: "时间间隔开始,格式 YYYYMMDD hhmmss(例如:20240315 000000),必须与 endTime 一起使用",
51
+ description: "(检索条件之一)时间间隔开始,格式 YYYYMMDD hhmmss(例如:20240315 000000),必须与 endTime 一起使用",
59
52
  },
60
53
  endTime: {
61
54
  type: "string",
62
- description: "时间间隔结束,格式 YYYYMMDD hhmmss(例如:20240315 235959),必须与 startTime 一起使用",
55
+ description: "(检索条件之一)时间间隔结束,格式 YYYYMMDD hhmmss(例如:20240315 235959),必须与 startTime 一起使用",
63
56
  },
64
57
  },
65
58
  },
@@ -27,6 +27,8 @@ export const searchCalendarTool = {
27
27
  注意:
28
28
  a. 该工具执行时间较长(最多60秒),请勿重复调用,超时或失败时最多重试一次。
29
29
  b. 使用该工具之前需获取当前真实时间
30
+ c. 该工具仅支持不超过28天时间范围的日程查询,如果时间区间大于该窗口需要拆分多个时间窗口进行多次查询
31
+ d. 如果查询结果返回-303,代表查询结果为空
30
32
 
31
33
  回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。
32
34
  `,
@@ -0,0 +1,5 @@
1
+ /**
2
+ * XY search email tool - searches emails on user's device (花瓣邮箱).
3
+ * Returns matching emails based on query text and search type.
4
+ */
5
+ export declare const searchEmailTool: any;
@@ -0,0 +1,137 @@
1
+ // Search Email tool implementation
2
+ import { getXYWebSocketManager } from "../client.js";
3
+ import { sendCommand } from "../formatter.js";
4
+ import { getCurrentSessionContext } from "./session-manager.js";
5
+ /**
6
+ * XY search email tool - searches emails on user's device (花瓣邮箱).
7
+ * Returns matching emails based on query text and search type.
8
+ */
9
+ export const searchEmailTool = {
10
+ name: "search_email",
11
+ label: "Search Email",
12
+ description: `检索用户花瓣邮箱中的邮件。根据查询语料和搜索类型检索邮件。
13
+
14
+ 使用示例:
15
+ - 通用搜索:{"queryText": "个人所得税邮件", "type": 0}
16
+ - 按主题搜索:{"queryText": "会议纪要", "type": 1}
17
+ - 按发件人搜索:{"queryText": "张三", "type": 2}
18
+ - 按收件人搜索:{"queryText": "李四", "type": 3}
19
+
20
+ 注意:
21
+ a. 操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。
22
+ b. 使用该工具之前需获取当前真实时间
23
+
24
+ 回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。`,
25
+ parameters: {
26
+ type: "object",
27
+ properties: {
28
+ queryText: {
29
+ type: "string",
30
+ description: "查询语料,用于搜索邮件的关键词或语句",
31
+ },
32
+ type: {
33
+ type: "number",
34
+ enum: [0, 1, 2, 3],
35
+ description: "搜索类型:0=全部(all),1=主题(subject),2=发件人(fromList),3=收件人(toList)。默认为0",
36
+ },
37
+ },
38
+ required: ["queryText"],
39
+ },
40
+ async execute(_toolCallId, params) {
41
+ // ===== Validate queryText =====
42
+ if (!params.queryText || typeof params.queryText !== "string" || !params.queryText.trim()) {
43
+ throw new Error("queryText 为必填参数,且不能为空字符串");
44
+ }
45
+ // ===== Validate type =====
46
+ const searchType = params.type ?? 0;
47
+ if (typeof searchType !== "number" || ![0, 1, 2, 3].includes(searchType)) {
48
+ throw new Error("type 必须是 0-3 的整数:0=全部,1=主题,2=发件人,3=收件人");
49
+ }
50
+ // Get session context
51
+ const sessionContext = getCurrentSessionContext();
52
+ if (!sessionContext) {
53
+ throw new Error("No active XY session found. Search email tool can only be used during an active conversation.");
54
+ }
55
+ const { config, sessionId, taskId, messageId } = sessionContext;
56
+ // Get WebSocket manager
57
+ const wsManager = getXYWebSocketManager(config);
58
+ // Build SearchEmails command
59
+ const command = {
60
+ header: {
61
+ namespace: "Common",
62
+ name: "Action",
63
+ },
64
+ payload: {
65
+ cardParam: {},
66
+ executeParam: {
67
+ executeMode: "background",
68
+ intentName: "SearchEmails",
69
+ bundleName: "com.huawei.hmos.email",
70
+ needUnlock: true,
71
+ actionResponse: true,
72
+ appType: "OHOS_APP",
73
+ timeOut: 5,
74
+ intentParam: {
75
+ type: searchType,
76
+ queryText: params.queryText.trim(),
77
+ },
78
+ permissionId: [],
79
+ achieveType: "INTENT",
80
+ },
81
+ responses: [
82
+ {
83
+ resultCode: "",
84
+ displayText: "",
85
+ ttsText: "",
86
+ },
87
+ ],
88
+ needUploadResult: true,
89
+ noHalfPage: false,
90
+ pageControlRelated: false,
91
+ },
92
+ };
93
+ // Send command and wait for response (60 second timeout)
94
+ return new Promise((resolve, reject) => {
95
+ const timeout = setTimeout(() => {
96
+ wsManager.off("data-event", handler);
97
+ reject(new Error("检索邮件超时(60秒)"));
98
+ }, 60000);
99
+ // Listen for data events from WebSocket
100
+ const handler = (event) => {
101
+ if (event.intentName === "SearchEmails") {
102
+ clearTimeout(timeout);
103
+ wsManager.off("data-event", handler);
104
+ if (event.status === "success" && event.outputs) {
105
+ resolve({
106
+ content: [
107
+ {
108
+ type: "text",
109
+ text: JSON.stringify(event.outputs),
110
+ },
111
+ ],
112
+ });
113
+ }
114
+ else {
115
+ reject(new Error(`检索邮件失败: ${event.status}`));
116
+ }
117
+ }
118
+ };
119
+ // Register event handler
120
+ wsManager.on("data-event", handler);
121
+ // Send the command
122
+ sendCommand({
123
+ config,
124
+ sessionId,
125
+ taskId,
126
+ messageId,
127
+ command,
128
+ })
129
+ .then(() => { })
130
+ .catch((error) => {
131
+ clearTimeout(timeout);
132
+ wsManager.off("data-event", handler);
133
+ reject(error);
134
+ });
135
+ });
136
+ },
137
+ };