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
package/gui/server/server.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
1
2
|
import { createReadStream, existsSync } from "node:fs";
|
|
2
3
|
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
3
4
|
import { extname, join, resolve } from "node:path";
|
|
@@ -12,30 +13,45 @@ import {
|
|
|
12
13
|
SettingsManager,
|
|
13
14
|
} from "@earendil-works/pi-coding-agent";
|
|
14
15
|
import { createOpenCandleSession } from "../../src/index.js";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { persistProviderCredential } from "../../src/onboarding/connect.js";
|
|
16
|
+
import type { ChatEvent } from "../shared/chat-events.js";
|
|
17
|
+
import { createAskUserBridge } from "./ask-user-bridge.js";
|
|
18
18
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import { invokeToolFromUi } from "./invoke-tool.js";
|
|
28
|
-
import { buildCatalog, setToolEnabled } from "./tool-metadata.js";
|
|
29
|
-
import { deleteSessionFile, renameSessionFile } from "./session-actions.js";
|
|
30
|
-
import { acquireWriterLock, refreshWriterLock, releaseWriterLock } from "./writer-lock.js";
|
|
19
|
+
createLocalAutomationHeartbeat,
|
|
20
|
+
normalizeAutomationHeartbeatMs,
|
|
21
|
+
} from "./automation-heartbeat.js";
|
|
22
|
+
import {
|
|
23
|
+
type BackgroundQuotePoller,
|
|
24
|
+
BackgroundQuoteRefreshes,
|
|
25
|
+
createBackgroundQuotePoller,
|
|
26
|
+
} from "./background-quotes.js";
|
|
31
27
|
import { sessionEntriesToChatEvents } from "./chat-event-adapter.js";
|
|
28
|
+
import { chatRunSessionConflict } from "./chat-run-session.js";
|
|
29
|
+
import { createInitialGuiSessionManager } from "./gui-session-manager.js";
|
|
30
|
+
import { createToolInvokeController } from "./invoke-tool.js";
|
|
32
31
|
import { createLiveChatEventAdapter } from "./live-chat-event-adapter.js";
|
|
33
|
-
import {
|
|
34
|
-
|
|
32
|
+
import {
|
|
33
|
+
buildMarketStateQuoteSnapshot,
|
|
34
|
+
buildMarketStateSnapshot,
|
|
35
|
+
searchInstrumentCandidates,
|
|
36
|
+
} from "./market-state-api.js";
|
|
37
|
+
import { buildModelSetupState, createModelSetupController } from "./model-setup.js";
|
|
38
|
+
import { isTrustedPrivateApiRequest, privateApiCookieHeader } from "./private-api-access.js";
|
|
39
|
+
import { createPromptObservation, observePromptEvent } from "./prompt-observation.js";
|
|
40
|
+
import { QuoteSnapshotStore } from "./quote-snapshot-store.js";
|
|
41
|
+
import { createSessionActionsController, promptAndSettle } from "./session-actions.js";
|
|
42
|
+
import { waitForNewEntryId } from "./session-entry-wait.js";
|
|
43
|
+
import { createGracefulShutdown } from "./shutdown.js";
|
|
44
|
+
import { acquireWriterLock, refreshWriterLock, releaseWriterLock } from "./writer-lock.js";
|
|
45
|
+
import { createWsHub, type WsHub } from "./ws-hub.js";
|
|
35
46
|
|
|
36
47
|
const cwd = process.cwd();
|
|
37
48
|
const host = process.env.OPENCANDLE_GUI_HOST ?? "127.0.0.1";
|
|
38
49
|
const port = Number(process.env.OPENCANDLE_GUI_PORT ?? 14567);
|
|
50
|
+
const automationHeartbeatMs = normalizeAutomationHeartbeatMs(
|
|
51
|
+
process.env.OPENCANDLE_AUTOMATION_HEARTBEAT_MS,
|
|
52
|
+
);
|
|
53
|
+
const allowRemotePrivateApi = process.env.OPENCANDLE_GUI_ALLOW_REMOTE_PRIVATE_API === "1";
|
|
54
|
+
const privateApiSessionToken = randomBytes(32).toString("base64url");
|
|
39
55
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
40
56
|
const webDist = resolve(__dirname, "../web/dist");
|
|
41
57
|
|
|
@@ -43,10 +59,16 @@ const agentDir = getAgentDir();
|
|
|
43
59
|
const authStorage = AuthStorage.create();
|
|
44
60
|
const modelRegistry = ModelRegistry.create(authStorage);
|
|
45
61
|
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
46
|
-
const initialSessionManager =
|
|
62
|
+
const initialSessionManager = createInitialGuiSessionManager(cwd);
|
|
47
63
|
let sessionManager = initialSessionManager;
|
|
48
64
|
const sessionDir = sessionManager.getSessionDir();
|
|
49
65
|
const lockResult = await acquireWriterLock(sessionDir, "gui");
|
|
66
|
+
let wsHub: WsHub;
|
|
67
|
+
let quotePoller: BackgroundQuotePoller;
|
|
68
|
+
const askUserBridge = createAskUserBridge({
|
|
69
|
+
broadcast: (message) => wsHub.broadcast(message),
|
|
70
|
+
getSessionId: () => sessionManager.getSessionId(),
|
|
71
|
+
});
|
|
50
72
|
const runtime = await createAgentSessionRuntime(
|
|
51
73
|
async (opts) => {
|
|
52
74
|
const services = await createAgentSessionServices({
|
|
@@ -63,24 +85,77 @@ const runtime = await createAgentSessionRuntime(
|
|
|
63
85
|
modelRegistry,
|
|
64
86
|
settingsManager,
|
|
65
87
|
sessionManager: opts.sessionManager,
|
|
88
|
+
askUserHandler: askUserBridge.ask,
|
|
66
89
|
});
|
|
67
90
|
return { ...result, services, diagnostics: services.diagnostics };
|
|
68
91
|
},
|
|
69
92
|
{ cwd, agentDir, sessionManager },
|
|
70
93
|
);
|
|
71
94
|
let session = runtime.session;
|
|
72
|
-
const clients = new Set<WsClient>();
|
|
73
95
|
const heartbeat = setInterval(() => refreshWriterLock(sessionDir), 5000);
|
|
74
96
|
const backgroundQuoteRefreshes = new BackgroundQuoteRefreshes();
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
const quoteSnapshotStore = new QuoteSnapshotStore(() => buildMarketStateQuoteSnapshot());
|
|
98
|
+
quotePoller = createBackgroundQuotePoller({
|
|
99
|
+
getClientCount: () => wsHub.getClientCount(),
|
|
100
|
+
getSessionManager: () => sessionManager,
|
|
101
|
+
refreshes: backgroundQuoteRefreshes,
|
|
102
|
+
broadcastState: () => wsHub.broadcastState(),
|
|
103
|
+
});
|
|
104
|
+
const localAutomationHeartbeat = createLocalAutomationHeartbeat({
|
|
105
|
+
role: lockResult.role,
|
|
106
|
+
getSessionId: () => sessionManager.getSessionId(),
|
|
107
|
+
intervalMs: automationHeartbeatMs,
|
|
108
|
+
});
|
|
109
|
+
const modelSetupController = createModelSetupController({
|
|
110
|
+
role: lockResult.role,
|
|
111
|
+
getSession: () => session,
|
|
112
|
+
getSessionManager: () => sessionManager,
|
|
113
|
+
broadcastState: () => wsHub.broadcastState(),
|
|
114
|
+
});
|
|
115
|
+
const toolInvokeController = createToolInvokeController({
|
|
116
|
+
role: lockResult.role,
|
|
117
|
+
getSessionManager: () => sessionManager,
|
|
118
|
+
broadcastState: () => wsHub.broadcastState(),
|
|
119
|
+
onMarketStateChanged: () => quoteSnapshotStore.invalidate(),
|
|
120
|
+
});
|
|
121
|
+
const sessionActionsController = createSessionActionsController({
|
|
122
|
+
role: lockResult.role,
|
|
123
|
+
cwd,
|
|
124
|
+
sessionDir,
|
|
125
|
+
getSession: () => session,
|
|
126
|
+
getSessionManager: () => sessionManager,
|
|
127
|
+
getModelSetupState: () => modelSetupController.buildCurrentModelSetupState(),
|
|
128
|
+
askUserBridge,
|
|
129
|
+
runtime,
|
|
130
|
+
sendBoot: (client) => wsHub.sendBoot(client),
|
|
131
|
+
broadcastState: () => wsHub.broadcastState(),
|
|
132
|
+
broadcastSessions: () => wsHub.broadcastSessions(),
|
|
133
|
+
});
|
|
134
|
+
wsHub = createWsHub({
|
|
135
|
+
role: lockResult.role,
|
|
136
|
+
lock: lockResult.lock,
|
|
137
|
+
cwd,
|
|
138
|
+
sessionDir,
|
|
139
|
+
getSession: () => session,
|
|
140
|
+
getSessionManager: () => sessionManager,
|
|
141
|
+
backgroundQuoteRefreshes,
|
|
142
|
+
askUserBridge,
|
|
143
|
+
modelSetupController,
|
|
144
|
+
toolInvokeController,
|
|
145
|
+
sessionActionsController,
|
|
146
|
+
onClientCountChanged: () => quotePoller.updatePoller(),
|
|
147
|
+
isTrustedRequest: (req) =>
|
|
148
|
+
isTrustedPrivateApiRequest(req.headers, privateApiSessionToken, req.socket.remoteAddress, {
|
|
149
|
+
allowRemote: allowRemotePrivateApi,
|
|
150
|
+
}),
|
|
151
|
+
});
|
|
77
152
|
|
|
78
|
-
let unsubscribeSession = subscribeToSessionEvents();
|
|
153
|
+
let unsubscribeSession = wsHub.subscribeToSessionEvents();
|
|
79
154
|
runtime.setRebindSession(async (nextSession) => {
|
|
80
155
|
unsubscribeSession();
|
|
81
156
|
session = nextSession;
|
|
82
157
|
sessionManager = nextSession.sessionManager;
|
|
83
|
-
unsubscribeSession = subscribeToSessionEvents();
|
|
158
|
+
unsubscribeSession = wsHub.subscribeToSessionEvents();
|
|
84
159
|
});
|
|
85
160
|
|
|
86
161
|
const server = createServer((req, res) => {
|
|
@@ -94,7 +169,27 @@ async function handleHttpRequest(req: IncomingMessage, res: ServerResponse): Pro
|
|
|
94
169
|
return;
|
|
95
170
|
}
|
|
96
171
|
|
|
172
|
+
if (url.pathname === "/api/bootstrap" && req.method === "GET") {
|
|
173
|
+
if (!allowTrustedGuiRequest(req, res, "Bootstrap API")) return;
|
|
174
|
+
writeJson(res, await wsHub.buildBootstrapPayload());
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (url.pathname === "/api/session/new" && req.method === "POST") {
|
|
179
|
+
if (!allowTrustedGuiRequest(req, res, "Session API")) return;
|
|
180
|
+
if (lockResult.role !== "writer") {
|
|
181
|
+
writeJson(res, { error: "Read-only follower mode" }, 409);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
await sessionActionsController.handleNewSession();
|
|
185
|
+
wsHub.broadcastState();
|
|
186
|
+
wsHub.broadcastSessions();
|
|
187
|
+
writeJson(res, await wsHub.buildBootstrapPayload());
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
97
191
|
if (url.pathname === "/api/sessions" && req.method === "GET") {
|
|
192
|
+
if (!allowTrustedGuiRequest(req, res, "Session API")) return;
|
|
98
193
|
writeJson(res, {
|
|
99
194
|
currentSessionId: sessionManager.getSessionId(),
|
|
100
195
|
role: lockResult.role,
|
|
@@ -104,15 +199,35 @@ async function handleHttpRequest(req: IncomingMessage, res: ServerResponse): Pro
|
|
|
104
199
|
}
|
|
105
200
|
|
|
106
201
|
if (url.pathname === "/api/session/events" && req.method === "GET") {
|
|
202
|
+
if (!allowTrustedGuiRequest(req, res, "Session API")) return;
|
|
107
203
|
writeJson(res, {
|
|
108
204
|
sessionId: sessionManager.getSessionId(),
|
|
109
205
|
role: lockResult.role,
|
|
110
|
-
events: currentChatEvents(),
|
|
206
|
+
events: wsHub.currentChatEvents(),
|
|
111
207
|
});
|
|
112
208
|
return;
|
|
113
209
|
}
|
|
114
210
|
|
|
211
|
+
if (url.pathname === "/api/market-state" && req.method === "GET") {
|
|
212
|
+
if (!allowTrustedGuiRequest(req, res, "Market-state API")) return;
|
|
213
|
+
writeJson(res, buildMarketStateSnapshot());
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (url.pathname === "/api/market-state/quotes" && req.method === "GET") {
|
|
218
|
+
if (!allowTrustedGuiRequest(req, res, "Market-state API")) return;
|
|
219
|
+
writeJson(res, await quoteSnapshotStore.get());
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (url.pathname === "/api/instruments/search" && req.method === "GET") {
|
|
224
|
+
if (!allowTrustedGuiRequest(req, res, "Market-state API")) return;
|
|
225
|
+
writeJson(res, await searchInstrumentCandidates(url.searchParams.get("q") ?? ""));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
115
229
|
if (url.pathname === "/api/chat/run" && req.method === "POST") {
|
|
230
|
+
if (!allowTrustedGuiRequest(req, res, "Chat run API")) return;
|
|
116
231
|
await handleSseChatRun(req, res);
|
|
117
232
|
return;
|
|
118
233
|
}
|
|
@@ -122,7 +237,7 @@ async function handleHttpRequest(req: IncomingMessage, res: ServerResponse): Pro
|
|
|
122
237
|
if (!path.startsWith(webDist) || !existsSync(path)) {
|
|
123
238
|
const fallback = resolve(join(webDist, "index.html"));
|
|
124
239
|
if (!extname(requested) && fallback.startsWith(webDist) && existsSync(fallback)) {
|
|
125
|
-
res.writeHead(200,
|
|
240
|
+
res.writeHead(200, privateGuiHeaders("text/html; charset=utf-8"));
|
|
126
241
|
createReadStream(fallback).pipe(res);
|
|
127
242
|
return;
|
|
128
243
|
}
|
|
@@ -130,278 +245,43 @@ async function handleHttpRequest(req: IncomingMessage, res: ServerResponse): Pro
|
|
|
130
245
|
return;
|
|
131
246
|
}
|
|
132
247
|
|
|
133
|
-
|
|
248
|
+
const type = contentType(path);
|
|
249
|
+
res.writeHead(200, privateGuiHeaders(type));
|
|
134
250
|
createReadStream(path).pipe(res);
|
|
135
251
|
}
|
|
136
252
|
|
|
137
|
-
server.on("upgrade", (req, socket) =>
|
|
138
|
-
if (req.url !== "/ws") {
|
|
139
|
-
socket.destroy();
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const client = acceptWebSocket(req, socket);
|
|
144
|
-
clients.add(client);
|
|
145
|
-
client.onClose(() => {
|
|
146
|
-
clients.delete(client);
|
|
147
|
-
updatePoller();
|
|
148
|
-
});
|
|
149
|
-
client.onMessage((message) => void handleClientMessage(client, message));
|
|
150
|
-
sendBoot(client);
|
|
151
|
-
updatePoller();
|
|
152
|
-
});
|
|
253
|
+
server.on("upgrade", (req, socket) => wsHub.handleUpgrade(req, socket));
|
|
153
254
|
|
|
154
255
|
server.listen(port, host, () => {
|
|
155
256
|
console.log(`OpenCandle GUI listening on http://${host}:${port}`);
|
|
156
257
|
if (host === "0.0.0.0") {
|
|
157
258
|
console.log(`OpenCandle GUI is accepting LAN/Tailscale connections on port ${port}`);
|
|
158
259
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
process.on("SIGINT", shutdown);
|
|
163
|
-
process.on("SIGTERM", shutdown);
|
|
164
|
-
|
|
165
|
-
async function handleClientMessage(client: WsClient, message: unknown): Promise<void> {
|
|
166
|
-
const data = asRecord(message);
|
|
167
|
-
try {
|
|
168
|
-
switch (data.type) {
|
|
169
|
-
case "chat.prompt":
|
|
170
|
-
await handlePrompt(String(data.prompt ?? ""));
|
|
171
|
-
break;
|
|
172
|
-
case "tool.invoke":
|
|
173
|
-
await handleToolInvoke(String(data.toolName ?? ""), asRecord(data.args));
|
|
174
|
-
break;
|
|
175
|
-
case "tool.enabled":
|
|
176
|
-
if (lockResult.role !== "writer") throw new Error("Read-only follower mode");
|
|
177
|
-
setToolEnabled(String(data.toolName), Boolean(data.enabled));
|
|
178
|
-
broadcast({ type: "catalog", catalog: buildCatalog(), restartRequired: true });
|
|
179
|
-
break;
|
|
180
|
-
case "catalog.refresh":
|
|
181
|
-
client.send({ type: "catalog", catalog: buildCatalog() });
|
|
182
|
-
break;
|
|
183
|
-
case "model.setup.refresh":
|
|
184
|
-
session.modelRegistry.refresh();
|
|
185
|
-
broadcastModelSetup();
|
|
186
|
-
break;
|
|
187
|
-
case "model.setup.save_api_key":
|
|
188
|
-
await handleSaveModelApiKey(String(data.provider ?? ""), String(data.apiKey ?? ""));
|
|
189
|
-
broadcastModelSetup();
|
|
190
|
-
break;
|
|
191
|
-
case "model.setup.select_model":
|
|
192
|
-
await handleSelectModel(String(data.provider ?? ""), String(data.modelId ?? ""));
|
|
193
|
-
broadcastModelSetup();
|
|
194
|
-
break;
|
|
195
|
-
case "provider.save_api_key":
|
|
196
|
-
await handleSaveProviderApiKey(String(data.providerId ?? ""), String(data.apiKey ?? ""));
|
|
197
|
-
broadcast({ type: "catalog", catalog: buildCatalog() });
|
|
198
|
-
break;
|
|
199
|
-
case "session.new":
|
|
200
|
-
if (lockResult.role !== "writer") throw new Error("Read-only follower mode");
|
|
201
|
-
await handleNewSession();
|
|
202
|
-
sendBoot(client);
|
|
203
|
-
broadcastState();
|
|
204
|
-
broadcastSessions();
|
|
205
|
-
break;
|
|
206
|
-
case "session.open":
|
|
207
|
-
if (lockResult.role !== "writer") throw new Error("Read-only follower mode");
|
|
208
|
-
await handleOpenSession(String(data.path ?? ""));
|
|
209
|
-
sendBoot(client);
|
|
210
|
-
broadcastState();
|
|
211
|
-
broadcastSessions();
|
|
212
|
-
break;
|
|
213
|
-
case "session.rename":
|
|
214
|
-
if (lockResult.role !== "writer") throw new Error("Read-only follower mode");
|
|
215
|
-
await handleRenameSession(String(data.path ?? ""), String(data.name ?? ""));
|
|
216
|
-
broadcastState();
|
|
217
|
-
broadcastSessions();
|
|
218
|
-
break;
|
|
219
|
-
case "session.delete":
|
|
220
|
-
if (lockResult.role !== "writer") throw new Error("Read-only follower mode");
|
|
221
|
-
await handleDeleteSession(client, String(data.path ?? ""));
|
|
222
|
-
break;
|
|
223
|
-
}
|
|
224
|
-
} catch (error) {
|
|
225
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
226
|
-
client.send({
|
|
227
|
-
type: "error",
|
|
228
|
-
message,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function handlePrompt(prompt: string): Promise<void> {
|
|
234
|
-
if (lockResult.role !== "writer") throw new Error("Read-only follower mode");
|
|
235
|
-
|
|
236
|
-
const modelSetup = buildCurrentModelSetupState();
|
|
237
|
-
const trimmedPrompt = prompt.trim();
|
|
238
|
-
if (!trimmedPrompt.startsWith("/") && modelSetup.requirement !== "ready") {
|
|
239
|
-
sessionManager.appendMessage({ role: "user", content: prompt, timestamp: Date.now() });
|
|
240
|
-
broadcastState();
|
|
241
|
-
const message =
|
|
242
|
-
modelSetup.requirement === "select_model"
|
|
243
|
-
? "Choose an available model before chat can run. OpenCandle found configured credentials but no active model."
|
|
244
|
-
: "Connect an AI model before chat can run. Paste a Google Gemini, OpenAI, or Anthropic API key in the setup panel.";
|
|
245
|
-
sessionManager.appendCustomMessageEntry(
|
|
246
|
-
"opencandle-model-setup",
|
|
247
|
-
message,
|
|
248
|
-
true,
|
|
249
|
-
{ source: "gui", requirement: modelSetup.requirement },
|
|
250
|
-
);
|
|
251
|
-
broadcastState();
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
await session.prompt(prompt);
|
|
256
|
-
broadcastState();
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async function handleNewSession(): Promise<void> {
|
|
260
|
-
const result = await runtime.newSession();
|
|
261
|
-
if (result.cancelled) throw new Error("Session switch cancelled");
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async function handleOpenSession(path: string): Promise<void> {
|
|
265
|
-
const sessions = await SessionManager.list(cwd, sessionDir);
|
|
266
|
-
const match = sessions.find((candidate) => candidate.path === path);
|
|
267
|
-
if (!match) throw new Error("Unknown saved session");
|
|
268
|
-
const result = await runtime.switchSession(match.path);
|
|
269
|
-
if (result.cancelled) throw new Error("Session switch cancelled");
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async function handleRenameSession(path: string, name: string): Promise<void> {
|
|
273
|
-
const nextName = name.trim();
|
|
274
|
-
if (!nextName) throw new Error("Session name cannot be empty");
|
|
275
|
-
if (sessionManager.getSessionFile() === path) {
|
|
276
|
-
sessionManager.appendSessionInfo(nextName);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
await renameSessionFile(cwd, sessionDir, path, nextName);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
async function handleDeleteSession(client: WsClient, path: string): Promise<void> {
|
|
283
|
-
const deletingCurrent = sessionManager.getSessionFile() === path;
|
|
284
|
-
await deleteSessionFile(cwd, sessionDir, path);
|
|
285
|
-
if (deletingCurrent) {
|
|
286
|
-
await handleNewSession();
|
|
287
|
-
sendBoot(client);
|
|
288
|
-
broadcastState();
|
|
289
|
-
}
|
|
290
|
-
broadcastSessions();
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async function handleSaveModelApiKey(providerId: string, apiKey: string): Promise<void> {
|
|
294
|
-
if (lockResult.role !== "writer") throw new Error("Read-only follower mode");
|
|
295
|
-
|
|
296
|
-
const provider = modelSetupProviders.find((candidate) => candidate.id === providerId);
|
|
297
|
-
if (!provider) throw new Error(`Unknown model provider: ${providerId}`);
|
|
298
|
-
|
|
299
|
-
const trimmed = apiKey.trim();
|
|
300
|
-
if (!trimmed) throw new Error(`Paste a ${provider.label} API key first.`);
|
|
301
|
-
|
|
302
|
-
session.modelRegistry.authStorage.set(provider.id, { type: "api_key", key: trimmed });
|
|
303
|
-
session.modelRegistry.refresh();
|
|
304
|
-
|
|
305
|
-
const model = findPreferredModel(session.modelRegistry, provider);
|
|
306
|
-
if (!model) {
|
|
307
|
-
throw new Error(`Saved the ${provider.label} key, but no ${provider.label} models are available yet.`);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
await session.setModel(model);
|
|
311
|
-
await session.settingsManager.flush();
|
|
312
|
-
sessionManager.appendCustomMessageEntry(
|
|
313
|
-
"opencandle-model-setup",
|
|
314
|
-
`Connected ${provider.label} and selected ${model.provider}/${model.id}.`,
|
|
315
|
-
true,
|
|
316
|
-
{ source: "gui", provider: provider.id, model: `${model.provider}/${model.id}` },
|
|
317
|
-
);
|
|
318
|
-
broadcastState();
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
async function handleSaveProviderApiKey(providerId: string, apiKey: string): Promise<void> {
|
|
322
|
-
if (lockResult.role !== "writer") throw new Error("Read-only follower mode");
|
|
323
|
-
|
|
324
|
-
const descriptor = PROVIDERS.find((candidate) => candidate.id === providerId);
|
|
325
|
-
if (!descriptor) throw new Error(`Unknown provider: ${providerId}`);
|
|
326
|
-
|
|
327
|
-
if (getCredentialSource(descriptor.id) === "env") {
|
|
328
|
-
throw new Error(
|
|
329
|
-
`${descriptor.displayName} is set via the ${descriptor.envVar} environment variable. Unset it to override here.`,
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const trimmed = apiKey.trim();
|
|
334
|
-
if (!trimmed) throw new Error(`Paste a ${descriptor.displayName} API key first.`);
|
|
335
|
-
|
|
336
|
-
const validation = await validateCredential(descriptor.id as ProviderId, trimmed);
|
|
337
|
-
if (validation.status === "invalid") {
|
|
338
|
-
const statusHint = validation.httpStatus !== undefined ? ` (HTTP ${validation.httpStatus})` : "";
|
|
339
|
-
const messageHint = validation.message ? ` — ${validation.message}` : "";
|
|
340
|
-
throw new Error(
|
|
341
|
-
`${descriptor.displayName} rejected the key${statusHint}${messageHint}. The existing configuration was not changed.`,
|
|
260
|
+
if (allowRemotePrivateApi) {
|
|
261
|
+
console.log(
|
|
262
|
+
"OpenCandle GUI private market-state API accepts cookie-authenticated remote requests.",
|
|
342
263
|
);
|
|
343
264
|
}
|
|
265
|
+
console.log(`Writer role: ${lockResult.role}`);
|
|
266
|
+
localAutomationHeartbeat.start();
|
|
267
|
+
});
|
|
344
268
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
broadcastState();
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
async function handleSelectModel(provider: string, modelId: string): Promise<void> {
|
|
362
|
-
if (lockResult.role !== "writer") throw new Error("Read-only follower mode");
|
|
363
|
-
session.modelRegistry.refresh();
|
|
364
|
-
const model = session.modelRegistry.find(provider, modelId);
|
|
365
|
-
if (!model) throw new Error(`Unknown model: ${provider}/${modelId}`);
|
|
366
|
-
await session.setModel(model);
|
|
367
|
-
await session.settingsManager.flush();
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
async function handleToolInvoke(toolName: string, args: Record<string, unknown>): Promise<void> {
|
|
371
|
-
if (lockResult.role !== "writer") throw new Error("Read-only follower mode");
|
|
372
|
-
const tool = getAllTools().find((candidate) => candidate.name === toolName);
|
|
373
|
-
if (!tool) throw new Error(`Unknown tool: ${toolName}`);
|
|
374
|
-
await invokeToolFromUi(sessionManager, tool, args, "ui");
|
|
375
|
-
broadcastState();
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function sendBoot(client: WsClient): void {
|
|
379
|
-
const snapshot = buildStateSnapshot();
|
|
380
|
-
client.send({
|
|
381
|
-
type: "boot",
|
|
382
|
-
role: lockResult.role,
|
|
383
|
-
lock: lockResult.lock,
|
|
384
|
-
sessionId: sessionManager.getSessionId(),
|
|
385
|
-
catalog: buildCatalog(),
|
|
386
|
-
modelSetup: buildCurrentModelSetupState(),
|
|
387
|
-
});
|
|
388
|
-
client.send({
|
|
389
|
-
type: "state.snapshot",
|
|
390
|
-
...snapshot,
|
|
391
|
-
});
|
|
392
|
-
void SessionManager.list(cwd, sessionDir).then((sessions) => client.send({ type: "sessions", sessions }));
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function broadcastModelSetup(): void {
|
|
396
|
-
broadcast({ type: "model.setup", modelSetup: buildCurrentModelSetupState() });
|
|
397
|
-
}
|
|
269
|
+
const shutdown = createGracefulShutdown({
|
|
270
|
+
server,
|
|
271
|
+
cleanup: async () => {
|
|
272
|
+
clearInterval(heartbeat);
|
|
273
|
+
quotePoller.stop();
|
|
274
|
+
localAutomationHeartbeat.stop();
|
|
275
|
+
wsHub.closeClients();
|
|
276
|
+
unsubscribeSession();
|
|
277
|
+
releaseWriterLock(sessionDir);
|
|
278
|
+
await runtime.dispose();
|
|
279
|
+
},
|
|
280
|
+
exit: (code) => process.exit(code),
|
|
281
|
+
});
|
|
398
282
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
type: "state.snapshot",
|
|
402
|
-
...buildStateSnapshot(),
|
|
403
|
-
});
|
|
404
|
-
}
|
|
283
|
+
process.once("SIGINT", shutdown);
|
|
284
|
+
process.once("SIGTERM", shutdown);
|
|
405
285
|
|
|
406
286
|
async function handleSseChatRun(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
407
287
|
if (lockResult.role !== "writer") {
|
|
@@ -416,6 +296,15 @@ async function handleSseChatRun(req: IncomingMessage, res: ServerResponse): Prom
|
|
|
416
296
|
return;
|
|
417
297
|
}
|
|
418
298
|
|
|
299
|
+
const sessionConflict = chatRunSessionConflict(
|
|
300
|
+
asRecord(body).sessionId,
|
|
301
|
+
sessionManager.getSessionId(),
|
|
302
|
+
);
|
|
303
|
+
if (sessionConflict) {
|
|
304
|
+
writeJson(res, sessionConflict, 409);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
419
308
|
res.writeHead(200, {
|
|
420
309
|
"content-type": "text/event-stream; charset=utf-8",
|
|
421
310
|
"cache-control": "no-cache, no-transform",
|
|
@@ -424,9 +313,17 @@ async function handleSseChatRun(req: IncomingMessage, res: ServerResponse): Prom
|
|
|
424
313
|
|
|
425
314
|
let seq = 1;
|
|
426
315
|
const runId = `gui-run-${Date.now()}`;
|
|
427
|
-
const
|
|
428
|
-
const
|
|
316
|
+
const runSession = session;
|
|
317
|
+
const runSessionManager = sessionManager;
|
|
318
|
+
const sessionId = runSessionManager.getSessionId();
|
|
319
|
+
// Name new sessions by the user's words before any workflow transform
|
|
320
|
+
// replaces the turn text, so the sidebar shows what the user actually asked.
|
|
321
|
+
if (!prompt.startsWith("/") && !runSessionManager.getSessionName()) {
|
|
322
|
+
runSessionManager.appendSessionInfo(prompt.length > 80 ? `${prompt.slice(0, 77)}...` : prompt);
|
|
323
|
+
}
|
|
324
|
+
const beforeEntries = runSessionManager.getEntries();
|
|
429
325
|
const beforeCount = beforeEntries.length;
|
|
326
|
+
const beforeIds = new Set(beforeEntries.map((entry) => entry.id));
|
|
430
327
|
writeSse(res, { type: "run.started", runId, sessionId, seq: seq++ });
|
|
431
328
|
res.flushHeaders?.();
|
|
432
329
|
const liveStartSeq = seq;
|
|
@@ -435,14 +332,41 @@ async function handleSseChatRun(req: IncomingMessage, res: ServerResponse): Prom
|
|
|
435
332
|
sessionId,
|
|
436
333
|
startSeq: seq,
|
|
437
334
|
emit: (event) => writeSse(res, event),
|
|
335
|
+
originalPrompt: prompt,
|
|
336
|
+
});
|
|
337
|
+
const observation = createPromptObservation();
|
|
338
|
+
const unsubscribeLive = runSession.subscribe((event) => {
|
|
339
|
+
liveAdapter.handle(event);
|
|
340
|
+
observePromptEvent(observation, event);
|
|
438
341
|
});
|
|
439
|
-
const unsubscribeLive = session.subscribe((event) => liveAdapter.handle(event));
|
|
440
342
|
|
|
441
343
|
try {
|
|
442
|
-
|
|
344
|
+
const modelSetup = buildModelSetupState(runSession.modelRegistry, runSession.model);
|
|
345
|
+
if (!prompt.startsWith("/") && modelSetup.requirement !== "ready") {
|
|
346
|
+
runSessionManager.appendMessage({ role: "user", content: prompt, timestamp: Date.now() });
|
|
347
|
+
const message =
|
|
348
|
+
modelSetup.requirement === "select_model"
|
|
349
|
+
? "Choose an available model before chat can run. OpenCandle found configured credentials but no active model."
|
|
350
|
+
: "Connect an AI model before chat can run. Paste a Google Gemini, OpenAI, or Anthropic API key in the setup panel.";
|
|
351
|
+
runSessionManager.appendCustomMessageEntry("opencandle-model-setup", message, true, {
|
|
352
|
+
source: "gui",
|
|
353
|
+
requirement: modelSetup.requirement,
|
|
354
|
+
});
|
|
355
|
+
wsHub.broadcastState();
|
|
356
|
+
} else {
|
|
357
|
+
await promptAndSettle(runSession, prompt, beforeIds, observation);
|
|
358
|
+
wsHub.broadcastState();
|
|
359
|
+
}
|
|
443
360
|
seq = liveAdapter.nextSeq();
|
|
444
361
|
if (seq === liveStartSeq) {
|
|
445
|
-
|
|
362
|
+
await waitForNewEntryId(
|
|
363
|
+
() => runSessionManager.getEntries().map((entry) => entry.id),
|
|
364
|
+
beforeIds,
|
|
365
|
+
);
|
|
366
|
+
const newEntries = runSessionManager
|
|
367
|
+
.getEntries()
|
|
368
|
+
.slice(beforeCount)
|
|
369
|
+
.filter((entry) => !beforeIds.has(entry.id));
|
|
446
370
|
const events = sessionEntriesToChatEvents(newEntries, {
|
|
447
371
|
sessionId,
|
|
448
372
|
updatedAt: new Date().toISOString(),
|
|
@@ -464,88 +388,33 @@ async function handleSseChatRun(req: IncomingMessage, res: ServerResponse): Prom
|
|
|
464
388
|
}
|
|
465
389
|
}
|
|
466
390
|
|
|
467
|
-
function
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
return {
|
|
471
|
-
sessionId,
|
|
472
|
-
state: projectDashboard(backgroundQuoteRefreshes.withEntries(entries), sessionId),
|
|
473
|
-
entries,
|
|
474
|
-
events: currentChatEvents(entries),
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function currentChatEvents(entries = sessionManager.getEntries()): ChatEvent[] {
|
|
479
|
-
return sessionEntriesToChatEvents(entries, {
|
|
480
|
-
sessionId: sessionManager.getSessionId(),
|
|
481
|
-
title: sessionManager.getSessionName(),
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function broadcastSessions(): void {
|
|
486
|
-
void SessionManager.list(cwd, sessionDir).then((sessions) => broadcast({ type: "sessions", sessions }));
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function buildCurrentModelSetupState() {
|
|
490
|
-
return buildModelSetupState(session.modelRegistry, session.model);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function broadcast(message: unknown): void {
|
|
494
|
-
for (const client of clients) client.send(message);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function subscribeToSessionEvents(): () => void {
|
|
498
|
-
return session.subscribe((event) => {
|
|
499
|
-
broadcast({ type: "session.event", event });
|
|
500
|
-
broadcastState();
|
|
501
|
-
});
|
|
391
|
+
function writeJson(res: ServerResponse, value: unknown, status = 200): void {
|
|
392
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
393
|
+
res.end(JSON.stringify(value));
|
|
502
394
|
}
|
|
503
395
|
|
|
504
|
-
function
|
|
505
|
-
if (
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
396
|
+
function allowTrustedGuiRequest(req: IncomingMessage, res: ServerResponse, label: string): boolean {
|
|
397
|
+
if (
|
|
398
|
+
isTrustedPrivateApiRequest(req.headers, privateApiSessionToken, req.socket.remoteAddress, {
|
|
399
|
+
allowRemote: allowRemotePrivateApi,
|
|
400
|
+
})
|
|
401
|
+
)
|
|
402
|
+
return true;
|
|
403
|
+
res.writeHead(403, { "content-type": "application/json" });
|
|
404
|
+
res.end(
|
|
405
|
+
JSON.stringify({
|
|
406
|
+
error: `${label} is only available to trusted GUI browser sessions.`,
|
|
407
|
+
}),
|
|
408
|
+
);
|
|
409
|
+
return false;
|
|
512
410
|
}
|
|
513
411
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const state = projectDashboard(sessionManager.getEntries(), sessionManager.getSessionId());
|
|
519
|
-
const tool = getAllTools().find((candidate) => candidate.name === "get_stock_quote");
|
|
520
|
-
if (!tool) return;
|
|
521
|
-
for (const row of state.watchlist.filter((item) => item.pinned || item.quote)) {
|
|
522
|
-
const result = await invokeToolFromUi(
|
|
523
|
-
sessionManager,
|
|
524
|
-
tool,
|
|
525
|
-
{ symbol: row.symbol },
|
|
526
|
-
"background",
|
|
527
|
-
{ recordTranscript: false },
|
|
528
|
-
);
|
|
529
|
-
backgroundQuoteRefreshes.upsert({
|
|
530
|
-
symbol: row.symbol,
|
|
531
|
-
toolName: tool.name,
|
|
532
|
-
args: { symbol: row.symbol },
|
|
533
|
-
value: result.result.details,
|
|
534
|
-
content: result.result.content,
|
|
535
|
-
isError: result.isError,
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
broadcastState();
|
|
539
|
-
} catch (error) {
|
|
540
|
-
console.warn(`Background quote refresh failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
541
|
-
} finally {
|
|
542
|
-
quotePollInFlight = false;
|
|
412
|
+
function privateGuiHeaders(contentTypeValue: string): Record<string, string> {
|
|
413
|
+
const headers: Record<string, string> = { "content-type": contentTypeValue };
|
|
414
|
+
if (contentTypeValue.startsWith("text/html")) {
|
|
415
|
+
headers["set-cookie"] = privateApiCookieHeader(privateApiSessionToken);
|
|
543
416
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
function writeJson(res: ServerResponse, value: unknown): void {
|
|
547
|
-
res.writeHead(200, { "content-type": "application/json" });
|
|
548
|
-
res.end(JSON.stringify(value));
|
|
417
|
+
return headers;
|
|
549
418
|
}
|
|
550
419
|
|
|
551
420
|
function writeSse(res: ServerResponse, event: ChatEvent): void {
|
|
@@ -578,15 +447,6 @@ function contentType(path: string): string {
|
|
|
578
447
|
|
|
579
448
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
580
449
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
581
|
-
? value as Record<string, unknown>
|
|
450
|
+
? (value as Record<string, unknown>)
|
|
582
451
|
: {};
|
|
583
452
|
}
|
|
584
|
-
|
|
585
|
-
function shutdown(): void {
|
|
586
|
-
clearInterval(heartbeat);
|
|
587
|
-
if (poller) clearInterval(poller);
|
|
588
|
-
releaseWriterLock(sessionDir);
|
|
589
|
-
void runtime.dispose().finally(() => {
|
|
590
|
-
server.close(() => process.exit(0));
|
|
591
|
-
});
|
|
592
|
-
}
|