opencandle 0.5.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 (527) hide show
  1. package/README.md +164 -187
  2. package/dist/analysts/contracts.d.ts +1 -3
  3. package/dist/analysts/contracts.js +1 -11
  4. package/dist/analysts/contracts.js.map +1 -1
  5. package/dist/analysts/orchestrator.d.ts +1 -3
  6. package/dist/analysts/orchestrator.js +1 -26
  7. package/dist/analysts/orchestrator.js.map +1 -1
  8. package/dist/cli.js +30 -7
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +3 -3
  11. package/dist/config.js +12 -5
  12. package/dist/config.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/infra/browser.js +3 -1
  17. package/dist/infra/browser.js.map +1 -1
  18. package/dist/infra/cache.d.ts +8 -11
  19. package/dist/infra/cache.js +17 -15
  20. package/dist/infra/cache.js.map +1 -1
  21. package/dist/infra/http-client.d.ts +4 -1
  22. package/dist/infra/http-client.js +59 -6
  23. package/dist/infra/http-client.js.map +1 -1
  24. package/dist/infra/index.d.ts +3 -3
  25. package/dist/infra/index.js +3 -3
  26. package/dist/infra/index.js.map +1 -1
  27. package/dist/infra/native-dependencies.js +2 -2
  28. package/dist/infra/native-dependencies.js.map +1 -1
  29. package/dist/infra/node-version.js.map +1 -1
  30. package/dist/infra/opencandle-paths.d.ts +0 -3
  31. package/dist/infra/opencandle-paths.js +4 -11
  32. package/dist/infra/opencandle-paths.js.map +1 -1
  33. package/dist/infra/rate-limiter.js +12 -9
  34. package/dist/infra/rate-limiter.js.map +1 -1
  35. package/dist/market-state/alert-conditions.d.ts +34 -0
  36. package/dist/market-state/alert-conditions.js +23 -0
  37. package/dist/market-state/alert-conditions.js.map +1 -0
  38. package/dist/market-state/alert-runner.d.ts +55 -0
  39. package/dist/market-state/alert-runner.js +634 -0
  40. package/dist/market-state/alert-runner.js.map +1 -0
  41. package/dist/market-state/daily-report.d.ts +26 -0
  42. package/dist/market-state/daily-report.js +179 -0
  43. package/dist/market-state/daily-report.js.map +1 -0
  44. package/dist/market-state/local-automation-service.d.ts +25 -0
  45. package/dist/market-state/local-automation-service.js +119 -0
  46. package/dist/market-state/local-automation-service.js.map +1 -0
  47. package/dist/market-state/notification-delivery.d.ts +14 -0
  48. package/dist/market-state/notification-delivery.js +139 -0
  49. package/dist/market-state/notification-delivery.js.map +1 -0
  50. package/dist/market-state/resolve-for-mutation.d.ts +10 -0
  51. package/dist/market-state/resolve-for-mutation.js +15 -0
  52. package/dist/market-state/resolve-for-mutation.js.map +1 -0
  53. package/dist/market-state/resolve.d.ts +14 -0
  54. package/dist/market-state/resolve.js +89 -0
  55. package/dist/market-state/resolve.js.map +1 -0
  56. package/dist/market-state/service.d.ts +527 -0
  57. package/dist/market-state/service.js +1099 -0
  58. package/dist/market-state/service.js.map +1 -0
  59. package/dist/memory/index.d.ts +7 -7
  60. package/dist/memory/index.js +6 -6
  61. package/dist/memory/index.js.map +1 -1
  62. package/dist/memory/manager.js +11 -11
  63. package/dist/memory/manager.js.map +1 -1
  64. package/dist/memory/retrieval.js +7 -4
  65. package/dist/memory/retrieval.js.map +1 -1
  66. package/dist/memory/sqlite.js +385 -3
  67. package/dist/memory/sqlite.js.map +1 -1
  68. package/dist/memory/storage.js +1 -2
  69. package/dist/memory/storage.js.map +1 -1
  70. package/dist/memory/tool-defaults.js +64 -28
  71. package/dist/memory/tool-defaults.js.map +1 -1
  72. package/dist/memory/types.js.map +1 -1
  73. package/dist/monitor.d.ts +2 -0
  74. package/dist/monitor.js +104 -0
  75. package/dist/monitor.js.map +1 -0
  76. package/dist/onboarding/connect.js +4 -6
  77. package/dist/onboarding/connect.js.map +1 -1
  78. package/dist/onboarding/credential-interceptor.js +1 -1
  79. package/dist/onboarding/credential-interceptor.js.map +1 -1
  80. package/dist/onboarding/degradation-accumulator.js +1 -3
  81. package/dist/onboarding/degradation-accumulator.js.map +1 -1
  82. package/dist/onboarding/providers.js +3 -16
  83. package/dist/onboarding/providers.js.map +1 -1
  84. package/dist/onboarding/state.js.map +1 -1
  85. package/dist/onboarding/tool-helpers.js +1 -1
  86. package/dist/onboarding/tool-helpers.js.map +1 -1
  87. package/dist/onboarding/tool-tags.js +6 -4
  88. package/dist/onboarding/tool-tags.js.map +1 -1
  89. package/dist/onboarding/validation.js +1 -1
  90. package/dist/onboarding/validation.js.map +1 -1
  91. package/dist/pi/opencandle-extension.d.ts +8 -0
  92. package/dist/pi/opencandle-extension.js +412 -28
  93. package/dist/pi/opencandle-extension.js.map +1 -1
  94. package/dist/pi/session.d.ts +1 -1
  95. package/dist/pi/session.js +3 -1
  96. package/dist/pi/session.js.map +1 -1
  97. package/dist/pi/setup.js +8 -3
  98. package/dist/pi/setup.js.map +1 -1
  99. package/dist/pi/tool-adapter.js +5 -2
  100. package/dist/pi/tool-adapter.js.map +1 -1
  101. package/dist/prompts/context-builder.d.ts +1 -1
  102. package/dist/prompts/context-builder.js +19 -6
  103. package/dist/prompts/context-builder.js.map +1 -1
  104. package/dist/prompts/policy-cards.d.ts +1 -1
  105. package/dist/prompts/policy-cards.js +1 -1
  106. package/dist/prompts/policy-cards.js.map +1 -1
  107. package/dist/prompts/sections.d.ts +1 -1
  108. package/dist/prompts/symbol-preflight.d.ts +20 -0
  109. package/dist/prompts/symbol-preflight.js +49 -0
  110. package/dist/prompts/symbol-preflight.js.map +1 -0
  111. package/dist/prompts/workflow-prompts.d.ts +1 -1
  112. package/dist/prompts/workflow-prompts.js +54 -16
  113. package/dist/prompts/workflow-prompts.js.map +1 -1
  114. package/dist/providers/alpha-vantage.d.ts +1 -1
  115. package/dist/providers/alpha-vantage.js +26 -7
  116. package/dist/providers/alpha-vantage.js.map +1 -1
  117. package/dist/providers/coingecko.js +1 -1
  118. package/dist/providers/coingecko.js.map +1 -1
  119. package/dist/providers/errors.d.ts +5 -0
  120. package/dist/providers/errors.js +11 -0
  121. package/dist/providers/errors.js.map +1 -0
  122. package/dist/providers/exa-search.d.ts +2 -2
  123. package/dist/providers/exa-search.js +19 -11
  124. package/dist/providers/exa-search.js.map +1 -1
  125. package/dist/providers/fear-greed.js +1 -1
  126. package/dist/providers/fear-greed.js.map +1 -1
  127. package/dist/providers/finnhub.js +3 -5
  128. package/dist/providers/finnhub.js.map +1 -1
  129. package/dist/providers/fred.js +2 -2
  130. package/dist/providers/fred.js.map +1 -1
  131. package/dist/providers/index.d.ts +7 -6
  132. package/dist/providers/index.js +6 -5
  133. package/dist/providers/index.js.map +1 -1
  134. package/dist/providers/reddit.js +2 -2
  135. package/dist/providers/reddit.js.map +1 -1
  136. package/dist/providers/sec-edgar.d.ts +1 -0
  137. package/dist/providers/sec-edgar.js +12 -4
  138. package/dist/providers/sec-edgar.js.map +1 -1
  139. package/dist/providers/tradingview.d.ts +47 -0
  140. package/dist/providers/tradingview.js +275 -0
  141. package/dist/providers/tradingview.js.map +1 -0
  142. package/dist/providers/twitter.js +6 -8
  143. package/dist/providers/twitter.js.map +1 -1
  144. package/dist/providers/web-search.js +26 -12
  145. package/dist/providers/web-search.js.map +1 -1
  146. package/dist/providers/with-fallback.js +4 -2
  147. package/dist/providers/with-fallback.js.map +1 -1
  148. package/dist/providers/wrap-provider.d.ts +2 -3
  149. package/dist/providers/wrap-provider.js +14 -8
  150. package/dist/providers/wrap-provider.js.map +1 -1
  151. package/dist/providers/yahoo-finance.d.ts +1 -1
  152. package/dist/providers/yahoo-finance.js +101 -17
  153. package/dist/providers/yahoo-finance.js.map +1 -1
  154. package/dist/routing/classify-intent.d.ts +6 -0
  155. package/dist/routing/classify-intent.js +78 -7
  156. package/dist/routing/classify-intent.js.map +1 -1
  157. package/dist/routing/defaults.d.ts +1 -1
  158. package/dist/routing/entity-extractor.d.ts +1 -0
  159. package/dist/routing/entity-extractor.js +234 -29
  160. package/dist/routing/entity-extractor.js.map +1 -1
  161. package/dist/routing/fund-symbols.d.ts +2 -0
  162. package/dist/routing/fund-symbols.js +55 -0
  163. package/dist/routing/fund-symbols.js.map +1 -0
  164. package/dist/routing/horizon.d.ts +1 -0
  165. package/dist/routing/horizon.js +10 -0
  166. package/dist/routing/horizon.js.map +1 -0
  167. package/dist/routing/index.d.ts +10 -10
  168. package/dist/routing/index.js +6 -6
  169. package/dist/routing/index.js.map +1 -1
  170. package/dist/routing/planning.d.ts +1 -1
  171. package/dist/routing/planning.js +65 -34
  172. package/dist/routing/planning.js.map +1 -1
  173. package/dist/routing/route-manifest.d.ts +2 -2
  174. package/dist/routing/route-manifest.js +25 -4
  175. package/dist/routing/route-manifest.js.map +1 -1
  176. package/dist/routing/router-llm-client.js.map +1 -1
  177. package/dist/routing/router-prompt.js +7 -9
  178. package/dist/routing/router-prompt.js.map +1 -1
  179. package/dist/routing/router-types.d.ts +1 -0
  180. package/dist/routing/router.js +137 -22
  181. package/dist/routing/router.js.map +1 -1
  182. package/dist/routing/slot-resolver.d.ts +1 -1
  183. package/dist/routing/slot-resolver.js +2 -4
  184. package/dist/routing/slot-resolver.js.map +1 -1
  185. package/dist/routing/symbol-disambiguator.d.ts +11 -0
  186. package/dist/routing/symbol-disambiguator.js +52 -0
  187. package/dist/routing/symbol-disambiguator.js.map +1 -0
  188. package/dist/routing/turn-context.d.ts +1 -1
  189. package/dist/routing/turn-context.js +1 -1
  190. package/dist/routing/turn-context.js.map +1 -1
  191. package/dist/routing/types.d.ts +2 -0
  192. package/dist/runtime/answer-contracts.js +36 -8
  193. package/dist/runtime/answer-contracts.js.map +1 -1
  194. package/dist/runtime/artifact-contracts.js.map +1 -1
  195. package/dist/runtime/planning-evidence.js +47 -26
  196. package/dist/runtime/planning-evidence.js.map +1 -1
  197. package/dist/runtime/prompt-step.d.ts +1 -9
  198. package/dist/runtime/prompt-step.js +0 -10
  199. package/dist/runtime/prompt-step.js.map +1 -1
  200. package/dist/runtime/run-context.d.ts +5 -2
  201. package/dist/runtime/run-context.js +8 -1
  202. package/dist/runtime/run-context.js.map +1 -1
  203. package/dist/runtime/session-coordinator.d.ts +13 -5
  204. package/dist/runtime/session-coordinator.js +160 -20
  205. package/dist/runtime/session-coordinator.js.map +1 -1
  206. package/dist/runtime/session-title.d.ts +14 -0
  207. package/dist/runtime/session-title.js +50 -0
  208. package/dist/runtime/session-title.js.map +1 -0
  209. package/dist/runtime/tool-defaults-wrapper.js +1 -3
  210. package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
  211. package/dist/runtime/validation.js.map +1 -1
  212. package/dist/runtime/workflow-events.js.map +1 -1
  213. package/dist/runtime/workflow-runner.d.ts +3 -3
  214. package/dist/runtime/workflow-runner.js +1 -1
  215. package/dist/runtime/workflow-runner.js.map +1 -1
  216. package/dist/sentiment/adapters/finnhub.d.ts +1 -1
  217. package/dist/sentiment/adapters/finnhub.js +6 -1
  218. package/dist/sentiment/adapters/finnhub.js.map +1 -1
  219. package/dist/sentiment/adapters/reddit.d.ts +2 -2
  220. package/dist/sentiment/adapters/twitter.d.ts +1 -1
  221. package/dist/sentiment/adapters/web.d.ts +1 -1
  222. package/dist/sentiment/index.d.ts +9 -11
  223. package/dist/sentiment/index.js +9 -20
  224. package/dist/sentiment/index.js.map +1 -1
  225. package/dist/sentiment/keywords.js +26 -4
  226. package/dist/sentiment/keywords.js.map +1 -1
  227. package/dist/sentiment/pipeline.d.ts +2 -2
  228. package/dist/sentiment/pipeline.js +1 -1
  229. package/dist/sentiment/pipeline.js.map +1 -1
  230. package/dist/sentiment/scorer.js +1 -1
  231. package/dist/sentiment/store.d.ts +1 -1
  232. package/dist/sentiment/store.js +1 -1
  233. package/dist/sentiment/store.js.map +1 -1
  234. package/dist/sentiment/trends.d.ts +1 -1
  235. package/dist/sentiment/trends.js.map +1 -1
  236. package/dist/sentiment/types.js.map +1 -1
  237. package/dist/system-prompt.js +3 -2
  238. package/dist/system-prompt.js.map +1 -1
  239. package/dist/tool-kit.d.ts +7 -7
  240. package/dist/tool-kit.js +4 -4
  241. package/dist/tool-kit.js.map +1 -1
  242. package/dist/tools/fundamentals/company-overview.js +11 -6
  243. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  244. package/dist/tools/fundamentals/comps.js +18 -9
  245. package/dist/tools/fundamentals/comps.js.map +1 -1
  246. package/dist/tools/fundamentals/dcf.js +23 -11
  247. package/dist/tools/fundamentals/dcf.js.map +1 -1
  248. package/dist/tools/fundamentals/earnings.js +8 -3
  249. package/dist/tools/fundamentals/earnings.js.map +1 -1
  250. package/dist/tools/fundamentals/financials.js +8 -3
  251. package/dist/tools/fundamentals/financials.js.map +1 -1
  252. package/dist/tools/fundamentals/sec-filings.js +21 -6
  253. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  254. package/dist/tools/index.d.ts +23 -19
  255. package/dist/tools/index.js +51 -39
  256. package/dist/tools/index.js.map +1 -1
  257. package/dist/tools/interaction/ask-user.js +15 -3
  258. package/dist/tools/interaction/ask-user.js.map +1 -1
  259. package/dist/tools/interaction/twitter-login.js +13 -3
  260. package/dist/tools/interaction/twitter-login.js.map +1 -1
  261. package/dist/tools/macro/fear-greed.js.map +1 -1
  262. package/dist/tools/macro/fred-data.d.ts +1 -1
  263. package/dist/tools/macro/fred-data.js +17 -6
  264. package/dist/tools/macro/fred-data.js.map +1 -1
  265. package/dist/tools/market/crypto-history.js +3 -1
  266. package/dist/tools/market/crypto-history.js.map +1 -1
  267. package/dist/tools/market/crypto-price.js +3 -1
  268. package/dist/tools/market/crypto-price.js.map +1 -1
  269. package/dist/tools/market/screen-stocks.d.ts +18 -0
  270. package/dist/tools/market/screen-stocks.js +252 -0
  271. package/dist/tools/market/screen-stocks.js.map +1 -0
  272. package/dist/tools/market/search-ticker.js +160 -8
  273. package/dist/tools/market/search-ticker.js.map +1 -1
  274. package/dist/tools/market/stock-history.d.ts +2 -2
  275. package/dist/tools/market/stock-history.js +26 -7
  276. package/dist/tools/market/stock-history.js.map +1 -1
  277. package/dist/tools/market/stock-quote.js +5 -3
  278. package/dist/tools/market/stock-quote.js.map +1 -1
  279. package/dist/tools/options/greeks.js +1 -1
  280. package/dist/tools/options/greeks.js.map +1 -1
  281. package/dist/tools/options/option-chain.js +19 -6
  282. package/dist/tools/options/option-chain.js.map +1 -1
  283. package/dist/tools/portfolio/alerts.d.ts +15 -0
  284. package/dist/tools/portfolio/alerts.js +357 -0
  285. package/dist/tools/portfolio/alerts.js.map +1 -0
  286. package/dist/tools/portfolio/correlation.d.ts +1 -1
  287. package/dist/tools/portfolio/correlation.js +33 -13
  288. package/dist/tools/portfolio/correlation.js.map +1 -1
  289. package/dist/tools/portfolio/daily-report.d.ts +8 -0
  290. package/dist/tools/portfolio/daily-report.js +83 -0
  291. package/dist/tools/portfolio/daily-report.js.map +1 -0
  292. package/dist/tools/portfolio/holdings-overlap.js +10 -3
  293. package/dist/tools/portfolio/holdings-overlap.js.map +1 -1
  294. package/dist/tools/portfolio/notifications.d.ts +7 -0
  295. package/dist/tools/portfolio/notifications.js +43 -0
  296. package/dist/tools/portfolio/notifications.js.map +1 -0
  297. package/dist/tools/portfolio/predictions.d.ts +12 -6
  298. package/dist/tools/portfolio/predictions.js +337 -87
  299. package/dist/tools/portfolio/predictions.js.map +1 -1
  300. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  301. package/dist/tools/portfolio/risk-analysis.js +45 -6
  302. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  303. package/dist/tools/portfolio/tracker.d.ts +4 -3
  304. package/dist/tools/portfolio/tracker.js +246 -101
  305. package/dist/tools/portfolio/tracker.js.map +1 -1
  306. package/dist/tools/portfolio/watchlist.d.ts +6 -4
  307. package/dist/tools/portfolio/watchlist.js +208 -108
  308. package/dist/tools/portfolio/watchlist.js.map +1 -1
  309. package/dist/tools/sentiment/reddit-sentiment.js +23 -10
  310. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  311. package/dist/tools/sentiment/sentiment-summary.js +15 -13
  312. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  313. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  314. package/dist/tools/sentiment/sentiment-trend.js +12 -2
  315. package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
  316. package/dist/tools/sentiment/twitter-sentiment.js +12 -5
  317. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  318. package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
  319. package/dist/tools/sentiment/untrusted-text.js +17 -0
  320. package/dist/tools/sentiment/untrusted-text.js.map +1 -0
  321. package/dist/tools/sentiment/web-search.js +9 -13
  322. package/dist/tools/sentiment/web-search.js.map +1 -1
  323. package/dist/tools/sentiment/web-sentiment.js +15 -3
  324. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  325. package/dist/tools/technical/backtest.d.ts +1 -1
  326. package/dist/tools/technical/backtest.js +27 -20
  327. package/dist/tools/technical/backtest.js.map +1 -1
  328. package/dist/tools/technical/indicators.js +23 -5
  329. package/dist/tools/technical/indicators.js.map +1 -1
  330. package/dist/types/index.d.ts +3 -3
  331. package/dist/types/index.js.map +1 -1
  332. package/dist/types/market.d.ts +1 -0
  333. package/dist/types/portfolio.d.ts +14 -4
  334. package/dist/workflows/compare-assets.d.ts +0 -3
  335. package/dist/workflows/compare-assets.js +20 -11
  336. package/dist/workflows/compare-assets.js.map +1 -1
  337. package/dist/workflows/index.d.ts +3 -4
  338. package/dist/workflows/index.js +3 -3
  339. package/dist/workflows/index.js.map +1 -1
  340. package/dist/workflows/options-screener.d.ts +0 -3
  341. package/dist/workflows/options-screener.js +4 -11
  342. package/dist/workflows/options-screener.js.map +1 -1
  343. package/dist/workflows/portfolio-builder.d.ts +0 -3
  344. package/dist/workflows/portfolio-builder.js +0 -8
  345. package/dist/workflows/portfolio-builder.js.map +1 -1
  346. package/gui/server/ask-user-bridge.ts +1 -1
  347. package/gui/server/automation-heartbeat.ts +97 -0
  348. package/gui/server/background-quotes.ts +97 -1
  349. package/gui/server/chat-event-adapter.ts +32 -10
  350. package/gui/server/chat-run-session.ts +16 -0
  351. package/gui/server/invoke-tool.ts +144 -1
  352. package/gui/server/live-chat-event-adapter.ts +21 -6
  353. package/gui/server/market-state-api.ts +315 -0
  354. package/gui/server/model-setup.ts +149 -2
  355. package/gui/server/private-api-access.ts +62 -0
  356. package/gui/server/projector.ts +12 -7
  357. package/gui/server/prompt-observation.ts +4 -7
  358. package/gui/server/quote-snapshot-store.ts +50 -0
  359. package/gui/server/server.ts +200 -451
  360. package/gui/server/session-actions.ts +186 -1
  361. package/gui/server/shutdown.ts +47 -0
  362. package/gui/server/tool-invoke-ack.ts +49 -0
  363. package/gui/server/tool-metadata.ts +23 -10
  364. package/gui/server/websocket.ts +13 -3
  365. package/gui/server/writer-lock.ts +6 -2
  366. package/gui/server/ws-hub.ts +292 -0
  367. package/gui/shared/chat-events.ts +16 -1
  368. package/gui/shared/event-reducer.ts +24 -6
  369. package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +1 -0
  370. package/gui/web/dist/assets/index-2KZtKBmu.css +1 -0
  371. package/gui/web/dist/assets/index-CveNgtDg.js +69 -0
  372. package/gui/web/dist/index.html +2 -2
  373. package/package.json +5 -1
  374. package/src/analysts/contracts.ts +10 -23
  375. package/src/analysts/orchestrator.ts +8 -43
  376. package/src/cli.ts +35 -12
  377. package/src/config.ts +17 -9
  378. package/src/index.ts +1 -1
  379. package/src/infra/browser.ts +3 -1
  380. package/src/infra/cache.ts +41 -30
  381. package/src/infra/http-client.ts +72 -6
  382. package/src/infra/index.ts +7 -10
  383. package/src/infra/native-dependencies.ts +8 -3
  384. package/src/infra/node-version.ts +3 -1
  385. package/src/infra/opencandle-paths.ts +3 -14
  386. package/src/infra/rate-limiter.ts +22 -19
  387. package/src/market-state/alert-conditions.ts +82 -0
  388. package/src/market-state/alert-runner.ts +863 -0
  389. package/src/market-state/daily-report.ts +247 -0
  390. package/src/market-state/local-automation-service.ts +162 -0
  391. package/src/market-state/notification-delivery.ts +158 -0
  392. package/src/market-state/resolve-for-mutation.ts +24 -0
  393. package/src/market-state/resolve.ts +112 -0
  394. package/src/market-state/service.ts +2344 -0
  395. package/src/memory/index.ts +7 -7
  396. package/src/memory/manager.ts +14 -16
  397. package/src/memory/retrieval.ts +8 -7
  398. package/src/memory/sqlite.ts +407 -6
  399. package/src/memory/storage.ts +5 -15
  400. package/src/memory/tool-defaults.ts +60 -39
  401. package/src/memory/types.ts +3 -3
  402. package/src/monitor.ts +121 -0
  403. package/src/onboarding/connect.ts +10 -33
  404. package/src/onboarding/credential-interceptor.ts +3 -15
  405. package/src/onboarding/degradation-accumulator.ts +1 -3
  406. package/src/onboarding/providers.ts +9 -40
  407. package/src/onboarding/state.ts +4 -15
  408. package/src/onboarding/tool-helpers.ts +2 -9
  409. package/src/onboarding/tool-tags.ts +6 -6
  410. package/src/onboarding/validation.ts +14 -20
  411. package/src/pi/opencandle-extension.ts +529 -85
  412. package/src/pi/session.ts +7 -5
  413. package/src/pi/setup.ts +61 -43
  414. package/src/pi/tool-adapter.ts +5 -2
  415. package/src/prompts/context-builder.ts +23 -12
  416. package/src/prompts/policy-cards.ts +2 -2
  417. package/src/prompts/sections.ts +1 -1
  418. package/src/prompts/symbol-preflight.ts +80 -0
  419. package/src/prompts/workflow-prompts.ts +77 -28
  420. package/src/providers/alpha-vantage.ts +58 -39
  421. package/src/providers/coingecko.ts +2 -5
  422. package/src/providers/errors.ts +9 -0
  423. package/src/providers/exa-search.ts +24 -22
  424. package/src/providers/fear-greed.ts +1 -1
  425. package/src/providers/finnhub.ts +7 -6
  426. package/src/providers/fred.ts +3 -3
  427. package/src/providers/index.ts +14 -6
  428. package/src/providers/reddit.ts +17 -6
  429. package/src/providers/sec-edgar.ts +20 -6
  430. package/src/providers/tradingview.ts +399 -0
  431. package/src/providers/twitter.ts +6 -8
  432. package/src/providers/web-search.ts +30 -20
  433. package/src/providers/with-fallback.ts +8 -7
  434. package/src/providers/wrap-provider.ts +15 -10
  435. package/src/providers/yahoo-finance.ts +140 -35
  436. package/src/routing/classify-intent.ts +101 -10
  437. package/src/routing/defaults.ts +1 -1
  438. package/src/routing/entity-extractor.ts +287 -38
  439. package/src/routing/fund-symbols.ts +58 -0
  440. package/src/routing/horizon.ts +7 -0
  441. package/src/routing/index.ts +48 -48
  442. package/src/routing/planning.ts +144 -53
  443. package/src/routing/route-manifest.ts +37 -15
  444. package/src/routing/router-llm-client.ts +4 -4
  445. package/src/routing/router-prompt.ts +15 -19
  446. package/src/routing/router-types.ts +2 -5
  447. package/src/routing/router.ts +251 -53
  448. package/src/routing/slot-resolver.ts +34 -11
  449. package/src/routing/symbol-disambiguator.ts +72 -0
  450. package/src/routing/turn-context.ts +6 -9
  451. package/src/routing/types.ts +2 -0
  452. package/src/runtime/answer-contracts.ts +82 -43
  453. package/src/runtime/artifact-contracts.ts +2 -1
  454. package/src/runtime/planning-evidence.ts +157 -66
  455. package/src/runtime/prompt-step.ts +1 -16
  456. package/src/runtime/run-context.ts +12 -2
  457. package/src/runtime/session-coordinator.ts +238 -63
  458. package/src/runtime/session-title.ts +60 -0
  459. package/src/runtime/tool-defaults-wrapper.ts +1 -3
  460. package/src/runtime/validation.ts +1 -4
  461. package/src/runtime/workflow-events.ts +7 -7
  462. package/src/runtime/workflow-runner.ts +5 -11
  463. package/src/sentiment/adapters/finnhub.ts +7 -2
  464. package/src/sentiment/adapters/reddit.ts +2 -2
  465. package/src/sentiment/adapters/twitter.ts +1 -1
  466. package/src/sentiment/adapters/web.ts +1 -1
  467. package/src/sentiment/index.ts +16 -26
  468. package/src/sentiment/keywords.ts +26 -4
  469. package/src/sentiment/pipeline.ts +15 -4
  470. package/src/sentiment/scorer.ts +1 -1
  471. package/src/sentiment/store.ts +2 -2
  472. package/src/sentiment/trends.ts +9 -3
  473. package/src/sentiment/types.ts +5 -4
  474. package/src/system-prompt.ts +3 -2
  475. package/src/tool-kit.ts +10 -9
  476. package/src/tools/fundamentals/company-overview.ts +19 -9
  477. package/src/tools/fundamentals/comps.ts +68 -55
  478. package/src/tools/fundamentals/dcf.ts +145 -95
  479. package/src/tools/fundamentals/earnings.ts +16 -6
  480. package/src/tools/fundamentals/financials.ts +16 -7
  481. package/src/tools/fundamentals/sec-filings.ts +37 -16
  482. package/src/tools/index.ts +51 -39
  483. package/src/tools/interaction/ask-user.ts +22 -10
  484. package/src/tools/interaction/twitter-login.ts +17 -5
  485. package/src/tools/macro/fear-greed.ts +1 -1
  486. package/src/tools/macro/fred-data.ts +58 -46
  487. package/src/tools/market/crypto-history.ts +8 -3
  488. package/src/tools/market/crypto-price.ts +6 -6
  489. package/src/tools/market/screen-stocks.ts +279 -0
  490. package/src/tools/market/search-ticker.ts +218 -17
  491. package/src/tools/market/stock-history.ts +37 -12
  492. package/src/tools/market/stock-quote.ts +10 -7
  493. package/src/tools/options/greeks.ts +5 -5
  494. package/src/tools/options/option-chain.ts +41 -17
  495. package/src/tools/portfolio/alerts.ts +457 -0
  496. package/src/tools/portfolio/correlation.ts +47 -20
  497. package/src/tools/portfolio/daily-report.ts +101 -0
  498. package/src/tools/portfolio/holdings-overlap.ts +31 -15
  499. package/src/tools/portfolio/notifications.ts +45 -0
  500. package/src/tools/portfolio/predictions.ts +406 -106
  501. package/src/tools/portfolio/risk-analysis.ts +46 -7
  502. package/src/tools/portfolio/tracker.ts +270 -109
  503. package/src/tools/portfolio/watchlist.ts +250 -121
  504. package/src/tools/sentiment/reddit-sentiment.ts +50 -24
  505. package/src/tools/sentiment/sentiment-summary.ts +62 -41
  506. package/src/tools/sentiment/sentiment-trend.ts +24 -7
  507. package/src/tools/sentiment/twitter-sentiment.ts +22 -15
  508. package/src/tools/sentiment/untrusted-text.ts +21 -0
  509. package/src/tools/sentiment/web-search.ts +21 -18
  510. package/src/tools/sentiment/web-sentiment.ts +26 -10
  511. package/src/tools/technical/backtest.ts +32 -22
  512. package/src/tools/technical/indicators.ts +39 -14
  513. package/src/types/index.ts +8 -3
  514. package/src/types/market.ts +1 -0
  515. package/src/types/portfolio.ts +14 -4
  516. package/src/types/sentiment.ts +2 -2
  517. package/src/workflows/compare-assets.ts +33 -21
  518. package/src/workflows/index.ts +3 -4
  519. package/src/workflows/options-screener.ts +27 -29
  520. package/src/workflows/portfolio-builder.ts +34 -27
  521. package/dist/workflows/types.d.ts +0 -4
  522. package/dist/workflows/types.js +0 -2
  523. package/dist/workflows/types.js.map +0 -1
  524. package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +0 -1
  525. package/gui/web/dist/assets/index-Bxt9QpLX.css +0 -1
  526. package/gui/web/dist/assets/index-CZ9DHZYy.js +0 -67
  527. package/src/workflows/types.ts +0 -4
