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
@@ -4,16 +4,26 @@ interface RunContext {
4
4
  providerTracker: ProviderTracker;
5
5
  }
6
6
 
7
+ export interface RunContextToken {
8
+ readonly id: symbol;
9
+ }
10
+
7
11
  let activeContext: RunContext | null = null;
12
+ let activeToken: RunContextToken | null = null;
8
13
 
9
14
  /** Set the active run context. Called by SessionCoordinator at workflow start. */
10
- export function setRunContext(ctx: RunContext): void {
15
+ export function setRunContext(ctx: RunContext): RunContextToken {
16
+ const token = { id: Symbol("run-context") };
11
17
  activeContext = ctx;
18
+ activeToken = token;
19
+ return token;
12
20
  }
13
21
 
14
22
  /** Clear the active run context. Called when a workflow ends or is cancelled. */
15
- export function clearRunContext(): void {
23
+ export function clearRunContext(token?: RunContextToken): void {
24
+ if (token && activeToken !== token) return;
16
25
  activeContext = null;
26
+ activeToken = null;
17
27
  }
18
28
 
19
29
  /** Get the current run's ProviderTracker, or undefined outside a workflow. */
@@ -1,4 +1,9 @@
1
- import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionCommandContext,
4
+ ExtensionContext,
5
+ SessionEntry,
6
+ } from "@earendil-works/pi-coding-agent";
2
7
  import { getAllDefaults, initDefaultDatabase, MemoryStorage } from "../memory/index.js";
3
8
 
4
9
  /**
@@ -8,22 +13,34 @@ import { getAllDefaults, initDefaultDatabase, MemoryStorage } from "../memory/in
8
13
  * the shape we need from the public `ExtensionContext` type.
9
14
  */
10
15
  type ReadonlySessionManager = ExtensionContext["sessionManager"];
16
+
17
+ import type Database from "better-sqlite3";
18
+ import { MarketStateService } from "../market-state/service.js";
19
+ import type { FilteredMemoryEntry } from "../memory/manager.js";
11
20
  import { MemoryManager } from "../memory/manager.js";
12
21
  import { extractPreferences } from "../memory/preference-extractor.js";
22
+ import type { MemoryEntry } from "../memory/types.js";
13
23
  import { runOpenCandleSetup } from "../pi/setup.js";
14
- import { WorkflowEventLogger } from "./workflow-events.js";
15
- import { ProviderTracker } from "./provider-tracker.js";
16
- import { WorkflowRunner } from "./workflow-runner.js";
17
- import { setRunContext, clearRunContext } from "./run-context.js";
18
- import { PromptContextBuilder, type FallbackContext } from "../prompts/context-builder.js";
24
+ import { type FallbackContext, PromptContextBuilder } from "../prompts/context-builder.js";
25
+ import type { SymbolValidationCache } from "../prompts/symbol-preflight.js";
26
+ import type { RouterRouteKind } from "../routing/router-types.js";
27
+ import type { ResolvedTurnContext } from "../routing/turn-context.js";
19
28
  import { getAddonToolDescriptions } from "../tool-kit.js";
20
29
  import type { WorkflowDefinition } from "./prompt-step.js";
21
- import { toStepDefinitions, promptStepOutput } from "./prompt-step.js";
22
- import type Database from "better-sqlite3";
30
+ import { promptStepOutput, toStepDefinitions } from "./prompt-step.js";
31
+ import { ProviderTracker } from "./provider-tracker.js";
32
+ import { clearRunContext, type RunContextToken, setRunContext } from "./run-context.js";
33
+ import { WorkflowEventLogger } from "./workflow-events.js";
34
+ import { WorkflowRunner } from "./workflow-runner.js";
23
35
 
24
36
  const PROMPT_SETTLE_POLL_MS = 25;
25
37
  const IMMEDIATE_IDLE_GRACE_MS = 100;
26
38
 
39
+ interface ActiveWorkflowRunRef {
40
+ active: boolean;
41
+ contextToken: RunContextToken;
42
+ }
43
+
27
44
  function parseMaybeJson(raw: unknown): Record<string, unknown> | undefined {
28
45
  if (typeof raw !== "string" || raw.length === 0) return undefined;
29
46
  try {
@@ -36,11 +53,13 @@ function parseMaybeJson(raw: unknown): Record<string, unknown> | undefined {
36
53
  }
37
54
  }
38
55
 
39
- type QueueContext = ExtensionCommandContext | {
40
- isIdle(): boolean;
41
- hasPendingMessages?(): boolean;
42
- ui?: { notify(message: string, level?: string): void };
43
- };
56
+ type QueueContext =
57
+ | ExtensionCommandContext
58
+ | {
59
+ isIdle(): boolean;
60
+ hasPendingMessages?(): boolean;
61
+ ui?: { notify(message: string, level?: string): void };
62
+ };
44
63
 
45
64
  function hasPendingMessages(ctx: QueueContext): boolean {
46
65
  return ctx.hasPendingMessages?.() ?? false;
@@ -57,6 +76,7 @@ function sleep(ms: number): Promise<void> {
57
76
  async function waitForPromptSettlement(
58
77
  ctx: QueueContext,
59
78
  isCurrentRun: () => boolean,
79
+ options: { requireActivity?: boolean } = {},
60
80
  ): Promise<boolean> {
61
81
  let sawBusyOrPending = !isReadyForNextPrompt(ctx);
62
82
  const startedAt = Date.now();
@@ -71,7 +91,12 @@ async function waitForPromptSettlement(
71
91
  return true;
72
92
  }
73
93
 
74
- if (!sawBusyOrPending && ready && Date.now() - startedAt >= IMMEDIATE_IDLE_GRACE_MS) {
94
+ if (
95
+ !options.requireActivity &&
96
+ !sawBusyOrPending &&
97
+ ready &&
98
+ Date.now() - startedAt >= IMMEDIATE_IDLE_GRACE_MS
99
+ ) {
75
100
  return true;
76
101
  }
77
102
 
@@ -92,6 +117,9 @@ export class SessionCoordinator {
92
117
  private eventLogger: WorkflowEventLogger | null = null;
93
118
  private runner: WorkflowRunner;
94
119
  private providerTracker: ProviderTracker;
120
+ private activeWorkflowRunRef: ActiveWorkflowRunRef | null = null;
121
+ private activeWorkflowType: string | undefined;
122
+ private tickerValidationCache: SymbolValidationCache = new Map();
95
123
  private sessionId = "unknown";
96
124
 
97
125
  constructor() {
@@ -108,6 +136,14 @@ export class SessionCoordinator {
108
136
  return this.runner;
109
137
  }
110
138
 
139
+ getTickerValidationCache(): SymbolValidationCache {
140
+ return this.tickerValidationCache;
141
+ }
142
+
143
+ clearTickerValidationCache(): void {
144
+ this.tickerValidationCache.clear();
145
+ }
146
+
111
147
  /** Initialize session: database, memory, event logger, workflow runner. */
112
148
  initSession(sessionId: string): void {
113
149
  this.db = initDefaultDatabase();
@@ -150,7 +186,7 @@ export class SessionCoordinator {
150
186
  entities: object,
151
187
  resolved: object,
152
188
  defaultsUsed: unknown[],
153
- turnType: "workflow" | "fallback" = "workflow",
189
+ turnType = "workflow",
154
190
  ): void {
155
191
  this.storage?.insertWorkflowRun({
156
192
  sessionId: this.sessionId,
@@ -263,34 +299,56 @@ export class SessionCoordinator {
263
299
  return { profileSnapshot, recentWorkflowRuns: runs, priorTurns };
264
300
  }
265
301
 
302
+ retrieveMemoryForRoute(
303
+ routeKind: RouterRouteKind,
304
+ workflowType?: string,
305
+ overriddenSlots?: string[],
306
+ ): { entries: MemoryEntry[]; filtered: FilteredMemoryEntry[] } {
307
+ if (!this.memoryManager) return { entries: [], filtered: [] };
308
+ return this.memoryManager.retrieveDetailed(workflowType ?? routeKind, overriddenSlots);
309
+ }
310
+
266
311
  /** Build system prompt using composable sections. */
267
312
  buildSystemPrompt(
268
313
  basePrompt: string,
269
314
  workflowType?: string,
270
315
  fallbackContext?: FallbackContext,
316
+ resolvedTurnContext?: ResolvedTurnContext,
271
317
  ): string {
272
318
  const builder = new PromptContextBuilder();
273
319
 
274
320
  const addonTools = getAddonToolDescriptions();
275
- const addonDescriptions = addonTools.length > 0
276
- ? addonTools.map((t) => `${t.name}: ${t.description}`)
277
- : undefined;
321
+ const addonDescriptions =
322
+ addonTools.length > 0 ? addonTools.map((t) => `${t.name}: ${t.description}`) : undefined;
278
323
 
279
324
  const memoryContext = this.memoryManager
280
- ? this.memoryManager.buildContext(workflowType ?? "unclassified")
325
+ ? this.memoryManager.buildContext(
326
+ resolvedTurnContext?.workflow ??
327
+ workflowType ??
328
+ resolvedTurnContext?.routeKind ??
329
+ "unclassified",
330
+ )
281
331
  : undefined;
332
+ const savedMarketStateContext =
333
+ this.db &&
334
+ shouldIncludeSavedMarketStateContext(workflowType, resolvedTurnContext, fallbackContext)
335
+ ? buildSavedMarketStateContext(this.db)
336
+ : "";
337
+ const combinedMemoryContext = [savedMarketStateContext, memoryContext]
338
+ .filter((part) => part && part.trim().length > 0)
339
+ .join("\n\n");
282
340
 
283
341
  builder.populateFromOptions({
284
342
  workflowType,
285
- memoryContext: memoryContext || undefined,
343
+ memoryContext: combinedMemoryContext || undefined,
286
344
  addonToolDescriptions: addonDescriptions,
287
345
  fallbackContext,
346
+ resolvedTurnContext,
288
347
  });
289
348
 
290
349
  const toolDefaults = formatToolDefaultsForPrompt();
291
- const defaultsSection = toolDefaults.length > 0
292
- ? `\n\n## User Tool Defaults\n${toolDefaults.join("\n")}`
293
- : "";
350
+ const defaultsSection =
351
+ toolDefaults.length > 0 ? `\n\n## User Tool Defaults\n${toolDefaults.join("\n")}` : "";
294
352
 
295
353
  return `${basePrompt}\n\n${builder.build()}${defaultsSection}`;
296
354
  }
@@ -301,71 +359,126 @@ export class SessionCoordinator {
301
359
  * subsequent turns do not inherit stale fallback directives.
302
360
  */
303
361
  private pendingFallbackContext: FallbackContext | null = null;
362
+ private pendingResolvedTurnContext: ResolvedTurnContext | null = null;
304
363
 
305
364
  setPendingFallbackContext(ctx: FallbackContext | null): void {
306
365
  this.pendingFallbackContext = ctx;
307
366
  }
308
367
 
368
+ setPendingResolvedTurnContext(ctx: ResolvedTurnContext | null): void {
369
+ this.pendingResolvedTurnContext = ctx;
370
+ }
371
+
309
372
  consumePendingFallbackContext(): FallbackContext | null {
310
373
  const ctx = this.pendingFallbackContext;
311
374
  this.pendingFallbackContext = null;
312
375
  return ctx;
313
376
  }
314
377
 
378
+ consumePendingResolvedTurnContext(): ResolvedTurnContext | null {
379
+ const ctx = this.pendingResolvedTurnContext;
380
+ this.pendingResolvedTurnContext = null;
381
+ return ctx;
382
+ }
383
+
315
384
  /**
316
385
  * Execute a workflow definition through the WorkflowRunner,
317
386
  * sending prompts via Pi with settlement-based sequencing.
318
387
  */
319
- executeWorkflow(
388
+ executeWorkflow(pi: ExtensionAPI, definition: WorkflowDefinition, ctx: QueueContext): void {
389
+ this.startWorkflowRun(pi, definition, ctx, "send");
390
+ }
391
+
392
+ /**
393
+ * Start workflow tracking for an input handler that will return a Pi
394
+ * transform result. The current prompt becomes the first workflow prompt;
395
+ * only later steps are sent through Pi.
396
+ */
397
+ /** Workflow type of the in-flight run, for prompt context on workflow turns. */
398
+ getActiveWorkflowType(): string | undefined {
399
+ return this.activeWorkflowRunRef?.active ? this.activeWorkflowType : undefined;
400
+ }
401
+
402
+ transformWorkflowInput(
320
403
  pi: ExtensionAPI,
321
404
  definition: WorkflowDefinition,
322
405
  ctx: QueueContext,
406
+ ): string | undefined {
407
+ if (definition.steps.length === 0) return undefined;
408
+ this.startWorkflowRun(pi, definition, ctx, "transform");
409
+ return definition.steps[0].prompt;
410
+ }
411
+
412
+ private startWorkflowRun(
413
+ pi: ExtensionAPI,
414
+ definition: WorkflowDefinition,
415
+ ctx: QueueContext,
416
+ firstPromptMode: "send" | "transform",
323
417
  ): void {
324
418
  if (definition.steps.length === 0) return;
325
419
 
326
420
  const runner = this.runner;
327
- const runRef = { active: true };
421
+ if (this.activeWorkflowRunRef) {
422
+ this.activeWorkflowRunRef.active = false;
423
+ }
424
+ runner.cancel();
425
+ this.activeWorkflowType = definition.workflowType;
328
426
 
329
- // Send the first prompt immediately
330
- const [firstStep, ...restSteps] = definition.steps;
331
- const startedBusy = !isReadyForNextPrompt(ctx);
427
+ const [firstStep] = definition.steps;
332
428
 
333
- if (startedBusy) {
334
- pi.sendUserMessage(firstStep.prompt, { deliverAs: "followUp" });
335
- ctx.ui?.notify?.("Analysis queued as follow-up.", "info");
336
- } else {
337
- pi.sendUserMessage(firstStep.prompt);
429
+ if (firstPromptMode === "send") {
430
+ const startedBusy = !isReadyForNextPrompt(ctx);
431
+ if (startedBusy) {
432
+ pi.sendUserMessage(firstStep.prompt, { deliverAs: "followUp" });
433
+ ctx.ui?.notify?.("Analysis queued as follow-up.", "info");
434
+ } else {
435
+ pi.sendUserMessage(firstStep.prompt);
436
+ }
338
437
  }
339
438
 
340
439
  // Make the run's ProviderTracker accessible to tools during execution
341
- setRunContext({ providerTracker: this.providerTracker });
440
+ const contextToken = setRunContext({ providerTracker: this.providerTracker });
441
+ const runRef: ActiveWorkflowRunRef = { active: true, contextToken };
442
+ this.activeWorkflowRunRef = runRef;
342
443
 
343
444
  // Start the runner in the background for state tracking
344
445
  const stepDefs = toStepDefinitions(definition.steps);
345
- void runner.start(definition.workflowType, stepDefs, async (step, stepIndex) => {
346
- // First step was already sent above — just wait for settlement
347
- if (stepIndex > 0) {
348
- const settled = await waitForPromptSettlement(ctx, () => runRef.active);
349
- if (!settled || !runRef.active) {
350
- throw new Error("run_cancelled");
446
+ void runner
447
+ .start(definition.workflowType, stepDefs, async (step, stepIndex) => {
448
+ // First step was already sent above — just wait for settlement
449
+ if (stepIndex > 0) {
450
+ const settled = await waitForPromptSettlement(ctx, () => runRef.active);
451
+ if (!settled || !runRef.active) {
452
+ throw new Error("run_cancelled");
453
+ }
454
+ pi.sendUserMessage(definition.steps[stepIndex].prompt);
455
+ } else {
456
+ // For the first step, just wait for it to settle
457
+ const settled = await waitForPromptSettlement(ctx, () => runRef.active, {
458
+ requireActivity: firstPromptMode === "transform",
459
+ });
460
+ if (!settled || !runRef.active) {
461
+ throw new Error("run_cancelled");
462
+ }
351
463
  }
352
- pi.sendUserMessage(definition.steps[stepIndex].prompt);
353
- } else {
354
- // For the first step, just wait for it to settle
355
- const settled = await waitForPromptSettlement(ctx, () => runRef.active);
356
- if (!settled || !runRef.active) {
357
- throw new Error("run_cancelled");
464
+ return promptStepOutput(stepIndex, step.stepType);
465
+ })
466
+ .finally(() => {
467
+ clearRunContext(runRef.contextToken);
468
+ if (this.activeWorkflowRunRef === runRef) {
469
+ this.activeWorkflowRunRef = null;
358
470
  }
359
- }
360
- return promptStepOutput(stepIndex, step.stepType);
361
- }).finally(() => {
362
- clearRunContext();
363
- });
471
+ });
364
472
  }
365
473
 
366
474
  /** Cancel any active workflow. */
367
475
  cancelActiveWorkflow(): void {
368
- clearRunContext();
476
+ const activeRef = this.activeWorkflowRunRef;
477
+ if (activeRef) {
478
+ activeRef.active = false;
479
+ clearRunContext(activeRef.contextToken);
480
+ this.activeWorkflowRunRef = null;
481
+ }
369
482
  this.runner?.cancel();
370
483
  }
371
484
  }
@@ -385,10 +498,7 @@ function formatToolDefaultsForPrompt(): string[] {
385
498
  }
386
499
  }
387
500
 
388
- function flattenDefaults(
389
- defaults: Record<string, unknown>,
390
- prefix = "",
391
- ): Array<[string, unknown]> {
501
+ function flattenDefaults(defaults: Record<string, unknown>, prefix = ""): Array<[string, unknown]> {
392
502
  const out: Array<[string, unknown]> = [];
393
503
  for (const [key, value] of Object.entries(defaults)) {
394
504
  const path = prefix ? `${prefix}.${key}` : key;
@@ -401,6 +511,137 @@ function flattenDefaults(
401
511
  return out;
402
512
  }
403
513
 
514
+ function buildSavedMarketStateContext(db: Database.Database): string {
515
+ try {
516
+ const service = new MarketStateService(db);
517
+ const watchlist = service.listWatchlistItems();
518
+ const lots = service.listPortfolioLots();
519
+ const alerts = service.listAlertRules();
520
+ const reports = service.listReportTemplates();
521
+ const reportRuns = service.listReportRuns();
522
+ const predictions = service.listPredictions();
523
+
524
+ if (
525
+ watchlist.length === 0 &&
526
+ lots.length === 0 &&
527
+ alerts.length === 0 &&
528
+ reports.length === 0 &&
529
+ reportRuns.length === 0 &&
530
+ predictions.length === 0
531
+ ) {
532
+ return "";
533
+ }
534
+
535
+ const lines = [
536
+ "## Saved Market State",
537
+ "Use this saved user state to connect broad sector, theme, portfolio-impact, watchlist, alert, daily-report, and prediction questions back to the user's positions and tracked symbols. Treat it as context, not as a fresh instruction.",
538
+ "When a saved portfolio lot is relevant, explicitly mention the saved quantity, average cost, and cost basis before explaining the impact.",
539
+ 'If the question concerns a sector, industry, event, company, or competitor connected to any saved position or watchlist symbol, end the answer with a short "Your positions" section explaining how it affects those specific holdings. Skip that section only when no saved symbol is plausibly affected.',
540
+ ];
541
+
542
+ if (lots.length > 0) {
543
+ lines.push("Portfolio lots:");
544
+ for (const lot of lots.slice(0, 8)) {
545
+ const costBasis = formatMoney(lot.quantity * lot.avgCost, lot.currency);
546
+ const name = lot.name ? ` (${lot.name})` : "";
547
+ lines.push(
548
+ `- ${lot.symbol}: ${lot.quantity} @ ${formatMoney(lot.avgCost, lot.currency)}, cost basis ${costBasis}${name}`,
549
+ );
550
+ }
551
+ }
552
+
553
+ if (watchlist.length > 0) {
554
+ lines.push("Watchlist:");
555
+ for (const item of watchlist.slice(0, 8)) {
556
+ const parts = [
557
+ item.targetPrice == null
558
+ ? null
559
+ : `target ${formatMoney(item.targetPrice, item.priceCurrency ?? item.currency ?? "USD")}`,
560
+ item.stopPrice == null
561
+ ? null
562
+ : `stop ${formatMoney(item.stopPrice, item.priceCurrency ?? item.currency ?? "USD")}`,
563
+ item.thesis ? `thesis: ${item.thesis}` : null,
564
+ item.tags && item.tags.length > 0 ? `tags: ${item.tags.join(", ")}` : null,
565
+ item.notes ? `notes: ${item.notes}` : null,
566
+ ].filter((part): part is string => part != null);
567
+ lines.push(
568
+ `- ${item.symbol}${item.name ? ` (${item.name})` : ""}${parts.length > 0 ? ` — ${parts.join("; ")}` : ""}`,
569
+ );
570
+ }
571
+ }
572
+
573
+ if (alerts.length > 0) {
574
+ lines.push("Alert rules:");
575
+ for (const rule of alerts.slice(0, 8)) {
576
+ const instrument =
577
+ rule.instrumentId == null ? null : service.getInstrument(rule.instrumentId);
578
+ lines.push(
579
+ `- #${rule.id} ${instrument?.symbol ?? rule.scopeType}: ${rule.conditionType} ${formatJsonSummary(rule.conditionJson)} (${rule.enabled ? "enabled" : "disabled"})`,
580
+ );
581
+ }
582
+ }
583
+
584
+ if (reports.length > 0) {
585
+ lines.push("Report templates:");
586
+ for (const report of reports.slice(0, 5)) {
587
+ lines.push(
588
+ `- ${report.name}: ${report.reportType}, ${report.cadence} at ${report.localTime} ${report.timezone} (${report.enabled ? "enabled" : "disabled"})`,
589
+ );
590
+ }
591
+ }
592
+
593
+ if (reportRuns.length > 0) {
594
+ const latest = reportRuns[0];
595
+ lines.push(
596
+ `Latest report run: ${latest.status} at ${latest.completedAt ?? latest.startedAt}`,
597
+ );
598
+ }
599
+
600
+ if (predictions.length > 0) {
601
+ lines.push("Predictions:");
602
+ for (const prediction of predictions.slice(0, 8)) {
603
+ const target =
604
+ prediction.targetPrice == null
605
+ ? ""
606
+ : ` target ${formatMoney(prediction.targetPrice, "USD")}`;
607
+ lines.push(
608
+ `- #${prediction.id} ${prediction.symbol}: ${prediction.direction} conv ${prediction.conviction}/10 from ${formatMoney(prediction.entryPrice, "USD")}${target}, status ${prediction.status}, expires ${prediction.expiresAt}`,
609
+ );
610
+ }
611
+ }
612
+
613
+ return lines.join("\n");
614
+ } catch {
615
+ return "";
616
+ }
617
+ }
618
+
619
+ function shouldIncludeSavedMarketStateContext(
620
+ workflowType: string | undefined,
621
+ resolvedTurnContext: ResolvedTurnContext | undefined,
622
+ fallbackContext: FallbackContext | undefined,
623
+ ): boolean {
624
+ if (resolvedTurnContext) {
625
+ return resolvedTurnContext.routeKind !== "pass_through";
626
+ }
627
+ // A pending fallback context only exists for routed finance turns (rules-mode
628
+ // general finance or LLM-router fallback), never for pass-through prompts.
629
+ return workflowType != null || fallbackContext != null;
630
+ }
631
+
632
+ function formatMoney(value: number, currency: string): string {
633
+ const normalized = currency.toUpperCase();
634
+ if (normalized === "USD") return `$${value.toFixed(2)}`;
635
+ return `${normalized} ${value.toFixed(2)}`;
636
+ }
637
+
638
+ function formatJsonSummary(value: unknown): string {
639
+ if (value == null) return "";
640
+ const json = JSON.stringify(value);
641
+ if (json.length <= 90) return json;
642
+ return `${json.slice(0, 87)}...`;
643
+ }
644
+
404
645
  function isPlainObject(value: unknown): value is Record<string, unknown> {
405
646
  return typeof value === "object" && value !== null && !Array.isArray(value);
406
647
  }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * LLM-summarized session titles.
3
+ *
4
+ * Builds a strict short-title prompt from the user's first message (plus an
5
+ * optional slice of the first assistant reply), delegates to an injected
6
+ * completion function, and sanitizes the result. Returns null when the model
7
+ * output is unusable (empty or rambling) so callers can keep the existing
8
+ * placeholder name. Completion errors propagate to the caller.
9
+ */
10
+
11
+ const MAX_TITLE_CHARS = 60;
12
+ const MAX_TITLE_WORDS = 12;
13
+
14
+ export interface SessionTitleInput {
15
+ userText: string;
16
+ assistantText?: string;
17
+ }
18
+
19
+ export async function generateSessionTitle(
20
+ input: SessionTitleInput,
21
+ complete: (prompt: string) => Promise<string>,
22
+ ): Promise<string | null> {
23
+ const lines = [
24
+ "Write a 4-8 word title for this finance chat session.",
25
+ "No quotes, no punctuation at the end, no markdown. Return only the title.",
26
+ "",
27
+ `User: ${input.userText}`,
28
+ ];
29
+ if (input.assistantText !== undefined && input.assistantText.trim().length > 0) {
30
+ lines.push("", `Assistant: ${input.assistantText}`);
31
+ }
32
+ const raw = await complete(lines.join("\n"));
33
+ return sanitizeTitle(raw);
34
+ }
35
+
36
+ function sanitizeTitle(raw: string): string | null {
37
+ // First non-empty line only — discard any model commentary after a newline.
38
+ let title =
39
+ raw
40
+ .split("\n")
41
+ .map((line) => line.trim())
42
+ .find((line) => line.length > 0) ?? "";
43
+ // Strip markdown emphasis/heading/code characters.
44
+ title = title.replace(/[*_`#]/g, "");
45
+ // Strip surrounding quotes.
46
+ title = title.replace(/^["'“”‘’]+/, "").replace(/["'“”‘’]+$/, "");
47
+ // Strip trailing punctuation.
48
+ title = title.replace(/[.!?:;,]+$/, "");
49
+ // Collapse whitespace.
50
+ title = title.replace(/\s+/g, " ").trim();
51
+
52
+ if (title.length === 0) return null;
53
+ if (title.split(" ").length > MAX_TITLE_WORDS) return null;
54
+ if (title.length > MAX_TITLE_CHARS) {
55
+ const cut = title.slice(0, MAX_TITLE_CHARS);
56
+ const lastSpace = cut.lastIndexOf(" ");
57
+ title = (lastSpace > 0 ? cut.slice(0, lastSpace) : cut).trim();
58
+ }
59
+ return title;
60
+ }
@@ -23,9 +23,7 @@ function mergeDefaults(
23
23
  const out: Record<string, unknown> = { ...defaults };
24
24
  for (const [key, value] of Object.entries(args)) {
25
25
  const base = out[key];
26
- out[key] = isPlainObject(base) && isPlainObject(value)
27
- ? mergeDefaults(base, value)
28
- : value;
26
+ out[key] = isPlainObject(base) && isPlainObject(value) ? mergeDefaults(base, value) : value;
29
27
  }
30
28
  return out;
31
29
  }
@@ -41,10 +41,7 @@ export function checkTimestamps(
41
41
  }
42
42
 
43
43
  /** Check that options expiry dates are in the future. */
44
- export function checkOptionsExpiries(
45
- evidence: EvidenceRecord[],
46
- today: string,
47
- ): ValidationEntry[] {
44
+ export function checkOptionsExpiries(evidence: EvidenceRecord[], today: string): ValidationEntry[] {
48
45
  const failures: ValidationEntry[] = [];
49
46
  for (const record of evidence) {
50
47
  if (
@@ -55,13 +55,13 @@ export class WorkflowEventLogger {
55
55
  "SELECT id, run_id, step_index, event_type, payload_json, timestamp FROM workflow_events WHERE run_id = ? ORDER BY id",
56
56
  )
57
57
  .all(runId) as Array<{
58
- id: number;
59
- run_id: string;
60
- step_index: number;
61
- event_type: string;
62
- payload_json: string | null;
63
- timestamp: string;
64
- }>;
58
+ id: number;
59
+ run_id: string;
60
+ step_index: number;
61
+ event_type: string;
62
+ payload_json: string | null;
63
+ timestamp: string;
64
+ }>;
65
65
 
66
66
  return rows.map((r) => ({
67
67
  id: r.id,
@@ -1,11 +1,8 @@
1
- import type { WorkflowRun, StepOutput, WorkflowStep } from "./workflow-types.js";
2
- import {
3
- createWorkflowRun,
4
- transitionStepStatus,
5
- } from "./workflow-types.js";
6
- import type { WorkflowEventLogger } from "./workflow-events.js";
7
- import type { ProviderTracker } from "./provider-tracker.js";
8
1
  import type { EvidenceRecord } from "./evidence.js";
2
+ import type { ProviderTracker } from "./provider-tracker.js";
3
+ import type { WorkflowEventLogger } from "./workflow-events.js";
4
+ import type { StepOutput, WorkflowRun, WorkflowStep } from "./workflow-types.js";
5
+ import { createWorkflowRun, transitionStepStatus } from "./workflow-types.js";
9
6
 
10
7
  /** Function that executes a single workflow step. */
11
8
  export type StepExecutor = (
@@ -106,10 +103,7 @@ export class WorkflowRunner {
106
103
  });
107
104
  }
108
105
 
109
- private async executeSteps(
110
- run: WorkflowRun,
111
- executor: StepExecutor,
112
- ): Promise<void> {
106
+ private async executeSteps(run: WorkflowRun, executor: StepExecutor): Promise<void> {
113
107
  for (let i = 0; i < run.steps.length; i++) {
114
108
  // Check if run was cancelled externally
115
109
  if (run.status !== "running") return;
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import type { SentinelRecord } from "../types.js";
3
2
  import type { FinnhubArticle } from "../../providers/finnhub.js";
4
3
  import { extractEntities } from "../../routing/entity-extractor.js";
4
+ import type { SentinelRecord } from "../types.js";
5
5
 
6
6
  const MAX_TICKERS = 3;
7
7
 
@@ -31,7 +31,12 @@ export class FinnhubAdapter {
31
31
  score: 0,
32
32
  confidence: 0,
33
33
  method: "keyword" as const,
34
- tickers: article.related ? article.related.split(",").map((t) => t.trim()).filter(Boolean) : [],
34
+ tickers: article.related
35
+ ? article.related
36
+ .split(",")
37
+ .map((t) => t.trim())
38
+ .filter(Boolean)
39
+ : [],
35
40
  },
36
41
  metadata: { category: article.category },
37
42
  }));