opencandle 0.4.0 → 0.6.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.
Files changed (564) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +186 -117
  3. package/dist/analysts/contracts.d.ts +1 -3
  4. package/dist/analysts/contracts.js +1 -11
  5. package/dist/analysts/contracts.js.map +1 -1
  6. package/dist/analysts/orchestrator.d.ts +1 -3
  7. package/dist/analysts/orchestrator.js +1 -26
  8. package/dist/analysts/orchestrator.js.map +1 -1
  9. package/dist/cli.js +32 -8
  10. package/dist/cli.js.map +1 -1
  11. package/dist/config.d.ts +19 -3
  12. package/dist/config.js +69 -3
  13. package/dist/config.js.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/infra/browser.d.ts +1 -3
  18. package/dist/infra/browser.js +4 -2
  19. package/dist/infra/browser.js.map +1 -1
  20. package/dist/infra/cache.d.ts +8 -11
  21. package/dist/infra/cache.js +17 -15
  22. package/dist/infra/cache.js.map +1 -1
  23. package/dist/infra/http-client.d.ts +4 -1
  24. package/dist/infra/http-client.js +59 -6
  25. package/dist/infra/http-client.js.map +1 -1
  26. package/dist/infra/index.d.ts +3 -3
  27. package/dist/infra/index.js +3 -3
  28. package/dist/infra/index.js.map +1 -1
  29. package/dist/infra/native-dependencies.js +2 -2
  30. package/dist/infra/native-dependencies.js.map +1 -1
  31. package/dist/infra/node-version.js.map +1 -1
  32. package/dist/infra/opencandle-paths.d.ts +0 -3
  33. package/dist/infra/opencandle-paths.js +4 -11
  34. package/dist/infra/opencandle-paths.js.map +1 -1
  35. package/dist/infra/rate-limiter.d.ts +4 -0
  36. package/dist/infra/rate-limiter.js +17 -10
  37. package/dist/infra/rate-limiter.js.map +1 -1
  38. package/dist/market-state/alert-conditions.d.ts +34 -0
  39. package/dist/market-state/alert-conditions.js +23 -0
  40. package/dist/market-state/alert-conditions.js.map +1 -0
  41. package/dist/market-state/alert-runner.d.ts +55 -0
  42. package/dist/market-state/alert-runner.js +634 -0
  43. package/dist/market-state/alert-runner.js.map +1 -0
  44. package/dist/market-state/daily-report.d.ts +26 -0
  45. package/dist/market-state/daily-report.js +179 -0
  46. package/dist/market-state/daily-report.js.map +1 -0
  47. package/dist/market-state/local-automation-service.d.ts +25 -0
  48. package/dist/market-state/local-automation-service.js +119 -0
  49. package/dist/market-state/local-automation-service.js.map +1 -0
  50. package/dist/market-state/notification-delivery.d.ts +14 -0
  51. package/dist/market-state/notification-delivery.js +139 -0
  52. package/dist/market-state/notification-delivery.js.map +1 -0
  53. package/dist/market-state/resolve-for-mutation.d.ts +10 -0
  54. package/dist/market-state/resolve-for-mutation.js +15 -0
  55. package/dist/market-state/resolve-for-mutation.js.map +1 -0
  56. package/dist/market-state/resolve.d.ts +14 -0
  57. package/dist/market-state/resolve.js +89 -0
  58. package/dist/market-state/resolve.js.map +1 -0
  59. package/dist/market-state/service.d.ts +527 -0
  60. package/dist/market-state/service.js +1099 -0
  61. package/dist/market-state/service.js.map +1 -0
  62. package/dist/memory/index.d.ts +7 -7
  63. package/dist/memory/index.js +6 -6
  64. package/dist/memory/index.js.map +1 -1
  65. package/dist/memory/manager.d.ts +9 -0
  66. package/dist/memory/manager.js +39 -22
  67. package/dist/memory/manager.js.map +1 -1
  68. package/dist/memory/retrieval.js +7 -4
  69. package/dist/memory/retrieval.js.map +1 -1
  70. package/dist/memory/sqlite.js +385 -3
  71. package/dist/memory/sqlite.js.map +1 -1
  72. package/dist/memory/storage.d.ts +3 -2
  73. package/dist/memory/storage.js +1 -2
  74. package/dist/memory/storage.js.map +1 -1
  75. package/dist/memory/tool-defaults.js +64 -28
  76. package/dist/memory/tool-defaults.js.map +1 -1
  77. package/dist/memory/types.js +4 -0
  78. package/dist/memory/types.js.map +1 -1
  79. package/dist/monitor.d.ts +2 -0
  80. package/dist/monitor.js +104 -0
  81. package/dist/monitor.js.map +1 -0
  82. package/dist/onboarding/connect.js +4 -6
  83. package/dist/onboarding/connect.js.map +1 -1
  84. package/dist/onboarding/credential-interceptor.js +1 -1
  85. package/dist/onboarding/credential-interceptor.js.map +1 -1
  86. package/dist/onboarding/degradation-accumulator.js +1 -3
  87. package/dist/onboarding/degradation-accumulator.js.map +1 -1
  88. package/dist/onboarding/providers.js +3 -16
  89. package/dist/onboarding/providers.js.map +1 -1
  90. package/dist/onboarding/state.js.map +1 -1
  91. package/dist/onboarding/tool-helpers.js +1 -1
  92. package/dist/onboarding/tool-helpers.js.map +1 -1
  93. package/dist/onboarding/tool-tags.js +6 -4
  94. package/dist/onboarding/tool-tags.js.map +1 -1
  95. package/dist/onboarding/validation.js +1 -1
  96. package/dist/onboarding/validation.js.map +1 -1
  97. package/dist/pi/opencandle-extension.d.ts +8 -0
  98. package/dist/pi/opencandle-extension.js +637 -59
  99. package/dist/pi/opencandle-extension.js.map +1 -1
  100. package/dist/pi/session.d.ts +1 -1
  101. package/dist/pi/session.js +3 -1
  102. package/dist/pi/session.js.map +1 -1
  103. package/dist/pi/setup.js +17 -2
  104. package/dist/pi/setup.js.map +1 -1
  105. package/dist/pi/tool-adapter.js +5 -2
  106. package/dist/pi/tool-adapter.js.map +1 -1
  107. package/dist/prompts/context-builder.d.ts +18 -3
  108. package/dist/prompts/context-builder.js +117 -18
  109. package/dist/prompts/context-builder.js.map +1 -1
  110. package/dist/prompts/disclaimer.js +1 -1
  111. package/dist/prompts/disclaimer.js.map +1 -1
  112. package/dist/prompts/policy-cards.d.ts +13 -0
  113. package/dist/prompts/policy-cards.js +197 -0
  114. package/dist/prompts/policy-cards.js.map +1 -0
  115. package/dist/prompts/sections.d.ts +1 -1
  116. package/dist/prompts/sections.js +3 -3
  117. package/dist/prompts/sections.js.map +1 -1
  118. package/dist/prompts/symbol-preflight.d.ts +20 -0
  119. package/dist/prompts/symbol-preflight.js +49 -0
  120. package/dist/prompts/symbol-preflight.js.map +1 -0
  121. package/dist/prompts/workflow-prompts.d.ts +1 -1
  122. package/dist/prompts/workflow-prompts.js +209 -19
  123. package/dist/prompts/workflow-prompts.js.map +1 -1
  124. package/dist/providers/alpha-vantage.d.ts +1 -1
  125. package/dist/providers/alpha-vantage.js +49 -8
  126. package/dist/providers/alpha-vantage.js.map +1 -1
  127. package/dist/providers/coingecko.js +1 -1
  128. package/dist/providers/coingecko.js.map +1 -1
  129. package/dist/providers/errors.d.ts +5 -0
  130. package/dist/providers/errors.js +11 -0
  131. package/dist/providers/errors.js.map +1 -0
  132. package/dist/providers/exa-search.d.ts +2 -2
  133. package/dist/providers/exa-search.js +19 -11
  134. package/dist/providers/exa-search.js.map +1 -1
  135. package/dist/providers/fear-greed.js +1 -1
  136. package/dist/providers/fear-greed.js.map +1 -1
  137. package/dist/providers/finnhub.js +3 -5
  138. package/dist/providers/finnhub.js.map +1 -1
  139. package/dist/providers/fred.js +2 -2
  140. package/dist/providers/fred.js.map +1 -1
  141. package/dist/providers/index.d.ts +7 -6
  142. package/dist/providers/index.js +6 -5
  143. package/dist/providers/index.js.map +1 -1
  144. package/dist/providers/reddit.js +2 -2
  145. package/dist/providers/reddit.js.map +1 -1
  146. package/dist/providers/sec-edgar.d.ts +9 -1
  147. package/dist/providers/sec-edgar.js +181 -6
  148. package/dist/providers/sec-edgar.js.map +1 -1
  149. package/dist/providers/tradingview.d.ts +47 -0
  150. package/dist/providers/tradingview.js +275 -0
  151. package/dist/providers/tradingview.js.map +1 -0
  152. package/dist/providers/twitter.js +6 -8
  153. package/dist/providers/twitter.js.map +1 -1
  154. package/dist/providers/web-search.js +26 -12
  155. package/dist/providers/web-search.js.map +1 -1
  156. package/dist/providers/with-fallback.js +4 -2
  157. package/dist/providers/with-fallback.js.map +1 -1
  158. package/dist/providers/wrap-provider.d.ts +2 -3
  159. package/dist/providers/wrap-provider.js +14 -8
  160. package/dist/providers/wrap-provider.js.map +1 -1
  161. package/dist/providers/yahoo-finance.d.ts +3 -1
  162. package/dist/providers/yahoo-finance.js +226 -11
  163. package/dist/providers/yahoo-finance.js.map +1 -1
  164. package/dist/routing/classify-intent.d.ts +9 -0
  165. package/dist/routing/classify-intent.js +153 -3
  166. package/dist/routing/classify-intent.js.map +1 -1
  167. package/dist/routing/defaults.d.ts +1 -1
  168. package/dist/routing/defaults.js +3 -3
  169. package/dist/routing/defaults.js.map +1 -1
  170. package/dist/routing/entity-extractor.d.ts +2 -0
  171. package/dist/routing/entity-extractor.js +377 -26
  172. package/dist/routing/entity-extractor.js.map +1 -1
  173. package/dist/routing/fund-symbols.d.ts +2 -0
  174. package/dist/routing/fund-symbols.js +55 -0
  175. package/dist/routing/fund-symbols.js.map +1 -0
  176. package/dist/routing/horizon.d.ts +1 -0
  177. package/dist/routing/horizon.js +10 -0
  178. package/dist/routing/horizon.js.map +1 -0
  179. package/dist/routing/index.d.ts +12 -6
  180. package/dist/routing/index.js +8 -4
  181. package/dist/routing/index.js.map +1 -1
  182. package/dist/routing/legacy-rule-router.d.ts +9 -0
  183. package/dist/routing/legacy-rule-router.js +12 -0
  184. package/dist/routing/legacy-rule-router.js.map +1 -0
  185. package/dist/routing/planning.d.ts +54 -0
  186. package/dist/routing/planning.js +562 -0
  187. package/dist/routing/planning.js.map +1 -0
  188. package/dist/routing/route-manifest.d.ts +35 -0
  189. package/dist/routing/route-manifest.js +242 -0
  190. package/dist/routing/route-manifest.js.map +1 -0
  191. package/dist/routing/router-llm-client.js.map +1 -1
  192. package/dist/routing/router-prompt.js +46 -45
  193. package/dist/routing/router-prompt.js.map +1 -1
  194. package/dist/routing/router-types.d.ts +10 -0
  195. package/dist/routing/router.d.ts +1 -0
  196. package/dist/routing/router.js +572 -13
  197. package/dist/routing/router.js.map +1 -1
  198. package/dist/routing/slot-resolver.d.ts +1 -1
  199. package/dist/routing/slot-resolver.js +45 -7
  200. package/dist/routing/slot-resolver.js.map +1 -1
  201. package/dist/routing/symbol-disambiguator.d.ts +11 -0
  202. package/dist/routing/symbol-disambiguator.js +52 -0
  203. package/dist/routing/symbol-disambiguator.js.map +1 -0
  204. package/dist/routing/turn-context.d.ts +44 -0
  205. package/dist/routing/turn-context.js +45 -0
  206. package/dist/routing/turn-context.js.map +1 -0
  207. package/dist/routing/types.d.ts +15 -1
  208. package/dist/runtime/answer-contracts.d.ts +82 -0
  209. package/dist/runtime/answer-contracts.js +442 -0
  210. package/dist/runtime/answer-contracts.js.map +1 -0
  211. package/dist/runtime/artifact-contracts.d.ts +14 -0
  212. package/dist/runtime/artifact-contracts.js +57 -0
  213. package/dist/runtime/artifact-contracts.js.map +1 -0
  214. package/dist/runtime/planning-evidence.d.ts +99 -0
  215. package/dist/runtime/planning-evidence.js +466 -0
  216. package/dist/runtime/planning-evidence.js.map +1 -0
  217. package/dist/runtime/prompt-step.d.ts +1 -9
  218. package/dist/runtime/prompt-step.js +0 -10
  219. package/dist/runtime/prompt-step.js.map +1 -1
  220. package/dist/runtime/run-context.d.ts +5 -2
  221. package/dist/runtime/run-context.js +8 -1
  222. package/dist/runtime/run-context.js.map +1 -1
  223. package/dist/runtime/session-coordinator.d.ts +29 -3
  224. package/dist/runtime/session-coordinator.js +204 -31
  225. package/dist/runtime/session-coordinator.js.map +1 -1
  226. package/dist/runtime/session-title.d.ts +14 -0
  227. package/dist/runtime/session-title.js +50 -0
  228. package/dist/runtime/session-title.js.map +1 -0
  229. package/dist/runtime/tool-defaults-wrapper.js +1 -3
  230. package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
  231. package/dist/runtime/validation.js.map +1 -1
  232. package/dist/runtime/workflow-events.js.map +1 -1
  233. package/dist/runtime/workflow-runner.d.ts +3 -3
  234. package/dist/runtime/workflow-runner.js +1 -1
  235. package/dist/runtime/workflow-runner.js.map +1 -1
  236. package/dist/sentiment/adapters/finnhub.d.ts +1 -1
  237. package/dist/sentiment/adapters/finnhub.js +6 -1
  238. package/dist/sentiment/adapters/finnhub.js.map +1 -1
  239. package/dist/sentiment/adapters/reddit.d.ts +2 -2
  240. package/dist/sentiment/adapters/twitter.d.ts +1 -1
  241. package/dist/sentiment/adapters/web.d.ts +1 -1
  242. package/dist/sentiment/index.d.ts +9 -11
  243. package/dist/sentiment/index.js +9 -20
  244. package/dist/sentiment/index.js.map +1 -1
  245. package/dist/sentiment/keywords.js +26 -4
  246. package/dist/sentiment/keywords.js.map +1 -1
  247. package/dist/sentiment/pipeline.d.ts +2 -2
  248. package/dist/sentiment/pipeline.js +1 -1
  249. package/dist/sentiment/pipeline.js.map +1 -1
  250. package/dist/sentiment/scorer.js +1 -1
  251. package/dist/sentiment/store.d.ts +1 -1
  252. package/dist/sentiment/store.js +1 -1
  253. package/dist/sentiment/store.js.map +1 -1
  254. package/dist/sentiment/trends.d.ts +1 -1
  255. package/dist/sentiment/trends.js.map +1 -1
  256. package/dist/sentiment/types.js.map +1 -1
  257. package/dist/system-prompt.js +7 -3
  258. package/dist/system-prompt.js.map +1 -1
  259. package/dist/tool-kit.d.ts +7 -7
  260. package/dist/tool-kit.js +4 -4
  261. package/dist/tool-kit.js.map +1 -1
  262. package/dist/tools/fundamentals/company-overview.js +12 -7
  263. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  264. package/dist/tools/fundamentals/comps.js +19 -10
  265. package/dist/tools/fundamentals/comps.js.map +1 -1
  266. package/dist/tools/fundamentals/dcf.js +24 -12
  267. package/dist/tools/fundamentals/dcf.js.map +1 -1
  268. package/dist/tools/fundamentals/earnings.js +9 -4
  269. package/dist/tools/fundamentals/earnings.js.map +1 -1
  270. package/dist/tools/fundamentals/financials.js +9 -4
  271. package/dist/tools/fundamentals/financials.js.map +1 -1
  272. package/dist/tools/fundamentals/sec-filings.d.ts +1 -0
  273. package/dist/tools/fundamentals/sec-filings.js +36 -4
  274. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  275. package/dist/tools/index.d.ts +23 -18
  276. package/dist/tools/index.js +53 -38
  277. package/dist/tools/index.js.map +1 -1
  278. package/dist/tools/interaction/ask-user.js +15 -3
  279. package/dist/tools/interaction/ask-user.js.map +1 -1
  280. package/dist/tools/interaction/twitter-login.js +13 -3
  281. package/dist/tools/interaction/twitter-login.js.map +1 -1
  282. package/dist/tools/macro/fear-greed.js +1 -1
  283. package/dist/tools/macro/fear-greed.js.map +1 -1
  284. package/dist/tools/macro/fred-data.d.ts +1 -1
  285. package/dist/tools/macro/fred-data.js +44 -9
  286. package/dist/tools/macro/fred-data.js.map +1 -1
  287. package/dist/tools/market/crypto-history.js +21 -3
  288. package/dist/tools/market/crypto-history.js.map +1 -1
  289. package/dist/tools/market/crypto-price.js +4 -2
  290. package/dist/tools/market/crypto-price.js.map +1 -1
  291. package/dist/tools/market/screen-stocks.d.ts +18 -0
  292. package/dist/tools/market/screen-stocks.js +252 -0
  293. package/dist/tools/market/screen-stocks.js.map +1 -0
  294. package/dist/tools/market/search-ticker.js +161 -9
  295. package/dist/tools/market/search-ticker.js.map +1 -1
  296. package/dist/tools/market/stock-history.d.ts +2 -2
  297. package/dist/tools/market/stock-history.js +27 -8
  298. package/dist/tools/market/stock-history.js.map +1 -1
  299. package/dist/tools/market/stock-quote.js +6 -4
  300. package/dist/tools/market/stock-quote.js.map +1 -1
  301. package/dist/tools/options/greeks.js +1 -2
  302. package/dist/tools/options/greeks.js.map +1 -1
  303. package/dist/tools/options/option-chain.js +27 -9
  304. package/dist/tools/options/option-chain.js.map +1 -1
  305. package/dist/tools/portfolio/alerts.d.ts +15 -0
  306. package/dist/tools/portfolio/alerts.js +357 -0
  307. package/dist/tools/portfolio/alerts.js.map +1 -0
  308. package/dist/tools/portfolio/correlation.d.ts +1 -1
  309. package/dist/tools/portfolio/correlation.js +34 -14
  310. package/dist/tools/portfolio/correlation.js.map +1 -1
  311. package/dist/tools/portfolio/daily-report.d.ts +8 -0
  312. package/dist/tools/portfolio/daily-report.js +83 -0
  313. package/dist/tools/portfolio/daily-report.js.map +1 -0
  314. package/dist/tools/portfolio/holdings-overlap.d.ts +8 -0
  315. package/dist/tools/portfolio/holdings-overlap.js +112 -0
  316. package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
  317. package/dist/tools/portfolio/notifications.d.ts +7 -0
  318. package/dist/tools/portfolio/notifications.js +43 -0
  319. package/dist/tools/portfolio/notifications.js.map +1 -0
  320. package/dist/tools/portfolio/predictions.d.ts +12 -6
  321. package/dist/tools/portfolio/predictions.js +338 -88
  322. package/dist/tools/portfolio/predictions.js.map +1 -1
  323. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  324. package/dist/tools/portfolio/risk-analysis.js +46 -7
  325. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  326. package/dist/tools/portfolio/tracker.d.ts +4 -3
  327. package/dist/tools/portfolio/tracker.js +247 -102
  328. package/dist/tools/portfolio/tracker.js.map +1 -1
  329. package/dist/tools/portfolio/watchlist.d.ts +6 -4
  330. package/dist/tools/portfolio/watchlist.js +209 -101
  331. package/dist/tools/portfolio/watchlist.js.map +1 -1
  332. package/dist/tools/sentiment/reddit-sentiment.js +24 -11
  333. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  334. package/dist/tools/sentiment/sentiment-summary.js +71 -14
  335. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  336. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  337. package/dist/tools/sentiment/sentiment-trend.js +12 -2
  338. package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
  339. package/dist/tools/sentiment/twitter-sentiment.js +13 -6
  340. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  341. package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
  342. package/dist/tools/sentiment/untrusted-text.js +17 -0
  343. package/dist/tools/sentiment/untrusted-text.js.map +1 -0
  344. package/dist/tools/sentiment/web-search.js +37 -12
  345. package/dist/tools/sentiment/web-search.js.map +1 -1
  346. package/dist/tools/sentiment/web-sentiment.js +16 -4
  347. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  348. package/dist/tools/technical/backtest.d.ts +3 -3
  349. package/dist/tools/technical/backtest.js +65 -44
  350. package/dist/tools/technical/backtest.js.map +1 -1
  351. package/dist/tools/technical/indicators.js +24 -8
  352. package/dist/tools/technical/indicators.js.map +1 -1
  353. package/dist/types/index.d.ts +3 -3
  354. package/dist/types/index.js.map +1 -1
  355. package/dist/types/market.d.ts +1 -0
  356. package/dist/types/options.d.ts +10 -0
  357. package/dist/types/portfolio.d.ts +41 -4
  358. package/dist/workflows/compare-assets.d.ts +0 -3
  359. package/dist/workflows/compare-assets.js +55 -10
  360. package/dist/workflows/compare-assets.js.map +1 -1
  361. package/dist/workflows/index.d.ts +3 -4
  362. package/dist/workflows/index.js +3 -3
  363. package/dist/workflows/index.js.map +1 -1
  364. package/dist/workflows/options-screener.d.ts +0 -3
  365. package/dist/workflows/options-screener.js +88 -14
  366. package/dist/workflows/options-screener.js.map +1 -1
  367. package/dist/workflows/portfolio-builder.d.ts +0 -3
  368. package/dist/workflows/portfolio-builder.js +7 -11
  369. package/dist/workflows/portfolio-builder.js.map +1 -1
  370. package/gui/server/ask-user-bridge.ts +82 -0
  371. package/gui/server/automation-heartbeat.ts +97 -0
  372. package/gui/server/background-quotes.ts +97 -1
  373. package/gui/server/chat-event-adapter.ts +32 -10
  374. package/gui/server/chat-run-session.ts +16 -0
  375. package/gui/server/gui-session-manager.ts +5 -0
  376. package/gui/server/invoke-tool.ts +144 -1
  377. package/gui/server/live-chat-event-adapter.ts +21 -6
  378. package/gui/server/market-state-api.ts +315 -0
  379. package/gui/server/model-setup.ts +149 -2
  380. package/gui/server/private-api-access.ts +62 -0
  381. package/gui/server/projector.ts +58 -11
  382. package/gui/server/prompt-observation.ts +58 -0
  383. package/gui/server/quote-snapshot-store.ts +50 -0
  384. package/gui/server/server.ts +236 -376
  385. package/gui/server/session-actions.ts +186 -1
  386. package/gui/server/session-entry-wait.ts +81 -0
  387. package/gui/server/shutdown.ts +47 -0
  388. package/gui/server/tool-invoke-ack.ts +49 -0
  389. package/gui/server/tool-metadata.ts +23 -10
  390. package/gui/server/websocket.ts +13 -3
  391. package/gui/server/writer-lock.ts +6 -2
  392. package/gui/server/ws-hub.ts +292 -0
  393. package/gui/shared/chat-events.ts +16 -1
  394. package/gui/shared/event-reducer.ts +24 -6
  395. package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +1 -0
  396. package/gui/web/dist/assets/index-2KZtKBmu.css +1 -0
  397. package/gui/web/dist/assets/index-CveNgtDg.js +69 -0
  398. package/gui/web/dist/index.html +2 -2
  399. package/package.json +22 -12
  400. package/src/analysts/contracts.ts +10 -23
  401. package/src/analysts/orchestrator.ts +8 -43
  402. package/src/cli.ts +37 -13
  403. package/src/config.ts +99 -7
  404. package/src/index.ts +1 -1
  405. package/src/infra/browser.ts +4 -2
  406. package/src/infra/cache.ts +41 -30
  407. package/src/infra/http-client.ts +72 -6
  408. package/src/infra/index.ts +7 -10
  409. package/src/infra/native-dependencies.ts +8 -3
  410. package/src/infra/node-version.ts +3 -1
  411. package/src/infra/opencandle-paths.ts +3 -14
  412. package/src/infra/rate-limiter.ts +32 -20
  413. package/src/market-state/alert-conditions.ts +82 -0
  414. package/src/market-state/alert-runner.ts +863 -0
  415. package/src/market-state/daily-report.ts +247 -0
  416. package/src/market-state/local-automation-service.ts +162 -0
  417. package/src/market-state/notification-delivery.ts +158 -0
  418. package/src/market-state/resolve-for-mutation.ts +24 -0
  419. package/src/market-state/resolve.ts +112 -0
  420. package/src/market-state/service.ts +2344 -0
  421. package/src/memory/index.ts +7 -7
  422. package/src/memory/manager.ts +57 -26
  423. package/src/memory/retrieval.ts +8 -7
  424. package/src/memory/sqlite.ts +407 -6
  425. package/src/memory/storage.ts +8 -17
  426. package/src/memory/tool-defaults.ts +60 -39
  427. package/src/memory/types.ts +7 -3
  428. package/src/monitor.ts +121 -0
  429. package/src/onboarding/connect.ts +10 -33
  430. package/src/onboarding/credential-interceptor.ts +3 -15
  431. package/src/onboarding/degradation-accumulator.ts +1 -3
  432. package/src/onboarding/providers.ts +9 -40
  433. package/src/onboarding/state.ts +4 -15
  434. package/src/onboarding/tool-helpers.ts +2 -9
  435. package/src/onboarding/tool-tags.ts +6 -6
  436. package/src/onboarding/validation.ts +14 -20
  437. package/src/pi/opencandle-extension.ts +795 -120
  438. package/src/pi/session.ts +7 -5
  439. package/src/pi/setup.ts +61 -33
  440. package/src/pi/tool-adapter.ts +5 -2
  441. package/src/prompts/context-builder.ts +143 -21
  442. package/src/prompts/disclaimer.ts +1 -1
  443. package/src/prompts/policy-cards.ts +220 -0
  444. package/src/prompts/sections.ts +4 -4
  445. package/src/prompts/symbol-preflight.ts +80 -0
  446. package/src/prompts/workflow-prompts.ts +231 -28
  447. package/src/providers/alpha-vantage.ts +82 -40
  448. package/src/providers/coingecko.ts +2 -5
  449. package/src/providers/errors.ts +9 -0
  450. package/src/providers/exa-search.ts +24 -22
  451. package/src/providers/fear-greed.ts +1 -1
  452. package/src/providers/finnhub.ts +7 -6
  453. package/src/providers/fred.ts +3 -3
  454. package/src/providers/index.ts +14 -6
  455. package/src/providers/reddit.ts +17 -6
  456. package/src/providers/sec-edgar.ts +235 -5
  457. package/src/providers/tradingview.ts +399 -0
  458. package/src/providers/twitter.ts +6 -8
  459. package/src/providers/web-search.ts +30 -20
  460. package/src/providers/with-fallback.ts +8 -7
  461. package/src/providers/wrap-provider.ts +15 -10
  462. package/src/providers/yahoo-finance.ts +292 -20
  463. package/src/routing/classify-intent.ts +186 -4
  464. package/src/routing/defaults.ts +4 -4
  465. package/src/routing/entity-extractor.ts +428 -28
  466. package/src/routing/fund-symbols.ts +58 -0
  467. package/src/routing/horizon.ts +7 -0
  468. package/src/routing/index.ts +60 -16
  469. package/src/routing/legacy-rule-router.ts +13 -0
  470. package/src/routing/planning.ts +823 -0
  471. package/src/routing/route-manifest.ts +309 -0
  472. package/src/routing/router-llm-client.ts +4 -4
  473. package/src/routing/router-prompt.ts +52 -52
  474. package/src/routing/router-types.ts +18 -0
  475. package/src/routing/router.ts +717 -20
  476. package/src/routing/slot-resolver.ts +75 -14
  477. package/src/routing/symbol-disambiguator.ts +72 -0
  478. package/src/routing/turn-context.ts +108 -0
  479. package/src/routing/types.ts +15 -1
  480. package/src/runtime/answer-contracts.ts +672 -0
  481. package/src/runtime/artifact-contracts.ts +77 -0
  482. package/src/runtime/planning-evidence.ts +682 -0
  483. package/src/runtime/prompt-step.ts +1 -16
  484. package/src/runtime/run-context.ts +12 -2
  485. package/src/runtime/session-coordinator.ts +297 -56
  486. package/src/runtime/session-title.ts +60 -0
  487. package/src/runtime/tool-defaults-wrapper.ts +1 -3
  488. package/src/runtime/validation.ts +1 -4
  489. package/src/runtime/workflow-events.ts +7 -7
  490. package/src/runtime/workflow-runner.ts +5 -11
  491. package/src/sentiment/adapters/finnhub.ts +7 -2
  492. package/src/sentiment/adapters/reddit.ts +2 -2
  493. package/src/sentiment/adapters/twitter.ts +1 -1
  494. package/src/sentiment/adapters/web.ts +1 -1
  495. package/src/sentiment/index.ts +16 -26
  496. package/src/sentiment/keywords.ts +26 -4
  497. package/src/sentiment/pipeline.ts +15 -4
  498. package/src/sentiment/scorer.ts +1 -1
  499. package/src/sentiment/store.ts +2 -2
  500. package/src/sentiment/trends.ts +9 -3
  501. package/src/sentiment/types.ts +5 -4
  502. package/src/system-prompt.ts +7 -3
  503. package/src/tool-kit.ts +10 -9
  504. package/src/tools/fundamentals/company-overview.ts +20 -10
  505. package/src/tools/fundamentals/comps.ts +69 -56
  506. package/src/tools/fundamentals/dcf.ts +146 -96
  507. package/src/tools/fundamentals/earnings.ts +17 -7
  508. package/src/tools/fundamentals/financials.ts +17 -8
  509. package/src/tools/fundamentals/sec-filings.ts +52 -8
  510. package/src/tools/index.ts +53 -38
  511. package/src/tools/interaction/ask-user.ts +22 -10
  512. package/src/tools/interaction/twitter-login.ts +17 -5
  513. package/src/tools/macro/fear-greed.ts +2 -2
  514. package/src/tools/macro/fred-data.ts +80 -42
  515. package/src/tools/market/crypto-history.ts +25 -4
  516. package/src/tools/market/crypto-price.ts +7 -7
  517. package/src/tools/market/screen-stocks.ts +279 -0
  518. package/src/tools/market/search-ticker.ts +219 -18
  519. package/src/tools/market/stock-history.ts +38 -13
  520. package/src/tools/market/stock-quote.ts +11 -8
  521. package/src/tools/options/greeks.ts +5 -6
  522. package/src/tools/options/option-chain.ts +47 -18
  523. package/src/tools/portfolio/alerts.ts +457 -0
  524. package/src/tools/portfolio/correlation.ts +48 -21
  525. package/src/tools/portfolio/daily-report.ts +101 -0
  526. package/src/tools/portfolio/holdings-overlap.ts +139 -0
  527. package/src/tools/portfolio/notifications.ts +45 -0
  528. package/src/tools/portfolio/predictions.ts +407 -107
  529. package/src/tools/portfolio/risk-analysis.ts +47 -8
  530. package/src/tools/portfolio/tracker.ts +271 -110
  531. package/src/tools/portfolio/watchlist.ts +251 -116
  532. package/src/tools/sentiment/reddit-sentiment.ts +51 -25
  533. package/src/tools/sentiment/sentiment-summary.ts +116 -35
  534. package/src/tools/sentiment/sentiment-trend.ts +24 -7
  535. package/src/tools/sentiment/twitter-sentiment.ts +23 -16
  536. package/src/tools/sentiment/untrusted-text.ts +21 -0
  537. package/src/tools/sentiment/web-search.ts +52 -16
  538. package/src/tools/sentiment/web-sentiment.ts +27 -11
  539. package/src/tools/technical/backtest.ts +78 -47
  540. package/src/tools/technical/indicators.ts +40 -17
  541. package/src/types/index.ts +8 -3
  542. package/src/types/market.ts +1 -0
  543. package/src/types/options.ts +17 -0
  544. package/src/types/portfolio.ts +46 -4
  545. package/src/types/sentiment.ts +2 -2
  546. package/src/workflows/compare-assets.ts +67 -19
  547. package/src/workflows/index.ts +3 -4
  548. package/src/workflows/options-screener.ts +98 -22
  549. package/src/workflows/portfolio-builder.ts +40 -29
  550. package/dist/runtime/index.d.ts +0 -16
  551. package/dist/runtime/index.js +0 -10
  552. package/dist/runtime/index.js.map +0 -1
  553. package/dist/runtime/provider-ids.d.ts +0 -14
  554. package/dist/runtime/provider-ids.js +0 -14
  555. package/dist/runtime/provider-ids.js.map +0 -1
  556. package/dist/workflows/types.d.ts +0 -4
  557. package/dist/workflows/types.js +0 -2
  558. package/dist/workflows/types.js.map +0 -1
  559. package/gui/web/dist/assets/CatalogOverlay-D1ImSJTe.js +0 -1
  560. package/gui/web/dist/assets/index-DBrWq43L.css +0 -1
  561. package/gui/web/dist/assets/index-RflHaj0y.js +0 -67
  562. package/src/runtime/index.ts +0 -55
  563. package/src/runtime/provider-ids.ts +0 -15
  564. package/src/workflows/types.ts +0 -4
