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,50 +1,53 @@
1
- import { Type } from "@sinclair/typebox";
2
1
  import type { AgentTool } from "@earendil-works/pi-agent-core";
3
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
4
- import { getQuote } from "../../providers/yahoo-finance.js";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { isZeroFilledQuote } from "../../market-state/resolve.js";
4
+ import { resolveInstrumentForMutation } from "../../market-state/resolve-for-mutation.js";
5
+ import { MarketStateService, type WatchlistItemRecord } from "../../market-state/service.js";
6
+ import { initDefaultDatabase } from "../../memory/sqlite.js";
7
+ import { getQuotes, type TradingViewQuote } from "../../providers/tradingview.js";
5
8
  import { wrapProvider } from "../../providers/wrap-provider.js";
6
- import { ensureParentDir, getWatchlistPath } from "../../infra/opencandle-paths.js";
7
-
8
- interface WatchlistItem {
9
- symbol: string;
10
- addedAt: string;
11
- targetPrice?: number;
12
- stopPrice?: number;
13
- notes?: string;
14
- }
15
-
16
- function loadWatchlist(): WatchlistItem[] {
17
- const watchlistPath = getWatchlistPath();
18
- if (!existsSync(watchlistPath)) return [];
19
- try {
20
- return JSON.parse(readFileSync(watchlistPath, "utf-8"));
21
- } catch {
22
- return [];
23
- }
24
- }
9
+ import { getQuote } from "../../providers/yahoo-finance.js";
25
10
 
