opencandle 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (564) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +186 -117
  3. package/dist/analysts/contracts.d.ts +1 -3
  4. package/dist/analysts/contracts.js +1 -11
  5. package/dist/analysts/contracts.js.map +1 -1
  6. package/dist/analysts/orchestrator.d.ts +1 -3
  7. package/dist/analysts/orchestrator.js +1 -26
  8. package/dist/analysts/orchestrator.js.map +1 -1
  9. package/dist/cli.js +32 -8
  10. package/dist/cli.js.map +1 -1
  11. package/dist/config.d.ts +19 -3
  12. package/dist/config.js +69 -3
  13. package/dist/config.js.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/infra/browser.d.ts +1 -3
  18. package/dist/infra/browser.js +4 -2
  19. package/dist/infra/browser.js.map +1 -1
  20. package/dist/infra/cache.d.ts +8 -11
  21. package/dist/infra/cache.js +17 -15
  22. package/dist/infra/cache.js.map +1 -1
  23. package/dist/infra/http-client.d.ts +4 -1
  24. package/dist/infra/http-client.js +59 -6
  25. package/dist/infra/http-client.js.map +1 -1
  26. package/dist/infra/index.d.ts +3 -3
  27. package/dist/infra/index.js +3 -3
  28. package/dist/infra/index.js.map +1 -1
  29. package/dist/infra/native-dependencies.js +2 -2
  30. package/dist/infra/native-dependencies.js.map +1 -1
  31. package/dist/infra/node-version.js.map +1 -1
  32. package/dist/infra/opencandle-paths.d.ts +0 -3
  33. package/dist/infra/opencandle-paths.js +4 -11
  34. package/dist/infra/opencandle-paths.js.map +1 -1
  35. package/dist/infra/rate-limiter.d.ts +4 -0
  36. package/dist/infra/rate-limiter.js +17 -10
  37. package/dist/infra/rate-limiter.js.map +1 -1
  38. package/dist/market-state/alert-conditions.d.ts +34 -0
  39. package/dist/market-state/alert-conditions.js +23 -0
  40. package/dist/market-state/alert-conditions.js.map +1 -0
  41. package/dist/market-state/alert-runner.d.ts +55 -0
  42. package/dist/market-state/alert-runner.js +634 -0
  43. package/dist/market-state/alert-runner.js.map +1 -0
  44. package/dist/market-state/daily-report.d.ts +26 -0
  45. package/dist/market-state/daily-report.js +179 -0
  46. package/dist/market-state/daily-report.js.map +1 -0
  47. package/dist/market-state/local-automation-service.d.ts +25 -0
  48. package/dist/market-state/local-automation-service.js +119 -0
  49. package/dist/market-state/local-automation-service.js.map +1 -0
  50. package/dist/market-state/notification-delivery.d.ts +14 -0
  51. package/dist/market-state/notification-delivery.js +139 -0
  52. package/dist/market-state/notification-delivery.js.map +1 -0
  53. package/dist/market-state/resolve-for-mutation.d.ts +10 -0
  54. package/dist/market-state/resolve-for-mutation.js +15 -0
  55. package/dist/market-state/resolve-for-mutation.js.map +1 -0
  56. package/dist/market-state/resolve.d.ts +14 -0
  57. package/dist/market-state/resolve.js +89 -0
  58. package/dist/market-state/resolve.js.map +1 -0
  59. package/dist/market-state/service.d.ts +527 -0
  60. package/dist/market-state/service.js +1099 -0
  61. package/dist/market-state/service.js.map +1 -0
  62. package/dist/memory/index.d.ts +7 -7
  63. package/dist/memory/index.js +6 -6
  64. package/dist/memory/index.js.map +1 -1
  65. package/dist/memory/manager.d.ts +9 -0
  66. package/dist/memory/manager.js +39 -22
  67. package/dist/memory/manager.js.map +1 -1
  68. package/dist/memory/retrieval.js +7 -4
  69. package/dist/memory/retrieval.js.map +1 -1
  70. package/dist/memory/sqlite.js +385 -3
  71. package/dist/memory/sqlite.js.map +1 -1
  72. package/dist/memory/storage.d.ts +3 -2
  73. package/dist/memory/storage.js +1 -2
  74. package/dist/memory/storage.js.map +1 -1
  75. package/dist/memory/tool-defaults.js +64 -28
  76. package/dist/memory/tool-defaults.js.map +1 -1
  77. package/dist/memory/types.js +4 -0
  78. package/dist/memory/types.js.map +1 -1
  79. package/dist/monitor.d.ts +2 -0
  80. package/dist/monitor.js +104 -0
  81. package/dist/monitor.js.map +1 -0
  82. package/dist/onboarding/connect.js +4 -6
  83. package/dist/onboarding/connect.js.map +1 -1
  84. package/dist/onboarding/credential-interceptor.js +1 -1
  85. package/dist/onboarding/credential-interceptor.js.map +1 -1
  86. package/dist/onboarding/degradation-accumulator.js +1 -3
  87. package/dist/onboarding/degradation-accumulator.js.map +1 -1
  88. package/dist/onboarding/providers.js +3 -16
  89. package/dist/onboarding/providers.js.map +1 -1
  90. package/dist/onboarding/state.js.map +1 -1
  91. package/dist/onboarding/tool-helpers.js +1 -1
  92. package/dist/onboarding/tool-helpers.js.map +1 -1
  93. package/dist/onboarding/tool-tags.js +6 -4
  94. package/dist/onboarding/tool-tags.js.map +1 -1
  95. package/dist/onboarding/validation.js +1 -1
  96. package/dist/onboarding/validation.js.map +1 -1
  97. package/dist/pi/opencandle-extension.d.ts +8 -0
  98. package/dist/pi/opencandle-extension.js +637 -59
  99. package/dist/pi/opencandle-extension.js.map +1 -1
  100. package/dist/pi/session.d.ts +1 -1
  101. package/dist/pi/session.js +3 -1
  102. package/dist/pi/session.js.map +1 -1
  103. package/dist/pi/setup.js +17 -2
  104. package/dist/pi/setup.js.map +1 -1
  105. package/dist/pi/tool-adapter.js +5 -2
  106. package/dist/pi/tool-adapter.js.map +1 -1
  107. package/dist/prompts/context-builder.d.ts +18 -3
  108. package/dist/prompts/context-builder.js +117 -18
  109. package/dist/prompts/context-builder.js.map +1 -1
  110. package/dist/prompts/disclaimer.js +1 -1
  111. package/dist/prompts/disclaimer.js.map +1 -1
  112. package/dist/prompts/policy-cards.d.ts +13 -0
  113. package/dist/prompts/policy-cards.js +197 -0
  114. package/dist/prompts/policy-cards.js.map +1 -0
  115. package/dist/prompts/sections.d.ts +1 -1
  116. package/dist/prompts/sections.js +3 -3
  117. package/dist/prompts/sections.js.map +1 -1
  118. package/dist/prompts/symbol-preflight.d.ts +20 -0
  119. package/dist/prompts/symbol-preflight.js +49 -0
  120. package/dist/prompts/symbol-preflight.js.map +1 -0
  121. package/dist/prompts/workflow-prompts.d.ts +1 -1
  122. package/dist/prompts/workflow-prompts.js +209 -19
  123. package/dist/prompts/workflow-prompts.js.map +1 -1
  124. package/dist/providers/alpha-vantage.d.ts +1 -1
  125. package/dist/providers/alpha-vantage.js +49 -8
  126. package/dist/providers/alpha-vantage.js.map +1 -1
  127. package/dist/providers/coingecko.js +1 -1
  128. package/dist/providers/coingecko.js.map +1 -1
  129. package/dist/providers/errors.d.ts +5 -0
  130. package/dist/providers/errors.js +11 -0
  131. package/dist/providers/errors.js.map +1 -0
  132. package/dist/providers/exa-search.d.ts +2 -2
  133. package/dist/providers/exa-search.js +19 -11
  134. package/dist/providers/exa-search.js.map +1 -1
  135. package/dist/providers/fear-greed.js +1 -1
  136. package/dist/providers/fear-greed.js.map +1 -1
  137. package/dist/providers/finnhub.js +3 -5
  138. package/dist/providers/finnhub.js.map +1 -1
  139. package/dist/providers/fred.js +2 -2
  140. package/dist/providers/fred.js.map +1 -1
  141. package/dist/providers/index.d.ts +7 -6
  142. package/dist/providers/index.js +6 -5
  143. package/dist/providers/index.js.map +1 -1
  144. package/dist/providers/reddit.js +2 -2
  145. package/dist/providers/reddit.js.map +1 -1
  146. package/dist/providers/sec-edgar.d.ts +9 -1
  147. package/dist/providers/sec-edgar.js +181 -6
  148. package/dist/providers/sec-edgar.js.map +1 -1
  149. package/dist/providers/tradingview.d.ts +47 -0
  150. package/dist/providers/tradingview.js +275 -0
  151. package/dist/providers/tradingview.js.map +1 -0
  152. package/dist/providers/twitter.js +6 -8
  153. package/dist/providers/twitter.js.map +1 -1
  154. package/dist/providers/web-search.js +26 -12
  155. package/dist/providers/web-search.js.map +1 -1
  156. package/dist/providers/with-fallback.js +4 -2
  157. package/dist/providers/with-fallback.js.map +1 -1
  158. package/dist/providers/wrap-provider.d.ts +2 -3
  159. package/dist/providers/wrap-provider.js +14 -8
  160. package/dist/providers/wrap-provider.js.map +1 -1
  161. package/dist/providers/yahoo-finance.d.ts +3 -1
  162. package/dist/providers/yahoo-finance.js +226 -11
  163. package/dist/providers/yahoo-finance.js.map +1 -1
  164. package/dist/routing/classify-intent.d.ts +9 -0
  165. package/dist/routing/classify-intent.js +153 -3
  166. package/dist/routing/classify-intent.js.map +1 -1
  167. package/dist/routing/defaults.d.ts +1 -1
  168. package/dist/routing/defaults.js +3 -3
  169. package/dist/routing/defaults.js.map +1 -1
  170. package/dist/routing/entity-extractor.d.ts +2 -0
  171. package/dist/routing/entity-extractor.js +377 -26
  172. package/dist/routing/entity-extractor.js.map +1 -1
  173. package/dist/routing/fund-symbols.d.ts +2 -0
  174. package/dist/routing/fund-symbols.js +55 -0
  175. package/dist/routing/fund-symbols.js.map +1 -0
  176. package/dist/routing/horizon.d.ts +1 -0
  177. package/dist/routing/horizon.js +10 -0
  178. package/dist/routing/horizon.js.map +1 -0
  179. package/dist/routing/index.d.ts +12 -6
  180. package/dist/routing/index.js +8 -4
  181. package/dist/routing/index.js.map +1 -1
  182. package/dist/routing/legacy-rule-router.d.ts +9 -0
  183. package/dist/routing/legacy-rule-router.js +12 -0
  184. package/dist/routing/legacy-rule-router.js.map +1 -0
  185. package/dist/routing/planning.d.ts +54 -0
  186. package/dist/routing/planning.js +562 -0
  187. package/dist/routing/planning.js.map +1 -0
  188. package/dist/routing/route-manifest.d.ts +35 -0
  189. package/dist/routing/route-manifest.js +242 -0
  190. package/dist/routing/route-manifest.js.map +1 -0
  191. package/dist/routing/router-llm-client.js.map +1 -1
  192. package/dist/routing/router-prompt.js +46 -45
  193. package/dist/routing/router-prompt.js.map +1 -1
  194. package/dist/routing/router-types.d.ts +10 -0
  195. package/dist/routing/router.d.ts +1 -0
  196. package/dist/routing/router.js +572 -13
  197. package/dist/routing/router.js.map +1 -1
  198. package/dist/routing/slot-resolver.d.ts +1 -1
  199. package/dist/routing/slot-resolver.js +45 -7
  200. package/dist/routing/slot-resolver.js.map +1 -1
  201. package/dist/routing/symbol-disambiguator.d.ts +11 -0
  202. package/dist/routing/symbol-disambiguator.js +52 -0
  203. package/dist/routing/symbol-disambiguator.js.map +1 -0
  204. package/dist/routing/turn-context.d.ts +44 -0
  205. package/dist/routing/turn-context.js +45 -0
  206. package/dist/routing/turn-context.js.map +1 -0
  207. package/dist/routing/types.d.ts +15 -1
  208. package/dist/runtime/answer-contracts.d.ts +82 -0
  209. package/dist/runtime/answer-contracts.js +442 -0
  210. package/dist/runtime/answer-contracts.js.map +1 -0
  211. package/dist/runtime/artifact-contracts.d.ts +14 -0
  212. package/dist/runtime/artifact-contracts.js +57 -0
  213. package/dist/runtime/artifact-contracts.js.map +1 -0
  214. package/dist/runtime/planning-evidence.d.ts +99 -0
  215. package/dist/runtime/planning-evidence.js +466 -0
  216. package/dist/runtime/planning-evidence.js.map +1 -0
  217. package/dist/runtime/prompt-step.d.ts +1 -9
  218. package/dist/runtime/prompt-step.js +0 -10
  219. package/dist/runtime/prompt-step.js.map +1 -1
  220. package/dist/runtime/run-context.d.ts +5 -2
  221. package/dist/runtime/run-context.js +8 -1
  222. package/dist/runtime/run-context.js.map +1 -1
  223. package/dist/runtime/session-coordinator.d.ts +29 -3
  224. package/dist/runtime/session-coordinator.js +204 -31
  225. package/dist/runtime/session-coordinator.js.map +1 -1
  226. package/dist/runtime/session-title.d.ts +14 -0
  227. package/dist/runtime/session-title.js +50 -0
  228. package/dist/runtime/session-title.js.map +1 -0
  229. package/dist/runtime/tool-defaults-wrapper.js +1 -3
  230. package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
  231. package/dist/runtime/validation.js.map +1 -1
  232. package/dist/runtime/workflow-events.js.map +1 -1
  233. package/dist/runtime/workflow-runner.d.ts +3 -3
  234. package/dist/runtime/workflow-runner.js +1 -1
  235. package/dist/runtime/workflow-runner.js.map +1 -1
  236. package/dist/sentiment/adapters/finnhub.d.ts +1 -1
  237. package/dist/sentiment/adapters/finnhub.js +6 -1
  238. package/dist/sentiment/adapters/finnhub.js.map +1 -1
  239. package/dist/sentiment/adapters/reddit.d.ts +2 -2
  240. package/dist/sentiment/adapters/twitter.d.ts +1 -1
  241. package/dist/sentiment/adapters/web.d.ts +1 -1
  242. package/dist/sentiment/index.d.ts +9 -11
  243. package/dist/sentiment/index.js +9 -20
  244. package/dist/sentiment/index.js.map +1 -1
  245. package/dist/sentiment/keywords.js +26 -4
  246. package/dist/sentiment/keywords.js.map +1 -1
  247. package/dist/sentiment/pipeline.d.ts +2 -2
  248. package/dist/sentiment/pipeline.js +1 -1
  249. package/dist/sentiment/pipeline.js.map +1 -1
  250. package/dist/sentiment/scorer.js +1 -1
  251. package/dist/sentiment/store.d.ts +1 -1
  252. package/dist/sentiment/store.js +1 -1
  253. package/dist/sentiment/store.js.map +1 -1
  254. package/dist/sentiment/trends.d.ts +1 -1
  255. package/dist/sentiment/trends.js.map +1 -1
  256. package/dist/sentiment/types.js.map +1 -1
  257. package/dist/system-prompt.js +7 -3
  258. package/dist/system-prompt.js.map +1 -1
  259. package/dist/tool-kit.d.ts +7 -7
  260. package/dist/tool-kit.js +4 -4
  261. package/dist/tool-kit.js.map +1 -1
  262. package/dist/tools/fundamentals/company-overview.js +12 -7
  263. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  264. package/dist/tools/fundamentals/comps.js +19 -10
  265. package/dist/tools/fundamentals/comps.js.map +1 -1
  266. package/dist/tools/fundamentals/dcf.js +24 -12
  267. package/dist/tools/fundamentals/dcf.js.map +1 -1
  268. package/dist/tools/fundamentals/earnings.js +9 -4
  269. package/dist/tools/fundamentals/earnings.js.map +1 -1
  270. package/dist/tools/fundamentals/financials.js +9 -4
  271. package/dist/tools/fundamentals/financials.js.map +1 -1
  272. package/dist/tools/fundamentals/sec-filings.d.ts +1 -0
  273. package/dist/tools/fundamentals/sec-filings.js +36 -4
  274. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  275. package/dist/tools/index.d.ts +23 -18
  276. package/dist/tools/index.js +53 -38
  277. package/dist/tools/index.js.map +1 -1
  278. package/dist/tools/interaction/ask-user.js +15 -3
  279. package/dist/tools/interaction/ask-user.js.map +1 -1
  280. package/dist/tools/interaction/twitter-login.js +13 -3
  281. package/dist/tools/interaction/twitter-login.js.map +1 -1
  282. package/dist/tools/macro/fear-greed.js +1 -1
  283. package/dist/tools/macro/fear-greed.js.map +1 -1
  284. package/dist/tools/macro/fred-data.d.ts +1 -1
  285. package/dist/tools/macro/fred-data.js +44 -9
  286. package/dist/tools/macro/fred-data.js.map +1 -1
  287. package/dist/tools/market/crypto-history.js +21 -3
  288. package/dist/tools/market/crypto-history.js.map +1 -1
  289. package/dist/tools/market/crypto-price.js +4 -2
  290. package/dist/tools/market/crypto-price.js.map +1 -1
  291. package/dist/tools/market/screen-stocks.d.ts +18 -0
  292. package/dist/tools/market/screen-stocks.js +252 -0
  293. package/dist/tools/market/screen-stocks.js.map +1 -0
  294. package/dist/tools/market/search-ticker.js +161 -9
  295. package/dist/tools/market/search-ticker.js.map +1 -1
  296. package/dist/tools/market/stock-history.d.ts +2 -2
  297. package/dist/tools/market/stock-history.js +27 -8
  298. package/dist/tools/market/stock-history.js.map +1 -1
  299. package/dist/tools/market/stock-quote.js +6 -4
  300. package/dist/tools/market/stock-quote.js.map +1 -1
  301. package/dist/tools/options/greeks.js +1 -2
  302. package/dist/tools/options/greeks.js.map +1 -1
  303. package/dist/tools/options/option-chain.js +27 -9
  304. package/dist/tools/options/option-chain.js.map +1 -1
  305. package/dist/tools/portfolio/alerts.d.ts +15 -0
  306. package/dist/tools/portfolio/alerts.js +357 -0
  307. package/dist/tools/portfolio/alerts.js.map +1 -0
  308. package/dist/tools/portfolio/correlation.d.ts +1 -1
  309. package/dist/tools/portfolio/correlation.js +34 -14
  310. package/dist/tools/portfolio/correlation.js.map +1 -1
  311. package/dist/tools/portfolio/daily-report.d.ts +8 -0
  312. package/dist/tools/portfolio/daily-report.js +83 -0
  313. package/dist/tools/portfolio/daily-report.js.map +1 -0
  314. package/dist/tools/portfolio/holdings-overlap.d.ts +8 -0
  315. package/dist/tools/portfolio/holdings-overlap.js +112 -0
  316. package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
  317. package/dist/tools/portfolio/notifications.d.ts +7 -0
  318. package/dist/tools/portfolio/notifications.js +43 -0
  319. package/dist/tools/portfolio/notifications.js.map +1 -0
  320. package/dist/tools/portfolio/predictions.d.ts +12 -6
  321. package/dist/tools/portfolio/predictions.js +338 -88
  322. package/dist/tools/portfolio/predictions.js.map +1 -1
  323. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  324. package/dist/tools/portfolio/risk-analysis.js +46 -7
  325. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  326. package/dist/tools/portfolio/tracker.d.ts +4 -3
  327. package/dist/tools/portfolio/tracker.js +247 -102
  328. package/dist/tools/portfolio/tracker.js.map +1 -1
  329. package/dist/tools/portfolio/watchlist.d.ts +6 -4
  330. package/dist/tools/portfolio/watchlist.js +209 -101
  331. package/dist/tools/portfolio/watchlist.js.map +1 -1
  332. package/dist/tools/sentiment/reddit-sentiment.js +24 -11
  333. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  334. package/dist/tools/sentiment/sentiment-summary.js +71 -14
  335. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  336. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  337. package/dist/tools/sentiment/sentiment-trend.js +12 -2
  338. package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
  339. package/dist/tools/sentiment/twitter-sentiment.js +13 -6
  340. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  341. package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
  342. package/dist/tools/sentiment/untrusted-text.js +17 -0
  343. package/dist/tools/sentiment/untrusted-text.js.map +1 -0
  344. package/dist/tools/sentiment/web-search.js +37 -12
  345. package/dist/tools/sentiment/web-search.js.map +1 -1
  346. package/dist/tools/sentiment/web-sentiment.js +16 -4
  347. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  348. package/dist/tools/technical/backtest.d.ts +3 -3
  349. package/dist/tools/technical/backtest.js +65 -44
  350. package/dist/tools/technical/backtest.js.map +1 -1
  351. package/dist/tools/technical/indicators.js +24 -8
  352. package/dist/tools/technical/indicators.js.map +1 -1
  353. package/dist/types/index.d.ts +3 -3
  354. package/dist/types/index.js.map +1 -1
  355. package/dist/types/market.d.ts +1 -0
  356. package/dist/types/options.d.ts +10 -0
  357. package/dist/types/portfolio.d.ts +41 -4
  358. package/dist/workflows/compare-assets.d.ts +0 -3
  359. package/dist/workflows/compare-assets.js +55 -10
  360. package/dist/workflows/compare-assets.js.map +1 -1
  361. package/dist/workflows/index.d.ts +3 -4
  362. package/dist/workflows/index.js +3 -3
  363. package/dist/workflows/index.js.map +1 -1
  364. package/dist/workflows/options-screener.d.ts +0 -3
  365. package/dist/workflows/options-screener.js +88 -14
  366. package/dist/workflows/options-screener.js.map +1 -1
  367. package/dist/workflows/portfolio-builder.d.ts +0 -3
  368. package/dist/workflows/portfolio-builder.js +7 -11
  369. package/dist/workflows/portfolio-builder.js.map +1 -1
  370. package/gui/server/ask-user-bridge.ts +82 -0
  371. package/gui/server/automation-heartbeat.ts +97 -0
  372. package/gui/server/background-quotes.ts +97 -1
  373. package/gui/server/chat-event-adapter.ts +32 -10
  374. package/gui/server/chat-run-session.ts +16 -0
  375. package/gui/server/gui-session-manager.ts +5 -0
  376. package/gui/server/invoke-tool.ts +144 -1
  377. package/gui/server/live-chat-event-adapter.ts +21 -6
  378. package/gui/server/market-state-api.ts +315 -0
  379. package/gui/server/model-setup.ts +149 -2
  380. package/gui/server/private-api-access.ts +62 -0
  381. package/gui/server/projector.ts +58 -11
  382. package/gui/server/prompt-observation.ts +58 -0
  383. package/gui/server/quote-snapshot-store.ts +50 -0
  384. package/gui/server/server.ts +236 -376
  385. package/gui/server/session-actions.ts +186 -1
  386. package/gui/server/session-entry-wait.ts +81 -0
  387. package/gui/server/shutdown.ts +47 -0
  388. package/gui/server/tool-invoke-ack.ts +49 -0
  389. package/gui/server/tool-metadata.ts +23 -10
  390. package/gui/server/websocket.ts +13 -3
  391. package/gui/server/writer-lock.ts +6 -2
  392. package/gui/server/ws-hub.ts +292 -0
  393. package/gui/shared/chat-events.ts +16 -1
  394. package/gui/shared/event-reducer.ts +24 -6
  395. package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +1 -0
  396. package/gui/web/dist/assets/index-2KZtKBmu.css +1 -0
  397. package/gui/web/dist/assets/index-CveNgtDg.js +69 -0
  398. package/gui/web/dist/index.html +2 -2
  399. package/package.json +22 -12
  400. package/src/analysts/contracts.ts +10 -23
  401. package/src/analysts/orchestrator.ts +8 -43
  402. package/src/cli.ts +37 -13
  403. package/src/config.ts +99 -7
  404. package/src/index.ts +1 -1
  405. package/src/infra/browser.ts +4 -2
  406. package/src/infra/cache.ts +41 -30
  407. package/src/infra/http-client.ts +72 -6
  408. package/src/infra/index.ts +7 -10
  409. package/src/infra/native-dependencies.ts +8 -3
  410. package/src/infra/node-version.ts +3 -1
  411. package/src/infra/opencandle-paths.ts +3 -14
  412. package/src/infra/rate-limiter.ts +32 -20
  413. package/src/market-state/alert-conditions.ts +82 -0
  414. package/src/market-state/alert-runner.ts +863 -0
  415. package/src/market-state/daily-report.ts +247 -0
  416. package/src/market-state/local-automation-service.ts +162 -0
  417. package/src/market-state/notification-delivery.ts +158 -0
  418. package/src/market-state/resolve-for-mutation.ts +24 -0
  419. package/src/market-state/resolve.ts +112 -0
  420. package/src/market-state/service.ts +2344 -0
  421. package/src/memory/index.ts +7 -7
  422. package/src/memory/manager.ts +57 -26
  423. package/src/memory/retrieval.ts +8 -7
  424. package/src/memory/sqlite.ts +407 -6
  425. package/src/memory/storage.ts +8 -17
  426. package/src/memory/tool-defaults.ts +60 -39
  427. package/src/memory/types.ts +7 -3
  428. package/src/monitor.ts +121 -0
  429. package/src/onboarding/connect.ts +10 -33
  430. package/src/onboarding/credential-interceptor.ts +3 -15
  431. package/src/onboarding/degradation-accumulator.ts +1 -3
  432. package/src/onboarding/providers.ts +9 -40
  433. package/src/onboarding/state.ts +4 -15
  434. package/src/onboarding/tool-helpers.ts +2 -9
  435. package/src/onboarding/tool-tags.ts +6 -6
  436. package/src/onboarding/validation.ts +14 -20
  437. package/src/pi/opencandle-extension.ts +795 -120
  438. package/src/pi/session.ts +7 -5
  439. package/src/pi/setup.ts +61 -33
  440. package/src/pi/tool-adapter.ts +5 -2
  441. package/src/prompts/context-builder.ts +143 -21
  442. package/src/prompts/disclaimer.ts +1 -1
  443. package/src/prompts/policy-cards.ts +220 -0
  444. package/src/prompts/sections.ts +4 -4
  445. package/src/prompts/symbol-preflight.ts +80 -0
  446. package/src/prompts/workflow-prompts.ts +231 -28
  447. package/src/providers/alpha-vantage.ts +82 -40
  448. package/src/providers/coingecko.ts +2 -5
  449. package/src/providers/errors.ts +9 -0
  450. package/src/providers/exa-search.ts +24 -22
  451. package/src/providers/fear-greed.ts +1 -1
  452. package/src/providers/finnhub.ts +7 -6
  453. package/src/providers/fred.ts +3 -3
  454. package/src/providers/index.ts +14 -6
  455. package/src/providers/reddit.ts +17 -6
  456. package/src/providers/sec-edgar.ts +235 -5
  457. package/src/providers/tradingview.ts +399 -0
  458. package/src/providers/twitter.ts +6 -8
  459. package/src/providers/web-search.ts +30 -20
  460. package/src/providers/with-fallback.ts +8 -7
  461. package/src/providers/wrap-provider.ts +15 -10
  462. package/src/providers/yahoo-finance.ts +292 -20
  463. package/src/routing/classify-intent.ts +186 -4
  464. package/src/routing/defaults.ts +4 -4
  465. package/src/routing/entity-extractor.ts +428 -28
  466. package/src/routing/fund-symbols.ts +58 -0
  467. package/src/routing/horizon.ts +7 -0
  468. package/src/routing/index.ts +60 -16
  469. package/src/routing/legacy-rule-router.ts +13 -0
  470. package/src/routing/planning.ts +823 -0
  471. package/src/routing/route-manifest.ts +309 -0
  472. package/src/routing/router-llm-client.ts +4 -4
  473. package/src/routing/router-prompt.ts +52 -52
  474. package/src/routing/router-types.ts +18 -0
  475. package/src/routing/router.ts +717 -20
  476. package/src/routing/slot-resolver.ts +75 -14
  477. package/src/routing/symbol-disambiguator.ts +72 -0
  478. package/src/routing/turn-context.ts +108 -0
  479. package/src/routing/types.ts +15 -1
  480. package/src/runtime/answer-contracts.ts +672 -0
  481. package/src/runtime/artifact-contracts.ts +77 -0
  482. package/src/runtime/planning-evidence.ts +682 -0
  483. package/src/runtime/prompt-step.ts +1 -16
  484. package/src/runtime/run-context.ts +12 -2
  485. package/src/runtime/session-coordinator.ts +297 -56
  486. package/src/runtime/session-title.ts +60 -0
  487. package/src/runtime/tool-defaults-wrapper.ts +1 -3
  488. package/src/runtime/validation.ts +1 -4
  489. package/src/runtime/workflow-events.ts +7 -7
  490. package/src/runtime/workflow-runner.ts +5 -11
  491. package/src/sentiment/adapters/finnhub.ts +7 -2
  492. package/src/sentiment/adapters/reddit.ts +2 -2
  493. package/src/sentiment/adapters/twitter.ts +1 -1
  494. package/src/sentiment/adapters/web.ts +1 -1
  495. package/src/sentiment/index.ts +16 -26
  496. package/src/sentiment/keywords.ts +26 -4
  497. package/src/sentiment/pipeline.ts +15 -4
  498. package/src/sentiment/scorer.ts +1 -1
  499. package/src/sentiment/store.ts +2 -2
  500. package/src/sentiment/trends.ts +9 -3
  501. package/src/sentiment/types.ts +5 -4
  502. package/src/system-prompt.ts +7 -3
  503. package/src/tool-kit.ts +10 -9
  504. package/src/tools/fundamentals/company-overview.ts +20 -10
  505. package/src/tools/fundamentals/comps.ts +69 -56
  506. package/src/tools/fundamentals/dcf.ts +146 -96
  507. package/src/tools/fundamentals/earnings.ts +17 -7
  508. package/src/tools/fundamentals/financials.ts +17 -8
  509. package/src/tools/fundamentals/sec-filings.ts +52 -8
  510. package/src/tools/index.ts +53 -38
  511. package/src/tools/interaction/ask-user.ts +22 -10
  512. package/src/tools/interaction/twitter-login.ts +17 -5
  513. package/src/tools/macro/fear-greed.ts +2 -2
  514. package/src/tools/macro/fred-data.ts +80 -42
  515. package/src/tools/market/crypto-history.ts +25 -4
  516. package/src/tools/market/crypto-price.ts +7 -7
  517. package/src/tools/market/screen-stocks.ts +279 -0
  518. package/src/tools/market/search-ticker.ts +219 -18
  519. package/src/tools/market/stock-history.ts +38 -13
  520. package/src/tools/market/stock-quote.ts +11 -8
  521. package/src/tools/options/greeks.ts +5 -6
  522. package/src/tools/options/option-chain.ts +47 -18
  523. package/src/tools/portfolio/alerts.ts +457 -0
  524. package/src/tools/portfolio/correlation.ts +48 -21
  525. package/src/tools/portfolio/daily-report.ts +101 -0
  526. package/src/tools/portfolio/holdings-overlap.ts +139 -0
  527. package/src/tools/portfolio/notifications.ts +45 -0
  528. package/src/tools/portfolio/predictions.ts +407 -107
  529. package/src/tools/portfolio/risk-analysis.ts +47 -8
  530. package/src/tools/portfolio/tracker.ts +271 -110
  531. package/src/tools/portfolio/watchlist.ts +251 -116
  532. package/src/tools/sentiment/reddit-sentiment.ts +51 -25
  533. package/src/tools/sentiment/sentiment-summary.ts +116 -35
  534. package/src/tools/sentiment/sentiment-trend.ts +24 -7
  535. package/src/tools/sentiment/twitter-sentiment.ts +23 -16
  536. package/src/tools/sentiment/untrusted-text.ts +21 -0
  537. package/src/tools/sentiment/web-search.ts +52 -16
  538. package/src/tools/sentiment/web-sentiment.ts +27 -11
  539. package/src/tools/technical/backtest.ts +78 -47
  540. package/src/tools/technical/indicators.ts +40 -17
  541. package/src/types/index.ts +8 -3
  542. package/src/types/market.ts +1 -0
  543. package/src/types/options.ts +17 -0
  544. package/src/types/portfolio.ts +46 -4
  545. package/src/types/sentiment.ts +2 -2
  546. package/src/workflows/compare-assets.ts +67 -19
  547. package/src/workflows/index.ts +3 -4
  548. package/src/workflows/options-screener.ts +98 -22
  549. package/src/workflows/portfolio-builder.ts +40 -29
  550. package/dist/runtime/index.d.ts +0 -16
  551. package/dist/runtime/index.js +0 -10
  552. package/dist/runtime/index.js.map +0 -1
  553. package/dist/runtime/provider-ids.d.ts +0 -14
  554. package/dist/runtime/provider-ids.js +0 -14
  555. package/dist/runtime/provider-ids.js.map +0 -1
  556. package/dist/workflows/types.d.ts +0 -4
  557. package/dist/workflows/types.js +0 -2
  558. package/dist/workflows/types.js.map +0 -1
  559. package/gui/web/dist/assets/CatalogOverlay-D1ImSJTe.js +0 -1
  560. package/gui/web/dist/assets/index-DBrWq43L.css +0 -1
  561. package/gui/web/dist/assets/index-RflHaj0y.js +0 -67
  562. package/src/runtime/index.ts +0 -55
  563. package/src/runtime/provider-ids.ts +0 -15
  564. package/src/workflows/types.ts +0 -4
