opencandle 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (564) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +186 -117
  3. package/dist/analysts/contracts.d.ts +1 -3
  4. package/dist/analysts/contracts.js +1 -11
  5. package/dist/analysts/contracts.js.map +1 -1
  6. package/dist/analysts/orchestrator.d.ts +1 -3
  7. package/dist/analysts/orchestrator.js +1 -26
  8. package/dist/analysts/orchestrator.js.map +1 -1
  9. package/dist/cli.js +32 -8
  10. package/dist/cli.js.map +1 -1
  11. package/dist/config.d.ts +19 -3
  12. package/dist/config.js +69 -3
  13. package/dist/config.js.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/infra/browser.d.ts +1 -3
  18. package/dist/infra/browser.js +4 -2
  19. package/dist/infra/browser.js.map +1 -1
  20. package/dist/infra/cache.d.ts +8 -11
  21. package/dist/infra/cache.js +17 -15
  22. package/dist/infra/cache.js.map +1 -1
  23. package/dist/infra/http-client.d.ts +4 -1
  24. package/dist/infra/http-client.js +59 -6
  25. package/dist/infra/http-client.js.map +1 -1
  26. package/dist/infra/index.d.ts +3 -3
  27. package/dist/infra/index.js +3 -3
  28. package/dist/infra/index.js.map +1 -1
  29. package/dist/infra/native-dependencies.js +2 -2
  30. package/dist/infra/native-dependencies.js.map +1 -1
  31. package/dist/infra/node-version.js.map +1 -1
  32. package/dist/infra/opencandle-paths.d.ts +0 -3
  33. package/dist/infra/opencandle-paths.js +4 -11
  34. package/dist/infra/opencandle-paths.js.map +1 -1
  35. package/dist/infra/rate-limiter.d.ts +4 -0
  36. package/dist/infra/rate-limiter.js +17 -10
  37. package/dist/infra/rate-limiter.js.map +1 -1
  38. package/dist/market-state/alert-conditions.d.ts +34 -0
  39. package/dist/market-state/alert-conditions.js +23 -0
  40. package/dist/market-state/alert-conditions.js.map +1 -0
  41. package/dist/market-state/alert-runner.d.ts +55 -0
  42. package/dist/market-state/alert-runner.js +634 -0
  43. package/dist/market-state/alert-runner.js.map +1 -0
  44. package/dist/market-state/daily-report.d.ts +26 -0
  45. package/dist/market-state/daily-report.js +179 -0
  46. package/dist/market-state/daily-report.js.map +1 -0
  47. package/dist/market-state/local-automation-service.d.ts +25 -0
  48. package/dist/market-state/local-automation-service.js +119 -0
  49. package/dist/market-state/local-automation-service.js.map +1 -0
  50. package/dist/market-state/notification-delivery.d.ts +14 -0
  51. package/dist/market-state/notification-delivery.js +139 -0
  52. package/dist/market-state/notification-delivery.js.map +1 -0
  53. package/dist/market-state/resolve-for-mutation.d.ts +10 -0
  54. package/dist/market-state/resolve-for-mutation.js +15 -0
  55. package/dist/market-state/resolve-for-mutation.js.map +1 -0
  56. package/dist/market-state/resolve.d.ts +14 -0
  57. package/dist/market-state/resolve.js +89 -0
  58. package/dist/market-state/resolve.js.map +1 -0
  59. package/dist/market-state/service.d.ts +527 -0
  60. package/dist/market-state/service.js +1099 -0
  61. package/dist/market-state/service.js.map +1 -0
  62. package/dist/memory/index.d.ts +7 -7
  63. package/dist/memory/index.js +6 -6
  64. package/dist/memory/index.js.map +1 -1
  65. package/dist/memory/manager.d.ts +9 -0
  66. package/dist/memory/manager.js +39 -22
  67. package/dist/memory/manager.js.map +1 -1
  68. package/dist/memory/retrieval.js +7 -4
  69. package/dist/memory/retrieval.js.map +1 -1
  70. package/dist/memory/sqlite.js +385 -3
  71. package/dist/memory/sqlite.js.map +1 -1
  72. package/dist/memory/storage.d.ts +3 -2
  73. package/dist/memory/storage.js +1 -2
  74. package/dist/memory/storage.js.map +1 -1
  75. package/dist/memory/tool-defaults.js +64 -28
  76. package/dist/memory/tool-defaults.js.map +1 -1
  77. package/dist/memory/types.js +4 -0
  78. package/dist/memory/types.js.map +1 -1
  79. package/dist/monitor.d.ts +2 -0
  80. package/dist/monitor.js +104 -0
  81. package/dist/monitor.js.map +1 -0
  82. package/dist/onboarding/connect.js +4 -6
  83. package/dist/onboarding/connect.js.map +1 -1
  84. package/dist/onboarding/credential-interceptor.js +1 -1
  85. package/dist/onboarding/credential-interceptor.js.map +1 -1
  86. package/dist/onboarding/degradation-accumulator.js +1 -3
  87. package/dist/onboarding/degradation-accumulator.js.map +1 -1
  88. package/dist/onboarding/providers.js +3 -16
  89. package/dist/onboarding/providers.js.map +1 -1
  90. package/dist/onboarding/state.js.map +1 -1
  91. package/dist/onboarding/tool-helpers.js +1 -1
  92. package/dist/onboarding/tool-helpers.js.map +1 -1
  93. package/dist/onboarding/tool-tags.js +6 -4
  94. package/dist/onboarding/tool-tags.js.map +1 -1
  95. package/dist/onboarding/validation.js +1 -1
  96. package/dist/onboarding/validation.js.map +1 -1
  97. package/dist/pi/opencandle-extension.d.ts +8 -0
  98. package/dist/pi/opencandle-extension.js +637 -59
  99. package/dist/pi/opencandle-extension.js.map +1 -1
  100. package/dist/pi/session.d.ts +1 -1
  101. package/dist/pi/session.js +3 -1
  102. package/dist/pi/session.js.map +1 -1
  103. package/dist/pi/setup.js +17 -2
  104. package/dist/pi/setup.js.map +1 -1
  105. package/dist/pi/tool-adapter.js +5 -2
  106. package/dist/pi/tool-adapter.js.map +1 -1
  107. package/dist/prompts/context-builder.d.ts +18 -3
  108. package/dist/prompts/context-builder.js +117 -18
  109. package/dist/prompts/context-builder.js.map +1 -1
  110. package/dist/prompts/disclaimer.js +1 -1
  111. package/dist/prompts/disclaimer.js.map +1 -1
  112. package/dist/prompts/policy-cards.d.ts +13 -0
  113. package/dist/prompts/policy-cards.js +197 -0
  114. package/dist/prompts/policy-cards.js.map +1 -0
  115. package/dist/prompts/sections.d.ts +1 -1
  116. package/dist/prompts/sections.js +3 -3
  117. package/dist/prompts/sections.js.map +1 -1
  118. package/dist/prompts/symbol-preflight.d.ts +20 -0
  119. package/dist/prompts/symbol-preflight.js +49 -0
  120. package/dist/prompts/symbol-preflight.js.map +1 -0
  121. package/dist/prompts/workflow-prompts.d.ts +1 -1
  122. package/dist/prompts/workflow-prompts.js +209 -19
  123. package/dist/prompts/workflow-prompts.js.map +1 -1
  124. package/dist/providers/alpha-vantage.d.ts +1 -1
  125. package/dist/providers/alpha-vantage.js +49 -8
  126. package/dist/providers/alpha-vantage.js.map +1 -1
  127. package/dist/providers/coingecko.js +1 -1
  128. package/dist/providers/coingecko.js.map +1 -1
  129. package/dist/providers/errors.d.ts +5 -0
  130. package/dist/providers/errors.js +11 -0
  131. package/dist/providers/errors.js.map +1 -0
  132. package/dist/providers/exa-search.d.ts +2 -2
  133. package/dist/providers/exa-search.js +19 -11
  134. package/dist/providers/exa-search.js.map +1 -1
  135. package/dist/providers/fear-greed.js +1 -1
  136. package/dist/providers/fear-greed.js.map +1 -1
  137. package/dist/providers/finnhub.js +3 -5
  138. package/dist/providers/finnhub.js.map +1 -1
  139. package/dist/providers/fred.js +2 -2
  140. package/dist/providers/fred.js.map +1 -1
  141. package/dist/providers/index.d.ts +7 -6
  142. package/dist/providers/index.js +6 -5
  143. package/dist/providers/index.js.map +1 -1
  144. package/dist/providers/reddit.js +2 -2
  145. package/dist/providers/reddit.js.map +1 -1
  146. package/dist/providers/sec-edgar.d.ts +9 -1
  147. package/dist/providers/sec-edgar.js +181 -6
  148. package/dist/providers/sec-edgar.js.map +1 -1
  149. package/dist/providers/tradingview.d.ts +47 -0
  150. package/dist/providers/tradingview.js +275 -0
  151. package/dist/providers/tradingview.js.map +1 -0
  152. package/dist/providers/twitter.js +6 -8
  153. package/dist/providers/twitter.js.map +1 -1
  154. package/dist/providers/web-search.js +26 -12
  155. package/dist/providers/web-search.js.map +1 -1
  156. package/dist/providers/with-fallback.js +4 -2
  157. package/dist/providers/with-fallback.js.map +1 -1
  158. package/dist/providers/wrap-provider.d.ts +2 -3
  159. package/dist/providers/wrap-provider.js +14 -8
  160. package/dist/providers/wrap-provider.js.map +1 -1
  161. package/dist/providers/yahoo-finance.d.ts +3 -1
  162. package/dist/providers/yahoo-finance.js +226 -11
  163. package/dist/providers/yahoo-finance.js.map +1 -1
  164. package/dist/routing/classify-intent.d.ts +9 -0
  165. package/dist/routing/classify-intent.js +153 -3
  166. package/dist/routing/classify-intent.js.map +1 -1
  167. package/dist/routing/defaults.d.ts +1 -1
  168. package/dist/routing/defaults.js +3 -3
  169. package/dist/routing/defaults.js.map +1 -1
  170. package/dist/routing/entity-extractor.d.ts +2 -0
  171. package/dist/routing/entity-extractor.js +377 -26
  172. package/dist/routing/entity-extractor.js.map +1 -1
  173. package/dist/routing/fund-symbols.d.ts +2 -0
  174. package/dist/routing/fund-symbols.js +55 -0
  175. package/dist/routing/fund-symbols.js.map +1 -0
  176. package/dist/routing/horizon.d.ts +1 -0
  177. package/dist/routing/horizon.js +10 -0
  178. package/dist/routing/horizon.js.map +1 -0
  179. package/dist/routing/index.d.ts +12 -6
  180. package/dist/routing/index.js +8 -4
  181. package/dist/routing/index.js.map +1 -1
  182. package/dist/routing/legacy-rule-router.d.ts +9 -0
  183. package/dist/routing/legacy-rule-router.js +12 -0
  184. package/dist/routing/legacy-rule-router.js.map +1 -0
  185. package/dist/routing/planning.d.ts +54 -0
  186. package/dist/routing/planning.js +562 -0
  187. package/dist/routing/planning.js.map +1 -0
  188. package/dist/routing/route-manifest.d.ts +35 -0
  189. package/dist/routing/route-manifest.js +242 -0
  190. package/dist/routing/route-manifest.js.map +1 -0
  191. package/dist/routing/router-llm-client.js.map +1 -1
  192. package/dist/routing/router-prompt.js +46 -45
  193. package/dist/routing/router-prompt.js.map +1 -1
  194. package/dist/routing/router-types.d.ts +10 -0
  195. package/dist/routing/router.d.ts +1 -0
  196. package/dist/routing/router.js +572 -13
  197. package/dist/routing/router.js.map +1 -1
  198. package/dist/routing/slot-resolver.d.ts +1 -1
  199. package/dist/routing/slot-resolver.js +45 -7
  200. package/dist/routing/slot-resolver.js.map +1 -1
  201. package/dist/routing/symbol-disambiguator.d.ts +11 -0
  202. package/dist/routing/symbol-disambiguator.js +52 -0
  203. package/dist/routing/symbol-disambiguator.js.map +1 -0
  204. package/dist/routing/turn-context.d.ts +44 -0
  205. package/dist/routing/turn-context.js +45 -0
  206. package/dist/routing/turn-context.js.map +1 -0
  207. package/dist/routing/types.d.ts +15 -1
  208. package/dist/runtime/answer-contracts.d.ts +82 -0
  209. package/dist/runtime/answer-contracts.js +442 -0
  210. package/dist/runtime/answer-contracts.js.map +1 -0
  211. package/dist/runtime/artifact-contracts.d.ts +14 -0
  212. package/dist/runtime/artifact-contracts.js +57 -0
  213. package/dist/runtime/artifact-contracts.js.map +1 -0
  214. package/dist/runtime/planning-evidence.d.ts +99 -0
  215. package/dist/runtime/planning-evidence.js +466 -0
  216. package/dist/runtime/planning-evidence.js.map +1 -0
  217. package/dist/runtime/prompt-step.d.ts +1 -9
  218. package/dist/runtime/prompt-step.js +0 -10
  219. package/dist/runtime/prompt-step.js.map +1 -1
  220. package/dist/runtime/run-context.d.ts +5 -2
  221. package/dist/runtime/run-context.js +8 -1
  222. package/dist/runtime/run-context.js.map +1 -1
  223. package/dist/runtime/session-coordinator.d.ts +29 -3
  224. package/dist/runtime/session-coordinator.js +204 -31
  225. package/dist/runtime/session-coordinator.js.map +1 -1
  226. package/dist/runtime/session-title.d.ts +14 -0
  227. package/dist/runtime/session-title.js +50 -0
  228. package/dist/runtime/session-title.js.map +1 -0
  229. package/dist/runtime/tool-defaults-wrapper.js +1 -3
  230. package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
  231. package/dist/runtime/validation.js.map +1 -1
  232. package/dist/runtime/workflow-events.js.map +1 -1
  233. package/dist/runtime/workflow-runner.d.ts +3 -3
  234. package/dist/runtime/workflow-runner.js +1 -1
  235. package/dist/runtime/workflow-runner.js.map +1 -1
  236. package/dist/sentiment/adapters/finnhub.d.ts +1 -1
  237. package/dist/sentiment/adapters/finnhub.js +6 -1
  238. package/dist/sentiment/adapters/finnhub.js.map +1 -1
  239. package/dist/sentiment/adapters/reddit.d.ts +2 -2
  240. package/dist/sentiment/adapters/twitter.d.ts +1 -1
  241. package/dist/sentiment/adapters/web.d.ts +1 -1
  242. package/dist/sentiment/index.d.ts +9 -11
  243. package/dist/sentiment/index.js +9 -20
  244. package/dist/sentiment/index.js.map +1 -1
  245. package/dist/sentiment/keywords.js +26 -4
  246. package/dist/sentiment/keywords.js.map +1 -1
  247. package/dist/sentiment/pipeline.d.ts +2 -2
  248. package/dist/sentiment/pipeline.js +1 -1
  249. package/dist/sentiment/pipeline.js.map +1 -1
  250. package/dist/sentiment/scorer.js +1 -1
  251. package/dist/sentiment/store.d.ts +1 -1
  252. package/dist/sentiment/store.js +1 -1
  253. package/dist/sentiment/store.js.map +1 -1
  254. package/dist/sentiment/trends.d.ts +1 -1
  255. package/dist/sentiment/trends.js.map +1 -1
  256. package/dist/sentiment/types.js.map +1 -1
  257. package/dist/system-prompt.js +7 -3
  258. package/dist/system-prompt.js.map +1 -1
  259. package/dist/tool-kit.d.ts +7 -7
  260. package/dist/tool-kit.js +4 -4
  261. package/dist/tool-kit.js.map +1 -1
  262. package/dist/tools/fundamentals/company-overview.js +12 -7
  263. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  264. package/dist/tools/fundamentals/comps.js +19 -10
  265. package/dist/tools/fundamentals/comps.js.map +1 -1
  266. package/dist/tools/fundamentals/dcf.js +24 -12
  267. package/dist/tools/fundamentals/dcf.js.map +1 -1
  268. package/dist/tools/fundamentals/earnings.js +9 -4
  269. package/dist/tools/fundamentals/earnings.js.map +1 -1
  270. package/dist/tools/fundamentals/financials.js +9 -4
  271. package/dist/tools/fundamentals/financials.js.map +1 -1
  272. package/dist/tools/fundamentals/sec-filings.d.ts +1 -0
  273. package/dist/tools/fundamentals/sec-filings.js +36 -4
  274. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  275. package/dist/tools/index.d.ts +23 -18
  276. package/dist/tools/index.js +53 -38
  277. package/dist/tools/index.js.map +1 -1
  278. package/dist/tools/interaction/ask-user.js +15 -3
  279. package/dist/tools/interaction/ask-user.js.map +1 -1
  280. package/dist/tools/interaction/twitter-login.js +13 -3
  281. package/dist/tools/interaction/twitter-login.js.map +1 -1
  282. package/dist/tools/macro/fear-greed.js +1 -1
  283. package/dist/tools/macro/fear-greed.js.map +1 -1
  284. package/dist/tools/macro/fred-data.d.ts +1 -1
  285. package/dist/tools/macro/fred-data.js +44 -9
  286. package/dist/tools/macro/fred-data.js.map +1 -1
  287. package/dist/tools/market/crypto-history.js +21 -3
  288. package/dist/tools/market/crypto-history.js.map +1 -1
  289. package/dist/tools/market/crypto-price.js +4 -2
  290. package/dist/tools/market/crypto-price.js.map +1 -1
  291. package/dist/tools/market/screen-stocks.d.ts +18 -0
  292. package/dist/tools/market/screen-stocks.js +252 -0
  293. package/dist/tools/market/screen-stocks.js.map +1 -0
  294. package/dist/tools/market/search-ticker.js +161 -9
  295. package/dist/tools/market/search-ticker.js.map +1 -1
  296. package/dist/tools/market/stock-history.d.ts +2 -2
  297. package/dist/tools/market/stock-history.js +27 -8
  298. package/dist/tools/market/stock-history.js.map +1 -1
  299. package/dist/tools/market/stock-quote.js +6 -4
  300. package/dist/tools/market/stock-quote.js.map +1 -1
  301. package/dist/tools/options/greeks.js +1 -2
  302. package/dist/tools/options/greeks.js.map +1 -1
  303. package/dist/tools/options/option-chain.js +27 -9
  304. package/dist/tools/options/option-chain.js.map +1 -1
  305. package/dist/tools/portfolio/alerts.d.ts +15 -0
  306. package/dist/tools/portfolio/alerts.js +357 -0
  307. package/dist/tools/portfolio/alerts.js.map +1 -0
  308. package/dist/tools/portfolio/correlation.d.ts +1 -1
  309. package/dist/tools/portfolio/correlation.js +34 -14
  310. package/dist/tools/portfolio/correlation.js.map +1 -1
  311. package/dist/tools/portfolio/daily-report.d.ts +8 -0
  312. package/dist/tools/portfolio/daily-report.js +83 -0
  313. package/dist/tools/portfolio/daily-report.js.map +1 -0
  314. package/dist/tools/portfolio/holdings-overlap.d.ts +8 -0
  315. package/dist/tools/portfolio/holdings-overlap.js +112 -0
  316. package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
  317. package/dist/tools/portfolio/notifications.d.ts +7 -0
  318. package/dist/tools/portfolio/notifications.js +43 -0
  319. package/dist/tools/portfolio/notifications.js.map +1 -0
  320. package/dist/tools/portfolio/predictions.d.ts +12 -6
  321. package/dist/tools/portfolio/predictions.js +338 -88
  322. package/dist/tools/portfolio/predictions.js.map +1 -1
  323. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  324. package/dist/tools/portfolio/risk-analysis.js +46 -7
  325. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  326. package/dist/tools/portfolio/tracker.d.ts +4 -3
  327. package/dist/tools/portfolio/tracker.js +247 -102
  328. package/dist/tools/portfolio/tracker.js.map +1 -1
  329. package/dist/tools/portfolio/watchlist.d.ts +6 -4
  330. package/dist/tools/portfolio/watchlist.js +209 -101
  331. package/dist/tools/portfolio/watchlist.js.map +1 -1
  332. package/dist/tools/sentiment/reddit-sentiment.js +24 -11
  333. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  334. package/dist/tools/sentiment/sentiment-summary.js +71 -14
  335. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  336. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  337. package/dist/tools/sentiment/sentiment-trend.js +12 -2
  338. package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
  339. package/dist/tools/sentiment/twitter-sentiment.js +13 -6
  340. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  341. package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
  342. package/dist/tools/sentiment/untrusted-text.js +17 -0
  343. package/dist/tools/sentiment/untrusted-text.js.map +1 -0
  344. package/dist/tools/sentiment/web-search.js +37 -12
  345. package/dist/tools/sentiment/web-search.js.map +1 -1
  346. package/dist/tools/sentiment/web-sentiment.js +16 -4
  347. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  348. package/dist/tools/technical/backtest.d.ts +3 -3
  349. package/dist/tools/technical/backtest.js +65 -44
  350. package/dist/tools/technical/backtest.js.map +1 -1
  351. package/dist/tools/technical/indicators.js +24 -8
  352. package/dist/tools/technical/indicators.js.map +1 -1
  353. package/dist/types/index.d.ts +3 -3
  354. package/dist/types/index.js.map +1 -1
  355. package/dist/types/market.d.ts +1 -0
  356. package/dist/types/options.d.ts +10 -0
  357. package/dist/types/portfolio.d.ts +41 -4
  358. package/dist/workflows/compare-assets.d.ts +0 -3
  359. package/dist/workflows/compare-assets.js +55 -10
  360. package/dist/workflows/compare-assets.js.map +1 -1
  361. package/dist/workflows/index.d.ts +3 -4
  362. package/dist/workflows/index.js +3 -3
  363. package/dist/workflows/index.js.map +1 -1
  364. package/dist/workflows/options-screener.d.ts +0 -3
  365. package/dist/workflows/options-screener.js +88 -14
  366. package/dist/workflows/options-screener.js.map +1 -1
  367. package/dist/workflows/portfolio-builder.d.ts +0 -3
  368. package/dist/workflows/portfolio-builder.js +7 -11
  369. package/dist/workflows/portfolio-builder.js.map +1 -1
  370. package/gui/server/ask-user-bridge.ts +82 -0
  371. package/gui/server/automation-heartbeat.ts +97 -0
  372. package/gui/server/background-quotes.ts +97 -1
  373. package/gui/server/chat-event-adapter.ts +32 -10
  374. package/gui/server/chat-run-session.ts +16 -0
  375. package/gui/server/gui-session-manager.ts +5 -0
  376. package/gui/server/invoke-tool.ts +144 -1
  377. package/gui/server/live-chat-event-adapter.ts +21 -6
  378. package/gui/server/market-state-api.ts +315 -0
  379. package/gui/server/model-setup.ts +149 -2
  380. package/gui/server/private-api-access.ts +62 -0
  381. package/gui/server/projector.ts +58 -11
  382. package/gui/server/prompt-observation.ts +58 -0
  383. package/gui/server/quote-snapshot-store.ts +50 -0
  384. package/gui/server/server.ts +236 -376
  385. package/gui/server/session-actions.ts +186 -1
  386. package/gui/server/session-entry-wait.ts +81 -0
  387. package/gui/server/shutdown.ts +47 -0
  388. package/gui/server/tool-invoke-ack.ts +49 -0
  389. package/gui/server/tool-metadata.ts +23 -10
  390. package/gui/server/websocket.ts +13 -3
  391. package/gui/server/writer-lock.ts +6 -2
  392. package/gui/server/ws-hub.ts +292 -0
  393. package/gui/shared/chat-events.ts +16 -1
  394. package/gui/shared/event-reducer.ts +24 -6
  395. package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +1 -0
  396. package/gui/web/dist/assets/index-2KZtKBmu.css +1 -0
  397. package/gui/web/dist/assets/index-CveNgtDg.js +69 -0
  398. package/gui/web/dist/index.html +2 -2
  399. package/package.json +22 -12
  400. package/src/analysts/contracts.ts +10 -23
  401. package/src/analysts/orchestrator.ts +8 -43
  402. package/src/cli.ts +37 -13
  403. package/src/config.ts +99 -7
  404. package/src/index.ts +1 -1
  405. package/src/infra/browser.ts +4 -2
  406. package/src/infra/cache.ts +41 -30
  407. package/src/infra/http-client.ts +72 -6
  408. package/src/infra/index.ts +7 -10
  409. package/src/infra/native-dependencies.ts +8 -3
  410. package/src/infra/node-version.ts +3 -1
  411. package/src/infra/opencandle-paths.ts +3 -14
  412. package/src/infra/rate-limiter.ts +32 -20
  413. package/src/market-state/alert-conditions.ts +82 -0
  414. package/src/market-state/alert-runner.ts +863 -0
  415. package/src/market-state/daily-report.ts +247 -0
  416. package/src/market-state/local-automation-service.ts +162 -0
  417. package/src/market-state/notification-delivery.ts +158 -0
  418. package/src/market-state/resolve-for-mutation.ts +24 -0
  419. package/src/market-state/resolve.ts +112 -0
  420. package/src/market-state/service.ts +2344 -0
  421. package/src/memory/index.ts +7 -7
  422. package/src/memory/manager.ts +57 -26
  423. package/src/memory/retrieval.ts +8 -7
  424. package/src/memory/sqlite.ts +407 -6
  425. package/src/memory/storage.ts +8 -17
  426. package/src/memory/tool-defaults.ts +60 -39
  427. package/src/memory/types.ts +7 -3
  428. package/src/monitor.ts +121 -0
  429. package/src/onboarding/connect.ts +10 -33
  430. package/src/onboarding/credential-interceptor.ts +3 -15
  431. package/src/onboarding/degradation-accumulator.ts +1 -3
  432. package/src/onboarding/providers.ts +9 -40
  433. package/src/onboarding/state.ts +4 -15
  434. package/src/onboarding/tool-helpers.ts +2 -9
  435. package/src/onboarding/tool-tags.ts +6 -6
  436. package/src/onboarding/validation.ts +14 -20
  437. package/src/pi/opencandle-extension.ts +795 -120
  438. package/src/pi/session.ts +7 -5
  439. package/src/pi/setup.ts +61 -33
  440. package/src/pi/tool-adapter.ts +5 -2
  441. package/src/prompts/context-builder.ts +143 -21
  442. package/src/prompts/disclaimer.ts +1 -1
  443. package/src/prompts/policy-cards.ts +220 -0
  444. package/src/prompts/sections.ts +4 -4
  445. package/src/prompts/symbol-preflight.ts +80 -0
  446. package/src/prompts/workflow-prompts.ts +231 -28
  447. package/src/providers/alpha-vantage.ts +82 -40
  448. package/src/providers/coingecko.ts +2 -5
  449. package/src/providers/errors.ts +9 -0
  450. package/src/providers/exa-search.ts +24 -22
  451. package/src/providers/fear-greed.ts +1 -1
  452. package/src/providers/finnhub.ts +7 -6
  453. package/src/providers/fred.ts +3 -3
  454. package/src/providers/index.ts +14 -6
  455. package/src/providers/reddit.ts +17 -6
  456. package/src/providers/sec-edgar.ts +235 -5
  457. package/src/providers/tradingview.ts +399 -0
  458. package/src/providers/twitter.ts +6 -8
  459. package/src/providers/web-search.ts +30 -20
  460. package/src/providers/with-fallback.ts +8 -7
  461. package/src/providers/wrap-provider.ts +15 -10
  462. package/src/providers/yahoo-finance.ts +292 -20
  463. package/src/routing/classify-intent.ts +186 -4
  464. package/src/routing/defaults.ts +4 -4
  465. package/src/routing/entity-extractor.ts +428 -28
  466. package/src/routing/fund-symbols.ts +58 -0
  467. package/src/routing/horizon.ts +7 -0
  468. package/src/routing/index.ts +60 -16
  469. package/src/routing/legacy-rule-router.ts +13 -0
  470. package/src/routing/planning.ts +823 -0
  471. package/src/routing/route-manifest.ts +309 -0
  472. package/src/routing/router-llm-client.ts +4 -4
  473. package/src/routing/router-prompt.ts +52 -52
  474. package/src/routing/router-types.ts +18 -0
  475. package/src/routing/router.ts +717 -20
  476. package/src/routing/slot-resolver.ts +75 -14
  477. package/src/routing/symbol-disambiguator.ts +72 -0
  478. package/src/routing/turn-context.ts +108 -0
  479. package/src/routing/types.ts +15 -1
  480. package/src/runtime/answer-contracts.ts +672 -0
  481. package/src/runtime/artifact-contracts.ts +77 -0
  482. package/src/runtime/planning-evidence.ts +682 -0
  483. package/src/runtime/prompt-step.ts +1 -16
  484. package/src/runtime/run-context.ts +12 -2
  485. package/src/runtime/session-coordinator.ts +297 -56
  486. package/src/runtime/session-title.ts +60 -0
  487. package/src/runtime/tool-defaults-wrapper.ts +1 -3
  488. package/src/runtime/validation.ts +1 -4
  489. package/src/runtime/workflow-events.ts +7 -7
  490. package/src/runtime/workflow-runner.ts +5 -11
  491. package/src/sentiment/adapters/finnhub.ts +7 -2
  492. package/src/sentiment/adapters/reddit.ts +2 -2
  493. package/src/sentiment/adapters/twitter.ts +1 -1
  494. package/src/sentiment/adapters/web.ts +1 -1
  495. package/src/sentiment/index.ts +16 -26
  496. package/src/sentiment/keywords.ts +26 -4
  497. package/src/sentiment/pipeline.ts +15 -4
  498. package/src/sentiment/scorer.ts +1 -1
  499. package/src/sentiment/store.ts +2 -2
  500. package/src/sentiment/trends.ts +9 -3
  501. package/src/sentiment/types.ts +5 -4
  502. package/src/system-prompt.ts +7 -3
  503. package/src/tool-kit.ts +10 -9
  504. package/src/tools/fundamentals/company-overview.ts +20 -10
  505. package/src/tools/fundamentals/comps.ts +69 -56
  506. package/src/tools/fundamentals/dcf.ts +146 -96
  507. package/src/tools/fundamentals/earnings.ts +17 -7
  508. package/src/tools/fundamentals/financials.ts +17 -8
  509. package/src/tools/fundamentals/sec-filings.ts +52 -8
  510. package/src/tools/index.ts +53 -38
  511. package/src/tools/interaction/ask-user.ts +22 -10
  512. package/src/tools/interaction/twitter-login.ts +17 -5
  513. package/src/tools/macro/fear-greed.ts +2 -2
  514. package/src/tools/macro/fred-data.ts +80 -42
  515. package/src/tools/market/crypto-history.ts +25 -4
  516. package/src/tools/market/crypto-price.ts +7 -7
  517. package/src/tools/market/screen-stocks.ts +279 -0
  518. package/src/tools/market/search-ticker.ts +219 -18
  519. package/src/tools/market/stock-history.ts +38 -13
  520. package/src/tools/market/stock-quote.ts +11 -8
  521. package/src/tools/options/greeks.ts +5 -6
  522. package/src/tools/options/option-chain.ts +47 -18
  523. package/src/tools/portfolio/alerts.ts +457 -0
  524. package/src/tools/portfolio/correlation.ts +48 -21
  525. package/src/tools/portfolio/daily-report.ts +101 -0
  526. package/src/tools/portfolio/holdings-overlap.ts +139 -0
  527. package/src/tools/portfolio/notifications.ts +45 -0
  528. package/src/tools/portfolio/predictions.ts +407 -107
  529. package/src/tools/portfolio/risk-analysis.ts +47 -8
  530. package/src/tools/portfolio/tracker.ts +271 -110
  531. package/src/tools/portfolio/watchlist.ts +251 -116
  532. package/src/tools/sentiment/reddit-sentiment.ts +51 -25
  533. package/src/tools/sentiment/sentiment-summary.ts +116 -35
  534. package/src/tools/sentiment/sentiment-trend.ts +24 -7
  535. package/src/tools/sentiment/twitter-sentiment.ts +23 -16
  536. package/src/tools/sentiment/untrusted-text.ts +21 -0
  537. package/src/tools/sentiment/web-search.ts +52 -16
  538. package/src/tools/sentiment/web-sentiment.ts +27 -11
  539. package/src/tools/technical/backtest.ts +78 -47
  540. package/src/tools/technical/indicators.ts +40 -17
  541. package/src/types/index.ts +8 -3
  542. package/src/types/market.ts +1 -0
  543. package/src/types/options.ts +17 -0
  544. package/src/types/portfolio.ts +46 -4
  545. package/src/types/sentiment.ts +2 -2
  546. package/src/workflows/compare-assets.ts +67 -19
  547. package/src/workflows/index.ts +3 -4
  548. package/src/workflows/options-screener.ts +98 -22
  549. package/src/workflows/portfolio-builder.ts +40 -29
  550. package/dist/runtime/index.d.ts +0 -16
  551. package/dist/runtime/index.js +0 -10
  552. package/dist/runtime/index.js.map +0 -1
  553. package/dist/runtime/provider-ids.d.ts +0 -14
  554. package/dist/runtime/provider-ids.js +0 -14
  555. package/dist/runtime/provider-ids.js.map +0 -1
  556. package/dist/workflows/types.d.ts +0 -4
  557. package/dist/workflows/types.js +0 -2
  558. package/dist/workflows/types.js.map +0 -1
  559. package/gui/web/dist/assets/CatalogOverlay-D1ImSJTe.js +0 -1
  560. package/gui/web/dist/assets/index-DBrWq43L.css +0 -1
  561. package/gui/web/dist/assets/index-RflHaj0y.js +0 -67
  562. package/src/runtime/index.ts +0 -55
  563. package/src/runtime/provider-ids.ts +0 -15
  564. package/src/workflows/types.ts +0 -4
