@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.
- package/dist/index.d.ts +4 -3
- package/dist/index.js +49 -6
- package/dist/index.js.map +1 -1
- package/dist/setup-api.d.ts +2 -0
- package/dist/setup-api.js +8 -0
- package/dist/setup-api.js.map +1 -0
- package/dist/src/channel.js +25 -23
- package/dist/src/channel.js.map +1 -1
- package/dist/src/core/database.js +449 -39
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/dailyMorningBriefingCron.d.ts +46 -0
- package/dist/src/dailyMorningBriefingCron.js +82 -0
- package/dist/src/dailyMorningBriefingCron.js.map +1 -0
- package/dist/src/features/chartOutput.d.ts +2 -0
- package/dist/src/features/chartOutput.js +35 -0
- package/dist/src/features/chartOutput.js.map +1 -0
- package/dist/src/features/dailyMorningBriefing/schema.d.ts +56 -0
- package/dist/src/features/dailyMorningBriefing/schema.js +22 -0
- package/dist/src/features/dailyMorningBriefing/schema.js.map +1 -0
- package/dist/src/features/dailyMorningBriefing/tools.d.ts +12 -0
- package/dist/src/features/dailyMorningBriefing/tools.js +204 -0
- package/dist/src/features/dailyMorningBriefing/tools.js.map +1 -0
- package/dist/src/features/deepReasoning/schema.d.ts +43 -0
- package/dist/src/features/deepReasoning/schema.js +17 -0
- package/dist/src/features/deepReasoning/schema.js.map +1 -0
- package/dist/src/features/deepReasoning/tools.d.ts +12 -0
- package/dist/src/features/deepReasoning/tools.js +163 -0
- package/dist/src/features/deepReasoning/tools.js.map +1 -0
- package/dist/src/features/index.d.ts +2 -1
- package/dist/src/features/index.js +9 -1
- package/dist/src/features/index.js.map +1 -1
- package/dist/src/features/informationVerification/schema.d.ts +43 -0
- package/dist/src/features/informationVerification/schema.js +17 -0
- package/dist/src/features/informationVerification/schema.js.map +1 -0
- package/dist/src/features/informationVerification/tools.d.ts +12 -0
- package/dist/src/features/informationVerification/tools.js +162 -0
- package/dist/src/features/informationVerification/tools.js.map +1 -0
- package/dist/src/features/notes/schema.d.ts +136 -39
- package/dist/src/features/notes/schema.js +13 -10
- package/dist/src/features/notes/schema.js.map +1 -1
- package/dist/src/features/notes/tools.d.ts +1 -0
- package/dist/src/features/notes/tools.js +47 -14
- package/dist/src/features/notes/tools.js.map +1 -1
- package/dist/src/features/pluginInfo/schema.d.ts +79 -0
- package/dist/src/features/pluginInfo/schema.js +14 -0
- package/dist/src/features/pluginInfo/schema.js.map +1 -0
- package/dist/src/features/pluginInfo/tools.d.ts +1 -0
- package/dist/src/features/pluginInfo/tools.js +157 -2
- package/dist/src/features/pluginInfo/tools.js.map +1 -1
- package/dist/src/features/profileLibrary/schema.d.ts +34 -6
- package/dist/src/features/profileLibrary/schema.js +1 -1
- package/dist/src/features/profileLibrary/schema.js.map +1 -1
- package/dist/src/features/stockAnalysis/schema.d.ts +224 -31
- package/dist/src/features/stockAnalysis/schema.js +76 -12
- package/dist/src/features/stockAnalysis/schema.js.map +1 -1
- package/dist/src/features/stockAnalysis/tools.d.ts +6 -4
- package/dist/src/features/stockAnalysis/tools.js +389 -44
- package/dist/src/features/stockAnalysis/tools.js.map +1 -1
- package/dist/src/features/stockBasic/schema.d.ts +149 -0
- package/dist/src/features/stockBasic/schema.js +26 -0
- package/dist/src/features/stockBasic/schema.js.map +1 -0
- package/dist/src/features/stockBasic/tools.d.ts +12 -0
- package/dist/src/features/stockBasic/tools.js +124 -0
- package/dist/src/features/stockBasic/tools.js.map +1 -0
- package/dist/src/features/watchlist/logic.d.ts +3 -3
- package/dist/src/features/watchlist/logic.js +47 -46
- package/dist/src/features/watchlist/logic.js.map +1 -1
- package/dist/src/features/watchlist/schema.d.ts +89 -54
- package/dist/src/features/watchlist/schema.js +7 -4
- package/dist/src/features/watchlist/schema.js.map +1 -1
- package/dist/src/features/watchlist/tools.d.ts +106 -59
- package/dist/src/features/watchlist/tools.js +182 -104
- package/dist/src/features/watchlist/tools.js.map +1 -1
- package/dist/src/openclawConfig.d.ts +6 -0
- package/dist/src/openclawConfig.js +74 -0
- package/dist/src/openclawConfig.js.map +1 -0
- package/dist/src/openclawConstants.d.ts +4 -0
- package/dist/src/openclawConstants.js +10 -0
- package/dist/src/openclawConstants.js.map +1 -0
- package/dist/src/runtime.js +20 -0
- package/dist/src/runtime.js.map +1 -1
- package/index.ts +52 -5
- package/openclaw.plugin.json +24 -0
- package/package.json +21 -6
- package/setup-api.ts +10 -0
- package/src/channel.ts +26 -25
- package/src/core/database.ts +447 -40
- package/src/dailyMorningBriefingCron.ts +129 -0
- package/src/features/chartOutput.ts +35 -0
- package/src/features/dailyMorningBriefing/schema.ts +38 -0
- package/src/features/dailyMorningBriefing/tools.ts +246 -0
- package/src/features/deepReasoning/schema.ts +22 -0
- package/src/features/deepReasoning/tools.ts +182 -0
- package/src/features/index.ts +11 -2
- package/src/features/informationVerification/schema.ts +22 -0
- package/src/features/informationVerification/tools.ts +181 -0
- package/src/features/notes/schema.ts +17 -12
- package/src/features/notes/tools.ts +54 -17
- package/src/features/pluginInfo/schema.ts +19 -0
- package/src/features/pluginInfo/tools.ts +173 -2
- package/src/features/profileLibrary/schema.ts +1 -1
- package/src/features/stockAnalysis/schema.ts +99 -17
- package/src/features/stockAnalysis/tools.ts +447 -49
- package/src/features/stockBasic/schema.ts +33 -0
- package/src/features/stockBasic/tools.ts +157 -0
- package/src/features/watchlist/logic.ts +56 -53
- package/src/features/watchlist/schema.ts +11 -6
- package/src/features/watchlist/tools.ts +191 -106
- package/src/openclawConfig.ts +101 -0
- package/src/openclawConstants.ts +11 -0
- package/src/runtime.ts +19 -0
|
@@ -1,80 +1,291 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getDB } from "../../core/database.js";
|
|
3
|
+
import { CHART_OUTPUT_GUIDANCE, ensureChartPlaceholdersInBody } from "../chartOutput.js";
|
|
3
4
|
import {
|
|
4
5
|
ArticleAiAnalysis,
|
|
6
|
+
BuildStockAiAnalysisMessageParams,
|
|
7
|
+
BuildStockAiAnalysisMessageParamsSchema,
|
|
5
8
|
GetArticleAiAnalysisParamsSchema,
|
|
9
|
+
GetStockAiAnalysisDetailParamsSchema,
|
|
6
10
|
GetStockAiAnalysisParamsSchema,
|
|
11
|
+
QueryArticleAiAnalysisHistoryParamsSchema,
|
|
12
|
+
QueryStockAiAnalysisStocksParamsSchema,
|
|
13
|
+
SaveArticleDeepReasoningParamsSchema,
|
|
7
14
|
QueryStockAiAnalysisHistoryParamsSchema,
|
|
8
15
|
SaveArticleAiAnalysisParamsSchema,
|
|
9
16
|
SaveStockAiAnalysisParamsSchema,
|
|
10
|
-
StockAiAnalysis
|
|
17
|
+
StockAiAnalysis,
|
|
18
|
+
StockAiAnalysisStockSummary
|
|
11
19
|
} from "./schema.js";
|
|
12
20
|
|
|
13
21
|
interface RuntimeTool {
|
|
14
22
|
name: string;
|
|
23
|
+
label?: string;
|
|
15
24
|
description: string;
|
|
16
25
|
parameters: unknown;
|
|
17
26
|
registerTool?: boolean;
|
|
18
|
-
execute(params: unknown, ctx
|
|
27
|
+
execute(params: unknown, ctx?: { userId: string }): Promise<string>;
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
const STOCK_AI_ANALYSIS_SKILL = "hedgehog-stock-research";
|
|
31
|
+
|
|
32
|
+
const BuildStockAiAnalysisMessageAgentToolSchema = {
|
|
33
|
+
type: "object",
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
required: ["stock_code", "stock_name"],
|
|
36
|
+
properties: {
|
|
37
|
+
stock_code: { type: "string", description: "股票代码" },
|
|
38
|
+
stock_name: { type: "string", description: "股票名称" },
|
|
39
|
+
market: { type: "string", description: "市场类型,默认 CN" }
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function normalizeStockCode(stock_code: string): string {
|
|
44
|
+
return stock_code.trim().toUpperCase().replace(/\.SS$/i, ".SH");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveToolUserId(ctx?: { userId: string }): string {
|
|
48
|
+
return ctx?.userId || "default";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildStockAiAnalysisContent(args: BuildStockAiAnalysisMessageParams): string {
|
|
52
|
+
return [
|
|
53
|
+
`分析一下${args.stock_name}股票`,
|
|
54
|
+
"",
|
|
55
|
+
`股票代码:${normalizeStockCode(args.stock_code)}`,
|
|
56
|
+
`股票名称:${args.stock_name}`,
|
|
57
|
+
`市场:${args.market}`
|
|
58
|
+
].join("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildStockAiAnalysisMessage(args: BuildStockAiAnalysisMessageParams): string {
|
|
62
|
+
const stock_code = normalizeStockCode(args.stock_code);
|
|
63
|
+
const buildGeneratingSaveParams = () => JSON.stringify({
|
|
64
|
+
stock_code,
|
|
65
|
+
stock_name: args.stock_name,
|
|
66
|
+
market: args.market,
|
|
67
|
+
status: "generating",
|
|
68
|
+
content: ""
|
|
69
|
+
});
|
|
70
|
+
const buildFinalSaveParams = (status: "completed" | "failed") => JSON.stringify({
|
|
71
|
+
stock_code,
|
|
72
|
+
stock_name: args.stock_name,
|
|
73
|
+
market: args.market,
|
|
74
|
+
status,
|
|
75
|
+
content: "..."
|
|
76
|
+
});
|
|
77
|
+
return JSON.stringify({
|
|
78
|
+
cw_system_prompt: [
|
|
79
|
+
`开始分析前必须先调用 save_stock_ai_analysis,参数为 ${buildGeneratingSaveParams()};如果返回 skipped=true,必须立即停止,不要继续调用 skill 或生成正文。`,
|
|
80
|
+
`必须触发并使用 ${STOCK_AI_ANALYSIS_SKILL} skill 生成个股 AI 分析报告。`,
|
|
81
|
+
`生成成功后必须调用 save_stock_ai_analysis,参数为 ${buildFinalSaveParams("completed")},content 传最终完整个股分析正文。`,
|
|
82
|
+
`生成失败后必须调用 save_stock_ai_analysis,参数为 ${buildFinalSaveParams("failed")},content 存放完整错误信息。`,
|
|
83
|
+
"如果最终内容包含 [图表数据],正文必须已经包含所有对应图表占位符。"
|
|
84
|
+
].join("\n"),
|
|
85
|
+
cw_context: JSON.stringify({
|
|
86
|
+
stock_code,
|
|
87
|
+
stock_name: args.stock_name,
|
|
88
|
+
market: args.market,
|
|
89
|
+
saveMode: "tool"
|
|
90
|
+
}),
|
|
91
|
+
cw_market: args.market,
|
|
92
|
+
cw_stock_code: stock_code,
|
|
93
|
+
cw_stock_name: args.stock_name,
|
|
94
|
+
cw_content: buildStockAiAnalysisContent({ ...args, stock_code }),
|
|
95
|
+
cw_output: [
|
|
96
|
+
`输出结构以 ${STOCK_AI_ANALYSIS_SKILL} skill 的交付模板为准。`,
|
|
97
|
+
CHART_OUTPUT_GUIDANCE
|
|
98
|
+
].join("\n")
|
|
99
|
+
});
|
|
23
100
|
}
|
|
24
101
|
|
|
25
102
|
function selectLatestStockAnalysis(
|
|
103
|
+
db: ReturnType<typeof getDB>,
|
|
104
|
+
stock_code: string,
|
|
105
|
+
market: string
|
|
106
|
+
): StockAiAnalysis | undefined {
|
|
107
|
+
return db.prepare(`
|
|
108
|
+
SELECT id, stock_code, stock_name, market, status, content, createdAt, updatedAt
|
|
109
|
+
FROM stock_ai_analysis
|
|
110
|
+
WHERE stock_code = ? AND market = ?
|
|
111
|
+
ORDER BY updatedAt DESC, createdAt DESC
|
|
112
|
+
LIMIT 1
|
|
113
|
+
`).get(stock_code, market) as StockAiAnalysis | undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function selectLatestGeneratingStockAnalysis(
|
|
26
117
|
db: ReturnType<typeof getDB>,
|
|
27
118
|
userId: string,
|
|
28
|
-
|
|
119
|
+
stock_code: string,
|
|
120
|
+
market: string
|
|
121
|
+
): StockAiAnalysis | undefined {
|
|
122
|
+
return db.prepare(`
|
|
123
|
+
SELECT id, stock_code, stock_name, market, status, content, createdAt, updatedAt
|
|
124
|
+
FROM stock_ai_analysis
|
|
125
|
+
WHERE userId = ? AND stock_code = ? AND market = ? AND status = 'generating'
|
|
126
|
+
ORDER BY updatedAt DESC, createdAt DESC
|
|
127
|
+
LIMIT 1
|
|
128
|
+
`).get(userId, stock_code, market) as StockAiAnalysis | undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function selectStockAnalysisDetail(
|
|
132
|
+
db: ReturnType<typeof getDB>,
|
|
133
|
+
id: string
|
|
29
134
|
): StockAiAnalysis | undefined {
|
|
30
135
|
return db.prepare(`
|
|
31
|
-
SELECT id,
|
|
136
|
+
SELECT id, stock_code, stock_name, market, status, content, createdAt, updatedAt
|
|
32
137
|
FROM stock_ai_analysis
|
|
33
|
-
WHERE
|
|
138
|
+
WHERE id = ?
|
|
34
139
|
ORDER BY updatedAt DESC, createdAt DESC
|
|
35
140
|
LIMIT 1
|
|
36
|
-
`).get(
|
|
141
|
+
`).get(id) as StockAiAnalysis | undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function queryStockAnalysisStocks(
|
|
145
|
+
db: ReturnType<typeof getDB>,
|
|
146
|
+
userId: string,
|
|
147
|
+
market: string,
|
|
148
|
+
page: number,
|
|
149
|
+
pageSize: number
|
|
150
|
+
): { rows: StockAiAnalysisStockSummary[]; total: number } {
|
|
151
|
+
const offset = (page - 1) * pageSize;
|
|
152
|
+
const rows = db.prepare(`
|
|
153
|
+
WITH grouped AS (
|
|
154
|
+
SELECT
|
|
155
|
+
stock_code,
|
|
156
|
+
market,
|
|
157
|
+
COUNT(*) AS analysisCount,
|
|
158
|
+
MAX(updatedAt || '|' || createdAt || '|' || id) AS latestKey
|
|
159
|
+
FROM stock_ai_analysis
|
|
160
|
+
WHERE userId = ? AND market = ?
|
|
161
|
+
GROUP BY stock_code, market
|
|
162
|
+
)
|
|
163
|
+
SELECT
|
|
164
|
+
a.stock_code,
|
|
165
|
+
a.stock_name,
|
|
166
|
+
a.market,
|
|
167
|
+
a.id AS latestAnalysisId,
|
|
168
|
+
a.status AS latestStatus,
|
|
169
|
+
a.createdAt AS latestCreatedAt,
|
|
170
|
+
a.updatedAt AS latestUpdatedAt,
|
|
171
|
+
g.analysisCount
|
|
172
|
+
FROM grouped g
|
|
173
|
+
JOIN stock_ai_analysis a
|
|
174
|
+
ON a.stock_code = g.stock_code
|
|
175
|
+
AND a.market = g.market
|
|
176
|
+
AND (a.updatedAt || '|' || a.createdAt || '|' || a.id) = g.latestKey
|
|
177
|
+
WHERE a.userId = ?
|
|
178
|
+
ORDER BY a.updatedAt DESC, a.createdAt DESC
|
|
179
|
+
LIMIT ? OFFSET ?
|
|
180
|
+
`).all(userId, market, userId, pageSize, offset) as StockAiAnalysisStockSummary[];
|
|
181
|
+
const countRow = db.prepare(`
|
|
182
|
+
SELECT COUNT(*) AS total
|
|
183
|
+
FROM (
|
|
184
|
+
SELECT 1
|
|
185
|
+
FROM stock_ai_analysis
|
|
186
|
+
WHERE userId = ? AND market = ?
|
|
187
|
+
GROUP BY stock_code, market
|
|
188
|
+
)
|
|
189
|
+
`).get(userId, market) as { total: number };
|
|
190
|
+
return { rows, total: countRow.total || 0 };
|
|
37
191
|
}
|
|
38
192
|
|
|
39
193
|
export function saveStockAiAnalysisRecord(
|
|
40
194
|
db: ReturnType<typeof getDB>,
|
|
41
195
|
userId: string,
|
|
42
196
|
args: {
|
|
43
|
-
|
|
44
|
-
|
|
197
|
+
stock_code: string;
|
|
198
|
+
stock_name?: string;
|
|
45
199
|
market: string;
|
|
46
200
|
content: string;
|
|
201
|
+
status?: string;
|
|
47
202
|
}
|
|
48
203
|
): StockAiAnalysis {
|
|
49
|
-
const
|
|
204
|
+
const stock_code = normalizeStockCode(args.stock_code);
|
|
205
|
+
const status = args.status || "completed";
|
|
206
|
+
const stock_name = args.stock_name?.trim() || stock_code;
|
|
207
|
+
const content = status === "completed" ? ensureChartPlaceholdersInBody(args.content) : args.content.trim();
|
|
50
208
|
const id = randomUUID();
|
|
51
209
|
|
|
210
|
+
const generating = status === "generating"
|
|
211
|
+
? undefined
|
|
212
|
+
: selectLatestGeneratingStockAnalysis(db, userId, stock_code, args.market);
|
|
213
|
+
if (generating) {
|
|
214
|
+
db.prepare(`
|
|
215
|
+
UPDATE stock_ai_analysis
|
|
216
|
+
SET status = ?,
|
|
217
|
+
content = ?,
|
|
218
|
+
updatedAt = STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')
|
|
219
|
+
WHERE id = ? AND userId = ?
|
|
220
|
+
`).run(status, content, generating.id, userId);
|
|
221
|
+
return selectStockAnalysisDetail(db, generating.id) as StockAiAnalysis;
|
|
222
|
+
}
|
|
223
|
+
|
|
52
224
|
db.prepare(`
|
|
53
|
-
INSERT INTO stock_ai_analysis (id, userId,
|
|
54
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
55
|
-
`).run(id, userId,
|
|
225
|
+
INSERT INTO stock_ai_analysis (id, userId, stock_code, stock_name, market, status, content)
|
|
226
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
227
|
+
`).run(id, userId, stock_code, stock_name, args.market, status, content);
|
|
56
228
|
|
|
57
229
|
return db.prepare(`
|
|
58
|
-
SELECT id,
|
|
230
|
+
SELECT id, stock_code, stock_name, market, status, content, createdAt, updatedAt
|
|
59
231
|
FROM stock_ai_analysis
|
|
60
232
|
WHERE userId = ? AND id = ?
|
|
61
233
|
`).get(userId, id) as StockAiAnalysis;
|
|
62
234
|
}
|
|
63
235
|
|
|
236
|
+
function tableForArticleAnalysis(analysisType: ArticleAiAnalysis["analysisType"]): string {
|
|
237
|
+
return analysisType === "verification" ? "news_fact_check_analysis" : "news_deep_reasoning_analysis";
|
|
238
|
+
}
|
|
239
|
+
|
|
64
240
|
function selectLatestArticleAnalysis(
|
|
65
241
|
db: ReturnType<typeof getDB>,
|
|
66
242
|
userId: string,
|
|
67
243
|
sourceId: string,
|
|
68
244
|
analysisType: ArticleAiAnalysis["analysisType"],
|
|
69
|
-
market: string
|
|
245
|
+
market: string = "CN"
|
|
246
|
+
): ArticleAiAnalysis | undefined {
|
|
247
|
+
const table = tableForArticleAnalysis(analysisType);
|
|
248
|
+
if (analysisType === "deduction") {
|
|
249
|
+
return db.prepare(`
|
|
250
|
+
SELECT id, sourceId, ? AS analysisType, sourceTitle, market, status, content, createdAt, updatedAt
|
|
251
|
+
FROM ${table}
|
|
252
|
+
WHERE userId = ? AND sourceId = ? AND market = ?
|
|
253
|
+
ORDER BY updatedAt DESC, createdAt DESC
|
|
254
|
+
LIMIT 1
|
|
255
|
+
`).get(analysisType, userId, sourceId, market) as ArticleAiAnalysis | undefined;
|
|
256
|
+
}
|
|
257
|
+
return db.prepare(`
|
|
258
|
+
SELECT id, sourceId, ? AS analysisType, sourceTitle, status, content, createdAt, updatedAt
|
|
259
|
+
FROM ${table}
|
|
260
|
+
WHERE userId = ? AND sourceId = ?
|
|
261
|
+
ORDER BY updatedAt DESC, createdAt DESC
|
|
262
|
+
LIMIT 1
|
|
263
|
+
`).get(analysisType, userId, sourceId) as ArticleAiAnalysis | undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function selectLatestGeneratingArticleAnalysis(
|
|
267
|
+
db: ReturnType<typeof getDB>,
|
|
268
|
+
userId: string,
|
|
269
|
+
sourceId: string,
|
|
270
|
+
analysisType: ArticleAiAnalysis["analysisType"]
|
|
70
271
|
): ArticleAiAnalysis | undefined {
|
|
272
|
+
const table = tableForArticleAnalysis(analysisType);
|
|
273
|
+
if (analysisType === "deduction") {
|
|
274
|
+
return db.prepare(`
|
|
275
|
+
SELECT id, sourceId, ? AS analysisType, sourceTitle, market, status, content, createdAt, updatedAt
|
|
276
|
+
FROM ${table}
|
|
277
|
+
WHERE userId = ? AND sourceId = ? AND status = 'generating'
|
|
278
|
+
ORDER BY updatedAt DESC, createdAt DESC
|
|
279
|
+
LIMIT 1
|
|
280
|
+
`).get(analysisType, userId, sourceId) as ArticleAiAnalysis | undefined;
|
|
281
|
+
}
|
|
71
282
|
return db.prepare(`
|
|
72
|
-
SELECT id, sourceId, analysisType,
|
|
73
|
-
FROM
|
|
74
|
-
WHERE userId = ? AND sourceId = ? AND
|
|
283
|
+
SELECT id, sourceId, ? AS analysisType, sourceTitle, status, content, createdAt, updatedAt
|
|
284
|
+
FROM ${table}
|
|
285
|
+
WHERE userId = ? AND sourceId = ? AND status = 'generating'
|
|
75
286
|
ORDER BY updatedAt DESC, createdAt DESC
|
|
76
287
|
LIMIT 1
|
|
77
|
-
`).get(userId, sourceId
|
|
288
|
+
`).get(analysisType, userId, sourceId) as ArticleAiAnalysis | undefined;
|
|
78
289
|
}
|
|
79
290
|
|
|
80
291
|
function saveArticleAiAnalysisRecord(
|
|
@@ -82,63 +293,100 @@ function saveArticleAiAnalysisRecord(
|
|
|
82
293
|
userId: string,
|
|
83
294
|
args: {
|
|
84
295
|
sourceId: string;
|
|
85
|
-
|
|
86
|
-
market: string;
|
|
296
|
+
sourceTitle: string;
|
|
87
297
|
content: string;
|
|
298
|
+
status: string;
|
|
88
299
|
}
|
|
89
300
|
): ArticleAiAnalysis {
|
|
90
301
|
const id = randomUUID();
|
|
91
302
|
|
|
92
303
|
db.prepare(`
|
|
93
|
-
INSERT INTO
|
|
304
|
+
INSERT INTO news_fact_check_analysis (id, sourceId, sourceTitle, userId, status, content)
|
|
94
305
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
95
|
-
ON CONFLICT(sourceId, userId
|
|
306
|
+
ON CONFLICT(sourceId, userId) DO UPDATE SET
|
|
307
|
+
sourceTitle = CASE WHEN excluded.sourceTitle != '' THEN excluded.sourceTitle ELSE news_fact_check_analysis.sourceTitle END,
|
|
308
|
+
status = excluded.status,
|
|
309
|
+
content = excluded.content,
|
|
310
|
+
updatedAt = STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')
|
|
311
|
+
`).run(id, args.sourceId, args.sourceTitle, userId, args.status, args.content);
|
|
312
|
+
|
|
313
|
+
return db.prepare(`
|
|
314
|
+
SELECT id, sourceId, 'verification' AS analysisType, sourceTitle, status, content, createdAt, updatedAt
|
|
315
|
+
FROM news_fact_check_analysis
|
|
316
|
+
WHERE userId = ? AND sourceId = ?
|
|
317
|
+
`).get(userId, args.sourceId) as ArticleAiAnalysis;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function saveArticleDeepReasoningRecord(
|
|
321
|
+
db: ReturnType<typeof getDB>,
|
|
322
|
+
userId: string,
|
|
323
|
+
args: {
|
|
324
|
+
sourceId: string;
|
|
325
|
+
sourceTitle: string;
|
|
326
|
+
market: string;
|
|
327
|
+
content: string;
|
|
328
|
+
status: string;
|
|
329
|
+
}
|
|
330
|
+
): ArticleAiAnalysis {
|
|
331
|
+
const id = randomUUID();
|
|
332
|
+
|
|
333
|
+
db.prepare(`
|
|
334
|
+
INSERT INTO news_deep_reasoning_analysis (id, sourceId, sourceTitle, userId, market, status, content)
|
|
335
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
336
|
+
ON CONFLICT(sourceId, userId, market) DO UPDATE SET
|
|
337
|
+
sourceTitle = CASE WHEN excluded.sourceTitle != '' THEN excluded.sourceTitle ELSE news_deep_reasoning_analysis.sourceTitle END,
|
|
338
|
+
status = excluded.status,
|
|
96
339
|
content = excluded.content,
|
|
97
340
|
updatedAt = STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')
|
|
98
|
-
`).run(id, args.sourceId, userId, args.
|
|
341
|
+
`).run(id, args.sourceId, args.sourceTitle, userId, args.market, args.status, args.content);
|
|
99
342
|
|
|
100
343
|
return db.prepare(`
|
|
101
|
-
SELECT id, sourceId, analysisType, market, content, createdAt, updatedAt
|
|
102
|
-
FROM
|
|
103
|
-
WHERE userId = ? AND sourceId = ? AND
|
|
104
|
-
`).get(userId, args.sourceId, args.
|
|
344
|
+
SELECT id, sourceId, 'deduction' AS analysisType, sourceTitle, market, status, content, createdAt, updatedAt
|
|
345
|
+
FROM news_deep_reasoning_analysis
|
|
346
|
+
WHERE userId = ? AND sourceId = ? AND market = ?
|
|
347
|
+
`).get(userId, args.sourceId, args.market) as ArticleAiAnalysis;
|
|
105
348
|
}
|
|
106
349
|
|
|
107
350
|
export const stockAnalysisTools: Record<string, RuntimeTool> = {
|
|
108
351
|
get_stock_ai_analysis: {
|
|
109
352
|
name: "get_stock_ai_analysis",
|
|
110
|
-
description: "
|
|
353
|
+
description: "查询指定股票最新一条 AI 分析详情。该接口仅读取已持久化的分析结果,返回完整 content,不触发新的模型生成流程。",
|
|
111
354
|
parameters: GetStockAiAnalysisParamsSchema,
|
|
112
355
|
registerTool: false,
|
|
113
356
|
async execute(params, ctx) {
|
|
114
357
|
const args = GetStockAiAnalysisParamsSchema.parse(params);
|
|
115
358
|
const db = getDB();
|
|
116
|
-
const data = selectLatestStockAnalysis(db,
|
|
359
|
+
const data = selectLatestStockAnalysis(db, normalizeStockCode(args.stock_code), args.market);
|
|
117
360
|
return JSON.stringify({ success: true, data: data || null });
|
|
118
361
|
}
|
|
119
362
|
},
|
|
120
363
|
query_stock_ai_analysis_history: {
|
|
121
364
|
name: "query_stock_ai_analysis_history",
|
|
122
|
-
description: "
|
|
365
|
+
description: "分页查询股票 AI 分析记录列表。支持按股票代码和市场过滤,返回记录标识、股票信息、状态和时间字段;列表结果不包含 content,详情内容请使用详情查询接口获取。",
|
|
123
366
|
parameters: QueryStockAiAnalysisHistoryParamsSchema,
|
|
124
367
|
registerTool: false,
|
|
125
368
|
async execute(params, ctx) {
|
|
126
|
-
const args = QueryStockAiAnalysisHistoryParamsSchema.parse(params);
|
|
369
|
+
const args = QueryStockAiAnalysisHistoryParamsSchema.parse(params ?? {});
|
|
127
370
|
const db = getDB();
|
|
128
|
-
const stockCode = normalizeStockCode(args.stockCode);
|
|
129
371
|
const offset = (args.page - 1) * args.pageSize;
|
|
372
|
+
const conditions = ["userId = ?", "market = ?"];
|
|
373
|
+
const queryParams: unknown[] = [resolveToolUserId(ctx), args.market];
|
|
374
|
+
if (args.stock_code) {
|
|
375
|
+
conditions.push("stock_code = ?");
|
|
376
|
+
queryParams.push(normalizeStockCode(args.stock_code));
|
|
377
|
+
}
|
|
130
378
|
const rows = db.prepare(`
|
|
131
|
-
SELECT id,
|
|
379
|
+
SELECT id, stock_code, stock_name, market, status, createdAt, updatedAt
|
|
132
380
|
FROM stock_ai_analysis
|
|
133
|
-
WHERE
|
|
381
|
+
WHERE ${conditions.join(" AND ")}
|
|
134
382
|
ORDER BY updatedAt DESC, createdAt DESC
|
|
135
383
|
LIMIT ? OFFSET ?
|
|
136
|
-
`).all(
|
|
384
|
+
`).all(...queryParams, args.pageSize, offset);
|
|
137
385
|
const countRow = db.prepare(`
|
|
138
386
|
SELECT COUNT(*) AS total
|
|
139
387
|
FROM stock_ai_analysis
|
|
140
|
-
WHERE
|
|
141
|
-
`).get(
|
|
388
|
+
WHERE ${conditions.join(" AND ")}
|
|
389
|
+
`).get(...queryParams) as { total: number };
|
|
142
390
|
const total = countRow.total || 0;
|
|
143
391
|
|
|
144
392
|
return JSON.stringify({
|
|
@@ -153,39 +401,189 @@ export const stockAnalysisTools: Record<string, RuntimeTool> = {
|
|
|
153
401
|
});
|
|
154
402
|
}
|
|
155
403
|
},
|
|
404
|
+
query_stock_ai_analysis_stocks: {
|
|
405
|
+
name: "query_stock_ai_analysis_stocks",
|
|
406
|
+
description: "分页查询当前用户所有已经产生过个股 AI 分析记录的股票列表。按股票代码和市场去重,返回每只股票最近一次分析记录 ID、最近状态、最近更新时间和累计分析次数;不返回分析正文 content。",
|
|
407
|
+
parameters: QueryStockAiAnalysisStocksParamsSchema,
|
|
408
|
+
registerTool: false,
|
|
409
|
+
async execute(params, ctx) {
|
|
410
|
+
const args = QueryStockAiAnalysisStocksParamsSchema.parse(params ?? {});
|
|
411
|
+
const db = getDB();
|
|
412
|
+
const { rows, total } = queryStockAnalysisStocks(db, resolveToolUserId(ctx), args.market, args.page, args.pageSize);
|
|
413
|
+
return JSON.stringify({
|
|
414
|
+
success: true,
|
|
415
|
+
data: rows,
|
|
416
|
+
pagination: {
|
|
417
|
+
page: args.page,
|
|
418
|
+
pageSize: args.pageSize,
|
|
419
|
+
total,
|
|
420
|
+
totalPages: Math.ceil(total / args.pageSize)
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
get_stock_ai_analysis_detail: {
|
|
426
|
+
name: "get_stock_ai_analysis_detail",
|
|
427
|
+
description: "根据分析记录 ID 查询股票 AI 分析详情。返回指定记录的完整分析内容 content 及元数据,用于详情页展示或历史记录回放。",
|
|
428
|
+
parameters: GetStockAiAnalysisDetailParamsSchema,
|
|
429
|
+
registerTool: false,
|
|
430
|
+
async execute(params, ctx) {
|
|
431
|
+
const args = GetStockAiAnalysisDetailParamsSchema.parse(params);
|
|
432
|
+
const db = getDB();
|
|
433
|
+
const data = selectStockAnalysisDetail(db, args.id);
|
|
434
|
+
return JSON.stringify({ success: true, data: data || null });
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
build_stock_ai_analysis_message: {
|
|
438
|
+
name: "build_stock_ai_analysis_message",
|
|
439
|
+
label: "构建个股分析消息",
|
|
440
|
+
description: "根据股票代码、名称和市场构建用于主动 RPC 发起 Agent 个股 AI 分析任务的标准消息。该工具只返回提示词消息体,不触发定时任务,也不保存分析结果。",
|
|
441
|
+
parameters: BuildStockAiAnalysisMessageAgentToolSchema,
|
|
442
|
+
registerTool: false,
|
|
443
|
+
async execute(params, ctx) {
|
|
444
|
+
const args = BuildStockAiAnalysisMessageParamsSchema.parse(params);
|
|
445
|
+
const db = getDB();
|
|
446
|
+
const userId = resolveToolUserId(ctx);
|
|
447
|
+
const stock_code = normalizeStockCode(args.stock_code);
|
|
448
|
+
const generating = selectLatestGeneratingStockAnalysis(db, userId, stock_code, args.market);
|
|
449
|
+
if (generating) {
|
|
450
|
+
return JSON.stringify({
|
|
451
|
+
success: true,
|
|
452
|
+
skipped: true,
|
|
453
|
+
reason: "already_generating",
|
|
454
|
+
data: generating
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
const message = buildStockAiAnalysisMessage({ ...args, stock_code });
|
|
458
|
+
return JSON.stringify({
|
|
459
|
+
success: true,
|
|
460
|
+
data: {
|
|
461
|
+
message,
|
|
462
|
+
payload: JSON.parse(message),
|
|
463
|
+
stock_code,
|
|
464
|
+
saveParams: {
|
|
465
|
+
stock_code,
|
|
466
|
+
stock_name: args.stock_name,
|
|
467
|
+
market: args.market
|
|
468
|
+
},
|
|
469
|
+
skill: STOCK_AI_ANALYSIS_SKILL
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
},
|
|
156
474
|
save_stock_ai_analysis: {
|
|
157
475
|
name: "save_stock_ai_analysis",
|
|
158
|
-
description:
|
|
476
|
+
description: [
|
|
477
|
+
"保存个股 AI 分析结果。生成前必须先以 status=generating、content=\"\" 调用,并传入 stock_code、stock_name、market;生成成功后以 status=completed 保存完整正文 content;生成失败后以 status=failed 保存完整错误信息。",
|
|
478
|
+
"个股分析的 cw_output 需要包含图表相关要求:",
|
|
479
|
+
CHART_OUTPUT_GUIDANCE
|
|
480
|
+
].join("\n"),
|
|
159
481
|
parameters: SaveStockAiAnalysisParamsSchema,
|
|
160
|
-
registerTool:
|
|
482
|
+
registerTool: true,
|
|
161
483
|
async execute(params, ctx) {
|
|
162
484
|
const args = SaveStockAiAnalysisParamsSchema.parse(params);
|
|
163
485
|
const db = getDB();
|
|
164
|
-
const
|
|
486
|
+
const userId = resolveToolUserId(ctx);
|
|
487
|
+
if (args.status === "generating") {
|
|
488
|
+
const stock_code = normalizeStockCode(args.stock_code);
|
|
489
|
+
const generating = selectLatestGeneratingStockAnalysis(db, userId, stock_code, args.market);
|
|
490
|
+
if (generating) {
|
|
491
|
+
return JSON.stringify({ success: true, skipped: true, reason: "already_generating", data: generating });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const data = saveStockAiAnalysisRecord(db, userId, args);
|
|
165
495
|
return JSON.stringify({ success: true, data });
|
|
166
496
|
}
|
|
167
497
|
},
|
|
168
498
|
get_article_ai_analysis: {
|
|
169
499
|
name: "get_article_ai_analysis",
|
|
170
|
-
description: "
|
|
500
|
+
description: "根据资讯来源 ID 查询文章 AI 分析详情。支持信息求证和深度推演两类结果,返回完整 content;该接口仅读取已持久化数据,不触发新的模型生成流程。",
|
|
171
501
|
parameters: GetArticleAiAnalysisParamsSchema,
|
|
172
502
|
registerTool: false,
|
|
173
503
|
async execute(params, ctx) {
|
|
174
504
|
const args = GetArticleAiAnalysisParamsSchema.parse(params);
|
|
175
505
|
const db = getDB();
|
|
176
|
-
const data = selectLatestArticleAnalysis(db, ctx
|
|
506
|
+
const data = selectLatestArticleAnalysis(db, resolveToolUserId(ctx), args.sourceId, args.analysisType, args.market);
|
|
177
507
|
return JSON.stringify({ success: true, data: data || null });
|
|
178
508
|
}
|
|
179
509
|
},
|
|
180
|
-
|
|
181
|
-
name: "
|
|
182
|
-
description: "
|
|
183
|
-
parameters:
|
|
510
|
+
query_article_ai_analysis_history: {
|
|
511
|
+
name: "query_article_ai_analysis_history",
|
|
512
|
+
description: "分页查询文章 AI 分析记录列表。支持按分析类型过滤,返回记录标识、sourceId、状态和时间字段;列表结果不包含 content,详情内容请按 sourceId 查询。",
|
|
513
|
+
parameters: QueryArticleAiAnalysisHistoryParamsSchema,
|
|
184
514
|
registerTool: false,
|
|
515
|
+
async execute(params, ctx) {
|
|
516
|
+
const args = QueryArticleAiAnalysisHistoryParamsSchema.parse(params ?? {});
|
|
517
|
+
const db = getDB();
|
|
518
|
+
const userId = resolveToolUserId(ctx);
|
|
519
|
+
const table = tableForArticleAnalysis(args.analysisType);
|
|
520
|
+
const offset = (args.page - 1) * args.pageSize;
|
|
521
|
+
const marketCondition = args.analysisType === "deduction" ? " AND market = ?" : "";
|
|
522
|
+
const queryParams = args.analysisType === "deduction"
|
|
523
|
+
? [args.analysisType, userId, args.market, args.pageSize, offset]
|
|
524
|
+
: [args.analysisType, userId, args.pageSize, offset];
|
|
525
|
+
const rows = db.prepare(`
|
|
526
|
+
SELECT id, sourceId, ? AS analysisType, sourceTitle, ${args.analysisType === "deduction" ? "market," : ""} status, createdAt, updatedAt
|
|
527
|
+
FROM ${table}
|
|
528
|
+
WHERE userId = ?${marketCondition}
|
|
529
|
+
ORDER BY updatedAt DESC, createdAt DESC
|
|
530
|
+
LIMIT ? OFFSET ?
|
|
531
|
+
`).all(...queryParams);
|
|
532
|
+
const countParams = args.analysisType === "deduction" ? [userId, args.market] : [userId];
|
|
533
|
+
const countRow = db.prepare(`
|
|
534
|
+
SELECT COUNT(*) AS total
|
|
535
|
+
FROM ${table}
|
|
536
|
+
WHERE userId = ?${marketCondition}
|
|
537
|
+
`).get(...countParams) as { total: number };
|
|
538
|
+
const total = countRow.total || 0;
|
|
539
|
+
|
|
540
|
+
return JSON.stringify({
|
|
541
|
+
success: true,
|
|
542
|
+
data: rows,
|
|
543
|
+
pagination: {
|
|
544
|
+
page: args.page,
|
|
545
|
+
pageSize: args.pageSize,
|
|
546
|
+
total,
|
|
547
|
+
totalPages: Math.ceil(total / args.pageSize)
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
save_information_verification: {
|
|
553
|
+
name: "save_information_verification",
|
|
554
|
+
description: "保存新闻信息求证结果。生成前必须先以 status=generating、content=\"\" 调用,并传入 sourceId、sourceTitle;生成成功后以 status=completed 保存完整正文 content;生成失败后以 status=failed 保存完整错误信息。",
|
|
555
|
+
parameters: SaveArticleAiAnalysisParamsSchema,
|
|
556
|
+
registerTool: true,
|
|
185
557
|
async execute(params, ctx) {
|
|
186
558
|
const args = SaveArticleAiAnalysisParamsSchema.parse(params);
|
|
187
559
|
const db = getDB();
|
|
188
|
-
const
|
|
560
|
+
const userId = resolveToolUserId(ctx);
|
|
561
|
+
if (args.status === "generating") {
|
|
562
|
+
const generating = selectLatestGeneratingArticleAnalysis(db, userId, args.sourceId, "verification");
|
|
563
|
+
if (generating) {
|
|
564
|
+
return JSON.stringify({ success: true, skipped: true, reason: "already_generating", data: generating });
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const data = saveArticleAiAnalysisRecord(db, userId, args);
|
|
568
|
+
return JSON.stringify({ success: true, data });
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
save_article_deep_reasoning_analysis: {
|
|
572
|
+
name: "save_article_deep_reasoning_analysis",
|
|
573
|
+
description: "保存新闻深度推演结果。生成前必须先以 status=generating、content=\"\" 调用,并传入 sourceId、sourceTitle、sourceContent、market;生成成功后以 status=completed 保存完整正文 content;生成失败后以 status=failed 保存完整错误信息。",
|
|
574
|
+
parameters: SaveArticleDeepReasoningParamsSchema,
|
|
575
|
+
registerTool: true,
|
|
576
|
+
async execute(params, ctx) {
|
|
577
|
+
const args = SaveArticleDeepReasoningParamsSchema.parse(params);
|
|
578
|
+
const db = getDB();
|
|
579
|
+
const userId = resolveToolUserId(ctx);
|
|
580
|
+
if (args.status === "generating") {
|
|
581
|
+
const generating = selectLatestGeneratingArticleAnalysis(db, userId, args.sourceId, "deduction");
|
|
582
|
+
if (generating) {
|
|
583
|
+
return JSON.stringify({ success: true, skipped: true, reason: "already_generating", data: generating });
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const data = saveArticleDeepReasoningRecord(db, userId, args);
|
|
189
587
|
return JSON.stringify({ success: true, data });
|
|
190
588
|
}
|
|
191
589
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const StockBasicItemSchema = z.object({
|
|
4
|
+
act_ent_type: z.string().optional().default(""),
|
|
5
|
+
act_name: z.string().optional().default(""),
|
|
6
|
+
area: z.string().optional().default(""),
|
|
7
|
+
cnspell: z.string().optional().default(""),
|
|
8
|
+
curr_type: z.string().optional().default(""),
|
|
9
|
+
enname: z.string().optional().default(""),
|
|
10
|
+
exchange: z.string().trim().min(1),
|
|
11
|
+
fullname: z.string().optional().default(""),
|
|
12
|
+
industry: z.string().optional().default(""),
|
|
13
|
+
is_hs: z.string().optional().default(""),
|
|
14
|
+
list_date: z.string().optional().default(""),
|
|
15
|
+
market: z.string().optional().default(""),
|
|
16
|
+
name: z.string().trim().min(1),
|
|
17
|
+
stock_code: z.string().trim().min(1),
|
|
18
|
+
symbol: z.string().trim().min(1)
|
|
19
|
+
});
|
|
20
|
+
export type StockBasicItem = z.infer<typeof StockBasicItemSchema>;
|
|
21
|
+
|
|
22
|
+
export const SyncStockBasicParamsSchema = z.object({
|
|
23
|
+
stocks: z.array(StockBasicItemSchema).min(1)
|
|
24
|
+
});
|
|
25
|
+
export type SyncStockBasicParams = z.infer<typeof SyncStockBasicParamsSchema>;
|
|
26
|
+
|
|
27
|
+
export const GetStockBasicListParamsSchema = z.object({}).nullish();
|
|
28
|
+
export type GetStockBasicListParams = z.infer<typeof GetStockBasicListParamsSchema>;
|
|
29
|
+
|
|
30
|
+
export const GetStockBasicInfoParamsSchema = z.object({
|
|
31
|
+
stock_code: z.string().trim().min(1).describe("股票代码")
|
|
32
|
+
});
|
|
33
|
+
export type GetStockBasicInfoParams = z.infer<typeof GetStockBasicInfoParamsSchema>;
|