@@ -1,10 +1,10 @@
1
- export { initDatabase, initDefaultDatabase, getTableNames, getSchemaVersion } from "./sqlite.js";
2
- export { MemoryStorage } from "./storage.js";
3
- export type { WorkflowPreferences } from "./storage.js";
4
- export { buildMemoryContext } from "./retrieval.js";
5
- export { extractPreferences } from "./preference-extractor.js";
6
1
  export { MemoryManager } from "./manager.js";
7
- export { getAllDefaults, getDefaults, setDefault, clearDefault } from "./tool-defaults.js";
2
+ export { extractPreferences } from "./preference-extractor.js";
3
+ export { buildMemoryContext } from "./retrieval.js";
4
+ export { getSchemaVersion, getTableNames, initDatabase, initDefaultDatabase } from "./sqlite.js";
5
+ export type { WorkflowPreferences } from "./storage.js";
6
+ export { MemoryStorage } from "./storage.js";
8
7
  export type { ToolDefaults } from "./tool-defaults.js";
8
+ export { clearDefault, getAllDefaults, getDefaults, setDefault } from "./tool-defaults.js";
9
9
  export type { MemoryCategory, MemoryEntry } from "./types.js";
10
- export { isStale, STALENESS_THRESHOLDS, NEVER_TRUST_FROM_MEMORY } from "./types.js";
10
+ export { isStale, NEVER_TRUST_FROM_MEMORY, STALENESS_THRESHOLDS } from "./types.js";
@@ -1,12 +1,22 @@
1
1
  import type { MemoryStorage } from "./storage.js";