@@ -1,23 +1,30 @@
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 { classifyIntent, createPiAiRouterClient, resolveOptionsScreenerSlots, resolvePortfolioSlots, route as routeLlm, } 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 { registerTwitterLoginTool } from "../tools/interaction/twitter-login.js";
19
+ import { buildCompareAssetsWorkflowDefinition, buildOptionsScreenerWorkflowDefinition, buildPortfolioWorkflowDefinition, } from "../workflows/index.js";
20
+ import { getOpenCandleToolDefinitions } from "./tool-adapter.js";
19
21
  export default function openCandleExtension(pi, options) {
20
22
  const coordinator = new SessionCoordinator();
23
+ // Workflow transforms replace the user's turn with the expanded prompt; this
24
+ // marker lets the GUI render the user's original words instead.
25
+ const markOriginalInput = (original) => {
26
+ pi.appendEntry("opencandle-user-input", { original });
27
+ };
21
28
  // Credential-interception state. Lifetime:
22
29
  // `sessionPromptedSet` — cleared on session_start, persists across turns
23
30
  // within a session so users don't get re-prompted after picking
@@ -34,6 +41,12 @@ export default function openCandleExtension(pi, options) {
34
41
  const sessionPromptedSet = new Set();
35
42
  let hardPromptFiredInWorkflow = false;
36
43
  const degradationAccumulator = createDegradationAccumulator();
44
+ let activeToolSnapshot = null;
45
+ let currentRouteToolContext = null;
46
+ // LLM session-title state: one title attempt per session per process.
47
+ // Reset on session_start; set before the (async) title call fires so
48
+ // overlapping turn_end events cannot double-title.
49
+ let sessionTitleAttempted = false;
37
50
  // Register tools
38
51
  for (const tool of getOpenCandleToolDefinitions()) {
39
52
  pi.registerTool(tool);
@@ -49,7 +62,9 @@ export default function openCandleExtension(pi, options) {
49
62
  ctx.ui.notify("Usage: /analyze <ticker>", "warning");
50
63
  return;
51
64
  }
52
- const definition = buildComprehensiveAnalysisDefinition(symbol, { debate: getConfig().debate });
65
+ const definition = buildComprehensiveAnalysisDefinition(symbol, {
66
+ debate: getConfig().debate,
67
+ });
53
68
  coordinator.executeWorkflow(pi, definition, ctx);
54
69
  },
55
70
  });
