opencandle 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (574) hide show
  1. package/README.md +170 -186
  2. package/dist/analysts/contracts.d.ts +1 -3
  3. package/dist/analysts/contracts.js +1 -11
  4. package/dist/analysts/contracts.js.map +1 -1
  5. package/dist/analysts/orchestrator.d.ts +1 -3
  6. package/dist/analysts/orchestrator.js +1 -26
  7. package/dist/analysts/orchestrator.js.map +1 -1
  8. package/dist/cli.js +66 -7
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +13 -3
  11. package/dist/config.js +25 -5
  12. package/dist/config.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/infra/cache.d.ts +8 -11
  17. package/dist/infra/cache.js +17 -15
  18. package/dist/infra/cache.js.map +1 -1
  19. package/dist/infra/http-client.d.ts +4 -1
  20. package/dist/infra/http-client.js +59 -6
  21. package/dist/infra/http-client.js.map +1 -1
  22. package/dist/infra/index.d.ts +2 -3
  23. package/dist/infra/index.js +2 -3
  24. package/dist/infra/index.js.map +1 -1
  25. package/dist/infra/native-dependencies.js +2 -2
  26. package/dist/infra/native-dependencies.js.map +1 -1
  27. package/dist/infra/node-version.js.map +1 -1
  28. package/dist/infra/opencandle-paths.d.ts +0 -3
  29. package/dist/infra/opencandle-paths.js +4 -11
  30. package/dist/infra/opencandle-paths.js.map +1 -1
  31. package/dist/infra/rate-limiter.js +12 -9
  32. package/dist/infra/rate-limiter.js.map +1 -1
  33. package/dist/market-state/alert-conditions.d.ts +34 -0
  34. package/dist/market-state/alert-conditions.js +23 -0
  35. package/dist/market-state/alert-conditions.js.map +1 -0
  36. package/dist/market-state/alert-runner.d.ts +55 -0
  37. package/dist/market-state/alert-runner.js +634 -0
  38. package/dist/market-state/alert-runner.js.map +1 -0
  39. package/dist/market-state/daily-report.d.ts +26 -0
  40. package/dist/market-state/daily-report.js +179 -0
  41. package/dist/market-state/daily-report.js.map +1 -0
  42. package/dist/market-state/local-automation-service.d.ts +25 -0
  43. package/dist/market-state/local-automation-service.js +119 -0
  44. package/dist/market-state/local-automation-service.js.map +1 -0
  45. package/dist/market-state/notification-delivery.d.ts +14 -0
  46. package/dist/market-state/notification-delivery.js +139 -0
  47. package/dist/market-state/notification-delivery.js.map +1 -0
  48. package/dist/market-state/resolve-for-mutation.d.ts +10 -0
  49. package/dist/market-state/resolve-for-mutation.js +15 -0
  50. package/dist/market-state/resolve-for-mutation.js.map +1 -0
  51. package/dist/market-state/resolve.d.ts +14 -0
  52. package/dist/market-state/resolve.js +89 -0
  53. package/dist/market-state/resolve.js.map +1 -0
  54. package/dist/market-state/service.d.ts +527 -0
  55. package/dist/market-state/service.js +1099 -0
  56. package/dist/market-state/service.js.map +1 -0
  57. package/dist/memory/index.d.ts +7 -7
  58. package/dist/memory/index.js +6 -6
  59. package/dist/memory/index.js.map +1 -1
  60. package/dist/memory/manager.js +11 -11
  61. package/dist/memory/manager.js.map +1 -1
  62. package/dist/memory/retrieval.js +7 -4
  63. package/dist/memory/retrieval.js.map +1 -1
  64. package/dist/memory/sqlite.js +385 -3
  65. package/dist/memory/sqlite.js.map +1 -1
  66. package/dist/memory/storage.js +1 -2
  67. package/dist/memory/storage.js.map +1 -1
  68. package/dist/memory/tool-defaults.js +64 -28
  69. package/dist/memory/tool-defaults.js.map +1 -1
  70. package/dist/memory/types.js.map +1 -1
  71. package/dist/monitor.d.ts +2 -0
  72. package/dist/monitor.js +104 -0
  73. package/dist/monitor.js.map +1 -0
  74. package/dist/onboarding/connect.d.ts +2 -2
  75. package/dist/onboarding/connect.js +13 -8
  76. package/dist/onboarding/connect.js.map +1 -1
  77. package/dist/onboarding/credential-interceptor.js +1 -1
  78. package/dist/onboarding/credential-interceptor.js.map +1 -1
  79. package/dist/onboarding/degradation-accumulator.js +1 -3
  80. package/dist/onboarding/degradation-accumulator.js.map +1 -1
  81. package/dist/onboarding/provider-status.d.ts +48 -0
  82. package/dist/onboarding/provider-status.js +285 -0
  83. package/dist/onboarding/provider-status.js.map +1 -0
  84. package/dist/onboarding/providers.d.ts +85 -8
  85. package/dist/onboarding/providers.js +83 -18
  86. package/dist/onboarding/providers.js.map +1 -1
  87. package/dist/onboarding/state.d.ts +1 -0
  88. package/dist/onboarding/state.js +5 -0
  89. package/dist/onboarding/state.js.map +1 -1
  90. package/dist/onboarding/tool-helpers.js +1 -1
  91. package/dist/onboarding/tool-helpers.js.map +1 -1
  92. package/dist/onboarding/tool-tags.d.ts +12 -1
  93. package/dist/onboarding/tool-tags.js +37 -5
  94. package/dist/onboarding/tool-tags.js.map +1 -1
  95. package/dist/onboarding/validation.d.ts +2 -2
  96. package/dist/onboarding/validation.js +1 -1
  97. package/dist/onboarding/validation.js.map +1 -1
  98. package/dist/pi/opencandle-extension.d.ts +8 -0
  99. package/dist/pi/opencandle-extension.js +502 -42
  100. package/dist/pi/opencandle-extension.js.map +1 -1
  101. package/dist/pi/session.d.ts +1 -1
  102. package/dist/pi/session.js +3 -1
  103. package/dist/pi/session.js.map +1 -1
  104. package/dist/pi/setup.js +8 -3
  105. package/dist/pi/setup.js.map +1 -1
  106. package/dist/pi/tool-adapter.d.ts +4 -1
  107. package/dist/pi/tool-adapter.js +10 -6
  108. package/dist/pi/tool-adapter.js.map +1 -1
  109. package/dist/prompts/context-builder.d.ts +1 -1
  110. package/dist/prompts/context-builder.js +20 -7
  111. package/dist/prompts/context-builder.js.map +1 -1
  112. package/dist/prompts/policy-cards.d.ts +1 -1
  113. package/dist/prompts/policy-cards.js +2 -2
  114. package/dist/prompts/policy-cards.js.map +1 -1
  115. package/dist/prompts/sections.d.ts +1 -1
  116. package/dist/prompts/symbol-preflight.d.ts +20 -0
  117. package/dist/prompts/symbol-preflight.js +49 -0
  118. package/dist/prompts/symbol-preflight.js.map +1 -0
  119. package/dist/prompts/workflow-prompts.d.ts +1 -1
  120. package/dist/prompts/workflow-prompts.js +54 -16
  121. package/dist/prompts/workflow-prompts.js.map +1 -1
  122. package/dist/providers/alpha-vantage.d.ts +1 -1
  123. package/dist/providers/alpha-vantage.js +26 -7
  124. package/dist/providers/alpha-vantage.js.map +1 -1
  125. package/dist/providers/coingecko.js +1 -1
  126. package/dist/providers/coingecko.js.map +1 -1
  127. package/dist/providers/errors.d.ts +5 -0
  128. package/dist/providers/errors.js +11 -0
  129. package/dist/providers/errors.js.map +1 -0
  130. package/dist/providers/exa-search.d.ts +2 -2
  131. package/dist/providers/exa-search.js +19 -11
  132. package/dist/providers/exa-search.js.map +1 -1
  133. package/dist/providers/external-tool-error.d.ts +10 -0
  134. package/dist/providers/external-tool-error.js +21 -0
  135. package/dist/providers/external-tool-error.js.map +1 -0
  136. package/dist/providers/fear-greed.js +1 -1
  137. package/dist/providers/fear-greed.js.map +1 -1
  138. package/dist/providers/finnhub.js +3 -5
  139. package/dist/providers/finnhub.js.map +1 -1
  140. package/dist/providers/fred.js +2 -2
  141. package/dist/providers/fred.js.map +1 -1
  142. package/dist/providers/index.d.ts +7 -6
  143. package/dist/providers/index.js +6 -5
  144. package/dist/providers/index.js.map +1 -1
  145. package/dist/providers/reddit-cli.d.ts +36 -0
  146. package/dist/providers/reddit-cli.js +201 -0
  147. package/dist/providers/reddit-cli.js.map +1 -0
  148. package/dist/providers/reddit.d.ts +1 -1
  149. package/dist/providers/reddit.js +9 -37
  150. package/dist/providers/reddit.js.map +1 -1
  151. package/dist/providers/sec-edgar.d.ts +1 -0
  152. package/dist/providers/sec-edgar.js +12 -4
  153. package/dist/providers/sec-edgar.js.map +1 -1
  154. package/dist/providers/tradingview.d.ts +47 -0
  155. package/dist/providers/tradingview.js +275 -0
  156. package/dist/providers/tradingview.js.map +1 -0
  157. package/dist/providers/twitter-cli.d.ts +40 -0
  158. package/dist/providers/twitter-cli.js +153 -0
  159. package/dist/providers/twitter-cli.js.map +1 -0
  160. package/dist/providers/twitter.d.ts +0 -8
  161. package/dist/providers/twitter.js +8 -60
  162. package/dist/providers/twitter.js.map +1 -1
  163. package/dist/providers/web-search.js +26 -12
  164. package/dist/providers/web-search.js.map +1 -1
  165. package/dist/providers/with-fallback.js +4 -2
  166. package/dist/providers/with-fallback.js.map +1 -1
  167. package/dist/providers/wrap-provider.d.ts +2 -3
  168. package/dist/providers/wrap-provider.js +44 -8
  169. package/dist/providers/wrap-provider.js.map +1 -1
  170. package/dist/providers/yahoo-finance.d.ts +1 -1
  171. package/dist/providers/yahoo-finance.js +153 -48
  172. package/dist/providers/yahoo-finance.js.map +1 -1
  173. package/dist/routing/classify-intent.d.ts +6 -0
  174. package/dist/routing/classify-intent.js +78 -7
  175. package/dist/routing/classify-intent.js.map +1 -1
  176. package/dist/routing/defaults.d.ts +1 -1
  177. package/dist/routing/entity-extractor.d.ts +1 -0
  178. package/dist/routing/entity-extractor.js +234 -29
  179. package/dist/routing/entity-extractor.js.map +1 -1
  180. package/dist/routing/fund-symbols.d.ts +2 -0
  181. package/dist/routing/fund-symbols.js +55 -0
  182. package/dist/routing/fund-symbols.js.map +1 -0
  183. package/dist/routing/horizon.d.ts +1 -0
  184. package/dist/routing/horizon.js +10 -0
  185. package/dist/routing/horizon.js.map +1 -0
  186. package/dist/routing/index.d.ts +10 -10
  187. package/dist/routing/index.js +6 -6
  188. package/dist/routing/index.js.map +1 -1
  189. package/dist/routing/planning.d.ts +2 -2
  190. package/dist/routing/planning.js +65 -34
  191. package/dist/routing/planning.js.map +1 -1
  192. package/dist/routing/route-manifest.d.ts +2 -2
  193. package/dist/routing/route-manifest.js +25 -4
  194. package/dist/routing/route-manifest.js.map +1 -1
  195. package/dist/routing/router-llm-client.js.map +1 -1
  196. package/dist/routing/router-prompt.js +7 -9
  197. package/dist/routing/router-prompt.js.map +1 -1
  198. package/dist/routing/router-types.d.ts +1 -0
  199. package/dist/routing/router.js +137 -22
  200. package/dist/routing/router.js.map +1 -1
  201. package/dist/routing/slot-resolver.d.ts +1 -1
  202. package/dist/routing/slot-resolver.js +2 -4
  203. package/dist/routing/slot-resolver.js.map +1 -1
  204. package/dist/routing/symbol-disambiguator.d.ts +11 -0
  205. package/dist/routing/symbol-disambiguator.js +52 -0
  206. package/dist/routing/symbol-disambiguator.js.map +1 -0
  207. package/dist/routing/turn-context.d.ts +1 -1
  208. package/dist/routing/turn-context.js +1 -1
  209. package/dist/routing/turn-context.js.map +1 -1
  210. package/dist/routing/types.d.ts +2 -0
  211. package/dist/runtime/answer-contracts.d.ts +1 -1
  212. package/dist/runtime/answer-contracts.js +48 -9
  213. package/dist/runtime/answer-contracts.js.map +1 -1
  214. package/dist/runtime/artifact-contracts.js.map +1 -1
  215. package/dist/runtime/planning-evidence.js +47 -26
  216. package/dist/runtime/planning-evidence.js.map +1 -1
  217. package/dist/runtime/prompt-step.d.ts +1 -9
  218. package/dist/runtime/prompt-step.js +0 -10
  219. package/dist/runtime/prompt-step.js.map +1 -1
  220. package/dist/runtime/run-context.d.ts +5 -2
  221. package/dist/runtime/run-context.js +8 -1
  222. package/dist/runtime/run-context.js.map +1 -1
  223. package/dist/runtime/session-coordinator.d.ts +13 -5
  224. package/dist/runtime/session-coordinator.js +160 -20
  225. package/dist/runtime/session-coordinator.js.map +1 -1
  226. package/dist/runtime/session-title.d.ts +14 -0
  227. package/dist/runtime/session-title.js +50 -0
  228. package/dist/runtime/session-title.js.map +1 -0
  229. package/dist/runtime/tool-defaults-wrapper.js +7 -5
  230. package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
  231. package/dist/runtime/validation.js.map +1 -1
  232. package/dist/runtime/workflow-events.js.map +1 -1
  233. package/dist/runtime/workflow-runner.d.ts +3 -3
  234. package/dist/runtime/workflow-runner.js +1 -1
  235. package/dist/runtime/workflow-runner.js.map +1 -1
  236. package/dist/sentiment/adapters/finnhub.d.ts +1 -1
  237. package/dist/sentiment/adapters/finnhub.js +6 -1
  238. package/dist/sentiment/adapters/finnhub.js.map +1 -1
  239. package/dist/sentiment/adapters/reddit.d.ts +2 -2
  240. package/dist/sentiment/adapters/twitter.d.ts +1 -1
  241. package/dist/sentiment/adapters/web.d.ts +1 -1
  242. package/dist/sentiment/index.d.ts +10 -11
  243. package/dist/sentiment/index.js +10 -20
  244. package/dist/sentiment/index.js.map +1 -1
  245. package/dist/sentiment/insights.d.ts +17 -0
  246. package/dist/sentiment/insights.js +206 -0
  247. package/dist/sentiment/insights.js.map +1 -0
  248. package/dist/sentiment/keywords.js +26 -4
  249. package/dist/sentiment/keywords.js.map +1 -1
  250. package/dist/sentiment/pipeline.d.ts +2 -2
  251. package/dist/sentiment/pipeline.js +14 -2
  252. package/dist/sentiment/pipeline.js.map +1 -1
  253. package/dist/sentiment/scorer.d.ts +2 -0
  254. package/dist/sentiment/scorer.js +11 -2
  255. package/dist/sentiment/scorer.js.map +1 -1
  256. package/dist/sentiment/store.d.ts +1 -1
  257. package/dist/sentiment/store.js +1 -1
  258. package/dist/sentiment/store.js.map +1 -1
  259. package/dist/sentiment/trends.d.ts +1 -1
  260. package/dist/sentiment/trends.js.map +1 -1
  261. package/dist/sentiment/types.d.ts +2 -0
  262. package/dist/sentiment/types.js.map +1 -1
  263. package/dist/system-prompt.js +6 -9
  264. package/dist/system-prompt.js.map +1 -1
  265. package/dist/tool-kit.d.ts +7 -7
  266. package/dist/tool-kit.js +4 -4
  267. package/dist/tool-kit.js.map +1 -1
  268. package/dist/tools/fundamentals/company-overview.js +11 -6
  269. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  270. package/dist/tools/fundamentals/comps.js +18 -9
  271. package/dist/tools/fundamentals/comps.js.map +1 -1
  272. package/dist/tools/fundamentals/dcf.js +23 -11
  273. package/dist/tools/fundamentals/dcf.js.map +1 -1
  274. package/dist/tools/fundamentals/earnings.js +8 -3
  275. package/dist/tools/fundamentals/earnings.js.map +1 -1
  276. package/dist/tools/fundamentals/financials.js +8 -3
  277. package/dist/tools/fundamentals/financials.js.map +1 -1
  278. package/dist/tools/fundamentals/sec-filings.js +21 -6
  279. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  280. package/dist/tools/index.d.ts +27 -20
  281. package/dist/tools/index.js +55 -43
  282. package/dist/tools/index.js.map +1 -1
  283. package/dist/tools/interaction/ask-user.js +15 -3
  284. package/dist/tools/interaction/ask-user.js.map +1 -1
  285. package/dist/tools/macro/fear-greed.js.map +1 -1
  286. package/dist/tools/macro/fred-data.d.ts +1 -1
  287. package/dist/tools/macro/fred-data.js +17 -6
  288. package/dist/tools/macro/fred-data.js.map +1 -1
  289. package/dist/tools/market/crypto-history.js +3 -1
  290. package/dist/tools/market/crypto-history.js.map +1 -1
  291. package/dist/tools/market/crypto-price.js +3 -1
  292. package/dist/tools/market/crypto-price.js.map +1 -1
  293. package/dist/tools/market/screen-stocks.d.ts +18 -0
  294. package/dist/tools/market/screen-stocks.js +252 -0
  295. package/dist/tools/market/screen-stocks.js.map +1 -0
  296. package/dist/tools/market/search-ticker.js +160 -8
  297. package/dist/tools/market/search-ticker.js.map +1 -1
  298. package/dist/tools/market/stock-history.d.ts +2 -2
  299. package/dist/tools/market/stock-history.js +26 -7
  300. package/dist/tools/market/stock-history.js.map +1 -1
  301. package/dist/tools/market/stock-quote.js +5 -3
  302. package/dist/tools/market/stock-quote.js.map +1 -1
  303. package/dist/tools/options/greeks.js +1 -1
  304. package/dist/tools/options/greeks.js.map +1 -1
  305. package/dist/tools/options/option-chain.js +19 -6
  306. package/dist/tools/options/option-chain.js.map +1 -1
  307. package/dist/tools/portfolio/alerts.d.ts +15 -0
  308. package/dist/tools/portfolio/alerts.js +357 -0
  309. package/dist/tools/portfolio/alerts.js.map +1 -0
  310. package/dist/tools/portfolio/correlation.d.ts +1 -1
  311. package/dist/tools/portfolio/correlation.js +33 -13
  312. package/dist/tools/portfolio/correlation.js.map +1 -1
  313. package/dist/tools/portfolio/daily-report.d.ts +8 -0
  314. package/dist/tools/portfolio/daily-report.js +83 -0
  315. package/dist/tools/portfolio/daily-report.js.map +1 -0
  316. package/dist/tools/portfolio/holdings-overlap.js +10 -3
  317. package/dist/tools/portfolio/holdings-overlap.js.map +1 -1
  318. package/dist/tools/portfolio/notifications.d.ts +7 -0
  319. package/dist/tools/portfolio/notifications.js +43 -0
  320. package/dist/tools/portfolio/notifications.js.map +1 -0
  321. package/dist/tools/portfolio/predictions.d.ts +12 -6
  322. package/dist/tools/portfolio/predictions.js +337 -87
  323. package/dist/tools/portfolio/predictions.js.map +1 -1
  324. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  325. package/dist/tools/portfolio/risk-analysis.js +45 -6
  326. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  327. package/dist/tools/portfolio/tracker.d.ts +4 -3
  328. package/dist/tools/portfolio/tracker.js +246 -101
  329. package/dist/tools/portfolio/tracker.js.map +1 -1
  330. package/dist/tools/portfolio/watchlist.d.ts +6 -4
  331. package/dist/tools/portfolio/watchlist.js +208 -108
  332. package/dist/tools/portfolio/watchlist.js.map +1 -1
  333. package/dist/tools/sentiment/insight-format.d.ts +2 -0
  334. package/dist/tools/sentiment/insight-format.js +36 -0
  335. package/dist/tools/sentiment/insight-format.js.map +1 -0
  336. package/dist/tools/sentiment/query-match.d.ts +3 -0
  337. package/dist/tools/sentiment/query-match.js +113 -0
  338. package/dist/tools/sentiment/query-match.js.map +1 -0
  339. package/dist/tools/sentiment/reddit-sentiment.d.ts +12 -1
  340. package/dist/tools/sentiment/reddit-sentiment.js +266 -107
  341. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  342. package/dist/tools/sentiment/sentiment-summary.d.ts +9 -1
  343. package/dist/tools/sentiment/sentiment-summary.js +223 -205
  344. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  345. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  346. package/dist/tools/sentiment/sentiment-trend.js +12 -2
  347. package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
  348. package/dist/tools/sentiment/twitter-sentiment.d.ts +11 -1
  349. package/dist/tools/sentiment/twitter-sentiment.js +188 -58
  350. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  351. package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
  352. package/dist/tools/sentiment/untrusted-text.js +17 -0
  353. package/dist/tools/sentiment/untrusted-text.js.map +1 -0
  354. package/dist/tools/sentiment/web-search.js +9 -13
  355. package/dist/tools/sentiment/web-search.js.map +1 -1
  356. package/dist/tools/sentiment/web-sentiment.js +19 -3
  357. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  358. package/dist/tools/technical/backtest.d.ts +1 -1
  359. package/dist/tools/technical/backtest.js +27 -20
  360. package/dist/tools/technical/backtest.js.map +1 -1
  361. package/dist/tools/technical/indicators.js +23 -5
  362. package/dist/tools/technical/indicators.js.map +1 -1
  363. package/dist/types/index.d.ts +3 -3
  364. package/dist/types/index.js.map +1 -1
  365. package/dist/types/market.d.ts +1 -0
  366. package/dist/types/portfolio.d.ts +14 -4
  367. package/dist/types/sentiment.d.ts +52 -0
  368. package/dist/workflows/compare-assets.d.ts +0 -3
  369. package/dist/workflows/compare-assets.js +20 -11
  370. package/dist/workflows/compare-assets.js.map +1 -1
  371. package/dist/workflows/index.d.ts +3 -4
  372. package/dist/workflows/index.js +3 -3
  373. package/dist/workflows/index.js.map +1 -1
  374. package/dist/workflows/options-screener.d.ts +0 -3
  375. package/dist/workflows/options-screener.js +4 -11
  376. package/dist/workflows/options-screener.js.map +1 -1
  377. package/dist/workflows/portfolio-builder.d.ts +0 -3
  378. package/dist/workflows/portfolio-builder.js +0 -8
  379. package/dist/workflows/portfolio-builder.js.map +1 -1
  380. package/gui/server/ask-user-bridge.ts +1 -1
  381. package/gui/server/automation-heartbeat.ts +97 -0
  382. package/gui/server/background-quotes.ts +97 -1
  383. package/gui/server/chat-event-adapter.ts +32 -10
  384. package/gui/server/chat-run-session.ts +16 -0
  385. package/gui/server/invoke-tool.ts +160 -3
  386. package/gui/server/live-chat-event-adapter.ts +21 -6
  387. package/gui/server/market-state-api.ts +315 -0
  388. package/gui/server/model-setup.ts +156 -2
  389. package/gui/server/private-api-access.ts +62 -0
  390. package/gui/server/projector.ts +18 -9
  391. package/gui/server/prompt-observation.ts +4 -7
  392. package/gui/server/quote-snapshot-store.ts +50 -0
  393. package/gui/server/server.ts +218 -451
  394. package/gui/server/session-actions.ts +186 -1
  395. package/gui/server/shutdown.ts +47 -0
  396. package/gui/server/tool-invoke-ack.ts +49 -0
  397. package/gui/server/tool-metadata.ts +101 -24
  398. package/gui/server/websocket.ts +13 -3
  399. package/gui/server/writer-lock.ts +6 -2
  400. package/gui/server/ws-hub.ts +311 -0
  401. package/gui/shared/chat-events.ts +16 -1
  402. package/gui/shared/event-reducer.ts +24 -6
  403. package/gui/web/dist/assets/CatalogOverlay-CgeY5Pkp.js +1 -0
  404. package/gui/web/dist/assets/index-C6W_2eAn.js +69 -0
  405. package/gui/web/dist/assets/index-hwbx24a5.css +1 -0
  406. package/gui/web/dist/index.html +2 -2
  407. package/package.json +9 -6
  408. package/src/analysts/contracts.ts +10 -23
  409. package/src/analysts/orchestrator.ts +8 -43
  410. package/src/cli.ts +76 -12
  411. package/src/config.ts +44 -9
  412. package/src/index.ts +1 -1
  413. package/src/infra/cache.ts +41 -30
  414. package/src/infra/http-client.ts +72 -6
  415. package/src/infra/index.ts +6 -10
  416. package/src/infra/native-dependencies.ts +8 -3
  417. package/src/infra/node-version.ts +3 -1
  418. package/src/infra/opencandle-paths.ts +3 -14
  419. package/src/infra/rate-limiter.ts +22 -19
  420. package/src/market-state/alert-conditions.ts +82 -0
  421. package/src/market-state/alert-runner.ts +863 -0
  422. package/src/market-state/daily-report.ts +247 -0
  423. package/src/market-state/local-automation-service.ts +162 -0
  424. package/src/market-state/notification-delivery.ts +158 -0
  425. package/src/market-state/resolve-for-mutation.ts +24 -0
  426. package/src/market-state/resolve.ts +112 -0
  427. package/src/market-state/service.ts +2344 -0
  428. package/src/memory/index.ts +7 -7
  429. package/src/memory/manager.ts +14 -16
  430. package/src/memory/retrieval.ts +8 -7
  431. package/src/memory/sqlite.ts +407 -6
  432. package/src/memory/storage.ts +5 -15
  433. package/src/memory/tool-defaults.ts +60 -39
  434. package/src/memory/types.ts +3 -3
  435. package/src/monitor.ts +121 -0
  436. package/src/onboarding/connect.ts +24 -31
  437. package/src/onboarding/credential-interceptor.ts +3 -15
  438. package/src/onboarding/degradation-accumulator.ts +1 -3
  439. package/src/onboarding/provider-status.ts +410 -0
  440. package/src/onboarding/providers.ts +144 -45
  441. package/src/onboarding/state.ts +13 -15
  442. package/src/onboarding/tool-helpers.ts +2 -9
  443. package/src/onboarding/tool-tags.ts +51 -8
  444. package/src/onboarding/validation.ts +16 -22
  445. package/src/pi/opencandle-extension.ts +643 -101
  446. package/src/pi/session.ts +7 -5
  447. package/src/pi/setup.ts +61 -43
  448. package/src/pi/tool-adapter.ts +19 -6
  449. package/src/prompts/context-builder.ts +24 -13
  450. package/src/prompts/policy-cards.ts +3 -3
  451. package/src/prompts/sections.ts +1 -1
  452. package/src/prompts/symbol-preflight.ts +80 -0
  453. package/src/prompts/workflow-prompts.ts +77 -28
  454. package/src/providers/alpha-vantage.ts +58 -39
  455. package/src/providers/coingecko.ts +2 -5
  456. package/src/providers/errors.ts +9 -0
  457. package/src/providers/exa-search.ts +24 -22
  458. package/src/providers/external-tool-error.ts +20 -0
  459. package/src/providers/fear-greed.ts +1 -1
  460. package/src/providers/finnhub.ts +7 -6
  461. package/src/providers/fred.ts +3 -3
  462. package/src/providers/index.ts +14 -6
  463. package/src/providers/reddit-cli.ts +317 -0
  464. package/src/providers/reddit.ts +14 -59
  465. package/src/providers/sec-edgar.ts +20 -6
  466. package/src/providers/tradingview.ts +399 -0
  467. package/src/providers/twitter-cli.ts +233 -0
  468. package/src/providers/twitter.ts +8 -79
  469. package/src/providers/web-search.ts +30 -20
  470. package/src/providers/with-fallback.ts +8 -7
  471. package/src/providers/wrap-provider.ts +49 -10
  472. package/src/providers/yahoo-finance.ts +204 -66
  473. package/src/routing/classify-intent.ts +101 -10
  474. package/src/routing/defaults.ts +1 -1
  475. package/src/routing/entity-extractor.ts +287 -38
  476. package/src/routing/fund-symbols.ts +58 -0
  477. package/src/routing/horizon.ts +7 -0
  478. package/src/routing/index.ts +48 -48
  479. package/src/routing/planning.ts +145 -53
  480. package/src/routing/route-manifest.ts +37 -15
  481. package/src/routing/router-llm-client.ts +4 -4
  482. package/src/routing/router-prompt.ts +15 -19
  483. package/src/routing/router-types.ts +2 -5
  484. package/src/routing/router.ts +251 -53
  485. package/src/routing/slot-resolver.ts +34 -11
  486. package/src/routing/symbol-disambiguator.ts +72 -0
  487. package/src/routing/turn-context.ts +6 -9
  488. package/src/routing/types.ts +2 -0
  489. package/src/runtime/answer-contracts.ts +105 -45
  490. package/src/runtime/artifact-contracts.ts +2 -1
  491. package/src/runtime/planning-evidence.ts +157 -66
  492. package/src/runtime/prompt-step.ts +1 -16
  493. package/src/runtime/run-context.ts +12 -2
  494. package/src/runtime/session-coordinator.ts +238 -63
  495. package/src/runtime/session-title.ts +60 -0
  496. package/src/runtime/tool-defaults-wrapper.ts +13 -5
  497. package/src/runtime/validation.ts +1 -4
  498. package/src/runtime/workflow-events.ts +7 -7
  499. package/src/runtime/workflow-runner.ts +5 -11
  500. package/src/sentiment/adapters/finnhub.ts +7 -2
  501. package/src/sentiment/adapters/reddit.ts +2 -2
  502. package/src/sentiment/adapters/twitter.ts +1 -1
  503. package/src/sentiment/adapters/web.ts +1 -1
  504. package/src/sentiment/index.ts +17 -26
  505. package/src/sentiment/insights.ts +269 -0
  506. package/src/sentiment/keywords.ts +26 -4
  507. package/src/sentiment/pipeline.ts +28 -5
  508. package/src/sentiment/scorer.ts +13 -2
  509. package/src/sentiment/store.ts +2 -2
  510. package/src/sentiment/trends.ts +9 -3
  511. package/src/sentiment/types.ts +8 -4
  512. package/src/system-prompt.ts +6 -9
  513. package/src/tool-kit.ts +10 -9
  514. package/src/tools/fundamentals/company-overview.ts +19 -9
  515. package/src/tools/fundamentals/comps.ts +68 -55
  516. package/src/tools/fundamentals/dcf.ts +145 -95
  517. package/src/tools/fundamentals/earnings.ts +16 -6
  518. package/src/tools/fundamentals/financials.ts +16 -7
  519. package/src/tools/fundamentals/sec-filings.ts +37 -16
  520. package/src/tools/index.ts +56 -43
  521. package/src/tools/interaction/ask-user.ts +22 -10
  522. package/src/tools/macro/fear-greed.ts +1 -1
  523. package/src/tools/macro/fred-data.ts +58 -46
  524. package/src/tools/market/crypto-history.ts +8 -3
  525. package/src/tools/market/crypto-price.ts +6 -6
  526. package/src/tools/market/screen-stocks.ts +279 -0
  527. package/src/tools/market/search-ticker.ts +218 -17
  528. package/src/tools/market/stock-history.ts +37 -12
  529. package/src/tools/market/stock-quote.ts +10 -7
  530. package/src/tools/options/greeks.ts +5 -5
  531. package/src/tools/options/option-chain.ts +41 -17
  532. package/src/tools/portfolio/alerts.ts +457 -0
  533. package/src/tools/portfolio/correlation.ts +47 -20
  534. package/src/tools/portfolio/daily-report.ts +101 -0
  535. package/src/tools/portfolio/holdings-overlap.ts +31 -15
  536. package/src/tools/portfolio/notifications.ts +45 -0
  537. package/src/tools/portfolio/predictions.ts +406 -106
  538. package/src/tools/portfolio/risk-analysis.ts +46 -7
  539. package/src/tools/portfolio/tracker.ts +270 -109
  540. package/src/tools/portfolio/watchlist.ts +250 -121
  541. package/src/tools/sentiment/insight-format.ts +50 -0
  542. package/src/tools/sentiment/query-match.ts +117 -0
  543. package/src/tools/sentiment/reddit-sentiment.ts +360 -121
  544. package/src/tools/sentiment/sentiment-summary.ts +302 -235
  545. package/src/tools/sentiment/sentiment-trend.ts +24 -7
  546. package/src/tools/sentiment/twitter-sentiment.ts +264 -73
  547. package/src/tools/sentiment/untrusted-text.ts +21 -0
  548. package/src/tools/sentiment/web-search.ts +21 -18
  549. package/src/tools/sentiment/web-sentiment.ts +30 -10
  550. package/src/tools/technical/backtest.ts +32 -22
  551. package/src/tools/technical/indicators.ts +39 -14
  552. package/src/types/index.ts +8 -3
  553. package/src/types/market.ts +1 -0
  554. package/src/types/portfolio.ts +14 -4
  555. package/src/types/sentiment.ts +61 -2
  556. package/src/workflows/compare-assets.ts +33 -21
  557. package/src/workflows/index.ts +3 -4
  558. package/src/workflows/options-screener.ts +27 -29
  559. package/src/workflows/portfolio-builder.ts +34 -27
  560. package/dist/infra/browser.d.ts +0 -35
  561. package/dist/infra/browser.js +0 -103
  562. package/dist/infra/browser.js.map +0 -1
  563. package/dist/tools/interaction/twitter-login.d.ts +0 -8
  564. package/dist/tools/interaction/twitter-login.js +0 -77
  565. package/dist/tools/interaction/twitter-login.js.map +0 -1
  566. package/dist/workflows/types.d.ts +0 -4
  567. package/dist/workflows/types.js +0 -2
  568. package/dist/workflows/types.js.map +0 -1
  569. package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +0 -1
  570. package/gui/web/dist/assets/index-Bxt9QpLX.css +0 -1
  571. package/gui/web/dist/assets/index-CZ9DHZYy.js +0 -67
  572. package/src/infra/browser.ts +0 -111
  573. package/src/tools/interaction/twitter-login.ts +0 -93
  574. package/src/workflows/types.ts +0 -4
