opencandle 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (527) hide show
  1. package/README.md +164 -187
  2. package/dist/analysts/contracts.d.ts +1 -3
  3. package/dist/analysts/contracts.js +1 -11
  4. package/dist/analysts/contracts.js.map +1 -1
  5. package/dist/analysts/orchestrator.d.ts +1 -3
  6. package/dist/analysts/orchestrator.js +1 -26
  7. package/dist/analysts/orchestrator.js.map +1 -1
  8. package/dist/cli.js +30 -7
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +3 -3
  11. package/dist/config.js +12 -5
  12. package/dist/config.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/infra/browser.js +3 -1
  17. package/dist/infra/browser.js.map +1 -1
  18. package/dist/infra/cache.d.ts +8 -11
  19. package/dist/infra/cache.js +17 -15
  20. package/dist/infra/cache.js.map +1 -1
  21. package/dist/infra/http-client.d.ts +4 -1
  22. package/dist/infra/http-client.js +59 -6
  23. package/dist/infra/http-client.js.map +1 -1
  24. package/dist/infra/index.d.ts +3 -3
  25. package/dist/infra/index.js +3 -3
  26. package/dist/infra/index.js.map +1 -1
  27. package/dist/infra/native-dependencies.js +2 -2
  28. package/dist/infra/native-dependencies.js.map +1 -1
  29. package/dist/infra/node-version.js.map +1 -1
  30. package/dist/infra/opencandle-paths.d.ts +0 -3
  31. package/dist/infra/opencandle-paths.js +4 -11
  32. package/dist/infra/opencandle-paths.js.map +1 -1
  33. package/dist/infra/rate-limiter.js +12 -9
  34. package/dist/infra/rate-limiter.js.map +1 -1
  35. package/dist/market-state/alert-conditions.d.ts +34 -0
  36. package/dist/market-state/alert-conditions.js +23 -0
  37. package/dist/market-state/alert-conditions.js.map +1 -0
  38. package/dist/market-state/alert-runner.d.ts +55 -0
  39. package/dist/market-state/alert-runner.js +634 -0
  40. package/dist/market-state/alert-runner.js.map +1 -0
  41. package/dist/market-state/daily-report.d.ts +26 -0
  42. package/dist/market-state/daily-report.js +179 -0
  43. package/dist/market-state/daily-report.js.map +1 -0
  44. package/dist/market-state/local-automation-service.d.ts +25 -0
  45. package/dist/market-state/local-automation-service.js +119 -0
  46. package/dist/market-state/local-automation-service.js.map +1 -0
  47. package/dist/market-state/notification-delivery.d.ts +14 -0
  48. package/dist/market-state/notification-delivery.js +139 -0
  49. package/dist/market-state/notification-delivery.js.map +1 -0
  50. package/dist/market-state/resolve-for-mutation.d.ts +10 -0
  51. package/dist/market-state/resolve-for-mutation.js +15 -0
  52. package/dist/market-state/resolve-for-mutation.js.map +1 -0
  53. package/dist/market-state/resolve.d.ts +14 -0
  54. package/dist/market-state/resolve.js +89 -0
  55. package/dist/market-state/resolve.js.map +1 -0
  56. package/dist/market-state/service.d.ts +527 -0
  57. package/dist/market-state/service.js +1099 -0
  58. package/dist/market-state/service.js.map +1 -0
  59. package/dist/memory/index.d.ts +7 -7
  60. package/dist/memory/index.js +6 -6
  61. package/dist/memory/index.js.map +1 -1
  62. package/dist/memory/manager.js +11 -11
  63. package/dist/memory/manager.js.map +1 -1
  64. package/dist/memory/retrieval.js +7 -4
  65. package/dist/memory/retrieval.js.map +1 -1
  66. package/dist/memory/sqlite.js +385 -3
  67. package/dist/memory/sqlite.js.map +1 -1
  68. package/dist/memory/storage.js +1 -2
  69. package/dist/memory/storage.js.map +1 -1
  70. package/dist/memory/tool-defaults.js +64 -28
  71. package/dist/memory/tool-defaults.js.map +1 -1
  72. package/dist/memory/types.js.map +1 -1
  73. package/dist/monitor.d.ts +2 -0
  74. package/dist/monitor.js +104 -0
  75. package/dist/monitor.js.map +1 -0
  76. package/dist/onboarding/connect.js +4 -6
  77. package/dist/onboarding/connect.js.map +1 -1
  78. package/dist/onboarding/credential-interceptor.js +1 -1
  79. package/dist/onboarding/credential-interceptor.js.map +1 -1
  80. package/dist/onboarding/degradation-accumulator.js +1 -3
  81. package/dist/onboarding/degradation-accumulator.js.map +1 -1
  82. package/dist/onboarding/providers.js +3 -16
  83. package/dist/onboarding/providers.js.map +1 -1
  84. package/dist/onboarding/state.js.map +1 -1
  85. package/dist/onboarding/tool-helpers.js +1 -1
  86. package/dist/onboarding/tool-helpers.js.map +1 -1
  87. package/dist/onboarding/tool-tags.js +6 -4
  88. package/dist/onboarding/tool-tags.js.map +1 -1
  89. package/dist/onboarding/validation.js +1 -1
  90. package/dist/onboarding/validation.js.map +1 -1
  91. package/dist/pi/opencandle-extension.d.ts +8 -0
  92. package/dist/pi/opencandle-extension.js +412 -28
  93. package/dist/pi/opencandle-extension.js.map +1 -1
  94. package/dist/pi/session.d.ts +1 -1
  95. package/dist/pi/session.js +3 -1
  96. package/dist/pi/session.js.map +1 -1
  97. package/dist/pi/setup.js +8 -3
  98. package/dist/pi/setup.js.map +1 -1
  99. package/dist/pi/tool-adapter.js +5 -2
  100. package/dist/pi/tool-adapter.js.map +1 -1
  101. package/dist/prompts/context-builder.d.ts +1 -1
  102. package/dist/prompts/context-builder.js +19 -6
  103. package/dist/prompts/context-builder.js.map +1 -1
  104. package/dist/prompts/policy-cards.d.ts +1 -1
  105. package/dist/prompts/policy-cards.js +1 -1
  106. package/dist/prompts/policy-cards.js.map +1 -1
  107. package/dist/prompts/sections.d.ts +1 -1
  108. package/dist/prompts/symbol-preflight.d.ts +20 -0
  109. package/dist/prompts/symbol-preflight.js +49 -0
  110. package/dist/prompts/symbol-preflight.js.map +1 -0
  111. package/dist/prompts/workflow-prompts.d.ts +1 -1
  112. package/dist/prompts/workflow-prompts.js +54 -16
  113. package/dist/prompts/workflow-prompts.js.map +1 -1
  114. package/dist/providers/alpha-vantage.d.ts +1 -1
  115. package/dist/providers/alpha-vantage.js +26 -7
  116. package/dist/providers/alpha-vantage.js.map +1 -1
  117. package/dist/providers/coingecko.js +1 -1
  118. package/dist/providers/coingecko.js.map +1 -1
  119. package/dist/providers/errors.d.ts +5 -0
  120. package/dist/providers/errors.js +11 -0
  121. package/dist/providers/errors.js.map +1 -0
  122. package/dist/providers/exa-search.d.ts +2 -2
  123. package/dist/providers/exa-search.js +19 -11
  124. package/dist/providers/exa-search.js.map +1 -1
  125. package/dist/providers/fear-greed.js +1 -1
  126. package/dist/providers/fear-greed.js.map +1 -1
  127. package/dist/providers/finnhub.js +3 -5
  128. package/dist/providers/finnhub.js.map +1 -1
  129. package/dist/providers/fred.js +2 -2
  130. package/dist/providers/fred.js.map +1 -1
  131. package/dist/providers/index.d.ts +7 -6
  132. package/dist/providers/index.js +6 -5
  133. package/dist/providers/index.js.map +1 -1
  134. package/dist/providers/reddit.js +2 -2
  135. package/dist/providers/reddit.js.map +1 -1
  136. package/dist/providers/sec-edgar.d.ts +1 -0
  137. package/dist/providers/sec-edgar.js +12 -4
  138. package/dist/providers/sec-edgar.js.map +1 -1
  139. package/dist/providers/tradingview.d.ts +47 -0
  140. package/dist/providers/tradingview.js +275 -0
  141. package/dist/providers/tradingview.js.map +1 -0
  142. package/dist/providers/twitter.js +6 -8
  143. package/dist/providers/twitter.js.map +1 -1
  144. package/dist/providers/web-search.js +26 -12
  145. package/dist/providers/web-search.js.map +1 -1
  146. package/dist/providers/with-fallback.js +4 -2
  147. package/dist/providers/with-fallback.js.map +1 -1
  148. package/dist/providers/wrap-provider.d.ts +2 -3
  149. package/dist/providers/wrap-provider.js +14 -8
  150. package/dist/providers/wrap-provider.js.map +1 -1
  151. package/dist/providers/yahoo-finance.d.ts +1 -1
  152. package/dist/providers/yahoo-finance.js +101 -17
  153. package/dist/providers/yahoo-finance.js.map +1 -1
  154. package/dist/routing/classify-intent.d.ts +6 -0
  155. package/dist/routing/classify-intent.js +78 -7
  156. package/dist/routing/classify-intent.js.map +1 -1
  157. package/dist/routing/defaults.d.ts +1 -1
  158. package/dist/routing/entity-extractor.d.ts +1 -0
  159. package/dist/routing/entity-extractor.js +234 -29
  160. package/dist/routing/entity-extractor.js.map +1 -1
  161. package/dist/routing/fund-symbols.d.ts +2 -0
  162. package/dist/routing/fund-symbols.js +55 -0
  163. package/dist/routing/fund-symbols.js.map +1 -0
  164. package/dist/routing/horizon.d.ts +1 -0
  165. package/dist/routing/horizon.js +10 -0
  166. package/dist/routing/horizon.js.map +1 -0
  167. package/dist/routing/index.d.ts +10 -10
  168. package/dist/routing/index.js +6 -6
  169. package/dist/routing/index.js.map +1 -1
  170. package/dist/routing/planning.d.ts +1 -1
  171. package/dist/routing/planning.js +65 -34
  172. package/dist/routing/planning.js.map +1 -1
  173. package/dist/routing/route-manifest.d.ts +2 -2
  174. package/dist/routing/route-manifest.js +25 -4
  175. package/dist/routing/route-manifest.js.map +1 -1
  176. package/dist/routing/router-llm-client.js.map +1 -1
  177. package/dist/routing/router-prompt.js +7 -9
  178. package/dist/routing/router-prompt.js.map +1 -1
  179. package/dist/routing/router-types.d.ts +1 -0
  180. package/dist/routing/router.js +137 -22
  181. package/dist/routing/router.js.map +1 -1
  182. package/dist/routing/slot-resolver.d.ts +1 -1
  183. package/dist/routing/slot-resolver.js +2 -4
  184. package/dist/routing/slot-resolver.js.map +1 -1
  185. package/dist/routing/symbol-disambiguator.d.ts +11 -0
  186. package/dist/routing/symbol-disambiguator.js +52 -0
  187. package/dist/routing/symbol-disambiguator.js.map +1 -0
  188. package/dist/routing/turn-context.d.ts +1 -1
  189. package/dist/routing/turn-context.js +1 -1
  190. package/dist/routing/turn-context.js.map +1 -1
  191. package/dist/routing/types.d.ts +2 -0
  192. package/dist/runtime/answer-contracts.js +36 -8
  193. package/dist/runtime/answer-contracts.js.map +1 -1
  194. package/dist/runtime/artifact-contracts.js.map +1 -1
  195. package/dist/runtime/planning-evidence.js +47 -26
  196. package/dist/runtime/planning-evidence.js.map +1 -1
  197. package/dist/runtime/prompt-step.d.ts +1 -9
  198. package/dist/runtime/prompt-step.js +0 -10
  199. package/dist/runtime/prompt-step.js.map +1 -1
  200. package/dist/runtime/run-context.d.ts +5 -2
  201. package/dist/runtime/run-context.js +8 -1
  202. package/dist/runtime/run-context.js.map +1 -1
  203. package/dist/runtime/session-coordinator.d.ts +13 -5
  204. package/dist/runtime/session-coordinator.js +160 -20
  205. package/dist/runtime/session-coordinator.js.map +1 -1
  206. package/dist/runtime/session-title.d.ts +14 -0
  207. package/dist/runtime/session-title.js +50 -0
  208. package/dist/runtime/session-title.js.map +1 -0
  209. package/dist/runtime/tool-defaults-wrapper.js +1 -3
  210. package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
  211. package/dist/runtime/validation.js.map +1 -1
  212. package/dist/runtime/workflow-events.js.map +1 -1
  213. package/dist/runtime/workflow-runner.d.ts +3 -3
  214. package/dist/runtime/workflow-runner.js +1 -1
  215. package/dist/runtime/workflow-runner.js.map +1 -1
  216. package/dist/sentiment/adapters/finnhub.d.ts +1 -1
  217. package/dist/sentiment/adapters/finnhub.js +6 -1
  218. package/dist/sentiment/adapters/finnhub.js.map +1 -1
  219. package/dist/sentiment/adapters/reddit.d.ts +2 -2
  220. package/dist/sentiment/adapters/twitter.d.ts +1 -1
  221. package/dist/sentiment/adapters/web.d.ts +1 -1
  222. package/dist/sentiment/index.d.ts +9 -11
  223. package/dist/sentiment/index.js +9 -20
  224. package/dist/sentiment/index.js.map +1 -1
  225. package/dist/sentiment/keywords.js +26 -4
  226. package/dist/sentiment/keywords.js.map +1 -1
  227. package/dist/sentiment/pipeline.d.ts +2 -2
  228. package/dist/sentiment/pipeline.js +1 -1
  229. package/dist/sentiment/pipeline.js.map +1 -1
  230. package/dist/sentiment/scorer.js +1 -1
  231. package/dist/sentiment/store.d.ts +1 -1
  232. package/dist/sentiment/store.js +1 -1
  233. package/dist/sentiment/store.js.map +1 -1
  234. package/dist/sentiment/trends.d.ts +1 -1
  235. package/dist/sentiment/trends.js.map +1 -1
  236. package/dist/sentiment/types.js.map +1 -1
  237. package/dist/system-prompt.js +3 -2
  238. package/dist/system-prompt.js.map +1 -1
  239. package/dist/tool-kit.d.ts +7 -7
  240. package/dist/tool-kit.js +4 -4
  241. package/dist/tool-kit.js.map +1 -1
  242. package/dist/tools/fundamentals/company-overview.js +11 -6
  243. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  244. package/dist/tools/fundamentals/comps.js +18 -9
  245. package/dist/tools/fundamentals/comps.js.map +1 -1
  246. package/dist/tools/fundamentals/dcf.js +23 -11
  247. package/dist/tools/fundamentals/dcf.js.map +1 -1
  248. package/dist/tools/fundamentals/earnings.js +8 -3
  249. package/dist/tools/fundamentals/earnings.js.map +1 -1
  250. package/dist/tools/fundamentals/financials.js +8 -3
  251. package/dist/tools/fundamentals/financials.js.map +1 -1
  252. package/dist/tools/fundamentals/sec-filings.js +21 -6
  253. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  254. package/dist/tools/index.d.ts +23 -19
  255. package/dist/tools/index.js +51 -39
  256. package/dist/tools/index.js.map +1 -1
  257. package/dist/tools/interaction/ask-user.js +15 -3
  258. package/dist/tools/interaction/ask-user.js.map +1 -1
  259. package/dist/tools/interaction/twitter-login.js +13 -3
  260. package/dist/tools/interaction/twitter-login.js.map +1 -1
  261. package/dist/tools/macro/fear-greed.js.map +1 -1
  262. package/dist/tools/macro/fred-data.d.ts +1 -1
  263. package/dist/tools/macro/fred-data.js +17 -6
  264. package/dist/tools/macro/fred-data.js.map +1 -1
  265. package/dist/tools/market/crypto-history.js +3 -1
  266. package/dist/tools/market/crypto-history.js.map +1 -1
  267. package/dist/tools/market/crypto-price.js +3 -1
  268. package/dist/tools/market/crypto-price.js.map +1 -1
  269. package/dist/tools/market/screen-stocks.d.ts +18 -0
  270. package/dist/tools/market/screen-stocks.js +252 -0
  271. package/dist/tools/market/screen-stocks.js.map +1 -0
  272. package/dist/tools/market/search-ticker.js +160 -8
  273. package/dist/tools/market/search-ticker.js.map +1 -1
  274. package/dist/tools/market/stock-history.d.ts +2 -2
  275. package/dist/tools/market/stock-history.js +26 -7
  276. package/dist/tools/market/stock-history.js.map +1 -1
  277. package/dist/tools/market/stock-quote.js +5 -3
  278. package/dist/tools/market/stock-quote.js.map +1 -1
  279. package/dist/tools/options/greeks.js +1 -1
  280. package/dist/tools/options/greeks.js.map +1 -1
  281. package/dist/tools/options/option-chain.js +19 -6
  282. package/dist/tools/options/option-chain.js.map +1 -1
  283. package/dist/tools/portfolio/alerts.d.ts +15 -0
  284. package/dist/tools/portfolio/alerts.js +357 -0
  285. package/dist/tools/portfolio/alerts.js.map +1 -0
  286. package/dist/tools/portfolio/correlation.d.ts +1 -1
  287. package/dist/tools/portfolio/correlation.js +33 -13
  288. package/dist/tools/portfolio/correlation.js.map +1 -1
  289. package/dist/tools/portfolio/daily-report.d.ts +8 -0
  290. package/dist/tools/portfolio/daily-report.js +83 -0
  291. package/dist/tools/portfolio/daily-report.js.map +1 -0
  292. package/dist/tools/portfolio/holdings-overlap.js +10 -3
  293. package/dist/tools/portfolio/holdings-overlap.js.map +1 -1
  294. package/dist/tools/portfolio/notifications.d.ts +7 -0
  295. package/dist/tools/portfolio/notifications.js +43 -0
  296. package/dist/tools/portfolio/notifications.js.map +1 -0
  297. package/dist/tools/portfolio/predictions.d.ts +12 -6
  298. package/dist/tools/portfolio/predictions.js +337 -87
  299. package/dist/tools/portfolio/predictions.js.map +1 -1
  300. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  301. package/dist/tools/portfolio/risk-analysis.js +45 -6
  302. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  303. package/dist/tools/portfolio/tracker.d.ts +4 -3
  304. package/dist/tools/portfolio/tracker.js +246 -101
  305. package/dist/tools/portfolio/tracker.js.map +1 -1
  306. package/dist/tools/portfolio/watchlist.d.ts +6 -4
  307. package/dist/tools/portfolio/watchlist.js +208 -108
  308. package/dist/tools/portfolio/watchlist.js.map +1 -1
  309. package/dist/tools/sentiment/reddit-sentiment.js +23 -10
  310. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  311. package/dist/tools/sentiment/sentiment-summary.js +15 -13
  312. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  313. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  314. package/dist/tools/sentiment/sentiment-trend.js +12 -2
  315. package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
  316. package/dist/tools/sentiment/twitter-sentiment.js +12 -5
  317. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  318. package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
  319. package/dist/tools/sentiment/untrusted-text.js +17 -0
  320. package/dist/tools/sentiment/untrusted-text.js.map +1 -0
  321. package/dist/tools/sentiment/web-search.js +9 -13
  322. package/dist/tools/sentiment/web-search.js.map +1 -1
  323. package/dist/tools/sentiment/web-sentiment.js +15 -3
  324. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  325. package/dist/tools/technical/backtest.d.ts +1 -1
  326. package/dist/tools/technical/backtest.js +27 -20
  327. package/dist/tools/technical/backtest.js.map +1 -1
  328. package/dist/tools/technical/indicators.js +23 -5
  329. package/dist/tools/technical/indicators.js.map +1 -1
  330. package/dist/types/index.d.ts +3 -3
  331. package/dist/types/index.js.map +1 -1
  332. package/dist/types/market.d.ts +1 -0
  333. package/dist/types/portfolio.d.ts +14 -4
  334. package/dist/workflows/compare-assets.d.ts +0 -3
  335. package/dist/workflows/compare-assets.js +20 -11
  336. package/dist/workflows/compare-assets.js.map +1 -1
  337. package/dist/workflows/index.d.ts +3 -4
  338. package/dist/workflows/index.js +3 -3
  339. package/dist/workflows/index.js.map +1 -1
  340. package/dist/workflows/options-screener.d.ts +0 -3
  341. package/dist/workflows/options-screener.js +4 -11
  342. package/dist/workflows/options-screener.js.map +1 -1
  343. package/dist/workflows/portfolio-builder.d.ts +0 -3
  344. package/dist/workflows/portfolio-builder.js +0 -8
  345. package/dist/workflows/portfolio-builder.js.map +1 -1
  346. package/gui/server/ask-user-bridge.ts +1 -1
  347. package/gui/server/automation-heartbeat.ts +97 -0
  348. package/gui/server/background-quotes.ts +97 -1
  349. package/gui/server/chat-event-adapter.ts +32 -10
  350. package/gui/server/chat-run-session.ts +16 -0
  351. package/gui/server/invoke-tool.ts +144 -1
  352. package/gui/server/live-chat-event-adapter.ts +21 -6
  353. package/gui/server/market-state-api.ts +315 -0
  354. package/gui/server/model-setup.ts +149 -2
  355. package/gui/server/private-api-access.ts +62 -0
  356. package/gui/server/projector.ts +12 -7
  357. package/gui/server/prompt-observation.ts +4 -7
  358. package/gui/server/quote-snapshot-store.ts +50 -0
  359. package/gui/server/server.ts +200 -451
  360. package/gui/server/session-actions.ts +186 -1
  361. package/gui/server/shutdown.ts +47 -0
  362. package/gui/server/tool-invoke-ack.ts +49 -0
  363. package/gui/server/tool-metadata.ts +23 -10
  364. package/gui/server/websocket.ts +13 -3
  365. package/gui/server/writer-lock.ts +6 -2
  366. package/gui/server/ws-hub.ts +292 -0
  367. package/gui/shared/chat-events.ts +16 -1
  368. package/gui/shared/event-reducer.ts +24 -6
  369. package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +1 -0
  370. package/gui/web/dist/assets/index-2KZtKBmu.css +1 -0
  371. package/gui/web/dist/assets/index-CveNgtDg.js +69 -0
  372. package/gui/web/dist/index.html +2 -2
  373. package/package.json +5 -1
  374. package/src/analysts/contracts.ts +10 -23
  375. package/src/analysts/orchestrator.ts +8 -43
  376. package/src/cli.ts +35 -12
  377. package/src/config.ts +17 -9
  378. package/src/index.ts +1 -1
  379. package/src/infra/browser.ts +3 -1
  380. package/src/infra/cache.ts +41 -30
  381. package/src/infra/http-client.ts +72 -6
  382. package/src/infra/index.ts +7 -10
  383. package/src/infra/native-dependencies.ts +8 -3
  384. package/src/infra/node-version.ts +3 -1
  385. package/src/infra/opencandle-paths.ts +3 -14
  386. package/src/infra/rate-limiter.ts +22 -19
  387. package/src/market-state/alert-conditions.ts +82 -0
  388. package/src/market-state/alert-runner.ts +863 -0
  389. package/src/market-state/daily-report.ts +247 -0
  390. package/src/market-state/local-automation-service.ts +162 -0
  391. package/src/market-state/notification-delivery.ts +158 -0
  392. package/src/market-state/resolve-for-mutation.ts +24 -0
  393. package/src/market-state/resolve.ts +112 -0
  394. package/src/market-state/service.ts +2344 -0
  395. package/src/memory/index.ts +7 -7
  396. package/src/memory/manager.ts +14 -16
  397. package/src/memory/retrieval.ts +8 -7
  398. package/src/memory/sqlite.ts +407 -6
  399. package/src/memory/storage.ts +5 -15
  400. package/src/memory/tool-defaults.ts +60 -39
  401. package/src/memory/types.ts +3 -3
  402. package/src/monitor.ts +121 -0
  403. package/src/onboarding/connect.ts +10 -33
  404. package/src/onboarding/credential-interceptor.ts +3 -15
  405. package/src/onboarding/degradation-accumulator.ts +1 -3
  406. package/src/onboarding/providers.ts +9 -40
  407. package/src/onboarding/state.ts +4 -15
  408. package/src/onboarding/tool-helpers.ts +2 -9
  409. package/src/onboarding/tool-tags.ts +6 -6
  410. package/src/onboarding/validation.ts +14 -20
  411. package/src/pi/opencandle-extension.ts +529 -85
  412. package/src/pi/session.ts +7 -5
  413. package/src/pi/setup.ts +61 -43
  414. package/src/pi/tool-adapter.ts +5 -2
  415. package/src/prompts/context-builder.ts +23 -12
  416. package/src/prompts/policy-cards.ts +2 -2
  417. package/src/prompts/sections.ts +1 -1
  418. package/src/prompts/symbol-preflight.ts +80 -0
  419. package/src/prompts/workflow-prompts.ts +77 -28
  420. package/src/providers/alpha-vantage.ts +58 -39
  421. package/src/providers/coingecko.ts +2 -5
  422. package/src/providers/errors.ts +9 -0
  423. package/src/providers/exa-search.ts +24 -22
  424. package/src/providers/fear-greed.ts +1 -1
  425. package/src/providers/finnhub.ts +7 -6
  426. package/src/providers/fred.ts +3 -3
  427. package/src/providers/index.ts +14 -6
  428. package/src/providers/reddit.ts +17 -6
  429. package/src/providers/sec-edgar.ts +20 -6
  430. package/src/providers/tradingview.ts +399 -0
  431. package/src/providers/twitter.ts +6 -8
  432. package/src/providers/web-search.ts +30 -20
  433. package/src/providers/with-fallback.ts +8 -7
  434. package/src/providers/wrap-provider.ts +15 -10
  435. package/src/providers/yahoo-finance.ts +140 -35
  436. package/src/routing/classify-intent.ts +101 -10
  437. package/src/routing/defaults.ts +1 -1
  438. package/src/routing/entity-extractor.ts +287 -38
  439. package/src/routing/fund-symbols.ts +58 -0
  440. package/src/routing/horizon.ts +7 -0
  441. package/src/routing/index.ts +48 -48
  442. package/src/routing/planning.ts +144 -53
  443. package/src/routing/route-manifest.ts +37 -15
  444. package/src/routing/router-llm-client.ts +4 -4
  445. package/src/routing/router-prompt.ts +15 -19
  446. package/src/routing/router-types.ts +2 -5
  447. package/src/routing/router.ts +251 -53
  448. package/src/routing/slot-resolver.ts +34 -11
  449. package/src/routing/symbol-disambiguator.ts +72 -0
  450. package/src/routing/turn-context.ts +6 -9
  451. package/src/routing/types.ts +2 -0
  452. package/src/runtime/answer-contracts.ts +82 -43
  453. package/src/runtime/artifact-contracts.ts +2 -1
  454. package/src/runtime/planning-evidence.ts +157 -66
  455. package/src/runtime/prompt-step.ts +1 -16
  456. package/src/runtime/run-context.ts +12 -2
  457. package/src/runtime/session-coordinator.ts +238 -63
  458. package/src/runtime/session-title.ts +60 -0
  459. package/src/runtime/tool-defaults-wrapper.ts +1 -3
  460. package/src/runtime/validation.ts +1 -4
  461. package/src/runtime/workflow-events.ts +7 -7
  462. package/src/runtime/workflow-runner.ts +5 -11
  463. package/src/sentiment/adapters/finnhub.ts +7 -2
  464. package/src/sentiment/adapters/reddit.ts +2 -2
  465. package/src/sentiment/adapters/twitter.ts +1 -1
  466. package/src/sentiment/adapters/web.ts +1 -1
  467. package/src/sentiment/index.ts +16 -26
  468. package/src/sentiment/keywords.ts +26 -4
  469. package/src/sentiment/pipeline.ts +15 -4
  470. package/src/sentiment/scorer.ts +1 -1
  471. package/src/sentiment/store.ts +2 -2
  472. package/src/sentiment/trends.ts +9 -3
  473. package/src/sentiment/types.ts +5 -4
  474. package/src/system-prompt.ts +3 -2
  475. package/src/tool-kit.ts +10 -9
  476. package/src/tools/fundamentals/company-overview.ts +19 -9
  477. package/src/tools/fundamentals/comps.ts +68 -55
  478. package/src/tools/fundamentals/dcf.ts +145 -95
  479. package/src/tools/fundamentals/earnings.ts +16 -6
  480. package/src/tools/fundamentals/financials.ts +16 -7
  481. package/src/tools/fundamentals/sec-filings.ts +37 -16
  482. package/src/tools/index.ts +51 -39
  483. package/src/tools/interaction/ask-user.ts +22 -10
  484. package/src/tools/interaction/twitter-login.ts +17 -5
  485. package/src/tools/macro/fear-greed.ts +1 -1
  486. package/src/tools/macro/fred-data.ts +58 -46
  487. package/src/tools/market/crypto-history.ts +8 -3
  488. package/src/tools/market/crypto-price.ts +6 -6
  489. package/src/tools/market/screen-stocks.ts +279 -0
  490. package/src/tools/market/search-ticker.ts +218 -17
  491. package/src/tools/market/stock-history.ts +37 -12
  492. package/src/tools/market/stock-quote.ts +10 -7
  493. package/src/tools/options/greeks.ts +5 -5
  494. package/src/tools/options/option-chain.ts +41 -17
  495. package/src/tools/portfolio/alerts.ts +457 -0
  496. package/src/tools/portfolio/correlation.ts +47 -20
  497. package/src/tools/portfolio/daily-report.ts +101 -0
  498. package/src/tools/portfolio/holdings-overlap.ts +31 -15
  499. package/src/tools/portfolio/notifications.ts +45 -0
  500. package/src/tools/portfolio/predictions.ts +406 -106
  501. package/src/tools/portfolio/risk-analysis.ts +46 -7
  502. package/src/tools/portfolio/tracker.ts +270 -109
  503. package/src/tools/portfolio/watchlist.ts +250 -121
  504. package/src/tools/sentiment/reddit-sentiment.ts +50 -24
  505. package/src/tools/sentiment/sentiment-summary.ts +62 -41
  506. package/src/tools/sentiment/sentiment-trend.ts +24 -7
  507. package/src/tools/sentiment/twitter-sentiment.ts +22 -15
  508. package/src/tools/sentiment/untrusted-text.ts +21 -0
  509. package/src/tools/sentiment/web-search.ts +21 -18
  510. package/src/tools/sentiment/web-sentiment.ts +26 -10
  511. package/src/tools/technical/backtest.ts +32 -22
  512. package/src/tools/technical/indicators.ts +39 -14
  513. package/src/types/index.ts +8 -3
  514. package/src/types/market.ts +1 -0
  515. package/src/types/portfolio.ts +14 -4
  516. package/src/types/sentiment.ts +2 -2
  517. package/src/workflows/compare-assets.ts +33 -21
  518. package/src/workflows/index.ts +3 -4
  519. package/src/workflows/options-screener.ts +27 -29
  520. package/src/workflows/portfolio-builder.ts +34 -27
  521. package/dist/workflows/types.d.ts +0 -4
  522. package/dist/workflows/types.js +0 -2
  523. package/dist/workflows/types.js.map +0 -1
  524. package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +0 -1
  525. package/gui/web/dist/assets/index-Bxt9QpLX.css +0 -1
  526. package/gui/web/dist/assets/index-CZ9DHZYy.js +0 -67
  527. package/src/workflows/types.ts +0 -4