@@ -1,14 +1,23 @@
1
- import { httpGet } from "../infra/http-client.js";
2
- import { cache, TTL, STALE_LIMIT } from "../infra/cache.js";
3
- import { rateLimiter } from "../infra/rate-limiter.js";
4
1
  import { StealthBrowser } from "../infra/browser.js";
5
- import type { StockQuote, OHLCV } from "../types/market.js";
6
- import type { OptionsChain, OptionContract, OptionsMarketSession, OptionsQuoteStatus } from "../types/options.js";
7
- import type { FundHoldings } from "../types/portfolio.js";
2
+ import { cache, STALE_LIMIT, TTL } from "../infra/cache.js";
3
+ import { HttpError, httpGet } from "../infra/http-client.js";
4
+ import { rateLimiter } from "../infra/rate-limiter.js";
8
5
  import { computeGreeks } from "../tools/options/greeks.js";
6
+ import type { OHLCV, StockQuote } from "../types/market.js";
7
+ import type {
8
+ OptionContract,
9
+ OptionsChain,
10
+ OptionsMarketSession,
11
+ OptionsQuoteStatus,
12
+ } from "../types/options.js";
13
+ import type { FundHoldings } from "../types/portfolio.js";
14
+ import { InvalidSymbolError } from "./errors.js";
9
15
 
