@hedgehog-finance/hedgehog-plugin 1.0.21 → 1.0.23

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 (111) hide show
  1. package/dist/index.d.ts +4 -3
  2. package/dist/index.js +49 -6
  3. package/dist/index.js.map +1 -1
  4. package/dist/setup-api.d.ts +2 -0
  5. package/dist/setup-api.js +8 -0
  6. package/dist/setup-api.js.map +1 -0
  7. package/dist/src/channel.js +25 -23
  8. package/dist/src/channel.js.map +1 -1
  9. package/dist/src/core/database.js +449 -39
  10. package/dist/src/core/database.js.map +1 -1
  11. package/dist/src/dailyMorningBriefingCron.d.ts +46 -0
  12. package/dist/src/dailyMorningBriefingCron.js +82 -0
  13. package/dist/src/dailyMorningBriefingCron.js.map +1 -0
  14. package/dist/src/features/chartOutput.d.ts +2 -0
  15. package/dist/src/features/chartOutput.js +35 -0
  16. package/dist/src/features/chartOutput.js.map +1 -0
  17. package/dist/src/features/dailyMorningBriefing/schema.d.ts +56 -0
  18. package/dist/src/features/dailyMorningBriefing/schema.js +22 -0
  19. package/dist/src/features/dailyMorningBriefing/schema.js.map +1 -0
  20. package/dist/src/features/dailyMorningBriefing/tools.d.ts +12 -0
  21. package/dist/src/features/dailyMorningBriefing/tools.js +204 -0
  22. package/dist/src/features/dailyMorningBriefing/tools.js.map +1 -0
  23. package/dist/src/features/deepReasoning/schema.d.ts +43 -0
  24. package/dist/src/features/deepReasoning/schema.js +17 -0
  25. package/dist/src/features/deepReasoning/schema.js.map +1 -0
  26. package/dist/src/features/deepReasoning/tools.d.ts +12 -0
  27. package/dist/src/features/deepReasoning/tools.js +163 -0
  28. package/dist/src/features/deepReasoning/tools.js.map +1 -0
  29. package/dist/src/features/index.d.ts +2 -1
  30. package/dist/src/features/index.js +9 -1
  31. package/dist/src/features/index.js.map +1 -1
  32. package/dist/src/features/informationVerification/schema.d.ts +43 -0
  33. package/dist/src/features/informationVerification/schema.js +17 -0
  34. package/dist/src/features/informationVerification/schema.js.map +1 -0
  35. package/dist/src/features/informationVerification/tools.d.ts +12 -0
  36. package/dist/src/features/informationVerification/tools.js +162 -0
  37. package/dist/src/features/informationVerification/tools.js.map +1 -0
  38. package/dist/src/features/notes/schema.d.ts +136 -39
  39. package/dist/src/features/notes/schema.js +13 -10
  40. package/dist/src/features/notes/schema.js.map +1 -1
  41. package/dist/src/features/notes/tools.d.ts +1 -0
  42. package/dist/src/features/notes/tools.js +47 -14
  43. package/dist/src/features/notes/tools.js.map +1 -1
  44. package/dist/src/features/pluginInfo/schema.d.ts +79 -0
  45. package/dist/src/features/pluginInfo/schema.js +14 -0
  46. package/dist/src/features/pluginInfo/schema.js.map +1 -0
  47. package/dist/src/features/pluginInfo/tools.d.ts +1 -0
  48. package/dist/src/features/pluginInfo/tools.js +157 -2
  49. package/dist/src/features/pluginInfo/tools.js.map +1 -1
  50. package/dist/src/features/profileLibrary/schema.d.ts +34 -6
  51. package/dist/src/features/profileLibrary/schema.js +1 -1
  52. package/dist/src/features/profileLibrary/schema.js.map +1 -1
  53. package/dist/src/features/stockAnalysis/schema.d.ts +224 -31
  54. package/dist/src/features/stockAnalysis/schema.js +76 -12
  55. package/dist/src/features/stockAnalysis/schema.js.map +1 -1
  56. package/dist/src/features/stockAnalysis/tools.d.ts +6 -4
  57. package/dist/src/features/stockAnalysis/tools.js +389 -44
  58. package/dist/src/features/stockAnalysis/tools.js.map +1 -1
  59. package/dist/src/features/stockBasic/schema.d.ts +149 -0
  60. package/dist/src/features/stockBasic/schema.js +26 -0
  61. package/dist/src/features/stockBasic/schema.js.map +1 -0
  62. package/dist/src/features/stockBasic/tools.d.ts +12 -0
  63. package/dist/src/features/stockBasic/tools.js +124 -0
  64. package/dist/src/features/stockBasic/tools.js.map +1 -0
  65. package/dist/src/features/watchlist/logic.d.ts +3 -3
  66. package/dist/src/features/watchlist/logic.js +47 -46
  67. package/dist/src/features/watchlist/logic.js.map +1 -1
  68. package/dist/src/features/watchlist/schema.d.ts +89 -54
  69. package/dist/src/features/watchlist/schema.js +7 -4
  70. package/dist/src/features/watchlist/schema.js.map +1 -1
  71. package/dist/src/features/watchlist/tools.d.ts +106 -59
  72. package/dist/src/features/watchlist/tools.js +182 -104
  73. package/dist/src/features/watchlist/tools.js.map +1 -1
  74. package/dist/src/openclawConfig.d.ts +6 -0
  75. package/dist/src/openclawConfig.js +74 -0
  76. package/dist/src/openclawConfig.js.map +1 -0
  77. package/dist/src/openclawConstants.d.ts +4 -0
  78. package/dist/src/openclawConstants.js +10 -0
  79. package/dist/src/openclawConstants.js.map +1 -0
  80. package/dist/src/runtime.js +20 -0
  81. package/dist/src/runtime.js.map +1 -1
  82. package/index.ts +52 -5
  83. package/openclaw.plugin.json +24 -0
  84. package/package.json +20 -5
  85. package/setup-api.ts +10 -0
  86. package/src/channel.ts +26 -25
  87. package/src/core/database.ts +447 -40
  88. package/src/dailyMorningBriefingCron.ts +129 -0
  89. package/src/features/chartOutput.ts +35 -0
  90. package/src/features/dailyMorningBriefing/schema.ts +38 -0
  91. package/src/features/dailyMorningBriefing/tools.ts +246 -0
  92. package/src/features/deepReasoning/schema.ts +22 -0
  93. package/src/features/deepReasoning/tools.ts +182 -0
  94. package/src/features/index.ts +11 -2
  95. package/src/features/informationVerification/schema.ts +22 -0
  96. package/src/features/informationVerification/tools.ts +181 -0
  97. package/src/features/notes/schema.ts +17 -12
  98. package/src/features/notes/tools.ts +54 -17
  99. package/src/features/pluginInfo/schema.ts +19 -0
  100. package/src/features/pluginInfo/tools.ts +173 -2
  101. package/src/features/profileLibrary/schema.ts +1 -1
  102. package/src/features/stockAnalysis/schema.ts +99 -17
  103. package/src/features/stockAnalysis/tools.ts +447 -49
  104. package/src/features/stockBasic/schema.ts +33 -0
  105. package/src/features/stockBasic/tools.ts +157 -0
  106. package/src/features/watchlist/logic.ts +56 -53
  107. package/src/features/watchlist/schema.ts +11 -6
  108. package/src/features/watchlist/tools.ts +191 -106
  109. package/src/openclawConfig.ts +101 -0
  110. package/src/openclawConstants.ts +11 -0
  111. package/src/runtime.ts +19 -0
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+
3
+ export const BuildInformationVerificationMessageParamsSchema = z.object({
4
+ newsId: z.string().trim().min(1).describe("新闻 ID,例如 news-5"),
5
+ sourceTitle: z.string().trim().min(1).describe("新闻标题"),
6
+ sourceContent: z.string().trim().min(1).describe("新闻正文")
7
+ });
8
+ export type BuildInformationVerificationMessageParams = z.infer<typeof BuildInformationVerificationMessageParamsSchema>;
9
+
10
+ export const QueryInformationVerificationHistoryParamsSchema = z.object({
11
+ page: z.number().int().min(1).default(1).describe("页码"),
12
+ pageSize: z.number().int().min(1).max(50).default(10).describe("每页数量,默认 10")
13
+ });
14
+ export type QueryInformationVerificationHistoryParams = z.infer<typeof QueryInformationVerificationHistoryParamsSchema>;
15
+
16
+ export const GetInformationVerificationDetailParamsSchema = z.object({
17
+ id: z.string().trim().min(1).optional().describe("记录 ID"),
18
+ sourceId: z.string().trim().min(1).optional().describe("新闻来源 ID,例如 news-5")
19
+ }).refine((value) => value.id || value.sourceId, {
20
+ message: "id 或 sourceId 至少提供一个"
21
+ });
22
+ export type GetInformationVerificationDetailParams = z.infer<typeof GetInformationVerificationDetailParamsSchema>;
@@ -0,0 +1,181 @@
1
+ import { getDB } from "../../core/database.js";
2
+ import {
3
+ BuildInformationVerificationMessageParams,
4
+ BuildInformationVerificationMessageParamsSchema,
5
+ GetInformationVerificationDetailParamsSchema,
6
+ QueryInformationVerificationHistoryParamsSchema
7
+ } from "./schema.js";
8
+
9
+ interface RuntimeTool {
10
+ name: string;
11
+ label?: string;
12
+ description: string;
13
+ parameters: unknown;
14
+ registerTool?: boolean;
15
+ execute(params: unknown, ctx?: { userId: string }): Promise<string>;
16
+ }
17
+
18
+ const INFORMATION_VERIFICATION_SKILL = "hedgehog-information-verification";
19
+
20
+ const BuildInformationVerificationMessageAgentToolSchema = {
21
+ type: "object",
22
+ additionalProperties: false,
23
+ required: ["newsId", "sourceTitle", "sourceContent"],
24
+ properties: {
25
+ newsId: { type: "string", description: "新闻 ID,例如 news-5" },
26
+ sourceTitle: { type: "string", description: "新闻标题" },
27
+ sourceContent: { type: "string", description: "新闻正文" }
28
+ }
29
+ };
30
+
31
+ function buildContent(args: BuildInformationVerificationMessageParams): string {
32
+ return [
33
+ "对这条新闻进行信息求证",
34
+ "",
35
+ `新闻标题:${args.sourceTitle}`,
36
+ "",
37
+ "新闻正文:",
38
+ args.sourceContent
39
+ ].join("\n");
40
+ }
41
+
42
+ function buildInformationVerificationMessage(args: BuildInformationVerificationMessageParams): string {
43
+ const buildGeneratingSaveParams = () => JSON.stringify({
44
+ sourceId: args.newsId,
45
+ sourceTitle: args.sourceTitle,
46
+ status: "generating",
47
+ content: ""
48
+ });
49
+ const buildFinalSaveParams = (status: "completed" | "failed") => JSON.stringify({
50
+ sourceId: args.newsId,
51
+ status,
52
+ content: "..."
53
+ });
54
+ return JSON.stringify({
55
+ cw_system_prompt: [
56
+ `开始分析前必须先调用 save_information_verification,参数为 ${buildGeneratingSaveParams()};如果返回 skipped=true,必须立即停止,不要继续调用 skill 或生成正文。`,
57
+ `必须触发并使用 ${INFORMATION_VERIFICATION_SKILL} skill 生成信息求证与置信度审计报告。`,
58
+ `生成成功后必须调用 save_information_verification,参数为 ${buildFinalSaveParams("completed")},content 传最终完整求证报告正文。`,
59
+ `生成失败后必须调用 save_information_verification,参数为 ${buildFinalSaveParams("failed")},content 存放完整错误信息。`
60
+ ].join("\n"),
61
+ cw_context: JSON.stringify({
62
+ sourceId: args.newsId,
63
+ sourceTitle: args.sourceTitle
64
+ }),
65
+ cw_content: buildContent(args),
66
+ cw_output: [
67
+ `输出结构以 ${INFORMATION_VERIFICATION_SKILL} skill 的交付模板为准。`
68
+ ].join("\n")
69
+ });
70
+ }
71
+
72
+ function selectGeneratingInformationVerification(userId: string, sourceId: string) {
73
+ const db = getDB();
74
+ return db.prepare(`
75
+ SELECT id, sourceId, sourceTitle, status, content, createdAt, updatedAt
76
+ FROM news_fact_check_analysis
77
+ WHERE userId = ? AND sourceId = ? AND status = 'generating'
78
+ ORDER BY updatedAt DESC, createdAt DESC
79
+ LIMIT 1
80
+ `).get(userId, sourceId);
81
+ }
82
+
83
+ export const informationVerificationTools: Record<string, RuntimeTool> = {
84
+ query_information_verification_history: {
85
+ name: "query_information_verification_history",
86
+ label: "查询信息求证列表",
87
+ description: "分页查询信息求证记录列表。返回记录标识、来源 ID、标题、状态和时间字段;列表结果不包含 content,详情请使用 get_information_verification_detail 查询。",
88
+ parameters: QueryInformationVerificationHistoryParamsSchema,
89
+ registerTool: false,
90
+ async execute(params, ctx) {
91
+ const args = QueryInformationVerificationHistoryParamsSchema.parse(params ?? {});
92
+ const db = getDB();
93
+ const userId = ctx?.userId || "default";
94
+ const offset = (args.page - 1) * args.pageSize;
95
+ const rows = db.prepare(`
96
+ SELECT id, sourceId, sourceTitle, status, createdAt, updatedAt
97
+ FROM news_fact_check_analysis
98
+ WHERE userId = ?
99
+ ORDER BY updatedAt DESC, createdAt DESC
100
+ LIMIT ? OFFSET ?
101
+ `).all(userId, args.pageSize, offset);
102
+ const countRow = db.prepare(`
103
+ SELECT COUNT(*) AS total FROM news_fact_check_analysis WHERE userId = ?
104
+ `).get(userId) as { total: number };
105
+ const total = countRow.total || 0;
106
+ return JSON.stringify({
107
+ success: true,
108
+ data: rows,
109
+ pagination: {
110
+ page: args.page,
111
+ pageSize: args.pageSize,
112
+ total,
113
+ totalPages: Math.ceil(total / args.pageSize)
114
+ }
115
+ });
116
+ }
117
+ },
118
+ get_information_verification_detail: {
119
+ name: "get_information_verification_detail",
120
+ label: "查询信息求证详情",
121
+ description: "根据记录 ID 或 sourceId 查询信息求证完整详情,包含 content 正文及所有元数据。",
122
+ parameters: GetInformationVerificationDetailParamsSchema,
123
+ registerTool: false,
124
+ async execute(params, ctx) {
125
+ const args = GetInformationVerificationDetailParamsSchema.parse(params);
126
+ const db = getDB();
127
+ if (args.sourceId) {
128
+ const row = db.prepare(`
129
+ SELECT id, sourceId, sourceTitle, status, content, createdAt, updatedAt
130
+ FROM news_fact_check_analysis
131
+ WHERE sourceId = ?
132
+ ORDER BY updatedAt DESC, createdAt DESC
133
+ LIMIT 1
134
+ `).get(args.sourceId);
135
+ return JSON.stringify({ success: true, data: row || null });
136
+ }
137
+ const row = db.prepare(`
138
+ SELECT id, sourceId, sourceTitle, status, content, createdAt, updatedAt
139
+ FROM news_fact_check_analysis
140
+ WHERE id = ?
141
+ ORDER BY updatedAt DESC, createdAt DESC
142
+ LIMIT 1
143
+ `).get(args.id);
144
+ return JSON.stringify({ success: true, data: row || null });
145
+ }
146
+ },
147
+ build_information_verification_message: {
148
+ name: "build_information_verification_message",
149
+ label: "构建信息求证消息",
150
+ description: "根据新闻 ID、标题和正文构建用于主动 RPC 发起 Agent 信息求证任务的标准消息。该工具只返回提示词消息体,不触发定时任务,也不保存分析结果。",
151
+ parameters: BuildInformationVerificationMessageAgentToolSchema,
152
+ registerTool: false,
153
+ async execute(params, ctx) {
154
+ const args = BuildInformationVerificationMessageParamsSchema.parse(params);
155
+ const userId = ctx?.userId || "default";
156
+ const generating = selectGeneratingInformationVerification(userId, args.newsId);
157
+ if (generating) {
158
+ return JSON.stringify({
159
+ success: true,
160
+ skipped: true,
161
+ reason: "already_generating",
162
+ data: generating
163
+ });
164
+ }
165
+ const message = buildInformationVerificationMessage(args);
166
+ return JSON.stringify({
167
+ success: true,
168
+ data: {
169
+ message,
170
+ payload: JSON.parse(message),
171
+ sourceId: args.newsId,
172
+ saveParams: {
173
+ sourceId: args.newsId,
174
+ sourceTitle: args.sourceTitle
175
+ },
176
+ skill: INFORMATION_VERIFICATION_SKILL
177
+ }
178
+ });
179
+ }
180
+ }
181
+ };
@@ -1,4 +1,4 @@
1
- import { z } from "openclaw/plugin-sdk/zod";
1
+ import { z } from "zod";
2
2
 
