tickflow-assist 0.2.18 → 0.3.1

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 (37) hide show
  1. package/README.md +13 -6
  2. package/dist/analysis/parsers/flash-alert-decision.parser.d.ts +8 -0
  3. package/dist/analysis/parsers/flash-alert-decision.parser.js +34 -0
  4. package/dist/background/jin10-flash.worker.d.ts +8 -0
  5. package/dist/background/jin10-flash.worker.js +24 -0
  6. package/dist/bootstrap.d.ts +4 -0
  7. package/dist/bootstrap.js +17 -0
  8. package/dist/config/normalize.js +15 -6
  9. package/dist/config/schema.d.ts +5 -1
  10. package/dist/config/schema.js +3 -0
  11. package/dist/dev/run-monitor-loop.js +6 -2
  12. package/dist/dev/tickflow-assist-cli.js +45 -0
  13. package/dist/plugin-commands.js +7 -0
  14. package/dist/prompts/analysis/flash-monitor-alert-prompt.d.ts +11 -0
  15. package/dist/prompts/analysis/flash-monitor-alert-prompt.js +44 -0
  16. package/dist/prompts/analysis/index.d.ts +1 -0
  17. package/dist/prompts/analysis/index.js +1 -0
  18. package/dist/services/alert-service.d.ts +1 -0
  19. package/dist/services/alert-service.js +21 -3
  20. package/dist/services/jin10-flash-monitor-service.d.ts +32 -0
  21. package/dist/services/jin10-flash-monitor-service.js +578 -0
  22. package/dist/services/jin10-mcp-service.d.ts +29 -0
  23. package/dist/services/jin10-mcp-service.js +242 -0
  24. package/dist/storage/repositories/jin10-flash-delivery-repo.d.ts +10 -0
  25. package/dist/storage/repositories/jin10-flash-delivery-repo.js +57 -0
  26. package/dist/storage/repositories/jin10-flash-repo.d.ts +15 -0
  27. package/dist/storage/repositories/jin10-flash-repo.js +126 -0
  28. package/dist/storage/schemas.d.ts +2 -0
  29. package/dist/storage/schemas.js +19 -0
  30. package/dist/tools/flash-monitor-status.tool.d.ts +6 -0
  31. package/dist/tools/flash-monitor-status.tool.js +9 -0
  32. package/dist/types/flash-monitor.d.ts +17 -0
  33. package/dist/types/flash-monitor.js +1 -0
  34. package/dist/types/jin10.d.ts +30 -0
  35. package/dist/types/jin10.js +1 -0
  36. package/openclaw.plugin.json +43 -1
  37. package/package.json +15 -7