2
2
  import type { MemoryCategory, MemoryEntry } from "./types.js";
3
3
  import {
4
+ isStale,
4
5
  KEY_TO_CATEGORY,
5
- WORKFLOW_RELEVANT_CATEGORIES,
6
6
  NEVER_TRUST_FROM_MEMORY,
7
- isStale,
7
+ WORKFLOW_RELEVANT_CATEGORIES,
8
8
  } from "./types.js";
9
9
 
10
+ export interface FilteredMemoryEntry {
11
+ entry: MemoryEntry;
12
+ reason: "suppressed_by_user_slot" | "never_trust" | "stale" | "irrelevant_category";
13
+ }
14
+
15
+ export interface MemoryRetrievalResult {
16
+ entries: MemoryEntry[];
17
+ filtered: FilteredMemoryEntry[];
18
+ }
19
+
10
20
  /** Slot name → preference key(s) mapping for suppression. */
11
21
  const SLOT_TO_PREF_KEYS: Record<string, string[]> = {
12
22
  riskProfile: ["risk_profile"],
@@ -36,31 +46,39 @@ export class MemoryManager {
36
46
  overriddenSlots?: string[],
37
47
  now: Date = new Date(),
38
48
  ): MemoryEntry[] {
39
- const relevantCategories = WORKFLOW_RELEVANT_CATEGORIES[workflowType] ??
40
- WORKFLOW_RELEVANT_CATEGORIES["unclassified"];
49
+ return this.retrieveDetailed(workflowType, overriddenSlots, now).entries;
50
+ }
51
+
52
+ retrieveDetailed(
53
+ workflowType: string,
54
+ overriddenSlots?: string[],
55
+ now: Date = new Date(),
56
+ ): MemoryRetrievalResult {
57
+ const relevantCategories =
58
+ WORKFLOW_RELEVANT_CATEGORIES[workflowType] ?? WORKFLOW_RELEVANT_CATEGORIES.unclassified;
41
59
 
42
60
  // Build set of preference keys to suppress
43
61
  const suppressedKeys = new Set<string>();
44
62
  if (overriddenSlots) {
45
63
  for (const slot of overriddenSlots) {
46
64
  const keys = SLOT_TO_PREF_KEYS[slot];
47
- if (keys) keys.forEach((k) => suppressedKeys.add(k));
65
+ if (keys) {
66
+ for (const key of keys) {
67
+ suppressedKeys.add(key);
68
+ }
69
+ }
48
70
  }
49
71
  }
50
72
 
51
73
  const entries: MemoryEntry[] = [];
74
+ const filtered: FilteredMemoryEntry[] = [];
52
75
 
53
76
  // Preferences as investor_profile entries
54
77
  if (relevantCategories.includes("investor_profile")) {
55
78
  const prefs = this.storage.getPreferencesByNamespace("global");
56
79
  for (const pref of prefs) {
57
80
  const key = String(pref.key);
58
- if (suppressedKeys.has(key)) continue;
59
- if (NEVER_TRUST_FROM_MEMORY.has(key)) continue;
60
-
61
81
  const category = KEY_TO_CATEGORY[key] ?? "investor_profile";
62
- if (!relevantCategories.includes(category)) continue;
63
-
64
82
  const entry: MemoryEntry = {
65
83
  key,
66
84
  value: tryParseValue(String(pref.value_json ?? "")),
@@ -70,9 +88,23 @@ export class MemoryManager {
70
88
  source: pref.source != null ? String(pref.source) : undefined,
71
89
  };
72
90
 
73
- if (!isStale(entry, now)) {
74
- entries.push(entry);
91
+ if (suppressedKeys.has(key)) {
92
+ filtered.push({ entry, reason: "suppressed_by_user_slot" });
93
+ continue;
94
+ }
95
+ if (NEVER_TRUST_FROM_MEMORY.has(key)) {
96
+ filtered.push({ entry, reason: "never_trust" });
97
+ continue;
75
98
  }
99
+ if (!relevantCategories.includes(category)) {
100
+ filtered.push({ entry, reason: "irrelevant_category" });
101
+ continue;
102
+ }
103
+ if (isStale(entry, now)) {
104
+ filtered.push({ entry, reason: "stale" });
105
+ continue;
106
+ }
107
+ entries.push(entry);
76
108
  }
77
109
  }
78
110
 
@@ -90,30 +122,29 @@ export class MemoryManager {
90
122
  const recordedAt = String(run.created_at ?? now.toISOString());
91
123
  const entry: MemoryEntry = {
92
124
  key: `workflow_run_${run.id}`,
93
- value: run.output_summary
94
- ? `${wfType}: ${run.output_summary}`
95
- : wfType,
125
+ value: run.output_summary ? `${wfType}: ${run.output_summary}` : wfType,
96
126
  category: "workflow_history",
97
127
  recordedAt,
98
128
  };
99
129
 
100
- if (!isStale(entry, now)) {
101
- entries.push(entry);
130
+ if (isStale(entry, now)) {
131
+ filtered.push({ entry, reason: "stale" });
132
+ continue;
102
133
  }
134
+ entries.push(entry);
103
135
  }
104
136
  }
105
137
 
106
- return entries.slice(0, MAX_PREFERENCE_LINES + MAX_WORKFLOW_HISTORY_PER_TYPE * 4);
138
+ return {
139
+ entries: entries.slice(0, MAX_PREFERENCE_LINES + MAX_WORKFLOW_HISTORY_PER_TYPE * 4),
140
+ filtered,
141
+ };
107
142
  }
108
143
 
109
144
  /**
110
145
  * Build compact text context from retrieved memory entries.
111
146
  */
112
- buildContext(
113
- workflowType: string,
114
- overriddenSlots?: string[],
115
- now: Date = new Date(),
116
- ): string {
147
+ buildContext(workflowType: string, overriddenSlots?: string[], now: Date = new Date()): string {
117
148
  const entries = this.retrieve(workflowType, overriddenSlots, now);
118
149
  if (entries.length === 0) return "";
119
150
 
@@ -130,19 +161,19 @@ export class MemoryManager {
130
161
  const profileEntries = byCategory.get("investor_profile");
131
162
  if (profileEntries && profileEntries.length > 0) {
132
163
  const lines = profileEntries.map((e) => `- ${e.key}: ${e.value}`);
133
- sections.push("User Preferences:\n" + lines.join("\n"));
164
+ sections.push(`User Preferences:\n${lines.join("\n")}`);
134
165
  }
135
166
 
136
167
  const historyEntries = byCategory.get("workflow_history");
137
168
  if (historyEntries && historyEntries.length > 0) {
138
169
  const lines = historyEntries.map((e) => `- ${e.value} (${e.recordedAt})`);
139
- sections.push("Recent Workflows:\n" + lines.join("\n"));
170
+ sections.push(`Recent Workflows:\n${lines.join("\n")}`);
140
171
  }
141
172
 
142
173
  const feedbackEntries = byCategory.get("interaction_feedback");
143
174
  if (feedbackEntries && feedbackEntries.length > 0) {
144
175
  const lines = feedbackEntries.map((e) => `- ${e.key}: ${e.value}`);
145
- sections.push("Feedback:\n" + lines.join("\n"));
176
+ sections.push(`Feedback:\n${lines.join("\n")}`);
146
177
  }
147
178
 
148
179
  return sections.join("\n\n");
@@ -19,10 +19,7 @@ const SLOT_TO_PREF_KEYS: Record<string, string[]> = {
19
19
  * The corresponding preference keys will be excluded from memory context to avoid
20
20
  * conflicting provenance signals.
21
21
  */
22
- export function buildMemoryContext(
23
- storage: MemoryStorage,
24
- overriddenSlots?: string[],
25
- ): string {
22
+ export function buildMemoryContext(storage: MemoryStorage, overriddenSlots?: string[]): string {
26
23
  const sections: string[] = [];
27
24
 
28
25
  // Build set of preference keys to suppress
@@ -30,7 +27,11 @@ export function buildMemoryContext(
30
27
  if (overriddenSlots) {
31
28
  for (const slot of overriddenSlots) {
32
29
  const keys = SLOT_TO_PREF_KEYS[slot];
33
- if (keys) keys.forEach((k) => suppressedKeys.add(k));
30
+ if (keys) {
31
+ for (const key of keys) {
32
+ suppressedKeys.add(key);
33
+ }
34
+ }
34
35
  }
35
36
  }
36
37
 
@@ -43,7 +44,7 @@ export function buildMemoryContext(
43
44
  const value = tryParseJson(p.value_json as string);
44
45
  return `- ${p.key}: ${value}`;
45
46
  });
46
- sections.push("User Preferences:\n" + lines.join("\n"));
47
+ sections.push(`User Preferences:\n${lines.join("\n")}`);
47
48
  }
48
49
  }
49
50
 
@@ -54,7 +55,7 @@ export function buildMemoryContext(
54
55
  const summary = r.output_summary ? ` — ${r.output_summary}` : "";
55
56
  return `- ${r.workflow_type} (${r.created_at})${summary}`;
56
57
  });
57
- sections.push("Recent Workflows:\n" + lines.join("\n"));
58
+ sections.push(`Recent Workflows:\n${lines.join("\n")}`);
58
59
  }
59
60
 
60
61
  return sections.join("\n\n");
@@ -1,9 +1,9 @@
1
1
  import { mkdirSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import Database from "better-sqlite3";
4
- import { getStateDbPath } from "../infra/opencandle-paths.js";
4
+ import { ensureOpenCandleHomeDir, getStateDbPath } from "../infra/opencandle-paths.js";
5
5
 
6
- const CURRENT_SCHEMA_VERSION = 4;
6
+ const CURRENT_SCHEMA_VERSION = 7;
7
7
 
8
8
  const CURRENT_SCHEMA = `
9
9
  CREATE TABLE IF NOT EXISTS schema_version (
@@ -62,6 +62,294 @@ const CURRENT_SCHEMA = `
62
62
  set_at TEXT NOT NULL,
63
63
  PRIMARY KEY (tool_name, param_path)
64
64
  );
65
+
66
+ CREATE TABLE IF NOT EXISTS instruments (
67
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
68
+ symbol TEXT NOT NULL,
69
+ asset_type TEXT NOT NULL,
70
+ name TEXT,
71
+ exchange TEXT,
72
+ currency TEXT,
73
+ provider TEXT NOT NULL,
74
+ provider_metadata_json TEXT,
75
+ last_resolved_at TEXT NOT NULL,
76
+ created_at TEXT NOT NULL,
77
+ updated_at TEXT NOT NULL
78
+ );
79
+
80
+ CREATE INDEX IF NOT EXISTS idx_instruments_symbol ON instruments(symbol);
81
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_instruments_provider_identity
82
+ ON instruments(provider, symbol, asset_type, IFNULL(exchange, ''));
83
+
84
+ CREATE TABLE IF NOT EXISTS instrument_aliases (
85
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
86
+ instrument_id INTEGER NOT NULL,
87
+ source TEXT NOT NULL,
88
+ source_symbol TEXT NOT NULL,
89
+ source_exchange TEXT,
90
+ source_asset_type TEXT,
91
+ source_id TEXT,
92
+ raw_json TEXT,
93
+ created_at TEXT NOT NULL,
94
+ updated_at TEXT NOT NULL,
95
+ FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE RESTRICT
96
+ );
97
+
98
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_instrument_aliases_source_id
99
+ ON instrument_aliases(source, source_id)
100
+ WHERE source_id IS NOT NULL;
101
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_instrument_aliases_source_symbol
102
+ ON instrument_aliases(source, source_symbol, IFNULL(source_exchange, ''), IFNULL(source_asset_type, ''))
103
+ WHERE source_id IS NULL;
104
+
105
+ CREATE TABLE IF NOT EXISTS watchlists (
106
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
107
+ name TEXT NOT NULL,
108
+ is_default INTEGER NOT NULL DEFAULT 0,
109
+ created_at TEXT NOT NULL,
110
+ updated_at TEXT NOT NULL
111
+ );
112
+
113
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlists_one_default
114
+ ON watchlists(is_default)
115
+ WHERE is_default = 1;
116
+
117
+ CREATE TABLE IF NOT EXISTS watchlist_items (
118
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
119
+ watchlist_id INTEGER NOT NULL,
120
+ instrument_id INTEGER NOT NULL,
121
+ thesis TEXT,
122
+ notes TEXT,
123
+ tags_json TEXT,
124
+ target_price REAL,
125
+ stop_price REAL,
126
+ price_currency TEXT,
127
+ source TEXT,
128
+ source_row_id TEXT,
129
+ source_metadata_json TEXT,
130
+ created_at TEXT NOT NULL,
131
+ updated_at TEXT NOT NULL,
132
+ UNIQUE(watchlist_id, instrument_id),
133
+ FOREIGN KEY (watchlist_id) REFERENCES watchlists(id) ON DELETE CASCADE,
134
+ FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE RESTRICT
135
+ );
136
+
137
+ CREATE INDEX IF NOT EXISTS idx_watchlist_items_source_row
138
+ ON watchlist_items(source, source_row_id)
139
+ WHERE source IS NOT NULL AND source_row_id IS NOT NULL;
140
+
141
+ CREATE TABLE IF NOT EXISTS portfolios (
142
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
143
+ name TEXT NOT NULL,
144
+ base_currency TEXT NOT NULL DEFAULT 'USD',
145
+ is_default INTEGER NOT NULL DEFAULT 0,
146
+ created_at TEXT NOT NULL,
147
+ updated_at TEXT NOT NULL
148
+ );
149
+
150
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_portfolios_one_default
151
+ ON portfolios(is_default)
152
+ WHERE is_default = 1;
153
+
154
+ CREATE TABLE IF NOT EXISTS portfolio_lots (
155
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156
+ portfolio_id INTEGER NOT NULL,
157
+ instrument_id INTEGER NOT NULL,
158
+ quantity REAL NOT NULL,
159
+ avg_cost REAL NOT NULL,
160
+ currency TEXT NOT NULL,
161
+ opened_at TEXT,
162
+ notes TEXT,
163
+ source TEXT,
164
+ source_account_ref TEXT,
165
+ source_lot_id TEXT,
166
+ source_row_id TEXT,
167
+ source_metadata_json TEXT,
168
+ created_at TEXT NOT NULL,
169
+ updated_at TEXT NOT NULL,
170
+ FOREIGN KEY (portfolio_id) REFERENCES portfolios(id) ON DELETE CASCADE,
171
+ FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE RESTRICT
172
+ );
173
+
174
+ CREATE INDEX IF NOT EXISTS idx_portfolio_lots_source_row
175
+ ON portfolio_lots(source, source_row_id)
176
+ WHERE source IS NOT NULL AND source_row_id IS NOT NULL;
177
+ CREATE INDEX IF NOT EXISTS idx_portfolio_lots_source_lot
178
+ ON portfolio_lots(source, source_lot_id)
179
+ WHERE source IS NOT NULL AND source_lot_id IS NOT NULL;
180
+
181
+ CREATE TABLE IF NOT EXISTS prediction_records (
182
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
183
+ instrument_id INTEGER NOT NULL,
184
+ direction TEXT NOT NULL,
185
+ conviction REAL NOT NULL,
186
+ entry_price REAL NOT NULL,
187
+ target_price REAL,
188
+ opened_at TEXT NOT NULL,
189
+ expires_at TEXT NOT NULL,
190
+ status TEXT NOT NULL,
191
+ resolved_at TEXT,
192
+ result_json TEXT,
193
+ created_at TEXT NOT NULL,
194
+ updated_at TEXT NOT NULL,
195
+ FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE RESTRICT
196
+ );
197
+
198
+ CREATE TABLE IF NOT EXISTS alert_rules (
199
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
200
+ scope_type TEXT NOT NULL,
201
+ scope_id INTEGER,
202
+ instrument_id INTEGER,
203
+ condition_type TEXT NOT NULL,
204
+ condition_version INTEGER NOT NULL,
205
+ condition_json TEXT NOT NULL,
206
+ timeframe TEXT NOT NULL,
207
+ enabled INTEGER NOT NULL DEFAULT 1,
208
+ check_interval_seconds INTEGER,
209
+ next_check_at TEXT,
210
+ last_checked_at TEXT,
211
+ last_observed_json TEXT,
212
+ status TEXT NOT NULL DEFAULT 'active',
213
+ retrigger_mode TEXT NOT NULL DEFAULT 'recurring',
214
+ last_condition_state TEXT NOT NULL DEFAULT 'unknown',
215
+ rule_revision INTEGER NOT NULL DEFAULT 1,
216
+ arm_cycle_id INTEGER NOT NULL DEFAULT 1,
217
+ cooldown_seconds INTEGER,
218
+ last_triggered_at TEXT,
219
+ created_at TEXT NOT NULL,
220
+ updated_at TEXT NOT NULL,
221
+ FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE RESTRICT
222
+ );
223
+
224
+ CREATE TABLE IF NOT EXISTS alert_events (
225
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
226
+ alert_rule_id INTEGER NOT NULL,
227
+ instrument_id INTEGER,
228
+ observed_value_json TEXT,
229
+ triggered_at TEXT NOT NULL,
230
+ observed_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
231
+ provider_data_at TEXT,
232
+ source_provider TEXT,
233
+ cache_status TEXT NOT NULL DEFAULT 'live',
234
+ data_delay_ms INTEGER,
235
+ trigger_source TEXT NOT NULL DEFAULT 'manual',
236
+ dedupe_key TEXT,
237
+ status TEXT NOT NULL,
238
+ message TEXT,
239
+ FOREIGN KEY (alert_rule_id) REFERENCES alert_rules(id) ON DELETE CASCADE,
240
+ FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE RESTRICT
241
+ );
242
+
243
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_alert_events_dedupe
244
+ ON alert_events(dedupe_key)
245
+ WHERE dedupe_key IS NOT NULL;
246
+
247
+ CREATE TABLE IF NOT EXISTS alert_check_runs (
248
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
249
+ started_at TEXT NOT NULL,
250
+ completed_at TEXT,
251
+ status TEXT NOT NULL,
252
+ trigger_type TEXT NOT NULL,
253
+ checked_count INTEGER NOT NULL DEFAULT 0,
254
+ triggered_count INTEGER NOT NULL DEFAULT 0,
255
+ unavailable_count INTEGER NOT NULL DEFAULT 0,
256
+ owner_id TEXT,
257
+ error_json TEXT,
258
+ provider_status_json TEXT
259
+ );
260
+
261
+ CREATE TABLE IF NOT EXISTS report_templates (
262
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
263
+ name TEXT NOT NULL,
264
+ report_type TEXT NOT NULL,
265
+ cadence TEXT NOT NULL,
266
+ timezone TEXT NOT NULL,
267
+ local_time TEXT NOT NULL,
268
+ config_json TEXT NOT NULL,
269
+ enabled INTEGER NOT NULL DEFAULT 1,
270
+ last_run_at TEXT,
271
+ next_run_at TEXT,
272
+ created_at TEXT NOT NULL,
273
+ updated_at TEXT NOT NULL
274
+ );
275
+
276
+ CREATE TABLE IF NOT EXISTS report_runs (
277
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
278
+ template_id INTEGER,
279
+ started_at TEXT NOT NULL,
280
+ completed_at TEXT,
281
+ status TEXT NOT NULL,
282
+ trigger_type TEXT NOT NULL DEFAULT 'manual',
283
+ scheduled_for TEXT,
284
+ owner_id TEXT,
285
+ artifact_path TEXT,
286
+ summary_json TEXT,
287
+ errors_json TEXT,
288
+ FOREIGN KEY (template_id) REFERENCES report_templates(id) ON DELETE SET NULL
289
+ );
290
+
291
+ CREATE TABLE IF NOT EXISTS automation_runner_leases (
292
+ id INTEGER PRIMARY KEY CHECK (id = 1),
293
+ owner_id TEXT NOT NULL,
294
+ owner_kind TEXT NOT NULL,
295
+ acquired_at TEXT NOT NULL,
296
+ heartbeat_at TEXT NOT NULL,
297
+ expires_at TEXT NOT NULL
298
+ );
299
+
300
+ CREATE TABLE IF NOT EXISTS notification_events (
301
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
302
+ source_type TEXT NOT NULL,
303
+ source_id INTEGER,
304
+ severity TEXT NOT NULL,
305
+ title TEXT NOT NULL,
306
+ body TEXT NOT NULL,
307
+ payload_json TEXT,
308
+ status TEXT NOT NULL,
309
+ created_at TEXT NOT NULL,
310
+ acknowledged_at TEXT
311
+ );
312
+
313
+ CREATE TABLE IF NOT EXISTS notification_delivery_attempts (
314
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
315
+ notification_event_id INTEGER NOT NULL,
316
+ channel TEXT NOT NULL,
317
+ status TEXT NOT NULL,
318
+ attempted_at TEXT NOT NULL,
319
+ completed_at TEXT,
320
+ response_json TEXT,
321
+ error TEXT,
322
+ FOREIGN KEY (notification_event_id) REFERENCES notification_events(id) ON DELETE CASCADE
323
+ );
324
+
325
+ CREATE TABLE IF NOT EXISTS import_batches (
326
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
327
+ source TEXT NOT NULL,
328
+ source_label TEXT,
329
+ imported_at TEXT NOT NULL,
330
+ status TEXT NOT NULL,
331
+ raw_metadata_json TEXT
332
+ );
333
+
334
+ CREATE TABLE IF NOT EXISTS import_rows (
335
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
336
+ batch_id INTEGER NOT NULL,
337
+ row_type TEXT NOT NULL,
338
+ source_symbol TEXT,
339
+ source_row_id TEXT,
340
+ source_account_ref TEXT,
341
+ normalized_instrument_id INTEGER,
342
+ status TEXT NOT NULL,
343
+ error TEXT,
344
+ source_metadata_json TEXT,
345
+ raw_json TEXT,
346
+ FOREIGN KEY (batch_id) REFERENCES import_batches(id) ON DELETE CASCADE,
347
+ FOREIGN KEY (normalized_instrument_id) REFERENCES instruments(id) ON DELETE SET NULL
348
+ );
349
+
350
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_import_rows_batch_source_row
351
+ ON import_rows(batch_id, source_row_id)
352
+ WHERE source_row_id IS NOT NULL;
65
353
  `;
66
354
 
67
355
  export function initDatabase(path: string): Database.Database {
@@ -70,6 +358,7 @@ export function initDatabase(path: string): Database.Database {
70
358
  }
71
359
  const db = new Database(path);
72
360
  db.pragma("journal_mode = WAL");
361
+ db.pragma("busy_timeout = 5000");
73
362
  db.pragma("foreign_keys = ON");
74
363
  ensureCurrentSchema(db);
75
364
 
@@ -77,6 +366,7 @@ export function initDatabase(path: string): Database.Database {
77
366
  }
78
367
 
79
368
  export function initDefaultDatabase(): Database.Database {
369
+ ensureOpenCandleHomeDir();
80
370
  return initDatabase(getStateDbPath());
81
371
  }
82
372
 
@@ -90,15 +380,39 @@ function ensureCurrentSchema(db: Database.Database): void {
90
380
  return;
91
381
  }
92
382
 
383
+ if (currentVersion === 6) {
384
+ migrateV6ToV7(db);
385
+ return;
386
+ }
387
+
388
+ if (currentVersion === 5) {
389
+ migrateV5ToV6(db);
390
+ migrateV6ToV7(db);
391
+ return;
392
+ }
393
+
394
+ if (currentVersion === 4) {
395
+ migrateV4ToV5(db);
396
+ migrateV5ToV6(db);
397
+ migrateV6ToV7(db);
398
+ return;
399
+ }
400
+
93
401
  if (currentVersion === 3) {
94
402
  migrateV3ToV4(db);
403
+ migrateV4ToV5(db);
404
+ migrateV5ToV6(db);
405
+ migrateV6ToV7(db);
95
406
  return;
96
407
  }
97
408
 
98
- // Additive v2 → v3 → v4 migration without dropping data.
409
+ // Additive v2 → v3 → v4 → v5 → v6 migration without dropping data.
99
410
  if (currentVersion === 2) {
100
411
  migrateV2ToV3(db);
101
412
  migrateV3ToV4(db);
413
+ migrateV4ToV5(db);
414
+ migrateV5ToV6(db);
415
+ migrateV6ToV7(db);
102
416
  return;
103
417
  }
104
418
 
@@ -112,9 +426,7 @@ function migrateV2ToV3(db: Database.Database): void {
112
426
  );
113
427
 
114
428
  if (!cols.includes("turn_type")) {
115
- db.exec(
116
- `ALTER TABLE workflow_runs ADD COLUMN turn_type TEXT NOT NULL DEFAULT 'workflow'`,
117
- );
429
+ db.exec(`ALTER TABLE workflow_runs ADD COLUMN turn_type TEXT NOT NULL DEFAULT 'workflow'`);
118
430
  }
119
431
 
120
432
  // Ensure any tables or indexes added between versions are present.
@@ -125,12 +437,84 @@ function migrateV2ToV3(db: Database.Database): void {
125
437
  }
126
438
 
127
439
  function migrateV3ToV4(db: Database.Database): void {
440
+ // CURRENT_SCHEMA indexes alert_events(dedupe_key); pre-v7 tables lack the column.
441
+ addColumnIfMissing(db, "alert_events", "dedupe_key", "TEXT");
442
+ db.exec(CURRENT_SCHEMA);
443
+
444
+ db.prepare("DELETE FROM schema_version").run();
445
+ db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(4);
446
+ }
447
+
448
+ function migrateV4ToV5(db: Database.Database): void {
449
+ addColumnIfMissing(db, "alert_events", "dedupe_key", "TEXT");
450
+ db.exec(CURRENT_SCHEMA);
451
+
452
+ db.prepare("DELETE FROM schema_version").run();
453
+ db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(5);
454
+ }
455
+
456
+ function migrateV5ToV6(db: Database.Database): void {
457
+ addColumnIfMissing(db, "import_rows", "source_row_id", "TEXT");
458
+ addColumnIfMissing(db, "import_rows", "source_account_ref", "TEXT");
459
+ addColumnIfMissing(db, "import_rows", "source_metadata_json", "TEXT");
460
+
461
+ db.prepare("DELETE FROM schema_version").run();
462
+ db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(6);
463
+ }
464
+
465
+ function migrateV6ToV7(db: Database.Database): void {
466
+ addColumnIfMissing(db, "alert_rules", "status", "TEXT NOT NULL DEFAULT 'active'");
467
+ addColumnIfMissing(db, "alert_rules", "retrigger_mode", "TEXT NOT NULL DEFAULT 'recurring'");
468
+ addColumnIfMissing(db, "alert_rules", "last_condition_state", "TEXT NOT NULL DEFAULT 'unknown'");
469
+ addColumnIfMissing(db, "alert_rules", "rule_revision", "INTEGER NOT NULL DEFAULT 1");
470
+ addColumnIfMissing(db, "alert_rules", "arm_cycle_id", "INTEGER NOT NULL DEFAULT 1");
471
+
472
+ addColumnIfMissing(db, "alert_events", "observed_at", "TEXT");
473
+ addColumnIfMissing(db, "alert_events", "provider_data_at", "TEXT");
474
+ addColumnIfMissing(db, "alert_events", "source_provider", "TEXT");
475
+ addColumnIfMissing(db, "alert_events", "cache_status", "TEXT NOT NULL DEFAULT 'live'");
476
+ addColumnIfMissing(db, "alert_events", "data_delay_ms", "INTEGER");
477
+ addColumnIfMissing(db, "alert_events", "trigger_source", "TEXT NOT NULL DEFAULT 'manual'");
478
+ addColumnIfMissing(db, "alert_events", "dedupe_key", "TEXT");
479
+
480
+ addColumnIfMissing(db, "report_runs", "trigger_type", "TEXT NOT NULL DEFAULT 'manual'");
481
+ addColumnIfMissing(db, "report_runs", "scheduled_for", "TEXT");
482
+ addColumnIfMissing(db, "report_runs", "owner_id", "TEXT");
483
+
484
+ if (tableExists(db, "alert_events")) {
485
+ db.exec("UPDATE alert_events SET observed_at = triggered_at WHERE observed_at IS NULL");
486
+ }
487
+
128
488
  db.exec(CURRENT_SCHEMA);
129
489
 
130
490
  db.prepare("DELETE FROM schema_version").run();
131
491
  db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(CURRENT_SCHEMA_VERSION);
132
492
  }
133
493
 
494
+ function addColumnIfMissing(
495
+ db: Database.Database,
496
+ tableName: string,
497
+ columnName: string,
498
+ definition: string,
499
+ ): void {
500
+ const cols = (db.pragma(`table_info(${tableName})`) as Array<{ name: string }>).map(
501
+ (c) => c.name,
502
+ );
503
+ if (cols.length === 0) {
504
+ if (!tableExists(db, tableName)) return;
505
+ }
506
+ if (!cols.includes(columnName)) {
507
+ db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${definition}`);
508
+ }
509
+ }
510
+
511
+ function tableExists(db: Database.Database, tableName: string): boolean {
512
+ const table = db
513
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
514
+ .get(tableName) as { name: string } | undefined;
515
+ return table != null;
516
+ }
517
+
134
518
  function readSchemaVersion(db: Database.Database): number | null {
135
519
  const table = db
136
520
  .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'schema_version'")
@@ -147,6 +531,23 @@ function readSchemaVersion(db: Database.Database): number | null {
147
531
 
148
532
  function resetSchema(db: Database.Database): void {
149
533
  db.exec(`
534
+ DROP TABLE IF EXISTS import_rows;
535
+ DROP TABLE IF EXISTS import_batches;
536
+ DROP TABLE IF EXISTS notification_delivery_attempts;
537
+ DROP TABLE IF EXISTS notification_events;
538
+ DROP TABLE IF EXISTS automation_runner_leases;
539
+ DROP TABLE IF EXISTS alert_check_runs;
540
+ DROP TABLE IF EXISTS report_runs;
541
+ DROP TABLE IF EXISTS report_templates;
542
+ DROP TABLE IF EXISTS alert_events;
543
+ DROP TABLE IF EXISTS alert_rules;
544
+ DROP TABLE IF EXISTS prediction_records;
545
+ DROP TABLE IF EXISTS portfolio_lots;
546
+ DROP TABLE IF EXISTS portfolios;
547
+ DROP TABLE IF EXISTS watchlist_items;
548
+ DROP TABLE IF EXISTS watchlists;
549
+ DROP TABLE IF EXISTS instrument_aliases;
550
+ DROP TABLE IF EXISTS instruments;
150
551
  DROP TABLE IF EXISTS recommendations;
151
552
  DROP TABLE IF EXISTS workflow_runs;
152
553
  DROP TABLE IF EXISTS user_preferences;