@@ -1,23 +1,29 @@
1
- import { isAnalysisRequest, normalizeSymbol, } from "../analysts/orchestrator.js";
2
- import { buildComprehensiveAnalysisDefinition } from "../analysts/orchestrator.js";
1
+ import { buildComprehensiveAnalysisDefinition, isAnalysisRequest, normalizeSymbol, } from "../analysts/orchestrator.js";
3
2
  import { getConfig } from "../config.js";
4
- import { classifyWithLegacyRules, createPiAiRouterClient, resolveOptionsScreenerSlots, resolvePortfolioSlots, route as routeLlm, buildResolvedTurnContext, } from "../routing/index.js";
5
- import { buildAssumptionsBlockFromRouter } from "../prompts/workflow-prompts.js";
6
- import { buildPortfolioWorkflowDefinition, buildOptionsScreenerWorkflowDefinition, buildCompareAssetsWorkflowDefinition, } from "../workflows/index.js";
7
- import { getOpenCandleToolDefinitions } from "./tool-adapter.js";
8
- import { registerAskUserTool } from "../tools/interaction/ask-user.js";
9
- import { registerTwitterLoginTool } from "../tools/interaction/twitter-login.js";
10
- import { SessionCoordinator } from "../runtime/session-coordinator.js";
11
- import { getProvider, } from "../onboarding/providers.js";
12
- import { loadOnboardingState, saveOnboardingState, markProviderSnoozed, markProviderNeverAsk, markWelcomeShown, shouldShowWelcome, } from "../onboarding/state.js";
13
- import { parseToolTag, buildSkippedTag, buildConnectedTag } from "../onboarding/tool-tags.js";
3
+ import { runProviderConnect } from "../onboarding/connect.js";
14
4
  import { resolveCredentialRequired } from "../onboarding/credential-interceptor.js";
