@hedgehog-finance/hedgehog-plugin 1.0.20 → 1.0.21
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 +1 -1
- package/dist/src/channel.js +124 -0
- package/dist/src/channel.js.map +1 -1
- package/dist/src/core/database.js +113 -0
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/features/index.js +5 -1
- package/dist/src/features/index.js.map +1 -1
- package/dist/src/features/pluginInfo/tools.d.ts +11 -0
- package/dist/src/features/pluginInfo/tools.js +49 -0
- package/dist/src/features/pluginInfo/tools.js.map +1 -0
- package/dist/src/features/stockAnalysis/schema.d.ts +61 -0
- package/dist/src/features/stockAnalysis/schema.js +30 -0
- package/dist/src/features/stockAnalysis/schema.js.map +1 -0
- package/dist/src/features/stockAnalysis/tools.d.ts +20 -0
- package/dist/src/features/stockAnalysis/tools.js +138 -0
- package/dist/src/features/stockAnalysis/tools.js.map +1 -0
- package/dist/src/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/channel.ts +128 -0
- package/src/core/database.ts +111 -0
- package/src/features/index.ts +5 -1
- package/src/features/pluginInfo/tools.ts +63 -0
- package/src/features/stockAnalysis/schema.ts +60 -0
- package/src/features/stockAnalysis/tools.ts +192 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "openclaw/plugin-sdk/zod";
|
|
2
|
+
export const GetStockAiAnalysisParamsSchema = z.object({
|
|
3
|
+
stockCode: z.string().trim().min(1).describe("股票代码"),
|
|
4
|
+
market: z.string().trim().min(1).default("CN").describe("市场类型,默认 CN")
|
|
5
|
+
});
|
|
6
|
+
export const QueryStockAiAnalysisHistoryParamsSchema = z.object({
|
|
7
|
+
stockCode: z.string().trim().min(1).describe("股票代码"),
|
|
8
|
+
market: z.string().trim().min(1).default("CN").describe("市场类型,默认 CN"),
|
|
9
|
+
page: z.number().int().min(1).default(1).describe("页码"),
|
|
10
|
+
pageSize: z.number().int().min(1).max(50).default(20).describe("每页数量")
|
|
11
|
+
});
|
|
12
|
+
export const SaveStockAiAnalysisParamsSchema = z.object({
|
|
13
|
+
stockCode: z.string().trim().min(1).describe("股票代码"),
|
|
14
|
+
stockName: z.string().trim().min(1).describe("股票名称"),
|
|
15
|
+
market: z.string().trim().min(1).default("CN").describe("市场类型,默认 CN"),
|
|
16
|
+
content: z.string().trim().min(1).describe("AI 分析内容")
|
|
17
|
+
});
|
|
18
|
+
export const ArticleAiAnalysisKindSchema = z.enum(["verification", "deduction"]);
|
|
19
|
+
export const GetArticleAiAnalysisParamsSchema = z.object({
|
|
20
|
+
id: z.string().trim().min(1).describe("文章来源 ID,例如 news-5、report-5、announce-5"),
|
|
21
|
+
analysisType: ArticleAiAnalysisKindSchema.describe("分析类型:verification 信息求证,deduction 深度推演"),
|
|
22
|
+
market: z.string().trim().min(1).default("CN").describe("市场类型,默认 CN")
|
|
23
|
+
});
|
|
24
|
+
export const SaveArticleAiAnalysisParamsSchema = z.object({
|
|
25
|
+
id: z.string().trim().min(1).describe("文章来源 ID,例如 news-5、report-5、announce-5"),
|
|
26
|
+
analysisType: ArticleAiAnalysisKindSchema.describe("分析类型:verification 信息求证,deduction 深度推演"),
|
|
27
|
+
market: z.string().trim().min(1).default("CN").describe("市场类型,默认 CN"),
|
|
28
|
+
content: z.string().trim().min(1).describe("AI 分析内容")
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../src/features/stockAnalysis/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getDB } from "../../core/database.js";
|
|
2
|
+
import { StockAiAnalysis } from "./schema.js";
|
|
3
|
+
interface RuntimeTool {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: unknown;
|
|
7
|
+
registerTool?: boolean;
|
|
8
|
+
execute(params: unknown, ctx: {
|
|
9
|
+
userId: string;
|
|
10
|
+
}): Promise<string>;
|
|
11
|
+
}
|
|
12
|
+
export declare function normalizeStockCode(stockCode: string): string;
|
|
13
|
+
export declare function saveStockAiAnalysisRecord(db: ReturnType<typeof getDB>, userId: string, args: {
|
|
14
|
+
stockCode: string;
|
|
15
|
+
stockName: string;
|
|
16
|
+
market: string;
|
|
17
|
+
content: string;
|
|
18
|
+
}): StockAiAnalysis;
|
|
19
|
+
export declare const stockAnalysisTools: Record<string, RuntimeTool>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getDB } from "../../core/database.js";
|
|
3
|
+
import { GetArticleAiAnalysisParamsSchema, GetStockAiAnalysisParamsSchema, QueryStockAiAnalysisHistoryParamsSchema, SaveArticleAiAnalysisParamsSchema, SaveStockAiAnalysisParamsSchema } from "./schema.js";
|
|
4
|
+
export function normalizeStockCode(stockCode) {
|
|
5
|
+
return stockCode.trim().toUpperCase().replace(/\.SS$/i, ".SH");
|
|
6
|
+
}
|
|
7
|
+
function selectLatestStockAnalysis(db, userId, stockCode) {
|
|
8
|
+
return db.prepare(`
|
|
9
|
+
SELECT id, stockCode, stockName, market, content, createdAt, updatedAt
|
|
10
|
+
FROM stock_ai_analysis
|
|
11
|
+
WHERE userId = ? AND stockCode = ?
|
|
12
|
+
ORDER BY updatedAt DESC, createdAt DESC
|
|
13
|
+
LIMIT 1
|
|
14
|
+
`).get(userId, stockCode);
|
|
15
|
+
}
|
|
16
|
+
export function saveStockAiAnalysisRecord(db, userId, args) {
|
|
17
|
+
const stockCode = normalizeStockCode(args.stockCode);
|
|
18
|
+
const id = randomUUID();
|
|
19
|
+
db.prepare(`
|
|
20
|
+
INSERT INTO stock_ai_analysis (id, userId, stockCode, stockName, market, content)
|
|
21
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
22
|
+
`).run(id, userId, stockCode, args.stockName, args.market, args.content);
|
|
23
|
+
return db.prepare(`
|
|
24
|
+
SELECT id, stockCode, stockName, market, content, createdAt, updatedAt
|
|
25
|
+
FROM stock_ai_analysis
|
|
26
|
+
WHERE userId = ? AND id = ?
|
|
27
|
+
`).get(userId, id);
|
|
28
|
+
}
|
|
29
|
+
function selectLatestArticleAnalysis(db, userId, sourceId, analysisType, market) {
|
|
30
|
+
return db.prepare(`
|
|
31
|
+
SELECT id, sourceId, analysisType, market, content, createdAt, updatedAt
|
|
32
|
+
FROM article_ai_analysis
|
|
33
|
+
WHERE userId = ? AND sourceId = ? AND analysisType = ? AND market = ?
|
|
34
|
+
ORDER BY updatedAt DESC, createdAt DESC
|
|
35
|
+
LIMIT 1
|
|
36
|
+
`).get(userId, sourceId, analysisType, market);
|
|
37
|
+
}
|
|
38
|
+
function saveArticleAiAnalysisRecord(db, userId, args) {
|
|
39
|
+
const id = randomUUID();
|
|
40
|
+
db.prepare(`
|
|
41
|
+
INSERT INTO article_ai_analysis (id, sourceId, userId, analysisType, market, content)
|
|
42
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
43
|
+
ON CONFLICT(sourceId, userId, analysisType, market) DO UPDATE SET
|
|
44
|
+
content = excluded.content,
|
|
45
|
+
updatedAt = STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')
|
|
46
|
+
`).run(id, args.sourceId, userId, args.analysisType, args.market, args.content);
|
|
47
|
+
return db.prepare(`
|
|
48
|
+
SELECT id, sourceId, analysisType, market, content, createdAt, updatedAt
|
|
49
|
+
FROM article_ai_analysis
|
|
50
|
+
WHERE userId = ? AND sourceId = ? AND analysisType = ? AND market = ?
|
|
51
|
+
`).get(userId, args.sourceId, args.analysisType, args.market);
|
|
52
|
+
}
|
|
53
|
+
export const stockAnalysisTools = {
|
|
54
|
+
get_stock_ai_analysis: {
|
|
55
|
+
name: "get_stock_ai_analysis",
|
|
56
|
+
description: "读取股票 AI 分析的最新一条历史记录;不触发模型分析。",
|
|
57
|
+
parameters: GetStockAiAnalysisParamsSchema,
|
|
58
|
+
registerTool: false,
|
|
59
|
+
async execute(params, ctx) {
|
|
60
|
+
const args = GetStockAiAnalysisParamsSchema.parse(params);
|
|
61
|
+
const db = getDB();
|
|
62
|
+
const data = selectLatestStockAnalysis(db, ctx.userId, normalizeStockCode(args.stockCode));
|
|
63
|
+
return JSON.stringify({ success: true, data: data || null });
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
query_stock_ai_analysis_history: {
|
|
67
|
+
name: "query_stock_ai_analysis_history",
|
|
68
|
+
description: "分页读取股票 AI 分析历史记录;不触发模型分析。",
|
|
69
|
+
parameters: QueryStockAiAnalysisHistoryParamsSchema,
|
|
70
|
+
registerTool: false,
|
|
71
|
+
async execute(params, ctx) {
|
|
72
|
+
const args = QueryStockAiAnalysisHistoryParamsSchema.parse(params);
|
|
73
|
+
const db = getDB();
|
|
74
|
+
const stockCode = normalizeStockCode(args.stockCode);
|
|
75
|
+
const offset = (args.page - 1) * args.pageSize;
|
|
76
|
+
const rows = db.prepare(`
|
|
77
|
+
SELECT id, stockCode, stockName, market, content, createdAt, updatedAt
|
|
78
|
+
FROM stock_ai_analysis
|
|
79
|
+
WHERE userId = ? AND stockCode = ? AND market = ?
|
|
80
|
+
ORDER BY updatedAt DESC, createdAt DESC
|
|
81
|
+
LIMIT ? OFFSET ?
|
|
82
|
+
`).all(ctx.userId, stockCode, args.market, args.pageSize, offset);
|
|
83
|
+
const countRow = db.prepare(`
|
|
84
|
+
SELECT COUNT(*) AS total
|
|
85
|
+
FROM stock_ai_analysis
|
|
86
|
+
WHERE userId = ? AND stockCode = ? AND market = ?
|
|
87
|
+
`).get(ctx.userId, stockCode, args.market);
|
|
88
|
+
const total = countRow.total || 0;
|
|
89
|
+
return JSON.stringify({
|
|
90
|
+
success: true,
|
|
91
|
+
data: rows,
|
|
92
|
+
pagination: {
|
|
93
|
+
page: args.page,
|
|
94
|
+
pageSize: args.pageSize,
|
|
95
|
+
total,
|
|
96
|
+
totalPages: Math.ceil(total / args.pageSize)
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
save_stock_ai_analysis: {
|
|
102
|
+
name: "save_stock_ai_analysis",
|
|
103
|
+
description: "追加保存一条股票 AI 分析历史记录。",
|
|
104
|
+
parameters: SaveStockAiAnalysisParamsSchema,
|
|
105
|
+
registerTool: false,
|
|
106
|
+
async execute(params, ctx) {
|
|
107
|
+
const args = SaveStockAiAnalysisParamsSchema.parse(params);
|
|
108
|
+
const db = getDB();
|
|
109
|
+
const data = saveStockAiAnalysisRecord(db, ctx.userId, args);
|
|
110
|
+
return JSON.stringify({ success: true, data });
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
get_article_ai_analysis: {
|
|
114
|
+
name: "get_article_ai_analysis",
|
|
115
|
+
description: "读取文章 AI 分析结果;支持信息求证与深度推演,不触发模型分析。",
|
|
116
|
+
parameters: GetArticleAiAnalysisParamsSchema,
|
|
117
|
+
registerTool: false,
|
|
118
|
+
async execute(params, ctx) {
|
|
119
|
+
const args = GetArticleAiAnalysisParamsSchema.parse(params);
|
|
120
|
+
const db = getDB();
|
|
121
|
+
const data = selectLatestArticleAnalysis(db, ctx.userId, args.id, args.analysisType, args.market);
|
|
122
|
+
return JSON.stringify({ success: true, data: data || null });
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
save_article_ai_analysis: {
|
|
126
|
+
name: "save_article_ai_analysis",
|
|
127
|
+
description: "保存文章 AI 分析结果;支持信息求证与深度推演。",
|
|
128
|
+
parameters: SaveArticleAiAnalysisParamsSchema,
|
|
129
|
+
registerTool: false,
|
|
130
|
+
async execute(params, ctx) {
|
|
131
|
+
const args = SaveArticleAiAnalysisParamsSchema.parse(params);
|
|
132
|
+
const db = getDB();
|
|
133
|
+
const data = saveArticleAiAnalysisRecord(db, ctx.userId, { ...args, sourceId: args.id });
|
|
134
|
+
return JSON.stringify({ success: true, data });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../../src/features/stockAnalysis/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -61,7 +61,6 @@ export declare const StockClassificationSchema: z.ZodObject<{
|
|
|
61
61
|
}>, "many">;
|
|
62
62
|
weight: z.ZodDefault<z.ZodNumber>;
|
|
63
63
|
}, "strip", z.ZodTypeAny, {
|
|
64
|
-
weight: number;
|
|
65
64
|
industry: {
|
|
66
65
|
name: string;
|
|
67
66
|
weight: number;
|
|
@@ -70,6 +69,7 @@ export declare const StockClassificationSchema: z.ZodObject<{
|
|
|
70
69
|
name: string;
|
|
71
70
|
weight: number;
|
|
72
71
|
}[];
|
|
72
|
+
weight: number;
|
|
73
73
|
}, {
|
|
74
74
|
industry: {
|
|
75
75
|
name: string;
|
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -14,11 +14,13 @@ import type {
|
|
|
14
14
|
} from "openclaw/plugin-sdk/channel-contract";
|
|
15
15
|
import { getHedgehogRuntime } from "./runtime.js";
|
|
16
16
|
import { logger } from "./core/logger.js";
|
|
17
|
+
import { getDB } from "./core/database.js";
|
|
17
18
|
import type {
|
|
18
19
|
HedgehogFinanceResolvedAccount,
|
|
19
20
|
RelayInboundMessage
|
|
20
21
|
} from "./types.js";
|
|
21
22
|
import { allFeaturesTools } from "./features/index.js";
|
|
23
|
+
import { saveStockAiAnalysisRecord } from "./features/stockAnalysis/tools.js";
|
|
22
24
|
|
|
23
25
|
function getCurrentTimestamp(): number {
|
|
24
26
|
return Date.now();
|
|
@@ -162,6 +164,90 @@ function isReasoningPayload(payload: any, info?: any): boolean {
|
|
|
162
164
|
return isReasoningReplyPayload(payload);
|
|
163
165
|
}
|
|
164
166
|
|
|
167
|
+
function parseStockAnalysisRequest(text: string, chatId?: string) {
|
|
168
|
+
let body: unknown;
|
|
169
|
+
try {
|
|
170
|
+
body = JSON.parse(text);
|
|
171
|
+
} catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const payload = body as Record<string, unknown>;
|
|
180
|
+
const cwContent = typeof payload.cw_content === "string" ? payload.cw_content.trim() : "";
|
|
181
|
+
|
|
182
|
+
let stockCode = "";
|
|
183
|
+
let stockName = "";
|
|
184
|
+
|
|
185
|
+
// 1. Try parsing cw_context if it exists
|
|
186
|
+
const cwContext = typeof payload.cw_context === "string" ? payload.cw_context.trim() : "";
|
|
187
|
+
if (cwContext) {
|
|
188
|
+
try {
|
|
189
|
+
const parsedContext = JSON.parse(cwContext);
|
|
190
|
+
if (parsedContext && typeof parsedContext === "object" && !Array.isArray(parsedContext)) {
|
|
191
|
+
stockCode = typeof parsedContext.stockCode === "string" ? parsedContext.stockCode.trim() : "";
|
|
192
|
+
stockName = typeof parsedContext.stockName === "string" ? parsedContext.stockName.trim() : "";
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
// If not a valid JSON string, treat cwContext as the plain stockCode
|
|
196
|
+
stockCode = cwContext;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 2. If we found a stockCode but no stockName, attempt database lookups
|
|
201
|
+
if (stockCode && !stockName) {
|
|
202
|
+
try {
|
|
203
|
+
const db = getDB();
|
|
204
|
+
const normalizedCode = stockCode.toUpperCase().replace(/\.SS$/i, ".SH");
|
|
205
|
+
// Query global_stock_metadata first
|
|
206
|
+
let row = db.prepare(`SELECT stockName FROM global_stock_metadata WHERE stockCode = ? OR stockCode = ? LIMIT 1`)
|
|
207
|
+
.get(normalizedCode, normalizedCode.replace(/\.SH$/i, "").replace(/\.SZ$/i, "").replace(/\.HK$/i, "")) as { stockName: string } | undefined;
|
|
208
|
+
if (!row) {
|
|
209
|
+
// Fallback to watchlist
|
|
210
|
+
row = db.prepare(`SELECT stockName FROM watchlist WHERE stockCode = ? LIMIT 1`)
|
|
211
|
+
.get(normalizedCode) as { stockName: string } | undefined;
|
|
212
|
+
}
|
|
213
|
+
if (row?.stockName) {
|
|
214
|
+
stockName = row.stockName;
|
|
215
|
+
} else {
|
|
216
|
+
stockName = stockCode; // fallback to code if name not found in db
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
stockName = stockCode;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 3. Fallback to legacy cwContent/chatId parsing if no stockCode was found via cw_context
|
|
224
|
+
if (!stockCode) {
|
|
225
|
+
if (!cwContent.startsWith("分析一下") || !cwContent.endsWith("股票")) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const chatIdMatch = typeof chatId === "string"
|
|
230
|
+
? /^stock_analysis_(.+)_\d+$/.exec(chatId)
|
|
231
|
+
: null;
|
|
232
|
+
stockCode = typeof payload.cw_stock_code === "string"
|
|
233
|
+
? payload.cw_stock_code.trim()
|
|
234
|
+
: chatIdMatch?.[1]?.trim() || "";
|
|
235
|
+
stockName = typeof payload.cw_stock_name === "string"
|
|
236
|
+
? payload.cw_stock_name.trim()
|
|
237
|
+
: cwContent.replace(/^分析一下/, "").replace(/股票$/, "").trim();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const market = typeof payload.cw_market === "string" && payload.cw_market.trim()
|
|
241
|
+
? payload.cw_market.trim()
|
|
242
|
+
: "CN";
|
|
243
|
+
|
|
244
|
+
if (!stockCode || !stockName) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return { stockCode, stockName, market };
|
|
249
|
+
}
|
|
250
|
+
|
|
165
251
|
async function getJsonlLineCountAsync(agentId: string, sessionId: string): Promise<number> {
|
|
166
252
|
try {
|
|
167
253
|
const stateDir = getStateDir();
|
|
@@ -618,6 +704,41 @@ export const hedgehogFinancePlugin: ChannelPlugin<HedgehogFinanceResolvedAccount
|
|
|
618
704
|
sendEvent("reply", { text: delta, isPartial: true });
|
|
619
705
|
};
|
|
620
706
|
|
|
707
|
+
const stockAnalysisRequest = parseStockAnalysisRequest(text, chatId);
|
|
708
|
+
let stockAnalysisReplyText = "";
|
|
709
|
+
let didSaveStockAnalysis = false;
|
|
710
|
+
const appendStockAnalysisReplyText = (content: string) => {
|
|
711
|
+
if (!stockAnalysisRequest) return;
|
|
712
|
+
const visibleContent = extractVisibleReplyText(content);
|
|
713
|
+
if (!visibleContent) return;
|
|
714
|
+
|
|
715
|
+
if (!stockAnalysisReplyText || visibleContent.startsWith(stockAnalysisReplyText)) {
|
|
716
|
+
stockAnalysisReplyText = visibleContent;
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (stockAnalysisReplyText.includes(visibleContent)) return;
|
|
720
|
+
stockAnalysisReplyText += visibleContent;
|
|
721
|
+
};
|
|
722
|
+
const saveStockAnalysisReply = (content: string) => {
|
|
723
|
+
if (!stockAnalysisRequest || didSaveStockAnalysis) return;
|
|
724
|
+
appendStockAnalysisReplyText(content);
|
|
725
|
+
const visibleContent = stockAnalysisReplyText.trim();
|
|
726
|
+
if (!visibleContent) return;
|
|
727
|
+
|
|
728
|
+
try {
|
|
729
|
+
saveStockAiAnalysisRecord(getDB(), accountId, {
|
|
730
|
+
...stockAnalysisRequest,
|
|
731
|
+
content: visibleContent
|
|
732
|
+
});
|
|
733
|
+
didSaveStockAnalysis = true;
|
|
734
|
+
} catch (err: any) {
|
|
735
|
+
const message = err?.message || "保存股票 AI 分析失败";
|
|
736
|
+
childLogger.error({ err: message, chatId, stockCode: stockAnalysisRequest.stockCode }, "保存股票 AI 分析失败");
|
|
737
|
+
sendEvent("error", { error: message });
|
|
738
|
+
throw err;
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
621
742
|
const normalizeId = (rawId?: string) => rawId?.replace(/^(command:|tool:|call_)/, '');
|
|
622
743
|
let hasSentModelEvent = false;
|
|
623
744
|
|
|
@@ -790,6 +911,12 @@ export const hedgehogFinancePlugin: ChannelPlugin<HedgehogFinanceResolvedAccount
|
|
|
790
911
|
sendReasoningText(payload.text);
|
|
791
912
|
return;
|
|
792
913
|
}
|
|
914
|
+
appendStockAnalysisReplyText(payload.text);
|
|
915
|
+
if (info.kind === "final") {
|
|
916
|
+
saveStockAnalysisReply(payload.text);
|
|
917
|
+
sendEvent("reply", { text: extractVisibleReplyText(payload.text), isFinal: true, replace: true });
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
793
920
|
sendReplyText({ text: payload.text, replace: true });
|
|
794
921
|
}
|
|
795
922
|
|
|
@@ -803,6 +930,7 @@ export const hedgehogFinancePlugin: ChannelPlugin<HedgehogFinanceResolvedAccount
|
|
|
803
930
|
},
|
|
804
931
|
}
|
|
805
932
|
});
|
|
933
|
+
saveStockAnalysisReply(stockAnalysisReplyText);
|
|
806
934
|
await sendFinalReplyAndUsage();
|
|
807
935
|
} finally {
|
|
808
936
|
delete replyTextStateMap[chatId];
|
package/src/core/database.ts
CHANGED
|
@@ -153,6 +153,88 @@ function runStockNotesMigrations(db: DatabaseSync) {
|
|
|
153
153
|
db.exec("CREATE INDEX IF NOT EXISTS idx_stock_notes_user_stock ON stock_notes(userId, watchlistId, updatedAt DESC)");
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function runStockAiAnalysisMigrations(db: DatabaseSync) {
|
|
157
|
+
const indexes = db.prepare("PRAGMA index_list(stock_ai_analysis)").all() as { name: string; unique: number }[];
|
|
158
|
+
const hasUniqueStockIndex = indexes.some(index => {
|
|
159
|
+
if (index.unique !== 1) return false;
|
|
160
|
+
const columns = db.prepare(`PRAGMA index_info(${index.name})`).all() as { name: string }[];
|
|
161
|
+
const columnNames = columns.map(column => column.name);
|
|
162
|
+
return columnNames.includes("userId") && columnNames.includes("stockCode");
|
|
163
|
+
});
|
|
164
|
+
if (!hasUniqueStockIndex) return;
|
|
165
|
+
|
|
166
|
+
db.exec("BEGIN");
|
|
167
|
+
try {
|
|
168
|
+
db.exec(`
|
|
169
|
+
DROP TABLE IF EXISTS stock_ai_analysis_history;
|
|
170
|
+
|
|
171
|
+
CREATE TABLE stock_ai_analysis_history (
|
|
172
|
+
id TEXT NOT NULL,
|
|
173
|
+
userId TEXT NOT NULL,
|
|
174
|
+
stockCode TEXT NOT NULL,
|
|
175
|
+
stockName TEXT NOT NULL,
|
|
176
|
+
market TEXT NOT NULL DEFAULT 'CN',
|
|
177
|
+
content TEXT NOT NULL,
|
|
178
|
+
createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
|
|
179
|
+
updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
|
|
180
|
+
PRIMARY KEY(id, userId)
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
INSERT INTO stock_ai_analysis_history (id, userId, stockCode, stockName, market, content, createdAt, updatedAt)
|
|
184
|
+
SELECT id, userId, stockCode, stockName, market, content, createdAt, updatedAt
|
|
185
|
+
FROM stock_ai_analysis;
|
|
186
|
+
|
|
187
|
+
DROP TABLE stock_ai_analysis;
|
|
188
|
+
ALTER TABLE stock_ai_analysis_history RENAME TO stock_ai_analysis;
|
|
189
|
+
CREATE INDEX IF NOT EXISTS idx_stock_ai_analysis_user_stock_updated ON stock_ai_analysis(userId, stockCode, updatedAt DESC);
|
|
190
|
+
`);
|
|
191
|
+
db.exec("COMMIT");
|
|
192
|
+
} catch (e) {
|
|
193
|
+
if (db.inTransaction) db.exec("ROLLBACK");
|
|
194
|
+
throw e;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function runArticleAiAnalysisMigrations(db: DatabaseSync) {
|
|
199
|
+
const columns = db.prepare("PRAGMA table_info(article_ai_analysis)").all() as { name: string }[];
|
|
200
|
+
if (columns.length === 0 || columns.some(column => column.name === "sourceId")) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
db.exec("BEGIN");
|
|
205
|
+
try {
|
|
206
|
+
db.exec(`
|
|
207
|
+
DROP TABLE IF EXISTS article_ai_analysis_v2;
|
|
208
|
+
|
|
209
|
+
CREATE TABLE article_ai_analysis_v2 (
|
|
210
|
+
id TEXT NOT NULL,
|
|
211
|
+
sourceId TEXT NOT NULL,
|
|
212
|
+
userId TEXT NOT NULL,
|
|
213
|
+
analysisType TEXT NOT NULL,
|
|
214
|
+
market TEXT NOT NULL DEFAULT 'CN',
|
|
215
|
+
content TEXT NOT NULL,
|
|
216
|
+
createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
|
|
217
|
+
updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
|
|
218
|
+
PRIMARY KEY(id, userId),
|
|
219
|
+
UNIQUE(sourceId, userId, analysisType, market)
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
INSERT INTO article_ai_analysis_v2 (id, sourceId, userId, analysisType, market, content, createdAt, updatedAt)
|
|
223
|
+
SELECT lower(hex(randomblob(16))), id, userId, analysisType, market, content, createdAt, updatedAt
|
|
224
|
+
FROM article_ai_analysis;
|
|
225
|
+
|
|
226
|
+
DROP TABLE article_ai_analysis;
|
|
227
|
+
ALTER TABLE article_ai_analysis_v2 RENAME TO article_ai_analysis;
|
|
228
|
+
CREATE INDEX IF NOT EXISTS idx_article_ai_analysis_user_source_type_updated
|
|
229
|
+
ON article_ai_analysis(userId, sourceId, analysisType, updatedAt DESC);
|
|
230
|
+
`);
|
|
231
|
+
db.exec("COMMIT");
|
|
232
|
+
} catch (e) {
|
|
233
|
+
if (db.inTransaction) db.exec("ROLLBACK");
|
|
234
|
+
throw e;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
156
238
|
export function getDB(): DatabaseSync {
|
|
157
239
|
if (!_db) {
|
|
158
240
|
const dbPath = getDbPath();
|
|
@@ -263,10 +345,39 @@ export function getDB(): DatabaseSync {
|
|
|
263
345
|
UNIQUE(noteId, userId, profileLibraryId)
|
|
264
346
|
);
|
|
265
347
|
CREATE INDEX IF NOT EXISTS idx_stock_note_profile_libraries_user_note ON stock_note_profile_libraries(userId, noteId);
|
|
348
|
+
|
|
349
|
+
CREATE TABLE IF NOT EXISTS stock_ai_analysis (
|
|
350
|
+
id TEXT NOT NULL,
|
|
351
|
+
userId TEXT NOT NULL,
|
|
352
|
+
stockCode TEXT NOT NULL,
|
|
353
|
+
stockName TEXT NOT NULL,
|
|
354
|
+
market TEXT NOT NULL DEFAULT 'CN',
|
|
355
|
+
content TEXT NOT NULL,
|
|
356
|
+
createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
|
|
357
|
+
updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
|
|
358
|
+
PRIMARY KEY(id, userId)
|
|
359
|
+
);
|
|
360
|
+
CREATE INDEX IF NOT EXISTS idx_stock_ai_analysis_user_stock_updated ON stock_ai_analysis(userId, stockCode, updatedAt DESC);
|
|
361
|
+
|
|
362
|
+
CREATE TABLE IF NOT EXISTS article_ai_analysis (
|
|
363
|
+
id TEXT NOT NULL,
|
|
364
|
+
sourceId TEXT NOT NULL,
|
|
365
|
+
userId TEXT NOT NULL,
|
|
366
|
+
analysisType TEXT NOT NULL,
|
|
367
|
+
market TEXT NOT NULL DEFAULT 'CN',
|
|
368
|
+
content TEXT NOT NULL,
|
|
369
|
+
createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
|
|
370
|
+
updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
|
|
371
|
+
PRIMARY KEY(id, userId),
|
|
372
|
+
UNIQUE(sourceId, userId, analysisType, market)
|
|
373
|
+
);
|
|
374
|
+
CREATE INDEX IF NOT EXISTS idx_article_ai_analysis_user_source_type_updated ON article_ai_analysis(userId, sourceId, analysisType, updatedAt DESC);
|
|
266
375
|
`);
|
|
267
376
|
|
|
268
377
|
runWatchlistDedupMigrations(_db);
|
|
269
378
|
runStockNotesMigrations(_db);
|
|
379
|
+
runStockAiAnalysisMigrations(_db);
|
|
380
|
+
runArticleAiAnalysisMigrations(_db);
|
|
270
381
|
}
|
|
271
382
|
return _db;
|
|
272
383
|
}
|
package/src/features/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { watchlistTools } from "./watchlist/tools.js";
|
|
2
2
|
import { profileLibraryTools } from "./profileLibrary/tools.js";
|
|
3
3
|
import { noteTools } from "./notes/tools.js";
|
|
4
|
+
import { stockAnalysisTools } from "./stockAnalysis/tools.js";
|
|
5
|
+
import { pluginInfoTools } from "./pluginInfo/tools.js";
|
|
4
6
|
|
|
5
7
|
export interface RuntimeTool {
|
|
6
8
|
name: string;
|
|
@@ -14,5 +16,7 @@ export interface RuntimeTool {
|
|
|
14
16
|
export const allFeaturesTools: Record<string, RuntimeTool> = {
|
|
15
17
|
...watchlistTools,
|
|
16
18
|
...profileLibraryTools,
|
|
17
|
-
...noteTools
|
|
19
|
+
...noteTools,
|
|
20
|
+
...stockAnalysisTools,
|
|
21
|
+
...pluginInfoTools
|
|
18
22
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { z } from "openclaw/plugin-sdk/zod";
|
|
5
|
+
|
|
6
|
+
interface RuntimeTool {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
parameters: unknown;
|
|
10
|
+
registerTool?: boolean;
|
|
11
|
+
execute(params: unknown, ctx: { userId: string }): Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const GetPluginVersionParamsSchema = z.object({}).nullish();
|
|
15
|
+
|
|
16
|
+
let cachedPluginVersion: string | null = null;
|
|
17
|
+
|
|
18
|
+
function findPackageJsonPath(startDir: string): string | null {
|
|
19
|
+
let currentDir = startDir;
|
|
20
|
+
|
|
21
|
+
while (true) {
|
|
22
|
+
const candidate = path.join(currentDir, "package.json");
|
|
23
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
24
|
+
|
|
25
|
+
const parentDir = path.dirname(currentDir);
|
|
26
|
+
if (parentDir === currentDir) return null;
|
|
27
|
+
currentDir = parentDir;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getPluginVersion(): string {
|
|
32
|
+
if (cachedPluginVersion) return cachedPluginVersion;
|
|
33
|
+
|
|
34
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
const packageJsonPath = findPackageJsonPath(moduleDir);
|
|
36
|
+
if (!packageJsonPath) {
|
|
37
|
+
throw new Error("无法找到插件 package.json");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as { version?: unknown };
|
|
41
|
+
if (typeof packageJson.version !== "string" || !packageJson.version.trim()) {
|
|
42
|
+
throw new Error("插件 package.json 缺少有效版本号");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
cachedPluginVersion = packageJson.version.trim();
|
|
46
|
+
return cachedPluginVersion;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const pluginInfoTools: Record<string, RuntimeTool> = {
|
|
50
|
+
get_plugin_version: {
|
|
51
|
+
name: "get_plugin_version",
|
|
52
|
+
description: "获取当前 Hedgehog 插件版本号",
|
|
53
|
+
parameters: GetPluginVersionParamsSchema,
|
|
54
|
+
registerTool: false,
|
|
55
|
+
async execute(params: unknown) {
|
|
56
|
+
GetPluginVersionParamsSchema.parse(params);
|
|
57
|
+
return JSON.stringify({
|
|
58
|
+
success: true,
|
|
59
|
+
version: getPluginVersion()
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from "openclaw/plugin-sdk/zod";
|
|
2
|
+
|
|
3
|
+
export const GetStockAiAnalysisParamsSchema = z.object({
|
|
4
|
+
stockCode: z.string().trim().min(1).describe("股票代码"),
|
|
5
|
+
market: z.string().trim().min(1).default("CN").describe("市场类型,默认 CN")
|
|
6
|
+
});
|
|
7
|
+
export type GetStockAiAnalysisParams = z.infer<typeof GetStockAiAnalysisParamsSchema>;
|
|
8
|
+
|
|
9
|
+
export const QueryStockAiAnalysisHistoryParamsSchema = z.object({
|
|
10
|
+
stockCode: z.string().trim().min(1).describe("股票代码"),
|
|
11
|
+
market: z.string().trim().min(1).default("CN").describe("市场类型,默认 CN"),
|
|
12
|
+
page: z.number().int().min(1).default(1).describe("页码"),
|
|
13
|
+
pageSize: z.number().int().min(1).max(50).default(20).describe("每页数量")
|
|
14
|
+
});
|
|
15
|
+
export type QueryStockAiAnalysisHistoryParams = z.infer<typeof QueryStockAiAnalysisHistoryParamsSchema>;
|
|
16
|
+
|
|
17
|
+
export const SaveStockAiAnalysisParamsSchema = z.object({
|
|
18
|
+
stockCode: z.string().trim().min(1).describe("股票代码"),
|
|
19
|
+
stockName: z.string().trim().min(1).describe("股票名称"),
|
|
20
|
+
market: z.string().trim().min(1).default("CN").describe("市场类型,默认 CN"),
|
|
21
|
+
content: z.string().trim().min(1).describe("AI 分析内容")
|
|
22
|
+
});
|
|
23
|
+
export type SaveStockAiAnalysisParams = z.infer<typeof SaveStockAiAnalysisParamsSchema>;
|
|
24
|
+
|
|
25
|
+
export interface StockAiAnalysis {
|
|
26
|
+
id: string;
|
|
27
|
+
stockCode: string;
|
|
28
|
+
stockName: string;
|
|
29
|
+
market: string;
|
|
30
|
+
content: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const ArticleAiAnalysisKindSchema = z.enum(["verification", "deduction"]);
|
|
36
|
+
|
|
37
|
+
export const GetArticleAiAnalysisParamsSchema = z.object({
|
|
38
|
+
id: z.string().trim().min(1).describe("文章来源 ID,例如 news-5、report-5、announce-5"),
|
|
39
|
+
analysisType: ArticleAiAnalysisKindSchema.describe("分析类型:verification 信息求证,deduction 深度推演"),
|
|
40
|
+
market: z.string().trim().min(1).default("CN").describe("市场类型,默认 CN")
|
|
41
|
+
});
|
|
42
|
+
export type GetArticleAiAnalysisParams = z.infer<typeof GetArticleAiAnalysisParamsSchema>;
|
|
43
|
+
|
|
44
|
+
export const SaveArticleAiAnalysisParamsSchema = z.object({
|
|
45
|
+
id: z.string().trim().min(1).describe("文章来源 ID,例如 news-5、report-5、announce-5"),
|
|
46
|
+
analysisType: ArticleAiAnalysisKindSchema.describe("分析类型:verification 信息求证,deduction 深度推演"),
|
|
47
|
+
market: z.string().trim().min(1).default("CN").describe("市场类型,默认 CN"),
|
|
48
|
+
content: z.string().trim().min(1).describe("AI 分析内容")
|
|
49
|
+
});
|
|
50
|
+
export type SaveArticleAiAnalysisParams = z.infer<typeof SaveArticleAiAnalysisParamsSchema>;
|
|
51
|
+
|
|
52
|
+
export interface ArticleAiAnalysis {
|
|
53
|
+
id: string;
|
|
54
|
+
sourceId: string;
|
|
55
|
+
analysisType: GetArticleAiAnalysisParams["analysisType"];
|
|
56
|
+
market: string;
|
|
57
|
+
content: string;
|
|
58
|
+
createdAt: string;
|
|
59
|
+
updatedAt: string;
|
|
60
|
+
}
|