@@ -135,6 +150,7 @@ export default function openCandleExtension(pi, options) {
135
150
  coordinator.initSession(ctx.sessionManager.getSessionId());
136
151
  sessionPromptedSet.clear();
137
152
  hardPromptFiredInWorkflow = false;
153
+ sessionTitleAttempted = false;
138
154
  if (!ctx.hasUI)
139
155
  return;
140
156
  // Pin the user-facing disclaimer in the UI footer for the entire session.
@@ -197,6 +213,7 @@ export default function openCandleExtension(pi, options) {
197
213
  const isFinalAssistantTurn = msg.role === "assistant" && msg.stopReason === "stop";
198
214
  if (isFinalAssistantTurn) {
199
215
  pi.appendEntry("opencandle-disclaimer", { text: DISCLAIMER_TEXT });
216
+ restoreRouteToolScope();
200
217
  }
201
218
  if (degradationAccumulator.isEmpty())
202
219
  return;
@@ -207,6 +224,83 @@ export default function openCandleExtension(pi, options) {
207
224
  }
208
225
  degradationAccumulator.reset();
209
226
  });
227
+ // LLM session titles. After the first completed user↔assistant exchange,
228
+ // replace the placeholder session name (the raw first prompt, set by the
229
+ // GUI server / Pi's firstMessage fallback) with a short model-written
230
+ // summary. Manual renames are left alone, and each session is attempted at
231
+ // most once per process. Model failures are swallowed (placeholder stays)
232
+ // but recorded as an `opencandle-title-error` entry for observability.
233
+ pi.on("turn_end", async (event, ctx) => {
234
+ if (sessionTitleAttempted)
235
+ return;
236
+ const msg = event.message;
237
+ if (msg?.role !== "assistant" || msg?.stopReason !== "stop")
238
+ return;
239
+ const sessionManager = ctx?.sessionManager;
240
+ if (typeof sessionManager?.getBranch !== "function")
241
+ return;
242
+ const branch = sessionManager.getBranch();
243
+ let firstUserText = null;
244
+ let firstAssistantText = null;
245
+ let originalInput = null;
246
+ for (const entry of branch) {
247
+ if (originalInput === null &&
248
+ entry.type === "custom" &&
249
+ entry.customType === "opencandle-user-input") {
250
+ const original = entry.data?.original;
251
+ if (typeof original === "string" && original.trim().length > 0) {
252
+ originalInput = original;
253
+ }
254
+ continue;
255
+ }
256
+ if (entry.type !== "message")
257
+ continue;
258
+ const message = entry.message;
259
+ const text = extractMessageText(message?.content);
260
+ if (text.trim().length === 0)
261
+ continue;
262
+ if (firstUserText === null && message?.role === "user")
263
+ firstUserText = text;
264
+ if (firstAssistantText === null && message?.role === "assistant") {
265
+ firstAssistantText = text;
266
+ }
267
+ if (firstUserText !== null && firstAssistantText !== null)
268
+ break;
269
+ }
270
+ // Fall back to the just-finished assistant message when the branch has
271
+ // not surfaced it yet at turn_end time.
272
+ if (firstAssistantText === null) {
273
+ const eventText = extractMessageText(msg.content);
274
+ if (eventText.trim().length > 0)
275
+ firstAssistantText = eventText;
276
+ }
277
+ if (firstUserText === null || firstAssistantText === null)
278
+ return;
279
+ const userText = originalInput ?? firstUserText;
280
+ // A manual rename must be left alone — only replace the placeholder
281
+ // (unset, the raw first prompt, or the GUI's "first 77 chars + ..." form).
282
+ if (!isPlaceholderSessionName(pi.getSessionName(), [userText, firstUserText]))
283
+ return;
284
+ const completion = options?.titleCompletion ??
285
+ (() => {
286
+ const client = options?.routerLlmClient ?? resolveRouterLlmClient(ctx);
287
+ return client ? (prompt) => client.complete(prompt) : null;
288
+ })();
289
+ if (!completion)
290
+ return;
291
+ sessionTitleAttempted = true;
292
+ try {
293
+ const title = await generateSessionTitle({ userText, assistantText: firstAssistantText.slice(0, 500) }, completion);
294
+ if (title !== null) {
295
+ pi.setSessionName(title);
296
+ }
297
+ }
298
+ catch (err) {
299
+ pi.appendEntry("opencandle-title-error", {
300
+ message: err instanceof Error ? err.message : String(err),
301
+ });
302
+ }
303
+ });
210
304
  // Intercept tool results for credential-required and soft-degraded tags.