15
5
  import { createDegradationAccumulator } from "../onboarding/degradation-accumulator.js";
16
6
  import { promptUser } from "../onboarding/prompt-user.js";
17
- import { runProviderConnect } from "../onboarding/connect.js";
7
+ import { getProvider } from "../onboarding/providers.js";
8
+ import { loadOnboardingState, markProviderNeverAsk, markProviderSnoozed, markWelcomeShown, saveOnboardingState, shouldShowWelcome, } from "../onboarding/state.js";
9
+ import { buildConnectedTag, buildSkippedTag, parseToolTag } from "../onboarding/tool-tags.js";
18
10
  import { DISCLAIMER_TEXT } from "../prompts/disclaimer.js";
11
+ import { formatPreflightDropAnnotation, preflightSymbols } from "../prompts/symbol-preflight.js";
12
+ import { buildAssumptionsBlockFromRouter } from "../prompts/workflow-prompts.js";
13
+ import { buildResolvedTurnContext, classifyWithLegacyRules, createPiAiRouterClient, hasFinanceSignals, resolveOptionsScreenerSlots, resolvePortfolioSlots, route as routeLlm, } from "../routing/index.js";
14
+ import { disambiguateSymbols } from "../routing/symbol-disambiguator.js";
15
+ import { SessionCoordinator } from "../runtime/session-coordinator.js";
16
+ import { generateSessionTitle } from "../runtime/session-title.js";
17
+ import { registerAskUserTool } from "../tools/interaction/ask-user.js";
18
+ import { buildCompareAssetsWorkflowDefinition, buildOptionsScreenerWorkflowDefinition, buildPortfolioWorkflowDefinition, } from "../workflows/index.js";
19
+ import { getOpenCandleToolDefinitions } from "./tool-adapter.js";
19
20
  export default function openCandleExtension(pi, options) {
20
21
  const coordinator = new SessionCoordinator();
22
+ // Workflow transforms replace the user's turn with the expanded prompt; this
23
+ // marker lets the GUI render the user's original words instead.
24
+ const markOriginalInput = (original) => {
25
+ pi.appendEntry("opencandle-user-input", { original });
26
+ };
21
27
  // Credential-interception state. Lifetime:
22
28
  // `sessionPromptedSet` — cleared on session_start, persists across turns
23
29
  // within a session so users don't get re-prompted after picking
@@ -36,12 +42,15 @@ export default function openCandleExtension(pi, options) {
36
42
  const degradationAccumulator = createDegradationAccumulator();
37
43
  let activeToolSnapshot = null;
38
44
  let currentRouteToolContext = null;
45
+ // LLM session-title state: one title attempt per session per process.
46
+ // Reset on session_start; set before the (async) title call fires so
47
+ // overlapping turn_end events cannot double-title.
48
+ let sessionTitleAttempted = false;
39
49
  // Register tools
40
- for (const tool of getOpenCandleToolDefinitions()) {
50
+ for (const tool of getOpenCandleToolDefinitions({ askUserHandler: options?.askUserHandler })) {
41
51
  pi.registerTool(tool);
42
52
  }
43
53
  registerAskUserTool(pi, options?.askUserHandler);
44
- registerTwitterLoginTool(pi);
45
54
  // /analyze command
46
55
  pi.registerCommand("analyze", {
47
56
  description: "Run the multi-analyst OpenCandle workflow for a ticker symbol",
@@ -51,7 +60,9 @@ export default function openCandleExtension(pi, options) {
51
60
  ctx.ui.notify("Usage: /analyze <ticker>", "warning");
52
61
  return;
53
62
  }
54
- const definition = buildComprehensiveAnalysisDefinition(symbol, { debate: getConfig().debate });
63
+ const definition = buildComprehensiveAnalysisDefinition(symbol, {
64
+ debate: getConfig().debate,
65
+ });
55
66
  coordinator.executeWorkflow(pi, definition, ctx);
56
67
  },
57
68
  });
@@ -71,9 +82,9 @@ export default function openCandleExtension(pi, options) {
71
82
  // `/connect <alias|id|category>` routes to a specific provider (or a
72
83
  // sub-picker for multi-provider categories like "search").
73
84
  pi.registerCommand("connect", {
74
- description: "Connect a data provider (Alpha Vantage, FRED, Finnhub, Brave, Exa)",
85
+ description: "Connect an API-key data provider (Alpha Vantage, FRED, Finnhub, Brave, Exa)",
75
86
  handler: async (args, ctx) => {
76
- const { listAllProviders, resolveProviderFromArgument, hasCredential } = await import("../onboarding/providers.js");
87
+ const { listApiKeyProviders, resolveProviderFromArgument, hasCredential, isApiKeyProvider } = await import("../onboarding/providers.js");
77
88
  const formatState = (id) => {
78
89
  const state = loadOnboardingState().providers[id];
79
90
  if (state?.status === "completed")
@@ -99,12 +110,12 @@ export default function openCandleExtension(pi, options) {
99
110
  let targetId;
100
111
  if (trimmed === "") {
101
112
  // Bare /connect → full picker.
102
- targetId = await pickProvider(listAllProviders());
113
+ targetId = await pickProvider(listApiKeyProviders());
103
114
  }
104
115
  else {
105
116
  const resolved = resolveProviderFromArgument(trimmed);
106
117
  if (!resolved) {
107
- const all = listAllProviders()
118
+ const all = listApiKeyProviders()
108
119
  .map((p) => ` ${p.displayName} (${p.aliases.join(", ")})`)
109
120
  .join("\n");
110
121
  ctx.ui.notify(`Unknown provider: "${trimmed}". Available:\n${all}`, "warning");
@@ -112,10 +123,20 @@ export default function openCandleExtension(pi, options) {
112
123
  }
113
124
  if (Array.isArray(resolved)) {
114
125
  // Multi-provider category — show a sub-picker.
115
- targetId = await pickProvider(resolved);
126
+ const apiKeyProviders = resolved.filter(isApiKeyProvider);
127
+ if (apiKeyProviders.length === 0) {
128
+ ctx.ui.notify(`"${trimmed}" does not use API-key setup. Run opencandle doctor for setup status.`, "warning");
129
+ return;
130
+ }
131
+ targetId = await pickProvider(apiKeyProviders);
116
132
  }
117
133
  else {
118
- targetId = resolved.id;
134
+ const descriptor = resolved;
135
+ if (!isApiKeyProvider(descriptor)) {
136
+ ctx.ui.notify(`${descriptor.displayName} does not use API-key setup. Run opencandle doctor for setup status.`, "warning");
137
+ return;
138
+ }
139
+ targetId = descriptor.id;
119
140
  }
120
141
  }
121
142
  if (!targetId) {
@@ -137,6 +158,7 @@ export default function openCandleExtension(pi, options) {
137
158
  coordinator.initSession(ctx.sessionManager.getSessionId());
138
159
  sessionPromptedSet.clear();
139
160
  hardPromptFiredInWorkflow = false;
161
+ sessionTitleAttempted = false;
140
162
  if (!ctx.hasUI)
141
163
  return;
142
164
  // Pin the user-facing disclaimer in the UI footer for the entire session.
@@ -210,6 +232,83 @@ export default function openCandleExtension(pi, options) {
210
232
  }
211
233
  degradationAccumulator.reset();
212
234
  });
235
+ // LLM session titles. After the first completed user↔assistant exchange,
236
+ // replace the placeholder session name (the raw first prompt, set by the
237
+ // GUI server / Pi's firstMessage fallback) with a short model-written
238
+ // summary. Manual renames are left alone, and each session is attempted at
239
+ // most once per process. Model failures are swallowed (placeholder stays)
240
+ // but recorded as an `opencandle-title-error` entry for observability.
241
+ pi.on("turn_end", async (event, ctx) => {
242
+ if (sessionTitleAttempted)
243
+ return;
244
+ const msg = event.message;
245
+ if (msg?.role !== "assistant" || msg?.stopReason !== "stop")
246
+ return;
247
+ const sessionManager = ctx?.sessionManager;
248
+ if (typeof sessionManager?.getBranch !== "function")
249
+ return;
250
+ const branch = sessionManager.getBranch();
251
+ let firstUserText = null;
252
+ let firstAssistantText = null;
253
+ let originalInput = null;
254
+ for (const entry of branch) {
255
+ if (originalInput === null &&
256
+ entry.type === "custom" &&
257
+ entry.customType === "opencandle-user-input") {
258
+ const original = entry.data?.original;
259
+ if (typeof original === "string" && original.trim().length > 0) {
260
+ originalInput = original;
261
+ }
262
+ continue;
263
+ }
264
+ if (entry.type !== "message")
265
+ continue;
266
+ const message = entry.message;
267
+ const text = extractMessageText(message?.content);
268
+ if (text.trim().length === 0)
269
+ continue;
270
+ if (firstUserText === null && message?.role === "user")
271
+ firstUserText = text;
272
+ if (firstAssistantText === null && message?.role === "assistant") {
273
+ firstAssistantText = text;
274
+ }
275
+ if (firstUserText !== null && firstAssistantText !== null)
276
+ break;
277
+ }
278
+ // Fall back to the just-finished assistant message when the branch has
279
+ // not surfaced it yet at turn_end time.
280
+ if (firstAssistantText === null) {
281
+ const eventText = extractMessageText(msg.content);
282
+ if (eventText.trim().length > 0)
283
+ firstAssistantText = eventText;
284
+ }
285
+ if (firstUserText === null || firstAssistantText === null)
286
+ return;
287
+ const userText = originalInput ?? firstUserText;
288
+ // A manual rename must be left alone — only replace the placeholder
289
+ // (unset, the raw first prompt, or the GUI's "first 77 chars + ..." form).
290
+ if (!isPlaceholderSessionName(pi.getSessionName(), [userText, firstUserText]))
291
+ return;
292
+ const completion = options?.titleCompletion ??
293
+ (() => {
294
+ const client = options?.routerLlmClient ?? resolveRouterLlmClient(ctx);
295
+ return client ? (prompt) => client.complete(prompt) : null;
296
+ })();
297
+ if (!completion)
298
+ return;
299
+ sessionTitleAttempted = true;
300
+ try {
301
+ const title = await generateSessionTitle({ userText, assistantText: firstAssistantText.slice(0, 500) }, completion);
302
+ if (title !== null) {
303
+ pi.setSessionName(title);
304
+ }
305
+ }
306
+ catch (err) {
307
+ pi.appendEntry("opencandle-title-error", {
308
+ message: err instanceof Error ? err.message : String(err),
309
+ });
310
+ }
311
+ });
213
312
  // Intercept tool results for credential-required and soft-degraded tags.
214
313
  pi.on("tool_result", async (event, ctx) => {
215
314
  // First pass: record any soft-degradation tags in the per-turn accumulator
@@ -224,17 +323,85 @@ export default function openCandleExtension(pi, options) {
224
323
  degradationAccumulator.record(parsed.provider);
225
324
  }
226
325
  }
227
- // Second pass: look for a credential-required tag; on match, run the
228
- // interception decision and either replace the tool result or prompt
229
- // the user. Only the first credential_required tag in the content list
230
- // is acted on — subsequent hard-tier prompts are silenced by the
231
- // per-workflow cap at the decision-function level.
326
+ // Second pass: look for setup-required tags; on match, run the interception
327
+ // decision and either replace the tool result or prompt the user. Only the
328
+ // first setup tag in the content list is acted on.
232
329
  for (const block of event.content) {
233
330
  if (block.type !== "text")
234
331
  continue;
235
332
  const parsed = parseToolTag(block.text);
236
- if (!parsed || parsed.kind !== "credential_required")
333
+ if (!parsed ||
334
+ (parsed.kind !== "credential_required" && parsed.kind !== "external_tool_required")) {
237
335
  continue;
336
+ }
337
+ if (parsed.kind === "external_tool_required") {
338
+ const state = loadOnboardingState();
339
+ const descriptor = getProvider(parsed.provider);
340
+ if (state.providers[parsed.provider]?.status === "never_ask") {
341
+ return {
342
+ content: [
343
+ {
344
+ type: "text",
345
+ text: `${buildSkippedTag({
346
+ provider: parsed.provider,
347
+ reason: "credential_not_provided",
348
+ remediation: `run ${parsed.installCmd} and ${parsed.loginCmd ?? "rdt login"} to enable ${descriptor.displayName} (silenced)`,
349
+ silenced: true,
350
+ })}\n\n` +
351
+ `${descriptor.displayName} data was not fetched because you previously asked not to be reminded about this provider.`,
352
+ },
353
+ ],
354
+ };
355
+ }
356
+ const setupLabel = parsed.reason === "not_installed"
357
+ ? `Continue after installing ${descriptor.displayName}`
358
+ : `Continue after logging in to ${descriptor.displayName}`;
359
+ const skipLabel = `Skip ${descriptor.displayName} once`;
360
+ const neverLabel = `Always skip ${descriptor.displayName}`;
361
+ const installOrLogin = parsed.reason === "not_installed"
362
+ ? `Install command: ${parsed.installCmd}.`
363
+ : `Login command: ${parsed.loginCmd ?? "rdt login"}.`;
364
+ const questionBody = `${descriptor.displayName} requires a local external tool before this data can be fetched. ` +
365
+ `${installOrLogin} How would you like to proceed?`;
366
+ sessionPromptedSet.add(parsed.provider);
367
+ const promptResult = await promptUser(ctx, {
368
+ question: questionBody,
369
+ questionType: "select",
370
+ options: [setupLabel, skipLabel, neverLabel],
371
+ }, options?.askUserHandler);
372
+ const answer = promptResult.cancelled ? skipLabel : (promptResult.answer ?? skipLabel);
373
+ if (answer.startsWith("Always")) {
374
+ saveOnboardingState(markProviderNeverAsk(state, parsed.provider));
375
+ }
376
+ if (answer.startsWith("Continue")) {
377
+ return {
378
+ content: [
379
+ {
380
+ type: "text",
381
+ text: `${buildConnectedTag({ provider: parsed.provider })}\n\n` +
382
+ `${descriptor.displayName} setup was acknowledged. Re-run the original ${descriptor.displayName} request now; if setup is still unavailable, continue without ${descriptor.displayName} and disclose the source gap.`,
383
+ },
384
+ ],
385
+ };
386
+ }
387
+ const silenced = answer.startsWith("Always");
388
+ const remediation = silenced
389
+ ? `run ${parsed.installCmd} and ${parsed.loginCmd ?? "rdt login"} to enable ${descriptor.displayName} (silenced)`
390
+ : `run ${parsed.installCmd} and ${parsed.loginCmd ?? "rdt login"} to enable ${descriptor.displayName}`;
391
+ return {
392
+ content: [
393
+ {
394
+ type: "text",
395
+ text: `${buildSkippedTag({
396
+ provider: parsed.provider,
397
+ reason: "credential_not_provided",
398
+ remediation,
399
+ silenced,
400
+ })}\n\n${descriptor.displayName} data was omitted per your choice.`,
401
+ },
402
+ ],
403
+ };
404
+ }
238
405
  const state = loadOnboardingState();
239
406
  const action = resolveCredentialRequired({
240
407
  provider: parsed.provider,
@@ -396,11 +563,16 @@ export default function openCandleExtension(pi, options) {
396
563
  pi.on("input", async (event, ctx) => {
397
564
  if (event.source === "extension")
398
565
  return;
566
+ coordinator.clearTickerValidationCache();
399
567
  // Check for comprehensive analysis pattern — same in both modes.
400
568
  const analysis = isAnalysisRequest(event.text);
401
569
  if (analysis.match && analysis.symbol) {
402
- const definition = buildComprehensiveAnalysisDefinition(analysis.symbol, { debate: getConfig().debate });
570
+ const definition = buildComprehensiveAnalysisDefinition(analysis.symbol, {
571
+ debate: getConfig().debate,
572
+ });
403
573
  const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
574
+ if (prompt)
575
+ markOriginalInput(event.text);
404
576
  return prompt ? { action: "transform", text: prompt } : { action: "handled" };
405
577
  }
406
578
  const mode = getConfig().routerMode;
@@ -418,46 +590,177 @@ export default function openCandleExtension(pi, options) {
418
590
  const storage = coordinator.getStorage();
419
591
  const workflowPrefs = storage?.getWorkflowPreferences("global") ?? {};
420
592
  // Classify intent
421
- const classification = classifyWithLegacyRules(event.text);
593
+ let classification = classifyWithLegacyRules(event.text);
594
+ const ruleModeDisambiguation = disambiguateRulesModeSymbols(event.text, classification.entities.symbols);
595
+ appendSymbolDropEntries(ruleModeDisambiguation.dropped, "rules");
596
+ classification = {
597
+ ...classification,
598
+ entities: {
599
+ ...classification.entities,
600
+ symbols: ruleModeDisambiguation.kept,
601
+ },
602
+ };
603
+ if (isComparePrompt(event.text) &&
604
+ ruleModeDisambiguation.dropped.length > 0 &&
605
+ classification.entities.symbols.length < 2) {
606
+ pi.appendEntry("opencandle-workflow-aborted", {
607
+ reason: "symbol-disambiguation-insufficient-symbols",
608
+ dropped: ruleModeDisambiguation.dropped,
609
+ validSymbols: classification.entities.symbols,
610
+ });
611
+ const base = coordinator.buildRouterContextBase(ctx.sessionManager);
612
+ const output = {
613
+ routeKind: "clarification",
614
+ route: "fallback",
615
+ workflow: "compare_assets",
616
+ entities: classification.entities,
617
+ slots: {},
618
+ preference_updates: [],
619
+ missing_required: ["symbols"],
620
+ tool_bundles: ["clarification"],
621
+ diagnostics: ruleModeDisambiguation.dropped.map((drop) => ({
622
+ code: "symbol_dropped",
623
+ message: `${drop.token} dropped: ${drop.reason}`,
624
+ details: {
625
+ token: drop.token,
626
+ reason: drop.reason,
627
+ signalsChecked: drop.signalsChecked,
628
+ source: "rules",
629
+ },
630
+ })),
631
+ reasoning: "rules-mode acronym disambiguation left fewer than two symbols for comparison",
632
+ };
633
+ const resolvedTurnContext = buildResolvedTurnContext({ text: event.text, ...base }, output, {
634
+ availableToolNames: safeGetAllToolNames(),
635
+ planning: {
636
+ migrationStatuses: getConfig().planningMigrationStatuses,
637
+ },
638
+ });
639
+ coordinator.setPendingResolvedTurnContext({
640
+ ...resolvedTurnContext,
641
+ diagnostics: [
642
+ ...resolvedTurnContext.diagnostics,
643
+ {
644
+ code: "compare_workflow_aborted",
645
+ message: "compare workflow needs at least two validated symbols after acronym disambiguation",
646
+ },
647
+ ],
648
+ });
649
+ coordinator.setPendingFallbackContext({
650
+ assumptionsBlock: [
651
+ "Assumptions Context:",
652
+ classification.entities.symbols.length > 0
653
+ ? ` valid symbols: ${classification.entities.symbols.join(", ")} (user)`
654
+ : " valid symbols: (none)",
655
+ ` dropped ambiguous ticker-like tokens: ${ruleModeDisambiguation.dropped.map((d) => d.token).join(", ")} (no positive ticker signal)`,
656
+ ].join("\n"),
657
+ missingRequired: ["symbols"],
658
+ extraContext: "Dropped ambiguous ticker-like tokens: " +
659
+ `${ruleModeDisambiguation.dropped.map((d) => d.token).join(", ")}. ` +
660
+ "Ask the user which ticker symbols they want compared before calling comparison tools.",
661
+ });
662
+ applyRouteToolScope(resolvedTurnContext);
663
+ return undefined;
664
+ }
422
665
  if (classification.workflow === "portfolio_builder") {
423
666
  const resolution = resolvePortfolioSlots(classification.entities, workflowPrefs);
424
667
  coordinator.recordWorkflowRun("portfolio_builder", classification.entities, resolution.resolved, resolution.defaultsUsed);
425
- pi.appendEntry("opencandle-workflow", { workflow: "portfolio_builder", entities: classification.entities, resolved: resolution.resolved });
668
+ pi.appendEntry("opencandle-workflow", {
669
+ workflow: "portfolio_builder",
670
+ entities: classification.entities,
671
+ resolved: resolution.resolved,
672
+ });
426
673
  const definition = buildPortfolioWorkflowDefinition(resolution);
427
674
  const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
675
+ if (prompt)
676
+ markOriginalInput(event.text);
428
677
  return prompt ? { action: "transform", text: prompt } : { action: "handled" };
429
678
  }
430
679
  if (classification.workflow === "options_screener") {
431
680
  const resolution = resolveOptionsScreenerSlots(classification.entities, workflowPrefs);
432
681
  if (resolution.missingRequired.length === 0) {
433
682
  coordinator.recordWorkflowRun("options_screener", classification.entities, resolution.resolved, resolution.defaultsUsed);
434
- pi.appendEntry("opencandle-workflow", { workflow: "options_screener", entities: classification.entities, resolved: resolution.resolved });
683
+ pi.appendEntry("opencandle-workflow", {
684
+ workflow: "options_screener",
685
+ entities: classification.entities,
686
+ resolved: resolution.resolved,
687
+ });
435
688
  const definition = buildOptionsScreenerWorkflowDefinition(resolution);
436
689
  const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
690
+ if (prompt)
691
+ markOriginalInput(event.text);
437
692
  return prompt ? { action: "transform", text: prompt } : { action: "handled" };
438
693
  }
439
694
  }
440
- if (classification.workflow === "compare_assets" && classification.entities.symbols.length >= 2) {
695
+ if (classification.workflow === "compare_assets" &&
696
+ classification.entities.symbols.length >= 2) {
441
697
  const resolution = {
442
698
  resolved: {
443
699
  symbols: classification.entities.symbols,
444
700
  metrics: classification.entities.compareMetrics,
445
701
  timeHorizon: classification.entities.timeHorizon,
702
+ budget: classification.entities.budget,
703
+ assetScope: classification.entities.assetScope,
446
704
  },
447
705
  sources: {
448
706
  symbols: "user",
449
707
  ...(classification.entities.timeHorizon ? { timeHorizon: "user" } : {}),
450
708
  ...(classification.entities.compareMetrics ? { metrics: "user" } : {}),
709
+ ...(classification.entities.budget !== undefined ? { budget: "user" } : {}),
710
+ ...(classification.entities.assetScope ? { assetScope: "user" } : {}),
451
711
  },
452
712
  defaultsUsed: [],
453
713
  missingRequired: [],
454
714
  };
455
715
  coordinator.recordWorkflowRun("compare_assets", classification.entities, resolution.resolved, resolution.defaultsUsed);
456
- pi.appendEntry("opencandle-workflow", { workflow: "compare_assets", symbols: classification.entities.symbols });
457
- const definition = buildCompareAssetsWorkflowDefinition(resolution);
716
+ pi.appendEntry("opencandle-workflow", {
717
+ workflow: "compare_assets",
718
+ symbols: classification.entities.symbols,
719
+ });
720
+ const preflight = await preflightCompareResolution(resolution);
721
+ if (!preflight) {
722
+ coordinator.recordWorkflowRun("fallback", classification.entities, resolution.resolved, [], "clarification");
723
+ coordinator.setPendingFallbackContext({
724
+ assumptionsBlock: [
725
+ "Assumptions Context:",
726
+ ` original symbols: ${classification.entities.symbols.join(", ")} (user)`,
727
+ ].join("\n"),
728
+ missingRequired: ["symbols"],
729
+ extraContext: "Compare workflow aborted because ticker preflight left fewer than two valid symbols. Ask the user to clarify the intended tickers before calling comparison tools.",
730
+ });
731
+ return undefined;
732
+ }
733
+ const definition = buildCompareAssetsWorkflowDefinition(preflight.resolution);
734
+ applyPreflightAnnotation(definition, preflight.dropped);
458
735
  const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
736
+ if (prompt)
737
+ markOriginalInput(event.text);
459
738
  return prompt ? { action: "transform", text: prompt } : { action: "handled" };
460
739
  }
740
+ // Rules-mode finance fallback: no workflow dispatched, but the turn is
741
+ // finance-shaped (classified finance intent, extracted symbols, or finance
742
+ // vocabulary). Record the fallback turn and stash a fallback context so
743
+ // the system prompt carries saved market state for this turn; non-finance
744
+ // prompts stay untouched.
745
+ const isFinanceFallback = classification.workflow !== "unclassified" ||
746
+ classification.entities.symbols.length > 0 ||
747
+ hasFinanceSignals(event.text);
748
+ if (isFinanceFallback) {
749
+ coordinator.recordWorkflowRun("fallback", classification.entities, {}, [], "agent_task");
750
+ coordinator.setPendingFallbackContext({
751
+ assumptionsBlock: "",
752
+ missingRequired: [],
753
+ extraContext: classification.entities.symbols.length > 0
754
+ ? `Rules-router extracted symbols: ${classification.entities.symbols.join(", ")}.`
755
+ : undefined,
756
+ });
757
+ pi.appendEntry("opencandle-fallback-context", {
758
+ mode: "rules",
759
+ classifiedWorkflow: classification.workflow,
760
+ symbols: classification.entities.symbols,
761
+ });
762
+ }
763
+ return undefined;
461
764
  });
462
765
  /**
463
766
  * LLM-mode input handler. In this mode `classifyIntent` and
@@ -515,6 +818,7 @@ export default function openCandleExtension(pi, options) {
515
818
  },
516
819
  });
517
820
  pi.appendEntry("opencandle-router", { output });
821
+ appendRouterSymbolDropEntries(output);
518
822
  pi.appendEntry("opencandle-route-context", resolvedTurnContext);
519
823
  coordinator.setPendingResolvedTurnContext(resolvedTurnContext);
520
824
  applyRouteToolScope(resolvedTurnContext);
@@ -539,7 +843,7 @@ export default function openCandleExtension(pi, options) {
539
843
  }
540
844
  // Workflow dispatch for recognised workflows.
541
845
  if (output.routeKind === "workflow_dispatch" && output.workflow) {
542
- return dispatchRouterWorkflow(output, ctx);
846
+ return dispatchRouterWorkflow(output, ctx, text);
543
847
  }
544
848
  if (output.routeKind === "pass_through") {
545
849
  return false;
@@ -552,13 +856,13 @@ export default function openCandleExtension(pi, options) {
552
856
  assumptionsBlock,
553
857
  missingRequired: output.missing_required,
554
858
  extraContext: output.entities.symbols.length > 0
555
- ? `Router-extracted symbols: ${output.entities.symbols.join(", ")}.`
556
- + ` Route kind: ${output.routeKind}. Tool bundles: ${output.tool_bundles.join(", ") || "(none)"}.`
859
+ ? `Router-extracted symbols: ${output.entities.symbols.join(", ")}.` +
860
+ ` Route kind: ${output.routeKind}. Tool bundles: ${output.tool_bundles.join(", ") || "(none)"}.`
557
861
  : undefined,
558
862
  });
559
863
  return false;
560
864
  }
561
- function dispatchRouterWorkflow(output, ctx) {
865
+ async function dispatchRouterWorkflow(output, ctx, originalText) {
562
866
  const workflow = output.workflow;
563
867
  const storage = coordinator.getStorage();
564
868
  const workflowPrefs = storage?.getWorkflowPreferences("global") ?? {};
@@ -573,6 +877,8 @@ export default function openCandleExtension(pi, options) {
573
877
  });
574
878
  const definition = buildPortfolioWorkflowDefinition(resolution);
575
879
  const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
880
+ if (prompt)
881
+ markOriginalInput(originalText);
576
882
  return prompt ? { action: "transform", text: prompt } : false;
577
883
  }
578
884
  if (workflow === "options_screener") {
@@ -588,6 +894,8 @@ export default function openCandleExtension(pi, options) {
588
894
  });
589
895
  const definition = buildOptionsScreenerWorkflowDefinition(resolution);
590
896
  const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
897
+ if (prompt)
898
+ markOriginalInput(originalText);
591
899
  return prompt ? { action: "transform", text: prompt } : false;
592
900
  }
593
901
  // Missing required symbol — treat as fallback with ask_user directive.
@@ -598,11 +906,17 @@ export default function openCandleExtension(pi, options) {
598
906
  symbols: entities.symbols,
599
907
  metrics: entities.compareMetrics,
600
908
  timeHorizon: entities.timeHorizon,
909
+ budget: entities.budget,
910
+ assetScope: entities.assetScope,
601
911
  },
602
912
  sources: {
603
913
  symbols: sourceForRouterSlot(output, "symbols", "user"),
604
914
  ...(entities.timeHorizon ? { timeHorizon: "user" } : {}),
605
915
  ...(entities.compareMetrics ? { metrics: "user" } : {}),
916
+ ...(entities.budget !== undefined
917
+ ? { budget: sourceForRouterSlot(output, "budget", "user") }
918
+ : {}),
919
+ ...(entities.assetScope ? { assetScope: "user" } : {}),
606
920
  },
607
921
  defaultsUsed: [],
608
922
  missingRequired: [],
@@ -612,8 +926,22 @@ export default function openCandleExtension(pi, options) {
612
926
  workflow: "compare_assets",
613
927
  symbols: entities.symbols,
614
928
  });
615
- const definition = buildCompareAssetsWorkflowDefinition(resolution);
929
+ const preflight = await preflightCompareResolution(resolution);
930
+ if (!preflight) {
931
+ coordinator.recordWorkflowRun("fallback", output.entities, Object.fromEntries(Object.entries(output.slots).map(([k, v]) => [k, v.value])), [], output.routeKind);
932
+ coordinator.setPendingResolvedTurnContext(null);
933
+ coordinator.setPendingFallbackContext({
934
+ assumptionsBlock: buildAssumptionsBlockFromRouter(output.slots),
935
+ missingRequired: ["symbols"],
936
+ extraContext: "Compare workflow aborted because ticker preflight left fewer than two valid symbols. Ask the user to clarify the intended tickers.",
937
+ });
938
+ return false;
939
+ }
940
+ const definition = buildCompareAssetsWorkflowDefinition(preflight.resolution);
941
+ applyPreflightAnnotation(definition, preflight.dropped);
616
942
  const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
943
+ if (prompt)
944
+ markOriginalInput(originalText);
617
945
  return prompt ? { action: "transform", text: prompt } : false;
618
946
  }
619
947
  // single_asset_analysis / watchlist / general_qa + any workflow with
@@ -628,6 +956,89 @@ export default function openCandleExtension(pi, options) {
628
956
  });
629
957
  return false;
630
958
  }
959
+ function appendRouterSymbolDropEntries(output) {
960
+ for (const diagnostic of output.diagnostics) {
961
+ if (diagnostic.code !== "symbol_dropped")
962
+ continue;
963
+ const details = diagnostic.details ?? {};
964
+ appendSymbolDropEntries([
965
+ {
966
+ token: String(details.token ?? ""),
967
+ reason: String(details.reason ?? ""),
968
+ signalsChecked: Array.isArray(details.signalsChecked)
969
+ ? details.signalsChecked.map(String)
970
+ : [],
971
+ },
972
+ ], String(details.source ?? "llm"));
973
+ }
974
+ }
975
+ function appendSymbolDropEntries(dropped, source) {
976
+ for (const drop of dropped) {
977
+ pi.appendEntry("opencandle-symbol-dropped", {
978
+ token: drop.token,
979
+ reason: drop.reason,
980
+ signalsChecked: drop.signalsChecked,
981
+ source,
982
+ });
983
+ }
984
+ }
985
+ function disambiguateRulesModeSymbols(text, extractedSymbols) {
986
+ const candidates = mergeSymbols(extractedSymbols, rawTickerLikeTokens(text));
987
+ const disambiguated = disambiguateSymbols(candidates, text);
988
+ return {
989
+ kept: disambiguated.kept.filter((symbol) => extractedSymbols.includes(symbol)),
990
+ dropped: disambiguated.dropped,
991
+ };
992
+ }
993
+ function rawTickerLikeTokens(text) {
994
+ const tokens = [];
995
+ for (const match of text.matchAll(/\$?([A-Za-z]{1,5})\b/g)) {
996
+ const raw = match[1];
997
+ if (raw !== raw.toUpperCase())
998
+ continue;
999
+ const token = raw.toUpperCase();
1000
+ if (!tokens.includes(token))
1001
+ tokens.push(token);
1002
+ }
1003
+ return tokens;
1004
+ }
1005
+ function isComparePrompt(text) {
1006
+ return /\b(?:compare|vs\.?|versus|which\s+is\s+better)\b/i.test(text);
1007
+ }
1008
+ async function preflightCompareResolution(resolution) {
1009
+ const result = await preflightSymbols(resolution.resolved.symbols, {
1010
+ cache: coordinator.getTickerValidationCache(),
1011
+ search: options?.symbolSearch,
1012
+ });
1013
+ for (const drop of result.dropped) {
1014
+ pi.appendEntry("opencandle-symbol-preflight-dropped", drop);
1015
+ }
1016
+ if (result.valid.length < 2) {
1017
+ pi.appendEntry("opencandle-workflow-aborted", {
1018
+ reason: "preflight-insufficient-symbols",
1019
+ dropped: result.dropped,
1020
+ });
1021
+ return null;
1022
+ }
1023
+ return {
1024
+ resolution: {
1025
+ ...resolution,
1026
+ resolved: {
1027
+ ...resolution.resolved,
1028
+ symbols: result.valid,
1029
+ },
1030
+ },
1031
+ dropped: result.dropped,
1032
+ };
1033
+ }
1034
+ function applyPreflightAnnotation(definition, dropped) {
1035
+ if (dropped.length === 0 || definition.steps.length === 0)
1036
+ return;
1037
+ definition.steps[0] = {
1038
+ ...definition.steps[0],
1039
+ prompt: `${formatPreflightDropAnnotation(dropped)}\n\n${definition.steps[0].prompt}`,
1040
+ };
1041
+ }
631
1042
  function mergeRouterSlotsIntoEntities(output) {
632
1043
  const entities = {
633
1044
  ...output.entities,
@@ -636,12 +1047,25 @@ export default function openCandleExtension(pi, options) {
636
1047
  if (entities.budget === undefined && typeof output.slots.budget?.value === "number") {
637
1048
  entities.budget = output.slots.budget.value;
638
1049
  }
639
- const slotSymbols = symbolsFromRouterSlots(output);
1050
+ const droppedSymbols = droppedSymbolsFromDiagnostics(output);
1051
+ const slotSymbols = symbolsFromRouterSlots(output).filter((symbol) => !droppedSymbols.has(symbol));
640
1052
  if (slotSymbols.length > 0 && slotSymbols.length > entities.symbols.length) {
641
1053
  entities.symbols = mergeSymbols(slotSymbols, entities.symbols);
642
1054
  }
643
1055
  return entities;
644
1056
  }
1057
+ function droppedSymbolsFromDiagnostics(output) {
1058
+ const dropped = new Set();
1059
+ for (const diagnostic of output.diagnostics) {
1060
+ if (diagnostic.code !== "symbol_dropped")
1061
+ continue;
1062
+ const token = diagnostic.details?.token;
1063
+ if (typeof token === "string" && token.trim() !== "") {
1064
+ dropped.add(token.toUpperCase());
1065
+ }
1066
+ }
1067
+ return dropped;
1068
+ }
645
1069
  function withRouterSlotSources(resolution, output) {
646
1070
  const sources = { ...resolution.sources };
647
1071
  if (output.entities.budget === undefined && output.slots.budget) {
@@ -770,8 +1194,44 @@ export default function openCandleExtension(pi, options) {
770
1194
  const fallbackContext = coordinator.consumePendingFallbackContext() ?? undefined;
771
1195
  const resolvedTurnContext = coordinator.consumePendingResolvedTurnContext() ?? undefined;
772
1196
  return {
773
- systemPrompt: coordinator.buildSystemPrompt(event.systemPrompt, undefined, fallbackContext, resolvedTurnContext),
1197
+ systemPrompt: coordinator.buildSystemPrompt(event.systemPrompt, coordinator.getActiveWorkflowType(), fallbackContext, resolvedTurnContext),
774
1198
  };
775
1199
  });
776
1200
  }
1201
+ /** Concatenate text from a Pi message `content` (plain string or block array). */
1202
+ function extractMessageText(content) {
1203
+ if (typeof content === "string")
1204
+ return content;
1205
+ if (!Array.isArray(content))
1206
+ return "";
1207
+ let text = "";
1208
+ for (const block of content) {
1209
+ if (block &&
1210
+ typeof block === "object" &&
1211
+ block.type === "text" &&
1212
+ typeof block.text === "string") {
1213
+ text += block.text;
1214
+ }
1215
+ }
1216
+ return text;
1217
+ }
1218
+ /**
1219
+ * True when the session name is still an auto-set placeholder that the LLM
1220
+ * title may replace: unset, exactly one of the candidate user texts, or the
1221
+ * GUI server's truncated "first 77 chars + ..." form of one of them.
1222
+ */
1223
+ function isPlaceholderSessionName(name, candidates) {
1224
+ if (name === undefined || name.trim().length === 0)
1225
+ return true;
1226
+ const trimmed = name.trim();
1227
+ for (const candidate of candidates) {
1228
+ const candidateTrimmed = candidate.trim();
1229
+ if (trimmed === candidateTrimmed)
1230
+ return true;
1231
+ if (trimmed.endsWith("...") && candidateTrimmed.startsWith(trimmed.slice(0, -3))) {
1232
+ return true;
1233
+ }
1234
+ }
1235
+ return false;
1236
+ }
777
1237
  //# sourceMappingURL=opencandle-extension.js.map