package/src/pi/session.ts CHANGED
@@ -1,17 +1,17 @@
1
1
  import "../infra/node-version.js";
2
2
  import {
3
3
  type AuthStorage,
4
+ type CreateAgentSessionResult,
4
5
  createAgentSession,
5
6
  DefaultResourceLoader,
7
+ getAgentDir,
6
8
  type ModelRegistry,
7
- type CreateAgentSessionResult,
8
- type SettingsManager,
9
9
  type SessionManager,
10
- getAgentDir,
10
+ type SettingsManager,
11
11
  } from "@earendil-works/pi-coding-agent";
12
12
  import { loadEnv } from "../config.js";
13
- import openCandleExtension from "./opencandle-extension.js";
14
13
  import type { AskUserHandler } from "../types/index.js";
14
+ import openCandleExtension from "./opencandle-extension.js";
15
15
 
16
16
  export interface CreateOpenCandleSessionOptions {
17
17
  cwd?: string;
@@ -38,7 +38,9 @@ export async function createOpenCandleSession(
38
38
  cwd,
39
39
  agentDir,
40
40
  settingsManager: options.settingsManager,
41
- extensionFactories: [(pi) => openCandleExtension(pi, { askUserHandler: options.askUserHandler })],
41
+ extensionFactories: [
42
+ (pi) => openCandleExtension(pi, { askUserHandler: options.askUserHandler }),
43
+ ],
42
44
  })