10
16
  const BASE_URL = "https://query1.finance.yahoo.com/v8/finance/chart";
11
17
  const QUOTE_SUMMARY_URL = "https://query1.finance.yahoo.com/v10/finance/quoteSummary";
18
+ const STALE_QUOTE_MAX_RETRY_AFTER_MS = 1_000;
19
+
20
+ type YahooNumber = number | { raw?: number; fmt?: string };
12
21
 
13
22
  interface YahooChartResponse {
14
23
  chart: {
@@ -42,10 +51,10 @@ interface YahooQuoteSummaryResponse {
42
51
  holdings?: Array<{
43
52
  symbol?: string;
44
53
  holdingName?: string;
45
- holdingPercent?: number;
54
+ holdingPercent?: YahooNumber;
46
55
  }>;
47
56
  equityHoldings?: {
48
- sectorWeightings?: Array<Record<string, number>>;
57
+ sectorWeightings?: Array<Record<string, YahooNumber>>;
49
58
  };
50
59
  };
51
60
  }>;
@@ -64,6 +73,7 @@ export async function getQuote(symbol: string): Promise<StockQuote> {
64
73
  const url = `${BASE_URL}/${encodeURIComponent(symbol)}?interval=1d&range=1d`;
65
74
  const data = await httpGet<YahooChartResponse>(url, {
66
75
  headers: { "User-Agent": "OpenCandle/1.0" },
76
+ maxRetryAfterMs: STALE_QUOTE_MAX_RETRY_AFTER_MS,
67
77
  });
68
78
 
69
79
  if (data.chart.error) {
@@ -97,8 +107,16 @@ export async function getQuote(symbol: string): Promise<StockQuote> {
97
107
  week52High: meta.fiftyTwoWeekHigh ?? 0,
98
108
  week52Low: meta.fiftyTwoWeekLow ?? 0,
99
109
  timestamp: Date.now(),
110
+ currency:
111
+ typeof meta.currency === "string" && meta.currency.trim() !== ""
112
+ ? meta.currency.trim().toUpperCase()
113
+ : null,
100
114
  };
101
115
 
116
+ if (isZeroResultQuote(quote)) {
117
+ throw new InvalidSymbolError(symbol.toUpperCase(), "yahoo");
118
+ }
119
+
102
120
  cache.set(cacheKey, quote, TTL.QUOTE);
103
121
  return quote;
104
122
  } catch (error) {
@@ -108,6 +126,16 @@ export async function getQuote(symbol: string): Promise<StockQuote> {
108
126
  }
109
127
  }
110
128
 
129
+ function isZeroResultQuote(quote: StockQuote): boolean {
130
+ return (
131
+ quote.price === 0 &&
132
+ quote.volume === 0 &&
133
+ quote.week52High === 0 &&
134
+ quote.week52Low === 0 &&
135
+ quote.marketCap === 0
136
+ );
137
+ }
138
+
111
139
  export async function getHistory(
112
140
  symbol: string,
113
141
  range: string = "6mo",
@@ -162,14 +190,12 @@ export async function getFundHoldings(symbol: string): Promise<FundHoldings> {
162
190
  try {
163
191
  await rateLimiter.acquire("yahoo");
164
192
 
165
- const modules = encodeURIComponent("price,topHoldings");
166
- const url = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(normalizedSymbol)}?modules=${modules}`;
167
- const data = await httpGet<YahooQuoteSummaryResponse>(url, {
168
- headers: { "User-Agent": "OpenCandle/1.0" },
169
- });
193
+ const data = await getFundHoldingsSummary(normalizedSymbol);
170
194
  const result = data.quoteSummary.result?.[0];
171
195
  if (data.quoteSummary.error) {
172
- throw new Error(`Yahoo Finance: ${data.quoteSummary.error.description ?? data.quoteSummary.error.code ?? "quoteSummary error"}`);
196
+ throw new Error(
197
+ `Yahoo Finance: ${data.quoteSummary.error.description ?? data.quoteSummary.error.code ?? "quoteSummary error"}`,
198
+ );
173
199
  }
174
200
  if (!result?.topHoldings?.holdings?.length) {
175
201
  throw new Error(`Yahoo Finance: no fund holdings returned for ${normalizedSymbol}`);
@@ -183,11 +209,13 @@ export async function getFundHoldings(symbol: string): Promise<FundHoldings> {
183
209
  const holdingSymbol = holding.symbol?.trim().toUpperCase();
184
210
  const weight = normalizeHoldingWeight(holding.holdingPercent);
185
211
  if (!holdingSymbol || weight === undefined) return [];
186
- return [{
187
- symbol: holdingSymbol,
188
- name: holding.holdingName?.trim() || holdingSymbol,
189
- weight,
190
- }];
212
+ return [
213
+ {
214
+ symbol: holdingSymbol,
215
+ name: holding.holdingName?.trim() || holdingSymbol,
216
+ weight,
217
+ },
218
+ ];
191
219
  }),
192
220
  sectorWeights: normalizeSectorWeights(result.topHoldings.equityHoldings?.sectorWeightings),
193
221
  };
@@ -204,13 +232,56 @@ export async function getFundHoldings(symbol: string): Promise<FundHoldings> {
204
232
  }
205
233
  }
206
234
 
207
- function normalizeHoldingWeight(value: number | undefined): number | undefined {
208
- if (value === undefined || !Number.isFinite(value) || value <= 0) return undefined;
209
- return value > 1 ? roundWeight(value / 100) : roundWeight(value);
235
+ async function getFundHoldingsSummary(symbol: string): Promise<YahooQuoteSummaryResponse> {
236
+ try {
237
+ return await fetchFundHoldingsSummary(symbol);
238
+ } catch (error) {
239
+ if (!isYahooAuthError(error)) throw error;
240
+ return fetchFundHoldingsSummaryWithCrumb(symbol);
241
+ }
242
+ }
243
+
244
+ async function fetchFundHoldingsSummary(symbol: string): Promise<YahooQuoteSummaryResponse> {
245
+ const modules = encodeURIComponent("price,topHoldings");
246
+ const url = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${modules}`;
247
+ return httpGet<YahooQuoteSummaryResponse>(url, {
248
+ headers: { "User-Agent": "OpenCandle/1.0" },
249
+ });
250
+ }
251
+
252
+ async function fetchFundHoldingsSummaryWithCrumb(
253
+ symbol: string,
254
+ ): Promise<YahooQuoteSummaryResponse> {
255
+ const modules = encodeURIComponent("price,topHoldings");
256
+ const { crumb, cookie } = await getYahooCrumb();
257
+ const url = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${modules}&crumb=${encodeURIComponent(crumb)}`;
258
+ try {
259
+ return await httpGet<YahooQuoteSummaryResponse>(url, {
260
+ headers: { "User-Agent": BROWSER_UA, Cookie: cookie },
261
+ });
262
+ } catch (error) {
263
+ if (!isYahooAuthError(error)) throw error;
264
+ clearCrumbCache();
265
+ const fresh = await getYahooCrumb();
266
+ const retryUrl = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${modules}&crumb=${encodeURIComponent(fresh.crumb)}`;
267
+ return httpGet<YahooQuoteSummaryResponse>(retryUrl, {
268
+ headers: { "User-Agent": BROWSER_UA, Cookie: fresh.cookie },
269
+ });
270
+ }
271
+ }
272
+
273
+ function isYahooAuthError(error: unknown): boolean {
274
+ return error instanceof HttpError && (error.status === 401 || error.status === 429);
275
+ }
276
+
277
+ function normalizeHoldingWeight(value: YahooNumber | undefined): number | undefined {
278
+ const numeric = typeof value === "number" ? value : value?.raw;
279
+ if (numeric === undefined || !Number.isFinite(numeric) || numeric <= 0) return undefined;
280
+ return numeric > 1 ? roundWeight(numeric / 100) : roundWeight(numeric);
210
281
  }
211
282
 
212
283
  function normalizeSectorWeights(
213
- sectors: Array<Record<string, number>> | undefined,
284
+ sectors: Array<Record<string, YahooNumber>> | undefined,
214
285
  ): Record<string, number> | undefined {
215
286
  if (!sectors?.length) return undefined;
216
287
  const weights: Record<string, number> = {};
@@ -231,6 +302,7 @@ function roundWeight(value: number): number {
231
302
 
232
303
  const BROWSER_UA =
233
304
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
305
+ const YAHOO_RAW_FETCH_TIMEOUT_MS = 10_000;
234
306
 
235
307
  let cachedCrumb: { crumb: string; cookie: string; expiresAt: number } | null = null;
236
308
 
@@ -246,15 +318,30 @@ export async function getYahooCrumb(): Promise<{ crumb: string; cookie: string }
246
318
  // Step 1: Hit fc.yahoo.com to get a session cookie
247
319
  const cookieRes = await fetch("https://fc.yahoo.com/t", {
248
320
  headers: { "User-Agent": BROWSER_UA },
321
+ signal: yahooRawFetchSignal(),
249
322
  });
250
323
  const setCookie = cookieRes.headers.get("set-cookie") ?? "";
251
324
  const cookie = setCookie.split(";")[0]; // Extract just the cookie value
325
+ if (!cookie) {
326
+ if (!cookieRes.ok) {
327
+ throw new Error(
328
+ `Yahoo crumb cookie request failed: HTTP ${cookieRes.status} ${cookieRes.statusText}`.trim(),
329
+ );
330
+ }
331
+ throw new Error("Yahoo crumb cookie request did not return a session cookie");
332
+ }
252
333
 
253
334
  // Step 2: Use the cookie to get a crumb
254
335
  const crumbRes = await fetch("https://query2.finance.yahoo.com/v1/test/getcrumb", {
255
336
  headers: { "User-Agent": BROWSER_UA, Cookie: cookie },
337
+ signal: yahooRawFetchSignal(),
256
338
  });
257
- const crumb = await crumbRes.text();
339
+ if (!crumbRes.ok) {
340
+ throw new Error(
341
+ `Yahoo crumb request failed: HTTP ${crumbRes.status} ${crumbRes.statusText}`.trim(),
342
+ );
343
+ }
344
+ const crumb = (await crumbRes.text()).trim();
258
345
 
259
346
  if (!crumb || crumb.includes("Unauthorized")) {
260
347
  throw new Error("Failed to acquire Yahoo Finance crumb");
@@ -281,25 +368,23 @@ interface YahooOptionsResponse {
281
368
  };
282
369
  }
283
370
 
284
- export async function getOptionsChain(
285
- symbol: string,
286
- expiration?: number,
287
- ): Promise<OptionsChain> {
371
+ export async function getOptionsChain(symbol: string, expiration?: number): Promise<OptionsChain> {
288
372
  const cacheKey = `yahoo:options:${symbol}:${expiration ?? "nearest"}`;
289
373
  const cached = cache.get<OptionsChain>(cacheKey);
290
374
  if (cached) return cached;
291
375
 
292
376
  await rateLimiter.acquire("yahoo");
293
377
 
294
- const { crumb, cookie } = await getYahooCrumb();
295
378
  const dateParam = expiration ? `&date=${expiration}` : "";
296
- const url = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}?crumb=${encodeURIComponent(crumb)}${dateParam}`;
297
379
 
298
380
  let res: Response | null = null;
299
381
  let fetchError: unknown;
300
382
  try {
383
+ const { crumb, cookie } = await getYahooCrumb();
384
+ const url = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}?crumb=${encodeURIComponent(crumb)}${dateParam}`;
301
385
  res = await fetch(url, {
302
386
  headers: { "User-Agent": BROWSER_UA, Cookie: cookie },
387
+ signal: yahooRawFetchSignal(),
303
388
  });
304
389
  } catch (error) {
305
390
  fetchError = error;
@@ -313,6 +398,7 @@ export async function getOptionsChain(
313
398
  const retryUrl = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}?crumb=${encodeURIComponent(fresh.crumb)}${dateParam}`;
314
399
  res = await fetch(retryUrl, {
315
400
  headers: { "User-Agent": BROWSER_UA, Cookie: fresh.cookie },
401
+ signal: yahooRawFetchSignal(),
316
402
  });
317
403
  } catch (error) {
318
404
  fetchError = error;
@@ -344,10 +430,13 @@ export async function getOptionsChain(
344
430
  throw new Error(message);
345
431
  }
346
432
  if (browserError instanceof Error) {
347
- const message = fetchError instanceof Error ? fetchError.message : "Yahoo Finance options: fetch failed";
433
+ const message =
434
+ fetchError instanceof Error ? fetchError.message : "Yahoo Finance options: fetch failed";
348
435
  throw new Error(`${message}; browser fallback failed: ${browserError.message}`);
349
436
  }
350
- throw fetchError instanceof Error ? fetchError : new Error("Yahoo Finance options: fetch failed");
437
+ throw fetchError instanceof Error
438
+ ? fetchError
439
+ : new Error("Yahoo Finance options: fetch failed");
351
440
  }
352
441
 
353
442
  const data: YahooOptionsResponse = await res.json();
@@ -356,6 +445,10 @@ export async function getOptionsChain(
356
445
  return chain;
357
446
  }
358
447
 
448
+ function yahooRawFetchSignal(): AbortSignal {
449
+ return AbortSignal.timeout(YAHOO_RAW_FETCH_TIMEOUT_MS);
450
+ }
451
+
359
452
  /**
360
453
  * Compute time to expiry in years from a Yahoo expiration timestamp (midnight UTC).
361
454
  * US equity options expire at 4:00 PM ET. During EDT that is 20:00 UTC.
@@ -364,7 +457,7 @@ export async function getOptionsChain(
364
457
  */
365
458
  export function computeTimeToExpiry(expirationTs: number, nowMs: number = Date.now()): number {
366
459
  const MARKET_CLOSE_OFFSET_S = 21 * 3600; // 21:00 UTC ≈ 4 PM ET
367
- const MIN_TIME_YEARS = 1 / (365 * 24); // ~1 hour floor
460
+ const MIN_TIME_YEARS = 1 / (365 * 24); // ~1 hour floor
368
461
  const SECONDS_PER_YEAR = 365 * 24 * 3600;
369
462
 
370
463
  const expiryCloseTs = expirationTs + MARKET_CLOSE_OFFSET_S;
@@ -449,6 +542,9 @@ function parseOptionsResponse(data: YahooOptionsResponse): OptionsChain {
449
542
  const quote = result.quote;
450
543
  const underlyingPrice = quote.regularMarketPrice ?? 0;
451
544
  const opts = result.options[0];
545
+ if (!opts && underlyingPrice === 0) {
546
+ throw new InvalidSymbolError(result.underlyingSymbol, "yahoo");
547
+ }
452
548
  const riskFreeRate = 0.05;
453
549
 
454
550
  const expirationTs = opts.expirationDate;
@@ -458,7 +554,14 @@ function parseOptionsResponse(data: YahooOptionsResponse): OptionsChain {
458
554
  const mapContract = (c: any, type: "call" | "put"): OptionContract => {
459
555
  const strike = c.strike ?? c.strike?.raw ?? 0;
460
556
  const iv = c.impliedVolatility ?? c.impliedVolatility?.raw ?? 0;
461
- const greeks = computeGreeks({ type, spot: underlyingPrice, strike, timeYears, iv, riskFreeRate });
557
+ const greeks = computeGreeks({
558
+ type,
559
+ spot: underlyingPrice,
560
+ strike,
561
+ timeYears,
562
+ iv,
563
+ riskFreeRate,
564
+ });
462
565
  return {
463
566
  contractSymbol: c.contractSymbol ?? "",
464
567
  type,
@@ -485,7 +588,9 @@ function parseOptionsResponse(data: YahooOptionsResponse): OptionsChain {
485
588
  symbol: result.underlyingSymbol,
486
589
  underlyingPrice,
487
590
  expirationDate,
488
- expirationDates: result.expirationDates.map((ts) => new Date(ts * 1000).toISOString().split("T")[0]),
591
+ expirationDates: result.expirationDates.map(
592
+ (ts) => new Date(ts * 1000).toISOString().split("T")[0],
593
+ ),
489
594
  calls,
490
595
  puts,
491
596
  totalCallVolume,
@@ -1,5 +1,5 @@
1
- import type { ClassificationResult, WorkflowType, ExtractedEntities } from "./types.js";
2
1
  import { extractEntities } from "./entity-extractor.js";
2
+ import type { ClassificationResult, ExtractedEntities, WorkflowType } from "./types.js";
3
3
 
4
4
  interface Rule {
5
5
  workflow: WorkflowType;
@@ -43,7 +43,9 @@ const RULES: Rule[] = [
43
43
  return (
44
44
  entities.symbols.length >= 1 &&
45
45
  (/\bi\s+own\b/.test(lower) || /\bmy\s+holdings\b/.test(lower)) &&
46
- (/\bportfolio\s+risk\b/.test(lower) || /\bbiggest\s+risk\b/.test(lower) || /\bconcentration\b/.test(lower))
46
+ (/\bportfolio\s+risk\b/.test(lower) ||
47
+ /\bbiggest\s+risk\b/.test(lower) ||
48
+ /\bconcentration\b/.test(lower))
47
49
  );
48
50
  },
49
51
  },
@@ -91,9 +93,7 @@ const RULES: Rule[] = [
91
93
  if (hasCompareKeywords && entities.symbols.length >= 2) return false;
92
94
 
93
95
  return (
94
- /\bbacktest\b/.test(lower) ||
95
- /\bsentiment\b/.test(lower) ||
96
- /\brate\s+cuts?\b/.test(lower)
96
+ /\bbacktest\b/.test(lower) || /\bsentiment\b/.test(lower) || /\brate\s+cuts?\b/.test(lower)
97
97
  );
98
98
  },
99
99
  },
@@ -145,6 +145,14 @@ const RULES: Rule[] = [
145
145
  return hasOptionKeywords && entities.symbols.length >= 1;
146
146
  },
147
147
  },
148
+ // Stateful portfolio/watchlist/alert/prediction mutations must not be
149
+ // mistaken for compare or portfolio-construction workflows just because a
150
+ // cost basis, target, or currency token is present.
151
+ {
152
+ workflow: "watchlist_or_tracking",
153
+ confidence: 0.95,
154
+ test: (input) => isStatefulTrackingRequest(input),
155
+ },
148
156
  // Compare: keyword + 2+ symbols (uppercase)
149
157
  {
150
158
  workflow: "compare_assets",
@@ -165,8 +173,10 @@ const RULES: Rule[] = [
165
173
  confidence: 0.85,
166
174
  test: (input, entities) => {
167
175
  const lower = input.toLowerCase();
168
- return entities.symbols.length >= 2 &&
169
- /\bcompare\s+[a-z]{1,5}\b(?:\s*,?\s*(?:and\s+)?[a-z]{1,5}\b)+/.test(lower);
176
+ return (
177
+ entities.symbols.length >= 2 &&
178
+ /\bcompare\s+[a-z]{1,5}\b(?:\s*,?\s*(?:and\s+)?[a-z]{1,5}\b)+/.test(lower)
179
+ );
170
180
  },
171
181
  },
172
182
  // Compare: 2+ uppercase symbols without explicit keyword
@@ -275,11 +285,92 @@ export function classifyIntent(input: string): ClassificationResult {
275
285
  function isPortfolioEvaluationRequest(input: string): boolean {
276
286
  const lower = input.toLowerCase();
277
287
  const hasEvaluationIntent =
278
- /\b(?:evaluat(?:e|ion)|review|assess|analy[sz]e|prospects?|risks?|opportunities?|mitigat(?:e|ion)|adjustment)\b/.test(lower);
288
+ /\b(?:evaluat(?:e|ion)|review|assess|analy[sz]e|prospects?|risks?|opportunities?|mitigat(?:e|ion)|adjustment)\b/.test(
289
+ lower,
290
+ );
279
291
  const hasPortfolioObject =
280
- /\b(?:portfolio|allocation|asset\s+allocation|60\/40|equity|fixed\s+income|bonds?)\b/.test(lower);
292
+ /\b(?:portfolio|allocation|asset\s+allocation|60\/40|equity|fixed\s+income|bonds?)\b/.test(
293
+ lower,
294
+ );
281
295
  const hasConstructionIntent =
282
296
  /\b(?:build|create|construct|put\s+together|invest|allocate)\b/.test(lower) &&
283
- (/\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower));
297
+ /\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower);
284
298
  return hasEvaluationIntent && hasPortfolioObject && !hasConstructionIntent;
285
299
  }
300
+
301
+ function isStatefulTrackingRequest(input: string): boolean {
302
+ const lower = input.toLowerCase();
303
+ const hasPortfolioConstructionIntent =
304
+ /\b(?:build|create|construct|put\s+together)\b/.test(lower) &&
305
+ /\bportfolio\b/.test(lower) &&
306
+ /\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower);
307
+ const hasStateVerb =
308
+ /\b(?:add|remove|update|record|track|create|configure|check|show|list|view|cancel)\b/.test(
309
+ lower,
310
+ );
311
+ const hasStateObject =
312
+ /\b(?:watchlist|portfolio|holding|holdings|position|positions|prediction|predictions|alert|alerts|daily\s+report|watchlist\s+report|report\s+history)\b/.test(
313
+ lower,
314
+ );
315
+ const hasPortfolioLotShape =
316
+ /\b(?:add|record|track)\b/.test(lower) &&
317
+ /\b\d+(?:\.\d+)?\s+shares?\b/.test(lower) &&
318
+ /\b(?:portfolio|holding|holdings|position|positions)\b/.test(lower);
319
+ if (hasPortfolioConstructionIntent) return false;
320
+ return (hasStateVerb && hasStateObject) || hasPortfolioLotShape;
321
+ }
322
+
323
+ const FINANCE_SIGNAL_TERMS = [
324
+ "stock",
325
+ "stocks",
326
+ "shares",
327
+ "ticker",
328
+ "tickers",
329
+ "etf",
330
+ "etfs",
331
+ "ipo",
332
+ "earnings",
333
+ "dividend",
334
+ "dividends",
335
+ "valuation",
336
+ "stock market",
337
+ "invest",
338
+ "investing",
339
+ "investment",
340
+ "portfolio",
341
+ "watchlist",
342
+ "bond",
343
+ "bonds",
344
+ "bond yield",
345
+ "treasury",
346
+ "the fed",
347
+ "inflation",
348
+ "interest rates",
349
+ "crypto",
350
+ "bitcoin",
351
+ "ethereum",
352
+ "options chain",
353
+ "covered call",
354
+ "puts",
355
+ "bullish",
356
+ "bearish",
357
+ "hedge",
358
+ "price target",
359
+ "cost basis",
360
+ "nasdaq",
361
+ "s&p",
362
+ ];
363
+
364
+ const FINANCE_SIGNAL_PATTERN = new RegExp(
365
+ `\\b(?:${FINANCE_SIGNAL_TERMS.map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`,
366
+ "i",
367
+ );
368
+
369
+ /**
370
+ * Deterministic finance-vocabulary check for rules-mode fallback turns whose
371
+ * intent did not match a workflow and whose entities carry no symbols (for
372
+ * example theme prompts about private companies or sectors).
373
+ */
374
+ export function hasFinanceSignals(input: string): boolean {
375
+ return FINANCE_SIGNAL_PATTERN.test(input);
376
+ }
@@ -1,4 +1,4 @@
1
- import type { PortfolioSlots, OptionsScreenerSlots } from "./types.js";
1
+ import type { OptionsScreenerSlots, PortfolioSlots } from "./types.js";
2
2
 
3
3
  export const PORTFOLIO_DEFAULTS: Omit<PortfolioSlots, "budget"> = {
4
4
  riskProfile: "balanced",