opencandle 0.5.0 → 0.7.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 (574) hide show
  1. package/README.md +170 -186
  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 +66 -7
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +13 -3
  11. package/dist/config.js +25 -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/cache.d.ts +8 -11
  17. package/dist/infra/cache.js +17 -15
  18. package/dist/infra/cache.js.map +1 -1
  19. package/dist/infra/http-client.d.ts +4 -1
  20. package/dist/infra/http-client.js +59 -6
  21. package/dist/infra/http-client.js.map +1 -1
  22. package/dist/infra/index.d.ts +2 -3
  23. package/dist/infra/index.js +2 -3
  24. package/dist/infra/index.js.map +1 -1
  25. package/dist/infra/native-dependencies.js +2 -2
  26. package/dist/infra/native-dependencies.js.map +1 -1
  27. package/dist/infra/node-version.js.map +1 -1
  28. package/dist/infra/opencandle-paths.d.ts +0 -3
  29. package/dist/infra/opencandle-paths.js +4 -11
  30. package/dist/infra/opencandle-paths.js.map +1 -1
  31. package/dist/infra/rate-limiter.js +12 -9
  32. package/dist/infra/rate-limiter.js.map +1 -1
  33. package/dist/market-state/alert-conditions.d.ts +34 -0
  34. package/dist/market-state/alert-conditions.js +23 -0
  35. package/dist/market-state/alert-conditions.js.map +1 -0
  36. package/dist/market-state/alert-runner.d.ts +55 -0
  37. package/dist/market-state/alert-runner.js +634 -0
  38. package/dist/market-state/alert-runner.js.map +1 -0
  39. package/dist/market-state/daily-report.d.ts +26 -0
  40. package/dist/market-state/daily-report.js +179 -0
  41. package/dist/market-state/daily-report.js.map +1 -0
  42. package/dist/market-state/local-automation-service.d.ts +25 -0
  43. package/dist/market-state/local-automation-service.js +119 -0
  44. package/dist/market-state/local-automation-service.js.map +1 -0
  45. package/dist/market-state/notification-delivery.d.ts +14 -0
  46. package/dist/market-state/notification-delivery.js +139 -0
  47. package/dist/market-state/notification-delivery.js.map +1 -0
  48. package/dist/market-state/resolve-for-mutation.d.ts +10 -0
  49. package/dist/market-state/resolve-for-mutation.js +15 -0
  50. package/dist/market-state/resolve-for-mutation.js.map +1 -0
  51. package/dist/market-state/resolve.d.ts +14 -0
  52. package/dist/market-state/resolve.js +89 -0
  53. package/dist/market-state/resolve.js.map +1 -0
  54. package/dist/market-state/service.d.ts +527 -0
  55. package/dist/market-state/service.js +1099 -0
  56. package/dist/market-state/service.js.map +1 -0
  57. package/dist/memory/index.d.ts +7 -7
  58. package/dist/memory/index.js +6 -6
  59. package/dist/memory/index.js.map +1 -1
  60. package/dist/memory/manager.js +11 -11
  61. package/dist/memory/manager.js.map +1 -1
  62. package/dist/memory/retrieval.js +7 -4
  63. package/dist/memory/retrieval.js.map +1 -1
  64. package/dist/memory/sqlite.js +385 -3
  65. package/dist/memory/sqlite.js.map +1 -1
  66. package/dist/memory/storage.js +1 -2
  67. package/dist/memory/storage.js.map +1 -1
  68. package/dist/memory/tool-defaults.js +64 -28
  69. package/dist/memory/tool-defaults.js.map +1 -1
  70. package/dist/memory/types.js.map +1 -1
  71. package/dist/monitor.d.ts +2 -0
  72. package/dist/monitor.js +104 -0
  73. package/dist/monitor.js.map +1 -0
  74. package/dist/onboarding/connect.d.ts +2 -2
  75. package/dist/onboarding/connect.js +13 -8
  76. package/dist/onboarding/connect.js.map +1 -1
  77. package/dist/onboarding/credential-interceptor.js +1 -1
  78. package/dist/onboarding/credential-interceptor.js.map +1 -1
  79. package/dist/onboarding/degradation-accumulator.js +1 -3
  80. package/dist/onboarding/degradation-accumulator.js.map +1 -1
  81. package/dist/onboarding/provider-status.d.ts +48 -0
  82. package/dist/onboarding/provider-status.js +285 -0
  83. package/dist/onboarding/provider-status.js.map +1 -0
  84. package/dist/onboarding/providers.d.ts +85 -8
  85. package/dist/onboarding/providers.js +83 -18
  86. package/dist/onboarding/providers.js.map +1 -1
  87. package/dist/onboarding/state.d.ts +1 -0
  88. package/dist/onboarding/state.js +5 -0
  89. package/dist/onboarding/state.js.map +1 -1
  90. package/dist/onboarding/tool-helpers.js +1 -1
  91. package/dist/onboarding/tool-helpers.js.map +1 -1
  92. package/dist/onboarding/tool-tags.d.ts +12 -1
  93. package/dist/onboarding/tool-tags.js +37 -5
  94. package/dist/onboarding/tool-tags.js.map +1 -1
  95. package/dist/onboarding/validation.d.ts +2 -2
  96. package/dist/onboarding/validation.js +1 -1
  97. package/dist/onboarding/validation.js.map +1 -1
  98. package/dist/pi/opencandle-extension.d.ts +8 -0
  99. package/dist/pi/opencandle-extension.js +502 -42
  100. package/dist/pi/opencandle-extension.js.map +1 -1
  101. package/dist/pi/session.d.ts +1 -1
  102. package/dist/pi/session.js +3 -1
  103. package/dist/pi/session.js.map +1 -1
  104. package/dist/pi/setup.js +8 -3
  105. package/dist/pi/setup.js.map +1 -1
  106. package/dist/pi/tool-adapter.d.ts +4 -1
  107. package/dist/pi/tool-adapter.js +10 -6
  108. package/dist/pi/tool-adapter.js.map +1 -1
  109. package/dist/prompts/context-builder.d.ts +1 -1
  110. package/dist/prompts/context-builder.js +20 -7
  111. package/dist/prompts/context-builder.js.map +1 -1
  112. package/dist/prompts/policy-cards.d.ts +1 -1
  113. package/dist/prompts/policy-cards.js +2 -2
  114. package/dist/prompts/policy-cards.js.map +1 -1
  115. package/dist/prompts/sections.d.ts +1 -1
  116. package/dist/prompts/symbol-preflight.d.ts +20 -0
  117. package/dist/prompts/symbol-preflight.js +49 -0
  118. package/dist/prompts/symbol-preflight.js.map +1 -0
  119. package/dist/prompts/workflow-prompts.d.ts +1 -1
  120. package/dist/prompts/workflow-prompts.js +54 -16
  121. package/dist/prompts/workflow-prompts.js.map +1 -1
  122. package/dist/providers/alpha-vantage.d.ts +1 -1
  123. package/dist/providers/alpha-vantage.js +26 -7
  124. package/dist/providers/alpha-vantage.js.map +1 -1
  125. package/dist/providers/coingecko.js +1 -1
  126. package/dist/providers/coingecko.js.map +1 -1
  127. package/dist/providers/errors.d.ts +5 -0
  128. package/dist/providers/errors.js +11 -0
  129. package/dist/providers/errors.js.map +1 -0
  130. package/dist/providers/exa-search.d.ts +2 -2
  131. package/dist/providers/exa-search.js +19 -11
  132. package/dist/providers/exa-search.js.map +1 -1
  133. package/dist/providers/external-tool-error.d.ts +10 -0
  134. package/dist/providers/external-tool-error.js +21 -0
  135. package/dist/providers/external-tool-error.js.map +1 -0
  136. package/dist/providers/fear-greed.js +1 -1
  137. package/dist/providers/fear-greed.js.map +1 -1
  138. package/dist/providers/finnhub.js +3 -5
  139. package/dist/providers/finnhub.js.map +1 -1
  140. package/dist/providers/fred.js +2 -2
  141. package/dist/providers/fred.js.map +1 -1
  142. package/dist/providers/index.d.ts +7 -6
  143. package/dist/providers/index.js +6 -5
  144. package/dist/providers/index.js.map +1 -1
  145. package/dist/providers/reddit-cli.d.ts +36 -0
  146. package/dist/providers/reddit-cli.js +201 -0
  147. package/dist/providers/reddit-cli.js.map +1 -0
  148. package/dist/providers/reddit.d.ts +1 -1
  149. package/dist/providers/reddit.js +9 -37
  150. package/dist/providers/reddit.js.map +1 -1
  151. package/dist/providers/sec-edgar.d.ts +1 -0
  152. package/dist/providers/sec-edgar.js +12 -4
  153. package/dist/providers/sec-edgar.js.map +1 -1
  154. package/dist/providers/tradingview.d.ts +47 -0
  155. package/dist/providers/tradingview.js +275 -0
  156. package/dist/providers/tradingview.js.map +1 -0
  157. package/dist/providers/twitter-cli.d.ts +40 -0
  158. package/dist/providers/twitter-cli.js +153 -0
  159. package/dist/providers/twitter-cli.js.map +1 -0
  160. package/dist/providers/twitter.d.ts +0 -8
  161. package/dist/providers/twitter.js +8 -60
  162. package/dist/providers/twitter.js.map +1 -1
  163. package/dist/providers/web-search.js +26 -12
  164. package/dist/providers/web-search.js.map +1 -1
  165. package/dist/providers/with-fallback.js +4 -2
  166. package/dist/providers/with-fallback.js.map +1 -1
  167. package/dist/providers/wrap-provider.d.ts +2 -3
  168. package/dist/providers/wrap-provider.js +44 -8
  169. package/dist/providers/wrap-provider.js.map +1 -1
  170. package/dist/providers/yahoo-finance.d.ts +1 -1
  171. package/dist/providers/yahoo-finance.js +153 -48
  172. package/dist/providers/yahoo-finance.js.map +1 -1
  173. package/dist/routing/classify-intent.d.ts +6 -0
  174. package/dist/routing/classify-intent.js +78 -7
  175. package/dist/routing/classify-intent.js.map +1 -1
  176. package/dist/routing/defaults.d.ts +1 -1
  177. package/dist/routing/entity-extractor.d.ts +1 -0
  178. package/dist/routing/entity-extractor.js +234 -29
  179. package/dist/routing/entity-extractor.js.map +1 -1
  180. package/dist/routing/fund-symbols.d.ts +2 -0
  181. package/dist/routing/fund-symbols.js +55 -0
  182. package/dist/routing/fund-symbols.js.map +1 -0
  183. package/dist/routing/horizon.d.ts +1 -0
  184. package/dist/routing/horizon.js +10 -0
  185. package/dist/routing/horizon.js.map +1 -0
  186. package/dist/routing/index.d.ts +10 -10
  187. package/dist/routing/index.js +6 -6
  188. package/dist/routing/index.js.map +1 -1
  189. package/dist/routing/planning.d.ts +2 -2
  190. package/dist/routing/planning.js +65 -34
  191. package/dist/routing/planning.js.map +1 -1
  192. package/dist/routing/route-manifest.d.ts +2 -2
  193. package/dist/routing/route-manifest.js +25 -4
  194. package/dist/routing/route-manifest.js.map +1 -1
  195. package/dist/routing/router-llm-client.js.map +1 -1
  196. package/dist/routing/router-prompt.js +7 -9
  197. package/dist/routing/router-prompt.js.map +1 -1
  198. package/dist/routing/router-types.d.ts +1 -0
  199. package/dist/routing/router.js +137 -22
  200. package/dist/routing/router.js.map +1 -1
  201. package/dist/routing/slot-resolver.d.ts +1 -1
  202. package/dist/routing/slot-resolver.js +2 -4
  203. package/dist/routing/slot-resolver.js.map +1 -1
  204. package/dist/routing/symbol-disambiguator.d.ts +11 -0
  205. package/dist/routing/symbol-disambiguator.js +52 -0
  206. package/dist/routing/symbol-disambiguator.js.map +1 -0
  207. package/dist/routing/turn-context.d.ts +1 -1
  208. package/dist/routing/turn-context.js +1 -1
  209. package/dist/routing/turn-context.js.map +1 -1
  210. package/dist/routing/types.d.ts +2 -0
  211. package/dist/runtime/answer-contracts.d.ts +1 -1
  212. package/dist/runtime/answer-contracts.js +48 -9
  213. package/dist/runtime/answer-contracts.js.map +1 -1
  214. package/dist/runtime/artifact-contracts.js.map +1 -1
  215. package/dist/runtime/planning-evidence.js +47 -26
  216. package/dist/runtime/planning-evidence.js.map +1 -1
  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 +13 -5
  224. package/dist/runtime/session-coordinator.js +160 -20
  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 +7 -5
  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 +10 -11
  243. package/dist/sentiment/index.js +10 -20
  244. package/dist/sentiment/index.js.map +1 -1
  245. package/dist/sentiment/insights.d.ts +17 -0
  246. package/dist/sentiment/insights.js +206 -0
  247. package/dist/sentiment/insights.js.map +1 -0
  248. package/dist/sentiment/keywords.js +26 -4
  249. package/dist/sentiment/keywords.js.map +1 -1
  250. package/dist/sentiment/pipeline.d.ts +2 -2
  251. package/dist/sentiment/pipeline.js +14 -2
  252. package/dist/sentiment/pipeline.js.map +1 -1
  253. package/dist/sentiment/scorer.d.ts +2 -0
  254. package/dist/sentiment/scorer.js +11 -2
  255. package/dist/sentiment/scorer.js.map +1 -1
  256. package/dist/sentiment/store.d.ts +1 -1
  257. package/dist/sentiment/store.js +1 -1
  258. package/dist/sentiment/store.js.map +1 -1
  259. package/dist/sentiment/trends.d.ts +1 -1
  260. package/dist/sentiment/trends.js.map +1 -1
  261. package/dist/sentiment/types.d.ts +2 -0
  262. package/dist/sentiment/types.js.map +1 -1
  263. package/dist/system-prompt.js +6 -9
  264. package/dist/system-prompt.js.map +1 -1
  265. package/dist/tool-kit.d.ts +7 -7
  266. package/dist/tool-kit.js +4 -4
  267. package/dist/tool-kit.js.map +1 -1
  268. package/dist/tools/fundamentals/company-overview.js +11 -6
  269. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  270. package/dist/tools/fundamentals/comps.js +18 -9
  271. package/dist/tools/fundamentals/comps.js.map +1 -1
  272. package/dist/tools/fundamentals/dcf.js +23 -11
  273. package/dist/tools/fundamentals/dcf.js.map +1 -1
  274. package/dist/tools/fundamentals/earnings.js +8 -3
  275. package/dist/tools/fundamentals/earnings.js.map +1 -1
  276. package/dist/tools/fundamentals/financials.js +8 -3
  277. package/dist/tools/fundamentals/financials.js.map +1 -1
  278. package/dist/tools/fundamentals/sec-filings.js +21 -6
  279. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  280. package/dist/tools/index.d.ts +27 -20
  281. package/dist/tools/index.js +55 -43
  282. package/dist/tools/index.js.map +1 -1
  283. package/dist/tools/interaction/ask-user.js +15 -3
  284. package/dist/tools/interaction/ask-user.js.map +1 -1
  285. package/dist/tools/macro/fear-greed.js.map +1 -1
  286. package/dist/tools/macro/fred-data.d.ts +1 -1
  287. package/dist/tools/macro/fred-data.js +17 -6
  288. package/dist/tools/macro/fred-data.js.map +1 -1
  289. package/dist/tools/market/crypto-history.js +3 -1
  290. package/dist/tools/market/crypto-history.js.map +1 -1
  291. package/dist/tools/market/crypto-price.js +3 -1
  292. package/dist/tools/market/crypto-price.js.map +1 -1
  293. package/dist/tools/market/screen-stocks.d.ts +18 -0
  294. package/dist/tools/market/screen-stocks.js +252 -0
  295. package/dist/tools/market/screen-stocks.js.map +1 -0
  296. package/dist/tools/market/search-ticker.js +160 -8
  297. package/dist/tools/market/search-ticker.js.map +1 -1
  298. package/dist/tools/market/stock-history.d.ts +2 -2
  299. package/dist/tools/market/stock-history.js +26 -7
  300. package/dist/tools/market/stock-history.js.map +1 -1
  301. package/dist/tools/market/stock-quote.js +5 -3
  302. package/dist/tools/market/stock-quote.js.map +1 -1
  303. package/dist/tools/options/greeks.js +1 -1
  304. package/dist/tools/options/greeks.js.map +1 -1
  305. package/dist/tools/options/option-chain.js +19 -6
  306. package/dist/tools/options/option-chain.js.map +1 -1
  307. package/dist/tools/portfolio/alerts.d.ts +15 -0
  308. package/dist/tools/portfolio/alerts.js +357 -0
  309. package/dist/tools/portfolio/alerts.js.map +1 -0
  310. package/dist/tools/portfolio/correlation.d.ts +1 -1
  311. package/dist/tools/portfolio/correlation.js +33 -13
  312. package/dist/tools/portfolio/correlation.js.map +1 -1
  313. package/dist/tools/portfolio/daily-report.d.ts +8 -0
  314. package/dist/tools/portfolio/daily-report.js +83 -0
  315. package/dist/tools/portfolio/daily-report.js.map +1 -0
  316. package/dist/tools/portfolio/holdings-overlap.js +10 -3
  317. package/dist/tools/portfolio/holdings-overlap.js.map +1 -1
  318. package/dist/tools/portfolio/notifications.d.ts +7 -0
  319. package/dist/tools/portfolio/notifications.js +43 -0
  320. package/dist/tools/portfolio/notifications.js.map +1 -0
  321. package/dist/tools/portfolio/predictions.d.ts +12 -6
  322. package/dist/tools/portfolio/predictions.js +337 -87
  323. package/dist/tools/portfolio/predictions.js.map +1 -1
  324. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  325. package/dist/tools/portfolio/risk-analysis.js +45 -6
  326. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  327. package/dist/tools/portfolio/tracker.d.ts +4 -3
  328. package/dist/tools/portfolio/tracker.js +246 -101
  329. package/dist/tools/portfolio/tracker.js.map +1 -1
  330. package/dist/tools/portfolio/watchlist.d.ts +6 -4
  331. package/dist/tools/portfolio/watchlist.js +208 -108
  332. package/dist/tools/portfolio/watchlist.js.map +1 -1
  333. package/dist/tools/sentiment/insight-format.d.ts +2 -0
  334. package/dist/tools/sentiment/insight-format.js +36 -0
  335. package/dist/tools/sentiment/insight-format.js.map +1 -0
  336. package/dist/tools/sentiment/query-match.d.ts +3 -0
  337. package/dist/tools/sentiment/query-match.js +113 -0
  338. package/dist/tools/sentiment/query-match.js.map +1 -0
  339. package/dist/tools/sentiment/reddit-sentiment.d.ts +12 -1
  340. package/dist/tools/sentiment/reddit-sentiment.js +266 -107
  341. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  342. package/dist/tools/sentiment/sentiment-summary.d.ts +9 -1
  343. package/dist/tools/sentiment/sentiment-summary.js +223 -205
  344. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  345. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  346. package/dist/tools/sentiment/sentiment-trend.js +12 -2
  347. package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
  348. package/dist/tools/sentiment/twitter-sentiment.d.ts +11 -1
  349. package/dist/tools/sentiment/twitter-sentiment.js +188 -58
  350. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  351. package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
  352. package/dist/tools/sentiment/untrusted-text.js +17 -0
  353. package/dist/tools/sentiment/untrusted-text.js.map +1 -0
  354. package/dist/tools/sentiment/web-search.js +9 -13
  355. package/dist/tools/sentiment/web-search.js.map +1 -1
  356. package/dist/tools/sentiment/web-sentiment.js +19 -3
  357. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  358. package/dist/tools/technical/backtest.d.ts +1 -1
  359. package/dist/tools/technical/backtest.js +27 -20
  360. package/dist/tools/technical/backtest.js.map +1 -1
  361. package/dist/tools/technical/indicators.js +23 -5
  362. package/dist/tools/technical/indicators.js.map +1 -1
  363. package/dist/types/index.d.ts +3 -3
  364. package/dist/types/index.js.map +1 -1
  365. package/dist/types/market.d.ts +1 -0
  366. package/dist/types/portfolio.d.ts +14 -4
  367. package/dist/types/sentiment.d.ts +52 -0
  368. package/dist/workflows/compare-assets.d.ts +0 -3
  369. package/dist/workflows/compare-assets.js +20 -11
  370. package/dist/workflows/compare-assets.js.map +1 -1
  371. package/dist/workflows/index.d.ts +3 -4
  372. package/dist/workflows/index.js +3 -3
  373. package/dist/workflows/index.js.map +1 -1
  374. package/dist/workflows/options-screener.d.ts +0 -3
  375. package/dist/workflows/options-screener.js +4 -11
  376. package/dist/workflows/options-screener.js.map +1 -1
  377. package/dist/workflows/portfolio-builder.d.ts +0 -3
  378. package/dist/workflows/portfolio-builder.js +0 -8
  379. package/dist/workflows/portfolio-builder.js.map +1 -1
  380. package/gui/server/ask-user-bridge.ts +1 -1
  381. package/gui/server/automation-heartbeat.ts +97 -0
  382. package/gui/server/background-quotes.ts +97 -1
  383. package/gui/server/chat-event-adapter.ts +32 -10
  384. package/gui/server/chat-run-session.ts +16 -0
  385. package/gui/server/invoke-tool.ts +160 -3
  386. package/gui/server/live-chat-event-adapter.ts +21 -6
  387. package/gui/server/market-state-api.ts +315 -0
  388. package/gui/server/model-setup.ts +156 -2
  389. package/gui/server/private-api-access.ts +62 -0
  390. package/gui/server/projector.ts +18 -9
  391. package/gui/server/prompt-observation.ts +4 -7
  392. package/gui/server/quote-snapshot-store.ts +50 -0
  393. package/gui/server/server.ts +218 -451
  394. package/gui/server/session-actions.ts +186 -1
  395. package/gui/server/shutdown.ts +47 -0
  396. package/gui/server/tool-invoke-ack.ts +49 -0
  397. package/gui/server/tool-metadata.ts +101 -24
  398. package/gui/server/websocket.ts +13 -3
  399. package/gui/server/writer-lock.ts +6 -2
  400. package/gui/server/ws-hub.ts +311 -0
  401. package/gui/shared/chat-events.ts +16 -1
  402. package/gui/shared/event-reducer.ts +24 -6
  403. package/gui/web/dist/assets/CatalogOverlay-CgeY5Pkp.js +1 -0
  404. package/gui/web/dist/assets/index-C6W_2eAn.js +69 -0
  405. package/gui/web/dist/assets/index-hwbx24a5.css +1 -0
  406. package/gui/web/dist/index.html +2 -2
  407. package/package.json +9 -6
  408. package/src/analysts/contracts.ts +10 -23
  409. package/src/analysts/orchestrator.ts +8 -43
  410. package/src/cli.ts +76 -12
  411. package/src/config.ts +44 -9
  412. package/src/index.ts +1 -1
  413. package/src/infra/cache.ts +41 -30
  414. package/src/infra/http-client.ts +72 -6
  415. package/src/infra/index.ts +6 -10
  416. package/src/infra/native-dependencies.ts +8 -3
  417. package/src/infra/node-version.ts +3 -1
  418. package/src/infra/opencandle-paths.ts +3 -14
  419. package/src/infra/rate-limiter.ts +22 -19
  420. package/src/market-state/alert-conditions.ts +82 -0
  421. package/src/market-state/alert-runner.ts +863 -0
  422. package/src/market-state/daily-report.ts +247 -0
  423. package/src/market-state/local-automation-service.ts +162 -0
  424. package/src/market-state/notification-delivery.ts +158 -0
  425. package/src/market-state/resolve-for-mutation.ts +24 -0
  426. package/src/market-state/resolve.ts +112 -0
  427. package/src/market-state/service.ts +2344 -0
  428. package/src/memory/index.ts +7 -7
  429. package/src/memory/manager.ts +14 -16
  430. package/src/memory/retrieval.ts +8 -7
  431. package/src/memory/sqlite.ts +407 -6
  432. package/src/memory/storage.ts +5 -15
  433. package/src/memory/tool-defaults.ts +60 -39
  434. package/src/memory/types.ts +3 -3
  435. package/src/monitor.ts +121 -0
  436. package/src/onboarding/connect.ts +24 -31
  437. package/src/onboarding/credential-interceptor.ts +3 -15
  438. package/src/onboarding/degradation-accumulator.ts +1 -3
  439. package/src/onboarding/provider-status.ts +410 -0
  440. package/src/onboarding/providers.ts +144 -45
  441. package/src/onboarding/state.ts +13 -15
  442. package/src/onboarding/tool-helpers.ts +2 -9
  443. package/src/onboarding/tool-tags.ts +51 -8
  444. package/src/onboarding/validation.ts +16 -22
  445. package/src/pi/opencandle-extension.ts +643 -101
  446. package/src/pi/session.ts +7 -5
  447. package/src/pi/setup.ts +61 -43
  448. package/src/pi/tool-adapter.ts +19 -6
  449. package/src/prompts/context-builder.ts +24 -13
  450. package/src/prompts/policy-cards.ts +3 -3
  451. package/src/prompts/sections.ts +1 -1
  452. package/src/prompts/symbol-preflight.ts +80 -0
  453. package/src/prompts/workflow-prompts.ts +77 -28
  454. package/src/providers/alpha-vantage.ts +58 -39
  455. package/src/providers/coingecko.ts +2 -5
  456. package/src/providers/errors.ts +9 -0
  457. package/src/providers/exa-search.ts +24 -22
  458. package/src/providers/external-tool-error.ts +20 -0
  459. package/src/providers/fear-greed.ts +1 -1
  460. package/src/providers/finnhub.ts +7 -6
  461. package/src/providers/fred.ts +3 -3
  462. package/src/providers/index.ts +14 -6
  463. package/src/providers/reddit-cli.ts +317 -0
  464. package/src/providers/reddit.ts +14 -59
  465. package/src/providers/sec-edgar.ts +20 -6
  466. package/src/providers/tradingview.ts +399 -0
  467. package/src/providers/twitter-cli.ts +233 -0
  468. package/src/providers/twitter.ts +8 -79
  469. package/src/providers/web-search.ts +30 -20
  470. package/src/providers/with-fallback.ts +8 -7
  471. package/src/providers/wrap-provider.ts +49 -10
  472. package/src/providers/yahoo-finance.ts +204 -66
  473. package/src/routing/classify-intent.ts +101 -10
  474. package/src/routing/defaults.ts +1 -1
  475. package/src/routing/entity-extractor.ts +287 -38
  476. package/src/routing/fund-symbols.ts +58 -0
  477. package/src/routing/horizon.ts +7 -0
  478. package/src/routing/index.ts +48 -48
  479. package/src/routing/planning.ts +145 -53
  480. package/src/routing/route-manifest.ts +37 -15
  481. package/src/routing/router-llm-client.ts +4 -4
  482. package/src/routing/router-prompt.ts +15 -19
  483. package/src/routing/router-types.ts +2 -5
  484. package/src/routing/router.ts +251 -53
  485. package/src/routing/slot-resolver.ts +34 -11
  486. package/src/routing/symbol-disambiguator.ts +72 -0
  487. package/src/routing/turn-context.ts +6 -9
  488. package/src/routing/types.ts +2 -0
  489. package/src/runtime/answer-contracts.ts +105 -45
  490. package/src/runtime/artifact-contracts.ts +2 -1
  491. package/src/runtime/planning-evidence.ts +157 -66
  492. package/src/runtime/prompt-step.ts +1 -16
  493. package/src/runtime/run-context.ts +12 -2
  494. package/src/runtime/session-coordinator.ts +238 -63
  495. package/src/runtime/session-title.ts +60 -0
  496. package/src/runtime/tool-defaults-wrapper.ts +13 -5
  497. package/src/runtime/validation.ts +1 -4
  498. package/src/runtime/workflow-events.ts +7 -7
  499. package/src/runtime/workflow-runner.ts +5 -11
  500. package/src/sentiment/adapters/finnhub.ts +7 -2
  501. package/src/sentiment/adapters/reddit.ts +2 -2
  502. package/src/sentiment/adapters/twitter.ts +1 -1
  503. package/src/sentiment/adapters/web.ts +1 -1
  504. package/src/sentiment/index.ts +17 -26
  505. package/src/sentiment/insights.ts +269 -0
  506. package/src/sentiment/keywords.ts +26 -4
  507. package/src/sentiment/pipeline.ts +28 -5
  508. package/src/sentiment/scorer.ts +13 -2
  509. package/src/sentiment/store.ts +2 -2
  510. package/src/sentiment/trends.ts +9 -3
  511. package/src/sentiment/types.ts +8 -4
  512. package/src/system-prompt.ts +6 -9
  513. package/src/tool-kit.ts +10 -9
  514. package/src/tools/fundamentals/company-overview.ts +19 -9
  515. package/src/tools/fundamentals/comps.ts +68 -55
  516. package/src/tools/fundamentals/dcf.ts +145 -95
  517. package/src/tools/fundamentals/earnings.ts +16 -6
  518. package/src/tools/fundamentals/financials.ts +16 -7
  519. package/src/tools/fundamentals/sec-filings.ts +37 -16
  520. package/src/tools/index.ts +56 -43
  521. package/src/tools/interaction/ask-user.ts +22 -10
  522. package/src/tools/macro/fear-greed.ts +1 -1
  523. package/src/tools/macro/fred-data.ts +58 -46
  524. package/src/tools/market/crypto-history.ts +8 -3
  525. package/src/tools/market/crypto-price.ts +6 -6
  526. package/src/tools/market/screen-stocks.ts +279 -0
  527. package/src/tools/market/search-ticker.ts +218 -17
  528. package/src/tools/market/stock-history.ts +37 -12
  529. package/src/tools/market/stock-quote.ts +10 -7
  530. package/src/tools/options/greeks.ts +5 -5
  531. package/src/tools/options/option-chain.ts +41 -17
  532. package/src/tools/portfolio/alerts.ts +457 -0
  533. package/src/tools/portfolio/correlation.ts +47 -20
  534. package/src/tools/portfolio/daily-report.ts +101 -0
  535. package/src/tools/portfolio/holdings-overlap.ts +31 -15
  536. package/src/tools/portfolio/notifications.ts +45 -0
  537. package/src/tools/portfolio/predictions.ts +406 -106
  538. package/src/tools/portfolio/risk-analysis.ts +46 -7
  539. package/src/tools/portfolio/tracker.ts +270 -109
  540. package/src/tools/portfolio/watchlist.ts +250 -121
  541. package/src/tools/sentiment/insight-format.ts +50 -0
  542. package/src/tools/sentiment/query-match.ts +117 -0
  543. package/src/tools/sentiment/reddit-sentiment.ts +360 -121
  544. package/src/tools/sentiment/sentiment-summary.ts +302 -235
  545. package/src/tools/sentiment/sentiment-trend.ts +24 -7
  546. package/src/tools/sentiment/twitter-sentiment.ts +264 -73
  547. package/src/tools/sentiment/untrusted-text.ts +21 -0
  548. package/src/tools/sentiment/web-search.ts +21 -18
  549. package/src/tools/sentiment/web-sentiment.ts +30 -10
  550. package/src/tools/technical/backtest.ts +32 -22
  551. package/src/tools/technical/indicators.ts +39 -14
  552. package/src/types/index.ts +8 -3
  553. package/src/types/market.ts +1 -0
  554. package/src/types/portfolio.ts +14 -4
  555. package/src/types/sentiment.ts +61 -2
  556. package/src/workflows/compare-assets.ts +33 -21
  557. package/src/workflows/index.ts +3 -4
  558. package/src/workflows/options-screener.ts +27 -29
  559. package/src/workflows/portfolio-builder.ts +34 -27
  560. package/dist/infra/browser.d.ts +0 -35
  561. package/dist/infra/browser.js +0 -103
  562. package/dist/infra/browser.js.map +0 -1
  563. package/dist/tools/interaction/twitter-login.d.ts +0 -8
  564. package/dist/tools/interaction/twitter-login.js +0 -77
  565. package/dist/tools/interaction/twitter-login.js.map +0 -1
  566. package/dist/workflows/types.d.ts +0 -4
  567. package/dist/workflows/types.js +0 -2
  568. package/dist/workflows/types.js.map +0 -1
  569. package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +0 -1
  570. package/gui/web/dist/assets/index-Bxt9QpLX.css +0 -1
  571. package/gui/web/dist/assets/index-CZ9DHZYy.js +0 -67
  572. package/src/infra/browser.ts +0 -111
  573. package/src/tools/interaction/twitter-login.ts +0 -93
  574. package/src/workflows/types.ts +0 -4