211
305
  pi.on("tool_result", async (event, ctx) => {
212
306
  // First pass: record any soft-degradation tags in the per-turn accumulator
@@ -367,16 +461,43 @@ export default function openCandleExtension(pi, options) {
367
461
  // No OpenCandle tag in this tool result — pass through.
368
462
  return undefined;
369
463
  });
464
+ pi.on("tool_call", async (event) => {
465
+ if (!currentRouteToolContext)
466
+ return undefined;
467
+ const allowed = new Set(currentRouteToolContext.activeToolNames);
468
+ if (allowed.has(event.toolName))
469
+ return undefined;
470
+ const diagnostic = {
471
+ routeKind: currentRouteToolContext.routeKind,
472
+ workflow: currentRouteToolContext.workflow,
473
+ toolName: event.toolName,
474
+ toolBundles: currentRouteToolContext.toolBundles,
475
+ activeToolNames: currentRouteToolContext.activeToolNames,
476
+ };
477
+ pi.appendEntry("opencandle-tool-scope-violation", diagnostic);
478
+ if (getConfig().toolScopeMode === "enforce") {
479
+ return {
480
+ block: true,
481
+ reason: `Tool ${event.toolName} is outside the route-selected OpenCandle tool bundle.`,
482
+ };
483
+ }
484
+ return undefined;
485
+ });
370
486
  // Input handling — branches on OPENCANDLE_ROUTER_MODE.
371
487
  pi.on("input", async (event, ctx) => {
372
488
  if (event.source === "extension")
373
489
  return;
490
+ coordinator.clearTickerValidationCache();
374
491
  // Check for comprehensive analysis pattern — same in both modes.
375
492
  const analysis = isAnalysisRequest(event.text);
376
493
  if (analysis.match && analysis.symbol) {
377
- const definition = buildComprehensiveAnalysisDefinition(analysis.symbol, { debate: getConfig().debate });
378
- coordinator.executeWorkflow(pi, definition, ctx);
379
- return { action: "handled" };
494
+ const definition = buildComprehensiveAnalysisDefinition(analysis.symbol, {
495
+ debate: getConfig().debate,
496
+ });
497
+ const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
498
+ if (prompt)
499
+ markOriginalInput(event.text);
500
+ return prompt ? { action: "transform", text: prompt } : { action: "handled" };
380
501
  }
381
502
  const mode = getConfig().routerMode;
382
503
  if (mode === "llm") {
@@ -385,46 +506,185 @@ export default function openCandleExtension(pi, options) {
385
506
  // the workflow's queued prompts; tell Pi not to also forward it.
386
507
  // Fallback path (no dispatch) → let Pi pass the user turn through to the
387
508
  // main agent, which will run under the router-supplied fallback context.
388
- return dispatched ? { action: "handled" } : undefined;
509
+ return dispatched || undefined;
389
510
  }
390
- // --- rules mode (default) ---
511
+ // --- explicit legacy rules mode (`OPENCANDLE_ROUTER_MODE=rules`) ---
391
512
  // Extract and persist user preferences (legacy regex path)
392
513
  coordinator.extractAndStorePreferences(event.text);
393
514
  const storage = coordinator.getStorage();
394
515
  const workflowPrefs = storage?.getWorkflowPreferences("global") ?? {};
395
516
  // Classify intent
396
- const classification = classifyIntent(event.text);
517
+ let classification = classifyWithLegacyRules(event.text);
518
+ const ruleModeDisambiguation = disambiguateRulesModeSymbols(event.text, classification.entities.symbols);
519
+ appendSymbolDropEntries(ruleModeDisambiguation.dropped, "rules");
520
+ classification = {
521
+ ...classification,
522
+ entities: {
523
+ ...classification.entities,
524
+ symbols: ruleModeDisambiguation.kept,
525
+ },
526
+ };
527
+ if (isComparePrompt(event.text) &&
528
+ ruleModeDisambiguation.dropped.length > 0 &&
529
+ classification.entities.symbols.length < 2) {
530
+ pi.appendEntry("opencandle-workflow-aborted", {
531
+ reason: "symbol-disambiguation-insufficient-symbols",
532
+ dropped: ruleModeDisambiguation.dropped,
533
+ validSymbols: classification.entities.symbols,
534
+ });
535
+ const base = coordinator.buildRouterContextBase(ctx.sessionManager);
536
+ const output = {
537
+ routeKind: "clarification",
538
+ route: "fallback",
539
+ workflow: "compare_assets",
540
+ entities: classification.entities,
541
+ slots: {},
542
+ preference_updates: [],
543
+ missing_required: ["symbols"],
544
+ tool_bundles: ["clarification"],
545
+ diagnostics: ruleModeDisambiguation.dropped.map((drop) => ({
546
+ code: "symbol_dropped",
547
+ message: `${drop.token} dropped: ${drop.reason}`,
548
+ details: {
549
+ token: drop.token,
550
+ reason: drop.reason,
551
+ signalsChecked: drop.signalsChecked,
552
+ source: "rules",
553
+ },
554
+ })),
555
+ reasoning: "rules-mode acronym disambiguation left fewer than two symbols for comparison",
556
+ };
557
+ const resolvedTurnContext = buildResolvedTurnContext({ text: event.text, ...base }, output, {
558
+ availableToolNames: safeGetAllToolNames(),
559
+ planning: {
560
+ migrationStatuses: getConfig().planningMigrationStatuses,
561
+ },
562
+ });
563
+ coordinator.setPendingResolvedTurnContext({
564
+ ...resolvedTurnContext,
565
+ diagnostics: [
566
+ ...resolvedTurnContext.diagnostics,
567
+ {
568
+ code: "compare_workflow_aborted",
569
+ message: "compare workflow needs at least two validated symbols after acronym disambiguation",
570
+ },
571
+ ],
572
+ });
573
+ coordinator.setPendingFallbackContext({
574
+ assumptionsBlock: [
575
+ "Assumptions Context:",
576
+ classification.entities.symbols.length > 0
577
+ ? ` valid symbols: ${classification.entities.symbols.join(", ")} (user)`
578
+ : " valid symbols: (none)",
579
+ ` dropped ambiguous ticker-like tokens: ${ruleModeDisambiguation.dropped.map((d) => d.token).join(", ")} (no positive ticker signal)`,
580
+ ].join("\n"),
581
+ missingRequired: ["symbols"],
582
+ extraContext: "Dropped ambiguous ticker-like tokens: " +
583
+ `${ruleModeDisambiguation.dropped.map((d) => d.token).join(", ")}. ` +
584
+ "Ask the user which ticker symbols they want compared before calling comparison tools.",
585
+ });
586
+ applyRouteToolScope(resolvedTurnContext);
587
+ return undefined;
588
+ }
397
589
  if (classification.workflow === "portfolio_builder") {
398
590
  const resolution = resolvePortfolioSlots(classification.entities, workflowPrefs);
399
591
  coordinator.recordWorkflowRun("portfolio_builder", classification.entities, resolution.resolved, resolution.defaultsUsed);
400
- pi.appendEntry("opencandle-workflow", { workflow: "portfolio_builder", entities: classification.entities, resolved: resolution.resolved });
592
+ pi.appendEntry("opencandle-workflow", {
593
+ workflow: "portfolio_builder",
594
+ entities: classification.entities,
595
+ resolved: resolution.resolved,
596
+ });
401
597
  const definition = buildPortfolioWorkflowDefinition(resolution);
402
- coordinator.executeWorkflow(pi, definition, ctx);
403
- return { action: "handled" };
598
+ const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
599
+ if (prompt)
600
+ markOriginalInput(event.text);
601
+ return prompt ? { action: "transform", text: prompt } : { action: "handled" };
404
602
  }
405
603
  if (classification.workflow === "options_screener") {
406
604
  const resolution = resolveOptionsScreenerSlots(classification.entities, workflowPrefs);
407
605
  if (resolution.missingRequired.length === 0) {
408
606
  coordinator.recordWorkflowRun("options_screener", classification.entities, resolution.resolved, resolution.defaultsUsed);
409
- pi.appendEntry("opencandle-workflow", { workflow: "options_screener", entities: classification.entities, resolved: resolution.resolved });
607
+ pi.appendEntry("opencandle-workflow", {
608
+ workflow: "options_screener",
609
+ entities: classification.entities,
610
+ resolved: resolution.resolved,
611
+ });
410
612
  const definition = buildOptionsScreenerWorkflowDefinition(resolution);
411
- coordinator.executeWorkflow(pi, definition, ctx);
412
- return { action: "handled" };
613
+ const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
614
+ if (prompt)
615
+ markOriginalInput(event.text);
616
+ return prompt ? { action: "transform", text: prompt } : { action: "handled" };
413
617
  }
414
618
  }
415
- if (classification.workflow === "compare_assets" && classification.entities.symbols.length >= 2) {
619
+ if (classification.workflow === "compare_assets" &&
620
+ classification.entities.symbols.length >= 2) {
416
621
  const resolution = {
417
- resolved: { symbols: classification.entities.symbols },
418
- sources: { symbols: "user" },
622
+ resolved: {
623
+ symbols: classification.entities.symbols,
624
+ metrics: classification.entities.compareMetrics,
625
+ timeHorizon: classification.entities.timeHorizon,
626
+ budget: classification.entities.budget,
627
+ assetScope: classification.entities.assetScope,
628
+ },
629
+ sources: {
630
+ symbols: "user",
631
+ ...(classification.entities.timeHorizon ? { timeHorizon: "user" } : {}),
632
+ ...(classification.entities.compareMetrics ? { metrics: "user" } : {}),
633
+ ...(classification.entities.budget !== undefined ? { budget: "user" } : {}),
634
+ ...(classification.entities.assetScope ? { assetScope: "user" } : {}),
635
+ },
419
636
  defaultsUsed: [],
420
637
  missingRequired: [],
421
638
  };
422
639
  coordinator.recordWorkflowRun("compare_assets", classification.entities, resolution.resolved, resolution.defaultsUsed);
423
- pi.appendEntry("opencandle-workflow", { workflow: "compare_assets", symbols: classification.entities.symbols });
424
- const definition = buildCompareAssetsWorkflowDefinition(resolution);
425
- coordinator.executeWorkflow(pi, definition, ctx);
426
- return { action: "handled" };
640
+ pi.appendEntry("opencandle-workflow", {
641
+ workflow: "compare_assets",
642
+ symbols: classification.entities.symbols,
643
+ });
644
+ const preflight = await preflightCompareResolution(resolution);
645
+ if (!preflight) {
646
+ coordinator.recordWorkflowRun("fallback", classification.entities, resolution.resolved, [], "clarification");
647
+ coordinator.setPendingFallbackContext({
648
+ assumptionsBlock: [
649
+ "Assumptions Context:",
650
+ ` original symbols: ${classification.entities.symbols.join(", ")} (user)`,
651
+ ].join("\n"),
652
+ missingRequired: ["symbols"],
653
+ 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.",
654
+ });
655
+ return undefined;
656
+ }
657
+ const definition = buildCompareAssetsWorkflowDefinition(preflight.resolution);
658
+ applyPreflightAnnotation(definition, preflight.dropped);
659
+ const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
660
+ if (prompt)
661
+ markOriginalInput(event.text);
662
+ return prompt ? { action: "transform", text: prompt } : { action: "handled" };
427
663
  }
664
+ // Rules-mode finance fallback: no workflow dispatched, but the turn is
665
+ // finance-shaped (classified finance intent, extracted symbols, or finance
666
+ // vocabulary). Record the fallback turn and stash a fallback context so
667
+ // the system prompt carries saved market state for this turn; non-finance
668
+ // prompts stay untouched.
669
+ const isFinanceFallback = classification.workflow !== "unclassified" ||
670
+ classification.entities.symbols.length > 0 ||
671
+ hasFinanceSignals(event.text);
672
+ if (isFinanceFallback) {
673
+ coordinator.recordWorkflowRun("fallback", classification.entities, {}, [], "agent_task");
674
+ coordinator.setPendingFallbackContext({
675
+ assumptionsBlock: "",
676
+ missingRequired: [],
677
+ extraContext: classification.entities.symbols.length > 0
678
+ ? `Rules-router extracted symbols: ${classification.entities.symbols.join(", ")}.`
679
+ : undefined,
680
+ });
681
+ pi.appendEntry("opencandle-fallback-context", {
682
+ mode: "rules",
683
+ classifiedWorkflow: classification.workflow,
684
+ symbols: classification.entities.symbols,
685
+ });
686
+ }
687
+ return undefined;
428
688
  });
429
689
  /**
430
690
  * LLM-mode input handler. In this mode `classifyIntent` and
@@ -463,7 +723,29 @@ export default function openCandleExtension(pi, options) {
463
723
  });
464
724
  return false;
465
725
  }
726
+ const availableToolNames = safeGetAllToolNames();
727
+ const memory = coordinator.retrieveMemoryForRoute(output.routeKind, output.workflow, Object.keys(output.slots));
728
+ const resolvedTurnContext = buildResolvedTurnContext(input, output, {
729
+ availableToolNames,
730
+ memoryEntries: memory.entries,
731
+ filteredMemory: memory.filtered.map(({ entry, reason }) => ({
732
+ category: entry.category,
733
+ key: entry.key,
734
+ source: entry.source,
735
+ recordedAt: entry.recordedAt,
736
+ confidence: entry.confidence,
737
+ filtered: true,
738
+ filterReason: reason,
739
+ })),
740
+ planning: {
741
+ migrationStatuses: getConfig().planningMigrationStatuses,
742
+ },
743
+ });
466
744
  pi.appendEntry("opencandle-router", { output });
745
+ appendRouterSymbolDropEntries(output);
746
+ pi.appendEntry("opencandle-route-context", resolvedTurnContext);
747
+ coordinator.setPendingResolvedTurnContext(resolvedTurnContext);
748
+ applyRouteToolScope(resolvedTurnContext);
467
749
  // Preference writes: HIGH-confidence only. Medium/low are logged for
468
750
  // observability even when no storage is available.
469
751
  const dropped = [];
@@ -484,83 +766,342 @@ export default function openCandleExtension(pi, options) {
484
766
  pi.appendEntry("opencandle-router-prefs-dropped", { dropped });
485
767
  }
486
768
  // Workflow dispatch for recognised workflows.
487
- if (output.route === "workflow" && output.workflow) {
488
- return dispatchRouterWorkflow(output, ctx);
769
+ if (output.routeKind === "workflow_dispatch" && output.workflow) {
770
+ return dispatchRouterWorkflow(output, ctx, text);
771
+ }
772
+ if (output.routeKind === "pass_through") {
773
+ return false;
489
774
  }
490
775
  // Fallback: record the turn and stash the fallback context for the
491
776
  // upcoming `before_agent_start` hook to render into the system prompt.
492
- coordinator.recordWorkflowRun("fallback", output.entities, Object.fromEntries(Object.entries(output.slots).map(([k, v]) => [k, v.value])), [], "fallback");
777
+ coordinator.recordWorkflowRun("fallback", output.entities, Object.fromEntries(Object.entries(output.slots).map(([k, v]) => [k, v.value])), [], output.routeKind);
493
778
  const assumptionsBlock = buildAssumptionsBlockFromRouter(output.slots);
494
779
  coordinator.setPendingFallbackContext({
495
780
  assumptionsBlock,
496
781
  missingRequired: output.missing_required,
497
782
  extraContext: output.entities.symbols.length > 0
498
- ? `Router-extracted symbols: ${output.entities.symbols.join(", ")}.`
783
+ ? `Router-extracted symbols: ${output.entities.symbols.join(", ")}.` +
784
+ ` Route kind: ${output.routeKind}. Tool bundles: ${output.tool_bundles.join(", ") || "(none)"}.`
499
785
  : undefined,
500
786
  });
501
787
  return false;
502
788
  }
503
- function dispatchRouterWorkflow(output, ctx) {
789
+ async function dispatchRouterWorkflow(output, ctx, originalText) {
504
790
  const workflow = output.workflow;
505
791
  const storage = coordinator.getStorage();
506
792
  const workflowPrefs = storage?.getWorkflowPreferences("global") ?? {};
793
+ const entities = mergeRouterSlotsIntoEntities(output);
507
794
  if (workflow === "portfolio_builder") {
508
- const resolution = resolvePortfolioSlots(output.entities, workflowPrefs);
509
- coordinator.recordWorkflowRun("portfolio_builder", output.entities, resolution.resolved, resolution.defaultsUsed, "workflow");
795
+ const resolution = withRouterSlotSources(resolvePortfolioSlots(entities, workflowPrefs), output);
796
+ coordinator.recordWorkflowRun("portfolio_builder", entities, resolution.resolved, resolution.defaultsUsed, output.routeKind);
510
797
  pi.appendEntry("opencandle-workflow", {
511
798
  workflow: "portfolio_builder",
512
- entities: output.entities,
799
+ entities,
513
800
  resolved: resolution.resolved,
514
801
  });
515
802
  const definition = buildPortfolioWorkflowDefinition(resolution);
516
- coordinator.executeWorkflow(pi, definition, ctx);
517
- return true;
803
+ const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
804
+ if (prompt)
805
+ markOriginalInput(originalText);
806
+ return prompt ? { action: "transform", text: prompt } : false;
518
807
  }
519
808
  if (workflow === "options_screener") {
520
- const resolution = resolveOptionsScreenerSlots(output.entities, workflowPrefs);
809
+ const resolution = withRouterSlotSources(resolveOptionsScreenerSlots(entities, workflowPrefs), output);
521
810
  // Router may emit missing_required; main agent handles via ask_user.
522
811
  // Still dispatch the workflow when symbol is present.
523
812
  if (resolution.missingRequired.length === 0) {
524
- coordinator.recordWorkflowRun("options_screener", output.entities, resolution.resolved, resolution.defaultsUsed, "workflow");
813
+ coordinator.recordWorkflowRun("options_screener", entities, resolution.resolved, resolution.defaultsUsed, output.routeKind);
525
814
  pi.appendEntry("opencandle-workflow", {
526
815
  workflow: "options_screener",
527
- entities: output.entities,
816
+ entities,
528
817
  resolved: resolution.resolved,
529
818
  });
530
819
  const definition = buildOptionsScreenerWorkflowDefinition(resolution);
531
- coordinator.executeWorkflow(pi, definition, ctx);
532
- return true;
820
+ const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
821
+ if (prompt)
822
+ markOriginalInput(originalText);
823
+ return prompt ? { action: "transform", text: prompt } : false;
533
824
  }
534
825
  // Missing required symbol — treat as fallback with ask_user directive.
535
826
  }
536
- if (workflow === "compare_assets" && output.entities.symbols.length >= 2) {
827
+ if (workflow === "compare_assets" && entities.symbols.length >= 2) {
537
828
  const resolution = {
538
- resolved: { symbols: output.entities.symbols },
539
- sources: { symbols: "user" },
829
+ resolved: {
830
+ symbols: entities.symbols,
831
+ metrics: entities.compareMetrics,
832
+ timeHorizon: entities.timeHorizon,
833
+ budget: entities.budget,
834
+ assetScope: entities.assetScope,
835
+ },
836
+ sources: {
837
+ symbols: sourceForRouterSlot(output, "symbols", "user"),
838
+ ...(entities.timeHorizon ? { timeHorizon: "user" } : {}),
839
+ ...(entities.compareMetrics ? { metrics: "user" } : {}),
840
+ ...(entities.budget !== undefined
841
+ ? { budget: sourceForRouterSlot(output, "budget", "user") }
842
+ : {}),
843
+ ...(entities.assetScope ? { assetScope: "user" } : {}),
844
+ },
540
845
  defaultsUsed: [],
541
846
  missingRequired: [],
542
847
  };
543
- coordinator.recordWorkflowRun("compare_assets", output.entities, resolution.resolved, [], "workflow");
848
+ coordinator.recordWorkflowRun("compare_assets", entities, resolution.resolved, [], output.routeKind);
544
849
  pi.appendEntry("opencandle-workflow", {
545
850
  workflow: "compare_assets",
546
- symbols: output.entities.symbols,
851
+ symbols: entities.symbols,
547
852
  });
548
- const definition = buildCompareAssetsWorkflowDefinition(resolution);
549
- coordinator.executeWorkflow(pi, definition, ctx);
550
- return true;
853
+ const preflight = await preflightCompareResolution(resolution);
854
+ if (!preflight) {
855
+ coordinator.recordWorkflowRun("fallback", output.entities, Object.fromEntries(Object.entries(output.slots).map(([k, v]) => [k, v.value])), [], output.routeKind);
856
+ coordinator.setPendingResolvedTurnContext(null);
857
+ coordinator.setPendingFallbackContext({
858
+ assumptionsBlock: buildAssumptionsBlockFromRouter(output.slots),
859
+ missingRequired: ["symbols"],
860
+ extraContext: "Compare workflow aborted because ticker preflight left fewer than two valid symbols. Ask the user to clarify the intended tickers.",
861
+ });
862
+ return false;
863
+ }
864
+ const definition = buildCompareAssetsWorkflowDefinition(preflight.resolution);
865
+ applyPreflightAnnotation(definition, preflight.dropped);
866
+ const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
867
+ if (prompt)
868
+ markOriginalInput(originalText);
869
+ return prompt ? { action: "transform", text: prompt } : false;
551
870
  }
552
871
  // single_asset_analysis / watchlist / general_qa + any workflow with
553
872
  // unmet required slots: fall through to fallback handling so the main
554
873
  // agent still gets an Assumptions block + ask_user directive.
555
- coordinator.recordWorkflowRun("fallback", output.entities, Object.fromEntries(Object.entries(output.slots).map(([k, v]) => [k, v.value])), [], "fallback");
874
+ coordinator.recordWorkflowRun("fallback", output.entities, Object.fromEntries(Object.entries(output.slots).map(([k, v]) => [k, v.value])), [], output.routeKind);
556
875
  const assumptionsBlock = buildAssumptionsBlockFromRouter(output.slots);
557
876
  coordinator.setPendingFallbackContext({
558
877
  assumptionsBlock,
559
878
  missingRequired: output.missing_required,
560
- extraContext: `Router classified as ${workflow} but declined to dispatch. Symbols: ${output.entities.symbols.join(", ") || "(none)"}.`,
879
+ extraContext: `Router classified as ${workflow} but declined to dispatch. Symbols: ${entities.symbols.join(", ") || "(none)"}.`,
561
880
  });
562
881
  return false;
563
882
  }
883
+ function appendRouterSymbolDropEntries(output) {
884
+ for (const diagnostic of output.diagnostics) {
885
+ if (diagnostic.code !== "symbol_dropped")
886
+ continue;
887
+ const details = diagnostic.details ?? {};
888
+ appendSymbolDropEntries([
889
+ {
890
+ token: String(details.token ?? ""),
891
+ reason: String(details.reason ?? ""),
892
+ signalsChecked: Array.isArray(details.signalsChecked)
893
+ ? details.signalsChecked.map(String)
894
+ : [],
895
+ },
896
+ ], String(details.source ?? "llm"));
897
+ }
898
+ }
899
+ function appendSymbolDropEntries(dropped, source) {
900
+ for (const drop of dropped) {
901
+ pi.appendEntry("opencandle-symbol-dropped", {
902
+ token: drop.token,
903
+ reason: drop.reason,
904
+ signalsChecked: drop.signalsChecked,
905
+ source,
906
+ });
907
+ }
908
+ }
909
+ function disambiguateRulesModeSymbols(text, extractedSymbols) {
910
+ const candidates = mergeSymbols(extractedSymbols, rawTickerLikeTokens(text));
911
+ const disambiguated = disambiguateSymbols(candidates, text);
912
+ return {
913
+ kept: disambiguated.kept.filter((symbol) => extractedSymbols.includes(symbol)),
914
+ dropped: disambiguated.dropped,
915
+ };
916
+ }
917
+ function rawTickerLikeTokens(text) {
918
+ const tokens = [];
919
+ for (const match of text.matchAll(/\$?([A-Za-z]{1,5})\b/g)) {
920
+ const raw = match[1];
921
+ if (raw !== raw.toUpperCase())
922
+ continue;
923
+ const token = raw.toUpperCase();
924
+ if (!tokens.includes(token))
925
+ tokens.push(token);
926
+ }
927
+ return tokens;
928
+ }
929
+ function isComparePrompt(text) {
930
+ return /\b(?:compare|vs\.?|versus|which\s+is\s+better)\b/i.test(text);
931
+ }
932
+ async function preflightCompareResolution(resolution) {
933
+ const result = await preflightSymbols(resolution.resolved.symbols, {
934
+ cache: coordinator.getTickerValidationCache(),
935
+ search: options?.symbolSearch,
936
+ });
937
+ for (const drop of result.dropped) {
938
+ pi.appendEntry("opencandle-symbol-preflight-dropped", drop);
939
+ }
940
+ if (result.valid.length < 2) {
941
+ pi.appendEntry("opencandle-workflow-aborted", {
942
+ reason: "preflight-insufficient-symbols",
943
+ dropped: result.dropped,
944
+ });
945
+ return null;
946
+ }
947
+ return {
948
+ resolution: {
949
+ ...resolution,
950
+ resolved: {
951
+ ...resolution.resolved,
952
+ symbols: result.valid,
953
+ },
954
+ },
955
+ dropped: result.dropped,
956
+ };
957
+ }
958
+ function applyPreflightAnnotation(definition, dropped) {
959
+ if (dropped.length === 0 || definition.steps.length === 0)
960
+ return;
961
+ definition.steps[0] = {
962
+ ...definition.steps[0],
963
+ prompt: `${formatPreflightDropAnnotation(dropped)}\n\n${definition.steps[0].prompt}`,
964
+ };
965
+ }
966
+ function mergeRouterSlotsIntoEntities(output) {
967
+ const entities = {
968
+ ...output.entities,
969
+ symbols: output.entities.symbols,
970
+ };
971
+ if (entities.budget === undefined && typeof output.slots.budget?.value === "number") {
972
+ entities.budget = output.slots.budget.value;
973
+ }
974
+ const droppedSymbols = droppedSymbolsFromDiagnostics(output);
975
+ const slotSymbols = symbolsFromRouterSlots(output).filter((symbol) => !droppedSymbols.has(symbol));
976
+ if (slotSymbols.length > 0 && slotSymbols.length > entities.symbols.length) {
977
+ entities.symbols = mergeSymbols(slotSymbols, entities.symbols);
978
+ }
979
+ return entities;
980
+ }
981
+ function droppedSymbolsFromDiagnostics(output) {
982
+ const dropped = new Set();
983
+ for (const diagnostic of output.diagnostics) {
984
+ if (diagnostic.code !== "symbol_dropped")
985
+ continue;
986
+ const token = diagnostic.details?.token;
987
+ if (typeof token === "string" && token.trim() !== "") {
988
+ dropped.add(token.toUpperCase());
989
+ }
990
+ }
991
+ return dropped;
992
+ }
993
+ function withRouterSlotSources(resolution, output) {
994
+ const sources = { ...resolution.sources };
995
+ if (output.entities.budget === undefined && output.slots.budget) {
996
+ sources.budget = output.slots.budget.source;
997
+ }
998
+ if (output.entities.symbols.length === 0 && output.slots.symbol) {
999
+ sources.symbol = output.slots.symbol.source;
1000
+ }
1001
+ if (output.entities.symbols.length < 2 && output.slots.symbols) {
1002
+ sources.symbols = output.slots.symbols.source;
1003
+ }
1004
+ return { ...resolution, sources: sources };
1005
+ }
1006
+ function sourceForRouterSlot(output, slotName, fallback) {
1007
+ return output.slots[slotName]?.source ?? fallback;
1008
+ }
1009
+ function symbolsFromRouterSlots(output) {
1010
+ const symbols = [];
1011
+ const symbol = output.slots.symbol?.value;
1012
+ if (typeof symbol === "string" && symbol.trim() !== "") {
1013
+ symbols.push(symbol.toUpperCase());
1014
+ }
1015
+ const symbolList = output.slots.symbols?.value;
1016
+ if (Array.isArray(symbolList)) {
1017
+ for (const value of symbolList) {
1018
+ if (typeof value === "string" && value.trim() !== "") {
1019
+ symbols.push(value.toUpperCase());
1020
+ }
1021
+ }
1022
+ }
1023
+ return symbols;
1024
+ }
1025
+ function mergeSymbols(primary, secondary) {
1026
+ const merged = [];
1027
+ for (const symbol of [...primary, ...secondary]) {
1028
+ if (!merged.includes(symbol))
1029
+ merged.push(symbol);
1030
+ }
1031
+ return merged;
1032
+ }
1033
+ function safeGetAllToolNames() {
1034
+ try {
1035
+ return pi.getAllTools().map((tool) => tool.name);
1036
+ }
1037
+ catch {
1038
+ return [];
1039
+ }
1040
+ }
1041
+ function applyRouteToolScope(context) {
1042
+ const mode = getConfig().toolScopeMode;
1043
+ currentRouteToolContext = context;
1044
+ pi.appendEntry("opencandle-tool-scope", {
1045
+ mode,
1046
+ routeKind: context.routeKind,
1047
+ workflow: context.workflow,
1048
+ toolBundles: context.toolBundles,
1049
+ activeToolNames: context.activeToolNames,
1050
+ enforced: false,
1051
+ });
1052
+ if (mode !== "enforce")
1053
+ return;
1054
+ if (context.activeToolNames.length === 0)
1055
+ return;
1056
+ try {
1057
+ if (activeToolSnapshot === null) {
1058
+ activeToolSnapshot = pi.getActiveTools();
1059
+ }
1060
+ pi.setActiveTools(context.activeToolNames);
1061
+ pi.appendEntry("opencandle-tool-scope", {
1062
+ mode,
1063
+ routeKind: context.routeKind,
1064
+ workflow: context.workflow,
1065
+ toolBundles: context.toolBundles,
1066
+ activeToolNames: context.activeToolNames,
1067
+ enforced: true,
1068
+ });
1069
+ }
1070
+ catch (err) {
1071
+ pi.appendEntry("opencandle-tool-scope", {
1072
+ mode,
1073
+ routeKind: context.routeKind,
1074
+ workflow: context.workflow,
1075
+ toolBundles: context.toolBundles,
1076
+ activeToolNames: context.activeToolNames,
1077
+ enforced: false,
1078
+ diagnostic: err instanceof Error ? err.message : String(err),
1079
+ });
1080
+ }
1081
+ }
1082
+ function restoreRouteToolScope() {
1083
+ currentRouteToolContext = null;
1084
+ if (activeToolSnapshot === null)
1085
+ return;
1086
+ try {
1087
+ pi.setActiveTools(activeToolSnapshot);
1088
+ pi.appendEntry("opencandle-tool-scope", {
1089
+ mode: getConfig().toolScopeMode,
1090
+ restored: true,
1091
+ activeToolNames: activeToolSnapshot,
1092
+ });
1093
+ }
1094
+ catch (err) {
1095
+ pi.appendEntry("opencandle-tool-scope", {
1096
+ mode: getConfig().toolScopeMode,
1097
+ restored: false,
1098
+ diagnostic: err instanceof Error ? err.message : String(err),
1099
+ });
1100
+ }
1101
+ finally {
1102
+ activeToolSnapshot = null;
1103
+ }
1104
+ }
564
1105
  function resolveRouterLlmClient(ctx) {
565
1106
  // `ctx.model` is the currently selected pi-ai model. When unset (no auth
566
1107
  // configured yet), we skip the router and the main agent will run with
@@ -575,9 +1116,46 @@ export default function openCandleExtension(pi, options) {
575
1116
  // is pending (router-mode fallback turns), inject it into the prompt.
576
1117
  pi.on("before_agent_start", async (event) => {
577
1118
  const fallbackContext = coordinator.consumePendingFallbackContext() ?? undefined;
1119
+ const resolvedTurnContext = coordinator.consumePendingResolvedTurnContext() ?? undefined;
578
1120
  return {
579
- systemPrompt: coordinator.buildSystemPrompt(event.systemPrompt, undefined, fallbackContext),
1121
+ systemPrompt: coordinator.buildSystemPrompt(event.systemPrompt, coordinator.getActiveWorkflowType(), fallbackContext, resolvedTurnContext),
580
1122
  };
581
1123
  });
582
1124
  }
1125
+ /** Concatenate text from a Pi message `content` (plain string or block array). */
1126
+ function extractMessageText(content) {
1127
+ if (typeof content === "string")
1128
+ return content;
1129
+ if (!Array.isArray(content))
1130
+ return "";
1131
+ let text = "";
1132
+ for (const block of content) {
1133
+ if (block &&
1134
+ typeof block === "object" &&
1135
+ block.type === "text" &&
1136
+ typeof block.text === "string") {
1137
+ text += block.text;
1138
+ }
1139
+ }
1140
+ return text;
1141
+ }
1142
+ /**
1143
+ * True when the session name is still an auto-set placeholder that the LLM
1144
+ * title may replace: unset, exactly one of the candidate user texts, or the
1145
+ * GUI server's truncated "first 77 chars + ..." form of one of them.
1146
+ */
1147
+ function isPlaceholderSessionName(name, candidates) {
1148
+ if (name === undefined || name.trim().length === 0)
1149
+ return true;
1150
+ const trimmed = name.trim();
1151
+ for (const candidate of candidates) {
1152
+ const candidateTrimmed = candidate.trim();
1153
+ if (trimmed === candidateTrimmed)
1154
+ return true;
1155
+ if (trimmed.endsWith("...") && candidateTrimmed.startsWith(trimmed.slice(0, -3))) {
1156
+ return true;
1157
+ }
1158
+ }
1159
+ return false;
1160
+ }
583
1161
  //# sourceMappingURL=opencandle-extension.js.map