@hedgehog-finance/hedgehog-plugin 1.0.12 → 1.0.13
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 +10 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/src/channel.d.ts +6 -0
- package/dist/src/channel.js +620 -0
- package/dist/src/channel.js.map +1 -0
- package/dist/src/core/database.d.ts +2 -0
- package/dist/src/core/database.js +220 -0
- package/dist/src/core/database.js.map +1 -0
- package/dist/src/core/logger.d.ts +3 -0
- package/dist/src/core/logger.js +20 -0
- package/dist/src/core/logger.js.map +1 -0
- package/dist/src/features/index.d.ts +22 -0
- package/dist/src/features/index.js +8 -0
- package/dist/src/features/index.js.map +1 -0
- package/dist/src/features/watchlist/logic.d.ts +48 -0
- package/dist/src/features/watchlist/logic.js +607 -0
- package/dist/src/features/watchlist/logic.js.map +1 -0
- package/dist/src/features/watchlist/schema.d.ts +85 -0
- package/dist/src/features/watchlist/schema.js +29 -0
- package/dist/src/features/watchlist/schema.js.map +1 -0
- package/dist/src/features/watchlist/store.d.ts +1 -0
- package/dist/src/features/watchlist/store.js +2 -0
- package/dist/src/features/watchlist/store.js.map +1 -0
- package/dist/src/features/watchlist/tools.d.ts +135 -0
- package/dist/src/features/watchlist/tools.js +572 -0
- package/dist/src/features/watchlist/tools.js.map +1 -0
- package/dist/src/runtime.d.ts +5 -0
- package/dist/src/runtime.js +40 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/types.d.ts +99 -0
- package/dist/src/types.js +16 -0
- package/dist/src/types.js.map +1 -0
- package/index.ts +4 -4
- package/package.json +23 -6
- package/src/channel.ts +26 -4
- package/src/core/database.ts +90 -3
- package/src/features/index.ts +2 -1
- package/src/features/watchlist/logic.ts +503 -128
- package/src/features/watchlist/schema.ts +1 -6
- package/src/features/watchlist/tools.ts +248 -103
- package/src/runtime.ts +3 -3
- package/src/types.ts +1 -1
- package/tsconfig.json +0 -16
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,IAAI,OAAO,GAAyB,IAAI,CAAC;AACzC,IAAI,MAAM,GAAW,EAAE,CAAC;AACxB,IAAI,SAAS,GAAW,EAAE,CAAC;AAE3B,MAAM,UAAU,kBAAkB,CAAC,IAAmB;IACrD,OAAO,GAAG,IAAI,CAAC;IAEf,IAAI,CAAC;QACJ,sCAAsC;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAyC,CAAC;QACnF,MAAM,aAAa,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,kBAAkB,CAAC,CAAC;QACzE,MAAM,YAAY,GAAG,aAAa,EAAE,SAAS;YAC5C,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS;YAC/B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,oBAAoB,CAAC,CAAC;QAE5D,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;QACxD,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,oBAAoB,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACZ,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,6BAA6B,CAAC,CAAC;IACzD,CAAC;AACF,CAAC;AAED,MAAM,UAAU,SAAS;IACxB,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IACtE,OAAO,MAAM,CAAC;AACf,CAAC;AAED,MAAM,UAAU,YAAY;IAC3B,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC5E,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,kBAAkB;IACjC,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACxE,OAAO,OAAO,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Hedgehog Finance Resolved Account
|
|
4
|
+
*/
|
|
5
|
+
export interface HedgehogFinanceResolvedAccount {
|
|
6
|
+
accountId: string;
|
|
7
|
+
config: {
|
|
8
|
+
token: string;
|
|
9
|
+
code: string;
|
|
10
|
+
};
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
configured: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Inbound Message from Relay (Manual Handling)
|
|
16
|
+
*/
|
|
17
|
+
export interface RelayInboundMessage {
|
|
18
|
+
type: "req" | "reply" | "item_event" | "usage" | "model" | "reasoning";
|
|
19
|
+
from: string;
|
|
20
|
+
chatId: string;
|
|
21
|
+
id: string;
|
|
22
|
+
text?: string;
|
|
23
|
+
method?: string;
|
|
24
|
+
params?: any;
|
|
25
|
+
replyTo?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Session entry structure in sessions.json
|
|
29
|
+
*/
|
|
30
|
+
export interface OpenClawSessionEntry {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
inputTokens?: number;
|
|
33
|
+
outputTokens?: number;
|
|
34
|
+
totalTokens?: number;
|
|
35
|
+
cacheRead?: number;
|
|
36
|
+
estimatedCostUsd?: number;
|
|
37
|
+
model?: string;
|
|
38
|
+
modelProvider?: string;
|
|
39
|
+
updatedAt?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Normalized Usage for UI and Internal logic
|
|
43
|
+
*/
|
|
44
|
+
export interface TurnUsage {
|
|
45
|
+
input: number;
|
|
46
|
+
output: number;
|
|
47
|
+
total: number;
|
|
48
|
+
cacheRead: number;
|
|
49
|
+
cost: number;
|
|
50
|
+
model: string;
|
|
51
|
+
provider: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Stock Classification Result (AI Schema)
|
|
55
|
+
*/
|
|
56
|
+
export declare const StockClassificationSchema: z.ZodObject<{
|
|
57
|
+
industry: z.ZodObject<{
|
|
58
|
+
name: z.ZodString;
|
|
59
|
+
weight: z.ZodDefault<z.ZodNumber>;
|
|
60
|
+
}, "strip", z.ZodTypeAny, {
|
|
61
|
+
name: string;
|
|
62
|
+
weight: number;
|
|
63
|
+
}, {
|
|
64
|
+
name: string;
|
|
65
|
+
weight?: number | undefined;
|
|
66
|
+
}>;
|
|
67
|
+
theme: z.ZodArray<z.ZodObject<{
|
|
68
|
+
name: z.ZodString;
|
|
69
|
+
weight: z.ZodDefault<z.ZodNumber>;
|
|
70
|
+
}, "strip", z.ZodTypeAny, {
|
|
71
|
+
name: string;
|
|
72
|
+
weight: number;
|
|
73
|
+
}, {
|
|
74
|
+
name: string;
|
|
75
|
+
weight?: number | undefined;
|
|
76
|
+
}>, "many">;
|
|
77
|
+
weight: z.ZodDefault<z.ZodNumber>;
|
|
78
|
+
}, "strip", z.ZodTypeAny, {
|
|
79
|
+
weight: number;
|
|
80
|
+
industry: {
|
|
81
|
+
name: string;
|
|
82
|
+
weight: number;
|
|
83
|
+
};
|
|
84
|
+
theme: {
|
|
85
|
+
name: string;
|
|
86
|
+
weight: number;
|
|
87
|
+
}[];
|
|
88
|
+
}, {
|
|
89
|
+
industry: {
|
|
90
|
+
name: string;
|
|
91
|
+
weight?: number | undefined;
|
|
92
|
+
};
|
|
93
|
+
theme: {
|
|
94
|
+
name: string;
|
|
95
|
+
weight?: number | undefined;
|
|
96
|
+
}[];
|
|
97
|
+
weight?: number | undefined;
|
|
98
|
+
}>;
|
|
99
|
+
export type StockClassification = z.infer<typeof StockClassificationSchema>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Stock Classification Result (AI Schema)
|
|
4
|
+
*/
|
|
5
|
+
export const StockClassificationSchema = z.object({
|
|
6
|
+
industry: z.object({
|
|
7
|
+
name: z.string(),
|
|
8
|
+
weight: z.number().min(0).max(100).default(50)
|
|
9
|
+
}).describe("Required main industry category with weight"),
|
|
10
|
+
theme: z.array(z.object({
|
|
11
|
+
name: z.string(),
|
|
12
|
+
weight: z.number().min(0).max(100).default(50)
|
|
13
|
+
})).describe("Thematic categories with weights"),
|
|
14
|
+
weight: z.number().min(0).max(100).default(50).describe("Overall priority weight")
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAyDxB;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;KACjD,CAAC,CAAC,QAAQ,CAAC,6CAA6C,CAAC;IAC1D,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;QACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;KACjD,CAAC,CAAC,CAAC,QAAQ,CAAC,kCAAkC,CAAC;IAChD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,yBAAyB,CAAC;CACrF,CAAC,CAAC"}
|
package/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
|
|
2
|
-
import { hedgehogFinancePlugin } from "./src/channel";
|
|
3
|
-
import { setHedgehogRuntime } from "./src/runtime";
|
|
4
|
-
import { allFeaturesTools } from "./src/features";
|
|
2
|
+
import { hedgehogFinancePlugin } from "./src/channel.js";
|
|
3
|
+
import { setHedgehogRuntime } from "./src/runtime.js";
|
|
4
|
+
import { allFeaturesTools } from "./src/features/index.js";
|
|
5
5
|
|
|
6
6
|
export default defineChannelPluginEntry({
|
|
7
7
|
id: "hedgehog_finance",
|
|
@@ -14,6 +14,7 @@ export default defineChannelPluginEntry({
|
|
|
14
14
|
registerFull(api) {
|
|
15
15
|
// 1. 自动化循环注册 Tool
|
|
16
16
|
Object.entries(allFeaturesTools).forEach(([name, tool]) => {
|
|
17
|
+
if (tool.registerTool === false) return;
|
|
17
18
|
const registerable = { ...tool, label: tool.description };
|
|
18
19
|
api.registerTool(registerable as any, { name });
|
|
19
20
|
});
|
|
@@ -21,4 +22,3 @@ export default defineChannelPluginEntry({
|
|
|
21
22
|
api.logger.info("[hedgehog-app] Registered tools and runtime context.");
|
|
22
23
|
},
|
|
23
24
|
});
|
|
24
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hedgehog-finance/hedgehog-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "Hedgehog App WebSocket channel and Watchlist Tools for OpenClaw",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bot",
|
|
@@ -11,11 +11,25 @@
|
|
|
11
11
|
"hedgehog-app"
|
|
12
12
|
],
|
|
13
13
|
"repository": "github:hedgehog-finance/hedgehog-plugin",
|
|
14
|
-
"main": "index.
|
|
14
|
+
"main": "dist/index.js",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"author": "Hedgehog Finance",
|
|
17
17
|
"license": "MIT",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist/**/*.js",
|
|
20
|
+
"dist/**/*.js.map",
|
|
21
|
+
"dist/**/*.d.ts",
|
|
22
|
+
"index.ts",
|
|
23
|
+
"src/**/*.ts",
|
|
24
|
+
"openclaw.plugin.json",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.json",
|
|
29
|
+
"prepack": "npm run build"
|
|
30
|
+
},
|
|
18
31
|
"dependencies": {
|
|
32
|
+
"@mariozechner/pi-ai": "^0.66.1",
|
|
19
33
|
"pino": "^9.0.0",
|
|
20
34
|
"pino-pretty": "^11.1.0",
|
|
21
35
|
"ws": "^8.20.0",
|
|
@@ -31,17 +45,20 @@
|
|
|
31
45
|
"extensions": [
|
|
32
46
|
"./index.ts"
|
|
33
47
|
],
|
|
48
|
+
"runtimeExtensions": [
|
|
49
|
+
"./dist/index.js"
|
|
50
|
+
],
|
|
34
51
|
"install": {
|
|
35
52
|
"npmSpec": "@hedgehog-finance/hedgehog-plugin",
|
|
36
53
|
"defaultChoice": "npm"
|
|
37
54
|
},
|
|
38
55
|
"compat": {
|
|
39
|
-
"pluginApi": ">=2026.4
|
|
40
|
-
"minGatewayVersion": "2026.4
|
|
56
|
+
"pluginApi": ">=2026.5.4",
|
|
57
|
+
"minGatewayVersion": "2026.5.4"
|
|
41
58
|
},
|
|
42
59
|
"build": {
|
|
43
|
-
"openclawVersion": "2026.4
|
|
44
|
-
"pluginSdkVersion": "2026.4
|
|
60
|
+
"openclawVersion": "2026.5.4",
|
|
61
|
+
"pluginSdkVersion": "2026.5.4"
|
|
45
62
|
}
|
|
46
63
|
}
|
|
47
64
|
}
|
package/src/channel.ts
CHANGED
|
@@ -11,13 +11,13 @@ import type {
|
|
|
11
11
|
ChannelAccountSnapshot,
|
|
12
12
|
ChannelStatusIssue
|
|
13
13
|
} from "openclaw/plugin-sdk/channel-contract";
|
|
14
|
-
import { getHedgehogRuntime } from "./runtime";
|
|
15
|
-
import { logger } from "./core/logger";
|
|
14
|
+
import { getHedgehogRuntime } from "./runtime.js";
|
|
15
|
+
import { logger } from "./core/logger.js";
|
|
16
16
|
import type {
|
|
17
17
|
HedgehogFinanceResolvedAccount,
|
|
18
18
|
RelayInboundMessage
|
|
19
|
-
} from "./types";
|
|
20
|
-
import { allFeaturesTools } from "./features";
|
|
19
|
+
} from "./types.js";
|
|
20
|
+
import { allFeaturesTools } from "./features/index.js";
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
|
|
@@ -346,6 +346,18 @@ export const hedgehogFinancePlugin: ChannelPlugin<HedgehogFinanceResolvedAccount
|
|
|
346
346
|
if (!method) return;
|
|
347
347
|
|
|
348
348
|
// 从中央工具注册表中查找方法
|
|
349
|
+
if (method === "ping") {
|
|
350
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
351
|
+
ws.send(JSON.stringify({
|
|
352
|
+
type: "res",
|
|
353
|
+
id: id,
|
|
354
|
+
ok: true,
|
|
355
|
+
payload: { success: true }
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
349
361
|
const tool = allFeaturesTools[method];
|
|
350
362
|
|
|
351
363
|
if (tool && typeof tool.execute === 'function') {
|
|
@@ -386,6 +398,16 @@ export const hedgehogFinancePlugin: ChannelPlugin<HedgehogFinanceResolvedAccount
|
|
|
386
398
|
}
|
|
387
399
|
return;
|
|
388
400
|
}
|
|
401
|
+
|
|
402
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
403
|
+
ws.send(JSON.stringify({
|
|
404
|
+
type: "res",
|
|
405
|
+
id: id,
|
|
406
|
+
ok: false,
|
|
407
|
+
error: { message: `Unknown RPC method: ${method}` }
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
return;
|
|
389
411
|
}
|
|
390
412
|
|
|
391
413
|
const { from, text, chatId, id } = appPayload;
|
package/src/core/database.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import { DatabaseSync } from 'node:sqlite';
|
|
4
4
|
import { mkdirSync, existsSync, copyFileSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { getDbPath, getBackupDir } from "../runtime";
|
|
7
|
-
import { logger } from "./logger";
|
|
6
|
+
import { getDbPath, getBackupDir } from "../runtime.js";
|
|
7
|
+
import { logger } from "./logger.js";
|
|
8
8
|
|
|
9
9
|
let _db: any = null;
|
|
10
10
|
let _backupJobStarted = false;
|
|
@@ -59,6 +59,92 @@ function startDailyBackupJob() {
|
|
|
59
59
|
scheduleNextBackup();
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function normalizeMetadataStockCode(stockCode: string, exchange: string): string {
|
|
63
|
+
const code = String(stockCode || "").trim().toUpperCase();
|
|
64
|
+
if (/\.(SH|SS|SZ|HK|US)$/i.test(code)) {
|
|
65
|
+
return code.replace(/\.SS$/i, ".SH");
|
|
66
|
+
}
|
|
67
|
+
switch (exchange) {
|
|
68
|
+
case "SSE":
|
|
69
|
+
return `${code}.SH`;
|
|
70
|
+
case "SZSE":
|
|
71
|
+
return `${code}.SZ`;
|
|
72
|
+
case "HKEX":
|
|
73
|
+
return `${code}.HK`;
|
|
74
|
+
default:
|
|
75
|
+
return code;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function runWatchlistDedupMigrations(db: DatabaseSync) {
|
|
80
|
+
db.exec("BEGIN TRANSACTION");
|
|
81
|
+
try {
|
|
82
|
+
const metadataRows = db.prepare(`
|
|
83
|
+
SELECT stockCode, exchange FROM global_stock_metadata
|
|
84
|
+
`).all() as { stockCode: string; exchange: string }[];
|
|
85
|
+
const metadataDeleteStmt = db.prepare(`
|
|
86
|
+
DELETE FROM global_stock_metadata WHERE stockCode = ? AND exchange = ?
|
|
87
|
+
`);
|
|
88
|
+
const metadataUpdateStmt = db.prepare(`
|
|
89
|
+
UPDATE global_stock_metadata SET stockCode = ? WHERE stockCode = ? AND exchange = ?
|
|
90
|
+
`);
|
|
91
|
+
const metadataExistsStmt = db.prepare(`
|
|
92
|
+
SELECT 1 FROM global_stock_metadata WHERE stockCode = ? AND exchange = ?
|
|
93
|
+
`);
|
|
94
|
+
for (const row of metadataRows) {
|
|
95
|
+
const normalizedCode = normalizeMetadataStockCode(row.stockCode, row.exchange);
|
|
96
|
+
if (!normalizedCode || normalizedCode === row.stockCode) continue;
|
|
97
|
+
const existing = metadataExistsStmt.get(normalizedCode, row.exchange);
|
|
98
|
+
if (existing) {
|
|
99
|
+
metadataDeleteStmt.run(row.stockCode, row.exchange);
|
|
100
|
+
} else {
|
|
101
|
+
metadataUpdateStmt.run(normalizedCode, row.stockCode, row.exchange);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const duplicateCategories = db.prepare(`
|
|
106
|
+
SELECT userId, name, type, MIN(id) AS keepId, GROUP_CONCAT(id) AS ids
|
|
107
|
+
FROM watchlist_categories
|
|
108
|
+
GROUP BY userId, name, type
|
|
109
|
+
HAVING COUNT(*) > 1
|
|
110
|
+
`).all() as { userId: string; name: string; type: 'industry' | 'theme'; keepId: string; ids: string }[];
|
|
111
|
+
for (const dup of duplicateCategories) {
|
|
112
|
+
const table = dup.type === 'industry' ? 'watchlist_industry_items' : 'watchlist_theme_items';
|
|
113
|
+
const ids = dup.ids.split(",").filter(id => id && id !== dup.keepId);
|
|
114
|
+
for (const oldId of ids) {
|
|
115
|
+
db.prepare(`UPDATE ${table} SET categoryId = ? WHERE userId = ? AND categoryId = ?`).run(dup.keepId, dup.userId, oldId);
|
|
116
|
+
db.prepare("DELETE FROM watchlist_categories WHERE id = ?").run(oldId);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
db.exec(`
|
|
121
|
+
DELETE FROM watchlist_industry_items
|
|
122
|
+
WHERE rowid NOT IN (
|
|
123
|
+
SELECT MIN(rowid) FROM watchlist_industry_items GROUP BY watchlistId, categoryId
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
DELETE FROM watchlist_theme_items
|
|
127
|
+
WHERE rowid NOT IN (
|
|
128
|
+
SELECT MIN(rowid) FROM watchlist_theme_items GROUP BY watchlistId, categoryId
|
|
129
|
+
);
|
|
130
|
+
`);
|
|
131
|
+
|
|
132
|
+
db.exec("COMMIT");
|
|
133
|
+
} catch (e) {
|
|
134
|
+
if (db.inTransaction) db.exec("ROLLBACK");
|
|
135
|
+
throw e;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
db.exec(`
|
|
139
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_categories_user_name_type
|
|
140
|
+
ON watchlist_categories(userId, name, type);
|
|
141
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_industry_items_watchlist_category
|
|
142
|
+
ON watchlist_industry_items(watchlistId, categoryId);
|
|
143
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_theme_items_watchlist_category
|
|
144
|
+
ON watchlist_theme_items(watchlistId, categoryId);
|
|
145
|
+
`);
|
|
146
|
+
}
|
|
147
|
+
|
|
62
148
|
export function getDB(): DatabaseSync {
|
|
63
149
|
if (!_db) {
|
|
64
150
|
const dbPath = getDbPath();
|
|
@@ -141,6 +227,7 @@ export function getDB(): DatabaseSync {
|
|
|
141
227
|
CREATE INDEX IF NOT EXISTS idx_watchlist_theme_user ON watchlist_theme_items(userId);
|
|
142
228
|
`);
|
|
143
229
|
|
|
230
|
+
runWatchlistDedupMigrations(_db);
|
|
144
231
|
}
|
|
145
232
|
return _db;
|
|
146
|
-
}
|
|
233
|
+
}
|
package/src/features/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { watchlistTools } from "./watchlist/tools";
|
|
1
|
+
import { watchlistTools } from "./watchlist/tools.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Runtime tool shape used for dynamic RPC dispatch and registerTool.
|
|
@@ -13,6 +13,7 @@ export interface RuntimeTool {
|
|
|
13
13
|
label?: string;
|
|
14
14
|
description: string;
|
|
15
15
|
parameters: unknown;
|
|
16
|
+
registerTool?: boolean;
|
|
16
17
|
// bivariant method signature — allows specific param types
|
|
17
18
|
execute(params: unknown, ctx: { userId: string }): Promise<string>;
|
|
18
19
|
}
|