3
3
  const ExchangeEnum = z.enum(["SSE", "SZSE", "NASDAQ", "NYSE", "AMEX", "HKEX"]);
4
4
  const ProfileLibraryInputSchema = z.object({
@@ -8,12 +8,12 @@ const ProfileLibraryInputSchema = z.object({
8
8
 
9
9
  export const AddStockNoteParamsSchema = z.object({
10
10
  watchlistId: z.string().trim().min(1).optional().describe("自选股 ID,优先使用该字段绑定股票"),
11
- stockCode: z.string().trim().min(1).optional().describe("股票代码;未传 watchlistId 时需与 exchange 一起定位股票"),
12
- exchange: ExchangeEnum.optional().describe("交易所;未传 watchlistId 时需与 stockCode 一起定位股票"),
11
+ stock_code: z.string().trim().min(1).optional().describe("股票代码;未传 watchlistId 时需与 exchange 一起定位股票"),
12
+ exchange: ExchangeEnum.optional().describe("交易所;未传 watchlistId 时需与 stock_code 一起定位股票"),
13
13
  note: z.string().trim().min(1).max(200).describe("笔记内容,200 字以内"),
14
14
  profileLibraryIds: z.array(ProfileLibraryInputSchema).optional().describe("关联资料库列表,格式为 { id, title }")
15
- }).refine((value) => Boolean(value.watchlistId || (value.stockCode && value.exchange)), {
16
- message: "watchlistId 或 stockCode + exchange 必须传一个"
15
+ }).refine((value) => Boolean(value.watchlistId || (value.stock_code && value.exchange)), {
16
+ message: "watchlistId 或 stock_code + exchange 必须传一个"
17
17
  });
18
18
  export type AddStockNoteParams = z.infer<typeof AddStockNoteParamsSchema>;
19
19
 
@@ -25,12 +25,12 @@ export type DeleteStockNoteParams = z.infer<typeof DeleteStockNoteParamsSchema>;
25
25
  export const UpdateStockNoteParamsSchema = z.object({
26
26
  id: z.string().trim().min(1).describe("笔记 ID"),
27
27
  watchlistId: z.string().trim().min(1).optional().describe("自选股 ID;传入则重新绑定股票"),
28
- stockCode: z.string().trim().min(1).optional().describe("股票代码;与 exchange 一起传入可重新绑定股票"),
29
- exchange: ExchangeEnum.optional().describe("交易所;与 stockCode 一起传入可重新绑定股票"),
28
+ stock_code: z.string().trim().min(1).optional().describe("股票代码;与 exchange 一起传入可重新绑定股票"),
29
+ exchange: ExchangeEnum.optional().describe("交易所;与 stock_code 一起传入可重新绑定股票"),
30
30
  note: z.string().trim().min(1).max(200).optional().describe("笔记内容,200 字以内"),
31
31
  profileLibraryIds: z.array(ProfileLibraryInputSchema).optional().describe("关联资料库列表,格式为 { id, title };传入则覆盖原有关联")
32
- }).refine((value) => !(value.stockCode && !value.exchange) && !(!value.stockCode && value.exchange), {
33
- message: "stockCode 与 exchange 必须同时传入"
32
+ }).refine((value) => !(value.stock_code && !value.exchange) && !(!value.stock_code && value.exchange), {
33
+ message: "stock_code 与 exchange 必须同时传入"
34
34
  });
35
35
  export type UpdateStockNoteParams = z.infer<typeof UpdateStockNoteParamsSchema>;
36
36
 
@@ -41,7 +41,7 @@ export type GetStockNoteByIdParams = z.infer<typeof GetStockNoteByIdParamsSchema
41
41
 
42
42
  export const QueryStockNotesParamsSchema = z.object({
43
43
  watchlistId: z.string().trim().min(1).optional().describe("自选股 ID"),
44
- stockCode: z.string().trim().min(1).optional().describe("股票代码"),
44
+ stock_code: z.string().trim().min(1).optional().describe("股票代码"),
45
45
  exchange: ExchangeEnum.optional().describe("交易所"),
46
46
  keyword: z.string().trim().optional().describe("模糊查询关键词,可匹配股票代码、股票名称或笔记内容"),
47
47
  page: z.number().int().min(1).optional().describe("页码,从 1 开始,默认 1"),
@@ -52,8 +52,8 @@ export type QueryStockNotesParams = z.infer<typeof QueryStockNotesParamsSchema>;
52
52
  export interface StockNoteRow {
53
53
  id: string;
54
54
  watchlistId: string;
55
- stockCode: string;
56
- stockName: string;
55
+ stock_code: string;
56
+ stock_name: string;
57
57
  exchange: string;
58
58
  market: string;
59
59
  note: string;
@@ -73,3 +73,8 @@ export interface StockNote extends StockNoteRow {
73
73
  title: string;
74
74
  }[];
75
75
  }
76
+
77
+ export const GetStockNoteParamsSchema = z.object({
78
+ stock_code: z.string().trim().min(1).describe("股票代码")
79
+ });
80
+ export type GetStockNoteParams = z.infer<typeof GetStockNoteParamsSchema>;
@@ -13,21 +13,33 @@ import {
13
13
  StockNoteProfileLibraryRow,
14
14
  StockNoteRow,
15
15
  UpdateStockNoteParams,
16
- UpdateStockNoteParamsSchema
16
+ UpdateStockNoteParamsSchema,
17
+ GetStockNoteParamsSchema,
18
+ GetStockNoteParams
17
19
  } from "./schema.js";
18
20
 
19
21
  interface RuntimeTool {
20
22
  name: string;
23
+ label?: string;
21
24
  description: string;
22
25
  parameters: unknown;
23
26
  registerTool?: boolean;
24
27
  execute(params: unknown, ctx: { userId: string }): Promise<string>;
25
28
  }
26
29
 
30
+ const GetStockNoteAgentToolSchema = {
31
+ type: "object",
32
+ additionalProperties: false,
33
+ required: ["stock_code"],
34
+ properties: {
35
+ stock_code: { type: "string", description: "股票代码,例如:000001.SZ 或 600000.SH" }
36
+ }
37
+ };
38
+
27
39
  interface WatchlistStock {
28
40
  id: string;
29
- stockCode: string;
30
- stockName: string;
41
+ stock_code: string;
42
+ stock_name: string;
31
43
  exchange: string;
32
44
  market: string;
33
45
  }
@@ -77,8 +89,8 @@ function getNoteSelectSql(whereSql: string): string {
77
89
  SELECT
78
90
  sn.id,
79
91
  sn.watchlistId,
80
- w.stockCode,
81
- w.stockName,
92
+ w.stock_code,
93
+ w.stock_name,
82
94
  w.exchange,
83
95
  w.market,
84
96
  sn.note,
@@ -93,22 +105,22 @@ function getNoteSelectSql(whereSql: string): string {
93
105
  function resolveWatchlistStock(
94
106
  db: ReturnType<typeof getDB>,
95
107
  userId: string,
96
- args: { watchlistId?: string; stockCode?: string; exchange?: string }
108
+ args: { watchlistId?: string; stock_code?: string; exchange?: string }
97
109
  ): WatchlistStock | null {
98
110
  if (args.watchlistId) {
99
111
  return db.prepare(`
100
- SELECT id, stockCode, stockName, exchange, market
112
+ SELECT id, stock_code, stock_name, exchange, market
101
113
  FROM watchlist
102
114
  WHERE id = ? AND userId = ? AND isDeleted = 0
103
115
  `).get(args.watchlistId.trim(), userId) as WatchlistStock | undefined || null;
104
116
  }
105
117
 
106
- if (args.stockCode && args.exchange) {
118
+ if (args.stock_code && args.exchange) {
107
119
  return db.prepare(`
108
- SELECT id, stockCode, stockName, exchange, market
120
+ SELECT id, stock_code, stock_name, exchange, market
109
121
  FROM watchlist
110
- WHERE userId = ? AND stockCode = ? AND exchange = ? AND isDeleted = 0
111
- `).get(userId, args.stockCode.trim(), args.exchange) as WatchlistStock | undefined || null;
122
+ WHERE userId = ? AND stock_code = ? AND exchange = ? AND isDeleted = 0
123
+ `).get(userId, args.stock_code.trim(), args.exchange) as WatchlistStock | undefined || null;
112
124
  }
113
125
 
114
126
  return null;
@@ -234,10 +246,10 @@ export const noteTools: Record<string, RuntimeTool> = {
234
246
  return JSON.stringify({ success: false, error: "笔记不存在" });
235
247
  }
236
248
 
237
- const stock = (args.watchlistId || args.stockCode || args.exchange)
249
+ const stock = (args.watchlistId || args.stock_code || args.exchange)
238
250
  ? resolveWatchlistStock(db, uId, args)
239
251
  : null;
240
- if ((args.watchlistId || args.stockCode || args.exchange) && !stock) {
252
+ if ((args.watchlistId || args.stock_code || args.exchange) && !stock) {
241
253
  return JSON.stringify({ success: false, error: "股票不存在或未在自选列表中" });
242
254
  }
243
255
  const profileLibraries = args.profileLibraryIds === undefined ? undefined : uniqueProfileLibraries(args.profileLibraryIds);
@@ -316,9 +328,9 @@ export const noteTools: Record<string, RuntimeTool> = {
316
328
  conditions.push("sn.watchlistId = ?");
317
329
  params.push(args.watchlistId);
318
330
  }
319
- if (args.stockCode) {
320
- conditions.push("w.stockCode = ?");
321
- params.push(args.stockCode);
331
+ if (args.stock_code) {
332
+ conditions.push("w.stock_code = ?");
333
+ params.push(args.stock_code);
322
334
  }
323
335
  if (args.exchange) {
324
336
  conditions.push("w.exchange = ?");
@@ -327,7 +339,7 @@ export const noteTools: Record<string, RuntimeTool> = {
327
339
  const keyword = args.keyword?.trim();
328
340
  if (keyword) {
329
341
  const pattern = `%${escapeLikePattern(keyword)}%`;
330
- conditions.push("(w.stockCode LIKE ? ESCAPE '\\' OR w.stockName LIKE ? ESCAPE '\\' OR sn.note LIKE ? ESCAPE '\\')");
342
+ conditions.push("(w.stock_code LIKE ? ESCAPE '\\' OR w.stock_name LIKE ? ESCAPE '\\' OR sn.note LIKE ? ESCAPE '\\')");
331
343
  params.push(pattern, pattern, pattern);
332
344
  }
333
345
 
@@ -348,5 +360,30 @@ export const noteTools: Record<string, RuntimeTool> = {
348
360
  return JSON.stringify({ success: false, error: e.message });
349
361
  }
350
362
  }
363
+ },
364
+
365
+ get_stock_note: {
366
+ name: "get_stock_note",
367
+ label: "拉取股票笔记",
368
+ description: "根据股票代码拉取股票笔记。",
369
+ parameters: GetStockNoteAgentToolSchema,
370
+ registerTool: true,
371
+ execute: async (rawArgs: unknown) => {
372
+ try {
373
+ const args = GetStockNoteParamsSchema.parse(rawArgs);
374
+ const db = getDB();
375
+ const code = args.stock_code.trim().toUpperCase().replace(/\.SS$/i, ".SH");
376
+ const rows = db.prepare(`
377
+ SELECT sn.id, sn.note, sn.createdAt, sn.updatedAt
378
+ FROM stock_notes sn
379
+ JOIN watchlist w ON w.id = sn.watchlistId
380
+ WHERE w.stock_code = ? AND w.isDeleted = 0
381
+ ORDER BY sn.updatedAt DESC, sn.createdAt DESC
382
+ `).all(code) as { id: string; note: string; createdAt: string; updatedAt: string }[];
383
+ return JSON.stringify({ success: true, data: rows });
384
+ } catch (e: any) {
385
+ return JSON.stringify({ success: false, error: e.message });
386
+ }
387
+ }
351
388
  }
352
389
  };
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+
3
+ export const GetPluginVersionParamsSchema = z.object({}).nullish();
4
+
5
+ export const GetSkillVersionsParamsSchema = z.object({}).nullish();
6
+
7
+ export const UpdateSkillVersionsParamsSchema = z.object({
8
+ versions: z.record(z.string()).optional(),
9
+ skills: z.array(z.object({
10
+ name: z.string(),
11
+ version: z.string()
12
+ })).optional()
13
+ }).refine(params => Boolean(params.versions || params.skills), {
14
+ message: "versions or skills is required"
15
+ });
16
+ export type UpdateSkillVersionsParams = z.infer<typeof UpdateSkillVersionsParamsSchema>;
17
+
18
+ export const BuildUpdateSkillVersionsMessageParamsSchema = UpdateSkillVersionsParamsSchema;
19
+ export type BuildUpdateSkillVersionsMessageParams = z.infer<typeof BuildUpdateSkillVersionsMessageParamsSchema>;
@@ -1,17 +1,27 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { z } from "openclaw/plugin-sdk/zod";
4
+ import { getDB } from "../../core/database.js";
5
+ import {
6
+ BuildUpdateSkillVersionsMessageParams,
7
+ BuildUpdateSkillVersionsMessageParamsSchema,
8
+ GetPluginVersionParamsSchema,
9
+ GetSkillVersionsParamsSchema,
10
+ UpdateSkillVersionsParams,
11
+ UpdateSkillVersionsParamsSchema
12
+ } from "./schema.js";
5
13
 
6
14
  interface RuntimeTool {
7
15
  name: string;
8
16
  description: string;
9
17
  parameters: unknown;
10
18
  registerTool?: boolean;
19
+ agentToolTarget?: "main";
11
20
  execute(params: unknown, ctx: { userId: string }): Promise<string>;
12
21
  }
13
22
 
14
- const GetPluginVersionParamsSchema = z.object({}).nullish();
23
+ const UPDATE_SKILL_VERSIONS_TOOL_NAME = "update_hedgehog_skill_versions";
24
+ const HEDGEHOG_INIT_SKILL_NAME = "hedgehog-init";
15
25
 
16
26
  let cachedPluginVersion: string | null = null;
17
27
 
@@ -46,6 +56,119 @@ function getPluginVersion(): string {
46
56
  return cachedPluginVersion;
47
57
  }
48
58
 
59
+ function getSkillVersions() {
60
+ const rows = getDB().prepare(`
61
+ SELECT skillName, version, createdAt, updatedAt
62
+ FROM skill_versions
63
+ ORDER BY skillName ASC
64
+ `).all() as { skillName: string; version: string; createdAt: string; updatedAt: string }[];
65
+ const versions: Record<string, string> = {};
66
+ const skills = rows.map(row => {
67
+ versions[row.skillName] = row.version;
68
+ return {
69
+ name: row.skillName,
70
+ version: row.version,
71
+ createdAt: row.createdAt,
72
+ updatedAt: row.updatedAt
73
+ };
74
+ });
75
+
76
+ return {
77
+ skills,
78
+ versions
79
+ };
80
+ }
81
+
82
+ function normalizeSkillVersionUpdates(params: UpdateSkillVersionsParams) {
83
+ const updates = new Map<string, string>();
84
+
85
+ if (params.versions) {
86
+ for (const [name, version] of Object.entries(params.versions)) {
87
+ const trimmedName = name.trim();
88
+ const trimmedVersion = version.trim();
89
+ if (trimmedName && trimmedVersion) updates.set(trimmedName, trimmedVersion);
90
+ }
91
+ }
92
+
93
+ if (params.skills) {
94
+ for (const skill of params.skills) {
95
+ const trimmedName = skill.name.trim();
96
+ const trimmedVersion = skill.version.trim();
97
+ if (trimmedName && trimmedVersion) updates.set(trimmedName, trimmedVersion);
98
+ }
99
+ }
100
+
101
+ return Array.from(updates.entries()).map(([name, version]) => ({ name, version }));
102
+ }
103
+
104
+ function updateSkillVersions(params: UpdateSkillVersionsParams) {
105
+ const updates = normalizeSkillVersionUpdates(params);
106
+ const db = getDB();
107
+ const stmt = db.prepare(`
108
+ INSERT INTO skill_versions (skillName, version, updatedAt)
109
+ VALUES (?, ?, STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))
110
+ ON CONFLICT(skillName) DO UPDATE SET
111
+ version = excluded.version,
112
+ updatedAt = excluded.updatedAt
113
+ `);
114
+
115
+ db.exec("BEGIN");
116
+ try {
117
+ for (const update of updates) {
118
+ stmt.run(update.name, update.version);
119
+ }
120
+ db.exec("COMMIT");
121
+ } catch (e) {
122
+ if (db.inTransaction) db.exec("ROLLBACK");
123
+ throw e;
124
+ }
125
+
126
+ return {
127
+ updated: updates,
128
+ ...getSkillVersions()
129
+ };
130
+ }
131
+
132
+ function buildUpdateSkillVersionsMessage(params: BuildUpdateSkillVersionsMessageParams): string {
133
+ const updates = normalizeSkillVersionUpdates(params);
134
+ const toolParams = {
135
+ skills: updates
136
+ };
137
+ const includesHedgehogInit = updates.some(update => update.name === HEDGEHOG_INIT_SKILL_NAME);
138
+
139
+ if (includesHedgehogInit) {
140
+ return JSON.stringify({
141
+ cw_system_prompt: [
142
+ "必须触发并使用 hedgehog-init skill 更新 hedgehog-workspace 的系统提示词到最新版本。",
143
+ `只有在 hedgehog-init skill 的系统提示词更新流程成功后,才可以调用 ${UPDATE_SKILL_VERSIONS_TOOL_NAME} 工具。`,
144
+ `成功后必须调用 ${UPDATE_SKILL_VERSIONS_TOOL_NAME} 工具更新本地数据库中的 skill 版本号,参数为 ${JSON.stringify(toolParams)}。`,
145
+ "如果系统提示词更新失败,禁止调用 update_hedgehog_skill_versions,必须直接说明失败原因。"
146
+ ].join("\n"),
147
+ cw_context: JSON.stringify({
148
+ action: "update_hedgehog_init_system_prompt",
149
+ skills: updates
150
+ }),
151
+ cw_content: "Skill升级了,帮我更新`hedgehog-workspace`系统提示词到最新版本。",
152
+ cw_output: "系统提示词更新成功后,调用 update_hedgehog_skill_versions 工具写入版本,并返回简短结果。"
153
+ });
154
+ }
155
+
156
+ return JSON.stringify({
157
+ cw_system_prompt: [
158
+ "必须先更新 hedgehog-workspace 中的 hedgehog skills。",
159
+ `只有在 hedgehog skills 更新成功后,才可以调用 ${UPDATE_SKILL_VERSIONS_TOOL_NAME} 工具。`,
160
+ `更新成功后必须调用 ${UPDATE_SKILL_VERSIONS_TOOL_NAME} 工具更新本地数据库中的 skill 版本号,参数为 ${JSON.stringify(toolParams)}。`,
161
+ "如果 hedgehog skills 更新失败,禁止调用 update_hedgehog_skill_versions,必须直接说明失败原因。"
162
+ ].join("\n"),
163
+ cw_context: JSON.stringify({
164
+ action: "update_hedgehog_skills",
165
+ skills: updates
166
+ }),
167
+ cw_content: "帮我更新`hedgehog-workspace`的hedgehog skills",
168
+ cw_output: "hedgehog skills 更新成功后,调用 update_hedgehog_skill_versions 工具写入版本,并返回简短结果。"
169
+ });
170
+ }
171
+
49
172
  export const pluginInfoTools: Record<string, RuntimeTool> = {
50
173
  get_plugin_version: {
51
174
  name: "get_plugin_version",
@@ -59,5 +182,53 @@ export const pluginInfoTools: Record<string, RuntimeTool> = {
59
182
  version: getPluginVersion()
60
183
  });
61
184
  }
185
+ },
186
+ get_skill_versions: {
187
+ name: "get_skill_versions",
188
+ description: "获取数据库中记录的所有 skill 版本号",
189
+ parameters: GetSkillVersionsParamsSchema,
190
+ registerTool: false,
191
+ async execute(params: unknown) {
192
+ GetSkillVersionsParamsSchema.parse(params);
193
+ return JSON.stringify({
194
+ success: true,
195
+ ...getSkillVersions()
196
+ });
197
+ }
198
+ },
199
+ update_hedgehog_skill_versions: {
200
+ name: "update_hedgehog_skill_versions",
201
+ description: "更新数据库中记录的 skill 版本号",
202
+ parameters: UpdateSkillVersionsParamsSchema,
203
+ registerTool: false,
204
+ agentToolTarget: "main",
205
+ async execute(params: unknown) {
206
+ const parsed = UpdateSkillVersionsParamsSchema.parse(params);
207
+ return JSON.stringify({
208
+ success: true,
209
+ ...updateSkillVersions(parsed)
210
+ });
211
+ }
212
+ },
213
+ build_update_skill_versions_message: {
214
+ name: "build_update_skill_versions_message",
215
+ description: "构建用于主动 RPC 发起 Agent 更新数据库 skill 版本号任务的标准消息。该方法只返回提示词消息体,不直接更新数据库。",
216
+ parameters: BuildUpdateSkillVersionsMessageParamsSchema,
217
+ registerTool: false,
218
+ async execute(params: unknown) {
219
+ const parsed = BuildUpdateSkillVersionsMessageParamsSchema.parse(params);
220
+ const message = buildUpdateSkillVersionsMessage(parsed);
221
+ return JSON.stringify({
222
+ success: true,
223
+ data: {
224
+ message,
225
+ payload: JSON.parse(message),
226
+ tool: UPDATE_SKILL_VERSIONS_TOOL_NAME,
227
+ updateParams: {
228
+ skills: normalizeSkillVersionUpdates(parsed)
229
+ }
230
+ }
231
+ });
232
+ }
62
233
  }
63
234
  };
@@ -1,4 +1,4 @@
1
- import { z } from "openclaw/plugin-sdk/zod";
1
+ import { z } from "zod";
2
2
 
3
3
  export const AddProfileLibraryParamsSchema = z.object({
4
4
  id: z.string().trim().min(1).optional().describe("资料库 ID,不传则自动生成"),