@@ -1,6 +1,9 @@
1
- import { extractEntities, isAmbiguousConceptUsage } from "./entity-extractor.js";
1
+ import {
2
+ extractEntities,
3
+ isAmbiguousConceptUsage,
4
+ isCurrencyCodeUsage,
5
+ } from "./entity-extractor.js";
2
6
  import { classifyWithLegacyRules } from "./legacy-rule-router.js";
3
- import { buildRouterPrompt } from "./router-prompt.js";
4
7
  import {
5
8
  computeMissingRequiredSlots,
6
9
  isDispatchableWorkflow,
@@ -10,6 +13,7 @@ import {
10
13
  routeKindFromLegacyRoute,
11
14
  selectToolBundles,
12
15
  } from "./route-manifest.js";
16
+ import { buildRouterPrompt } from "./router-prompt.js";
13
17
  import type {
14
18
  RouterDiagnostic,
15
19
  RouterInputContext,
@@ -21,6 +25,7 @@ import type {
21
25
  RouterSlot,
22
26
  ToolBundleName,
23
27
  } from "./router-types.js";
28
+ import { disambiguateSymbols } from "./symbol-disambiguator.js";
24
29
  import type { ExtractedEntities, WorkflowType } from "./types.js";
25
30
 
26
31
  const VALID_ROUTES: readonly RouterRoute[] = ["workflow", "fallback"];
@@ -105,11 +110,19 @@ export function validateRouterOutput(raw: string): RouterOutput {
105
110
  : routeKindFromLegacyRoute(route, rawMissingRequired);
106
111
 
107
112
  if (route === "workflow" || routeKind === "workflow_dispatch") {
108
- if (typeof obj.workflow !== "string" || !VALID_WORKFLOWS.includes(obj.workflow as Exclude<WorkflowType, "unclassified">)) {
109
- throw new Error(`workflow route requires a valid workflow; got ${JSON.stringify(obj.workflow)}`);
113
+ if (
114
+ typeof obj.workflow !== "string" ||
115
+ !VALID_WORKFLOWS.includes(obj.workflow as Exclude<WorkflowType, "unclassified">)
116
+ ) {
117
+ throw new Error(
118
+ `workflow route requires a valid workflow; got ${JSON.stringify(obj.workflow)}`,
119
+ );
110
120
  }
111
121
  workflow = obj.workflow as Exclude<WorkflowType, "unclassified">;
112
- } else if (typeof obj.workflow === "string" && VALID_WORKFLOWS.includes(obj.workflow as Exclude<WorkflowType, "unclassified">)) {
122
+ } else if (
123
+ typeof obj.workflow === "string" &&
124
+ VALID_WORKFLOWS.includes(obj.workflow as Exclude<WorkflowType, "unclassified">)
125
+ ) {
113
126
  workflow = obj.workflow as Exclude<WorkflowType, "unclassified">;
114
127
  }
115
128
 
@@ -119,8 +132,7 @@ export function validateRouterOutput(raw: string): RouterOutput {
119
132
  const missing_required = rawMissingRequired;
120
133
  const tool_bundles = validateToolBundles(obj.tool_bundles);
121
134
  const diagnostics = validateDiagnostics(obj.diagnostics);
122
- const reasoning =
123
- typeof obj.reasoning === "string" ? obj.reasoning : "";
135
+ const reasoning = typeof obj.reasoning === "string" ? obj.reasoning : "";
124
136
 
125
137
  return {
126
138
  routeKind,
@@ -156,9 +168,7 @@ function validateEntities(raw: unknown): ExtractedEntities {
156
168
  throw new Error("entities must be an object");
157
169
  }
158
170
  const e = raw as Record<string, unknown>;
159
- const symbols = validateStringArray(e.symbols, "entities.symbols").map((s) =>
160
- s.toUpperCase(),
161
- );
171
+ const symbols = validateStringArray(e.symbols, "entities.symbols").map((s) => s.toUpperCase());
162
172
 
163
173
  const out: ExtractedEntities = { symbols };
164
174
  if (typeof e.budget === "number") out.budget = e.budget;
@@ -169,10 +179,11 @@ function validateEntities(raw: unknown): ExtractedEntities {
169
179
  if (typeof e.riskProfile === "string") out.riskProfile = e.riskProfile;
170
180
  if (e.direction === "bullish" || e.direction === "bearish") out.direction = e.direction;
171
181
  if (typeof e.dteHint === "string") out.dteHint = e.dteHint;
172
- if (e.optionStrategy === "covered_call" || e.optionStrategy === "protective_put") out.optionStrategy = e.optionStrategy;
182
+ if (e.optionStrategy === "covered_call" || e.optionStrategy === "protective_put")
183
+ out.optionStrategy = e.optionStrategy;
173
184
  if (typeof e.heldSymbol === "string") out.heldSymbol = e.heldSymbol.toUpperCase();
174
- const catalystSymbols = validateStringArray(e.catalystSymbols, "entities.catalystSymbols").map((s) =>
175
- s.toUpperCase(),
185
+ const catalystSymbols = validateStringArray(e.catalystSymbols, "entities.catalystSymbols").map(
186
+ (s) => s.toUpperCase(),
176
187
  );
177
188
  if (catalystSymbols.length > 0) out.catalystSymbols = catalystSymbols;
178
189
  const compareMetrics = validateStringArray(e.compareMetrics, "entities.compareMetrics");
@@ -188,9 +199,7 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
188
199
  ...output,
189
200
  entities: {
190
201
  ...output.entities,
191
- symbols: output.entities.symbols.filter((symbol) =>
192
- !isAmbiguousConceptUsage(text, symbol),
193
- ),
202
+ symbols: output.entities.symbols.filter((symbol) => !isAmbiguousConceptUsage(text, symbol)),
194
203
  budget: output.entities.budget ?? extracted.budget,
195
204
  maxPremium: output.entities.maxPremium ?? extracted.maxPremium,
196
205
  timeHorizon: output.entities.timeHorizon ?? extracted.timeHorizon,
@@ -203,21 +212,30 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
203
212
  shareQuantity: output.entities.shareQuantity ?? extracted.shareQuantity,
204
213
  heldSymbol: output.entities.heldSymbol ?? extracted.heldSymbol,
205
214
  catalystSymbols: output.entities.catalystSymbols ?? extracted.catalystSymbols,
206
- dteHint: output.entities.dteHint ?? (output.workflow === "options_screener" ? extracted.dteHint : undefined),
215
+ dteHint:
216
+ output.entities.dteHint ??
217
+ (output.workflow === "options_screener" ? extracted.dteHint : undefined),
207
218
  },
208
219
  diagnostics,
209
220
  };
210
221
 
211
- if (next.workflow === "options_screener" && isExistingPositionOptionRequest(text, extracted) && extracted.heldSymbol) {
222
+ if (
223
+ next.workflow === "options_screener" &&
224
+ isExistingPositionOptionRequest(text, extracted) &&
225
+ extracted.heldSymbol
226
+ ) {
212
227
  const reorderedSymbols = [
213
228
  extracted.heldSymbol,
214
- ...mergeSymbols(next.entities.symbols, extracted.symbols).filter((symbol) => symbol !== extracted.heldSymbol),
229
+ ...mergeSymbols(next.entities.symbols, extracted.symbols).filter(
230
+ (symbol) => symbol !== extracted.heldSymbol,
231
+ ),
215
232
  ];
216
233
  if (next.entities.symbols[0] !== extracted.heldSymbol) {
217
234
  diagnostics.push({
218
- code: extracted.optionStrategy === "protective_put"
219
- ? "existing_position_underlying_corrected"
220
- : "covered_call_underlying_corrected",
235
+ code:
236
+ extracted.optionStrategy === "protective_put"
237
+ ? "existing_position_underlying_corrected"
238
+ : "covered_call_underlying_corrected",
221
239
  message: `using owned position ${extracted.heldSymbol} as the option-chain underlying`,
222
240
  });
223
241
  }
@@ -245,7 +263,8 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
245
263
  ) {
246
264
  diagnostics.push({
247
265
  code: "options_workflow_corrected_to_policy_task",
248
- message: "options education or suitability prompt should use policy-card synthesis, not contract-screen workflow dispatch",
266
+ message:
267
+ "options education or suitability prompt should use policy-card synthesis, not contract-screen workflow dispatch",
249
268
  });
250
269
  next = {
251
270
  ...next,
@@ -278,7 +297,10 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
278
297
  timeHorizon: deterministic.entities.timeHorizon ?? extracted.timeHorizon,
279
298
  riskProfile: deterministic.entities.riskProfile ?? extracted.riskProfile,
280
299
  assetScope: deterministic.entities.assetScope ?? extracted.assetScope,
281
- compareMetrics: mergeStringArrays(deterministic.entities.compareMetrics, extracted.compareMetrics),
300
+ compareMetrics: mergeStringArrays(
301
+ deterministic.entities.compareMetrics,
302
+ extracted.compareMetrics,
303
+ ),
282
304
  direction: deterministic.entities.direction ?? extracted.direction,
283
305
  costBasis: deterministic.entities.costBasis ?? extracted.costBasis,
284
306
  shareQuantity: deterministic.entities.shareQuantity ?? extracted.shareQuantity,
@@ -314,6 +336,30 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
314
336
  };
315
337
  }
316
338
 
339
+ if (
340
+ (next.workflow === "compare_assets" || next.workflow === "portfolio_builder") &&
341
+ isStatefulTrackingRequest(text)
342
+ ) {
343
+ diagnostics.push({
344
+ code: "stateful_tracking_corrected_to_agent_task",
345
+ message:
346
+ "portfolio/watchlist tracking mutation should use stateful tracking tools, not compare or construction workflow dispatch",
347
+ });
348
+ next = {
349
+ ...next,
350
+ routeKind: "agent_task",
351
+ route: "fallback",
352
+ workflow: "watchlist_or_tracking",
353
+ missing_required: [],
354
+ entities: {
355
+ ...next.entities,
356
+ symbols: filterCurrencyUnitSymbols(text, next.entities.symbols),
357
+ },
358
+ slots: removeCurrencyUnitSymbolSlots(text, next.slots),
359
+ diagnostics,
360
+ };
361
+ }
362
+
317
363
  if (next.routeKind === "agent_task" && isDispatchableWorkflow(next.workflow)) {
318
364
  diagnostics.push({
319
365
  code: "dispatchable_workflow_corrected_to_workflow_dispatch",
@@ -349,7 +395,8 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
349
395
  if (next.workflow === "compare_assets" && isPortfolioEvaluationRequest(text)) {
350
396
  diagnostics.push({
351
397
  code: "portfolio_evaluation_corrected_to_agent_task",
352
- message: "existing portfolio/allocation risk review should not be reduced to asset comparison",
398
+ message:
399
+ "existing portfolio/allocation risk review should not be reduced to asset comparison",
353
400
  });
354
401
  next = {
355
402
  ...next,
@@ -381,7 +428,8 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
381
428
  if (next.workflow === "portfolio_builder" && isCryptoSizingRequest(text)) {
382
429
  diagnostics.push({
383
430
  code: "crypto_sizing_corrected_to_agent_task",
384
- message: "crypto allocation-range and drawdown questions are advisory tradeoffs, not portfolio construction",
431
+ message:
432
+ "crypto allocation-range and drawdown questions are advisory tradeoffs, not portfolio construction",
385
433
  });
386
434
  next = {
387
435
  ...next,
@@ -396,7 +444,8 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
396
444
  if (next.workflow === "portfolio_builder" && isPortfolioEvaluationRequest(text)) {
397
445
  diagnostics.push({
398
446
  code: "portfolio_evaluation_corrected_to_agent_task",
399
- message: "existing portfolio/allocation evaluation does not require portfolio-construction budget",
447
+ message:
448
+ "existing portfolio/allocation evaluation does not require portfolio-construction budget",
400
449
  });
401
450
  next = {
402
451
  ...next,
@@ -415,7 +464,8 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
415
464
  ) {
416
465
  diagnostics.push({
417
466
  code: "portfolio_tradeoff_corrected_to_compare_assets",
418
- message: "explicit multi-asset tradeoff question should compare the requested assets before constructing a portfolio",
467
+ message:
468
+ "explicit multi-asset tradeoff question should compare the requested assets before constructing a portfolio",
419
469
  });
420
470
  next = {
421
471
  ...next,
@@ -427,10 +477,7 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
427
477
  };
428
478
  }
429
479
 
430
- if (
431
- next.workflow === "single_asset_analysis" &&
432
- isSpecializedSingleAssetPolicyRequest(text)
433
- ) {
480
+ if (next.workflow === "single_asset_analysis" && isSpecializedSingleAssetPolicyRequest(text)) {
434
481
  diagnostics.push({
435
482
  code: "single_asset_workflow_corrected_to_general_policy_task",
436
483
  message: "prompt asks for policy-card planning outside a single-asset buy/sell analysis",
@@ -442,6 +489,34 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
442
489
  };
443
490
  }
444
491
 
492
+ const disambiguated = disambiguateSymbols(next.entities.symbols, text);
493
+ if (disambiguated.dropped.length > 0) {
494
+ for (const drop of disambiguated.dropped) {
495
+ diagnostics.push({
496
+ code: "symbol_dropped",
497
+ message: `${drop.token} dropped: ${drop.reason}`,
498
+ details: {
499
+ token: drop.token,
500
+ reason: drop.reason,
501
+ signalsChecked: drop.signalsChecked,
502
+ source: "llm",
503
+ },
504
+ });
505
+ }
506
+ next = {
507
+ ...next,
508
+ entities: {
509
+ ...next.entities,
510
+ symbols: disambiguated.kept,
511
+ },
512
+ slots: removeDroppedSymbolSlots(
513
+ next.slots,
514
+ disambiguated.dropped.map((drop) => drop.token),
515
+ ),
516
+ diagnostics,
517
+ };
518
+ }
519
+
445
520
  const missingRequired = computeMissingRequiredSlots(
446
521
  next.workflow,
447
522
  next.entities,
@@ -473,7 +548,9 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
473
548
  message: "conceptual education prompt does not need live finance tools",
474
549
  });
475
550
  }
476
- const emittedUnsupported = next.tool_bundles.filter((bundle) => !selectedToolBundles.includes(bundle));
551
+ const emittedUnsupported = next.tool_bundles.filter(
552
+ (bundle) => !selectedToolBundles.includes(bundle),
553
+ );
477
554
  if (emittedUnsupported.length > 0) {
478
555
  diagnostics.push({
479
556
  code: "tool_bundles_corrected",
@@ -490,22 +567,34 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
490
567
  }
491
568
 
492
569
  function isExplicitMacroDataRequest(text: string): boolean {
493
- return /\b(?:get_economic_data|fred|cpi|inflation|fed\s+funds?|unemployment|gdp|macro)\b/i.test(text);
570
+ return /\b(?:get_economic_data|fred|cpi|inflation|fed\s+funds?|unemployment|gdp|macro)\b/i.test(
571
+ text,
572
+ );
494
573
  }
495
574
 
496
575
  function isConceptualEducationRequest(text: string, output: RouterOutput): boolean {
497
576
  if (output.routeKind !== "agent_task") return false;
498
577
  if (output.entities.symbols.length > 0) return false;
499
578
  if (isForwardLookingMacroContextRequest(text)) return false;
500
- if (/\b(?:current|recent|today|right now|latest|news|sentiment|build|portfolio|buy|sell|allocate|compare)\b/i.test(text)) {
579
+ if (
580
+ /\b(?:current|recent|today|right now|latest|news|sentiment|build|portfolio|buy|sell|allocate|compare)\b/i.test(
581
+ text,
582
+ )
583
+ ) {
501
584
  return false;
502
585
  }
503
- return /\b(?:explain|what is|define|how (?:do|should|to)|teach me|help me understand)\b/i.test(text);
586
+ return /\b(?:explain|what is|define|how (?:do|should|to)|teach me|help me understand)\b/i.test(
587
+ text,
588
+ );
504
589
  }
505
590
 
506
591
  function isForwardLookingMacroContextRequest(text: string): boolean {
507
- return /\b(?:rates?|rate\s*cuts?|fed|inflation|macro)\b/i.test(text) &&
508
- /\b(?:next\s+(?:year|12\s*months?)|over\s+the\s+next|outlook|affect|impact|falling|rising)\b/i.test(text);
592
+ return (
593
+ /\b(?:rates?|rate\s*cuts?|fed|inflation|macro)\b/i.test(text) &&
594
+ /\b(?:next\s+(?:year|12\s*months?)|over\s+the\s+next|outlook|affect|impact|falling|rising)\b/i.test(
595
+ text,
596
+ )
597
+ );
509
598
  }
510
599
 
511
600
  function isCoveredCallRequest(text: string): boolean {
@@ -515,19 +604,75 @@ function isCoveredCallRequest(text: string): boolean {
515
604
  function isPortfolioEvaluationRequest(text: string): boolean {
516
605
  const lower = text.toLowerCase();
517
606
  const hasEvaluationIntent =
518
- /\b(?:evaluat(?:e|ion)|review|assess|analy[sz]e|prospects?|risks?|risky|opportunities?|mitigat(?:e|ion)|adjustment|rebalance|diversify|concentration|overweight|underweight|target\s+bands?|drift|worried|crash|protect|protection|missing\s+out\s+on\s+growth)\b/.test(lower);
607
+ /\b(?:evaluat(?:e|ion)|review|assess|analy[sz]e|prospects?|risks?|risky|opportunities?|mitigat(?:e|ion)|adjustment|rebalance|diversify|concentration|overweight|underweight|target\s+bands?|drift|worried|crash|protect|protection|missing\s+out\s+on\s+growth)\b/.test(
608
+ lower,
609
+ );
519
610
  const hasPortfolioObject =
520
- /\b(?:portfolio|allocation|asset\s+allocation|60\/40|equity|fixed\s+income|bonds?)\b/.test(lower);
611
+ /\b(?:portfolio|allocation|asset\s+allocation|60\/40|equity|fixed\s+income|bonds?)\b/.test(
612
+ lower,
613
+ );
521
614
  const hasConstructionIntent =
522
615
  /\b(?:build|create|construct|put\s+together|invest|allocate)\b/.test(lower) &&
523
- (/\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower));
616
+ /\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower);
524
617
  return hasEvaluationIntent && hasPortfolioObject && !hasConstructionIntent;
525
618
  }
526
619
 
620
+ function isStatefulTrackingRequest(text: string): boolean {
621
+ const lower = text.toLowerCase();
622
+ const hasPortfolioConstructionIntent =
623
+ /\b(?:build|create|construct|put\s+together)\b/.test(lower) &&
624
+ /\bportfolio\b/.test(lower) &&
625
+ /\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower);
626
+ const hasStateVerb =
627
+ /\b(?:add|remove|update|record|track|create|configure|check|show|list|view|cancel)\b/.test(
628
+ lower,
629
+ );
630
+ const hasStateObject =
631
+ /\b(?:watchlist|portfolio|holding|holdings|position|positions|prediction|predictions|alert|alerts|daily\s+report|watchlist\s+report|report\s+history)\b/.test(
632
+ lower,
633
+ );
634
+ const hasPortfolioLotShape =
635
+ /\b(?:add|record|track)\b/.test(lower) &&
636
+ /\b\d+(?:\.\d+)?\s+shares?\b/.test(lower) &&
637
+ /\b(?:portfolio|holding|holdings|position|positions)\b/.test(lower);
638
+ if (hasPortfolioConstructionIntent) return false;
639
+ return (hasStateVerb && hasStateObject) || hasPortfolioLotShape;
640
+ }
641
+
642
+ function filterCurrencyUnitSymbols(text: string, symbols: string[]): string[] {
643
+ return symbols.filter((symbol) => !isCurrencyCodeUsage(text, symbol));
644
+ }
645
+
646
+ function removeCurrencyUnitSymbolSlots(
647
+ text: string,
648
+ slots: Record<string, RouterSlot>,
649
+ ): Record<string, RouterSlot> {
650
+ const next = { ...slots };
651
+ for (const key of ["symbol", "symbols"]) {
652
+ const slot = next[key];
653
+ if (!slot) continue;
654
+ if (Array.isArray(slot.value)) {
655
+ const value = slot.value.filter(
656
+ (item) => typeof item !== "string" || !isCurrencyCodeUsage(text, item.toUpperCase()),
657
+ );
658
+ if (value.length === 0) delete next[key];
659
+ else next[key] = { ...slot, value };
660
+ continue;
661
+ }
662
+ if (typeof slot.value === "string" && isCurrencyCodeUsage(text, slot.value.toUpperCase())) {
663
+ delete next[key];
664
+ }
665
+ }
666
+ return next;
667
+ }
668
+
527
669
  function isPortfolioTradeoffComparisonRequest(text: string): boolean {
528
670
  const lower = text.toLowerCase();
529
- return /\b(?:prioritize|tradeoffs?|growth[-\s]?oriented|dividend|income|which\s+(?:one|is)\s+better|should\s+i)\b/.test(lower) &&
530
- /\b(?:or|vs\.?|versus|compare)\b/.test(lower);
671
+ return (
672
+ /\b(?:prioritize|tradeoffs?|growth[-\s]?oriented|dividend|income|which\s+(?:one|is)\s+better|should\s+i)\b/.test(
673
+ lower,
674
+ ) && /\b(?:or|vs\.?|versus|compare)\b/.test(lower)
675
+ );
531
676
  }
532
677
 
533
678
  function isCryptoSizingRequest(text: string): boolean {
@@ -536,16 +681,20 @@ function isCryptoSizingRequest(text: string): boolean {
536
681
  /\b(?:build|create|construct|put\s+together)\b/.test(lower) &&
537
682
  /\b(?:portfolio|allocation)\b/.test(lower);
538
683
  if (hasPortfolioConstructionIntent) return false;
539
- return /\b(?:btc|bitcoin|crypto)\b/.test(lower) &&
540
- /\b(?:allocation|range|position\s+size|sizing|exposure|drawdown)\b/.test(lower);
684
+ return (
685
+ /\b(?:btc|bitcoin|crypto)\b/.test(lower) &&
686
+ /\b(?:allocation|range|position\s+size|sizing|exposure|drawdown)\b/.test(lower)
687
+ );
541
688
  }
542
689
 
543
690
  function isSpecializedSingleAssetPolicyRequest(text: string): boolean {
544
691
  const lower = text.toLowerCase();
545
- return /\b(?:ticker|symbol|formerly|old ticker|earnings are|earnings tonight)\b/.test(lower) ||
692
+ return (
693
+ /\b(?:ticker|symbol|formerly|old ticker|earnings are|earnings tonight)\b/.test(lower) ||
546
694
  /\b(?:today|right now|this morning|after close|moved|catalyst)\b/.test(lower) ||
547
695
  /\b(?:sentiment|mood|reddit|twitter|x\/twitter)\b/.test(lower) ||
548
- /\b(?:filing|10-k|10-q|8-k|sec)\b/.test(lower);
696
+ /\b(?:filing|10-k|10-q|8-k|sec)\b/.test(lower)
697
+ );
549
698
  }
550
699
 
551
700
  function isExistingPositionOptionRequest(text: string, extracted: ExtractedEntities): boolean {
@@ -554,14 +703,23 @@ function isExistingPositionOptionRequest(text: string, extracted: ExtractedEntit
554
703
 
555
704
  function isOptionsEducationOrSuitabilityRequest(text: string): boolean {
556
705
  const lower = text.toLowerCase();
557
- return /\b(?:how\s+does|how\s+do|explain|what\s+is|good\s+idea|make\s+sense|suitable|suitability|is\s+it\s+(?:good|worth|smart))\b/.test(lower) &&
558
- /\b(?:covered\s+calls?|protective\s+puts?|options?|selling\s+calls?|option\s+income)\b/.test(lower);
706
+ return (
707
+ /\b(?:how\s+does|how\s+do|explain|what\s+is|good\s+idea|make\s+sense|suitable|suitability|is\s+it\s+(?:good|worth|smart))\b/.test(
708
+ lower,
709
+ ) &&
710
+ /\b(?:covered\s+calls?|protective\s+puts?|options?|selling\s+calls?|option\s+income)\b/.test(
711
+ lower,
712
+ )
713
+ );
559
714
  }
560
715
 
561
716
  function isSpecificOptionContractSelectionRequest(text: string): boolean {
562
717
  const lower = text.toLowerCase();
563
- return /\b(?:best|which|what\s+(?:strike|contract|option)|rank|screen|specific|right\s+now|today|around\s+earnings|expiration|dte|premium\s+under)\b/.test(lower) &&
564
- /\b(?:sell|buy|trade|contract|strike|expiration|premium|call|put)\b/.test(lower);
718
+ return (
719
+ /\b(?:best|which|what\s+(?:strike|contract|option)|rank|screen|specific|right\s+now|today|around\s+earnings|expiration|dte|premium\s+under)\b/.test(
720
+ lower,
721
+ ) && /\b(?:sell|buy|trade|contract|strike|expiration|premium|call|put)\b/.test(lower)
722
+ );
565
723
  }
566
724
 
567
725
  function mergeSymbols(primary: string[], secondary: string[]): string[] {
@@ -639,7 +797,9 @@ function validatePreferenceUpdates(raw: unknown): RouterPreferenceUpdate[] {
639
797
  // (normalized), but any explicit non-"inferred" value is an invariant
640
798
  // violation the caller should see rather than silently lose.
641
799
  if (p.source !== undefined && p.source !== "inferred") {
642
- throw new Error(`preference_updates[${idx}].source must be "inferred" (got ${JSON.stringify(p.source)})`);
800
+ throw new Error(
801
+ `preference_updates[${idx}].source must be "inferred" (got ${JSON.stringify(p.source)})`,
802
+ );
643
803
  }
644
804
  return {
645
805
  key: p.key,
@@ -655,6 +815,36 @@ function validateToolBundles(raw: unknown): ToolBundleName[] {
655
815
  return bundles.filter(isToolBundleName);
656
816
  }
657
817
 
818
+ function removeDroppedSymbolSlots(
819
+ slots: Record<string, RouterSlot>,
820
+ droppedTokens: string[],
821
+ ): Record<string, RouterSlot> {
822
+ if (droppedTokens.length === 0) return slots;
823
+ const dropped = new Set(droppedTokens.map((token) => token.toUpperCase()));
824
+ const next = { ...slots };
825
+
826
+ for (const key of ["symbol", "symbols"]) {
827
+ const slot = next[key];
828
+ if (!slot) continue;
829
+ if (Array.isArray(slot.value)) {
830
+ const value = slot.value.filter(
831
+ (item) => typeof item !== "string" || !dropped.has(item.toUpperCase()),
832
+ );
833
+ if (value.length === 0) {
834
+ delete next[key];
835
+ } else {
836
+ next[key] = { ...slot, value };
837
+ }
838
+ continue;
839
+ }
840
+ if (typeof slot.value === "string" && dropped.has(slot.value.toUpperCase())) {
841
+ delete next[key];
842
+ }
843
+ }
844
+
845
+ return next;
846
+ }
847
+
658
848
  function validateDiagnostics(raw: unknown): RouterDiagnostic[] {
659
849
  if (raw === undefined || raw === null) return [];
660
850
  if (!Array.isArray(raw)) {
@@ -671,10 +861,18 @@ function validateDiagnostics(raw: unknown): RouterDiagnostic[] {
671
861
  if (typeof diagnostic.message !== "string") {
672
862
  throw new Error(`diagnostics[${idx}].message must be a string`);
673
863
  }
674
- return {
864
+ const out: RouterDiagnostic = {
675
865
  code: diagnostic.code,
676
866
  message: diagnostic.message,
677
867
  };
868
+ if (
869
+ diagnostic.details &&
870
+ typeof diagnostic.details === "object" &&
871
+ !Array.isArray(diagnostic.details)
872
+ ) {
873
+ out.details = diagnostic.details as Record<string, unknown>;
874
+ }
875
+ return out;
678
876
  });
679
877
  }
680
878
 
@@ -1,11 +1,11 @@
1
+ import { OPTIONS_SCREENER_DEFAULTS, PORTFOLIO_DEFAULTS } from "./defaults.js";
1
2
  import type {
2
3
  ExtractedEntities,
3
- PortfolioSlots,
4
4
  OptionsScreenerSlots,
5
+ PortfolioSlots,
5
6
  SlotResolution,
6
7
  SlotSource,
7
8
  } from "./types.js";
8
- import { PORTFOLIO_DEFAULTS, OPTIONS_SCREENER_DEFAULTS } from "./defaults.js";
9
9
 
10
10
  interface Preferences {
11
11
  riskProfile?: string;
@@ -85,11 +85,19 @@ export function resolvePortfolioSlots(
85
85
  sources.budget = "default";
86
86
  }
87
87
 
88
- const risk = resolve(entities.riskProfile, preferences.riskProfile, PORTFOLIO_DEFAULTS.riskProfile);
88
+ const risk = resolve(
89
+ entities.riskProfile,
90
+ preferences.riskProfile,
91
+ PORTFOLIO_DEFAULTS.riskProfile,
92
+ );
89
93
  sources.riskProfile = risk.source;
90
94
  if (risk.source === "default") defaultsUsed.push("riskProfile");
91
95
 
92
- const horizon = resolve(entities.timeHorizon, preferences.timeHorizon, PORTFOLIO_DEFAULTS.timeHorizon);
96
+ const horizon = resolve(
97
+ entities.timeHorizon,
98
+ preferences.timeHorizon,
99
+ PORTFOLIO_DEFAULTS.timeHorizon,
100
+ );
93
101
  sources.timeHorizon = horizon.source;
94
102
  if (horizon.source === "default") defaultsUsed.push("timeHorizon");
95
103
 
@@ -101,7 +109,11 @@ export function resolvePortfolioSlots(
101
109
  sources.positionCount = count.source;
102
110
  if (count.source === "default") defaultsUsed.push("positionCount");
103
111
 
104
- const maxPct = resolve(undefined, preferences.maxSinglePositionPct, PORTFOLIO_DEFAULTS.maxSinglePositionPct);
112
+ const maxPct = resolve(
113
+ undefined,
114
+ preferences.maxSinglePositionPct,
115
+ PORTFOLIO_DEFAULTS.maxSinglePositionPct,
116
+ );
105
117
  sources.maxSinglePositionPct = maxPct.source;
106
118
  if (maxPct.source === "default") defaultsUsed.push("maxSinglePositionPct");
107
119
 
@@ -140,14 +152,17 @@ export function resolveOptionsScreenerSlots(
140
152
  }
141
153
 
142
154
  // Direction: default to bullish unless the user specified a protective put hedge.
143
- const inferredDirection = entities.optionStrategy === "protective_put"
144
- ? "bearish"
145
- : entities.direction;
155
+ const inferredDirection =
156
+ entities.optionStrategy === "protective_put" ? "bearish" : entities.direction;
146
157
  const dir = resolve(inferredDirection, undefined, "bullish" as const);
147
158
  sources.direction = dir.source;
148
159
  if (dir.source === "default") defaultsUsed.push("direction");
149
160
 
150
- const dte = resolve(mapDteHintToTarget(entities.dteHint), preferences.dteTarget, OPTIONS_SCREENER_DEFAULTS.dteTarget);
161
+ const dte = resolve(
162
+ mapDteHintToTarget(entities.dteHint),
163
+ preferences.dteTarget,
164
+ OPTIONS_SCREENER_DEFAULTS.dteTarget,
165
+ );
151
166
  sources.dteTarget = dte.source;
152
167
  if (dte.source === "default") defaultsUsed.push("dteTarget");
153
168
 
@@ -155,11 +170,19 @@ export function resolveOptionsScreenerSlots(
155
170
  sources.objective = obj.source;
156
171
  if (obj.source === "default") defaultsUsed.push("objective");
157
172
 
158
- const moneyness = resolve(undefined, preferences.moneynessPreference, OPTIONS_SCREENER_DEFAULTS.moneynessPreference);
173
+ const moneyness = resolve(
174
+ undefined,
175
+ preferences.moneynessPreference,
176
+ OPTIONS_SCREENER_DEFAULTS.moneynessPreference,
177
+ );
159
178
  sources.moneynessPreference = moneyness.source;
160
179
  if (moneyness.source === "default") defaultsUsed.push("moneynessPreference");
161
180
 
162
- const liquidity = resolve(undefined, preferences.liquidityMinimum, OPTIONS_SCREENER_DEFAULTS.liquidityMinimum);
181
+ const liquidity = resolve(
182
+ undefined,
183
+ preferences.liquidityMinimum,
184
+ OPTIONS_SCREENER_DEFAULTS.liquidityMinimum,
185
+ );
163
186
  sources.liquidityMinimum = liquidity.source;
164
187
  if (liquidity.source === "default") defaultsUsed.push("liquidityMinimum");
165
188
 
@@ -0,0 +1,72 @@
1
+ export const FINANCE_ACRONYM_DICTIONARY = new Set([
2
+ "IV",
3
+ "HV",
4
+ "ITM",
5
+ "OTM",
6
+ "ATM",
7
+ "IPO",
8
+ "SEC",
9
+ "FED",
10
+ "FOMC",
11
+ "IRS",
12
+ "ECB",
13
+ "BOE",
14
+ "BOJ",
15
+ "GDP",
16
+ "CPI",
17
+ "PPI",
18
+ "FX",
19
+ "NDA",
20
+ ]);
21
+
22
+ export interface DroppedSymbol {
23
+ token: string;
24
+ reason: string;
25
+ signalsChecked: string[];
26
+ }
27
+
28
+ export interface SymbolDisambiguation {
29
+ kept: string[];
30
+ dropped: DroppedSymbol[];
31
+ }
32
+
33
+ export function disambiguateSymbols(candidates: string[], rawInput: string): SymbolDisambiguation {
34
+ const kept: string[] = [];
35
+ const dropped: DroppedSymbol[] = [];
36
+
37
+ for (const candidate of candidates) {
38
+ const token = candidate.toUpperCase();
39
+ if (!FINANCE_ACRONYM_DICTIONARY.has(token) || hasPositiveTickerSignal(token, rawInput)) {
40
+ if (!kept.includes(token)) kept.push(token);
41
+ continue;
42
+ }
43
+
44
+ dropped.push({
45
+ token,
46
+ reason: "no positive ticker signal",
47
+ signalsChecked: ["cashtag", "local ticker phrase"],
48
+ });
49
+ }
50
+
51
+ return { kept, dropped };
52
+ }
53
+
54
+ function hasPositiveTickerSignal(token: string, rawInput: string): boolean {
55
+ return hasCashtag(token, rawInput) || hasLocalTickerPhrase(token, rawInput);
56
+ }
57
+
58
+ function hasCashtag(token: string, rawInput: string): boolean {
59
+ return new RegExp(`\\$${escapeRegExp(token)}\\b`, "i").test(rawInput);
60
+ }
61
+
62
+ function hasLocalTickerPhrase(token: string, rawInput: string): boolean {
63
+ const escaped = escapeRegExp(token);
64
+ return new RegExp(
65
+ `\\b${escaped}\\s+(?:ticker|stock|symbol)\\b|\\b(?:ticker|stock|symbol)\\s+${escaped}\\b`,
66
+ "i",
67
+ ).test(rawInput);
68
+ }
69
+
70
+ function escapeRegExp(value: string): string {
71
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
72
+ }