@@ -1,5 +1,8 @@
1
- import { extractEntities } from "./entity-extractor.js";
1
+ import { extractEntities, isAmbiguousConceptUsage, isCurrencyCodeUsage, } from "./entity-extractor.js";
2
+ import { classifyWithLegacyRules } from "./legacy-rule-router.js";
3
+ import { computeMissingRequiredSlots, isDispatchableWorkflow, isRouteKind, isToolBundleName, legacyRouteForRouteKind, routeKindFromLegacyRoute, selectToolBundles, } from "./route-manifest.js";
2
4
  import { buildRouterPrompt } from "./router-prompt.js";
5
+ import { disambiguateSymbols } from "./symbol-disambiguator.js";
3
6
  const VALID_ROUTES = ["workflow", "fallback"];
4
7
  const VALID_WORKFLOWS = [
5
8
  "portfolio_builder",
@@ -9,7 +12,7 @@ const VALID_WORKFLOWS = [
9
12
  "watchlist_or_tracking",
10
13
  "general_finance_qa",
11
14
  ];
12
- const VALID_SOURCES = new Set(["user", "preference", "default"]);
15
+ const VALID_SOURCES = new Set(["user", "preference", "default", "prior_context", "memory"]);
13
16
  const VALID_CONFIDENCE = new Set(["high", "medium", "low"]);
14
17
  /**
15
18
  * Run the LLM router against the given input context. Retries once on
@@ -23,7 +26,7 @@ export async function route(input, client) {
23
26
  let firstError;
24
27
  try {
25
28
  const raw = await client.complete(prompt);
26
- return validateRouterOutput(raw);
29
+ return postProcessRouterOutput(input.text, validateRouterOutput(raw));
27
30
  }
28
31
  catch (err) {
29
32
  firstError = err instanceof Error ? err.message : String(err);
@@ -32,11 +35,11 @@ export async function route(input, client) {
32
35
  try {
33
36
  const retryPrompt = `${prompt}\n\n(Your previous response failed validation: ${firstError}. Return a valid JSON object conforming to RouterOutput. Nothing else.)`;
34
37
  const raw = await client.complete(retryPrompt);
35
- return validateRouterOutput(raw);
38
+ return postProcessRouterOutput(input.text, validateRouterOutput(raw));
36
39
  }
37
40
  catch {
38
41
  // Persistent failure — return a minimal fallback with regex-extracted symbols.
39
- return minimalFallback(input.text);
42
+ return postProcessRouterOutput(input.text, minimalFallback(input.text));
40
43
  }
41
44
  }
42
45
  export function validateRouterOutput(raw) {
@@ -45,29 +48,58 @@ export function validateRouterOutput(raw) {
45
48
  throw new Error("router output was not a JSON object");
46
49
  }
47
50
  const obj = parsed;
48
- const route = obj.route;
49
- if (typeof route !== "string" || !VALID_ROUTES.includes(route)) {
50
- throw new Error(`invalid route: ${JSON.stringify(route)}`);
51
+ const rawMissingRequired = validateStringArray(obj.missing_required, "missing_required");
52
+ const explicitRouteKind = obj.routeKind;
53
+ if (explicitRouteKind !== undefined &&
54
+ (typeof explicitRouteKind !== "string" || !isRouteKind(explicitRouteKind))) {
55
+ throw new Error(`invalid routeKind: ${JSON.stringify(explicitRouteKind)}`);
56
+ }
57
+ const rawRoute = obj.route;
58
+ let route;
59
+ if (typeof rawRoute === "string") {
60
+ if (!VALID_ROUTES.includes(rawRoute)) {
61
+ throw new Error(`invalid route: ${JSON.stringify(rawRoute)}`);
62
+ }
63
+ route = rawRoute;
64
+ }
65
+ else if (typeof explicitRouteKind === "string" && isRouteKind(explicitRouteKind)) {
66
+ route = legacyRouteForRouteKind(explicitRouteKind);
67
+ }
68
+ else {
69
+ throw new Error(`invalid route: ${JSON.stringify(rawRoute)}`);
51
70
  }
52
71
  let workflow;
53
- if (route === "workflow") {
54
- if (typeof obj.workflow !== "string" || !VALID_WORKFLOWS.includes(obj.workflow)) {
72
+ const routeKind = typeof explicitRouteKind === "string" && isRouteKind(explicitRouteKind)
73
+ ? explicitRouteKind
74
+ : routeKindFromLegacyRoute(route, rawMissingRequired);
75
+ if (route === "workflow" || routeKind === "workflow_dispatch") {
76
+ if (typeof obj.workflow !== "string" ||
77
+ !VALID_WORKFLOWS.includes(obj.workflow)) {
55
78
  throw new Error(`workflow route requires a valid workflow; got ${JSON.stringify(obj.workflow)}`);
56
79
  }
57
80
  workflow = obj.workflow;
58
81
  }
82
+ else if (typeof obj.workflow === "string" &&
83
+ VALID_WORKFLOWS.includes(obj.workflow)) {
84
+ workflow = obj.workflow;
85
+ }
59
86
  const entities = validateEntities(obj.entities);
60
87
  const slots = validateSlots(obj.slots);
61
88
  const preference_updates = validatePreferenceUpdates(obj.preference_updates);
62
- const missing_required = validateStringArray(obj.missing_required, "missing_required");
89
+ const missing_required = rawMissingRequired;
90
+ const tool_bundles = validateToolBundles(obj.tool_bundles);
91
+ const diagnostics = validateDiagnostics(obj.diagnostics);
63
92
  const reasoning = typeof obj.reasoning === "string" ? obj.reasoning : "";
64
93
  return {
65
- route: route,
94
+ routeKind,
95
+ route: legacyRouteForRouteKind(routeKind),
66
96
  workflow,
67
97
  entities,
68
98
  slots,
69
99
  preference_updates,
70
100
  missing_required,
101
+ tool_bundles,
102
+ diagnostics,
71
103
  reasoning,
72
104
  };
73
105
  }
@@ -97,6 +129,10 @@ function validateEntities(raw) {
97
129
  out.budget = e.budget;
98
130
  if (typeof e.maxPremium === "number")
99
131
  out.maxPremium = e.maxPremium;
132
+ if (typeof e.costBasis === "number")
133
+ out.costBasis = e.costBasis;
134
+ if (typeof e.shareQuantity === "number")
135
+ out.shareQuantity = e.shareQuantity;
100
136
  if (typeof e.timeHorizon === "string")
101
137
  out.timeHorizon = e.timeHorizon;
102
138
  if (typeof e.riskProfile === "string")
@@ -105,6 +141,463 @@ function validateEntities(raw) {
105
141
  out.direction = e.direction;
106
142
  if (typeof e.dteHint === "string")
107
143
  out.dteHint = e.dteHint;
144
+ if (e.optionStrategy === "covered_call" || e.optionStrategy === "protective_put")
145
+ out.optionStrategy = e.optionStrategy;
146
+ if (typeof e.heldSymbol === "string")
147
+ out.heldSymbol = e.heldSymbol.toUpperCase();
148
+ const catalystSymbols = validateStringArray(e.catalystSymbols, "entities.catalystSymbols").map((s) => s.toUpperCase());
149
+ if (catalystSymbols.length > 0)
150
+ out.catalystSymbols = catalystSymbols;
151
+ const compareMetrics = validateStringArray(e.compareMetrics, "entities.compareMetrics");
152
+ if (compareMetrics.length > 0)
153
+ out.compareMetrics = compareMetrics;
154
+ return out;
155
+ }
156
+ export function postProcessRouterOutput(text, output) {
157
+ const extracted = extractEntities(text);
158
+ const deterministic = classifyWithLegacyRules(text);
159
+ let diagnostics = [...output.diagnostics];
160
+ let next = {
161
+ ...output,
162
+ entities: {
163
+ ...output.entities,
164
+ symbols: output.entities.symbols.filter((symbol) => !isAmbiguousConceptUsage(text, symbol)),
165
+ budget: output.entities.budget ?? extracted.budget,
166
+ maxPremium: output.entities.maxPremium ?? extracted.maxPremium,
167
+ timeHorizon: output.entities.timeHorizon ?? extracted.timeHorizon,
168
+ riskProfile: output.entities.riskProfile ?? extracted.riskProfile,
169
+ assetScope: output.entities.assetScope ?? extracted.assetScope,
170
+ compareMetrics: mergeStringArrays(output.entities.compareMetrics, extracted.compareMetrics),
171
+ direction: output.entities.direction ?? extracted.direction,
172
+ optionStrategy: output.entities.optionStrategy ?? extracted.optionStrategy,
173
+ costBasis: output.entities.costBasis ?? extracted.costBasis,
174
+ shareQuantity: output.entities.shareQuantity ?? extracted.shareQuantity,
175
+ heldSymbol: output.entities.heldSymbol ?? extracted.heldSymbol,
176
+ catalystSymbols: output.entities.catalystSymbols ?? extracted.catalystSymbols,
177
+ dteHint: output.entities.dteHint ??
178
+ (output.workflow === "options_screener" ? extracted.dteHint : undefined),
179
+ },
180
+ diagnostics,
181
+ };
182
+ if (next.workflow === "options_screener" &&
183
+ isExistingPositionOptionRequest(text, extracted) &&
184
+ extracted.heldSymbol) {
185
+ const reorderedSymbols = [
186
+ extracted.heldSymbol,
187
+ ...mergeSymbols(next.entities.symbols, extracted.symbols).filter((symbol) => symbol !== extracted.heldSymbol),
188
+ ];
189
+ if (next.entities.symbols[0] !== extracted.heldSymbol) {
190
+ diagnostics.push({
191
+ code: extracted.optionStrategy === "protective_put"
192
+ ? "existing_position_underlying_corrected"
193
+ : "covered_call_underlying_corrected",
194
+ message: `using owned position ${extracted.heldSymbol} as the option-chain underlying`,
195
+ });
196
+ }
197
+ next = {
198
+ ...next,
199
+ entities: {
200
+ ...next.entities,
201
+ symbols: reorderedSymbols,
202
+ optionStrategy: extracted.optionStrategy ?? next.entities.optionStrategy,
203
+ direction: extracted.direction ?? next.entities.direction,
204
+ heldSymbol: extracted.heldSymbol,
205
+ catalystSymbols: reorderedSymbols.filter((symbol) => symbol !== extracted.heldSymbol),
206
+ costBasis: extracted.costBasis ?? next.entities.costBasis,
207
+ shareQuantity: extracted.shareQuantity ?? next.entities.shareQuantity,
208
+ dteHint: extracted.dteHint ?? next.entities.dteHint,
209
+ },
210
+ diagnostics,
211
+ };
212
+ }
213
+ if (next.workflow === "options_screener" &&
214
+ isOptionsEducationOrSuitabilityRequest(text) &&
215
+ !isSpecificOptionContractSelectionRequest(text)) {
216
+ diagnostics.push({
217
+ code: "options_workflow_corrected_to_policy_task",
218
+ message: "options education or suitability prompt should use policy-card synthesis, not contract-screen workflow dispatch",
219
+ });
220
+ next = {
221
+ ...next,
222
+ routeKind: "agent_task",
223
+ route: "fallback",
224
+ workflow: "general_finance_qa",
225
+ missing_required: [],
226
+ diagnostics,
227
+ };
228
+ }
229
+ // Legacy rules may recover a primary route only when the LLM router path has
230
+ // already failed validation. Otherwise they are limited to enrichment and
231
+ // narrow corrections below.
232
+ if (next.diagnostics.some((d) => d.code === "router_validation_failed") &&
233
+ deterministic.workflow !== "unclassified") {
234
+ next = {
235
+ ...next,
236
+ routeKind: isDispatchableWorkflow(deterministic.workflow)
237
+ ? "workflow_dispatch"
238
+ : "agent_task",
239
+ route: isDispatchableWorkflow(deterministic.workflow) ? "workflow" : "fallback",
240
+ workflow: deterministic.workflow,
241
+ entities: {
242
+ ...deterministic.entities,
243
+ budget: deterministic.entities.budget ?? extracted.budget,
244
+ maxPremium: deterministic.entities.maxPremium ?? extracted.maxPremium,
245
+ timeHorizon: deterministic.entities.timeHorizon ?? extracted.timeHorizon,
246
+ riskProfile: deterministic.entities.riskProfile ?? extracted.riskProfile,
247
+ assetScope: deterministic.entities.assetScope ?? extracted.assetScope,
248
+ compareMetrics: mergeStringArrays(deterministic.entities.compareMetrics, extracted.compareMetrics),
249
+ direction: deterministic.entities.direction ?? extracted.direction,
250
+ costBasis: deterministic.entities.costBasis ?? extracted.costBasis,
251
+ shareQuantity: deterministic.entities.shareQuantity ?? extracted.shareQuantity,
252
+ heldSymbol: deterministic.entities.heldSymbol ?? extracted.heldSymbol,
253
+ catalystSymbols: deterministic.entities.catalystSymbols ?? extracted.catalystSymbols,
254
+ },
255
+ diagnostics: [
256
+ ...diagnostics,
257
+ {
258
+ code: "deterministic_failure_recovery",
259
+ message: `deterministic classifier selected ${deterministic.workflow} after router validation failure`,
260
+ },
261
+ ],
262
+ reasoning: next.reasoning
263
+ ? `${next.reasoning}; deterministic classifier selected ${deterministic.workflow}`
264
+ : `deterministic classifier selected ${deterministic.workflow}`,
265
+ };
266
+ diagnostics = next.diagnostics;
267
+ }
268
+ if (next.routeKind === "workflow_dispatch" && !isDispatchableWorkflow(next.workflow)) {
269
+ diagnostics.push({
270
+ code: "route_kind_corrected_to_agent_task",
271
+ message: next.workflow
272
+ ? `${next.workflow} is not a dispatchable workflow`
273
+ : "workflow_dispatch requires a dispatchable workflow",
274
+ });
275
+ next = {
276
+ ...next,
277
+ routeKind: "agent_task",
278
+ route: "fallback",
279
+ diagnostics,
280
+ };
281
+ }
282
+ if ((next.workflow === "compare_assets" || next.workflow === "portfolio_builder") &&
283
+ isStatefulTrackingRequest(text)) {
284
+ diagnostics.push({
285
+ code: "stateful_tracking_corrected_to_agent_task",
286
+ message: "portfolio/watchlist tracking mutation should use stateful tracking tools, not compare or construction workflow dispatch",
287
+ });
288
+ next = {
289
+ ...next,
290
+ routeKind: "agent_task",
291
+ route: "fallback",
292
+ workflow: "watchlist_or_tracking",
293
+ missing_required: [],
294
+ entities: {
295
+ ...next.entities,
296
+ symbols: filterCurrencyUnitSymbols(text, next.entities.symbols),
297
+ },
298
+ slots: removeCurrencyUnitSymbolSlots(text, next.slots),
299
+ diagnostics,
300
+ };
301
+ }
302
+ if (next.routeKind === "agent_task" && isDispatchableWorkflow(next.workflow)) {
303
+ diagnostics.push({
304
+ code: "dispatchable_workflow_corrected_to_workflow_dispatch",
305
+ message: `${next.workflow} is a dispatchable workflow`,
306
+ });
307
+ next = {
308
+ ...next,
309
+ routeKind: "workflow_dispatch",
310
+ route: "workflow",
311
+ diagnostics,
312
+ };
313
+ }
314
+ if (next.workflow === "compare_assets" &&
315
+ next.entities.symbols.length === 0 &&
316
+ isExplicitMacroDataRequest(text)) {
317
+ diagnostics.push({
318
+ code: "compare_route_corrected_to_macro_task",
319
+ message: "macro/source acronyms were not explicit tickers",
320
+ });
321
+ next = {
322
+ ...next,
323
+ routeKind: "agent_task",
324
+ route: "fallback",
325
+ workflow: "general_finance_qa",
326
+ missing_required: [],
327
+ diagnostics,
328
+ };
329
+ }
330
+ if (next.workflow === "compare_assets" && isPortfolioEvaluationRequest(text)) {
331
+ diagnostics.push({
332
+ code: "portfolio_evaluation_corrected_to_agent_task",
333
+ message: "existing portfolio/allocation risk review should not be reduced to asset comparison",
334
+ });
335
+ next = {
336
+ ...next,
337
+ routeKind: "agent_task",
338
+ route: "fallback",
339
+ workflow: "general_finance_qa",
340
+ missing_required: [],
341
+ diagnostics,
342
+ };
343
+ }
344
+ if (next.routeKind === "agent_task" &&
345
+ !next.workflow &&
346
+ next.entities.symbols.length === 0 &&
347
+ isExplicitMacroDataRequest(text)) {
348
+ diagnostics.push({
349
+ code: "macro_task_inferred_from_prompt",
350
+ message: "macro data terms were present without explicit tickers",
351
+ });
352
+ next = {
353
+ ...next,
354
+ workflow: "general_finance_qa",
355
+ diagnostics,
356
+ };
357
+ }
358
+ if (next.workflow === "portfolio_builder" && isCryptoSizingRequest(text)) {
359
+ diagnostics.push({
360
+ code: "crypto_sizing_corrected_to_agent_task",
361
+ message: "crypto allocation-range and drawdown questions are advisory tradeoffs, not portfolio construction",
362
+ });
363
+ next = {
364
+ ...next,
365
+ routeKind: "agent_task",
366
+ route: "fallback",
367
+ workflow: "general_finance_qa",
368
+ missing_required: [],
369
+ diagnostics,
370
+ };
371
+ }
372
+ if (next.workflow === "portfolio_builder" && isPortfolioEvaluationRequest(text)) {
373
+ diagnostics.push({
374
+ code: "portfolio_evaluation_corrected_to_agent_task",
375
+ message: "existing portfolio/allocation evaluation does not require portfolio-construction budget",
376
+ });
377
+ next = {
378
+ ...next,
379
+ routeKind: "agent_task",
380
+ route: "fallback",
381
+ workflow: "general_finance_qa",
382
+ missing_required: [],
383
+ diagnostics,
384
+ };
385
+ }
386
+ if (next.workflow === "portfolio_builder" &&
387
+ next.entities.symbols.length >= 2 &&
388
+ isPortfolioTradeoffComparisonRequest(text)) {
389
+ diagnostics.push({
390
+ code: "portfolio_tradeoff_corrected_to_compare_assets",
391
+ message: "explicit multi-asset tradeoff question should compare the requested assets before constructing a portfolio",
392
+ });
393
+ next = {
394
+ ...next,
395
+ routeKind: "workflow_dispatch",
396
+ route: "workflow",
397
+ workflow: "compare_assets",
398
+ missing_required: [],
399
+ diagnostics,
400
+ };
401
+ }
402
+ if (next.workflow === "single_asset_analysis" && isSpecializedSingleAssetPolicyRequest(text)) {
403
+ diagnostics.push({
404
+ code: "single_asset_workflow_corrected_to_general_policy_task",
405
+ message: "prompt asks for policy-card planning outside a single-asset buy/sell analysis",
406
+ });
407
+ next = {
408
+ ...next,
409
+ workflow: "general_finance_qa",
410
+ diagnostics,
411
+ };
412
+ }
413
+ const disambiguated = disambiguateSymbols(next.entities.symbols, text);
414
+ if (disambiguated.dropped.length > 0) {
415
+ for (const drop of disambiguated.dropped) {
416
+ diagnostics.push({
417
+ code: "symbol_dropped",
418
+ message: `${drop.token} dropped: ${drop.reason}`,
419
+ details: {
420
+ token: drop.token,
421
+ reason: drop.reason,
422
+ signalsChecked: drop.signalsChecked,
423
+ source: "llm",
424
+ },
425
+ });
426
+ }
427
+ next = {
428
+ ...next,
429
+ entities: {
430
+ ...next.entities,
431
+ symbols: disambiguated.kept,
432
+ },
433
+ slots: removeDroppedSymbolSlots(next.slots, disambiguated.dropped.map((drop) => drop.token)),
434
+ diagnostics,
435
+ };
436
+ }
437
+ const missingRequired = computeMissingRequiredSlots(next.workflow, next.entities, next.slots, next.missing_required);
438
+ if (missingRequired.length > 0 && next.routeKind !== "pass_through") {
439
+ if (next.routeKind !== "clarification") {
440
+ diagnostics.push({
441
+ code: "route_kind_corrected_to_clarification",
442
+ message: `missing required slots: ${missingRequired.join(", ")}`,
443
+ });
444
+ }
445
+ next = {
446
+ ...next,
447
+ routeKind: "clarification",
448
+ route: "fallback",
449
+ missing_required: missingRequired,
450
+ diagnostics,
451
+ };
452
+ }
453
+ const selectedToolBundles = isConceptualEducationRequest(text, next)
454
+ ? []
455
+ : selectToolBundles(next);
456
+ if (selectedToolBundles.length === 0 && isConceptualEducationRequest(text, next)) {
457
+ diagnostics.push({
458
+ code: "conceptual_education_no_tools",
459
+ message: "conceptual education prompt does not need live finance tools",
460
+ });
461
+ }
462
+ const emittedUnsupported = next.tool_bundles.filter((bundle) => !selectedToolBundles.includes(bundle));
463
+ if (emittedUnsupported.length > 0) {
464
+ diagnostics.push({
465
+ code: "tool_bundles_corrected",
466
+ message: `unsupported emitted bundles dropped: ${emittedUnsupported.join(", ")}`,
467
+ });
468
+ }
469
+ return omitUndefined({
470
+ ...next,
471
+ route: legacyRouteForRouteKind(next.routeKind),
472
+ tool_bundles: selectedToolBundles,
473
+ diagnostics,
474
+ });
475
+ }
476
+ function isExplicitMacroDataRequest(text) {
477
+ return /\b(?:get_economic_data|fred|cpi|inflation|fed\s+funds?|unemployment|gdp|macro)\b/i.test(text);
478
+ }
479
+ function isConceptualEducationRequest(text, output) {
480
+ if (output.routeKind !== "agent_task")
481
+ return false;
482
+ if (output.entities.symbols.length > 0)
483
+ return false;
484
+ if (isForwardLookingMacroContextRequest(text))
485
+ return false;
486
+ if (/\b(?:current|recent|today|right now|latest|news|sentiment|build|portfolio|buy|sell|allocate|compare)\b/i.test(text)) {
487
+ return false;
488
+ }
489
+ return /\b(?:explain|what is|define|how (?:do|should|to)|teach me|help me understand)\b/i.test(text);
490
+ }
491
+ function isForwardLookingMacroContextRequest(text) {
492
+ return (/\b(?:rates?|rate\s*cuts?|fed|inflation|macro)\b/i.test(text) &&
493
+ /\b(?:next\s+(?:year|12\s*months?)|over\s+the\s+next|outlook|affect|impact|falling|rising)\b/i.test(text));
494
+ }
495
+ function isCoveredCallRequest(text) {
496
+ return /\bcovered\s+calls?\b/i.test(text);
497
+ }
498
+ function isPortfolioEvaluationRequest(text) {
499
+ const lower = text.toLowerCase();
500
+ const hasEvaluationIntent = /\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);
501
+ const hasPortfolioObject = /\b(?:portfolio|allocation|asset\s+allocation|60\/40|equity|fixed\s+income|bonds?)\b/.test(lower);
502
+ const hasConstructionIntent = /\b(?:build|create|construct|put\s+together|invest|allocate)\b/.test(lower) &&
503
+ /\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower);
504
+ return hasEvaluationIntent && hasPortfolioObject && !hasConstructionIntent;
505
+ }
506
+ function isStatefulTrackingRequest(text) {
507
+ const lower = text.toLowerCase();
508
+ const hasPortfolioConstructionIntent = /\b(?:build|create|construct|put\s+together)\b/.test(lower) &&
509
+ /\bportfolio\b/.test(lower) &&
510
+ /\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower);
511
+ const hasStateVerb = /\b(?:add|remove|update|record|track|create|configure|check|show|list|view|cancel)\b/.test(lower);
512
+ const hasStateObject = /\b(?:watchlist|portfolio|holding|holdings|position|positions|prediction|predictions|alert|alerts|daily\s+report|watchlist\s+report|report\s+history)\b/.test(lower);
513
+ const hasPortfolioLotShape = /\b(?:add|record|track)\b/.test(lower) &&
514
+ /\b\d+(?:\.\d+)?\s+shares?\b/.test(lower) &&
515
+ /\b(?:portfolio|holding|holdings|position|positions)\b/.test(lower);
516
+ if (hasPortfolioConstructionIntent)
517
+ return false;
518
+ return (hasStateVerb && hasStateObject) || hasPortfolioLotShape;
519
+ }
520
+ function filterCurrencyUnitSymbols(text, symbols) {
521
+ return symbols.filter((symbol) => !isCurrencyCodeUsage(text, symbol));
522
+ }
523
+ function removeCurrencyUnitSymbolSlots(text, slots) {
524
+ const next = { ...slots };
525
+ for (const key of ["symbol", "symbols"]) {
526
+ const slot = next[key];
527
+ if (!slot)
528
+ continue;
529
+ if (Array.isArray(slot.value)) {
530
+ const value = slot.value.filter((item) => typeof item !== "string" || !isCurrencyCodeUsage(text, item.toUpperCase()));
531
+ if (value.length === 0)
532
+ delete next[key];
533
+ else
534
+ next[key] = { ...slot, value };
535
+ continue;
536
+ }
537
+ if (typeof slot.value === "string" && isCurrencyCodeUsage(text, slot.value.toUpperCase())) {
538
+ delete next[key];
539
+ }
540
+ }
541
+ return next;
542
+ }
543
+ function isPortfolioTradeoffComparisonRequest(text) {
544
+ const lower = text.toLowerCase();
545
+ return (/\b(?:prioritize|tradeoffs?|growth[-\s]?oriented|dividend|income|which\s+(?:one|is)\s+better|should\s+i)\b/.test(lower) && /\b(?:or|vs\.?|versus|compare)\b/.test(lower));
546
+ }
547
+ function isCryptoSizingRequest(text) {
548
+ const lower = text.toLowerCase();
549
+ const hasPortfolioConstructionIntent = /\b(?:build|create|construct|put\s+together)\b/.test(lower) &&
550
+ /\b(?:portfolio|allocation)\b/.test(lower);
551
+ if (hasPortfolioConstructionIntent)
552
+ return false;
553
+ return (/\b(?:btc|bitcoin|crypto)\b/.test(lower) &&
554
+ /\b(?:allocation|range|position\s+size|sizing|exposure|drawdown)\b/.test(lower));
555
+ }
556
+ function isSpecializedSingleAssetPolicyRequest(text) {
557
+ const lower = text.toLowerCase();
558
+ return (/\b(?:ticker|symbol|formerly|old ticker|earnings are|earnings tonight)\b/.test(lower) ||
559
+ /\b(?:today|right now|this morning|after close|moved|catalyst)\b/.test(lower) ||
560
+ /\b(?:sentiment|mood|reddit|twitter|x\/twitter)\b/.test(lower) ||
561
+ /\b(?:filing|10-k|10-q|8-k|sec)\b/.test(lower));
562
+ }
563
+ function isExistingPositionOptionRequest(text, extracted) {
564
+ return isCoveredCallRequest(text) || extracted.optionStrategy === "protective_put";
565
+ }
566
+ function isOptionsEducationOrSuitabilityRequest(text) {
567
+ const lower = text.toLowerCase();
568
+ 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) &&
569
+ /\b(?:covered\s+calls?|protective\s+puts?|options?|selling\s+calls?|option\s+income)\b/.test(lower));
570
+ }
571
+ function isSpecificOptionContractSelectionRequest(text) {
572
+ const lower = text.toLowerCase();
573
+ 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) && /\b(?:sell|buy|trade|contract|strike|expiration|premium|call|put)\b/.test(lower));
574
+ }
575
+ function mergeSymbols(primary, secondary) {
576
+ const merged = [];
577
+ for (const symbol of [...primary, ...secondary]) {
578
+ if (!merged.includes(symbol))
579
+ merged.push(symbol);
580
+ }
581
+ return merged;
582
+ }
583
+ function mergeStringArrays(primary, secondary) {
584
+ const merged = [];
585
+ for (const value of [...(primary ?? []), ...(secondary ?? [])]) {
586
+ if (!merged.includes(value))
587
+ merged.push(value);
588
+ }
589
+ return merged.length > 0 ? merged : undefined;
590
+ }
591
+ function omitUndefined(value) {
592
+ if (Array.isArray(value))
593
+ return value.map(omitUndefined);
594
+ if (!value || typeof value !== "object")
595
+ return value;
596
+ const out = {};
597
+ for (const [key, entry] of Object.entries(value)) {
598
+ if (entry !== undefined)
599
+ out[key] = omitUndefined(entry);
600
+ }
108
601
  return out;
109
602
  }
110
603
  function validateSlots(raw) {
@@ -167,6 +660,64 @@ function validatePreferenceUpdates(raw) {
167
660
  };
168
661
  });
169
662
  }
663
+ function validateToolBundles(raw) {
664
+ const bundles = validateStringArray(raw, "tool_bundles");
665
+ return bundles.filter(isToolBundleName);
666
+ }
667
+ function removeDroppedSymbolSlots(slots, droppedTokens) {
668
+ if (droppedTokens.length === 0)
669
+ return slots;
670
+ const dropped = new Set(droppedTokens.map((token) => token.toUpperCase()));
671
+ const next = { ...slots };
672
+ for (const key of ["symbol", "symbols"]) {
673
+ const slot = next[key];
674
+ if (!slot)
675
+ continue;
676
+ if (Array.isArray(slot.value)) {
677
+ const value = slot.value.filter((item) => typeof item !== "string" || !dropped.has(item.toUpperCase()));
678
+ if (value.length === 0) {
679
+ delete next[key];
680
+ }
681
+ else {
682
+ next[key] = { ...slot, value };
683
+ }
684
+ continue;
685
+ }
686
+ if (typeof slot.value === "string" && dropped.has(slot.value.toUpperCase())) {
687
+ delete next[key];
688
+ }
689
+ }
690
+ return next;
691
+ }
692
+ function validateDiagnostics(raw) {
693
+ if (raw === undefined || raw === null)
694
+ return [];
695
+ if (!Array.isArray(raw)) {
696
+ throw new Error("diagnostics must be an array");
697
+ }
698
+ return raw.map((item, idx) => {
699
+ if (!item || typeof item !== "object") {
700
+ throw new Error(`diagnostics[${idx}] must be an object`);
701
+ }
702
+ const diagnostic = item;
703
+ if (typeof diagnostic.code !== "string" || diagnostic.code.length === 0) {
704
+ throw new Error(`diagnostics[${idx}].code must be a non-empty string`);
705
+ }
706
+ if (typeof diagnostic.message !== "string") {
707
+ throw new Error(`diagnostics[${idx}].message must be a string`);
708
+ }
709
+ const out = {
710
+ code: diagnostic.code,
711
+ message: diagnostic.message,
712
+ };
713
+ if (diagnostic.details &&
714
+ typeof diagnostic.details === "object" &&
715
+ !Array.isArray(diagnostic.details)) {
716
+ out.details = diagnostic.details;
717
+ }
718
+ return out;
719
+ });
720
+ }
170
721
  function validateStringArray(raw, field) {
171
722
  if (raw === undefined || raw === null)
172
723
  return [];
@@ -183,11 +734,19 @@ function validateStringArray(raw, field) {
183
734
  function minimalFallback(text) {
184
735
  const entities = extractEntities(text);
185
736
  return {
737
+ routeKind: "agent_task",
186
738
  route: "fallback",
187
- entities: { symbols: entities.symbols },
739
+ entities,
188
740
  slots: {},
189
741
  preference_updates: [],
190
742
  missing_required: [],
743
+ tool_bundles: [],
744
+ diagnostics: [
745
+ {
746
+ code: "router_validation_failed",
747
+ message: "router validation failed persistently; emitted minimal fallback",
748
+ },
749
+ ],
191
750
  reasoning: "router validation failed; emitted minimal fallback",
192
751
  };
193
752
  }