26
- function saveWatchlist(items: WatchlistItem[]): void {
27
- const watchlistPath = getWatchlistPath();
28
- ensureParentDir(watchlistPath);
29
- writeFileSync(watchlistPath, JSON.stringify(items, null, 2));
11
+ interface WatchlistCheck extends WatchlistItemRecord {
12
+ currentPrice: number | null;
13
+ alerts: string[];
14
+ statuses: string[];
15
+ sourceProvider?: "tradingview" | "yahoo";
16
+ dataCaveat?: string;
30
17
  }
31
18
 
32
19
  const params = Type.Object({
33
20
  action: Type.Union(
34
- [Type.Literal("add"), Type.Literal("remove"), Type.Literal("check")],
35
- { description: "One of: 'add', 'remove', or 'check'" },
36
- ),
37
- symbol: Type.Optional(
38
- Type.String({ description: "Ticker symbol (required for add/remove)" }),
21
+ [Type.Literal("add"), Type.Literal("update"), Type.Literal("remove"), Type.Literal("check")],
22
+ { description: "One of: 'add', 'update', 'remove', or 'check'" },
39
23
  ),
24
+ symbol: Type.Optional(Type.String({ description: "Ticker symbol (required for add/remove)" })),
40
25
  target_price: Type.Optional(
41
- Type.Number({ description: "Alert when price rises above this level" }),
26
+ Type.Union([
27
+ Type.Number({ description: "Alert when price rises above this level" }),
28
+ Type.Null({ description: "Clear the existing target price during update" }),
29
+ ]),
42
30
  ),
43
31
  stop_price: Type.Optional(
44
- Type.Number({ description: "Alert when price falls below this level" }),
32
+ Type.Union([
33
+ Type.Number({ description: "Alert when price falls below this level" }),
34
+ Type.Null({ description: "Clear the existing stop price during update" }),
35
+ ]),
45
36
  ),
46
37
  notes: Type.Optional(
47
- Type.String({ description: "Optional notes for why you're watching this" }),
38
+ Type.Union([
39
+ Type.String({ description: "Optional notes for why you're watching this" }),
40
+ Type.Null({ description: "Clear existing notes during update" }),
41
+ ]),
42
+ ),
43
+ thesis: Type.Optional(
44
+ Type.Union([
45
+ Type.String({ description: "Optional thesis for why you're watching this" }),
46
+ Type.Null({ description: "Clear the existing thesis during update" }),
47
+ ]),
48
+ ),
49
+ tags: Type.Optional(
50
+ Type.Array(Type.String(), { description: "Optional tags for organizing the watchlist item" }),
48
51
  ),
49
52
  });
50
53
 
@@ -52,102 +55,234 @@ export const watchlistTool: AgentTool<typeof params> = {
52
55
  name: "manage_watchlist",
53
56
  label: "Watchlist",
54
57
  description:
55
- "Manage your watchlist of stocks and crypto. Add symbols with optional target and stop prices, remove symbols, or check current prices against your alert levels. Data persisted to ~/.opencandle/watchlist.json.",
58
+ "Manage your watchlist of stocks and crypto. Add symbols with optional target and stop prices, remove symbols, or check current prices against your watchlist levels.",
56
59
  parameters: params,
57
- async execute(toolCallId, args) {
58
- const items = loadWatchlist();
60
+ async execute(_toolCallId, args) {
61
+ const db = initDefaultDatabase();
62
+ const service = new MarketStateService(db);
59
63
 
60
- if (args.action === "add") {
61
- if (!args.symbol) {
62
- throw new Error("symbol is required for add action.");
64
+ try {
65
+ if (args.action === "add") {
66
+ if (!args.symbol) {
67
+ throw new Error("symbol is required for add action.");
68
+ }
69
+ const instrument = await resolveInstrumentForMutation(args.symbol);
70
+ if (instrument.status === "needs_selection") {
71
+ return {
72
+ content: [
73
+ {
74
+ type: "text",
75
+ text: `Could not verify ${instrument.query}. Choose one of the returned candidates before adding it to the watchlist.`,
76
+ },
77
+ ],
78
+ details: instrument,
79
+ };
80
+ }
81
+ const item = service.addWatchlistItem({
82
+ instrument: instrument.instrument,
83
+ targetPrice: args.target_price,
84
+ stopPrice: args.stop_price,
85
+ thesis: args.thesis,
86
+ notes: args.notes,
87
+ tags: args.tags,
88
+ });
89
+ const alerts = [];
90
+ if (args.target_price) alerts.push(`target: $${args.target_price}`);
91
+ if (args.stop_price) alerts.push(`stop: $${args.stop_price}`);
92
+ const alertStr = alerts.length > 0 ? ` (${alerts.join(", ")})` : "";
93
+ return {
94
+ content: [{ type: "text", text: `Added ${item.symbol} to watchlist${alertStr}` }],
95
+ details: item,
96
+ };
63
97
  }
64
- const symbol = args.symbol.toUpperCase();
65
- const existing = items.findIndex((i) => i.symbol === symbol);
66
- const item: WatchlistItem = {
67
- symbol,
68
- addedAt: new Date().toISOString(),
69
- ...(args.target_price != null && { targetPrice: args.target_price }),
70
- ...(args.stop_price != null && { stopPrice: args.stop_price }),
71
- ...(args.notes != null && { notes: args.notes }),
72
- };
73
- if (existing >= 0) {
74
- items[existing] = item;
75
- } else {
76
- items.push(item);
98
+
99
+ if (args.action === "remove") {
100
+ if (!args.symbol) {
101
+ throw new Error("symbol is required for remove action.");
102
+ }
103
+ const symbol = args.symbol.toUpperCase();
104
+ if (!service.removeWatchlistItemBySymbol(symbol)) {
105
+ return {
106
+ content: [{ type: "text", text: `${symbol} not found in watchlist` }],
107
+ details: null,
108
+ };
109
+ }
110
+ return {
111
+ content: [{ type: "text", text: `Removed ${symbol} from watchlist` }],
112
+ details: null,
113
+ };
77
114
  }
78
- saveWatchlist(items);
79
- const alerts = [];
80
- if (args.target_price) alerts.push(`target: $${args.target_price}`);
81
- if (args.stop_price) alerts.push(`stop: $${args.stop_price}`);
82
- const alertStr = alerts.length > 0 ? ` (${alerts.join(", ")})` : "";
83
- return {
84
- content: [{ type: "text", text: `Added ${symbol} to watchlist${alertStr}` }],
85
- details: null,
86
- };
87
- }
88
115
 
89
- if (args.action === "remove") {
90
- if (!args.symbol) {
91
- throw new Error("symbol is required for remove action.");
116
+ if (args.action === "update") {
117
+ if (!args.symbol) {
118
+ throw new Error("symbol is required for update action.");
119
+ }
120
+ const symbol = args.symbol.toUpperCase();
121
+ const item = service.updateWatchlistItemBySymbol(symbol, {
122
+ targetPrice: args.target_price,
123
+ stopPrice: args.stop_price,
124
+ notes: args.notes,
125
+ thesis: args.thesis,
126
+ tags: args.tags,
127
+ });
128
+ if (item == null) {
129
+ return {
130
+ content: [{ type: "text", text: `${symbol} not found in watchlist` }],
131
+ details: null,
132
+ };
133
+ }
134
+ return {
135
+ content: [{ type: "text", text: `Updated ${item.symbol} in watchlist` }],
136
+ details: item,
137
+ };
92
138
  }
93
- const symbol = args.symbol.toUpperCase();
94
- const idx = items.findIndex((i) => i.symbol === symbol);
95
- if (idx === -1) {
139
+
140
+ const items = service.listWatchlistItems();
141
+ if (items.length === 0) {
96
142
  return {
97
- content: [{ type: "text", text: `${symbol} not found in watchlist` }],
143
+ content: [{ type: "text", text: "Watchlist is empty. Use add action to add symbols." }],
98
144
  details: null,
99
145
  };
100
146
  }
101
- items.splice(idx, 1);
102
- saveWatchlist(items);
103
- return {
104
- content: [{ type: "text", text: `Removed ${symbol} from watchlist` }],
105
- details: null,
106
- };
107
- }
108
147
 
109
- // Check action
110
- if (items.length === 0) {
148
+ const checks = await checkWatchlistPrices(items);
149
+ const alertItems = checks.filter((c) => c.alerts.length > 0);
150
+ const lines = [
151
+ `**Watchlist** — ${items.length} symbols${alertItems.length > 0 ? ` | ${alertItems.length} ALERT(S)` : ""}`,
152
+ "",
153
+ ];
154
+
155
+ const tradingViewCaveats = Array.from(
156
+ new Set(
157
+ checks
158
+ .filter((c) => c.sourceProvider === "tradingview")
159
+ .map(
160
+ (c) =>
161
+ c.dataCaveat ??
162
+ "TradingView scanner data may be delayed about 15 minutes and comes from an unofficial endpoint.",
163
+ ),
164
+ ),
165
+ );
166
+ if (tradingViewCaveats.length > 0) {
167
+ lines.push(...tradingViewCaveats.map((caveat) => `Data caveat: ${caveat}`), "");
168
+ }
169
+
170
+ for (const c of checks) {
171
+ const alertStr = c.alerts.length > 0 ? ` ** ${c.alerts.join(" | ")} **` : "";
172
+ const statusStr = c.statuses.length > 0 ? ` | ${c.statuses.join(" | ")}` : "";
173
+ const targetStr = c.targetPrice ? ` | Target: $${c.targetPrice}` : "";
174
+ const stopStr = c.stopPrice ? ` | Stop: $${c.stopPrice}` : "";
175
+ const sourceStr = c.sourceProvider
176
+ ? ` | Source: ${c.sourceProvider === "tradingview" ? "TradingView" : "Yahoo"}`
177
+ : "";
178
+ const priceStr =
179
+ typeof c.currentPrice === "number" ? `$${c.currentPrice.toFixed(2)}` : "Unavailable";
180
+ lines.push(
181
+ ` ${c.symbol}: ${priceStr}${targetStr}${stopStr}${sourceStr}${statusStr}${alertStr}`,
182
+ );
183
+ }
184
+
111
185
  return {
112
- content: [{ type: "text", text: "Watchlist is empty. Use add action to add symbols." }],
113
- details: null,
186
+ content: [{ type: "text", text: lines.join("\n") }],
187
+ details: { items: checks },
114
188
  };
189
+ } finally {
190
+ db.close();
115
191
  }
192
+ },
193
+ };
116
194
 
117
- const checks = await Promise.all(
118
- items.map(async (item) => {
119
- const result = await wrapProvider("yahoo", () => getQuote(item.symbol));
120
- if (result.status === "unavailable") {
121
- return { ...item, currentPrice: 0, alerts: [`UNAVAILABLE: ${result.reason}`] };
122
- }
123
- const quote = result.data;
124
- const alerts: string[] = [];
125
- if (item.targetPrice && quote.price >= item.targetPrice) {
126
- alerts.push(`TARGET HIT: $${quote.price.toFixed(2)} >= $${item.targetPrice}`);
127
- }
128
- if (item.stopPrice && quote.price <= item.stopPrice) {
129
- alerts.push(`STOP ALERT: $${quote.price.toFixed(2)} fell below $${item.stopPrice}`);
130
- }
131
- return { ...item, currentPrice: quote.price, alerts };
132
- }),
133
- );
134
-
135
- const alertItems = checks.filter((c) => c.alerts.length > 0);
136
- const lines = [
137
- `**Watchlist** — ${items.length} symbols${alertItems.length > 0 ? ` | ${alertItems.length} ALERT(S)` : ""}`,
138
- "",
139
- ];
140
-
141
- for (const c of checks) {
142
- const alertStr = c.alerts.length > 0 ? ` ** ${c.alerts.join(" | ")} **` : "";
143
- const targetStr = c.targetPrice ? ` | Target: $${c.targetPrice}` : "";
144
- const stopStr = c.stopPrice ? ` | Stop: $${c.stopPrice}` : "";
145
- lines.push(` ${c.symbol}: $${c.currentPrice.toFixed(2)}${targetStr}${stopStr}${alertStr}`);
195
+ async function checkWatchlistPrices(items: WatchlistItemRecord[]): Promise<WatchlistCheck[]> {
196
+ const tradingViewSymbols = items
197
+ .map((item) => item.symbol)
198
+ .filter((symbol) => !shouldSkipTradingView(symbol));
199
+ const tradingViewQuotes = new Map<string, TradingViewQuote>();
200
+
201
+ if (tradingViewSymbols.length > 0) {
202
+ const result = await wrapProvider("tradingview", () => getQuotes(tradingViewSymbols));
203
+ if (result.status === "ok" && result.data.length > 0) {
204
+ for (const quote of result.data) {
205
+ tradingViewQuotes.set(quote.requestedSymbol.toUpperCase(), {
206
+ ...quote,
207
+ dataCaveat: result.stale
208
+ ? `cached TradingView data from ${result.timestamp}; ${quote.dataCaveat}`
209
+ : quote.dataCaveat,
210
+ });
211
+ }
146
212
  }
213
+ }
147
214
 
148
- return {
149
- content: [{ type: "text", text: lines.join("\n") }],
150
- details: { items: checks },
151
- };
152
- },
153
- };
215
+ return Promise.all(
216
+ items.map(async (item) => {
217
+ const tradingViewQuote = tradingViewQuotes.get(item.symbol.toUpperCase());
218
+ if (tradingViewQuote) {
219
+ return buildCheckResult(
220
+ item,
221
+ tradingViewQuote.price,
222
+ "tradingview",
223
+ tradingViewQuote.dataCaveat,
224
+ );
225
+ }
226
+
227
+ const result = await wrapProvider("yahoo", () => getQuote(item.symbol));
228
+ if (result.status === "unavailable") {
229
+ return {
230
+ ...item,
231
+ currentPrice: null,
232
+ alerts: [`UNAVAILABLE: ${result.reason}`],
233
+ statuses: [],
234
+ };
235
+ }
236
+ if (result.stale) {
237
+ return {
238
+ ...item,
239
+ currentPrice: null,
240
+ alerts: ["UNAVAILABLE: provider returned stale market data"],
241
+ statuses: [],
242
+ };
243
+ }
244
+ const quote = result.data;
245
+ if (isZeroFilledQuote(quote)) {
246
+ return {
247
+ ...item,
248
+ currentPrice: null,
249
+ alerts: ["UNAVAILABLE: Yahoo returned no valid market data."],
250
+ statuses: [],
251
+ };
252
+ }
253
+ return buildCheckResult(item, quote.price, "yahoo");
254
+ }),
255
+ );
256
+ }
257
+
258
+ function buildCheckResult(
259
+ item: WatchlistItemRecord,
260
+ price: number,
261
+ sourceProvider: "tradingview" | "yahoo",
262
+ dataCaveat?: string,
263
+ ): WatchlistCheck {
264
+ const alerts: string[] = [];
265
+ const statuses: string[] = [];
266
+ if (item.targetPrice && price >= item.targetPrice) {
267
+ alerts.push(`TARGET HIT: $${price.toFixed(2)} >= $${item.targetPrice}`);
268
+ } else if (item.targetPrice) {
269
+ statuses.push(`Target pending: $${price.toFixed(2)} < $${item.targetPrice}`);
270
+ }
271
+ if (item.stopPrice && price <= item.stopPrice) {
272
+ alerts.push(`STOP ALERT: $${price.toFixed(2)} fell below $${item.stopPrice}`);
273
+ } else if (item.stopPrice) {
274
+ statuses.push(`Stop OK: $${price.toFixed(2)} > $${item.stopPrice}`);
275
+ }
276
+ return {
277
+ ...item,
278
+ currentPrice: price,
279
+ alerts,
280
+ statuses,
281
+ sourceProvider,
282
+ dataCaveat,
283
+ };
284
+ }
285
+
286
+ function shouldSkipTradingView(symbol: string): boolean {
287
+ return /(?:-USD|\.(?:TO|DE|T|L|HK))$/i.test(symbol.trim());
288
+ }
@@ -1,11 +1,12 @@
1
- import { Type } from "@sinclair/typebox";
2
1
  import type { AgentTool } from "@earendil-works/pi-agent-core";
3
- import { getSubredditPosts, getPostComments } from "../../providers/reddit.js";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { getConfig } from "../../config.js";
4
+ import { getPostComments, getSubredditPosts } from "../../providers/reddit.js";
4
5
  import { wrapProvider } from "../../providers/wrap-provider.js";
5
- import type { RedditSentimentResult } from "../../types/sentiment.js";
6
6
  import { RedditAdapter } from "../../sentiment/adapters/reddit.js";
7
7
  import { getSentimentPipeline } from "../../sentiment/index.js";
8
- import { getConfig } from "../../config.js";
8
+ import type { RedditSentimentResult } from "../../types/sentiment.js";
9
+ import { renderUntrustedText, untrustedContentHeader } from "./untrusted-text.js";
9
10
 
10
11
  const params = Type.Object({
11
12
  subreddit: Type.Optional(
@@ -36,7 +37,7 @@ export const redditSentimentTool: AgentTool<typeof params, RedditSentimentResult
36
37
  description:
37
38
  "Analyze sentiment from financial Reddit communities. Supports single subreddit, multi-subreddit, and topic filtering. Returns scored posts with comment analysis and trend context.",
38
39
  parameters: params,
39
- async execute(toolCallId, args) {
40
+ async execute(_toolCallId, args) {
40
41
  const limit = Math.min(args.limit ?? 25, 100);
41
42
  const config = getConfig();
42
43
 
@@ -47,7 +48,12 @@ export const redditSentimentTool: AgentTool<typeof params, RedditSentimentResult
47
48
  } else if (args.subreddit) {
48
49
  subreddits = [args.subreddit];
49
50
  } else {
50
- subreddits = config.sentiment?.defaultSubreddits ?? ["wallstreetbets", "stocks", "investing", "options"];
51
+ subreddits = config.sentiment?.defaultSubreddits ?? [
52
+ "wallstreetbets",
53
+ "stocks",
54
+ "investing",
55
+ "options",
56
+ ];
51
57
  }
52
58
 
53
59
  // Fetch from all subreddits
@@ -64,21 +70,26 @@ export const redditSentimentTool: AgentTool<typeof params, RedditSentimentResult
64
70
 
65
71
  if (allResults.length === 0) {
66
72
  return {
67
- content: [{ type: "text", text: `⚠ Reddit sentiment unavailable (${warnings.join("; ")}).` }],
73
+ content: [
74
+ { type: "text", text: `⚠ Reddit sentiment unavailable (${warnings.join("; ")}).` },
75
+ ],
68
76
  details: null as any,
69
77
  };
70
78
  }
71
79
 
72
80
  // Merge and filter by query if provided
73
81
  const adapter = new RedditAdapter();
74
- let allRecords = allResults.flatMap((r) => adapter.mapPostsToRecords(r, args.query ?? subreddits.join("+")));
82
+ let allRecords = allResults.flatMap((r) =>
83
+ adapter.mapPostsToRecords(r, args.query ?? subreddits.join("+")),
84
+ );
75
85
 
76
86
  // Topic filtering
77
87
  if (args.query) {
78
88
  const queryLower = args.query.toLowerCase();
79
- allRecords = allRecords.filter((r) =>
80
- r.text.toLowerCase().includes(queryLower) ||
81
- (r.title?.toLowerCase().includes(queryLower) ?? false),
89
+ allRecords = allRecords.filter(
90
+ (r) =>
91
+ r.text.toLowerCase().includes(queryLower) ||
92
+ (r.title?.toLowerCase().includes(queryLower) ?? false),
82
93
  );
83
94
  }
84
95
 
@@ -115,23 +126,33 @@ export const redditSentimentTool: AgentTool<typeof params, RedditSentimentResult
115
126
 
116
127
  // Process through pipeline
117
128
  const pipeline = getSentimentPipeline();
118
- const pipelineResult = await pipeline.processRecords(allRecords, args.query ?? subreddits.join("+"));
129
+ const pipelineResult = await pipeline.processRecords(
130
+ allRecords,
131
+ args.query ?? subreddits.join("+"),
132
+ );
119
133
 
120
134
  // Build output using first result as base for backward compatibility
121
135
  const firstResult = allResults[0];
122
136
  const postRecords = pipelineResult.fresh.filter((r) => !r.metadata.isComment);
123
137
  const commentRecords = pipelineResult.fresh.filter((r) => r.metadata.isComment);
124
- const avgScore = postRecords.length > 0
125
- ? postRecords.reduce((s, r) => s + r.sentiment.score, 0) / postRecords.length
126
- : 0;
138
+ const avgScore =
139
+ postRecords.length > 0
140
+ ? postRecords.reduce((s, r) => s + r.sentiment.score, 0) / postRecords.length
141
+ : 0;
127
142
 
128
143
  const sentimentLabel =
129
- avgScore > 0.3 ? "Bullish" :
130
- avgScore < -0.3 ? "Bearish" :
131
- avgScore > 0 ? "Leaning Bullish" :
132
- avgScore < 0 ? "Leaning Bearish" : "Neutral";
133
-
134
- const subLabel = subreddits.length === 1 ? `r/${subreddits[0]}` : `${subreddits.length} subreddits`;
144
+ avgScore > 0.3
145
+ ? "Bullish"
146
+ : avgScore < -0.3
147
+ ? "Bearish"
148
+ : avgScore > 0
149
+ ? "Leaning Bullish"
150
+ : avgScore < 0
151
+ ? "Leaning Bearish"
152
+ : "Neutral";
153
+
154
+ const subLabel =
155
+ subreddits.length === 1 ? `r/${subreddits[0]}` : `${subreddits.length} subreddits`;
135
156
  const lines = [
136
157
  `**Reddit: ${args.query ?? subLabel}** — ${postRecords.length} posts, ${commentRecords.length} comments`,
137
158
  `Sentiment: ${avgScore.toFixed(2)} (${sentimentLabel})`,
@@ -142,16 +163,21 @@ export const redditSentimentTool: AgentTool<typeof params, RedditSentimentResult
142
163
  }
143
164
 
144
165
  lines.push("");
145
- lines.push("Top posts:");
166
+ lines.push(untrustedContentHeader("Reddit posts"));
146
167
  for (const post of postRecords.slice(0, 10)) {
147
- const scoreIndicator = post.sentiment.score > 0 ? "🟢" : post.sentiment.score < 0 ? "🔴" : "⚪";
148
- lines.push(` ${scoreIndicator} ⬆${post.engagement.score} 💬${post.engagement.replies ?? 0} ${(post.title ?? post.text).slice(0, 100)}`);
168
+ const scoreIndicator =
169
+ post.sentiment.score > 0 ? "🟢" : post.sentiment.score < 0 ? "🔴" : "⚪";
170
+ lines.push(
171
+ ` ${scoreIndicator} ⬆${post.engagement.score} 💬${post.engagement.replies ?? 0} — ${renderUntrustedText(post.title ?? post.text, 100)}`,
172
+ );
149
173
  }
150
174
 
151
175
  if (pipelineResult.trend && pipelineResult.trend.length > 0) {
152
176
  const t = pipelineResult.trend[0];
153
177
  lines.push("");
154
- lines.push(`Trend: ${t.sparkline} ${t.direction} (${t.delta >= 0 ? "+" : ""}${t.delta.toFixed(2)}, ${t.count} records)`);
178
+ lines.push(
179
+ `Trend: ${t.sparkline} ${t.direction} (${t.delta >= 0 ? "+" : ""}${t.delta.toFixed(2)}, ${t.count} records)`,
180
+ );
155
181
  }
156
182
 
157
183
  if (warnings.length > 0) {