tickflow-assist 0.2.0
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/README.md +151 -0
- package/day_future.txt +8797 -0
- package/dist/analysis/orchestrators/composite-analysis.orchestrator.d.ts +35 -0
- package/dist/analysis/orchestrators/composite-analysis.orchestrator.js +232 -0
- package/dist/analysis/parsers/json-block.parser.d.ts +1 -0
- package/dist/analysis/parsers/json-block.parser.js +13 -0
- package/dist/analysis/parsers/key-levels.parser.d.ts +5 -0
- package/dist/analysis/parsers/key-levels.parser.js +61 -0
- package/dist/analysis/parsers/post-close-review.parser.d.ts +2 -0
- package/dist/analysis/parsers/post-close-review.parser.js +79 -0
- package/dist/analysis/parsers/watchlist-profile.parser.d.ts +6 -0
- package/dist/analysis/parsers/watchlist-profile.parser.js +93 -0
- package/dist/analysis/providers/financial-analysis.provider.d.ts +12 -0
- package/dist/analysis/providers/financial-analysis.provider.js +85 -0
- package/dist/analysis/providers/market-analysis.provider.d.ts +27 -0
- package/dist/analysis/providers/market-analysis.provider.js +187 -0
- package/dist/analysis/providers/news-analysis.provider.d.ts +9 -0
- package/dist/analysis/providers/news-analysis.provider.js +60 -0
- package/dist/analysis/tasks/analysis-step-task.d.ts +11 -0
- package/dist/analysis/tasks/analysis-step-task.js +1 -0
- package/dist/analysis/tasks/analysis-task.d.ts +13 -0
- package/dist/analysis/tasks/analysis-task.js +1 -0
- package/dist/analysis/tasks/composite-stock-analysis.task.d.ts +17 -0
- package/dist/analysis/tasks/composite-stock-analysis.task.js +47 -0
- package/dist/analysis/tasks/financial-fundamental-lite.task.d.ts +10 -0
- package/dist/analysis/tasks/financial-fundamental-lite.task.js +21 -0
- package/dist/analysis/tasks/financial-fundamental.task.d.ts +12 -0
- package/dist/analysis/tasks/financial-fundamental.task.js +64 -0
- package/dist/analysis/tasks/kline-technical-signal.task.d.ts +10 -0
- package/dist/analysis/tasks/kline-technical-signal.task.js +41 -0
- package/dist/analysis/tasks/kline-technical.task.d.ts +32 -0
- package/dist/analysis/tasks/kline-technical.task.js +61 -0
- package/dist/analysis/tasks/news-catalyst.task.d.ts +11 -0
- package/dist/analysis/tasks/news-catalyst.task.js +62 -0
- package/dist/analysis/tasks/post-close-review.task.d.ts +12 -0
- package/dist/analysis/tasks/post-close-review.task.js +35 -0
- package/dist/analysis/types/composite-analysis.d.ts +123 -0
- package/dist/analysis/types/composite-analysis.js +1 -0
- package/dist/background/daily-update.worker.d.ts +50 -0
- package/dist/background/daily-update.worker.js +546 -0
- package/dist/background/realtime-monitor.worker.d.ts +8 -0
- package/dist/background/realtime-monitor.worker.js +28 -0
- package/dist/bootstrap.d.ts +45 -0
- package/dist/bootstrap.js +214 -0
- package/dist/config/normalize.d.ts +4 -0
- package/dist/config/normalize.js +99 -0
- package/dist/config/schema.d.ts +23 -0
- package/dist/config/schema.js +18 -0
- package/dist/config/tickflow-access.d.ts +4 -0
- package/dist/config/tickflow-access.js +28 -0
- package/dist/constants/market-indexes.d.ts +5 -0
- package/dist/constants/market-indexes.js +4 -0
- package/dist/dev/run-daily-update-loop.d.ts +1 -0
- package/dist/dev/run-daily-update-loop.js +48 -0
- package/dist/dev/run-monitor-loop.d.ts +1 -0
- package/dist/dev/run-monitor-loop.js +60 -0
- package/dist/dev/run-tool.d.ts +1 -0
- package/dist/dev/run-tool.js +49 -0
- package/dist/dev/tickflow-assist-cli.d.ts +2 -0
- package/dist/dev/tickflow-assist-cli.js +525 -0
- package/dist/dev/validate-mx-search.d.ts +1 -0
- package/dist/dev/validate-mx-search.js +212 -0
- package/dist/plugin-commands.d.ts +3 -0
- package/dist/plugin-commands.js +229 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.js +151 -0
- package/dist/prompts/analysis/common-system-prompt.d.ts +1 -0
- package/dist/prompts/analysis/common-system-prompt.js +44 -0
- package/dist/prompts/analysis/composite-analysis-user-prompt.d.ts +11 -0
- package/dist/prompts/analysis/composite-analysis-user-prompt.js +102 -0
- package/dist/prompts/analysis/financial-analysis-user-prompt.d.ts +7 -0
- package/dist/prompts/analysis/financial-analysis-user-prompt.js +106 -0
- package/dist/prompts/analysis/financial-lite-analysis-user-prompt.d.ts +7 -0
- package/dist/prompts/analysis/financial-lite-analysis-user-prompt.js +59 -0
- package/dist/prompts/analysis/index.d.ts +8 -0
- package/dist/prompts/analysis/index.js +8 -0
- package/dist/prompts/analysis/kline-analysis-user-prompt.d.ts +13 -0
- package/dist/prompts/analysis/kline-analysis-user-prompt.js +215 -0
- package/dist/prompts/analysis/news-analysis-user-prompt.d.ts +8 -0
- package/dist/prompts/analysis/news-analysis-user-prompt.js +57 -0
- package/dist/prompts/analysis/post-close-review-user-prompt.d.ts +3 -0
- package/dist/prompts/analysis/post-close-review-user-prompt.js +129 -0
- package/dist/prompts/analysis/watchlist-profile-extraction-prompt.d.ts +7 -0
- package/dist/prompts/analysis/watchlist-profile-extraction-prompt.js +52 -0
- package/dist/prompts/analysis-system-prompt.d.ts +1 -0
- package/dist/prompts/analysis-system-prompt.js +1 -0
- package/dist/prompts/analysis-user-prompt.d.ts +1 -0
- package/dist/prompts/analysis-user-prompt.js +1 -0
- package/dist/runtime/daily-update-process.d.ts +3 -0
- package/dist/runtime/daily-update-process.js +24 -0
- package/dist/runtime/monitor-process.d.ts +3 -0
- package/dist/runtime/monitor-process.js +24 -0
- package/dist/runtime/plugin-api.d.ts +22 -0
- package/dist/runtime/plugin-api.js +2 -0
- package/dist/runtime/process-config.d.ts +8 -0
- package/dist/runtime/process-config.js +35 -0
- package/dist/services/alert-service.d.ts +45 -0
- package/dist/services/alert-service.js +198 -0
- package/dist/services/analysis-service.d.ts +20 -0
- package/dist/services/analysis-service.js +73 -0
- package/dist/services/analysis-view-service.d.ts +23 -0
- package/dist/services/analysis-view-service.js +343 -0
- package/dist/services/financial-lite-service.d.ts +23 -0
- package/dist/services/financial-lite-service.js +201 -0
- package/dist/services/financial-service.d.ts +20 -0
- package/dist/services/financial-service.js +47 -0
- package/dist/services/indicator-service.d.ts +9 -0
- package/dist/services/indicator-service.js +67 -0
- package/dist/services/instrument-service.d.ts +6 -0
- package/dist/services/instrument-service.js +21 -0
- package/dist/services/key-level-service.d.ts +2 -0
- package/dist/services/key-level-service.js +2 -0
- package/dist/services/key-levels-backtest-service.d.ts +71 -0
- package/dist/services/key-levels-backtest-service.js +427 -0
- package/dist/services/kline-service.d.ts +19 -0
- package/dist/services/kline-service.js +91 -0
- package/dist/services/monitor-service.d.ts +44 -0
- package/dist/services/monitor-service.js +598 -0
- package/dist/services/mx-search-service.d.ts +22 -0
- package/dist/services/mx-search-service.js +286 -0
- package/dist/services/post-close-review-service.d.ts +31 -0
- package/dist/services/post-close-review-service.js +402 -0
- package/dist/services/quote-service.d.ts +7 -0
- package/dist/services/quote-service.js +9 -0
- package/dist/services/review-memory-service.d.ts +7 -0
- package/dist/services/review-memory-service.js +76 -0
- package/dist/services/tickflow-client.d.ts +43 -0
- package/dist/services/tickflow-client.js +126 -0
- package/dist/services/trading-calendar-service.d.ts +20 -0
- package/dist/services/trading-calendar-service.js +102 -0
- package/dist/services/update-service.d.ts +21 -0
- package/dist/services/update-service.js +130 -0
- package/dist/services/watchlist-profile-service.d.ts +20 -0
- package/dist/services/watchlist-profile-service.js +76 -0
- package/dist/services/watchlist-service.d.ts +43 -0
- package/dist/services/watchlist-service.js +204 -0
- package/dist/storage/db.d.ts +23 -0
- package/dist/storage/db.js +70 -0
- package/dist/storage/repositories/alert-log-repo.d.ts +15 -0
- package/dist/storage/repositories/alert-log-repo.js +54 -0
- package/dist/storage/repositories/analysis-log-repo.d.ts +8 -0
- package/dist/storage/repositories/analysis-log-repo.js +48 -0
- package/dist/storage/repositories/composite-analysis-repo.d.ts +9 -0
- package/dist/storage/repositories/composite-analysis-repo.js +116 -0
- package/dist/storage/repositories/financial-analysis-repo.d.ts +9 -0
- package/dist/storage/repositories/financial-analysis-repo.js +107 -0
- package/dist/storage/repositories/indicators-repo.d.ts +8 -0
- package/dist/storage/repositories/indicators-repo.js +98 -0
- package/dist/storage/repositories/intraday-klines-repo.d.ts +9 -0
- package/dist/storage/repositories/intraday-klines-repo.js +102 -0
- package/dist/storage/repositories/key-levels-history-repo.d.ts +9 -0
- package/dist/storage/repositories/key-levels-history-repo.js +91 -0
- package/dist/storage/repositories/key-levels-repo.d.ts +9 -0
- package/dist/storage/repositories/key-levels-repo.js +83 -0
- package/dist/storage/repositories/klines-repo.d.ts +8 -0
- package/dist/storage/repositories/klines-repo.js +60 -0
- package/dist/storage/repositories/news-analysis-repo.d.ts +9 -0
- package/dist/storage/repositories/news-analysis-repo.js +107 -0
- package/dist/storage/repositories/technical-analysis-repo.d.ts +9 -0
- package/dist/storage/repositories/technical-analysis-repo.js +80 -0
- package/dist/storage/repositories/watchlist-repo.d.ts +10 -0
- package/dist/storage/repositories/watchlist-repo.js +124 -0
- package/dist/storage/schemas.d.ts +13 -0
- package/dist/storage/schemas.js +177 -0
- package/dist/tools/add-stock.tool.d.ts +13 -0
- package/dist/tools/add-stock.tool.js +123 -0
- package/dist/tools/analyze.tool.d.ts +8 -0
- package/dist/tools/analyze.tool.js +24 -0
- package/dist/tools/backtest-key-levels.tool.d.ts +8 -0
- package/dist/tools/backtest-key-levels.tool.js +43 -0
- package/dist/tools/daily-update-status.tool.d.ts +6 -0
- package/dist/tools/daily-update-status.tool.js +9 -0
- package/dist/tools/fetch-financials.tool.d.ts +8 -0
- package/dist/tools/fetch-financials.tool.js +224 -0
- package/dist/tools/fetch-intraday-klines.tool.d.ts +11 -0
- package/dist/tools/fetch-intraday-klines.tool.js +58 -0
- package/dist/tools/fetch-klines.tool.d.ts +11 -0
- package/dist/tools/fetch-klines.tool.js +61 -0
- package/dist/tools/list-watchlist.tool.d.ts +6 -0
- package/dist/tools/list-watchlist.tool.js +22 -0
- package/dist/tools/monitor-status.tool.d.ts +6 -0
- package/dist/tools/monitor-status.tool.js +9 -0
- package/dist/tools/mx-search.tool.d.ts +8 -0
- package/dist/tools/mx-search.tool.js +77 -0
- package/dist/tools/mx-select-stock.tool.d.ts +8 -0
- package/dist/tools/mx-select-stock.tool.js +118 -0
- package/dist/tools/query-database.tool.d.ts +8 -0
- package/dist/tools/query-database.tool.js +283 -0
- package/dist/tools/refresh-watchlist-names.tool.d.ts +7 -0
- package/dist/tools/refresh-watchlist-names.tool.js +17 -0
- package/dist/tools/refresh-watchlist-profiles.tool.d.ts +9 -0
- package/dist/tools/refresh-watchlist-profiles.tool.js +67 -0
- package/dist/tools/remove-stock.tool.d.ts +9 -0
- package/dist/tools/remove-stock.tool.js +27 -0
- package/dist/tools/start-daily-update.tool.d.ts +10 -0
- package/dist/tools/start-daily-update.tool.js +23 -0
- package/dist/tools/start-monitor.tool.d.ts +9 -0
- package/dist/tools/start-monitor.tool.js +52 -0
- package/dist/tools/stop-daily-update.tool.d.ts +9 -0
- package/dist/tools/stop-daily-update.tool.js +20 -0
- package/dist/tools/stop-monitor.tool.d.ts +9 -0
- package/dist/tools/stop-monitor.tool.js +44 -0
- package/dist/tools/test-alert.tool.d.ts +7 -0
- package/dist/tools/test-alert.tool.js +21 -0
- package/dist/tools/update-all.tool.d.ts +9 -0
- package/dist/tools/update-all.tool.js +17 -0
- package/dist/tools/view-analysis.tool.d.ts +8 -0
- package/dist/tools/view-analysis.tool.js +95 -0
- package/dist/types/daily-update.d.ts +26 -0
- package/dist/types/daily-update.js +1 -0
- package/dist/types/domain.d.ts +140 -0
- package/dist/types/domain.js +1 -0
- package/dist/types/indicator.d.ts +43 -0
- package/dist/types/indicator.js +1 -0
- package/dist/types/monitor.d.ts +17 -0
- package/dist/types/monitor.js +1 -0
- package/dist/types/mx-search.d.ts +14 -0
- package/dist/types/mx-search.js +1 -0
- package/dist/types/mx-select-stock.d.ts +28 -0
- package/dist/types/mx-select-stock.js +1 -0
- package/dist/types/tickflow.d.ts +133 -0
- package/dist/types/tickflow.js +1 -0
- package/dist/utils/abortable-sleep.d.ts +1 -0
- package/dist/utils/abortable-sleep.js +20 -0
- package/dist/utils/china-time.d.ts +3 -0
- package/dist/utils/china-time.js +18 -0
- package/dist/utils/cost-price.d.ts +3 -0
- package/dist/utils/cost-price.js +18 -0
- package/dist/utils/format.d.ts +1 -0
- package/dist/utils/format.js +3 -0
- package/dist/utils/process.d.ts +5 -0
- package/dist/utils/process.js +1 -0
- package/dist/utils/symbol.d.ts +1 -0
- package/dist/utils/symbol.js +13 -0
- package/docs/installation.md +391 -0
- package/docs/usage.md +244 -0
- package/openclaw.plugin.json +116 -0
- package/package.json +57 -0
- package/python/indicator_runner.py +31 -0
- package/python/indicators.py +148 -0
- package/python/pyproject.toml +9 -0
- package/python/requirements.txt +3 -0
- package/python/uv.lock +366 -0
- package/skills/database-query/SKILL.md +58 -0
- package/skills/stock-analysis/SKILL.md +106 -0
- package/skills/usage-help/SKILL.md +102 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { chinaToday, formatChinaDateTime } from "../utils/china-time.js";
|
|
4
|
+
import { sleepWithAbort } from "../utils/abortable-sleep.js";
|
|
5
|
+
import { isPidAlive, spawnDailyUpdateLoop } from "../runtime/daily-update-process.js";
|
|
6
|
+
const DAILY_UPDATE_READY_TIME = "15:25";
|
|
7
|
+
const POST_CLOSE_REVIEW_READY_TIME = "20:00";
|
|
8
|
+
const DEFAULT_STATE = {
|
|
9
|
+
running: false,
|
|
10
|
+
startedAt: null,
|
|
11
|
+
lastStoppedAt: null,
|
|
12
|
+
workerPid: null,
|
|
13
|
+
expectedStop: false,
|
|
14
|
+
runtimeHost: null,
|
|
15
|
+
runtimeObservedAt: null,
|
|
16
|
+
runtimeConfigSource: null,
|
|
17
|
+
lastHeartbeatAt: null,
|
|
18
|
+
lastAttemptAt: null,
|
|
19
|
+
lastAttemptDate: null,
|
|
20
|
+
lastSuccessAt: null,
|
|
21
|
+
lastSuccessDate: null,
|
|
22
|
+
lastResultType: null,
|
|
23
|
+
lastResultSummary: null,
|
|
24
|
+
consecutiveFailures: 0,
|
|
25
|
+
lastReviewAttemptAt: null,
|
|
26
|
+
lastReviewAttemptDate: null,
|
|
27
|
+
lastReviewSuccessAt: null,
|
|
28
|
+
lastReviewSuccessDate: null,
|
|
29
|
+
lastReviewResultType: null,
|
|
30
|
+
lastReviewResultSummary: null,
|
|
31
|
+
reviewConsecutiveFailures: 0,
|
|
32
|
+
};
|
|
33
|
+
export class DailyUpdateWorker {
|
|
34
|
+
updateService;
|
|
35
|
+
postCloseReviewService;
|
|
36
|
+
tradingCalendarService;
|
|
37
|
+
baseDir;
|
|
38
|
+
alertService;
|
|
39
|
+
notifyEnabled;
|
|
40
|
+
configSource;
|
|
41
|
+
intervalMs;
|
|
42
|
+
constructor(updateService, postCloseReviewService, tradingCalendarService, baseDir, alertService, notifyEnabled, configSource, intervalMs = 15 * 60 * 1000) {
|
|
43
|
+
this.updateService = updateService;
|
|
44
|
+
this.postCloseReviewService = postCloseReviewService;
|
|
45
|
+
this.tradingCalendarService = tradingCalendarService;
|
|
46
|
+
this.baseDir = baseDir;
|
|
47
|
+
this.alertService = alertService;
|
|
48
|
+
this.notifyEnabled = notifyEnabled;
|
|
49
|
+
this.configSource = configSource;
|
|
50
|
+
this.intervalMs = intervalMs;
|
|
51
|
+
}
|
|
52
|
+
async run(force = false) {
|
|
53
|
+
const updateOutput = await this.executeDailyUpdateAndRecord(force, "manual", true);
|
|
54
|
+
if (updateOutput.resultType !== "success") {
|
|
55
|
+
return updateOutput.message;
|
|
56
|
+
}
|
|
57
|
+
const reviewOutput = await this.executeReviewAndRecord("manual");
|
|
58
|
+
return joinMessages(updateOutput.message, reviewOutput?.combinedText);
|
|
59
|
+
}
|
|
60
|
+
async runLoop(signal, runtimeHost, runtimeConfigSource) {
|
|
61
|
+
while (!signal?.aborted) {
|
|
62
|
+
await this.recordHeartbeat(runtimeHost, runtimeConfigSource);
|
|
63
|
+
const state = await this.readState();
|
|
64
|
+
if (state.running) {
|
|
65
|
+
await this.runScheduledPasses();
|
|
66
|
+
}
|
|
67
|
+
await sleepWithAbort(getNextAlignedDelayMs(this.intervalMs), signal);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async ensureLoopRunning(config, configSource) {
|
|
71
|
+
const state = await this.readState();
|
|
72
|
+
if (state.workerPid != null && isPidAlive(state.workerPid)) {
|
|
73
|
+
await this.markSchedulerRunning(state.workerPid, state.runtimeConfigSource ?? configSource);
|
|
74
|
+
return { started: false, pid: state.workerPid };
|
|
75
|
+
}
|
|
76
|
+
const workerPid = spawnDailyUpdateLoop(config, configSource);
|
|
77
|
+
if (workerPid == null) {
|
|
78
|
+
throw new Error("无法启动 TickFlow 日更定时进程");
|
|
79
|
+
}
|
|
80
|
+
await this.markSchedulerRunning(workerPid, configSource);
|
|
81
|
+
return { started: true, pid: workerPid };
|
|
82
|
+
}
|
|
83
|
+
async stopLoop() {
|
|
84
|
+
const state = await this.readState();
|
|
85
|
+
const workerPid = state.workerPid;
|
|
86
|
+
const alive = workerPid != null && isPidAlive(workerPid);
|
|
87
|
+
if (!alive) {
|
|
88
|
+
await this.markSchedulerStopped();
|
|
89
|
+
return { stopped: false, pid: null };
|
|
90
|
+
}
|
|
91
|
+
await this.setExpectedStop(true);
|
|
92
|
+
await this.markSchedulerStopped();
|
|
93
|
+
try {
|
|
94
|
+
process.kill(workerPid, "SIGTERM");
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Best-effort stop for the detached daily-update worker.
|
|
98
|
+
}
|
|
99
|
+
return { stopped: true, pid: workerPid };
|
|
100
|
+
}
|
|
101
|
+
async enableManagedLoop(configSource) {
|
|
102
|
+
const state = await this.readState();
|
|
103
|
+
const now = formatChinaDateTime();
|
|
104
|
+
await this.writeState({
|
|
105
|
+
...state,
|
|
106
|
+
running: true,
|
|
107
|
+
startedAt: state.startedAt ?? now,
|
|
108
|
+
workerPid: null,
|
|
109
|
+
expectedStop: false,
|
|
110
|
+
runtimeHost: "plugin_service",
|
|
111
|
+
runtimeObservedAt: now,
|
|
112
|
+
runtimeConfigSource: configSource,
|
|
113
|
+
});
|
|
114
|
+
return { started: !state.running, pid: null };
|
|
115
|
+
}
|
|
116
|
+
async bindManagedServiceRuntime(configSource) {
|
|
117
|
+
const state = await this.readState();
|
|
118
|
+
const now = formatChinaDateTime();
|
|
119
|
+
await this.writeState({
|
|
120
|
+
...state,
|
|
121
|
+
workerPid: null,
|
|
122
|
+
expectedStop: false,
|
|
123
|
+
runtimeHost: "plugin_service",
|
|
124
|
+
runtimeObservedAt: now,
|
|
125
|
+
runtimeConfigSource: configSource,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async markSchedulerRunning(workerPid, runtimeConfigSource) {
|
|
129
|
+
const state = await this.readState();
|
|
130
|
+
const now = formatChinaDateTime();
|
|
131
|
+
await this.writeState({
|
|
132
|
+
...state,
|
|
133
|
+
running: true,
|
|
134
|
+
startedAt: state.startedAt ?? now,
|
|
135
|
+
workerPid,
|
|
136
|
+
expectedStop: false,
|
|
137
|
+
runtimeHost: "project_scheduler",
|
|
138
|
+
runtimeObservedAt: now,
|
|
139
|
+
runtimeConfigSource,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
async markSchedulerStopped() {
|
|
143
|
+
const state = await this.readState();
|
|
144
|
+
await this.writeState({
|
|
145
|
+
...state,
|
|
146
|
+
running: false,
|
|
147
|
+
startedAt: null,
|
|
148
|
+
lastStoppedAt: formatChinaDateTime(),
|
|
149
|
+
workerPid: null,
|
|
150
|
+
expectedStop: false,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async setExpectedStop(expectedStop) {
|
|
154
|
+
const state = await this.readState();
|
|
155
|
+
await this.writeState({
|
|
156
|
+
...state,
|
|
157
|
+
expectedStop,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async getState() {
|
|
161
|
+
return this.readState();
|
|
162
|
+
}
|
|
163
|
+
async getStatusReport() {
|
|
164
|
+
const state = await this.readState();
|
|
165
|
+
const today = chinaToday();
|
|
166
|
+
const lines = [
|
|
167
|
+
"🕒 定时日更 / 收盘复盘状态",
|
|
168
|
+
`状态: ${formatProcessState(state)}`,
|
|
169
|
+
`运行方式: ${formatRuntimeHost(state)}`,
|
|
170
|
+
`配置来源: ${this.configSource}`,
|
|
171
|
+
`调度: ${Math.floor(this.intervalMs / 60_000)} 分钟对齐轮询 | 日更 ${DAILY_UPDATE_READY_TIME} 后执行 | 复盘 ${POST_CLOSE_REVIEW_READY_TIME} 后执行`,
|
|
172
|
+
`最近心跳: ${state.lastHeartbeatAt ?? "暂无"}`,
|
|
173
|
+
"",
|
|
174
|
+
"日更执行:",
|
|
175
|
+
`• 今日已更新: ${state.lastSuccessDate === today ? "是" : "否"}`,
|
|
176
|
+
`• 最近尝试: ${state.lastAttemptAt ?? "暂无"}`,
|
|
177
|
+
`• 最近成功: ${state.lastSuccessAt ?? "暂无"}`,
|
|
178
|
+
`• 最近结果: ${formatResultType(state.lastResultType)}`,
|
|
179
|
+
];
|
|
180
|
+
if (state.consecutiveFailures > 0) {
|
|
181
|
+
lines.push(`• 连续失败: ${state.consecutiveFailures}`);
|
|
182
|
+
}
|
|
183
|
+
if (state.lastResultSummary) {
|
|
184
|
+
lines.push(`• 最近摘要: ${state.lastResultSummary}`);
|
|
185
|
+
}
|
|
186
|
+
lines.push("", "复盘执行:", `• 今日已复盘: ${state.lastReviewSuccessDate === today ? "是" : "否"}`, `• 最近尝试: ${state.lastReviewAttemptAt ?? "暂无"}`, `• 最近成功: ${state.lastReviewSuccessAt ?? "暂无"}`, `• 最近结果: ${formatResultType(state.lastReviewResultType)}`);
|
|
187
|
+
if (state.reviewConsecutiveFailures > 0) {
|
|
188
|
+
lines.push(`• 连续失败: ${state.reviewConsecutiveFailures}`);
|
|
189
|
+
}
|
|
190
|
+
if (state.lastReviewResultSummary) {
|
|
191
|
+
lines.push(`• 最近摘要: ${state.lastReviewResultSummary}`);
|
|
192
|
+
}
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
async runScheduledPasses() {
|
|
196
|
+
await this.runScheduledUpdatePass();
|
|
197
|
+
await this.runScheduledReviewPass();
|
|
198
|
+
}
|
|
199
|
+
async runScheduledUpdatePass() {
|
|
200
|
+
const today = chinaToday();
|
|
201
|
+
const state = await this.readState();
|
|
202
|
+
if (hasCompletedScheduledWindow(state.lastSuccessDate, state.lastSuccessAt, today, DAILY_UPDATE_READY_TIME)) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const readiness = await this.tradingCalendarService.canRunDailyUpdate();
|
|
206
|
+
if (!readiness.ok && readiness.reason.includes("须等到") && state.lastSuccessDate === today) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
await this.executeDailyUpdateAndRecord(false, "scheduled", false);
|
|
210
|
+
}
|
|
211
|
+
async runScheduledReviewPass() {
|
|
212
|
+
if (!this.postCloseReviewService) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const today = chinaToday();
|
|
216
|
+
const state = await this.readState();
|
|
217
|
+
if (hasCompletedScheduledWindow(state.lastReviewSuccessDate, state.lastReviewSuccessAt, today, POST_CLOSE_REVIEW_READY_TIME)) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const readiness = await this.getScheduledReviewReadiness(state, today);
|
|
221
|
+
if (!readiness.ok) {
|
|
222
|
+
if (readiness.code === "waiting_time" && state.lastReviewSuccessDate === today) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
await this.recordReviewSkip(state, today, readiness.reason);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
await this.executeReviewAndRecord("scheduled");
|
|
229
|
+
}
|
|
230
|
+
async getScheduledReviewReadiness(state, today) {
|
|
231
|
+
if (!hasCompletedScheduledWindow(state.lastSuccessDate, state.lastSuccessAt, today, DAILY_UPDATE_READY_TIME)) {
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
reason: `今日日更尚未在 ${DAILY_UPDATE_READY_TIME} 后成功完成,暂不执行收盘复盘`,
|
|
235
|
+
code: "waiting_daily_update",
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const readiness = await this.tradingCalendarService.canRunPostCloseReview();
|
|
239
|
+
if (!readiness.ok) {
|
|
240
|
+
return {
|
|
241
|
+
ok: false,
|
|
242
|
+
reason: readiness.reason,
|
|
243
|
+
code: readiness.reason.includes("须等到") ? "waiting_time" : "non_trading_day",
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
ok: true,
|
|
248
|
+
reason: readiness.reason,
|
|
249
|
+
code: "ready",
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
async recordReviewSkip(state, today, reason) {
|
|
253
|
+
await this.writeState({
|
|
254
|
+
...state,
|
|
255
|
+
lastReviewAttemptAt: formatChinaDateTime(),
|
|
256
|
+
lastReviewAttemptDate: today,
|
|
257
|
+
lastReviewResultType: "skipped",
|
|
258
|
+
lastReviewResultSummary: reason,
|
|
259
|
+
reviewConsecutiveFailures: 0,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
async executeDailyUpdateAndRecord(force, trigger, throwOnError) {
|
|
263
|
+
const today = chinaToday();
|
|
264
|
+
const state = await this.readState();
|
|
265
|
+
const attemptedAt = formatChinaDateTime();
|
|
266
|
+
try {
|
|
267
|
+
const message = await this.updateService.updateAll(force);
|
|
268
|
+
const output = {
|
|
269
|
+
resultType: classifyResult(message),
|
|
270
|
+
message,
|
|
271
|
+
};
|
|
272
|
+
const nextState = {
|
|
273
|
+
...state,
|
|
274
|
+
lastAttemptAt: attemptedAt,
|
|
275
|
+
lastAttemptDate: today,
|
|
276
|
+
lastResultType: output.resultType,
|
|
277
|
+
lastResultSummary: summarizeUpdateResult(output.message),
|
|
278
|
+
consecutiveFailures: output.resultType === "failed" ? state.consecutiveFailures + 1 : 0,
|
|
279
|
+
};
|
|
280
|
+
if (output.resultType === "success") {
|
|
281
|
+
nextState.lastSuccessAt = attemptedAt;
|
|
282
|
+
nextState.lastSuccessDate = today;
|
|
283
|
+
}
|
|
284
|
+
await this.writeState(nextState);
|
|
285
|
+
if (trigger === "scheduled") {
|
|
286
|
+
await this.maybeSendDailyUpdateNotification(output);
|
|
287
|
+
}
|
|
288
|
+
return output;
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
292
|
+
const failureText = `异常: ${message}`;
|
|
293
|
+
await this.writeState({
|
|
294
|
+
...state,
|
|
295
|
+
lastAttemptAt: attemptedAt,
|
|
296
|
+
lastAttemptDate: today,
|
|
297
|
+
lastResultType: "failed",
|
|
298
|
+
lastResultSummary: failureText,
|
|
299
|
+
consecutiveFailures: state.consecutiveFailures + 1,
|
|
300
|
+
});
|
|
301
|
+
const output = {
|
|
302
|
+
resultType: "failed",
|
|
303
|
+
message: failureText,
|
|
304
|
+
};
|
|
305
|
+
if (trigger === "scheduled") {
|
|
306
|
+
await this.maybeSendDailyUpdateNotification(output);
|
|
307
|
+
}
|
|
308
|
+
if (throwOnError) {
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
return output;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async executeReviewAndRecord(trigger) {
|
|
315
|
+
if (!this.postCloseReviewService) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
const today = chinaToday();
|
|
319
|
+
const state = await this.readState();
|
|
320
|
+
const attemptedAt = formatChinaDateTime();
|
|
321
|
+
try {
|
|
322
|
+
const reviewResult = await this.postCloseReviewService.run();
|
|
323
|
+
const output = createReviewExecutionOutput(reviewResult);
|
|
324
|
+
await this.writeState({
|
|
325
|
+
...state,
|
|
326
|
+
lastReviewAttemptAt: attemptedAt,
|
|
327
|
+
lastReviewAttemptDate: today,
|
|
328
|
+
lastReviewSuccessAt: attemptedAt,
|
|
329
|
+
lastReviewSuccessDate: today,
|
|
330
|
+
lastReviewResultType: output.resultType,
|
|
331
|
+
lastReviewResultSummary: summarizeReviewResult(output.combinedText),
|
|
332
|
+
reviewConsecutiveFailures: 0,
|
|
333
|
+
});
|
|
334
|
+
if (trigger === "scheduled") {
|
|
335
|
+
await this.maybeSendReviewNotification(output);
|
|
336
|
+
}
|
|
337
|
+
return output;
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
341
|
+
const failureText = `⚠️ 收盘复盘失败: ${message}`;
|
|
342
|
+
await this.writeState({
|
|
343
|
+
...state,
|
|
344
|
+
lastReviewAttemptAt: attemptedAt,
|
|
345
|
+
lastReviewAttemptDate: today,
|
|
346
|
+
lastReviewResultType: "failed",
|
|
347
|
+
lastReviewResultSummary: failureText,
|
|
348
|
+
reviewConsecutiveFailures: state.reviewConsecutiveFailures + 1,
|
|
349
|
+
});
|
|
350
|
+
const output = {
|
|
351
|
+
resultType: "failed",
|
|
352
|
+
overviewMessage: null,
|
|
353
|
+
detailMessages: [],
|
|
354
|
+
combinedText: failureText,
|
|
355
|
+
};
|
|
356
|
+
if (trigger === "scheduled") {
|
|
357
|
+
await this.maybeSendReviewNotification(output);
|
|
358
|
+
}
|
|
359
|
+
return output;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
getStateFilePath() {
|
|
363
|
+
return path.join(this.baseDir, "daily-update-state.json");
|
|
364
|
+
}
|
|
365
|
+
async recordHeartbeat(runtimeHost, runtimeConfigSource) {
|
|
366
|
+
const state = await this.readState();
|
|
367
|
+
const observedAt = formatChinaDateTime();
|
|
368
|
+
await this.writeState({
|
|
369
|
+
...state,
|
|
370
|
+
lastHeartbeatAt: observedAt,
|
|
371
|
+
runtimeHost: runtimeHost ?? state.runtimeHost,
|
|
372
|
+
runtimeObservedAt: observedAt,
|
|
373
|
+
runtimeConfigSource: runtimeConfigSource ?? state.runtimeConfigSource,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
async readState() {
|
|
377
|
+
const file = this.getStateFilePath();
|
|
378
|
+
try {
|
|
379
|
+
const raw = await readFile(file, "utf-8");
|
|
380
|
+
return { ...DEFAULT_STATE, ...JSON.parse(raw) };
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
if (error.code === "ENOENT") {
|
|
384
|
+
return { ...DEFAULT_STATE };
|
|
385
|
+
}
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async writeState(state) {
|
|
390
|
+
const file = this.getStateFilePath();
|
|
391
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
392
|
+
await writeFile(file, JSON.stringify(state, null, 2), "utf-8");
|
|
393
|
+
}
|
|
394
|
+
async maybeSendDailyUpdateNotification(output) {
|
|
395
|
+
if (!this.notifyEnabled || output.resultType === "skipped") {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (output.resultType !== "success") {
|
|
399
|
+
const message = this.alertService.formatSystemNotification("❌ 定时日更失败", selectUpdateNotificationLines(output.message));
|
|
400
|
+
await this.alertService.send(message);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const message = this.alertService.formatSystemNotification("📊 定时日更完成", normalizeResultLines(output.message));
|
|
404
|
+
await this.alertService.send(message);
|
|
405
|
+
}
|
|
406
|
+
async maybeSendReviewNotification(output) {
|
|
407
|
+
if (!this.notifyEnabled || output.resultType === "skipped") {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (output.resultType !== "success") {
|
|
411
|
+
const message = this.alertService.formatSystemNotification("❌ 收盘复盘失败", selectReviewNotificationLines(output.combinedText));
|
|
412
|
+
await this.alertService.send(message);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const messages = [
|
|
416
|
+
this.alertService.formatSystemNotification("📘 收盘复盘完成", selectReviewNotificationLines(output.combinedText)),
|
|
417
|
+
output.overviewMessage,
|
|
418
|
+
...output.detailMessages,
|
|
419
|
+
].filter((message) => Boolean(message));
|
|
420
|
+
for (const message of messages) {
|
|
421
|
+
await this.alertService.send(message);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function createReviewExecutionOutput(reviewResult) {
|
|
426
|
+
return {
|
|
427
|
+
resultType: "success",
|
|
428
|
+
overviewMessage: reviewResult.overviewMessage,
|
|
429
|
+
detailMessages: reviewResult.detailMessages,
|
|
430
|
+
combinedText: joinMessages(reviewResult.overviewMessage, ...reviewResult.detailMessages),
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function joinMessages(...parts) {
|
|
434
|
+
return parts
|
|
435
|
+
.map((part) => part?.trim())
|
|
436
|
+
.filter((part) => Boolean(part))
|
|
437
|
+
.join("\n\n");
|
|
438
|
+
}
|
|
439
|
+
function getNextAlignedDelayMs(intervalMs) {
|
|
440
|
+
const intervalMinutes = Math.max(1, Math.floor(intervalMs / 60_000));
|
|
441
|
+
const anchorMinute = 10;
|
|
442
|
+
const now = new Date();
|
|
443
|
+
const chinaNow = new Date(now.toLocaleString("en-US", { timeZone: "Asia/Shanghai" }));
|
|
444
|
+
const currentMinute = chinaNow.getMinutes();
|
|
445
|
+
const currentSecond = chinaNow.getSeconds();
|
|
446
|
+
const currentMillisecond = chinaNow.getMilliseconds();
|
|
447
|
+
const normalized = ((currentMinute - anchorMinute) % intervalMinutes + intervalMinutes) % intervalMinutes;
|
|
448
|
+
let minutesToAdd = intervalMinutes - normalized;
|
|
449
|
+
if (minutesToAdd === intervalMinutes && currentSecond === 0 && currentMillisecond === 0) {
|
|
450
|
+
minutesToAdd = intervalMinutes;
|
|
451
|
+
}
|
|
452
|
+
if (normalized === 0 && (currentSecond > 0 || currentMillisecond > 0)) {
|
|
453
|
+
minutesToAdd = intervalMinutes;
|
|
454
|
+
}
|
|
455
|
+
const next = new Date(chinaNow);
|
|
456
|
+
next.setSeconds(0, 0);
|
|
457
|
+
next.setMinutes(currentMinute + minutesToAdd);
|
|
458
|
+
return Math.max(1_000, next.getTime() - chinaNow.getTime());
|
|
459
|
+
}
|
|
460
|
+
function classifyResult(result) {
|
|
461
|
+
if (result.startsWith("📊") || result.startsWith("📋")) {
|
|
462
|
+
return "success";
|
|
463
|
+
}
|
|
464
|
+
if (result.startsWith("🚫")) {
|
|
465
|
+
return "skipped";
|
|
466
|
+
}
|
|
467
|
+
return "failed";
|
|
468
|
+
}
|
|
469
|
+
function summarizeUpdateResult(result) {
|
|
470
|
+
return selectUpdateSummaryLines(result).join(" | ");
|
|
471
|
+
}
|
|
472
|
+
function summarizeReviewResult(result) {
|
|
473
|
+
return selectReviewSummaryLines(result).join(" | ");
|
|
474
|
+
}
|
|
475
|
+
function selectUpdateSummaryLines(result) {
|
|
476
|
+
const lines = normalizeResultLines(result);
|
|
477
|
+
const head = lines.slice(0, 2);
|
|
478
|
+
const highlights = lines.filter((line) => /^🏁/.test(line));
|
|
479
|
+
return dedupeLines([...head, ...highlights]).slice(0, 6);
|
|
480
|
+
}
|
|
481
|
+
function selectReviewSummaryLines(result) {
|
|
482
|
+
const lines = normalizeResultLines(result);
|
|
483
|
+
const head = lines.slice(0, 2);
|
|
484
|
+
const highlights = lines.filter((line) => /^(🧭|复盘数量:|关键位验证:|明日处理:|⚠️ 收盘复盘失败|⚠️ 收盘分析\/回测失败)/.test(line));
|
|
485
|
+
return dedupeLines([...head, ...highlights]).slice(0, 6);
|
|
486
|
+
}
|
|
487
|
+
function selectUpdateNotificationLines(result) {
|
|
488
|
+
const lines = normalizeResultLines(result);
|
|
489
|
+
const head = lines.slice(0, 4);
|
|
490
|
+
const highlights = lines.filter((line) => /^🏁/.test(line));
|
|
491
|
+
return dedupeLines([...head, ...highlights]).slice(0, 12);
|
|
492
|
+
}
|
|
493
|
+
function selectReviewNotificationLines(result) {
|
|
494
|
+
const lines = normalizeResultLines(result);
|
|
495
|
+
const head = lines.slice(0, 4);
|
|
496
|
+
const highlights = lines.filter((line) => /^(🧭|复盘数量:|关键位验证:|明日处理:|⚠️ 收盘复盘失败|⚠️ 收盘分析\/回测失败)/.test(line));
|
|
497
|
+
return dedupeLines([...head, ...highlights]).slice(0, 12);
|
|
498
|
+
}
|
|
499
|
+
function normalizeResultLines(result) {
|
|
500
|
+
return result
|
|
501
|
+
.split("\n")
|
|
502
|
+
.map((line) => line.trim())
|
|
503
|
+
.filter(Boolean);
|
|
504
|
+
}
|
|
505
|
+
function dedupeLines(lines) {
|
|
506
|
+
return [...new Set(lines)];
|
|
507
|
+
}
|
|
508
|
+
function formatResultType(type) {
|
|
509
|
+
switch (type) {
|
|
510
|
+
case "success":
|
|
511
|
+
return "成功";
|
|
512
|
+
case "skipped":
|
|
513
|
+
return "跳过";
|
|
514
|
+
case "failed":
|
|
515
|
+
return "失败";
|
|
516
|
+
default:
|
|
517
|
+
return "暂无";
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function formatProcessState(state) {
|
|
521
|
+
if (!state.running) {
|
|
522
|
+
return "⭕ 未启动";
|
|
523
|
+
}
|
|
524
|
+
if (state.workerPid == null) {
|
|
525
|
+
return state.startedAt ? `✅ 运行中 (启动于 ${state.startedAt})` : "✅ 运行中";
|
|
526
|
+
}
|
|
527
|
+
if (!isPidAlive(state.workerPid)) {
|
|
528
|
+
return `⚠️ 状态残留 (PID=${state.workerPid} 已不存在)`;
|
|
529
|
+
}
|
|
530
|
+
return state.startedAt
|
|
531
|
+
? `✅ 运行中 (PID=${state.workerPid}, 启动于 ${state.startedAt})`
|
|
532
|
+
: `✅ 运行中 (PID=${state.workerPid})`;
|
|
533
|
+
}
|
|
534
|
+
function formatRuntimeHost(state) {
|
|
535
|
+
return state.runtimeHost === "project_scheduler"
|
|
536
|
+
? "project_scheduler"
|
|
537
|
+
: state.runtimeHost === "plugin_service"
|
|
538
|
+
? "plugin_service"
|
|
539
|
+
: "unknown";
|
|
540
|
+
}
|
|
541
|
+
function hasCompletedScheduledWindow(successDate, successAt, today, readyTime) {
|
|
542
|
+
return successDate === today && extractChinaTime(successAt) >= readyTime;
|
|
543
|
+
}
|
|
544
|
+
function extractChinaTime(dateTime) {
|
|
545
|
+
return dateTime?.slice(11, 16) ?? "00:00";
|
|
546
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MonitorService } from "../services/monitor-service.js";
|
|
2
|
+
export declare class RealtimeMonitorWorker {
|
|
3
|
+
private readonly monitorService;
|
|
4
|
+
private readonly intervalMs;
|
|
5
|
+
constructor(monitorService: MonitorService, intervalMs: number);
|
|
6
|
+
runOnce(): Promise<number>;
|
|
7
|
+
runLoop(signal?: AbortSignal, runtimeHost?: "plugin_service" | "fallback_process"): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { sleepWithAbort } from "../utils/abortable-sleep.js";
|
|
2
|
+
export class RealtimeMonitorWorker {
|
|
3
|
+
monitorService;
|
|
4
|
+
intervalMs;
|
|
5
|
+
constructor(monitorService, intervalMs) {
|
|
6
|
+
this.monitorService = monitorService;
|
|
7
|
+
this.intervalMs = intervalMs;
|
|
8
|
+
}
|
|
9
|
+
async runOnce() {
|
|
10
|
+
const state = await this.monitorService.getState();
|
|
11
|
+
if (!state.running) {
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
return this.monitorService.runMonitorOnce();
|
|
15
|
+
}
|
|
16
|
+
async runLoop(signal, runtimeHost) {
|
|
17
|
+
while (!signal?.aborted) {
|
|
18
|
+
await this.monitorService.recordHeartbeat(runtimeHost);
|
|
19
|
+
try {
|
|
20
|
+
await this.runOnce();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
await this.monitorService.recordLoopError(error);
|
|
24
|
+
}
|
|
25
|
+
await sleepWithAbort(this.intervalMs, signal);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { PluginConfig } from "./config/schema.js";
|
|
2
|
+
import { Database } from "./storage/db.js";
|
|
3
|
+
import { WatchlistService } from "./services/watchlist-service.js";
|
|
4
|
+
import { MonitorService } from "./services/monitor-service.js";
|
|
5
|
+
import { AlertService } from "./services/alert-service.js";
|
|
6
|
+
import type { LocalTool, OpenClawPluginConfig, OpenClawPluginRuntime, RegisteredService } from "./runtime/plugin-api.js";
|
|
7
|
+
import { RealtimeMonitorWorker } from "./background/realtime-monitor.worker.js";
|
|
8
|
+
import { DailyUpdateWorker } from "./background/daily-update.worker.js";
|
|
9
|
+
import type { WatchlistItem } from "./types/domain.js";
|
|
10
|
+
export interface AppContext {
|
|
11
|
+
config: PluginConfig;
|
|
12
|
+
tools: LocalTool[];
|
|
13
|
+
backgroundServices: RegisteredService[];
|
|
14
|
+
runtime: {
|
|
15
|
+
configSource: "openclaw_plugin" | "local_config";
|
|
16
|
+
pluginManagedServices: boolean;
|
|
17
|
+
openclawConfig?: OpenClawPluginConfig;
|
|
18
|
+
pluginRuntime?: OpenClawPluginRuntime;
|
|
19
|
+
};
|
|
20
|
+
services: {
|
|
21
|
+
alertService: AlertService;
|
|
22
|
+
monitorService: MonitorService;
|
|
23
|
+
realtimeMonitorWorker: RealtimeMonitorWorker;
|
|
24
|
+
dailyUpdateWorker: DailyUpdateWorker;
|
|
25
|
+
watchlistService: WatchlistService;
|
|
26
|
+
database: Database;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare function createAppContext(config: PluginConfig, options?: {
|
|
30
|
+
pluginManagedServices?: boolean;
|
|
31
|
+
configSource?: "openclaw_plugin" | "local_config";
|
|
32
|
+
openclawConfig?: OpenClawPluginConfig;
|
|
33
|
+
pluginRuntime?: OpenClawPluginRuntime;
|
|
34
|
+
}): AppContext;
|
|
35
|
+
export interface WatchlistDebugSnapshot {
|
|
36
|
+
databasePath: string;
|
|
37
|
+
calendarFile: string;
|
|
38
|
+
requestInterval: number;
|
|
39
|
+
configSource: "openclaw_plugin" | "local_config";
|
|
40
|
+
pid: number;
|
|
41
|
+
watchlistTableExists: boolean;
|
|
42
|
+
watchlistCount: number;
|
|
43
|
+
watchlistPreview: WatchlistItem[];
|
|
44
|
+
}
|
|
45
|
+
export declare function buildWatchlistDebugSnapshot(app: AppContext): Promise<WatchlistDebugSnapshot>;
|