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.
- package/LICENSE +1 -1
- package/README.md +186 -117
- 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 +32 -8
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +19 -3
- package/dist/config.js +69 -3
- 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.d.ts +1 -3
- package/dist/infra/browser.js +4 -2
- 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.d.ts +4 -0
- package/dist/infra/rate-limiter.js +17 -10
- 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.d.ts +9 -0
- package/dist/memory/manager.js +39 -22
- 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.d.ts +3 -2
- 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 +4 -0
- 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 +637 -59
- 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 +17 -2
- 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 +18 -3
- package/dist/prompts/context-builder.js +117 -18
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/prompts/disclaimer.js +1 -1
- package/dist/prompts/disclaimer.js.map +1 -1
- package/dist/prompts/policy-cards.d.ts +13 -0
- package/dist/prompts/policy-cards.js +197 -0
- package/dist/prompts/policy-cards.js.map +1 -0
- package/dist/prompts/sections.d.ts +1 -1
- package/dist/prompts/sections.js +3 -3
- package/dist/prompts/sections.js.map +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 +209 -19
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/alpha-vantage.d.ts +1 -1
- package/dist/providers/alpha-vantage.js +49 -8
- 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 +9 -1
- package/dist/providers/sec-edgar.js +181 -6
- 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 +3 -1
- package/dist/providers/yahoo-finance.js +226 -11
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.d.ts +9 -0
- package/dist/routing/classify-intent.js +153 -3
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/defaults.d.ts +1 -1
- package/dist/routing/defaults.js +3 -3
- package/dist/routing/defaults.js.map +1 -1
- package/dist/routing/entity-extractor.d.ts +2 -0
- package/dist/routing/entity-extractor.js +377 -26
- 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 +12 -6
- package/dist/routing/index.js +8 -4
- package/dist/routing/index.js.map +1 -1
- package/dist/routing/legacy-rule-router.d.ts +9 -0
- package/dist/routing/legacy-rule-router.js +12 -0
- package/dist/routing/legacy-rule-router.js.map +1 -0
- package/dist/routing/planning.d.ts +54 -0
- package/dist/routing/planning.js +562 -0
- package/dist/routing/planning.js.map +1 -0
- package/dist/routing/route-manifest.d.ts +35 -0
- package/dist/routing/route-manifest.js +242 -0
- package/dist/routing/route-manifest.js.map +1 -0
- package/dist/routing/router-llm-client.js.map +1 -1
- package/dist/routing/router-prompt.js +46 -45
- package/dist/routing/router-prompt.js.map +1 -1
- package/dist/routing/router-types.d.ts +10 -0
- package/dist/routing/router.d.ts +1 -0
- package/dist/routing/router.js +572 -13
- package/dist/routing/router.js.map +1 -1
- package/dist/routing/slot-resolver.d.ts +1 -1
- package/dist/routing/slot-resolver.js +45 -7
- 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 +44 -0
- package/dist/routing/turn-context.js +45 -0
- package/dist/routing/turn-context.js.map +1 -0
- package/dist/routing/types.d.ts +15 -1
- package/dist/runtime/answer-contracts.d.ts +82 -0
- package/dist/runtime/answer-contracts.js +442 -0
- package/dist/runtime/answer-contracts.js.map +1 -0
- package/dist/runtime/artifact-contracts.d.ts +14 -0
- package/dist/runtime/artifact-contracts.js +57 -0
- package/dist/runtime/artifact-contracts.js.map +1 -0
- package/dist/runtime/planning-evidence.d.ts +99 -0
- package/dist/runtime/planning-evidence.js +466 -0
- package/dist/runtime/planning-evidence.js.map +1 -0
- 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 +29 -3
- package/dist/runtime/session-coordinator.js +204 -31
- 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 +7 -3
- 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 +12 -7
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.js +19 -10
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +24 -12
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.js +9 -4
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.js +9 -4
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.d.ts +1 -0
- package/dist/tools/fundamentals/sec-filings.js +36 -4
- package/dist/tools/fundamentals/sec-filings.js.map +1 -1
- package/dist/tools/index.d.ts +23 -18
- package/dist/tools/index.js +53 -38
- 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 +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 +44 -9
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/market/crypto-history.js +21 -3
- package/dist/tools/market/crypto-history.js.map +1 -1
- package/dist/tools/market/crypto-price.js +4 -2
- 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 +161 -9
- 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 +27 -8
- package/dist/tools/market/stock-history.js.map +1 -1
- package/dist/tools/market/stock-quote.js +6 -4
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/greeks.js +1 -2
- package/dist/tools/options/greeks.js.map +1 -1
- package/dist/tools/options/option-chain.js +27 -9
- 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 +34 -14
- 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.d.ts +8 -0
- package/dist/tools/portfolio/holdings-overlap.js +112 -0
- package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
- 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 +338 -88
- 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 +46 -7
- 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 +247 -102
- package/dist/tools/portfolio/tracker.js.map +1 -1
- package/dist/tools/portfolio/watchlist.d.ts +6 -4
- package/dist/tools/portfolio/watchlist.js +209 -101
- package/dist/tools/portfolio/watchlist.js.map +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js +24 -11
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.js +71 -14
- 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 +13 -6
- 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 +37 -12
- package/dist/tools/sentiment/web-search.js.map +1 -1
- package/dist/tools/sentiment/web-sentiment.js +16 -4
- package/dist/tools/sentiment/web-sentiment.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +3 -3
- package/dist/tools/technical/backtest.js +65 -44
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +24 -8
- 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/options.d.ts +10 -0
- package/dist/types/portfolio.d.ts +41 -4
- package/dist/workflows/compare-assets.d.ts +0 -3
- package/dist/workflows/compare-assets.js +55 -10
- 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 +88 -14
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.d.ts +0 -3
- package/dist/workflows/portfolio-builder.js +7 -11
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/ask-user-bridge.ts +82 -0
- 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/gui-session-manager.ts +5 -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 +58 -11
- package/gui/server/prompt-observation.ts +58 -0
- package/gui/server/quote-snapshot-store.ts +50 -0
- package/gui/server/server.ts +236 -376
- package/gui/server/session-actions.ts +186 -1
- package/gui/server/session-entry-wait.ts +81 -0
- 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 +22 -12
- package/src/analysts/contracts.ts +10 -23
- package/src/analysts/orchestrator.ts +8 -43
- package/src/cli.ts +37 -13
- package/src/config.ts +99 -7
- package/src/index.ts +1 -1
- package/src/infra/browser.ts +4 -2
- 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 +32 -20
- 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 +57 -26
- package/src/memory/retrieval.ts +8 -7
- package/src/memory/sqlite.ts +407 -6
- package/src/memory/storage.ts +8 -17
- package/src/memory/tool-defaults.ts +60 -39
- package/src/memory/types.ts +7 -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 +795 -120
- package/src/pi/session.ts +7 -5
- package/src/pi/setup.ts +61 -33
- package/src/pi/tool-adapter.ts +5 -2
- package/src/prompts/context-builder.ts +143 -21
- package/src/prompts/disclaimer.ts +1 -1
- package/src/prompts/policy-cards.ts +220 -0
- package/src/prompts/sections.ts +4 -4
- package/src/prompts/symbol-preflight.ts +80 -0
- package/src/prompts/workflow-prompts.ts +231 -28
- package/src/providers/alpha-vantage.ts +82 -40
- 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 +235 -5
- 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 +292 -20
- package/src/routing/classify-intent.ts +186 -4
- package/src/routing/defaults.ts +4 -4
- package/src/routing/entity-extractor.ts +428 -28
- package/src/routing/fund-symbols.ts +58 -0
- package/src/routing/horizon.ts +7 -0
- package/src/routing/index.ts +60 -16
- package/src/routing/legacy-rule-router.ts +13 -0
- package/src/routing/planning.ts +823 -0
- package/src/routing/route-manifest.ts +309 -0
- package/src/routing/router-llm-client.ts +4 -4
- package/src/routing/router-prompt.ts +52 -52
- package/src/routing/router-types.ts +18 -0
- package/src/routing/router.ts +717 -20
- package/src/routing/slot-resolver.ts +75 -14
- package/src/routing/symbol-disambiguator.ts +72 -0
- package/src/routing/turn-context.ts +108 -0
- package/src/routing/types.ts +15 -1
- package/src/runtime/answer-contracts.ts +672 -0
- package/src/runtime/artifact-contracts.ts +77 -0
- package/src/runtime/planning-evidence.ts +682 -0
- package/src/runtime/prompt-step.ts +1 -16
- package/src/runtime/run-context.ts +12 -2
- package/src/runtime/session-coordinator.ts +297 -56
- 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 +7 -3
- package/src/tool-kit.ts +10 -9
- package/src/tools/fundamentals/company-overview.ts +20 -10
- package/src/tools/fundamentals/comps.ts +69 -56
- package/src/tools/fundamentals/dcf.ts +146 -96
- package/src/tools/fundamentals/earnings.ts +17 -7
- package/src/tools/fundamentals/financials.ts +17 -8
- package/src/tools/fundamentals/sec-filings.ts +52 -8
- package/src/tools/index.ts +53 -38
- 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 +2 -2
- package/src/tools/macro/fred-data.ts +80 -42
- package/src/tools/market/crypto-history.ts +25 -4
- package/src/tools/market/crypto-price.ts +7 -7
- package/src/tools/market/screen-stocks.ts +279 -0
- package/src/tools/market/search-ticker.ts +219 -18
- package/src/tools/market/stock-history.ts +38 -13
- package/src/tools/market/stock-quote.ts +11 -8
- package/src/tools/options/greeks.ts +5 -6
- package/src/tools/options/option-chain.ts +47 -18
- package/src/tools/portfolio/alerts.ts +457 -0
- package/src/tools/portfolio/correlation.ts +48 -21
- package/src/tools/portfolio/daily-report.ts +101 -0
- package/src/tools/portfolio/holdings-overlap.ts +139 -0
- package/src/tools/portfolio/notifications.ts +45 -0
- package/src/tools/portfolio/predictions.ts +407 -107
- package/src/tools/portfolio/risk-analysis.ts +47 -8
- package/src/tools/portfolio/tracker.ts +271 -110
- package/src/tools/portfolio/watchlist.ts +251 -116
- package/src/tools/sentiment/reddit-sentiment.ts +51 -25
- package/src/tools/sentiment/sentiment-summary.ts +116 -35
- package/src/tools/sentiment/sentiment-trend.ts +24 -7
- package/src/tools/sentiment/twitter-sentiment.ts +23 -16
- package/src/tools/sentiment/untrusted-text.ts +21 -0
- package/src/tools/sentiment/web-search.ts +52 -16
- package/src/tools/sentiment/web-sentiment.ts +27 -11
- package/src/tools/technical/backtest.ts +78 -47
- package/src/tools/technical/indicators.ts +40 -17
- package/src/types/index.ts +8 -3
- package/src/types/market.ts +1 -0
- package/src/types/options.ts +17 -0
- package/src/types/portfolio.ts +46 -4
- package/src/types/sentiment.ts +2 -2
- package/src/workflows/compare-assets.ts +67 -19
- package/src/workflows/index.ts +3 -4
- package/src/workflows/options-screener.ts +98 -22
- package/src/workflows/portfolio-builder.ts +40 -29
- package/dist/runtime/index.d.ts +0 -16
- package/dist/runtime/index.js +0 -10
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/provider-ids.d.ts +0 -14
- package/dist/runtime/provider-ids.js +0 -14
- package/dist/runtime/provider-ids.js.map +0 -1
- 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-D1ImSJTe.js +0 -1
- package/gui/web/dist/assets/index-DBrWq43L.css +0 -1
- package/gui/web/dist/assets/index-RflHaj0y.js +0 -67
- package/src/runtime/index.ts +0 -55
- package/src/runtime/provider-ids.ts +0 -15
- 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 {
|
|
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 {
|
|
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, {
|
|
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, {
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|
509
|
+
return dispatched || undefined;
|
|
389
510
|
}
|
|
390
|
-
// --- rules mode (
|
|
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
|
-
|
|
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", {
|
|
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.
|
|
403
|
-
|
|
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", {
|
|
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.
|
|
412
|
-
|
|
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" &&
|
|
619
|
+
if (classification.workflow === "compare_assets" &&
|
|
620
|
+
classification.entities.symbols.length >= 2) {
|
|
416
621
|
const resolution = {
|
|
417
|
-
resolved: {
|
|
418
|
-
|
|
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", {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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.
|
|
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])), [],
|
|
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(
|
|
509
|
-
coordinator.recordWorkflowRun("portfolio_builder",
|
|
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
|
|
799
|
+
entities,
|
|
513
800
|
resolved: resolution.resolved,
|
|
514
801
|
});
|
|
515
802
|
const definition = buildPortfolioWorkflowDefinition(resolution);
|
|
516
|
-
coordinator.
|
|
517
|
-
|
|
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(
|
|
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",
|
|
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
|
|
816
|
+
entities,
|
|
528
817
|
resolved: resolution.resolved,
|
|
529
818
|
});
|
|
530
819
|
const definition = buildOptionsScreenerWorkflowDefinition(resolution);
|
|
531
|
-
coordinator.
|
|
532
|
-
|
|
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" &&
|
|
827
|
+
if (workflow === "compare_assets" && entities.symbols.length >= 2) {
|
|
537
828
|
const resolution = {
|
|
538
|
-
resolved: {
|
|
539
|
-
|
|
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",
|
|
848
|
+
coordinator.recordWorkflowRun("compare_assets", entities, resolution.resolved, [], output.routeKind);
|
|
544
849
|
pi.appendEntry("opencandle-workflow", {
|
|
545
850
|
workflow: "compare_assets",
|
|
546
|
-
symbols:
|
|
851
|
+
symbols: entities.symbols,
|
|
547
852
|
});
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
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])), [],
|
|
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: ${
|
|
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,
|
|
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
|