@@ -0,0 +1,242 @@
1
+ export class Jin10McpService {
2
+ serverUrl;
3
+ apiToken;
4
+ requestId = 1;
5
+ initialized = false;
6
+ sessionId = null;
7
+ constructor(serverUrl, apiToken) {
8
+ this.serverUrl = serverUrl;
9
+ this.apiToken = apiToken;
10
+ }
11
+ isConfigured() {
12
+ return this.getConfigurationError() == null;
13
+ }
14
+ getConfigurationError() {
15
+ if (!this.serverUrl.trim()) {
16
+ return "Jin10 MCP 未配置接口地址,请设置 jin10McpUrl";
17
+ }
18
+ if (!this.apiToken.trim()) {
19
+ return "Jin10 MCP 未配置 API Token,请设置 jin10ApiToken";
20
+ }
21
+ return null;
22
+ }
23
+ async listFlash(cursor) {
24
+ const payload = await this.callTool("list_flash", cursor ? { cursor } : {});
25
+ return normalizeFlashPage(payload);
26
+ }
27
+ async initialize() {
28
+ if (this.initialized) {
29
+ return;
30
+ }
31
+ await this.request("initialize", {
32
+ protocolVersion: "2025-11-25",
33
+ capabilities: {},
34
+ clientInfo: {
35
+ name: "mcp-client",
36
+ version: "1.0.0",
37
+ },
38
+ });
39
+ await this.notify("notifications/initialized");
40
+ try {
41
+ await this.request("tools/list", {});
42
+ await this.request("resources/list", {});
43
+ }
44
+ catch {
45
+ // Tool listing is a best-effort handshake step.
46
+ }
47
+ this.initialized = true;
48
+ }
49
+ async callTool(name, args) {
50
+ await this.initialize();
51
+ const result = await this.request("tools/call", {
52
+ name,
53
+ arguments: args,
54
+ });
55
+ if (!result) {
56
+ throw new Error(`jin10 tool ${name} returned empty result`);
57
+ }
58
+ if (result.isError) {
59
+ throw new Error(`jin10 tool ${name} returned MCP error`);
60
+ }
61
+ if (result.structuredContent !== undefined) {
62
+ return result.structuredContent;
63
+ }
64
+ const structured = result.content?.find((item) => item.structuredContent !== undefined)?.structuredContent;
65
+ if (structured !== undefined) {
66
+ return structured;
67
+ }
68
+ const text = result.content?.find((item) => typeof item.text === "string")?.text;
69
+ if (typeof text === "string" && text.trim()) {
70
+ try {
71
+ return JSON.parse(text);
72
+ }
73
+ catch {
74
+ return text;
75
+ }
76
+ }
77
+ return result;
78
+ }
79
+ async request(method, params) {
80
+ const configError = this.getConfigurationError();
81
+ if (configError) {
82
+ throw new Error(configError);
83
+ }
84
+ const payload = {
85
+ jsonrpc: "2.0",
86
+ id: this.requestId++,
87
+ method,
88
+ params,
89
+ };
90
+ const response = await fetch(this.serverUrl, {
91
+ method: "POST",
92
+ headers: this.buildHeaders(),
93
+ body: JSON.stringify(payload),
94
+ });
95
+ const sessionId = response.headers.get("mcp-session-id");
96
+ if (sessionId) {
97
+ this.sessionId = sessionId;
98
+ }
99
+ const rawText = await response.text();
100
+ if (!response.ok) {
101
+ throw new Error(`jin10 MCP request failed: ${response.status} ${response.statusText} ${rawText}`);
102
+ }
103
+ const parsed = parseJsonRpcResponse(rawText, payload.id);
104
+ if (parsed.error) {
105
+ throw new Error(`jin10 MCP error (${parsed.error.code ?? "unknown"}): ${parsed.error.message ?? "unknown"}`);
106
+ }
107
+ return parsed.result;
108
+ }
109
+ async notify(method) {
110
+ const configError = this.getConfigurationError();
111
+ if (configError) {
112
+ throw new Error(configError);
113
+ }
114
+ const payload = {
115
+ jsonrpc: "2.0",
116
+ method,
117
+ };
118
+ const response = await fetch(this.serverUrl, {
119
+ method: "POST",
120
+ headers: this.buildHeaders(),
121
+ body: JSON.stringify(payload),
122
+ });
123
+ const sessionId = response.headers.get("mcp-session-id");
124
+ if (sessionId) {
125
+ this.sessionId = sessionId;
126
+ }
127
+ if (!response.ok) {
128
+ const text = await response.text();
129
+ throw new Error(`jin10 MCP notification failed: ${response.status} ${response.statusText} ${text}`);
130
+ }
131
+ }
132
+ buildHeaders() {
133
+ const headers = {
134
+ "Content-Type": "application/json",
135
+ Authorization: `Bearer ${this.apiToken}`,
136
+ };
137
+ if (this.sessionId) {
138
+ headers["Mcp-Session-Id"] = this.sessionId;
139
+ }
140
+ return headers;
141
+ }
142
+ }
143
+ export function parseJsonRpcResponse(rawText, expectedId) {
144
+ const trimmed = rawText.trim();
145
+ if (!trimmed) {
146
+ throw new Error("jin10 MCP returned empty body");
147
+ }
148
+ if (!looksLikeSsePayload(trimmed)) {
149
+ return JSON.parse(trimmed);
150
+ }
151
+ const candidates = parseSseJsonRpcResponses(trimmed);
152
+ if (candidates.length === 0) {
153
+ throw new Error(`jin10 MCP SSE payload missing JSON-RPC data: ${truncate(trimmed, 160)}`);
154
+ }
155
+ if (expectedId !== undefined) {
156
+ const matched = candidates.find((entry) => entry.id === expectedId);
157
+ if (matched) {
158
+ return matched;
159
+ }
160
+ }
161
+ const withResult = candidates.find((entry) => entry.result !== undefined || entry.error !== undefined);
162
+ if (withResult) {
163
+ return withResult;
164
+ }
165
+ return candidates[candidates.length - 1];
166
+ }
167
+ function parseSseJsonRpcResponses(rawText) {
168
+ const events = rawText
169
+ .split(/\r?\n\r?\n/)
170
+ .map((chunk) => chunk.trim())
171
+ .filter(Boolean);
172
+ const responses = [];
173
+ for (const eventText of events) {
174
+ const payload = extractSseData(eventText);
175
+ if (!payload || payload === "[DONE]") {
176
+ continue;
177
+ }
178
+ try {
179
+ responses.push(JSON.parse(payload));
180
+ }
181
+ catch {
182
+ continue;
183
+ }
184
+ }
185
+ return responses;
186
+ }
187
+ function extractSseData(eventText) {
188
+ const dataLines = eventText
189
+ .split(/\r?\n/)
190
+ .filter((line) => line.startsWith("data:"))
191
+ .map((line) => line.replace(/^data:\s?/, ""));
192
+ return dataLines.join("\n").trim();
193
+ }
194
+ function looksLikeSsePayload(value) {
195
+ return value.startsWith("data:")
196
+ || value.startsWith("event:")
197
+ || value.startsWith(":")
198
+ || /\r?\ndata:/.test(value)
199
+ || /\r?\nevent:/.test(value);
200
+ }
201
+ function truncate(value, maxLength) {
202
+ if (value.length <= maxLength) {
203
+ return value;
204
+ }
205
+ return `${value.slice(0, Math.max(0, maxLength - 3))}...`;
206
+ }
207
+ function normalizeFlashPage(value) {
208
+ const root = isRecord(value) ? value : {};
209
+ const data = isRecord(root.data) ? root.data : {};
210
+ const items = Array.isArray(data.items) ? data.items : [];
211
+ return {
212
+ hasMore: data.has_more === true,
213
+ items: items
214
+ .map((item) => normalizeFlashItem(item))
215
+ .filter((item) => item != null),
216
+ nextCursor: normalizeNullableString(data.next_cursor),
217
+ };
218
+ }
219
+ function normalizeFlashItem(value) {
220
+ if (!isRecord(value)) {
221
+ return null;
222
+ }
223
+ const content = String(value.content ?? "").trim();
224
+ const time = String(value.time ?? "").trim();
225
+ const url = String(value.url ?? "").trim();
226
+ if (!content || !time || !url) {
227
+ return null;
228
+ }
229
+ return {
230
+ content,
231
+ time,
232
+ url,
233
+ raw: value,
234
+ };
235
+ }
236
+ function normalizeNullableString(value) {
237
+ const text = typeof value === "string" ? value.trim() : "";
238
+ return text || null;
239
+ }
240
+ function isRecord(value) {
241
+ return typeof value === "object" && value !== null && !Array.isArray(value);
242
+ }
@@ -0,0 +1,10 @@
1
+ import type { Jin10FlashDeliveryEntry } from "../../types/jin10.js";
2
+ import { Database } from "../db.js";
3
+ export declare class Jin10FlashDeliveryRepository {
4
+ private readonly db;
5
+ constructor(db: Database);
6
+ append(entry: Jin10FlashDeliveryEntry): Promise<void>;
7
+ hasDelivered(flashKey: string): Promise<boolean>;
8
+ countSinceDeliveredAt(deliveredAt: string): Promise<number>;
9
+ pruneOlderThanDeliveredAt(deliveredAt: string): Promise<void>;
10
+ }
@@ -0,0 +1,57 @@
1
+ import { jin10FlashDeliverySchema } from "../schemas.js";
2
+ const JIN10_FLASH_DELIVERY_TABLE = "jin10_flash_delivery";
3
+ export class Jin10FlashDeliveryRepository {
4
+ db;
5
+ constructor(db) {
6
+ this.db = db;
7
+ }
8
+ async append(entry) {
9
+ const row = toDeliveryRow(entry);
10
+ if (!(await this.db.hasTable(JIN10_FLASH_DELIVERY_TABLE))) {
11
+ await this.db.createTable(JIN10_FLASH_DELIVERY_TABLE, [row], jin10FlashDeliverySchema);
12
+ return;
13
+ }
14
+ const table = await this.db.openTable(JIN10_FLASH_DELIVERY_TABLE);
15
+ await table.add([row]);
16
+ }
17
+ async hasDelivered(flashKey) {
18
+ if (!flashKey || !(await this.db.hasTable(JIN10_FLASH_DELIVERY_TABLE))) {
19
+ return false;
20
+ }
21
+ const table = await this.db.openTable(JIN10_FLASH_DELIVERY_TABLE);
22
+ const rows = (await table
23
+ .query()
24
+ .where(`flash_key = '${escapeSqlString(flashKey)}'`)
25
+ .toArray());
26
+ return rows.length > 0;
27
+ }
28
+ async countSinceDeliveredAt(deliveredAt) {
29
+ if (!(await this.db.hasTable(JIN10_FLASH_DELIVERY_TABLE))) {
30
+ return 0;
31
+ }
32
+ const table = await this.db.openTable(JIN10_FLASH_DELIVERY_TABLE);
33
+ return table.countRows(`delivered_at >= '${escapeSqlString(deliveredAt)}'`);
34
+ }
35
+ async pruneOlderThanDeliveredAt(deliveredAt) {
36
+ if (!(await this.db.hasTable(JIN10_FLASH_DELIVERY_TABLE))) {
37
+ return;
38
+ }
39
+ const table = await this.db.openTable(JIN10_FLASH_DELIVERY_TABLE);
40
+ await table.delete(`delivered_at < '${escapeSqlString(deliveredAt)}'`);
41
+ }
42
+ }
43
+ function toDeliveryRow(entry) {
44
+ return {
45
+ flash_key: entry.flash_key,
46
+ published_at: entry.published_at,
47
+ symbols_json: JSON.stringify(entry.symbols),
48
+ headline: entry.headline,
49
+ reason: entry.reason,
50
+ importance: entry.importance,
51
+ message: entry.message,
52
+ delivered_at: entry.delivered_at,
53
+ };
54
+ }
55
+ function escapeSqlString(value) {
56
+ return value.replace(/'/g, "''");
57
+ }
@@ -0,0 +1,15 @@
1
+ import type { Jin10FlashRecord } from "../../types/jin10.js";
2
+ import { Database } from "../db.js";
3
+ export declare class Jin10FlashRepository {
4
+ private readonly db;
5
+ constructor(db: Database);
6
+ saveAll(entries: Jin10FlashRecord[]): Promise<{
7
+ added: number;
8
+ skipped: number;
9
+ addedKeys: string[];
10
+ }>;
11
+ getLatest(): Promise<Jin10FlashRecord | null>;
12
+ countSincePublishedTs(publishedTs: number): Promise<number>;
13
+ pruneOlderThanPublishedTs(publishedTs: number): Promise<void>;
14
+ private listExistingKeys;
15
+ }
@@ -0,0 +1,126 @@
1
+ import { jin10FlashSchema } from "../schemas.js";
2
+ const JIN10_FLASH_TABLE = "jin10_flash";
3
+ export class Jin10FlashRepository {
4
+ db;
5
+ constructor(db) {
6
+ this.db = db;
7
+ }
8
+ async saveAll(entries) {
9
+ const uniqueEntries = dedupeEntries(entries);
10
+ if (uniqueEntries.length === 0) {
11
+ return { added: 0, skipped: 0, addedKeys: [] };
12
+ }
13
+ const rows = uniqueEntries.map((entry) => toFlashRow(entry));
14
+ if (!(await this.db.hasTable(JIN10_FLASH_TABLE))) {
15
+ await this.db.createTable(JIN10_FLASH_TABLE, rows, jin10FlashSchema);
16
+ return {
17
+ added: rows.length,
18
+ skipped: 0,
19
+ addedKeys: uniqueEntries.map((entry) => entry.flash_key),
20
+ };
21
+ }
22
+ const existingKeys = await this.listExistingKeys(uniqueEntries.map((entry) => entry.flash_key));
23
+ const newEntries = uniqueEntries.filter((entry) => !existingKeys.has(entry.flash_key));
24
+ const newRows = newEntries.map((entry) => toFlashRow(entry));
25
+ if (newRows.length === 0) {
26
+ return {
27
+ added: 0,
28
+ skipped: rows.length,
29
+ addedKeys: [],
30
+ };
31
+ }
32
+ const table = await this.db.openTable(JIN10_FLASH_TABLE);
33
+ await table.add(newRows);
34
+ return {
35
+ added: newRows.length,
36
+ skipped: rows.length - newRows.length,
37
+ addedKeys: newEntries.map((entry) => entry.flash_key),
38
+ };
39
+ }
40
+ async getLatest() {
41
+ if (!(await this.db.hasTable(JIN10_FLASH_TABLE))) {
42
+ return null;
43
+ }
44
+ const rows = await this.db.tableToArray(JIN10_FLASH_TABLE);
45
+ if (rows.length === 0) {
46
+ return null;
47
+ }
48
+ return fromFlashRow(rows[rows.length - 1]);
49
+ }
50
+ async countSincePublishedTs(publishedTs) {
51
+ if (!(await this.db.hasTable(JIN10_FLASH_TABLE))) {
52
+ return 0;
53
+ }
54
+ const table = await this.db.openTable(JIN10_FLASH_TABLE);
55
+ return table.countRows(`published_ts >= ${Math.trunc(publishedTs)}`);
56
+ }
57
+ async pruneOlderThanPublishedTs(publishedTs) {
58
+ if (!(await this.db.hasTable(JIN10_FLASH_TABLE))) {
59
+ return;
60
+ }
61
+ const table = await this.db.openTable(JIN10_FLASH_TABLE);
62
+ await table.delete(`published_ts < ${Math.trunc(publishedTs)}`);
63
+ }
64
+ async listExistingKeys(keys) {
65
+ if (keys.length === 0 || !(await this.db.hasTable(JIN10_FLASH_TABLE))) {
66
+ return new Set();
67
+ }
68
+ const table = await this.db.openTable(JIN10_FLASH_TABLE);
69
+ const rows = (await table
70
+ .query()
71
+ .where(`flash_key IN (${keys.map((key) => `'${escapeSqlString(key)}'`).join(", ")})`)
72
+ .toArray());
73
+ return new Set(rows.map((row) => String(row.flash_key ?? "")));
74
+ }
75
+ }
76
+ function toFlashRow(entry) {
77
+ return {
78
+ flash_key: entry.flash_key,
79
+ published_at: entry.published_at,
80
+ published_ts: Math.trunc(entry.published_ts),
81
+ content: entry.content,
82
+ url: entry.url,
83
+ ingested_at: entry.ingested_at,
84
+ raw_json: JSON.stringify(entry.raw),
85
+ };
86
+ }
87
+ function fromFlashRow(row) {
88
+ return {
89
+ flash_key: String(row.flash_key ?? ""),
90
+ published_at: String(row.published_at ?? ""),
91
+ published_ts: Number(row.published_ts ?? 0),
92
+ content: String(row.content ?? ""),
93
+ url: String(row.url ?? ""),
94
+ ingested_at: String(row.ingested_at ?? ""),
95
+ raw: parseJsonObject(row.raw_json),
96
+ };
97
+ }
98
+ function dedupeEntries(entries) {
99
+ const seen = new Set();
100
+ const result = [];
101
+ for (const entry of entries) {
102
+ if (!entry.flash_key || seen.has(entry.flash_key)) {
103
+ continue;
104
+ }
105
+ seen.add(entry.flash_key);
106
+ result.push(entry);
107
+ }
108
+ return result;
109
+ }
110
+ function parseJsonObject(value) {
111
+ if (typeof value !== "string" || !value.trim()) {
112
+ return {};
113
+ }
114
+ try {
115
+ const parsed = JSON.parse(value);
116
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
117
+ ? parsed
118
+ : {};
119
+ }
120
+ catch {
121
+ return {};
122
+ }
123
+ }
124
+ function escapeSqlString(value) {
125
+ return value.replace(/'/g, "''");
126
+ }
@@ -11,3 +11,5 @@ export declare const financialAnalysisSchema: Schema<any>;
11
11
  export declare const newsAnalysisSchema: Schema<any>;
12
12
  export declare const compositeAnalysisSchema: Schema<any>;
13
13
  export declare const alertLogSchema: Schema<any>;
14
+ export declare const jin10FlashSchema: Schema<any>;
15
+ export declare const jin10FlashDeliverySchema: Schema<any>;
@@ -175,3 +175,22 @@ export const alertLogSchema = new Schema([
175
175
  new Field("message", new Utf8(), false),
176
176
  new Field("triggered_at", new Utf8(), false),
177
177
  ]);
178
+ export const jin10FlashSchema = new Schema([
179
+ new Field("flash_key", new Utf8(), false),
180
+ new Field("published_at", new Utf8(), false),
181
+ new Field("published_ts", new Int64(), false),
182
+ new Field("content", new Utf8(), false),
183
+ new Field("url", new Utf8(), false),
184
+ new Field("ingested_at", new Utf8(), false),
185
+ new Field("raw_json", new Utf8(), false),
186
+ ]);
187
+ export const jin10FlashDeliverySchema = new Schema([
188
+ new Field("flash_key", new Utf8(), false),
189
+ new Field("published_at", new Utf8(), false),
190
+ new Field("symbols_json", new Utf8(), false),
191
+ new Field("headline", new Utf8(), false),
192
+ new Field("reason", new Utf8(), false),
193
+ new Field("importance", new Utf8(), false),
194
+ new Field("message", new Utf8(), false),
195
+ new Field("delivered_at", new Utf8(), false),
196
+ ]);
@@ -0,0 +1,6 @@
1
+ import { Jin10FlashMonitorService } from "../services/jin10-flash-monitor-service.js";
2
+ export declare function flashMonitorStatusTool(flashMonitorService: Jin10FlashMonitorService): {
3
+ name: string;
4
+ description: string;
5
+ run(): Promise<string>;
6
+ };
@@ -0,0 +1,9 @@
1
+ export function flashMonitorStatusTool(flashMonitorService) {
2
+ return {
3
+ name: "flash_monitor_status",
4
+ description: "Show Jin10 flash monitor state, recent poll summary, and storage counters.",
5
+ async run() {
6
+ return flashMonitorService.getStatusReport();
7
+ },
8
+ };
9
+ }
@@ -0,0 +1,17 @@
1
+ export interface FlashMonitorState {
2
+ initialized: boolean;
3
+ lastSeenKey: string | null;
4
+ lastSeenPublishedAt: string | null;
5
+ lastSeenUrl: string | null;
6
+ backfillCursor: string | null;
7
+ runtimeHost: "plugin_service" | "fallback_process" | null;
8
+ runtimeObservedAt: string | null;
9
+ lastHeartbeatAt: string | null;
10
+ lastPollAt: string | null;
11
+ lastPollStored: number;
12
+ lastPollCandidates: number;
13
+ lastPollAlerts: number;
14
+ lastPrunedAt: string | null;
15
+ lastLoopError: string | null;
16
+ lastLoopErrorAt: string | null;
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ export interface Jin10FlashItem {
2
+ content: string;
3
+ time: string;
4
+ url: string;
5
+ raw: Record<string, unknown>;
6
+ }
7
+ export interface Jin10FlashPage {
8
+ hasMore: boolean;
9
+ items: Jin10FlashItem[];
10
+ nextCursor: string | null;
11
+ }
12
+ export interface Jin10FlashRecord {
13
+ flash_key: string;
14
+ published_at: string;
15
+ published_ts: number;
16
+ content: string;
17
+ url: string;
18
+ ingested_at: string;
19
+ raw: Record<string, unknown>;
20
+ }
21
+ export interface Jin10FlashDeliveryEntry {
22
+ flash_key: string;
23
+ published_at: string;
24
+ symbols: string[];
25
+ headline: string;
26
+ reason: string;
27
+ importance: "high" | "medium" | "low";
28
+ message: string;
29
+ delivered_at: string;
30
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "tickflow-assist",
3
3
  "name": "TickFlow Assist",
4
- "version": "0.2.18",
4
+ "version": "0.3.1",
5
5
  "description": "A-share watchlist analysis, monitoring, and alert delivery powered by TickFlow and OpenClaw.",
6
6
  "skills": [
7
7
  "skills"
@@ -36,6 +36,28 @@
36
36
  "default": "",
37
37
  "description": "Optional. Enables mx_search, mx_select_stock, and non-Expert financial fallback."
38
38
  },
39
+ "jin10McpUrl": {
40
+ "type": "string",
41
+ "default": "https://mcp.jin10.com/mcp",
42
+ "description": "Jin10 MCP server URL used for realtime flash monitoring."
43
+ },
44
+ "jin10ApiToken": {
45
+ "type": "string",
46
+ "default": "",
47
+ "description": "Optional. Enables 24/7 Jin10 realtime flash monitoring when configured."
48
+ },
49
+ "jin10FlashPollInterval": {
50
+ "type": "integer",
51
+ "minimum": 10,
52
+ "default": 300,
53
+ "description": "Jin10 flash polling interval in seconds."
54
+ },
55
+ "jin10FlashRetentionDays": {
56
+ "type": "integer",
57
+ "minimum": 1,
58
+ "default": 7,
59
+ "description": "How many days of raw Jin10 flash data to retain locally."
60
+ },
39
61
  "llmBaseUrl": {
40
62
  "type": "string",
41
63
  "default": "https://api.openai.com/v1",
@@ -125,6 +147,26 @@
125
147
  "help": "Optional. Enables mx_search, mx_select_stock, and lite financial fallback.",
126
148
  "sensitive": true
127
149
  },
150
+ "jin10McpUrl": {
151
+ "label": "Jin10 MCP URL",
152
+ "help": "Optional. MCP endpoint used by the 24/7 Jin10 flash monitor.",
153
+ "advanced": true
154
+ },
155
+ "jin10ApiToken": {
156
+ "label": "Jin10 API Token",
157
+ "help": "Optional. Enables Jin10 realtime flash monitoring.",
158
+ "sensitive": true
159
+ },
160
+ "jin10FlashPollInterval": {
161
+ "label": "Jin10 Poll Interval",
162
+ "help": "Polling interval in seconds for Jin10 flash monitoring.",
163
+ "advanced": true
164
+ },
165
+ "jin10FlashRetentionDays": {
166
+ "label": "Jin10 Retention Days",
167
+ "help": "How many days of Jin10 raw flash data to keep locally.",
168
+ "advanced": true
169
+ },
128
170
  "llmBaseUrl": {
129
171
  "label": "LLM Base URL",
130
172
  "help": "OpenAI-compatible analysis endpoint."