opencandle 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -187
- package/dist/analysts/contracts.d.ts +1 -3
- package/dist/analysts/contracts.js +1 -11
- package/dist/analysts/contracts.js.map +1 -1
- package/dist/analysts/orchestrator.d.ts +1 -3
- package/dist/analysts/orchestrator.js +1 -26
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/cli.js +30 -7
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -3
- package/dist/config.js +12 -5
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infra/browser.js +3 -1
- package/dist/infra/browser.js.map +1 -1
- package/dist/infra/cache.d.ts +8 -11
- package/dist/infra/cache.js +17 -15
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/http-client.d.ts +4 -1
- package/dist/infra/http-client.js +59 -6
- package/dist/infra/http-client.js.map +1 -1
- package/dist/infra/index.d.ts +3 -3
- package/dist/infra/index.js +3 -3
- package/dist/infra/index.js.map +1 -1
- package/dist/infra/native-dependencies.js +2 -2
- package/dist/infra/native-dependencies.js.map +1 -1
- package/dist/infra/node-version.js.map +1 -1
- package/dist/infra/opencandle-paths.d.ts +0 -3
- package/dist/infra/opencandle-paths.js +4 -11
- package/dist/infra/opencandle-paths.js.map +1 -1
- package/dist/infra/rate-limiter.js +12 -9
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/market-state/alert-conditions.d.ts +34 -0
- package/dist/market-state/alert-conditions.js +23 -0
- package/dist/market-state/alert-conditions.js.map +1 -0
- package/dist/market-state/alert-runner.d.ts +55 -0
- package/dist/market-state/alert-runner.js +634 -0
- package/dist/market-state/alert-runner.js.map +1 -0
- package/dist/market-state/daily-report.d.ts +26 -0
- package/dist/market-state/daily-report.js +179 -0
- package/dist/market-state/daily-report.js.map +1 -0
- package/dist/market-state/local-automation-service.d.ts +25 -0
- package/dist/market-state/local-automation-service.js +119 -0
- package/dist/market-state/local-automation-service.js.map +1 -0
- package/dist/market-state/notification-delivery.d.ts +14 -0
- package/dist/market-state/notification-delivery.js +139 -0
- package/dist/market-state/notification-delivery.js.map +1 -0
- package/dist/market-state/resolve-for-mutation.d.ts +10 -0
- package/dist/market-state/resolve-for-mutation.js +15 -0
- package/dist/market-state/resolve-for-mutation.js.map +1 -0
- package/dist/market-state/resolve.d.ts +14 -0
- package/dist/market-state/resolve.js +89 -0
- package/dist/market-state/resolve.js.map +1 -0
- package/dist/market-state/service.d.ts +527 -0
- package/dist/market-state/service.js +1099 -0
- package/dist/market-state/service.js.map +1 -0
- package/dist/memory/index.d.ts +7 -7
- package/dist/memory/index.js +6 -6
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/manager.js +11 -11
- package/dist/memory/manager.js.map +1 -1
- package/dist/memory/retrieval.js +7 -4
- package/dist/memory/retrieval.js.map +1 -1
- package/dist/memory/sqlite.js +385 -3
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/memory/storage.js +1 -2
- package/dist/memory/storage.js.map +1 -1
- package/dist/memory/tool-defaults.js +64 -28
- package/dist/memory/tool-defaults.js.map +1 -1
- package/dist/memory/types.js.map +1 -1
- package/dist/monitor.d.ts +2 -0
- package/dist/monitor.js +104 -0
- package/dist/monitor.js.map +1 -0
- package/dist/onboarding/connect.js +4 -6
- package/dist/onboarding/connect.js.map +1 -1
- package/dist/onboarding/credential-interceptor.js +1 -1
- package/dist/onboarding/credential-interceptor.js.map +1 -1
- package/dist/onboarding/degradation-accumulator.js +1 -3
- package/dist/onboarding/degradation-accumulator.js.map +1 -1
- package/dist/onboarding/providers.js +3 -16
- package/dist/onboarding/providers.js.map +1 -1
- package/dist/onboarding/state.js.map +1 -1
- package/dist/onboarding/tool-helpers.js +1 -1
- package/dist/onboarding/tool-helpers.js.map +1 -1
- package/dist/onboarding/tool-tags.js +6 -4
- package/dist/onboarding/tool-tags.js.map +1 -1
- package/dist/onboarding/validation.js +1 -1
- package/dist/onboarding/validation.js.map +1 -1
- package/dist/pi/opencandle-extension.d.ts +8 -0
- package/dist/pi/opencandle-extension.js +412 -28
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session.d.ts +1 -1
- package/dist/pi/session.js +3 -1
- package/dist/pi/session.js.map +1 -1
- package/dist/pi/setup.js +8 -3
- package/dist/pi/setup.js.map +1 -1
- package/dist/pi/tool-adapter.js +5 -2
- package/dist/pi/tool-adapter.js.map +1 -1
- package/dist/prompts/context-builder.d.ts +1 -1
- package/dist/prompts/context-builder.js +19 -6
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/prompts/policy-cards.d.ts +1 -1
- package/dist/prompts/policy-cards.js +1 -1
- package/dist/prompts/policy-cards.js.map +1 -1
- package/dist/prompts/sections.d.ts +1 -1
- package/dist/prompts/symbol-preflight.d.ts +20 -0
- package/dist/prompts/symbol-preflight.js +49 -0
- package/dist/prompts/symbol-preflight.js.map +1 -0
- package/dist/prompts/workflow-prompts.d.ts +1 -1
- package/dist/prompts/workflow-prompts.js +54 -16
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/alpha-vantage.d.ts +1 -1
- package/dist/providers/alpha-vantage.js +26 -7
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/coingecko.js +1 -1
- package/dist/providers/coingecko.js.map +1 -1
- package/dist/providers/errors.d.ts +5 -0
- package/dist/providers/errors.js +11 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/exa-search.d.ts +2 -2
- package/dist/providers/exa-search.js +19 -11
- package/dist/providers/exa-search.js.map +1 -1
- package/dist/providers/fear-greed.js +1 -1
- package/dist/providers/fear-greed.js.map +1 -1
- package/dist/providers/finnhub.js +3 -5
- package/dist/providers/finnhub.js.map +1 -1
- package/dist/providers/fred.js +2 -2
- package/dist/providers/fred.js.map +1 -1
- package/dist/providers/index.d.ts +7 -6
- package/dist/providers/index.js +6 -5
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/reddit.js +2 -2
- package/dist/providers/reddit.js.map +1 -1
- package/dist/providers/sec-edgar.d.ts +1 -0
- package/dist/providers/sec-edgar.js +12 -4
- package/dist/providers/sec-edgar.js.map +1 -1
- package/dist/providers/tradingview.d.ts +47 -0
- package/dist/providers/tradingview.js +275 -0
- package/dist/providers/tradingview.js.map +1 -0
- package/dist/providers/twitter.js +6 -8
- package/dist/providers/twitter.js.map +1 -1
- package/dist/providers/web-search.js +26 -12
- package/dist/providers/web-search.js.map +1 -1
- package/dist/providers/with-fallback.js +4 -2
- package/dist/providers/with-fallback.js.map +1 -1
- package/dist/providers/wrap-provider.d.ts +2 -3
- package/dist/providers/wrap-provider.js +14 -8
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +1 -1
- package/dist/providers/yahoo-finance.js +101 -17
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.d.ts +6 -0
- package/dist/routing/classify-intent.js +78 -7
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/defaults.d.ts +1 -1
- package/dist/routing/entity-extractor.d.ts +1 -0
- package/dist/routing/entity-extractor.js +234 -29
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/fund-symbols.d.ts +2 -0
- package/dist/routing/fund-symbols.js +55 -0
- package/dist/routing/fund-symbols.js.map +1 -0
- package/dist/routing/horizon.d.ts +1 -0
- package/dist/routing/horizon.js +10 -0
- package/dist/routing/horizon.js.map +1 -0
- package/dist/routing/index.d.ts +10 -10
- package/dist/routing/index.js +6 -6
- package/dist/routing/index.js.map +1 -1
- package/dist/routing/planning.d.ts +1 -1
- package/dist/routing/planning.js +65 -34
- package/dist/routing/planning.js.map +1 -1
- package/dist/routing/route-manifest.d.ts +2 -2
- package/dist/routing/route-manifest.js +25 -4
- package/dist/routing/route-manifest.js.map +1 -1
- package/dist/routing/router-llm-client.js.map +1 -1
- package/dist/routing/router-prompt.js +7 -9
- package/dist/routing/router-prompt.js.map +1 -1
- package/dist/routing/router-types.d.ts +1 -0
- package/dist/routing/router.js +137 -22
- package/dist/routing/router.js.map +1 -1
- package/dist/routing/slot-resolver.d.ts +1 -1
- package/dist/routing/slot-resolver.js +2 -4
- package/dist/routing/slot-resolver.js.map +1 -1
- package/dist/routing/symbol-disambiguator.d.ts +11 -0
- package/dist/routing/symbol-disambiguator.js +52 -0
- package/dist/routing/symbol-disambiguator.js.map +1 -0
- package/dist/routing/turn-context.d.ts +1 -1
- package/dist/routing/turn-context.js +1 -1
- package/dist/routing/turn-context.js.map +1 -1
- package/dist/routing/types.d.ts +2 -0
- package/dist/runtime/answer-contracts.js +36 -8
- package/dist/runtime/answer-contracts.js.map +1 -1
- package/dist/runtime/artifact-contracts.js.map +1 -1
- package/dist/runtime/planning-evidence.js +47 -26
- package/dist/runtime/planning-evidence.js.map +1 -1
- package/dist/runtime/prompt-step.d.ts +1 -9
- package/dist/runtime/prompt-step.js +0 -10
- package/dist/runtime/prompt-step.js.map +1 -1
- package/dist/runtime/run-context.d.ts +5 -2
- package/dist/runtime/run-context.js +8 -1
- package/dist/runtime/run-context.js.map +1 -1
- package/dist/runtime/session-coordinator.d.ts +13 -5
- package/dist/runtime/session-coordinator.js +160 -20
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/session-title.d.ts +14 -0
- package/dist/runtime/session-title.js +50 -0
- package/dist/runtime/session-title.js.map +1 -0
- package/dist/runtime/tool-defaults-wrapper.js +1 -3
- package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
- package/dist/runtime/validation.js.map +1 -1
- package/dist/runtime/workflow-events.js.map +1 -1
- package/dist/runtime/workflow-runner.d.ts +3 -3
- package/dist/runtime/workflow-runner.js +1 -1
- package/dist/runtime/workflow-runner.js.map +1 -1
- package/dist/sentiment/adapters/finnhub.d.ts +1 -1
- package/dist/sentiment/adapters/finnhub.js +6 -1
- package/dist/sentiment/adapters/finnhub.js.map +1 -1
- package/dist/sentiment/adapters/reddit.d.ts +2 -2
- package/dist/sentiment/adapters/twitter.d.ts +1 -1
- package/dist/sentiment/adapters/web.d.ts +1 -1
- package/dist/sentiment/index.d.ts +9 -11
- package/dist/sentiment/index.js +9 -20
- package/dist/sentiment/index.js.map +1 -1
- package/dist/sentiment/keywords.js +26 -4
- package/dist/sentiment/keywords.js.map +1 -1
- package/dist/sentiment/pipeline.d.ts +2 -2
- package/dist/sentiment/pipeline.js +1 -1
- package/dist/sentiment/pipeline.js.map +1 -1
- package/dist/sentiment/scorer.js +1 -1
- package/dist/sentiment/store.d.ts +1 -1
- package/dist/sentiment/store.js +1 -1
- package/dist/sentiment/store.js.map +1 -1
- package/dist/sentiment/trends.d.ts +1 -1
- package/dist/sentiment/trends.js.map +1 -1
- package/dist/sentiment/types.js.map +1 -1
- package/dist/system-prompt.js +3 -2
- package/dist/system-prompt.js.map +1 -1
- package/dist/tool-kit.d.ts +7 -7
- package/dist/tool-kit.js +4 -4
- package/dist/tool-kit.js.map +1 -1
- package/dist/tools/fundamentals/company-overview.js +11 -6
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.js +18 -9
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +23 -11
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.js +8 -3
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.js +8 -3
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.js +21 -6
- package/dist/tools/fundamentals/sec-filings.js.map +1 -1
- package/dist/tools/index.d.ts +23 -19
- package/dist/tools/index.js +51 -39
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.js +15 -3
- package/dist/tools/interaction/ask-user.js.map +1 -1
- package/dist/tools/interaction/twitter-login.js +13 -3
- package/dist/tools/interaction/twitter-login.js.map +1 -1
- package/dist/tools/macro/fear-greed.js.map +1 -1
- package/dist/tools/macro/fred-data.d.ts +1 -1
- package/dist/tools/macro/fred-data.js +17 -6
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/market/crypto-history.js +3 -1
- package/dist/tools/market/crypto-history.js.map +1 -1
- package/dist/tools/market/crypto-price.js +3 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/screen-stocks.d.ts +18 -0
- package/dist/tools/market/screen-stocks.js +252 -0
- package/dist/tools/market/screen-stocks.js.map +1 -0
- package/dist/tools/market/search-ticker.js +160 -8
- package/dist/tools/market/search-ticker.js.map +1 -1
- package/dist/tools/market/stock-history.d.ts +2 -2
- package/dist/tools/market/stock-history.js +26 -7
- package/dist/tools/market/stock-history.js.map +1 -1
- package/dist/tools/market/stock-quote.js +5 -3
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/greeks.js +1 -1
- package/dist/tools/options/greeks.js.map +1 -1
- package/dist/tools/options/option-chain.js +19 -6
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/alerts.d.ts +15 -0
- package/dist/tools/portfolio/alerts.js +357 -0
- package/dist/tools/portfolio/alerts.js.map +1 -0
- package/dist/tools/portfolio/correlation.d.ts +1 -1
- package/dist/tools/portfolio/correlation.js +33 -13
- package/dist/tools/portfolio/correlation.js.map +1 -1
- package/dist/tools/portfolio/daily-report.d.ts +8 -0
- package/dist/tools/portfolio/daily-report.js +83 -0
- package/dist/tools/portfolio/daily-report.js.map +1 -0
- package/dist/tools/portfolio/holdings-overlap.js +10 -3
- package/dist/tools/portfolio/holdings-overlap.js.map +1 -1
- package/dist/tools/portfolio/notifications.d.ts +7 -0
- package/dist/tools/portfolio/notifications.js +43 -0
- package/dist/tools/portfolio/notifications.js.map +1 -0
- package/dist/tools/portfolio/predictions.d.ts +12 -6
- package/dist/tools/portfolio/predictions.js +337 -87
- package/dist/tools/portfolio/predictions.js.map +1 -1
- package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
- package/dist/tools/portfolio/risk-analysis.js +45 -6
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/portfolio/tracker.d.ts +4 -3
- package/dist/tools/portfolio/tracker.js +246 -101
- package/dist/tools/portfolio/tracker.js.map +1 -1
- package/dist/tools/portfolio/watchlist.d.ts +6 -4
- package/dist/tools/portfolio/watchlist.js +208 -108
- package/dist/tools/portfolio/watchlist.js.map +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js +23 -10
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.js +15 -13
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
- package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
- package/dist/tools/sentiment/sentiment-trend.js +12 -2
- package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js +12 -5
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
- package/dist/tools/sentiment/untrusted-text.js +17 -0
- package/dist/tools/sentiment/untrusted-text.js.map +1 -0
- package/dist/tools/sentiment/web-search.js +9 -13
- package/dist/tools/sentiment/web-search.js.map +1 -1
- package/dist/tools/sentiment/web-sentiment.js +15 -3
- package/dist/tools/sentiment/web-sentiment.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +1 -1
- package/dist/tools/technical/backtest.js +27 -20
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +23 -5
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/market.d.ts +1 -0
- package/dist/types/portfolio.d.ts +14 -4
- package/dist/workflows/compare-assets.d.ts +0 -3
- package/dist/workflows/compare-assets.js +20 -11
- package/dist/workflows/compare-assets.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -4
- package/dist/workflows/index.js +3 -3
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/options-screener.d.ts +0 -3
- package/dist/workflows/options-screener.js +4 -11
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.d.ts +0 -3
- package/dist/workflows/portfolio-builder.js +0 -8
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/ask-user-bridge.ts +1 -1
- package/gui/server/automation-heartbeat.ts +97 -0
- package/gui/server/background-quotes.ts +97 -1
- package/gui/server/chat-event-adapter.ts +32 -10
- package/gui/server/chat-run-session.ts +16 -0
- package/gui/server/invoke-tool.ts +144 -1
- package/gui/server/live-chat-event-adapter.ts +21 -6
- package/gui/server/market-state-api.ts +315 -0
- package/gui/server/model-setup.ts +149 -2
- package/gui/server/private-api-access.ts +62 -0
- package/gui/server/projector.ts +12 -7
- package/gui/server/prompt-observation.ts +4 -7
- package/gui/server/quote-snapshot-store.ts +50 -0
- package/gui/server/server.ts +200 -451
- package/gui/server/session-actions.ts +186 -1
- package/gui/server/shutdown.ts +47 -0
- package/gui/server/tool-invoke-ack.ts +49 -0
- package/gui/server/tool-metadata.ts +23 -10
- package/gui/server/websocket.ts +13 -3
- package/gui/server/writer-lock.ts +6 -2
- package/gui/server/ws-hub.ts +292 -0
- package/gui/shared/chat-events.ts +16 -1
- package/gui/shared/event-reducer.ts +24 -6
- package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +1 -0
- package/gui/web/dist/assets/index-2KZtKBmu.css +1 -0
- package/gui/web/dist/assets/index-CveNgtDg.js +69 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +5 -1
- package/src/analysts/contracts.ts +10 -23
- package/src/analysts/orchestrator.ts +8 -43
- package/src/cli.ts +35 -12
- package/src/config.ts +17 -9
- package/src/index.ts +1 -1
- package/src/infra/browser.ts +3 -1
- package/src/infra/cache.ts +41 -30
- package/src/infra/http-client.ts +72 -6
- package/src/infra/index.ts +7 -10
- package/src/infra/native-dependencies.ts +8 -3
- package/src/infra/node-version.ts +3 -1
- package/src/infra/opencandle-paths.ts +3 -14
- package/src/infra/rate-limiter.ts +22 -19
- package/src/market-state/alert-conditions.ts +82 -0
- package/src/market-state/alert-runner.ts +863 -0
- package/src/market-state/daily-report.ts +247 -0
- package/src/market-state/local-automation-service.ts +162 -0
- package/src/market-state/notification-delivery.ts +158 -0
- package/src/market-state/resolve-for-mutation.ts +24 -0
- package/src/market-state/resolve.ts +112 -0
- package/src/market-state/service.ts +2344 -0
- package/src/memory/index.ts +7 -7
- package/src/memory/manager.ts +14 -16
- package/src/memory/retrieval.ts +8 -7
- package/src/memory/sqlite.ts +407 -6
- package/src/memory/storage.ts +5 -15
- package/src/memory/tool-defaults.ts +60 -39
- package/src/memory/types.ts +3 -3
- package/src/monitor.ts +121 -0
- package/src/onboarding/connect.ts +10 -33
- package/src/onboarding/credential-interceptor.ts +3 -15
- package/src/onboarding/degradation-accumulator.ts +1 -3
- package/src/onboarding/providers.ts +9 -40
- package/src/onboarding/state.ts +4 -15
- package/src/onboarding/tool-helpers.ts +2 -9
- package/src/onboarding/tool-tags.ts +6 -6
- package/src/onboarding/validation.ts +14 -20
- package/src/pi/opencandle-extension.ts +529 -85
- package/src/pi/session.ts +7 -5
- package/src/pi/setup.ts +61 -43
- package/src/pi/tool-adapter.ts +5 -2
- package/src/prompts/context-builder.ts +23 -12
- package/src/prompts/policy-cards.ts +2 -2
- package/src/prompts/sections.ts +1 -1
- package/src/prompts/symbol-preflight.ts +80 -0
- package/src/prompts/workflow-prompts.ts +77 -28
- package/src/providers/alpha-vantage.ts +58 -39
- package/src/providers/coingecko.ts +2 -5
- package/src/providers/errors.ts +9 -0
- package/src/providers/exa-search.ts +24 -22
- package/src/providers/fear-greed.ts +1 -1
- package/src/providers/finnhub.ts +7 -6
- package/src/providers/fred.ts +3 -3
- package/src/providers/index.ts +14 -6
- package/src/providers/reddit.ts +17 -6
- package/src/providers/sec-edgar.ts +20 -6
- package/src/providers/tradingview.ts +399 -0
- package/src/providers/twitter.ts +6 -8
- package/src/providers/web-search.ts +30 -20
- package/src/providers/with-fallback.ts +8 -7
- package/src/providers/wrap-provider.ts +15 -10
- package/src/providers/yahoo-finance.ts +140 -35
- package/src/routing/classify-intent.ts +101 -10
- package/src/routing/defaults.ts +1 -1
- package/src/routing/entity-extractor.ts +287 -38
- package/src/routing/fund-symbols.ts +58 -0
- package/src/routing/horizon.ts +7 -0
- package/src/routing/index.ts +48 -48
- package/src/routing/planning.ts +144 -53
- package/src/routing/route-manifest.ts +37 -15
- package/src/routing/router-llm-client.ts +4 -4
- package/src/routing/router-prompt.ts +15 -19
- package/src/routing/router-types.ts +2 -5
- package/src/routing/router.ts +251 -53
- package/src/routing/slot-resolver.ts +34 -11
- package/src/routing/symbol-disambiguator.ts +72 -0
- package/src/routing/turn-context.ts +6 -9
- package/src/routing/types.ts +2 -0
- package/src/runtime/answer-contracts.ts +82 -43
- package/src/runtime/artifact-contracts.ts +2 -1
- package/src/runtime/planning-evidence.ts +157 -66
- package/src/runtime/prompt-step.ts +1 -16
- package/src/runtime/run-context.ts +12 -2
- package/src/runtime/session-coordinator.ts +238 -63
- package/src/runtime/session-title.ts +60 -0
- package/src/runtime/tool-defaults-wrapper.ts +1 -3
- package/src/runtime/validation.ts +1 -4
- package/src/runtime/workflow-events.ts +7 -7
- package/src/runtime/workflow-runner.ts +5 -11
- package/src/sentiment/adapters/finnhub.ts +7 -2
- package/src/sentiment/adapters/reddit.ts +2 -2
- package/src/sentiment/adapters/twitter.ts +1 -1
- package/src/sentiment/adapters/web.ts +1 -1
- package/src/sentiment/index.ts +16 -26
- package/src/sentiment/keywords.ts +26 -4
- package/src/sentiment/pipeline.ts +15 -4
- package/src/sentiment/scorer.ts +1 -1
- package/src/sentiment/store.ts +2 -2
- package/src/sentiment/trends.ts +9 -3
- package/src/sentiment/types.ts +5 -4
- package/src/system-prompt.ts +3 -2
- package/src/tool-kit.ts +10 -9
- package/src/tools/fundamentals/company-overview.ts +19 -9
- package/src/tools/fundamentals/comps.ts +68 -55
- package/src/tools/fundamentals/dcf.ts +145 -95
- package/src/tools/fundamentals/earnings.ts +16 -6
- package/src/tools/fundamentals/financials.ts +16 -7
- package/src/tools/fundamentals/sec-filings.ts +37 -16
- package/src/tools/index.ts +51 -39
- package/src/tools/interaction/ask-user.ts +22 -10
- package/src/tools/interaction/twitter-login.ts +17 -5
- package/src/tools/macro/fear-greed.ts +1 -1
- package/src/tools/macro/fred-data.ts +58 -46
- package/src/tools/market/crypto-history.ts +8 -3
- package/src/tools/market/crypto-price.ts +6 -6
- package/src/tools/market/screen-stocks.ts +279 -0
- package/src/tools/market/search-ticker.ts +218 -17
- package/src/tools/market/stock-history.ts +37 -12
- package/src/tools/market/stock-quote.ts +10 -7
- package/src/tools/options/greeks.ts +5 -5
- package/src/tools/options/option-chain.ts +41 -17
- package/src/tools/portfolio/alerts.ts +457 -0
- package/src/tools/portfolio/correlation.ts +47 -20
- package/src/tools/portfolio/daily-report.ts +101 -0
- package/src/tools/portfolio/holdings-overlap.ts +31 -15
- package/src/tools/portfolio/notifications.ts +45 -0
- package/src/tools/portfolio/predictions.ts +406 -106
- package/src/tools/portfolio/risk-analysis.ts +46 -7
- package/src/tools/portfolio/tracker.ts +270 -109
- package/src/tools/portfolio/watchlist.ts +250 -121
- package/src/tools/sentiment/reddit-sentiment.ts +50 -24
- package/src/tools/sentiment/sentiment-summary.ts +62 -41
- package/src/tools/sentiment/sentiment-trend.ts +24 -7
- package/src/tools/sentiment/twitter-sentiment.ts +22 -15
- package/src/tools/sentiment/untrusted-text.ts +21 -0
- package/src/tools/sentiment/web-search.ts +21 -18
- package/src/tools/sentiment/web-sentiment.ts +26 -10
- package/src/tools/technical/backtest.ts +32 -22
- package/src/tools/technical/indicators.ts +39 -14
- package/src/types/index.ts +8 -3
- package/src/types/market.ts +1 -0
- package/src/types/portfolio.ts +14 -4
- package/src/types/sentiment.ts +2 -2
- package/src/workflows/compare-assets.ts +33 -21
- package/src/workflows/index.ts +3 -4
- package/src/workflows/options-screener.ts +27 -29
- package/src/workflows/portfolio-builder.ts +34 -27
- package/dist/workflows/types.d.ts +0 -4
- package/dist/workflows/types.js +0 -2
- package/dist/workflows/types.js.map +0 -1
- package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +0 -1
- package/gui/web/dist/assets/index-Bxt9QpLX.css +0 -1
- package/gui/web/dist/assets/index-CZ9DHZYy.js +0 -67
- package/src/workflows/types.ts +0 -4
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
|
+
buildComprehensiveAnalysisDefinition,
|
|
3
4
|
isAnalysisRequest,
|
|
4
5
|
normalizeSymbol,
|
|
5
6
|
} from "../analysts/orchestrator.js";
|
|
6
|
-
import { buildComprehensiveAnalysisDefinition } from "../analysts/orchestrator.js";
|
|
7
7
|
import { getConfig } from "../config.js";
|
|
8
|
+
import type { InstrumentCandidate } from "../market-state/resolve.js";
|
|
9
|
+
import { runProviderConnect } from "../onboarding/connect.js";
|
|
10
|
+
import { resolveCredentialRequired } from "../onboarding/credential-interceptor.js";
|
|
11
|
+
import { createDegradationAccumulator } from "../onboarding/degradation-accumulator.js";
|
|
12
|
+
import { promptUser } from "../onboarding/prompt-user.js";
|
|
13
|
+
import { getProvider, type ProviderId } from "../onboarding/providers.js";
|
|
8
14
|
import {
|
|
15
|
+
loadOnboardingState,
|
|
16
|
+
markProviderNeverAsk,
|
|
17
|
+
markProviderSnoozed,
|
|
18
|
+
markWelcomeShown,
|
|
19
|
+
saveOnboardingState,
|
|
20
|
+
shouldShowWelcome,
|
|
21
|
+
} from "../onboarding/state.js";
|
|
22
|
+
import { buildConnectedTag, buildSkippedTag, parseToolTag } from "../onboarding/tool-tags.js";
|
|
23
|
+
import { DISCLAIMER_TEXT } from "../prompts/disclaimer.js";
|
|
24
|
+
import { formatPreflightDropAnnotation, preflightSymbols } from "../prompts/symbol-preflight.js";
|
|
25
|
+
import { buildAssumptionsBlockFromRouter } from "../prompts/workflow-prompts.js";
|
|
26
|
+
import {
|
|
27
|
+
buildResolvedTurnContext,
|
|
9
28
|
classifyWithLegacyRules,
|
|
10
29
|
createPiAiRouterClient,
|
|
30
|
+
hasFinanceSignals,
|
|
11
31
|
resolveOptionsScreenerSlots,
|
|
12
32
|
resolvePortfolioSlots,
|
|
13
33
|
route as routeLlm,
|
|
14
|
-
buildResolvedTurnContext,
|
|
15
34
|
} from "../routing/index.js";
|
|
16
|
-
import type {
|
|
17
|
-
|
|
18
|
-
RouterLlmClient,
|
|
19
|
-
RouterOutput,
|
|
20
|
-
} from "../routing/router-types.js";
|
|
35
|
+
import type { RouterInputContext, RouterLlmClient, RouterOutput } from "../routing/router-types.js";
|
|
36
|
+
import { disambiguateSymbols } from "../routing/symbol-disambiguator.js";
|
|
21
37
|
import type { ResolvedTurnContext } from "../routing/turn-context.js";
|
|
22
38
|
import type {
|
|
23
39
|
CompareAssetsSlots,
|
|
@@ -25,35 +41,17 @@ import type {
|
|
|
25
41
|
SlotResolution,
|
|
26
42
|
SlotSource,
|
|
27
43
|
} from "../routing/types.js";
|
|
28
|
-
import {
|
|
44
|
+
import { SessionCoordinator } from "../runtime/session-coordinator.js";
|
|
45
|
+
import { generateSessionTitle } from "../runtime/session-title.js";
|
|
46
|
+
import { registerAskUserTool } from "../tools/interaction/ask-user.js";
|
|
47
|
+
import { registerTwitterLoginTool } from "../tools/interaction/twitter-login.js";
|
|
48
|
+
import type { AskUserHandler } from "../types/index.js";
|
|
29
49
|
import {
|
|
30
|
-
buildPortfolioWorkflowDefinition,
|
|
31
|
-
buildOptionsScreenerWorkflowDefinition,
|
|
32
50
|
buildCompareAssetsWorkflowDefinition,
|
|
51
|
+
buildOptionsScreenerWorkflowDefinition,
|
|
52
|
+
buildPortfolioWorkflowDefinition,
|
|
33
53
|
} from "../workflows/index.js";
|
|
34
54
|
import { getOpenCandleToolDefinitions } from "./tool-adapter.js";
|
|
35
|
-
import { registerAskUserTool } from "../tools/interaction/ask-user.js";
|
|
36
|
-
import { registerTwitterLoginTool } from "../tools/interaction/twitter-login.js";
|
|
37
|
-
import { SessionCoordinator } from "../runtime/session-coordinator.js";
|
|
38
|
-
import {
|
|
39
|
-
getProvider,
|
|
40
|
-
type ProviderId,
|
|
41
|
-
} from "../onboarding/providers.js";
|
|
42
|
-
import {
|
|
43
|
-
loadOnboardingState,
|
|
44
|
-
saveOnboardingState,
|
|
45
|
-
markProviderSnoozed,
|
|
46
|
-
markProviderNeverAsk,
|
|
47
|
-
markWelcomeShown,
|
|
48
|
-
shouldShowWelcome,
|
|
49
|
-
} from "../onboarding/state.js";
|
|
50
|
-
import { parseToolTag, buildSkippedTag, buildConnectedTag } from "../onboarding/tool-tags.js";
|
|
51
|
-
import { resolveCredentialRequired } from "../onboarding/credential-interceptor.js";
|
|
52
|
-
import { createDegradationAccumulator } from "../onboarding/degradation-accumulator.js";
|
|
53
|
-
import { promptUser } from "../onboarding/prompt-user.js";
|
|
54
|
-
import { runProviderConnect } from "../onboarding/connect.js";
|
|
55
|
-
import type { AskUserHandler } from "../types/index.js";
|
|
56
|
-
import { DISCLAIMER_TEXT } from "../prompts/disclaimer.js";
|
|
57
55
|
|
|
58
56
|
export interface OpenCandleExtensionOptions {
|
|
59
57
|
askUserHandler?: AskUserHandler;
|
|
@@ -62,11 +60,27 @@ export interface OpenCandleExtensionOptions {
|
|
|
62
60
|
* of the pi-ai-backed default. Intended for tests + offline eval runners.
|
|
63
61
|
*/
|
|
64
62
|
routerLlmClient?: RouterLlmClient;
|
|
63
|
+
symbolSearch?: (query: string) => Promise<InstrumentCandidate[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Optional completion function for LLM session titles. When omitted, the
|
|
66
|
+
* extension resolves a pi-ai client from the session's current model.
|
|
67
|
+
* Intended for tests + offline runners.
|
|
68
|
+
*/
|
|
69
|
+
titleCompletion?: (prompt: string) => Promise<string>;
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
export default function openCandleExtension(
|
|
72
|
+
export default function openCandleExtension(
|
|
73
|
+
pi: ExtensionAPI,
|
|
74
|
+
options?: OpenCandleExtensionOptions,
|
|
75
|
+
): void {
|
|
68
76
|
const coordinator = new SessionCoordinator();
|
|
69
77
|
|
|
78
|
+
// Workflow transforms replace the user's turn with the expanded prompt; this
|
|
79
|
+
// marker lets the GUI render the user's original words instead.
|
|
80
|
+
const markOriginalInput = (original: string): void => {
|
|
81
|
+
pi.appendEntry("opencandle-user-input", { original });
|
|
82
|
+
};
|
|
83
|
+
|
|
70
84
|
// Credential-interception state. Lifetime:
|
|
71
85
|
// `sessionPromptedSet` — cleared on session_start, persists across turns
|
|
72
86
|
// within a session so users don't get re-prompted after picking
|
|
@@ -85,6 +99,10 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
85
99
|
const degradationAccumulator = createDegradationAccumulator();
|
|
86
100
|
let activeToolSnapshot: string[] | null = null;
|
|
87
101
|
let currentRouteToolContext: ResolvedTurnContext | null = null;
|
|
102
|
+
// LLM session-title state: one title attempt per session per process.
|
|
103
|
+
// Reset on session_start; set before the (async) title call fires so
|
|
104
|
+
// overlapping turn_end events cannot double-title.
|
|
105
|
+
let sessionTitleAttempted = false;
|
|
88
106
|
|
|
89
107
|
// Register tools
|
|
90
108
|
for (const tool of getOpenCandleToolDefinitions()) {
|
|
@@ -102,7 +120,9 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
102
120
|
ctx.ui.notify("Usage: /analyze <ticker>", "warning");
|
|
103
121
|
return;
|
|
104
122
|
}
|
|
105
|
-
const definition = buildComprehensiveAnalysisDefinition(symbol, {
|
|
123
|
+
const definition = buildComprehensiveAnalysisDefinition(symbol, {
|
|
124
|
+
debate: getConfig().debate,
|
|
125
|
+
});
|
|
106
126
|
coordinator.executeWorkflow(pi, definition, ctx);
|
|
107
127
|
},
|
|
108
128
|
});
|
|
@@ -165,10 +185,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
165
185
|
const all = listAllProviders()
|
|
166
186
|
.map((p) => ` ${p.displayName} (${p.aliases.join(", ")})`)
|
|
167
187
|
.join("\n");
|
|
168
|
-
ctx.ui.notify(
|
|
169
|
-
`Unknown provider: "${trimmed}". Available:\n${all}`,
|
|
170
|
-
"warning",
|
|
171
|
-
);
|
|
188
|
+
ctx.ui.notify(`Unknown provider: "${trimmed}". Available:\n${all}`, "warning");
|
|
172
189
|
return;
|
|
173
190
|
}
|
|
174
191
|
if (Array.isArray(resolved)) {
|
|
@@ -199,6 +216,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
199
216
|
coordinator.initSession(ctx.sessionManager.getSessionId());
|
|
200
217
|
sessionPromptedSet.clear();
|
|
201
218
|
hardPromptFiredInWorkflow = false;
|
|
219
|
+
sessionTitleAttempted = false;
|
|
202
220
|
|
|
203
221
|
if (!ctx.hasUI) return;
|
|
204
222
|
// Pin the user-facing disclaimer in the UI footer for the entire session.
|
|
@@ -266,8 +284,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
266
284
|
// footer status pinned at session_start is the primary user-visible channel.
|
|
267
285
|
pi.on("turn_end", async (event) => {
|
|
268
286
|
const msg = event.message;
|
|
269
|
-
const isFinalAssistantTurn =
|
|
270
|
-
msg.role === "assistant" && msg.stopReason === "stop";
|
|
287
|
+
const isFinalAssistantTurn = msg.role === "assistant" && msg.stopReason === "stop";
|
|
271
288
|
if (isFinalAssistantTurn) {
|
|
272
289
|
pi.appendEntry("opencandle-disclaimer", { text: DISCLAIMER_TEXT });
|
|
273
290
|
restoreRouteToolScope();
|
|
@@ -282,6 +299,82 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
282
299
|
degradationAccumulator.reset();
|
|
283
300
|
});
|
|
284
301
|
|
|
302
|
+
// LLM session titles. After the first completed user↔assistant exchange,
|
|
303
|
+
// replace the placeholder session name (the raw first prompt, set by the
|
|
304
|
+
// GUI server / Pi's firstMessage fallback) with a short model-written
|
|
305
|
+
// summary. Manual renames are left alone, and each session is attempted at
|
|
306
|
+
// most once per process. Model failures are swallowed (placeholder stays)
|
|
307
|
+
// but recorded as an `opencandle-title-error` entry for observability.
|
|
308
|
+
pi.on("turn_end", async (event, ctx) => {
|
|
309
|
+
if (sessionTitleAttempted) return;
|
|
310
|
+
const msg = event.message as { role?: unknown; stopReason?: unknown };
|
|
311
|
+
if (msg?.role !== "assistant" || msg?.stopReason !== "stop") return;
|
|
312
|
+
const sessionManager = ctx?.sessionManager;
|
|
313
|
+
if (typeof sessionManager?.getBranch !== "function") return;
|
|
314
|
+
|
|
315
|
+
const branch = sessionManager.getBranch();
|
|
316
|
+
let firstUserText: string | null = null;
|
|
317
|
+
let firstAssistantText: string | null = null;
|
|
318
|
+
let originalInput: string | null = null;
|
|
319
|
+
for (const entry of branch) {
|
|
320
|
+
if (
|
|
321
|
+
originalInput === null &&
|
|
322
|
+
entry.type === "custom" &&
|
|
323
|
+
(entry as { customType?: unknown }).customType === "opencandle-user-input"
|
|
324
|
+
) {
|
|
325
|
+
const original = (entry as { data?: { original?: unknown } }).data?.original;
|
|
326
|
+
if (typeof original === "string" && original.trim().length > 0) {
|
|
327
|
+
originalInput = original;
|
|
328
|
+
}
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (entry.type !== "message") continue;
|
|
332
|
+
const message = (entry as { message?: { role?: unknown; content?: unknown } }).message;
|
|
333
|
+
const text = extractMessageText(message?.content);
|
|
334
|
+
if (text.trim().length === 0) continue;
|
|
335
|
+
if (firstUserText === null && message?.role === "user") firstUserText = text;
|
|
336
|
+
if (firstAssistantText === null && message?.role === "assistant") {
|
|
337
|
+
firstAssistantText = text;
|
|
338
|
+
}
|
|
339
|
+
if (firstUserText !== null && firstAssistantText !== null) break;
|
|
340
|
+
}
|
|
341
|
+
// Fall back to the just-finished assistant message when the branch has
|
|
342
|
+
// not surfaced it yet at turn_end time.
|
|
343
|
+
if (firstAssistantText === null) {
|
|
344
|
+
const eventText = extractMessageText((msg as { content?: unknown }).content);
|
|
345
|
+
if (eventText.trim().length > 0) firstAssistantText = eventText;
|
|
346
|
+
}
|
|
347
|
+
if (firstUserText === null || firstAssistantText === null) return;
|
|
348
|
+
|
|
349
|
+
const userText = originalInput ?? firstUserText;
|
|
350
|
+
// A manual rename must be left alone — only replace the placeholder
|
|
351
|
+
// (unset, the raw first prompt, or the GUI's "first 77 chars + ..." form).
|
|
352
|
+
if (!isPlaceholderSessionName(pi.getSessionName(), [userText, firstUserText])) return;
|
|
353
|
+
|
|
354
|
+
const completion =
|
|
355
|
+
options?.titleCompletion ??
|
|
356
|
+
(() => {
|
|
357
|
+
const client = options?.routerLlmClient ?? resolveRouterLlmClient(ctx);
|
|
358
|
+
return client ? (prompt: string) => client.complete(prompt) : null;
|
|
359
|
+
})();
|
|
360
|
+
if (!completion) return;
|
|
361
|
+
|
|
362
|
+
sessionTitleAttempted = true;
|
|
363
|
+
try {
|
|
364
|
+
const title = await generateSessionTitle(
|
|
365
|
+
{ userText, assistantText: firstAssistantText.slice(0, 500) },
|
|
366
|
+
completion,
|
|
367
|
+
);
|
|
368
|
+
if (title !== null) {
|
|
369
|
+
pi.setSessionName(title);
|
|
370
|
+
}
|
|
371
|
+
} catch (err) {
|
|
372
|
+
pi.appendEntry("opencandle-title-error", {
|
|
373
|
+
message: err instanceof Error ? err.message : String(err),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
285
378
|
// Intercept tool results for credential-required and soft-degraded tags.
|
|
286
379
|
pi.on("tool_result", async (event, ctx) => {
|
|
287
380
|
// First pass: record any soft-degradation tags in the per-turn accumulator
|
|
@@ -346,10 +439,9 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
346
439
|
// action === "prompt": pause and ask the user via promptUser.
|
|
347
440
|
const descriptor = getProvider(parsed.provider);
|
|
348
441
|
const connectLabel = `Connect now — ${descriptor.instructionsHint}`;
|
|
349
|
-
const continueLabel =
|
|
350
|
-
descriptor.fallbackDescription
|
|
351
|
-
|
|
352
|
-
: `Continue without ${descriptor.displayName} for this run`;
|
|
442
|
+
const continueLabel = descriptor.fallbackDescription
|
|
443
|
+
? `Continue with ${descriptor.fallbackDescription} for this run`
|
|
444
|
+
: `Continue without ${descriptor.displayName} for this run`;
|
|
353
445
|
const snoozeLabel = `Snooze ${descriptor.snoozeDurationDays} days`;
|
|
354
446
|
const neverLabel = `Never ask again`;
|
|
355
447
|
const questionBody =
|
|
@@ -378,12 +470,11 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
378
470
|
content: [
|
|
379
471
|
{
|
|
380
472
|
type: "text",
|
|
381
|
-
text:
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
})}\n\nPrompt was cancelled.`,
|
|
473
|
+
text: `${buildSkippedTag({
|
|
474
|
+
provider: parsed.provider,
|
|
475
|
+
reason: "credential_not_provided",
|
|
476
|
+
remediation: `run /connect ${descriptor.aliases[0] ?? descriptor.id} to unlock`,
|
|
477
|
+
})}\n\nPrompt was cancelled.`,
|
|
387
478
|
},
|
|
388
479
|
],
|
|
389
480
|
};
|
|
@@ -421,12 +512,11 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
421
512
|
content: [
|
|
422
513
|
{
|
|
423
514
|
type: "text",
|
|
424
|
-
text:
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
})}\n\n${descriptor.displayName} connect was ${connectOutcomeDescription}.`,
|
|
515
|
+
text: `${buildSkippedTag({
|
|
516
|
+
provider: parsed.provider,
|
|
517
|
+
reason: "credential_not_provided",
|
|
518
|
+
remediation: `run /connect ${descriptor.aliases[0] ?? descriptor.id} to unlock`,
|
|
519
|
+
})}\n\n${descriptor.displayName} connect was ${connectOutcomeDescription}.`,
|
|
430
520
|
},
|
|
431
521
|
],
|
|
432
522
|
};
|
|
@@ -448,13 +538,12 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
448
538
|
content: [
|
|
449
539
|
{
|
|
450
540
|
type: "text",
|
|
451
|
-
text:
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
})}\n\n${descriptor.displayName} data was omitted per your choice.`,
|
|
541
|
+
text: `${buildSkippedTag({
|
|
542
|
+
provider: parsed.provider,
|
|
543
|
+
reason: "credential_not_provided",
|
|
544
|
+
remediation,
|
|
545
|
+
silenced,
|
|
546
|
+
})}\n\n${descriptor.displayName} data was omitted per your choice.`,
|
|
458
547
|
},
|
|
459
548
|
],
|
|
460
549
|
};
|
|
@@ -490,12 +579,16 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
490
579
|
// Input handling — branches on OPENCANDLE_ROUTER_MODE.
|
|
491
580
|
pi.on("input", async (event, ctx) => {
|
|
492
581
|
if (event.source === "extension") return;
|
|
582
|
+
coordinator.clearTickerValidationCache();
|
|
493
583
|
|
|
494
584
|
// Check for comprehensive analysis pattern — same in both modes.
|
|
495
585
|
const analysis = isAnalysisRequest(event.text);
|
|
496
586
|
if (analysis.match && analysis.symbol) {
|
|
497
|
-
const definition = buildComprehensiveAnalysisDefinition(analysis.symbol, {
|
|
587
|
+
const definition = buildComprehensiveAnalysisDefinition(analysis.symbol, {
|
|
588
|
+
debate: getConfig().debate,
|
|
589
|
+
});
|
|
498
590
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
591
|
+
if (prompt) markOriginalInput(event.text);
|
|
499
592
|
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
500
593
|
}
|
|
501
594
|
|
|
@@ -516,49 +609,211 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
516
609
|
const workflowPrefs = storage?.getWorkflowPreferences("global") ?? {};
|
|
517
610
|
|
|
518
611
|
// Classify intent
|
|
519
|
-
|
|
612
|
+
let classification = classifyWithLegacyRules(event.text);
|
|
613
|
+
const ruleModeDisambiguation = disambiguateRulesModeSymbols(
|
|
614
|
+
event.text,
|
|
615
|
+
classification.entities.symbols,
|
|
616
|
+
);
|
|
617
|
+
appendSymbolDropEntries(ruleModeDisambiguation.dropped, "rules");
|
|
618
|
+
classification = {
|
|
619
|
+
...classification,
|
|
620
|
+
entities: {
|
|
621
|
+
...classification.entities,
|
|
622
|
+
symbols: ruleModeDisambiguation.kept,
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
if (
|
|
626
|
+
isComparePrompt(event.text) &&
|
|
627
|
+
ruleModeDisambiguation.dropped.length > 0 &&
|
|
628
|
+
classification.entities.symbols.length < 2
|
|
629
|
+
) {
|
|
630
|
+
pi.appendEntry("opencandle-workflow-aborted", {
|
|
631
|
+
reason: "symbol-disambiguation-insufficient-symbols",
|
|
632
|
+
dropped: ruleModeDisambiguation.dropped,
|
|
633
|
+
validSymbols: classification.entities.symbols,
|
|
634
|
+
});
|
|
635
|
+
const base = coordinator.buildRouterContextBase(ctx.sessionManager);
|
|
636
|
+
const output: RouterOutput = {
|
|
637
|
+
routeKind: "clarification",
|
|
638
|
+
route: "fallback",
|
|
639
|
+
workflow: "compare_assets",
|
|
640
|
+
entities: classification.entities,
|
|
641
|
+
slots: {},
|
|
642
|
+
preference_updates: [],
|
|
643
|
+
missing_required: ["symbols"],
|
|
644
|
+
tool_bundles: ["clarification"],
|
|
645
|
+
diagnostics: ruleModeDisambiguation.dropped.map((drop) => ({
|
|
646
|
+
code: "symbol_dropped",
|
|
647
|
+
message: `${drop.token} dropped: ${drop.reason}`,
|
|
648
|
+
details: {
|
|
649
|
+
token: drop.token,
|
|
650
|
+
reason: drop.reason,
|
|
651
|
+
signalsChecked: drop.signalsChecked,
|
|
652
|
+
source: "rules",
|
|
653
|
+
},
|
|
654
|
+
})),
|
|
655
|
+
reasoning: "rules-mode acronym disambiguation left fewer than two symbols for comparison",
|
|
656
|
+
};
|
|
657
|
+
const resolvedTurnContext = buildResolvedTurnContext({ text: event.text, ...base }, output, {
|
|
658
|
+
availableToolNames: safeGetAllToolNames(),
|
|
659
|
+
planning: {
|
|
660
|
+
migrationStatuses: getConfig().planningMigrationStatuses,
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
coordinator.setPendingResolvedTurnContext({
|
|
664
|
+
...resolvedTurnContext,
|
|
665
|
+
diagnostics: [
|
|
666
|
+
...resolvedTurnContext.diagnostics,
|
|
667
|
+
{
|
|
668
|
+
code: "compare_workflow_aborted",
|
|
669
|
+
message:
|
|
670
|
+
"compare workflow needs at least two validated symbols after acronym disambiguation",
|
|
671
|
+
},
|
|
672
|
+
],
|
|
673
|
+
});
|
|
674
|
+
coordinator.setPendingFallbackContext({
|
|
675
|
+
assumptionsBlock: [
|
|
676
|
+
"Assumptions Context:",
|
|
677
|
+
classification.entities.symbols.length > 0
|
|
678
|
+
? ` valid symbols: ${classification.entities.symbols.join(", ")} (user)`
|
|
679
|
+
: " valid symbols: (none)",
|
|
680
|
+
` dropped ambiguous ticker-like tokens: ${ruleModeDisambiguation.dropped.map((d) => d.token).join(", ")} (no positive ticker signal)`,
|
|
681
|
+
].join("\n"),
|
|
682
|
+
missingRequired: ["symbols"],
|
|
683
|
+
extraContext:
|
|
684
|
+
"Dropped ambiguous ticker-like tokens: " +
|
|
685
|
+
`${ruleModeDisambiguation.dropped.map((d) => d.token).join(", ")}. ` +
|
|
686
|
+
"Ask the user which ticker symbols they want compared before calling comparison tools.",
|
|
687
|
+
});
|
|
688
|
+
applyRouteToolScope(resolvedTurnContext);
|
|
689
|
+
return undefined;
|
|
690
|
+
}
|
|
520
691
|
|
|
521
692
|
if (classification.workflow === "portfolio_builder") {
|
|
522
693
|
const resolution = resolvePortfolioSlots(classification.entities, workflowPrefs);
|
|
523
|
-
coordinator.recordWorkflowRun(
|
|
524
|
-
|
|
694
|
+
coordinator.recordWorkflowRun(
|
|
695
|
+
"portfolio_builder",
|
|
696
|
+
classification.entities,
|
|
697
|
+
resolution.resolved,
|
|
698
|
+
resolution.defaultsUsed,
|
|
699
|
+
);
|
|
700
|
+
pi.appendEntry("opencandle-workflow", {
|
|
701
|
+
workflow: "portfolio_builder",
|
|
702
|
+
entities: classification.entities,
|
|
703
|
+
resolved: resolution.resolved,
|
|
704
|
+
});
|
|
525
705
|
const definition = buildPortfolioWorkflowDefinition(resolution);
|
|
526
706
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
707
|
+
if (prompt) markOriginalInput(event.text);
|
|
527
708
|
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
528
709
|
}
|
|
529
710
|
|
|
530
711
|
if (classification.workflow === "options_screener") {
|
|
531
712
|
const resolution = resolveOptionsScreenerSlots(classification.entities, workflowPrefs);
|
|
532
713
|
if (resolution.missingRequired.length === 0) {
|
|
533
|
-
coordinator.recordWorkflowRun(
|
|
534
|
-
|
|
714
|
+
coordinator.recordWorkflowRun(
|
|
715
|
+
"options_screener",
|
|
716
|
+
classification.entities,
|
|
717
|
+
resolution.resolved,
|
|
718
|
+
resolution.defaultsUsed,
|
|
719
|
+
);
|
|
720
|
+
pi.appendEntry("opencandle-workflow", {
|
|
721
|
+
workflow: "options_screener",
|
|
722
|
+
entities: classification.entities,
|
|
723
|
+
resolved: resolution.resolved,
|
|
724
|
+
});
|
|
535
725
|
const definition = buildOptionsScreenerWorkflowDefinition(resolution);
|
|
536
726
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
727
|
+
if (prompt) markOriginalInput(event.text);
|
|
537
728
|
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
538
729
|
}
|
|
539
730
|
}
|
|
540
731
|
|
|
541
|
-
if (
|
|
732
|
+
if (
|
|
733
|
+
classification.workflow === "compare_assets" &&
|
|
734
|
+
classification.entities.symbols.length >= 2
|
|
735
|
+
) {
|
|
542
736
|
const resolution: SlotResolution<CompareAssetsSlots> = {
|
|
543
737
|
resolved: {
|
|
544
738
|
symbols: classification.entities.symbols,
|
|
545
739
|
metrics: classification.entities.compareMetrics,
|
|
546
740
|
timeHorizon: classification.entities.timeHorizon,
|
|
741
|
+
budget: classification.entities.budget,
|
|
742
|
+
assetScope: classification.entities.assetScope,
|
|
547
743
|
},
|
|
548
744
|
sources: {
|
|
549
745
|
symbols: "user",
|
|
550
746
|
...(classification.entities.timeHorizon ? { timeHorizon: "user" as const } : {}),
|
|
551
747
|
...(classification.entities.compareMetrics ? { metrics: "user" as const } : {}),
|
|
748
|
+
...(classification.entities.budget !== undefined ? { budget: "user" as const } : {}),
|
|
749
|
+
...(classification.entities.assetScope ? { assetScope: "user" as const } : {}),
|
|
552
750
|
},
|
|
553
751
|
defaultsUsed: [],
|
|
554
752
|
missingRequired: [],
|
|
555
753
|
};
|
|
556
|
-
coordinator.recordWorkflowRun(
|
|
557
|
-
|
|
558
|
-
|
|
754
|
+
coordinator.recordWorkflowRun(
|
|
755
|
+
"compare_assets",
|
|
756
|
+
classification.entities,
|
|
757
|
+
resolution.resolved,
|
|
758
|
+
resolution.defaultsUsed,
|
|
759
|
+
);
|
|
760
|
+
pi.appendEntry("opencandle-workflow", {
|
|
761
|
+
workflow: "compare_assets",
|
|
762
|
+
symbols: classification.entities.symbols,
|
|
763
|
+
});
|
|
764
|
+
const preflight = await preflightCompareResolution(resolution);
|
|
765
|
+
if (!preflight) {
|
|
766
|
+
coordinator.recordWorkflowRun(
|
|
767
|
+
"fallback",
|
|
768
|
+
classification.entities,
|
|
769
|
+
resolution.resolved,
|
|
770
|
+
[],
|
|
771
|
+
"clarification",
|
|
772
|
+
);
|
|
773
|
+
coordinator.setPendingFallbackContext({
|
|
774
|
+
assumptionsBlock: [
|
|
775
|
+
"Assumptions Context:",
|
|
776
|
+
` original symbols: ${classification.entities.symbols.join(", ")} (user)`,
|
|
777
|
+
].join("\n"),
|
|
778
|
+
missingRequired: ["symbols"],
|
|
779
|
+
extraContext:
|
|
780
|
+
"Compare workflow aborted because ticker preflight left fewer than two valid symbols. Ask the user to clarify the intended tickers before calling comparison tools.",
|
|
781
|
+
});
|
|
782
|
+
return undefined;
|
|
783
|
+
}
|
|
784
|
+
const definition = buildCompareAssetsWorkflowDefinition(preflight.resolution);
|
|
785
|
+
applyPreflightAnnotation(definition, preflight.dropped);
|
|
559
786
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
787
|
+
if (prompt) markOriginalInput(event.text);
|
|
560
788
|
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
561
789
|
}
|
|
790
|
+
|
|
791
|
+
// Rules-mode finance fallback: no workflow dispatched, but the turn is
|
|
792
|
+
// finance-shaped (classified finance intent, extracted symbols, or finance
|
|
793
|
+
// vocabulary). Record the fallback turn and stash a fallback context so
|
|
794
|
+
// the system prompt carries saved market state for this turn; non-finance
|
|
795
|
+
// prompts stay untouched.
|
|
796
|
+
const isFinanceFallback =
|
|
797
|
+
classification.workflow !== "unclassified" ||
|
|
798
|
+
classification.entities.symbols.length > 0 ||
|
|
799
|
+
hasFinanceSignals(event.text);
|
|
800
|
+
if (isFinanceFallback) {
|
|
801
|
+
coordinator.recordWorkflowRun("fallback", classification.entities, {}, [], "agent_task");
|
|
802
|
+
coordinator.setPendingFallbackContext({
|
|
803
|
+
assumptionsBlock: "",
|
|
804
|
+
missingRequired: [],
|
|
805
|
+
extraContext:
|
|
806
|
+
classification.entities.symbols.length > 0
|
|
807
|
+
? `Rules-router extracted symbols: ${classification.entities.symbols.join(", ")}.`
|
|
808
|
+
: undefined,
|
|
809
|
+
});
|
|
810
|
+
pi.appendEntry("opencandle-fallback-context", {
|
|
811
|
+
mode: "rules",
|
|
812
|
+
classifiedWorkflow: classification.workflow,
|
|
813
|
+
symbols: classification.entities.symbols,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
return undefined;
|
|
562
817
|
});
|
|
563
818
|
|
|
564
819
|
/**
|
|
@@ -573,8 +828,9 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
573
828
|
ctx: Parameters<Parameters<ExtensionAPI["on"]>[1]>[1],
|
|
574
829
|
): Promise<{ action: "transform"; text: string } | false> {
|
|
575
830
|
const storage = coordinator.getStorage();
|
|
576
|
-
const { profileSnapshot, recentWorkflowRuns, priorTurns } =
|
|
577
|
-
|
|
831
|
+
const { profileSnapshot, recentWorkflowRuns, priorTurns } = coordinator.buildRouterContextBase(
|
|
832
|
+
ctx.sessionManager,
|
|
833
|
+
);
|
|
578
834
|
// priorTurns is not scrubbed for /forget — tracked in proposal.md follow-ups.
|
|
579
835
|
const input: RouterInputContext = {
|
|
580
836
|
text,
|
|
@@ -628,6 +884,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
628
884
|
});
|
|
629
885
|
|
|
630
886
|
pi.appendEntry("opencandle-router", { output });
|
|
887
|
+
appendRouterSymbolDropEntries(output);
|
|
631
888
|
pi.appendEntry("opencandle-route-context", resolvedTurnContext);
|
|
632
889
|
coordinator.setPendingResolvedTurnContext(resolvedTurnContext);
|
|
633
890
|
applyRouteToolScope(resolvedTurnContext);
|
|
@@ -653,7 +910,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
653
910
|
|
|
654
911
|
// Workflow dispatch for recognised workflows.
|
|
655
912
|
if (output.routeKind === "workflow_dispatch" && output.workflow) {
|
|
656
|
-
return dispatchRouterWorkflow(output, ctx);
|
|
913
|
+
return dispatchRouterWorkflow(output, ctx, text);
|
|
657
914
|
}
|
|
658
915
|
|
|
659
916
|
if (output.routeKind === "pass_through") {
|
|
@@ -674,18 +931,20 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
674
931
|
coordinator.setPendingFallbackContext({
|
|
675
932
|
assumptionsBlock,
|
|
676
933
|
missingRequired: output.missing_required,
|
|
677
|
-
extraContext:
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
934
|
+
extraContext:
|
|
935
|
+
output.entities.symbols.length > 0
|
|
936
|
+
? `Router-extracted symbols: ${output.entities.symbols.join(", ")}.` +
|
|
937
|
+
` Route kind: ${output.routeKind}. Tool bundles: ${output.tool_bundles.join(", ") || "(none)"}.`
|
|
938
|
+
: undefined,
|
|
681
939
|
});
|
|
682
940
|
return false;
|
|
683
941
|
}
|
|
684
942
|
|
|
685
|
-
function dispatchRouterWorkflow(
|
|
943
|
+
async function dispatchRouterWorkflow(
|
|
686
944
|
output: RouterOutput,
|
|
687
945
|
ctx: Parameters<Parameters<ExtensionAPI["on"]>[1]>[1],
|
|
688
|
-
|
|
946
|
+
originalText: string,
|
|
947
|
+
): Promise<{ action: "transform"; text: string } | false> {
|
|
689
948
|
const workflow = output.workflow!;
|
|
690
949
|
const storage = coordinator.getStorage();
|
|
691
950
|
const workflowPrefs = storage?.getWorkflowPreferences("global") ?? {};
|
|
@@ -710,6 +969,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
710
969
|
});
|
|
711
970
|
const definition = buildPortfolioWorkflowDefinition(resolution);
|
|
712
971
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
972
|
+
if (prompt) markOriginalInput(originalText);
|
|
713
973
|
return prompt ? { action: "transform", text: prompt } : false;
|
|
714
974
|
}
|
|
715
975
|
if (workflow === "options_screener") {
|
|
@@ -734,6 +994,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
734
994
|
});
|
|
735
995
|
const definition = buildOptionsScreenerWorkflowDefinition(resolution);
|
|
736
996
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
997
|
+
if (prompt) markOriginalInput(originalText);
|
|
737
998
|
return prompt ? { action: "transform", text: prompt } : false;
|
|
738
999
|
}
|
|
739
1000
|
// Missing required symbol — treat as fallback with ask_user directive.
|
|
@@ -744,11 +1005,17 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
744
1005
|
symbols: entities.symbols,
|
|
745
1006
|
metrics: entities.compareMetrics,
|
|
746
1007
|
timeHorizon: entities.timeHorizon,
|
|
1008
|
+
budget: entities.budget,
|
|
1009
|
+
assetScope: entities.assetScope,
|
|
747
1010
|
},
|
|
748
1011
|
sources: {
|
|
749
1012
|
symbols: sourceForRouterSlot(output, "symbols", "user"),
|
|
750
1013
|
...(entities.timeHorizon ? { timeHorizon: "user" as const } : {}),
|
|
751
1014
|
...(entities.compareMetrics ? { metrics: "user" as const } : {}),
|
|
1015
|
+
...(entities.budget !== undefined
|
|
1016
|
+
? { budget: sourceForRouterSlot(output, "budget", "user") }
|
|
1017
|
+
: {}),
|
|
1018
|
+
...(entities.assetScope ? { assetScope: "user" as const } : {}),
|
|
752
1019
|
},
|
|
753
1020
|
defaultsUsed: [],
|
|
754
1021
|
missingRequired: [],
|
|
@@ -764,8 +1031,28 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
764
1031
|
workflow: "compare_assets",
|
|
765
1032
|
symbols: entities.symbols,
|
|
766
1033
|
});
|
|
767
|
-
const
|
|
1034
|
+
const preflight = await preflightCompareResolution(resolution);
|
|
1035
|
+
if (!preflight) {
|
|
1036
|
+
coordinator.recordWorkflowRun(
|
|
1037
|
+
"fallback",
|
|
1038
|
+
output.entities,
|
|
1039
|
+
Object.fromEntries(Object.entries(output.slots).map(([k, v]) => [k, v.value])),
|
|
1040
|
+
[],
|
|
1041
|
+
output.routeKind,
|
|
1042
|
+
);
|
|
1043
|
+
coordinator.setPendingResolvedTurnContext(null);
|
|
1044
|
+
coordinator.setPendingFallbackContext({
|
|
1045
|
+
assumptionsBlock: buildAssumptionsBlockFromRouter(output.slots),
|
|
1046
|
+
missingRequired: ["symbols"],
|
|
1047
|
+
extraContext:
|
|
1048
|
+
"Compare workflow aborted because ticker preflight left fewer than two valid symbols. Ask the user to clarify the intended tickers.",
|
|
1049
|
+
});
|
|
1050
|
+
return false;
|
|
1051
|
+
}
|
|
1052
|
+
const definition = buildCompareAssetsWorkflowDefinition(preflight.resolution);
|
|
1053
|
+
applyPreflightAnnotation(definition, preflight.dropped);
|
|
768
1054
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
1055
|
+
if (prompt) markOriginalInput(originalText);
|
|
769
1056
|
return prompt ? { action: "transform", text: prompt } : false;
|
|
770
1057
|
}
|
|
771
1058
|
|
|
@@ -788,6 +1075,112 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
788
1075
|
return false;
|
|
789
1076
|
}
|
|
790
1077
|
|
|
1078
|
+
function appendRouterSymbolDropEntries(output: RouterOutput): void {
|
|
1079
|
+
for (const diagnostic of output.diagnostics) {
|
|
1080
|
+
if (diagnostic.code !== "symbol_dropped") continue;
|
|
1081
|
+
const details = diagnostic.details ?? {};
|
|
1082
|
+
appendSymbolDropEntries(
|
|
1083
|
+
[
|
|
1084
|
+
{
|
|
1085
|
+
token: String(details.token ?? ""),
|
|
1086
|
+
reason: String(details.reason ?? ""),
|
|
1087
|
+
signalsChecked: Array.isArray(details.signalsChecked)
|
|
1088
|
+
? details.signalsChecked.map(String)
|
|
1089
|
+
: [],
|
|
1090
|
+
},
|
|
1091
|
+
],
|
|
1092
|
+
String(details.source ?? "llm"),
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function appendSymbolDropEntries(
|
|
1098
|
+
dropped: Array<{ token: string; reason: string; signalsChecked: string[] }>,
|
|
1099
|
+
source: string,
|
|
1100
|
+
): void {
|
|
1101
|
+
for (const drop of dropped) {
|
|
1102
|
+
pi.appendEntry("opencandle-symbol-dropped", {
|
|
1103
|
+
token: drop.token,
|
|
1104
|
+
reason: drop.reason,
|
|
1105
|
+
signalsChecked: drop.signalsChecked,
|
|
1106
|
+
source,
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function disambiguateRulesModeSymbols(
|
|
1112
|
+
text: string,
|
|
1113
|
+
extractedSymbols: string[],
|
|
1114
|
+
): {
|
|
1115
|
+
kept: string[];
|
|
1116
|
+
dropped: Array<{ token: string; reason: string; signalsChecked: string[] }>;
|
|
1117
|
+
} {
|
|
1118
|
+
const candidates = mergeSymbols(extractedSymbols, rawTickerLikeTokens(text));
|
|
1119
|
+
const disambiguated = disambiguateSymbols(candidates, text);
|
|
1120
|
+
return {
|
|
1121
|
+
kept: disambiguated.kept.filter((symbol) => extractedSymbols.includes(symbol)),
|
|
1122
|
+
dropped: disambiguated.dropped,
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function rawTickerLikeTokens(text: string): string[] {
|
|
1127
|
+
const tokens: string[] = [];
|
|
1128
|
+
for (const match of text.matchAll(/\$?([A-Za-z]{1,5})\b/g)) {
|
|
1129
|
+
const raw = match[1];
|
|
1130
|
+
if (raw !== raw.toUpperCase()) continue;
|
|
1131
|
+
const token = raw.toUpperCase();
|
|
1132
|
+
if (!tokens.includes(token)) tokens.push(token);
|
|
1133
|
+
}
|
|
1134
|
+
return tokens;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function isComparePrompt(text: string): boolean {
|
|
1138
|
+
return /\b(?:compare|vs\.?|versus|which\s+is\s+better)\b/i.test(text);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
async function preflightCompareResolution(
|
|
1142
|
+
resolution: SlotResolution<CompareAssetsSlots>,
|
|
1143
|
+
): Promise<{
|
|
1144
|
+
resolution: SlotResolution<CompareAssetsSlots>;
|
|
1145
|
+
dropped: Array<{ symbol: string; reason: string }>;
|
|
1146
|
+
} | null> {
|
|
1147
|
+
const result = await preflightSymbols(resolution.resolved.symbols, {
|
|
1148
|
+
cache: coordinator.getTickerValidationCache(),
|
|
1149
|
+
search: options?.symbolSearch,
|
|
1150
|
+
});
|
|
1151
|
+
for (const drop of result.dropped) {
|
|
1152
|
+
pi.appendEntry("opencandle-symbol-preflight-dropped", drop);
|
|
1153
|
+
}
|
|
1154
|
+
if (result.valid.length < 2) {
|
|
1155
|
+
pi.appendEntry("opencandle-workflow-aborted", {
|
|
1156
|
+
reason: "preflight-insufficient-symbols",
|
|
1157
|
+
dropped: result.dropped,
|
|
1158
|
+
});
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
resolution: {
|
|
1163
|
+
...resolution,
|
|
1164
|
+
resolved: {
|
|
1165
|
+
...resolution.resolved,
|
|
1166
|
+
symbols: result.valid,
|
|
1167
|
+
},
|
|
1168
|
+
},
|
|
1169
|
+
dropped: result.dropped,
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function applyPreflightAnnotation(
|
|
1174
|
+
definition: ReturnType<typeof buildCompareAssetsWorkflowDefinition>,
|
|
1175
|
+
dropped: Array<{ symbol: string; reason: string }>,
|
|
1176
|
+
): void {
|
|
1177
|
+
if (dropped.length === 0 || definition.steps.length === 0) return;
|
|
1178
|
+
definition.steps[0] = {
|
|
1179
|
+
...definition.steps[0],
|
|
1180
|
+
prompt: `${formatPreflightDropAnnotation(dropped)}\n\n${definition.steps[0].prompt}`,
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
|
|
791
1184
|
function mergeRouterSlotsIntoEntities(output: RouterOutput): ExtractedEntities {
|
|
792
1185
|
const entities: ExtractedEntities = {
|
|
793
1186
|
...output.entities,
|
|
@@ -798,7 +1191,10 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
798
1191
|
entities.budget = output.slots.budget.value;
|
|
799
1192
|
}
|
|
800
1193
|
|
|
801
|
-
const
|
|
1194
|
+
const droppedSymbols = droppedSymbolsFromDiagnostics(output);
|
|
1195
|
+
const slotSymbols = symbolsFromRouterSlots(output).filter(
|
|
1196
|
+
(symbol) => !droppedSymbols.has(symbol),
|
|
1197
|
+
);
|
|
802
1198
|
if (slotSymbols.length > 0 && slotSymbols.length > entities.symbols.length) {
|
|
803
1199
|
entities.symbols = mergeSymbols(slotSymbols, entities.symbols);
|
|
804
1200
|
}
|
|
@@ -806,6 +1202,18 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
806
1202
|
return entities;
|
|
807
1203
|
}
|
|
808
1204
|
|
|
1205
|
+
function droppedSymbolsFromDiagnostics(output: RouterOutput): Set<string> {
|
|
1206
|
+
const dropped = new Set<string>();
|
|
1207
|
+
for (const diagnostic of output.diagnostics) {
|
|
1208
|
+
if (diagnostic.code !== "symbol_dropped") continue;
|
|
1209
|
+
const token = diagnostic.details?.token;
|
|
1210
|
+
if (typeof token === "string" && token.trim() !== "") {
|
|
1211
|
+
dropped.add(token.toUpperCase());
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return dropped;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
809
1217
|
function withRouterSlotSources<T extends object>(
|
|
810
1218
|
resolution: SlotResolution<T>,
|
|
811
1219
|
output: RouterOutput,
|
|
@@ -946,10 +1354,46 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
946
1354
|
return {
|
|
947
1355
|
systemPrompt: coordinator.buildSystemPrompt(
|
|
948
1356
|
event.systemPrompt,
|
|
949
|
-
|
|
1357
|
+
coordinator.getActiveWorkflowType(),
|
|
950
1358
|
fallbackContext,
|
|
951
1359
|
resolvedTurnContext,
|
|
952
1360
|
),
|
|
953
1361
|
};
|
|
954
1362
|
});
|
|
955
1363
|
}
|
|
1364
|
+
|
|
1365
|
+
/** Concatenate text from a Pi message `content` (plain string or block array). */
|
|
1366
|
+
function extractMessageText(content: unknown): string {
|
|
1367
|
+
if (typeof content === "string") return content;
|
|
1368
|
+
if (!Array.isArray(content)) return "";
|
|
1369
|
+
let text = "";
|
|
1370
|
+
for (const block of content) {
|
|
1371
|
+
if (
|
|
1372
|
+
block &&
|
|
1373
|
+
typeof block === "object" &&
|
|
1374
|
+
(block as { type?: unknown }).type === "text" &&
|
|
1375
|
+
typeof (block as { text?: unknown }).text === "string"
|
|
1376
|
+
) {
|
|
1377
|
+
text += (block as { text: string }).text;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return text;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
/**
|
|
1384
|
+
* True when the session name is still an auto-set placeholder that the LLM
|
|
1385
|
+
* title may replace: unset, exactly one of the candidate user texts, or the
|
|
1386
|
+
* GUI server's truncated "first 77 chars + ..." form of one of them.
|
|
1387
|
+
*/
|
|
1388
|
+
function isPlaceholderSessionName(name: string | undefined, candidates: string[]): boolean {
|
|
1389
|
+
if (name === undefined || name.trim().length === 0) return true;
|
|
1390
|
+
const trimmed = name.trim();
|
|
1391
|
+
for (const candidate of candidates) {
|
|
1392
|
+
const candidateTrimmed = candidate.trim();
|
|
1393
|
+
if (trimmed === candidateTrimmed) return true;
|
|
1394
|
+
if (trimmed.endsWith("...") && candidateTrimmed.startsWith(trimmed.slice(0, -3))) {
|
|
1395
|
+
return true;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return false;
|
|
1399
|
+
}
|