@hedgehog-finance/hedgehog-plugin 1.0.21 → 1.0.22

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 +21 -6
  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,157 @@
1
+ import { getDB } from "../../core/database.js";
2
+ import {
3
+ GetStockBasicListParamsSchema,
4
+ GetStockBasicInfoParamsSchema,
5
+ StockBasicItem,
6
+ SyncStockBasicParamsSchema
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 GetStockBasicInfoAgentToolSchema = {
19
+ type: "object",
20
+ additionalProperties: false,
21
+ required: ["stock_code"],
22
+ properties: {
23
+ stock_code: { type: "string", description: "股票代码,例如:000001.SZ 或 600000.SH" }
24
+ }
25
+ };
26
+
27
+ function normalizeStockBasic(stock: StockBasicItem): StockBasicItem {
28
+ return {
29
+ ...stock,
30
+ stock_code: stock.stock_code.trim().toUpperCase().replace(/\.SS$/i, ".SH"),
31
+ symbol: stock.symbol.trim(),
32
+ exchange: stock.exchange.trim().toUpperCase(),
33
+ name: stock.name.trim(),
34
+ fullname: stock.fullname.trim(),
35
+ enname: stock.enname.trim(),
36
+ cnspell: stock.cnspell.trim().toUpperCase()
37
+ };
38
+ }
39
+
40
+ function escapeLike(value: string): string {
41
+ return value.replace(/[\\%_]/g, "\\$&");
42
+ }
43
+
44
+ export const stockBasicTools: Record<string, RuntimeTool> = {
45
+ sync_stock_basic: {
46
+ name: "sync_stock_basic",
47
+ description: "批量同步证券基础资料主数据。系统以 stock_code 作为唯一标识执行新增或覆盖更新,适用于初始化或定期刷新股票代码、名称、交易所、行业、上市日期等基础字段。",
48
+ parameters: SyncStockBasicParamsSchema,
49
+ registerTool: false,
50
+ async execute(params) {
51
+ const args = SyncStockBasicParamsSchema.parse(params);
52
+ const db = getDB();
53
+ const stmt = db.prepare(`
54
+ INSERT INTO stock_basic (
55
+ stock_code,
56
+ symbol,
57
+ name,
58
+ fullname,
59
+ enname,
60
+ cnspell,
61
+ exchange,
62
+ market,
63
+ industry,
64
+ area,
65
+ curr_type,
66
+ list_date,
67
+ is_hs,
68
+ act_name,
69
+ act_ent_type
70
+ )
71
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
72
+ ON CONFLICT(stock_code) DO UPDATE SET
73
+ symbol = excluded.symbol,
74
+ name = excluded.name,
75
+ fullname = excluded.fullname,
76
+ enname = excluded.enname,
77
+ cnspell = excluded.cnspell,
78
+ exchange = excluded.exchange,
79
+ market = excluded.market,
80
+ industry = excluded.industry,
81
+ area = excluded.area,
82
+ curr_type = excluded.curr_type,
83
+ list_date = excluded.list_date,
84
+ is_hs = excluded.is_hs,
85
+ act_name = excluded.act_name,
86
+ act_ent_type = excluded.act_ent_type,
87
+ updatedAt = STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')
88
+ `);
89
+
90
+ if (db.inTransaction) db.exec("ROLLBACK");
91
+ db.exec("BEGIN TRANSACTION");
92
+ try {
93
+ for (const rawStock of args.stocks) {
94
+ const stock = normalizeStockBasic(rawStock);
95
+ stmt.run(
96
+ stock.stock_code,
97
+ stock.symbol,
98
+ stock.name,
99
+ stock.fullname,
100
+ stock.enname,
101
+ stock.cnspell,
102
+ stock.exchange,
103
+ stock.market,
104
+ stock.industry,
105
+ stock.area,
106
+ stock.curr_type,
107
+ stock.list_date,
108
+ stock.is_hs,
109
+ stock.act_name,
110
+ stock.act_ent_type
111
+ );
112
+ }
113
+ db.exec("COMMIT");
114
+ return JSON.stringify({ success: true, synced: args.stocks.length });
115
+ } catch (e: any) {
116
+ if (db.inTransaction) db.exec("ROLLBACK");
117
+ return JSON.stringify({ success: false, error: e.message });
118
+ }
119
+ }
120
+ },
121
+
122
+ get_stock_basic_list: {
123
+ name: "get_stock_basic_list",
124
+ description: "查询证券基础资料全量列表。返回股票代码、证券简称、公司全称、交易所、市场板块、行业及上市日期等基础字段,用于前端缓存、下拉选择或基础数据校验。",
125
+ parameters: GetStockBasicListParamsSchema,
126
+ registerTool: false,
127
+ async execute(params) {
128
+ GetStockBasicListParamsSchema.parse(params);
129
+ const db = getDB();
130
+ const rows = db.prepare(`
131
+ SELECT stock_code, symbol, name, fullname, enname, cnspell, exchange, market, industry, area, curr_type, list_date, is_hs, act_name, act_ent_type, createdAt, updatedAt
132
+ FROM stock_basic
133
+ ORDER BY exchange ASC, symbol ASC
134
+ `).all();
135
+ return JSON.stringify({ success: true, data: rows });
136
+ }
137
+ },
138
+
139
+ get_stock_basic_info: {
140
+ name: "get_stock_basic_info",
141
+ label: "查询股票基本信息",
142
+ description: "根据股票代码查询证券基础资料。",
143
+ parameters: GetStockBasicInfoAgentToolSchema,
144
+ registerTool: true,
145
+ async execute(params) {
146
+ const args = GetStockBasicInfoParamsSchema.parse(params);
147
+ const db = getDB();
148
+ const code = args.stock_code.trim().toUpperCase().replace(/\.SS$/i, ".SH");
149
+ const row = db.prepare(`
150
+ SELECT stock_code, name, enname, exchange, market, industry, is_hs
151
+ FROM stock_basic
152
+ WHERE stock_code = ?
153
+ `).get(code);
154
+ return JSON.stringify({ success: true, data: row || null });
155
+ }
156
+ }
157
+ };
@@ -10,10 +10,10 @@ import { logger } from "../../core/logger.js";
10
10
  import { StockClassification, StockClassificationSchema } from "../../types.js";
11
11
 
12
12
  interface GlobalStockMetadataRow {
13
- industryJson: string;
14
- themeJson: string;
15
- stockName: string;
16
- lastUpdated: string;
13
+ industry_classification: string;
14
+ theme_classification: string;
15
+ stock_name: string;
16
+ last_updated: string;
17
17
  }
18
18
 
19
19
  const MAIN_AGENT_ID = "hedgehog-finance";
@@ -32,8 +32,8 @@ const CLASSIFIER_SYSTEM_PROMPT = [
32
32
  "只允许根据用户消息中提供的行业/主题分类字典和股票列表输出纯 JSON 数组。"
33
33
  ].join("\n");
34
34
 
35
- function normalizeStockCodeForCache(stockCode: string, exchange?: string): string {
36
- const code = String(stockCode || "")
35
+ function normalizeStockCodeForCache(stock_code: string, exchange?: string): string {
36
+ const code = String(stock_code || "")
37
37
  .trim()
38
38
  .toUpperCase();
39
39
  if (/\.(SH|SS|SZ|HK|US)$/i.test(code)) {
@@ -51,19 +51,19 @@ function normalizeStockCodeForCache(stockCode: string, exchange?: string): strin
51
51
  }
52
52
  }
53
53
 
54
- function legacyStockCodeWithoutSuffix(stockCode: string): string {
55
- return String(stockCode || "")
54
+ function legacyStockCodeWithoutSuffix(stock_code: string): string {
55
+ return String(stock_code || "")
56
56
  .trim()
57
57
  .toUpperCase()
58
58
  .replace(/\.(SH|SS|SZ|HK|US)$/i, "");
59
59
  }
60
60
 
61
- function getCachedClassificationRow(db: any, stockCode: string, exchange: string): GlobalStockMetadataRow | undefined {
62
- const cacheCode = normalizeStockCodeForCache(stockCode, exchange);
63
- const legacyCode = legacyStockCodeWithoutSuffix(stockCode);
61
+ function getCachedClassificationRow(db: any, stock_code: string, exchange: string): GlobalStockMetadataRow | undefined {
62
+ const cacheCode = normalizeStockCodeForCache(stock_code, exchange);
63
+ const legacyCode = legacyStockCodeWithoutSuffix(stock_code);
64
64
  const stmt = db.prepare(`
65
- SELECT industryJson, themeJson FROM global_stock_metadata
66
- WHERE stockCode = ? AND exchange = ?
65
+ SELECT industry_classification, theme_classification FROM stock_classification_cache
66
+ WHERE stock_code = ? AND exchange = ?
67
67
  `);
68
68
  return (stmt.get(cacheCode, exchange) || (legacyCode !== cacheCode ? stmt.get(legacyCode, exchange) : undefined)) as GlobalStockMetadataRow | undefined;
69
69
  }
@@ -180,27 +180,27 @@ export const watchlistLogic = {
180
180
 
181
181
  async getStockClassification(
182
182
  rt: PluginRuntime,
183
- stockName: string,
184
- stockCode: string,
183
+ stock_name: string,
184
+ stock_code: string,
185
185
  exchange: string,
186
186
  _userId: string
187
187
  ): Promise<StockClassification | null> {
188
188
  const db = getDB();
189
189
 
190
- const cached = getCachedClassificationRow(db, stockCode, exchange);
190
+ const cached = getCachedClassificationRow(db, stock_code, exchange);
191
191
 
192
- if (cached && cached.industryJson) {
192
+ if (cached && cached.industry_classification) {
193
193
  try {
194
194
  return watchlistLogic._normalizeCachedClassification({
195
- industry: JSON.parse(cached.industryJson),
196
- theme: JSON.parse(cached.themeJson || '[]'),
195
+ industry: JSON.parse(cached.industry_classification),
196
+ theme: JSON.parse(cached.theme_classification || '[]'),
197
197
  weight: 50
198
198
  });
199
199
  } catch (e) {
200
200
  }
201
201
  }
202
202
 
203
- const classification = await watchlistLogic._autoClassifyWithAI(rt, stockName, stockCode, exchange);
203
+ const classification = await watchlistLogic._autoClassifyWithAI(rt, stock_name, stock_code, exchange);
204
204
 
205
205
  return classification;
206
206
  },
@@ -212,16 +212,16 @@ export const watchlistLogic = {
212
212
  ): Promise<StockClassification[]> {
213
213
  const db = getDB();
214
214
  const results = new Array<StockClassification | null>(stocks.length).fill(null);
215
- const pendingStocks: { idx: number, stockName: string, stockCode: string, exchange: string }[] = [];
215
+ const pendingStocks: { idx: number, stock_name: string, stock_code: string, exchange: string }[] = [];
216
216
 
217
217
  stocks.forEach((stock, idx) => {
218
- const cached = getCachedClassificationRow(db, stock.stockCode, stock.exchange);
218
+ const cached = getCachedClassificationRow(db, stock.stock_code, stock.exchange);
219
219
 
220
- if (cached && cached.industryJson) {
220
+ if (cached && cached.industry_classification) {
221
221
  try {
222
222
  results[idx] = watchlistLogic._normalizeCachedClassification({
223
- industry: JSON.parse(cached.industryJson),
224
- theme: JSON.parse(cached.themeJson || '[]'),
223
+ industry: JSON.parse(cached.industry_classification),
224
+ theme: JSON.parse(cached.theme_classification || '[]'),
225
225
  weight: 50
226
226
  });
227
227
  return;
@@ -231,8 +231,8 @@ export const watchlistLogic = {
231
231
 
232
232
  pendingStocks.push({
233
233
  idx,
234
- stockName: stock.stockName,
235
- stockCode: stock.stockCode,
234
+ stock_name: stock.stock_name,
235
+ stock_code: stock.stock_code,
236
236
  exchange: stock.exchange
237
237
  });
238
238
  });
@@ -246,7 +246,7 @@ export const watchlistLogic = {
246
246
  throw new Error("行业分类字典为空,无法分析行业/主题关系");
247
247
  }
248
248
 
249
- const stocksList = pendingStocks.map(s => `- ${s.stockName} (${s.stockCode})`).join("\n");
249
+ const stocksList = pendingStocks.map(s => `- ${s.stock_name} (${s.stock_code})`).join("\n");
250
250
  const prompt = watchlistLogic._buildAiPrompt(cats.industries, cats.themes, stocksList, true);
251
251
  const sessionId = `classify-batch-${randomUUID()}`;
252
252
  const aiText = await watchlistLogic._callClassifierAi(rt, sessionId, prompt, resolveBatchClassificationTimeoutMs(pendingStocks.length));
@@ -256,7 +256,7 @@ export const watchlistLogic = {
256
256
  } catch (e) {
257
257
  logger.warn({
258
258
  err: e instanceof Error ? e.message : String(e),
259
- pendingCodes: pendingStocks.map(stock => stock.stockCode),
259
+ pendingCodes: pendingStocks.map(stock => stock.stock_code),
260
260
  aiTextLength: aiText.length,
261
261
  aiText
262
262
  }, "[Watchlist] batch classification AI parse failed");
@@ -271,16 +271,16 @@ export const watchlistLogic = {
271
271
  let parsedResults: { stock: typeof pendingStocks[number]; data: StockClassification }[];
272
272
  try {
273
273
  parsedResults = pendingStocks.map((stock, i) => {
274
- const raw = parsedByCode.get(String(stock.stockCode)) || parsed[i];
274
+ const raw = parsedByCode.get(String(stock.stock_code)) || parsed[i];
275
275
  return {
276
276
  stock,
277
- data: watchlistLogic._parseClassification(raw, cats, stock.stockName || stock.stockCode)
277
+ data: watchlistLogic._parseClassification(raw, cats, stock.stock_name || stock.stock_code)
278
278
  };
279
279
  });
280
280
  } catch (e) {
281
281
  logger.warn({
282
282
  err: e instanceof Error ? e.message : String(e),
283
- pendingCodes: pendingStocks.map(stock => stock.stockCode),
283
+ pendingCodes: pendingStocks.map(stock => stock.stock_code),
284
284
  aiTextLength: aiText.length,
285
285
  aiText
286
286
  }, "[Watchlist] batch classification AI semantic parse failed");
@@ -293,7 +293,7 @@ export const watchlistLogic = {
293
293
 
294
294
  const missing = stocks.find((_, i) => !results[i]);
295
295
  if (missing) {
296
- throw new Error(`行业/主题关系分析失败: ${missing.stockName || missing.stockCode}`);
296
+ throw new Error(`行业/主题关系分析失败: ${missing.stock_name || missing.stock_code}`);
297
297
  }
298
298
  return results as StockClassification[];
299
299
  },
@@ -310,19 +310,19 @@ export const watchlistLogic = {
310
310
  stocks.forEach((s, i) => {
311
311
  const cached = options.forceRefresh
312
312
  ? undefined
313
- : getCachedClassificationRow(db, s.stockCode, s.exchange);
314
- if (cached && cached.industryJson) {
313
+ : getCachedClassificationRow(db, s.stock_code, s.exchange);
314
+ if (cached && cached.industry_classification) {
315
315
  try {
316
316
  results[i] = watchlistLogic._normalizeCachedClassification({
317
- industry: JSON.parse(cached.industryJson),
318
- theme: JSON.parse(cached.themeJson || '[]'),
317
+ industry: JSON.parse(cached.industry_classification),
318
+ theme: JSON.parse(cached.theme_classification || '[]'),
319
319
  weight: 50
320
320
  });
321
321
  } catch (e) {
322
- pendingStocks.push({ idx: i, name: s.stockName, code: s.stockCode, exchange: s.exchange });
322
+ pendingStocks.push({ idx: i, name: s.stock_name, code: s.stock_code, exchange: s.exchange });
323
323
  }
324
324
  } else {
325
- pendingStocks.push({ idx: i, name: s.stockName, code: s.stockCode, exchange: s.exchange });
325
+ pendingStocks.push({ idx: i, name: s.stock_name, code: s.stock_code, exchange: s.exchange });
326
326
  }
327
327
  });
328
328
  if (pendingStocks.length > 0) {
@@ -362,15 +362,15 @@ export const watchlistLogic = {
362
362
  if (options.requireComplete) {
363
363
  const missing = stocks.find((_, i) => !results[i]);
364
364
  if (missing) {
365
- throw new Error(`行业/主题关系分析失败: ${missing.stockName || missing.stockCode}`);
365
+ throw new Error(`行业/主题关系分析失败: ${missing.stock_name || missing.stock_code}`);
366
366
  }
367
367
  }
368
368
  return results;
369
369
  },
370
370
 
371
371
  _getKnownCategories(db: any) {
372
- const industries = db.prepare("SELECT name FROM watchlist_categories WHERE type = 'industry'").all() as any[];
373
- const themes = db.prepare("SELECT name FROM watchlist_categories WHERE type = 'theme'").all() as any[];
372
+ const industries = db.prepare("SELECT name FROM industry_theme_categories WHERE type = 'industry'").all() as any[];
373
+ const themes = db.prepare("SELECT name FROM industry_theme_categories WHERE type = 'theme'").all() as any[];
374
374
  return {
375
375
  industries: industries.map(i => i.name),
376
376
  themes: themes.map(t => t.name)
@@ -379,8 +379,8 @@ export const watchlistLogic = {
379
379
 
380
380
  async _autoClassifyWithAI(
381
381
  rt: PluginRuntime,
382
- stockName: string,
383
- stockCode: string,
382
+ stock_name: string,
383
+ stock_code: string,
384
384
  exchange: string
385
385
  ): Promise<StockClassification | null> {
386
386
  const db = getDB();
@@ -390,17 +390,17 @@ export const watchlistLogic = {
390
390
  return null;
391
391
  }
392
392
 
393
- const prompt = watchlistLogic._buildAiPrompt(cats.industries, cats.themes, `${stockName} (${stockCode})`, false);
394
- const aiText = await watchlistLogic._callClassifierAi(rt, `classify-${stockCode}`, prompt, SINGLE_CLASSIFICATION_TIMEOUT_MS);
393
+ const prompt = watchlistLogic._buildAiPrompt(cats.industries, cats.themes, `${stock_name} (${stock_code})`, false);
394
+ const aiText = await watchlistLogic._callClassifierAi(rt, `classify-${stock_code}`, prompt, SINGLE_CLASSIFICATION_TIMEOUT_MS);
395
395
 
396
396
  try {
397
397
  const parsed = extractJsonArray(aiText);
398
398
  const raw = parsed[0];
399
399
  if (raw) {
400
- return watchlistLogic._parseClassification(raw, cats, stockName || stockCode);
400
+ return watchlistLogic._parseClassification(raw, cats, stock_name || stock_code);
401
401
  }
402
402
  } catch (e) {
403
- logger.warn({ err: e instanceof Error ? e.message : String(e), stockCode, stockName }, "[Watchlist] AI 分类解析异常");
403
+ logger.warn({ err: e instanceof Error ? e.message : String(e), stock_code, stock_name }, "[Watchlist] AI 分类解析异常");
404
404
  }
405
405
  return null;
406
406
  },
@@ -418,11 +418,14 @@ export const watchlistLogic = {
418
418
  }
419
419
  }
420
420
  };
421
- const prepared = await prepareSimpleCompletionModelForAgent({
421
+ const completionModelParams: Parameters<typeof prepareSimpleCompletionModelForAgent>[0] & {
422
+ allowBundledStaticCatalogFallback?: boolean;
423
+ } = {
422
424
  cfg: embeddedCfg,
423
425
  agentId: MAIN_AGENT_ID,
424
426
  allowBundledStaticCatalogFallback: true
425
- });
427
+ };
428
+ const prepared = await prepareSimpleCompletionModelForAgent(completionModelParams);
426
429
  if ("error" in prepared) {
427
430
  throw new Error(prepared.error);
428
431
  }
@@ -472,12 +475,12 @@ export const watchlistLogic = {
472
475
  },
473
476
 
474
477
  _ensureCategory(db: any, name: string, type: 'industry' | 'theme', userId: string): string {
475
- const existing = db.prepare("SELECT id FROM watchlist_categories WHERE userId = ? AND name = ? AND type = ?").get(userId, name, type) as { id: string } | undefined;
478
+ const existing = db.prepare("SELECT id FROM industry_theme_categories WHERE userId = ? AND name = ? AND type = ?").get(userId, name, type) as { id: string } | undefined;
476
479
  if (existing) return existing.id;
477
- const maxOrderRow = db.prepare("SELECT MAX(sortOrder) as max FROM watchlist_categories WHERE userId = ?").get(userId) as { max: number } | undefined;
480
+ const maxOrderRow = db.prepare("SELECT MAX(sortOrder) as max FROM industry_theme_categories WHERE userId = ?").get(userId) as { max: number } | undefined;
478
481
  const nextOrder = (maxOrderRow?.max || 0) + 10;
479
482
  const id = randomUUID();
480
- db.prepare(`INSERT INTO watchlist_categories (id, userId, name, type, sortOrder, weight) VALUES (?, ?, ?, ?, ?, 0)`).run(id, userId, name, type, nextOrder);
483
+ db.prepare(`INSERT INTO industry_theme_categories (id, userId, name, type, sortOrder, weight) VALUES (?, ?, ?, ?, ?, 0)`).run(id, userId, name, type, nextOrder);
481
484
  return id;
482
485
  },
483
486
 
@@ -1,4 +1,4 @@
1
- import { z } from "openclaw/plugin-sdk/zod";
1
+ import { z } from "zod";
2
2
 
3
3
  export const GetWatchlistParamsSchema = z.object({
4
4
  categoryId: z.string().optional().describe("分类 ID,不传返回所有"),
@@ -21,8 +21,8 @@ const ExchangeEnum = z.enum(["SSE", "SZSE", "NASDAQ", "NYSE", "AMEX", "HKEX"]);
21
21
  const MarketEnum = z.enum(["A_SHARE", "US_SHARE", "HK_SHARE", "FUTURES", "FUND", "OTHER"]);
22
22
 
23
23
  export const AddToWatchlistParamsSchema = z.object({
24
- stockCode: z.string(),
25
- stockName: z.string(),
24
+ stock_code: z.string(),
25
+ stock_name: z.string(),
26
26
  exchange: ExchangeEnum,
27
27
  market: MarketEnum
28
28
  });
@@ -37,15 +37,15 @@ export type BatchAddToWatchlistParams = z.infer<typeof BatchAddToWatchlistParams
37
37
 
38
38
  export const UpdateWatchlistItemSchema = z.object({
39
39
  id: z.string(),
40
- stockName: z.string().optional(),
40
+ stock_name: z.string().optional(),
41
41
  sortOrder: z.number().optional()
42
42
  });
43
43
  export type UpdateWatchlistItemParams = z.infer<typeof UpdateWatchlistItemSchema>;
44
44
 
45
45
  export interface WatchlistRow {
46
46
  id: string;
47
- stockCode: string;
48
- stockName: string;
47
+ stock_code: string;
48
+ stock_name: string;
49
49
  exchange: string;
50
50
  market?: string;
51
51
  userId: string;
@@ -60,3 +60,8 @@ export interface CategoryRow {
60
60
  type?: 'industry' | 'theme';
61
61
  weight?: number;
62
62
  }
63
+
64
+ export const GetIndustryListParamsSchema = z.object({
65
+ type: z.string().optional().describe("分类类型:industry 行业,theme 主题,空字符串或不传表示全部")
66
+ });
67
+ export type GetIndustryListParams = z.infer<typeof GetIndustryListParamsSchema>;