43
45
  : undefined;
44
46
 
package/src/pi/setup.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { Model } from "@earendil-works/pi-ai";
2
2
  import {
3
- LoginDialogComponent,
4
3
  type ExtensionAPI,
5
4
  type ExtensionContext,
5
+ LoginDialogComponent,
6
6
  } from "@earendil-works/pi-coding-agent";
7
7
 
8
8
  type SetupMode = "startup" | "manual";
@@ -54,14 +54,18 @@ function getAvailableModels(ctx: ExtensionContext, preferredProvider?: string):
54
54
  return sortModels(ctx.modelRegistry.getAvailable(), preferredProvider);
55
55
  }
56
56
 
57
- export function getLlmSetupRequirement(ctx: Pick<ExtensionContext, "model" | "modelRegistry">): SetupRequirement {
57
+ export function getLlmSetupRequirement(
58
+ ctx: Pick<ExtensionContext, "model" | "modelRegistry">,
59
+ ): SetupRequirement {
58
60
  if (ctx.model && ctx.modelRegistry.hasConfiguredAuth(ctx.model)) {
59
61
  return "ready";
60
62
  }
61
63
  return ctx.modelRegistry.getAvailable().length > 0 ? "select_model" : "connect_auth";
62
64
  }
63
65
 
64
- async function selectProviderForApiKey(ctx: ExtensionContext): Promise<ApiKeyProviderId | undefined> {
66
+ async function selectProviderForApiKey(
67
+ ctx: ExtensionContext,
68
+ ): Promise<ApiKeyProviderId | undefined> {
65
69
  const choice = await ctx.ui.select("Connect an AI model", [
66
70
  "Google Gemini API",
67
71
  "OpenAI API",
@@ -79,7 +83,9 @@ async function selectProviderForApiKey(ctx: ExtensionContext): Promise<ApiKeyPro
79
83
  }
80
84
  }
81
85
 
82
- async function selectProviderForLogin(ctx: ExtensionContext): Promise<OAuthProviderChoice | undefined> {
86
+ async function selectProviderForLogin(
87
+ ctx: ExtensionContext,
88
+ ): Promise<OAuthProviderChoice | undefined> {
83
89
  const choice = await ctx.ui.select("Connect an AI model", [
84
90
  "Google",
85
91
  "OpenAI",
@@ -112,7 +118,9 @@ async function selectAdvancedOAuthProvider(ctx: ExtensionContext): Promise<strin
112
118
  }
113
119
 
114
120
  async function runLoginDialog(ctx: ExtensionContext, providerId: string): Promise<boolean> {
115
- const provider = ctx.modelRegistry.authStorage.getOAuthProviders().find((item) => item.id === providerId);
121
+ const provider = ctx.modelRegistry.authStorage
122
+ .getOAuthProviders()
123
+ .find((item) => item.id === providerId);
116
124
  const providerName = provider?.name ?? providerId;
117
125
  const usesCallbackServer = provider?.usesCallbackServer ?? false;
118
126
 
@@ -136,43 +144,49 @@ async function runLoginDialog(ctx: ExtensionContext, providerId: string): Promis
136
144
  });
137
145
 
138
146
  // Cast required: advanced providers return dynamic IDs outside the SDK's static union type
139
- void ctx.modelRegistry.authStorage.login(providerId as any, {
140
- onAuth: (info) => {
141
- dialog.showAuth(info.url, info.instructions);
142
- if (usesCallbackServer) {
143
- void dialog
144
- .showManualInput("Paste redirect URL below, or complete login in your browser:")
145
- .then((value) => {
146
- if (value && manualCodeResolve) {
147
- manualCodeResolve(value);
148
- manualCodeResolve = undefined;
149
- }
150
- })
151
- .catch(() => {
152
- if (manualCodeReject) {
153
- manualCodeReject(new Error("Login cancelled"));
154
- manualCodeReject = undefined;
155
- }
156
- });
157
- } else if (providerId === "github-copilot") {
158
- dialog.showWaiting("Waiting for browser authentication...");
159
- }
160
- },
161
- onDeviceCode: (info) => {
162
- dialog.showDeviceCode(info);
163
- dialog.showWaiting("Waiting for authentication...");
164
- },
165
- onPrompt: async (prompt) => dialog.showPrompt(prompt.message, prompt.placeholder),
166
- onProgress: (message) => dialog.showProgress(message),
167
- onSelect: async (prompt) => {
168
- const options = prompt.options.map((option, index) => `${index + 1}. ${option.label}`).join("\n");
169
- const answer = await dialog.showPrompt(`${prompt.message}\n\n${options}`, "Enter a number");
170
- const selectedIndex = Number.parseInt(answer.trim(), 10) - 1;
171
- return prompt.options[selectedIndex]?.id;
172
- },
173
- onManualCodeInput: () => manualCodePromise,
174
- signal: dialog.signal,
175
- })
147
+ void ctx.modelRegistry.authStorage
148
+ .login(providerId as any, {
149
+ onAuth: (info) => {
150
+ dialog.showAuth(info.url, info.instructions);
151
+ if (usesCallbackServer) {
152
+ void dialog
153
+ .showManualInput("Paste redirect URL below, or complete login in your browser:")
154
+ .then((value) => {
155
+ if (value && manualCodeResolve) {
156
+ manualCodeResolve(value);
157
+ manualCodeResolve = undefined;
158
+ }
159
+ })
160
+ .catch(() => {
161
+ if (manualCodeReject) {
162
+ manualCodeReject(new Error("Login cancelled"));
163
+ manualCodeReject = undefined;
164
+ }
165
+ });
166
+ } else if (providerId === "github-copilot") {
167
+ dialog.showWaiting("Waiting for browser authentication...");
168
+ }
169
+ },
170
+ onDeviceCode: (info) => {
171
+ dialog.showDeviceCode(info);
172
+ dialog.showWaiting("Waiting for authentication...");
173
+ },
174
+ onPrompt: async (prompt) => dialog.showPrompt(prompt.message, prompt.placeholder),
175
+ onProgress: (message) => dialog.showProgress(message),
176
+ onSelect: async (prompt) => {
177
+ const options = prompt.options
178
+ .map((option, index) => `${index + 1}. ${option.label}`)
179
+ .join("\n");
180
+ const answer = await dialog.showPrompt(
181
+ `${prompt.message}\n\n${options}`,
182
+ "Enter a number",
183
+ );
184
+ const selectedIndex = Number.parseInt(answer.trim(), 10) - 1;
185
+ return prompt.options[selectedIndex]?.id;
186
+ },
187
+ onManualCodeInput: () => manualCodePromise,
188
+ signal: dialog.signal,
189
+ })
176
190
  .then(() => finish(true))
177
191
  .catch((error) => {
178
192
  const message = error instanceof Error ? error.message : String(error);
@@ -248,7 +262,11 @@ async function activateDefaultModel(
248
262
  return true;
249
263
  }
250
264
 
251
- async function selectModel(api: ExtensionAPI, ctx: ExtensionContext, preferredProvider?: string): Promise<boolean> {
265
+ async function selectModel(
266
+ api: ExtensionAPI,
267
+ ctx: ExtensionContext,
268
+ preferredProvider?: string,
269
+ ): Promise<boolean> {
252
270
  const models = getAvailableModels(ctx, preferredProvider);
253
271
  if (models.length === 0) {
254
272
  ctx.ui.notify("No available models found yet. Connect a provider first.", "warning");
@@ -1,9 +1,9 @@
1
1
  import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
3
3
  import type { TSchema } from "@sinclair/typebox";
4
- import { getAllTools } from "../tools/index.js";
5
4
  import { getDefaults } from "../memory/tool-defaults.js";
6
5
  import { wrapWithDefaults } from "../runtime/tool-defaults-wrapper.js";
6
+ import { getAllTools } from "../tools/index.js";
7
7
 
8
8
  export function agentToolToPiTool<TParams extends TSchema, TDetails>(
9
9
  tool: AgentTool<TParams, TDetails>,
@@ -24,7 +24,10 @@ export function getOpenCandleToolDefinitions(): ToolDefinition[] {
24
24
  return getAllTools()
25
25
  .map((tool) => ({ tool, defaults: safeGetDefaults(tool.name) }))
26
26
  .filter(({ defaults }) => defaults.__enabled !== false)
27
- .map(({ tool, defaults }) => agentToolToPiTool(wrapWithDefaults(tool, defaults)));
27
+ .map(({ tool, defaults }) => {
28
+ const { __enabled: _enabled, ...paramDefaults } = defaults;
29
+ return agentToolToPiTool(wrapWithDefaults(tool, paramDefaults));
30
+ });
28
31
  }
29
32
 
30
33
  function safeGetDefaults(toolName: string): Record<string, unknown> {
@@ -1,7 +1,7 @@
1
- import type { PromptSection, SectionName } from "./sections.js";
2
- import { SECTION_ORDER, DEFAULT_BUDGETS, truncateTobudget } from "./sections.js";
3
- import { renderPolicyCardForPlanning } from "./policy-cards.js";
4
1
  import type { ResolvedTurnContext } from "../routing/turn-context.js";
2
+ import { renderPolicyCardForPlanning } from "./policy-cards.js";
3
+ import type { PromptSection, SectionName } from "./sections.js";
4
+ import { DEFAULT_BUDGETS, SECTION_ORDER, truncateTobudget } from "./sections.js";
5
5
 
6
6
  export interface PromptSectionReport {
7
7
  name: SectionName;
@@ -122,10 +122,7 @@ export class PromptContextBuilder {
122
122
  policyCard ? `${policyCard}\n\n${routePlaybook}` : routePlaybook,
123
123
  );
124
124
  } else if (options.fallbackContext) {
125
- this.setSection(
126
- "workflow-instructions",
127
- buildFallbackPlaybook(options.fallbackContext),
128
- );
125
+ this.setSection("workflow-instructions", buildFallbackPlaybook(options.fallbackContext));
129
126
  }
130
127
  if (options.memoryContext) {
131
128
  this.setSection("memory-context", formatMemorySection(options.memoryContext));
@@ -150,12 +147,26 @@ export function buildFallbackPlaybook(ctx: FallbackContext): string {
150
147
 
151
148
  export function buildRoutePlaybook(ctx: ResolvedTurnContext): string {
152
149
  const assumptionsBlock = buildResolvedAssumptionsBlock(ctx);
150
+ const droppedSymbols = ctx.diagnostics
151
+ .filter((diagnostic) => diagnostic.code === "symbol_dropped")
152
+ .map((diagnostic) => diagnostic.details?.token)
153
+ .filter((token): token is string => typeof token === "string" && token.length > 0);
154
+ const dropContext =
155
+ droppedSymbols.length > 0
156
+ ? `Dropped ambiguous ticker-like tokens: ${droppedSymbols.join(", ")}.`
157
+ : undefined;
158
+ const extraContext = [
159
+ ctx.entities.symbols.length > 0
160
+ ? `Router-extracted symbols: ${ctx.entities.symbols.join(", ")}. Route kind: ${ctx.routeKind}. Tool bundles: ${ctx.toolBundles.join(", ") || "(none)"}.`
161
+ : `Route kind: ${ctx.routeKind}. Tool bundles: ${ctx.toolBundles.join(", ") || "(none)"}.`,
162
+ dropContext,
163
+ ]
164
+ .filter((part): part is string => Boolean(part))
165
+ .join(" ");
153
166
  const fallbackContext: FallbackContext = {
154
167
  assumptionsBlock,
155
168
  missingRequired: ctx.missingRequired,
156
- extraContext: ctx.entities.symbols.length > 0
157
- ? `Router-extracted symbols: ${ctx.entities.symbols.join(", ")}. Route kind: ${ctx.routeKind}. Tool bundles: ${ctx.toolBundles.join(", ") || "(none)"}.`
158
- : `Route kind: ${ctx.routeKind}. Tool bundles: ${ctx.toolBundles.join(", ") || "(none)"}.`,
169
+ extraContext,
159
170
  };
160
171
 
161
172
  if (ctx.routeKind === "clarification") {
@@ -273,14 +284,14 @@ Keep questions concise and offer specific options when possible. Prefer select-t
273
284
  CRITICAL: After ask_user answers come back, your NEXT action MUST be tool calls — not a text response. You are a data agent, not a chatbot. Never respond with generic investment categories or tell the user to come back with tickers. YOU pick the relevant assets and indicators based on what you learned, then fetch the data.`;
274
285
 
275
286
  const TOOL_CATALOG = `## Available Tools
276
- - **Market Data**: get_stock_quote, get_stock_history, get_crypto_price, get_crypto_history — real-time and historical price data
287
+ - **Market Data**: screen_stocks, get_stock_quote, get_stock_history, get_crypto_price, get_crypto_history — use screen_stocks for breadth and screening prompts such as large-cap lists, oversold stocks, market movers, and filtered market scans; use get_stock_quote/get_stock_history for a single-security quote or historical OHLCV
277
288
  - **Fundamentals**: get_company_overview, get_financials, get_earnings, compute_dcf, compare_companies, get_sec_filings — company financials, valuation metrics, DCF intrinsic value, peer comparison, and SEC EDGAR filings (10-K, 10-Q, 8-K)
278
289
  - **Technical Analysis**: get_technical_indicators, backtest_strategy — SMA, EMA, RSI, MACD, Bollinger Bands, OBV, VWAP computed from price data, plus simple strategy backtesting
279
290
  - **Macro**: get_economic_data, get_fear_greed — FRED economic indicators and market sentiment
280
291
  - **Sentiment**: get_reddit_sentiment, get_twitter_sentiment, get_web_sentiment, get_sentiment_trend, get_sentiment_summary — retail and news sentiment from Reddit, Twitter/X, and web sources with historical trends and cross-source divergence detection
281
292
  - **Web Search**: search_web — breaking news, earnings context, company events, regulatory developments. Supported freshness values are hours, day, week, and month; use category general with freshness month for broad industry context; never pass unsupported values such as all, year, 3mo, quarter, or custom date ranges. When a dedicated tool can answer the question (quotes, fundamentals, earnings, macro, SEC filings, sentiment), use that tool instead — do not add search_web as a supplementary source for data available through dedicated tools
282
293
  - **Options**: get_option_chain — full options chain with strikes, bids/asks, volume, OI, IV, and computed Greeks (delta, gamma, theta, vega, rho)
283
- - **Portfolio**: track_portfolio, analyze_risk, manage_watchlist, analyze_correlation, analyze_holdings_overlap, track_prediction — position tracking, P&L, Sharpe ratio, VaR, watchlist with price alerts, correlation matrix, ETF/fund holdings overlap, and prediction tracking with accuracy scoring
294
+ - **Portfolio**: track_portfolio, analyze_risk, manage_watchlist, analyze_correlation, analyze_holdings_overlap, track_prediction, manage_alerts, daily_watchlist_report, manage_notifications — position tracking, P&L, Sharpe ratio, VaR, watchlist tracking, durable local alerts, daily watchlist reports, notification history, correlation matrix, ETF/fund holdings overlap, and prediction tracking with accuracy scoring
284
295
  - **User Interaction**: ask_user — ask the user a clarification question when their request is ambiguous or missing key details`;
285
296
 
286
297
  function buildToolCatalog(addonDescriptions?: string[]): string {
@@ -27,7 +27,7 @@ export const POLICY_CARD_IDS = [
27
27
  "general_fallback",
28
28
  ] as const;
29
29
 
30
- export type PromptPolicyCardId = typeof POLICY_CARD_IDS[number];
30
+ export type PromptPolicyCardId = (typeof POLICY_CARD_IDS)[number];
31
31
 
32
32
  export interface PolicyCard {
33
33
  id: PromptPolicyCardId;
@@ -133,7 +133,7 @@ For strategy backtest prompts, use backtest_strategy evidence before judging whe
133
133
  status: "implemented",
134
134
  capabilityGapIds: [],
135
135
  content: `## Stateful Tracking Update Policy
136
- For watchlist, portfolio tracking, prediction recording, and prediction-check prompts, use the state tool that owns the change or lookup: manage_watchlist, track_prediction, or track_portfolio. Do not confirm a saved change from prose alone. Confirm the persisted state update with the symbol, action, direction, entry price, target, stop, conviction, timeframe, or check result that the tool accepted. If required fields for a state mutation are missing, ask the smallest clarification question instead of inventing values. For check or list operations, summarize the saved state and say clearly when no records exist. Do not turn a state update into a buy/sell recommendation unless the user separately asks for market analysis.`,
136
+ For watchlist, portfolio tracking, alert management, daily-report, prediction recording, and prediction-check prompts, use the state tool that owns the change or lookup: manage_watchlist, track_prediction, track_portfolio, manage_alerts, or daily_watchlist_report. Do not confirm a saved change from prose alone. Confirm the persisted state update with the symbol, action, direction, entry price, target, stop, conviction, timeframe, or check result that the tool accepted. If the user asks to create an alert and check it now in the same request, call manage_alerts with the matching create action and check_after_create=true. If required fields for a state mutation are missing, ask the smallest clarification question instead of inventing values. For check or list operations, summarize the saved state and say clearly when no records exist. Do not turn a state update into a buy/sell recommendation unless the user separately asks for market analysis.`,
137
137
  },
138
138
  retail_finance_tradeoff: {
139
139
  id: "retail_finance_tradeoff",
@@ -32,7 +32,7 @@ export const SECTION_ORDER = [
32
32
  "output-format",
33
33
  ] as const;
34
34
 
35
- export type SectionName = typeof SECTION_ORDER[number];
35
+ export type SectionName = (typeof SECTION_ORDER)[number];
36
36
 
37
37
  /** Default character budgets per section. */
38
38
  export const DEFAULT_BUDGETS: Record<SectionName, number> = {
@@ -0,0 +1,80 @@
1
+ import { type InstrumentCandidate, searchYahooInstruments } from "../market-state/resolve.js";
2
+
3
+ export interface SymbolValidation {
4
+ valid: boolean;
5
+ reason?: string;
6
+ checkedAt: number;
7
+ }
8
+
9
+ export type SymbolValidationCache = Map<string, SymbolValidation>;
10
+
11
+ export interface SymbolPreflightDrop {
12
+ symbol: string;
13
+ reason: string;
14
+ }
15
+
16
+ export interface SymbolPreflightResult {
17
+ valid: string[];
18
+ dropped: SymbolPreflightDrop[];
19
+ }
20
+
21
+ export async function preflightSymbols(
22
+ symbols: string[],
23
+ options: {
24
+ cache?: SymbolValidationCache;
25
+ search?: (query: string) => Promise<InstrumentCandidate[]>;
26
+ } = {},
27
+ ): Promise<SymbolPreflightResult> {
28
+ const cache = options.cache;
29
+ const search = options.search ?? searchYahooInstruments;
30
+ const valid: string[] = [];
31
+ const dropped: SymbolPreflightDrop[] = [];
32
+
33
+ for (const rawSymbol of symbols) {
34
+ const symbol = rawSymbol.trim().toUpperCase();
35
+ if (!symbol) continue;
36
+
37
+ const cached = cache?.get(symbol);
38
+ const validation = cached ?? (await validateSymbol(symbol, search));
39
+ if (!cached) cache?.set(symbol, validation);
40
+
41
+ if (validation.valid) {
42
+ valid.push(symbol);
43
+ } else {
44
+ dropped.push({
45
+ symbol,
46
+ reason: validation.reason ?? "no matching ticker found via resolver search",
47
+ });
48
+ }
49
+ }
50
+
51
+ return { valid, dropped };
52
+ }
53
+
54
+ export function formatPreflightDropAnnotation(dropped: SymbolPreflightDrop[]): string {
55
+ const count = dropped.length;
56
+ const symbols = dropped.map((drop) => `${drop.symbol} (${drop.reason})`).join(", ");
57
+ return `[Pre-flight: dropped ${count} unknown symbol${count === 1 ? "" : "s"} - ${symbols}]`;
58
+ }
59
+
60
+ function validateSymbol(
61
+ symbol: string,
62
+ search: (query: string) => Promise<InstrumentCandidate[]>,
63
+ ): Promise<SymbolValidation> {
64
+ return search(symbol)
65
+ .then((candidates) => ({
66
+ valid: candidates.some((candidate) => candidate.symbol.toUpperCase() === symbol),
67
+ reason: candidates.some((candidate) => candidate.symbol.toUpperCase() === symbol)
68
+ ? undefined
69
+ : "no matching ticker found via resolver search",
70
+ checkedAt: Date.now(),
71
+ }))
72
+ .catch((error) => ({
73
+ valid: true,
74
+ reason:
75
+ error instanceof Error
76
+ ? `resolver search unavailable: ${error.message}`
77
+ : "resolver search unavailable",
78
+ checkedAt: Date.now(),
79
+ }));
80
+ }
@@ -1,12 +1,14 @@
1
+ import { parseDteTarget } from "../routing/defaults.js";
2
+ import { areLikelyFundOrIndexSymbols, isFundOrIndexAssetScope } from "../routing/fund-symbols.js";
3
+ import { isLongInvestmentHorizon } from "../routing/horizon.js";
4
+ import type { RouterOutput } from "../routing/router-types.js";
1
5
  import type {
2
- PortfolioSlots,
3
- OptionsScreenerSlots,
4
6
  CompareAssetsSlots,
7
+ OptionsScreenerSlots,
8
+ PortfolioSlots,
5
9
  SlotResolution,
6
10
  SlotSource,
7
11
  } from "../routing/types.js";
8
- import type { RouterOutput } from "../routing/router-types.js";
9
- import { parseDteTarget } from "../routing/defaults.js";
10
12
 
11
13
  function tag(source: string | undefined): string {
12
14
  switch (source) {
@@ -14,7 +16,6 @@ function tag(source: string | undefined): string {
14
16
  return " [DEFAULT]";
15
17
  case "preference":
16
18
  return " [SAVED PREFERENCE]";
17
- case "user":
18
19
  default:
19
20
  return "";
20
21
  }
@@ -96,8 +97,10 @@ export function buildDisclosureBlock(
96
97
  const lines: string[] = [];
97
98
  lines.push("Assumptions (reproduce this block exactly — do not relabel sources):");
98
99
  if (userSpecified.length > 0) lines.push(` User-specified: ${userSpecified.join(", ")}`);
99
- if (fromPreferences.length > 0) lines.push(` From saved preferences: ${fromPreferences.join(", ")}`);
100
- if (fromPriorContext.length > 0) lines.push(` From prior context: ${fromPriorContext.join(", ")}`);
100
+ if (fromPreferences.length > 0)
101
+ lines.push(` From saved preferences: ${fromPreferences.join(", ")}`);
102
+ if (fromPriorContext.length > 0)
103
+ lines.push(` From prior context: ${fromPriorContext.join(", ")}`);
101
104
  if (fromMemory.length > 0) lines.push(` From memory: ${fromMemory.join(", ")}`);
102
105
  if (defaults.length > 0) lines.push(` Defaults: ${defaults.join(", ")}`);
103
106
  if (workflowConstraints && workflowConstraints.length > 0) {
@@ -139,7 +142,10 @@ function formatSlotValue(value: unknown): string {
139
142
  export function buildPortfolioPrompt(resolution: SlotResolution<PortfolioSlots>): string {
140
143
  const { resolved: s, sources } = resolution;
141
144
  const normalizedScope = s.assetScope.toLowerCase();
142
- const isFundBuildingBlocks = normalizedScope.includes("etf") || normalizedScope.includes("fund") || normalizedScope.includes("building_blocks");
145
+ const isFundBuildingBlocks =
146
+ normalizedScope.includes("etf") ||
147
+ normalizedScope.includes("fund") ||
148
+ normalizedScope.includes("building_blocks");
143
149
 
144
150
  const disclosureBlock = buildDisclosureBlock(
145
151
  {
@@ -198,7 +204,9 @@ Response format:
198
204
  - Suggest what to change for more growth or more safety.`;
199
205
  }
200
206
 
201
- export function buildOptionsScreenerPrompt(resolution: SlotResolution<OptionsScreenerSlots>): string {
207
+ export function buildOptionsScreenerPrompt(
208
+ resolution: SlotResolution<OptionsScreenerSlots>,
209
+ ): string {
202
210
  const { resolved: s, sources } = resolution;
203
211
 
204
212
  const dateStr = todayStr();
@@ -225,14 +233,15 @@ Ranking constraints:
225
233
  - Do NOT rank ultra-cheap near-zero-delta contracts as "best."
226
234
  `
227
235
  : "";
228
- const longDatedInstructions = s.dteTarget === "180_plus_days"
229
- ? `
236
+ const longDatedInstructions =
237
+ s.dteTarget === "180_plus_days"
238
+ ? `
230
239
  For LEAPS / long-dated options:
231
240
  - First call get_option_chain without an expiration to inspect available expirations.
232
241
  - Choose available expirations inside the target window, then call get_option_chain again with explicit \`expiration\` dates before ranking contracts.
233
242
  - Do not rank the nearest-expiration chain as a LEAPS result.
234
243
  `
235
- : "";
244
+ : "";
236
245
 
237
246
  const disclosureBlock = buildDisclosureBlock(
238
247
  {
@@ -253,18 +262,26 @@ For LEAPS / long-dated options:
253
262
  );
254
263
 
255
264
  const coveredCallContext = [
256
- s.optionStrategy ? `\n- Option strategy: ${s.optionStrategy}${tag(sources.optionStrategy)}` : "",
257
- s.costBasis !== undefined ? `\n- Cost basis: ${formatBudget(s.costBasis)} (Position cost basis: ${formatBudget(s.costBasis)})${tag(sources.costBasis)}` : "",
258
- s.shareQuantity !== undefined ? `\n- Share quantity: ${s.shareQuantity} shares${tag(sources.shareQuantity)}` : "",
259
- s.catalystSymbols?.length ? `\n- Catalyst/context tickers: ${s.catalystSymbols.join(", ")}${tag(sources.catalystSymbols)}` : "",
265
+ s.optionStrategy
266
+ ? `\n- Option strategy: ${s.optionStrategy}${tag(sources.optionStrategy)}`
267
+ : "",
268
+ s.costBasis !== undefined
269
+ ? `\n- Cost basis: ${formatBudget(s.costBasis)} (Position cost basis: ${formatBudget(s.costBasis)})${tag(sources.costBasis)}`
270
+ : "",
271
+ s.shareQuantity !== undefined
272
+ ? `\n- Share quantity: ${s.shareQuantity} shares${tag(sources.shareQuantity)}`
273
+ : "",
274
+ s.catalystSymbols?.length
275
+ ? `\n- Catalyst/context tickers: ${s.catalystSymbols.join(", ")}${tag(sources.catalystSymbols)}`
276
+ : "",
260
277
  ].join("");
261
278
 
262
279
  const isProtectivePutContext = s.optionStrategy === "protective_put";
263
- const isCoveredCallContext = !isProtectivePutContext && (
264
- s.optionStrategy === "covered_call" ||
265
- s.costBasis !== undefined ||
266
- (s.catalystSymbols?.length ?? 0) > 0
267
- );
280
+ const isCoveredCallContext =
281
+ !isProtectivePutContext &&
282
+ (s.optionStrategy === "covered_call" ||
283
+ s.costBasis !== undefined ||
284
+ (s.catalystSymbols?.length ?? 0) > 0);
268
285
  const coveredCallInstructions = isCoveredCallContext
269
286
  ? `
270
287
  Covered-call sale guidance:
@@ -314,9 +331,17 @@ Steps:
314
331
  3. Filter contracts matching: ${s.direction === "bullish" && !isProtectivePutContext ? "calls" : "puts"}, DTE near ${s.dteTarget}, ${s.moneynessPreference} strikes.
315
332
  4. ${isProtectivePutContext ? "Rank by hedge quality: protection per dollar of premium, expiration fit, moneyness, liquidity, and hedge floor." : `Rank by ${s.objective}: balance premium cost, delta exposure, and probability of profit.`}${s.maxPremium !== undefined ? ` Do not rank contracts above the user's max premium of ${formatBudget(s.maxPremium)} unless no contracts under that cap are liquid; if so, say the cap could not be met.` : ""}
316
333
  5. Filter for ${s.liquidityMinimum}: high open interest and tight bid-ask spread.
317
- ${s.optionStrategy === "covered_call" ? `6. Covered call framing: treat option premium as premium received, not paid. Use the user's cost basis when provided, and include return-if-assigned and assignment/downside risk instead of long-call max-loss framing.
318
- ` : ""}${isCoveredCallContext && s.costBasis !== undefined ? `Cost-basis math: if assigned, share gain/loss is strike minus ${formatBudget(s.costBasis)} before premium. Total return if assigned is (strike - cost basis + premium received) / cost basis.
319
- ` : ""}
334
+ ${
335
+ s.optionStrategy === "covered_call"
336
+ ? `6. Covered call framing: treat option premium as premium received, not paid. Use the user's cost basis when provided, and include return-if-assigned and assignment/downside risk instead of long-call max-loss framing.
337
+ `
338
+ : ""
339
+ }${
340
+ isCoveredCallContext && s.costBasis !== undefined
341
+ ? `Cost-basis math: if assigned, share gain/loss is strike minus ${formatBudget(s.costBasis)} before premium. Total return if assigned is (strike - cost basis + premium received) / cost basis.
342
+ `
343
+ : ""
344
+ }
320
345
  ${longDatedInstructions}
321
346
  ${coveredCallInstructions}
322
347
  ${protectivePutInstructions}
@@ -336,10 +361,15 @@ export function buildCompareAssetsPrompt(resolution: SlotResolution<CompareAsset
336
361
  const symbols = resolution.resolved.symbols;
337
362
  const symbolList = symbols.join(", ");
338
363
  const timeHorizon = resolution.resolved.timeHorizon;
364
+ const budget = resolution.resolved.budget;
339
365
  const includeSentiment = resolution.resolved.metrics?.includes("sentiment") ?? false;
340
366
  const isMacroHedge = resolution.resolved.metrics?.includes("macro_hedge") ?? false;
341
367
  const isInterestRateSensitive = resolution.resolved.metrics?.includes("interest_rates") ?? false;
342
368
  const isOverlapComparison = resolution.resolved.metrics?.includes("overlap") ?? false;
369
+ const hasFundContext =
370
+ isFundOrIndexAssetScope(resolution.resolved.assetScope) || areLikelyFundOrIndexSymbols(symbols);
371
+ const shouldProbeFundOverlap =
372
+ !isOverlapComparison && isLongInvestmentHorizon(timeHorizon) && hasFundContext;
343
373
  const sentimentStep = includeSentiment
344
374
  ? `\n6. Use get_sentiment_summary for each of: ${symbolList} to compare retail/news sentiment and note source availability.`
345
375
  : "";
@@ -365,7 +395,16 @@ ETF overlap guidance:
365
395
  - Use provider top holdings and overlap weights when available. If provider coverage is partial or unavailable, say so directly and fall back to plain-language fund structure.
366
396
  - Discuss top holdings, shared mega-cap names, sector concentration, and whether the position is a deliberate tilt or accidental duplication.
367
397
  - avoid treating price, RSI, or generic risk metrics as the main answer.`
368
- : "";
398
+ : shouldProbeFundOverlap
399
+ ? `
400
+ ETF/fund overlap check:
401
+ - If these assets are ETFs, funds, or index products, use provider-backed holdings-overlap evidence before making diversification claims.
402
+ - Compare fund role, style/factor tilt, concentration, and broad sector exposure when available; do not invent exact holdings or weights.
403
+ - For dividend/income funds versus growth funds over multi-year horizons, explain taxable account dividend drag: dividends can be taxed annually even when reinvested, while more return may be deferred as capital gains in growth-oriented funds. Contrast that with tax-advantaged accounts.
404
+ - Include expense ratios, dividend yields, and AUM only when fetched evidence supports them; otherwise tell the user to verify current fund facts before acting.
405
+ - Treat holdings overlap and sector concentration as different from correlation; correlation is supporting evidence, not a substitute for constituent exposure.
406
+ - If provider holdings coverage is partial or unavailable, say so directly and continue with the available price, risk, and correlation evidence.`
407
+ : "";
369
408
  const macroHedgeSteps = isMacroHedge
370
409
  ? `
371
410
  macro hedge decision guidance:
@@ -380,16 +419,24 @@ macro hedge decision guidance:
380
419
  ? "- Present a comparison table with hedge-relevant columns: hedge role, macro drivers, volatility/drawdown evidence, correlation regime, liquidity/risk-on sensitivity, current data, and missing evidence."
381
420
  : isOverlapComparison
382
421
  ? "- Present an ETF overlap table with columns: fund role, shared top holdings/overlap weight from provider when available, sector concentration, what exposure is duplicated, what exposure is new, and diversification implication."
383
- : `- Present a comparison table with key metrics: price, P/E, revenue growth, profit margin, RSI, Sharpe, max drawdown${sentimentMetric}.
422
+ : shouldProbeFundOverlap
423
+ ? "- Present a long-horizon fund comparison table with columns: fund role/style, dividend/income versus growth tradeoff, risk evidence, holdings-overlap availability, tax and expense/yield/AUM verification gaps, and horizon fit."
424
+ : `- Present a comparison table with key metrics: price, P/E, revenue growth, profit margin, RSI, Sharpe, max drawdown${sentimentMetric}.
384
425
  - Highlight which asset is stronger on each metric.`;
385
426
  const technicalRiskSteps = isOverlapComparison
386
427
  ? `3. Use analyze_holdings_overlap with symbols [${symbolList}] to fetch provider top holdings and compute pairwise overlap by weight.
387
428
  4. Use analyze_correlation across [${symbolList}] only as supporting diversification evidence; do not substitute correlation for holdings overlap.
388
429
  5. Skip momentum/risk tool calls unless the user asks about timing or trade setup; the core question is top holdings and sector overlap.`
389
- : `3. Use get_technical_indicators for each to compare momentum and trend.
430
+ : shouldProbeFundOverlap
431
+ ? `3. Use analyze_holdings_overlap with symbols [${symbolList}] to fetch provider top holdings and compute pairwise overlap by weight.
432
+ 4. Use analyze_correlation across [${symbolList}] as supporting diversification evidence.
433
+ 5. Use analyze_risk for each to compare long-horizon risk context.
434
+ 6. Use get_technical_indicators only as secondary timing context; do not let RSI or short-term momentum dominate the long-horizon fund decision.`
435
+ : `3. Use get_technical_indicators for each to compare momentum and trend.
390
436
  4. Use analyze_risk for each to compare risk metrics.
391
437
  5. Use analyze_correlation across [${symbolList}] to check diversification.`;
392
438
  const horizonLine = timeHorizon ? `\nTime horizon: ${timeHorizon}` : "";
439
+ const budgetLine = budget !== undefined ? `\nBudget: ${formatBudget(budget)}` : "";
393
440
  const horizonSteps = timeHorizon
394
441
  ? `
395
442
  6. Adapt the comparison to the ${timeHorizon} horizon: prioritize near-term catalysts, earnings/guidance, estimate revisions, sentiment, and forward-looking valuation evidence over long-term historical averages.
@@ -406,6 +453,8 @@ macro hedge decision guidance:
406
453
  {
407
454
  symbols: symbolList,
408
455
  ...(timeHorizon ? { timeHorizon } : {}),
456
+ ...(budget !== undefined ? { budget: formatBudget(budget) } : {}),
457
+ ...(resolution.resolved.assetScope ? { assetScope: resolution.resolved.assetScope } : {}),
409
458
  ...(resolution.resolved.metrics ? { metrics: resolution.resolved.metrics.join(", ") } : {}),
410
459
  },
411
460
  resolution.sources as Record<string, SlotSource | undefined>,
@@ -413,7 +462,7 @@ macro hedge decision guidance:
413
462
 
414
463
  return `Current date: ${todayStr()}
415
464
 
416
- Compare these assets side by side: ${symbolList}${horizonLine}
465
+ Compare these assets side by side: ${symbolList}${horizonLine}${budgetLine}
417
466
 
418
467
  Steps:
419
468
  1. Use get_stock_quote for each of: ${symbolList}.