opencandle 0.3.0 → 0.5.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 +106 -14
- package/assets/logo.svg +187 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +40 -3
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +25 -0
- package/dist/config.js +72 -0
- package/dist/config.js.map +1 -1
- package/dist/infra/browser.d.ts +11 -3
- package/dist/infra/browser.js +2 -1
- package/dist/infra/browser.js.map +1 -1
- package/dist/infra/native-dependencies.d.ts +1 -0
- package/dist/infra/native-dependencies.js +10 -0
- package/dist/infra/native-dependencies.js.map +1 -0
- package/dist/infra/node-version.d.ts +2 -0
- package/dist/infra/node-version.js +23 -0
- package/dist/infra/node-version.js.map +1 -0
- package/dist/infra/rate-limiter.d.ts +4 -0
- package/dist/infra/rate-limiter.js +5 -1
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/memory/index.d.ts +2 -0
- package/dist/memory/index.js +1 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/manager.d.ts +9 -0
- package/dist/memory/manager.js +28 -11
- package/dist/memory/manager.js.map +1 -1
- package/dist/memory/sqlite.js +42 -4
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/memory/storage.d.ts +7 -0
- package/dist/memory/storage.js +3 -3
- package/dist/memory/storage.js.map +1 -1
- package/dist/memory/tool-defaults.d.ts +8 -0
- package/dist/memory/tool-defaults.js +59 -0
- package/dist/memory/tool-defaults.js.map +1 -0
- package/dist/memory/types.js +4 -0
- package/dist/memory/types.js.map +1 -1
- package/dist/onboarding/connect.d.ts +13 -1
- package/dist/onboarding/connect.js +21 -10
- package/dist/onboarding/connect.js.map +1 -1
- package/dist/onboarding/prompt-user.d.ts +1 -1
- package/dist/onboarding/providers.d.ts +7 -0
- package/dist/onboarding/providers.js +6 -3
- package/dist/onboarding/providers.js.map +1 -1
- package/dist/onboarding/tool-helpers.d.ts +1 -1
- package/dist/pi/opencandle-extension.d.ts +7 -1
- package/dist/pi/opencandle-extension.js +391 -21
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session-storage.d.ts +2 -0
- package/dist/pi/session-storage.js +5 -0
- package/dist/pi/session-storage.js.map +1 -0
- package/dist/pi/session.d.ts +4 -1
- package/dist/pi/session.js +25 -3
- package/dist/pi/session.js.map +1 -1
- package/dist/pi/setup.d.ts +1 -1
- package/dist/pi/setup.js +11 -1
- package/dist/pi/setup.js.map +1 -1
- package/dist/pi/tool-adapter.d.ts +2 -2
- package/dist/pi/tool-adapter.js +14 -1
- package/dist/pi/tool-adapter.js.map +1 -1
- package/dist/prompts/context-builder.d.ts +40 -3
- package/dist/prompts/context-builder.js +140 -19
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/prompts/disclaimer.d.ts +6 -0
- package/dist/prompts/disclaimer.js +9 -0
- package/dist/prompts/disclaimer.js.map +1 -0
- 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.js +3 -3
- package/dist/prompts/sections.js.map +1 -1
- package/dist/prompts/workflow-prompts.d.ts +8 -0
- package/dist/prompts/workflow-prompts.js +208 -22
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/alpha-vantage.js +23 -1
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/sec-edgar.d.ts +8 -1
- package/dist/providers/sec-edgar.js +172 -5
- package/dist/providers/sec-edgar.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +2 -0
- package/dist/providers/yahoo-finance.js +203 -35
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.d.ts +3 -0
- package/dist/routing/classify-intent.js +82 -3
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/defaults.js +4 -4
- package/dist/routing/defaults.js.map +1 -1
- package/dist/routing/entity-extractor.d.ts +1 -0
- package/dist/routing/entity-extractor.js +158 -12
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/index.d.ts +10 -0
- package/dist/routing/index.js +7 -0
- 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 +531 -0
- package/dist/routing/planning.js.map +1 -0
- package/dist/routing/route-manifest.d.ts +35 -0
- package/dist/routing/route-manifest.js +221 -0
- package/dist/routing/route-manifest.js.map +1 -0
- package/dist/routing/router-llm-client.d.ts +11 -0
- package/dist/routing/router-llm-client.js +42 -0
- package/dist/routing/router-llm-client.js.map +1 -0
- package/dist/routing/router-prompt.d.ts +2 -0
- package/dist/routing/router-prompt.js +141 -0
- package/dist/routing/router-prompt.js.map +1 -0
- package/dist/routing/router-types.d.ts +71 -0
- package/dist/routing/router-types.js +2 -0
- package/dist/routing/router-types.js.map +1 -0
- package/dist/routing/router.d.ts +11 -0
- package/dist/routing/router.js +638 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/routing/slot-resolver.js +46 -6
- package/dist/routing/slot-resolver.js.map +1 -1
- 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 +13 -1
- package/dist/runtime/answer-contracts.d.ts +82 -0
- package/dist/runtime/answer-contracts.js +414 -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 +445 -0
- package/dist/runtime/planning-evidence.js.map +1 -0
- package/dist/runtime/session-coordinator.d.ts +81 -3
- package/dist/runtime/session-coordinator.js +201 -17
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/tool-defaults-wrapper.d.ts +3 -0
- package/dist/runtime/tool-defaults-wrapper.js +25 -0
- package/dist/runtime/tool-defaults-wrapper.js.map +1 -0
- package/dist/sentiment/store.js +5 -0
- package/dist/sentiment/store.js.map +1 -1
- package/dist/system-prompt.js +23 -12
- package/dist/system-prompt.js.map +1 -1
- package/dist/tool-kit.d.ts +4 -4
- package/dist/tools/fundamentals/company-overview.d.ts +1 -1
- package/dist/tools/fundamentals/company-overview.js +1 -1
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.d.ts +1 -1
- package/dist/tools/fundamentals/comps.js +1 -1
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.d.ts +1 -1
- package/dist/tools/fundamentals/dcf.js +1 -1
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.d.ts +1 -1
- package/dist/tools/fundamentals/earnings.js +1 -1
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.d.ts +1 -1
- package/dist/tools/fundamentals/financials.js +1 -1
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.d.ts +2 -1
- package/dist/tools/fundamentals/sec-filings.js +19 -2
- package/dist/tools/fundamentals/sec-filings.js.map +1 -1
- package/dist/tools/index.d.ts +29 -1
- package/dist/tools/index.js +30 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.d.ts +1 -1
- package/dist/tools/interaction/twitter-login.d.ts +1 -1
- package/dist/tools/macro/fear-greed.d.ts +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 +29 -5
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/market/crypto-history.d.ts +1 -1
- package/dist/tools/market/crypto-history.js +18 -2
- package/dist/tools/market/crypto-history.js.map +1 -1
- package/dist/tools/market/crypto-price.d.ts +1 -1
- package/dist/tools/market/crypto-price.js +1 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/search-ticker.d.ts +1 -1
- package/dist/tools/market/search-ticker.js +1 -1
- package/dist/tools/market/search-ticker.js.map +1 -1
- package/dist/tools/market/stock-history.d.ts +1 -1
- package/dist/tools/market/stock-history.js +1 -1
- package/dist/tools/market/stock-history.js.map +1 -1
- package/dist/tools/market/stock-quote.d.ts +1 -1
- package/dist/tools/market/stock-quote.js +1 -1
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/greeks.js +0 -1
- package/dist/tools/options/greeks.js.map +1 -1
- package/dist/tools/options/option-chain.d.ts +1 -1
- package/dist/tools/options/option-chain.js +13 -5
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/correlation.d.ts +1 -1
- package/dist/tools/portfolio/correlation.js +1 -1
- package/dist/tools/portfolio/correlation.js.map +1 -1
- package/dist/tools/portfolio/holdings-overlap.d.ts +8 -0
- package/dist/tools/portfolio/holdings-overlap.js +105 -0
- package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
- package/dist/tools/portfolio/predictions.d.ts +1 -1
- package/dist/tools/portfolio/predictions.js +1 -1
- 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 +1 -1
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/portfolio/tracker.d.ts +1 -1
- package/dist/tools/portfolio/tracker.js +1 -1
- package/dist/tools/portfolio/tracker.js.map +1 -1
- package/dist/tools/portfolio/watchlist.d.ts +1 -1
- package/dist/tools/portfolio/watchlist.js +12 -4
- package/dist/tools/portfolio/watchlist.js.map +1 -1
- package/dist/tools/sentiment/reddit-sentiment.d.ts +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.d.ts +1 -1
- package/dist/tools/sentiment/sentiment-summary.js +57 -2
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
- package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
- package/dist/tools/sentiment/twitter-sentiment.d.ts +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/web-search.d.ts +1 -1
- package/dist/tools/sentiment/web-search.js +32 -3
- package/dist/tools/sentiment/web-search.js.map +1 -1
- package/dist/tools/sentiment/web-sentiment.d.ts +1 -1
- package/dist/tools/sentiment/web-sentiment.js +1 -1
- 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 +41 -27
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.d.ts +1 -1
- package/dist/tools/technical/indicators.js +8 -4
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/options.d.ts +10 -0
- package/dist/types/portfolio.d.ts +27 -0
- package/dist/workflows/compare-assets.js +38 -2
- package/dist/workflows/compare-assets.js.map +1 -1
- package/dist/workflows/options-screener.js +94 -8
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.js +9 -5
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/ask-user-bridge.ts +82 -0
- package/gui/server/background-quotes.ts +31 -0
- package/gui/server/chat-event-adapter.ts +142 -0
- package/gui/server/gui-session-manager.ts +5 -0
- package/gui/server/invoke-tool.ts +89 -0
- package/gui/server/live-chat-event-adapter.ts +181 -0
- package/gui/server/model-setup.ts +100 -0
- package/gui/server/package.json +5 -0
- package/gui/server/projector.ts +254 -0
- package/gui/server/prompt-observation.ts +61 -0
- package/gui/server/server.ts +703 -0
- package/gui/server/session-actions.ts +31 -0
- package/gui/server/session-entry-wait.ts +81 -0
- package/gui/server/tool-metadata.ts +88 -0
- package/gui/server/websocket.ts +128 -0
- package/gui/server/writer-lock.ts +118 -0
- package/gui/shared/chat-events.ts +118 -0
- package/gui/shared/event-reducer.ts +186 -0
- package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +1 -0
- package/gui/web/dist/assets/index-Bxt9QpLX.css +1 -0
- package/gui/web/dist/assets/index-CZ9DHZYy.js +67 -0
- package/gui/web/dist/assets/logo-CWpt6Y2a.svg +187 -0
- package/gui/web/dist/index.html +17 -0
- package/package.json +50 -18
- package/src/analysts/contracts.ts +189 -0
- package/src/analysts/orchestrator.ts +300 -0
- package/src/cli.ts +206 -0
- package/src/config.ts +245 -0
- package/src/index.ts +5 -0
- package/src/infra/browser.ts +111 -0
- package/src/infra/cache.ts +103 -0
- package/src/infra/http-client.ts +68 -0
- package/src/infra/index.ts +18 -0
- package/src/infra/native-dependencies.ts +12 -0
- package/src/infra/node-version.ts +24 -0
- package/src/infra/open-url.ts +28 -0
- package/src/infra/opencandle-paths.ts +64 -0
- package/src/infra/rate-limiter.ts +73 -0
- package/src/memory/index.ts +10 -0
- package/src/memory/manager.ts +192 -0
- package/src/memory/preference-extractor.ts +106 -0
- package/src/memory/retrieval.ts +70 -0
- package/src/memory/sqlite.ts +172 -0
- package/src/memory/storage.ts +205 -0
- package/src/memory/tool-defaults.ts +87 -0
- package/src/memory/types.ts +71 -0
- package/src/onboarding/connect.ts +184 -0
- package/src/onboarding/credential-interceptor.ts +134 -0
- package/src/onboarding/degradation-accumulator.ts +79 -0
- package/src/onboarding/prompt-user.ts +85 -0
- package/src/onboarding/providers.ts +315 -0
- package/src/onboarding/state.ts +218 -0
- package/src/onboarding/tool-helpers.ts +111 -0
- package/src/onboarding/tool-tags.ts +201 -0
- package/src/onboarding/validation.ts +158 -0
- package/src/pi/opencandle-extension.ts +955 -0
- package/src/pi/session-storage.ts +5 -0
- package/src/pi/session.ts +81 -0
- package/src/pi/setup.ts +381 -0
- package/src/pi/tool-adapter.ts +36 -0
- package/src/prompts/context-builder.ts +315 -0
- package/src/prompts/disclaimer.ts +9 -0
- package/src/prompts/policy-cards.ts +220 -0
- package/src/prompts/sections.ts +46 -0
- package/src/prompts/workflow-prompts.ts +433 -0
- package/src/providers/alpha-vantage.ts +315 -0
- package/src/providers/coingecko.ts +96 -0
- package/src/providers/exa-search.ts +373 -0
- package/src/providers/fear-greed.ts +45 -0
- package/src/providers/finnhub.ts +124 -0
- package/src/providers/fred.ts +83 -0
- package/src/providers/index.ts +9 -0
- package/src/providers/provider-credential-error.ts +23 -0
- package/src/providers/reddit.ts +151 -0
- package/src/providers/sec-edgar.ts +312 -0
- package/src/providers/twitter.ts +173 -0
- package/src/providers/web-search.ts +293 -0
- package/src/providers/with-fallback.ts +41 -0
- package/src/providers/wrap-provider.ts +64 -0
- package/src/providers/yahoo-finance.ts +534 -0
- package/src/routing/classify-intent.ts +285 -0
- package/src/routing/defaults.ts +29 -0
- package/src/routing/entity-extractor.ts +291 -0
- package/src/routing/index.ts +70 -0
- package/src/routing/legacy-rule-router.ts +13 -0
- package/src/routing/planning.ts +732 -0
- package/src/routing/route-manifest.ts +287 -0
- package/src/routing/router-llm-client.ts +51 -0
- package/src/routing/router-prompt.ts +163 -0
- package/src/routing/router-types.ts +87 -0
- package/src/routing/router.ts +712 -0
- package/src/routing/slot-resolver.ts +190 -0
- package/src/routing/turn-context.ts +111 -0
- package/src/routing/types.ts +75 -0
- package/src/runtime/answer-contracts.ts +633 -0
- package/src/runtime/artifact-contracts.ts +76 -0
- package/src/runtime/evidence.ts +77 -0
- package/src/runtime/planning-evidence.ts +591 -0
- package/src/runtime/prompt-step.ts +75 -0
- package/src/runtime/provider-tracker.ts +40 -0
- package/src/runtime/run-context.ts +22 -0
- package/src/runtime/session-coordinator.ts +472 -0
- package/src/runtime/tool-defaults-wrapper.ts +35 -0
- package/src/runtime/validation.ts +214 -0
- package/src/runtime/workflow-events.ts +75 -0
- package/src/runtime/workflow-runner.ts +188 -0
- package/src/runtime/workflow-types.ts +102 -0
- package/src/sentiment/adapters/finnhub.ts +44 -0
- package/src/sentiment/adapters/reddit.ts +65 -0
- package/src/sentiment/adapters/twitter.ts +36 -0
- package/src/sentiment/adapters/web.ts +44 -0
- package/src/sentiment/index.ts +58 -0
- package/src/sentiment/keywords.ts +9 -0
- package/src/sentiment/pipeline.ts +68 -0
- package/src/sentiment/scorer.ts +78 -0
- package/src/sentiment/store.ts +260 -0
- package/src/sentiment/trends.ts +90 -0
- package/src/sentiment/types.ts +108 -0
- package/src/system-prompt.ts +118 -0
- package/src/tool-kit.ts +68 -0
- package/src/tools/AGENTS.md +36 -0
- package/src/tools/fundamentals/company-overview.ts +54 -0
- package/src/tools/fundamentals/comps.ts +156 -0
- package/src/tools/fundamentals/dcf.ts +267 -0
- package/src/tools/fundamentals/earnings.ts +47 -0
- package/src/tools/fundamentals/financials.ts +54 -0
- package/src/tools/fundamentals/sec-filings.ts +84 -0
- package/src/tools/index.ts +91 -0
- package/src/tools/interaction/ask-user.ts +81 -0
- package/src/tools/interaction/twitter-login.ts +93 -0
- package/src/tools/macro/fear-greed.ts +41 -0
- package/src/tools/macro/fred-data.ts +80 -0
- package/src/tools/market/crypto-history.ts +67 -0
- package/src/tools/market/crypto-price.ts +53 -0
- package/src/tools/market/search-ticker.ts +53 -0
- package/src/tools/market/stock-history.ts +79 -0
- package/src/tools/market/stock-quote.ts +64 -0
- package/src/tools/options/greeks.ts +81 -0
- package/src/tools/options/option-chain.ts +96 -0
- package/src/tools/portfolio/correlation.ts +162 -0
- package/src/tools/portfolio/holdings-overlap.ts +123 -0
- package/src/tools/portfolio/predictions.ts +253 -0
- package/src/tools/portfolio/risk-analysis.ts +134 -0
- package/src/tools/portfolio/tracker.ts +147 -0
- package/src/tools/portfolio/watchlist.ts +159 -0
- package/src/tools/sentiment/reddit-sentiment.ts +164 -0
- package/src/tools/sentiment/sentiment-summary.ts +316 -0
- package/src/tools/sentiment/sentiment-trend.ts +58 -0
- package/src/tools/sentiment/twitter-sentiment.ts +96 -0
- package/src/tools/sentiment/web-search.ts +183 -0
- package/src/tools/sentiment/web-sentiment.ts +76 -0
- package/src/tools/technical/backtest.ts +267 -0
- package/src/tools/technical/indicators.ts +256 -0
- package/src/types/fundamentals.ts +46 -0
- package/src/types/index.ts +20 -0
- package/src/types/macro.ts +27 -0
- package/src/types/market.ts +43 -0
- package/src/types/options.ts +52 -0
- package/src/types/portfolio.ts +73 -0
- package/src/types/sentiment.ts +70 -0
- package/src/workflows/compare-assets.ts +75 -0
- package/src/workflows/index.ts +4 -0
- package/src/workflows/options-screener.ts +127 -0
- package/src/workflows/portfolio-builder.ts +56 -0
- package/src/workflows/types.ts +4 -0
- 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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
interface BucketConfig {
|
|
2
|
+
maxTokens: number;
|
|
3
|
+
refillRate: number; // tokens per second
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface Bucket {
|
|
7
|
+
tokens: number;
|
|
8
|
+
lastRefill: number;
|
|
9
|
+
config: BucketConfig;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ALPHA_VANTAGE_RATE_LIMIT = {
|
|
13
|
+
maxTokens: 5,
|
|
14
|
+
refillRate: 0.083,
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export class RateLimiter {
|
|
18
|
+
private buckets = new Map<string, Bucket>();
|
|
19
|
+
|
|
20
|
+
configure(provider: string, maxTokens: number, refillRate: number): void {
|
|
21
|
+
this.buckets.set(provider, {
|
|
22
|
+
tokens: maxTokens,
|
|
23
|
+
lastRefill: Date.now(),
|
|
24
|
+
config: { maxTokens, refillRate },
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async acquire(provider: string): Promise<void> {
|
|
29
|
+
const bucket = this.buckets.get(provider);
|
|
30
|
+
if (!bucket) return; // No limit configured
|
|
31
|
+
|
|
32
|
+
this.refill(bucket);
|
|
33
|
+
|
|
34
|
+
if (bucket.tokens >= 1) {
|
|
35
|
+
bucket.tokens -= 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Wait until a token is available
|
|
40
|
+
const waitMs = ((1 - bucket.tokens) / bucket.config.refillRate) * 1000;
|
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, Math.ceil(waitMs)));
|
|
42
|
+
this.refill(bucket);
|
|
43
|
+
bucket.tokens -= 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private refill(bucket: Bucket): void {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const elapsed = (now - bucket.lastRefill) / 1000;
|
|
49
|
+
bucket.tokens = Math.min(
|
|
50
|
+
bucket.config.maxTokens,
|
|
51
|
+
bucket.tokens + elapsed * bucket.config.refillRate,
|
|
52
|
+
);
|
|
53
|
+
bucket.lastRefill = now;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Shared instance with default provider limits
|
|
58
|
+
export const rateLimiter = new RateLimiter();
|
|
59
|
+
rateLimiter.configure("yahoo", 5, 5); // 5 req/s
|
|
60
|
+
rateLimiter.configure("coingecko", 10, 0.167); // 10 req/min
|
|
61
|
+
rateLimiter.configure(
|
|
62
|
+
"alphavantage",
|
|
63
|
+
ALPHA_VANTAGE_RATE_LIMIT.maxTokens,
|
|
64
|
+
ALPHA_VANTAGE_RATE_LIMIT.refillRate,
|
|
65
|
+
); // 5 req/min (free tier)
|
|
66
|
+
rateLimiter.configure("fred", 120, 2); // 120 req/min
|
|
67
|
+
rateLimiter.configure("twitter", 5, 0.167); // 5 req, ~10 req/min
|
|
68
|
+
rateLimiter.configure("reddit", 5, 0.167); // 5 req, ~10 req/min
|
|
69
|
+
rateLimiter.configure("reddit_comments", 10, 0.5); // 10 req, ~30 req/min
|
|
70
|
+
rateLimiter.configure("ddg", 3, 0.1); // 3 req, ~6 req/min
|
|
71
|
+
rateLimiter.configure("brave_search", 5, 0.083); // 5 req, ~5 req/min
|
|
72
|
+
rateLimiter.configure("exa", 5, 0.1); // 5 req, ~6 req/min
|
|
73
|
+
rateLimiter.configure("finnhub", 60, 1); // 60 req/min (free tier)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { initDatabase, initDefaultDatabase, getTableNames, getSchemaVersion } from "./sqlite.js";
|
|
2
|
+
export { MemoryStorage } from "./storage.js";
|
|
3
|
+
export type { WorkflowPreferences } from "./storage.js";
|
|
4
|
+
export { buildMemoryContext } from "./retrieval.js";
|
|
5
|
+
export { extractPreferences } from "./preference-extractor.js";
|
|
6
|
+
export { MemoryManager } from "./manager.js";
|
|
7
|
+
export { getAllDefaults, getDefaults, setDefault, clearDefault } from "./tool-defaults.js";
|
|
8
|
+
export type { ToolDefaults } from "./tool-defaults.js";
|
|
9
|
+
export type { MemoryCategory, MemoryEntry } from "./types.js";
|
|
10
|
+
export { isStale, STALENESS_THRESHOLDS, NEVER_TRUST_FROM_MEMORY } from "./types.js";
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { MemoryStorage } from "./storage.js";
|
|
2
|
+
import type { MemoryCategory, MemoryEntry } from "./types.js";
|
|
3
|
+
import {
|
|
4
|
+
KEY_TO_CATEGORY,
|
|
5
|
+
WORKFLOW_RELEVANT_CATEGORIES,
|
|
6
|
+
NEVER_TRUST_FROM_MEMORY,
|
|
7
|
+
isStale,
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
|
|
10
|
+
export interface FilteredMemoryEntry {
|
|
11
|
+
entry: MemoryEntry;
|
|
12
|
+
reason: "suppressed_by_user_slot" | "never_trust" | "stale" | "irrelevant_category";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MemoryRetrievalResult {
|
|
16
|
+
entries: MemoryEntry[];
|
|
17
|
+
filtered: FilteredMemoryEntry[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Slot name → preference key(s) mapping for suppression. */
|
|
21
|
+
const SLOT_TO_PREF_KEYS: Record<string, string[]> = {
|
|
22
|
+
riskProfile: ["risk_profile"],
|
|
23
|
+
assetScope: ["asset_scope"],
|
|
24
|
+
timeHorizon: ["time_horizon"],
|
|
25
|
+
dteTarget: ["dte_target"],
|
|
26
|
+
moneynessPreference: ["moneyness_preference"],
|
|
27
|
+
liquidityMinimum: ["liquidity_minimum", "options_liquidity"],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const MAX_WORKFLOW_HISTORY_PER_TYPE = 3;
|
|
31
|
+
const MAX_PREFERENCE_LINES = 15;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Selective, typed memory retrieval with staleness rules
|
|
35
|
+
* and override suppression.
|
|
36
|
+
*/
|
|
37
|
+
export class MemoryManager {
|
|
38
|
+
constructor(private readonly storage: MemoryStorage) {}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Retrieve memory entries relevant to the given workflow type,
|
|
42
|
+
* filtering by category, staleness, and overrides.
|
|
43
|
+
*/
|
|
44
|
+
retrieve(
|
|
45
|
+
workflowType: string,
|
|
46
|
+
overriddenSlots?: string[],
|
|
47
|
+
now: Date = new Date(),
|
|
48
|
+
): MemoryEntry[] {
|
|
49
|
+
return this.retrieveDetailed(workflowType, overriddenSlots, now).entries;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
retrieveDetailed(
|
|
53
|
+
workflowType: string,
|
|
54
|
+
overriddenSlots?: string[],
|
|
55
|
+
now: Date = new Date(),
|
|
56
|
+
): MemoryRetrievalResult {
|
|
57
|
+
const relevantCategories = WORKFLOW_RELEVANT_CATEGORIES[workflowType] ??
|
|
58
|
+
WORKFLOW_RELEVANT_CATEGORIES["unclassified"];
|
|
59
|
+
|
|
60
|
+
// Build set of preference keys to suppress
|
|
61
|
+
const suppressedKeys = new Set<string>();
|
|
62
|
+
if (overriddenSlots) {
|
|
63
|
+
for (const slot of overriddenSlots) {
|
|
64
|
+
const keys = SLOT_TO_PREF_KEYS[slot];
|
|
65
|
+
if (keys) keys.forEach((k) => suppressedKeys.add(k));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const entries: MemoryEntry[] = [];
|
|
70
|
+
const filtered: FilteredMemoryEntry[] = [];
|
|
71
|
+
|
|
72
|
+
// Preferences as investor_profile entries
|
|
73
|
+
if (relevantCategories.includes("investor_profile")) {
|
|
74
|
+
const prefs = this.storage.getPreferencesByNamespace("global");
|
|
75
|
+
for (const pref of prefs) {
|
|
76
|
+
const key = String(pref.key);
|
|
77
|
+
const category = KEY_TO_CATEGORY[key] ?? "investor_profile";
|
|
78
|
+
const entry: MemoryEntry = {
|
|
79
|
+
key,
|
|
80
|
+
value: tryParseValue(String(pref.value_json ?? "")),
|
|
81
|
+
category,
|
|
82
|
+
recordedAt: String(pref.updated_at ?? pref.created_at ?? now.toISOString()),
|
|
83
|
+
confidence: pref.confidence != null ? String(pref.confidence) : undefined,
|
|
84
|
+
source: pref.source != null ? String(pref.source) : undefined,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (suppressedKeys.has(key)) {
|
|
88
|
+
filtered.push({ entry, reason: "suppressed_by_user_slot" });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (NEVER_TRUST_FROM_MEMORY.has(key)) {
|
|
92
|
+
filtered.push({ entry, reason: "never_trust" });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!relevantCategories.includes(category)) {
|
|
96
|
+
filtered.push({ entry, reason: "irrelevant_category" });
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (isStale(entry, now)) {
|
|
100
|
+
filtered.push({ entry, reason: "stale" });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
entries.push(entry);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Workflow history
|
|
108
|
+
if (relevantCategories.includes("workflow_history")) {
|
|
109
|
+
const runs = this.storage.getRecentWorkflowRuns(MAX_WORKFLOW_HISTORY_PER_TYPE * 4);
|
|
110
|
+
const countsByType = new Map<string, number>();
|
|
111
|
+
|
|
112
|
+
for (const run of runs) {
|
|
113
|
+
const wfType = String(run.workflow_type);
|
|
114
|
+
const count = countsByType.get(wfType) ?? 0;
|
|
115
|
+
if (count >= MAX_WORKFLOW_HISTORY_PER_TYPE) continue;
|
|
116
|
+
countsByType.set(wfType, count + 1);
|
|
117
|
+
|
|
118
|
+
const recordedAt = String(run.created_at ?? now.toISOString());
|
|
119
|
+
const entry: MemoryEntry = {
|
|
120
|
+
key: `workflow_run_${run.id}`,
|
|
121
|
+
value: run.output_summary
|
|
122
|
+
? `${wfType}: ${run.output_summary}`
|
|
123
|
+
: wfType,
|
|
124
|
+
category: "workflow_history",
|
|
125
|
+
recordedAt,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (isStale(entry, now)) {
|
|
129
|
+
filtered.push({ entry, reason: "stale" });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
entries.push(entry);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
entries: entries.slice(0, MAX_PREFERENCE_LINES + MAX_WORKFLOW_HISTORY_PER_TYPE * 4),
|
|
138
|
+
filtered,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build compact text context from retrieved memory entries.
|
|
144
|
+
*/
|
|
145
|
+
buildContext(
|
|
146
|
+
workflowType: string,
|
|
147
|
+
overriddenSlots?: string[],
|
|
148
|
+
now: Date = new Date(),
|
|
149
|
+
): string {
|
|
150
|
+
const entries = this.retrieve(workflowType, overriddenSlots, now);
|
|
151
|
+
if (entries.length === 0) return "";
|
|
152
|
+
|
|
153
|
+
const sections: string[] = [];
|
|
154
|
+
|
|
155
|
+
// Group by category
|
|
156
|
+
const byCategory = new Map<MemoryCategory, MemoryEntry[]>();
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
const list = byCategory.get(entry.category) ?? [];
|
|
159
|
+
list.push(entry);
|
|
160
|
+
byCategory.set(entry.category, list);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const profileEntries = byCategory.get("investor_profile");
|
|
164
|
+
if (profileEntries && profileEntries.length > 0) {
|
|
165
|
+
const lines = profileEntries.map((e) => `- ${e.key}: ${e.value}`);
|
|
166
|
+
sections.push("User Preferences:\n" + lines.join("\n"));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const historyEntries = byCategory.get("workflow_history");
|
|
170
|
+
if (historyEntries && historyEntries.length > 0) {
|
|
171
|
+
const lines = historyEntries.map((e) => `- ${e.value} (${e.recordedAt})`);
|
|
172
|
+
sections.push("Recent Workflows:\n" + lines.join("\n"));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const feedbackEntries = byCategory.get("interaction_feedback");
|
|
176
|
+
if (feedbackEntries && feedbackEntries.length > 0) {
|
|
177
|
+
const lines = feedbackEntries.map((e) => `- ${e.key}: ${e.value}`);
|
|
178
|
+
sections.push("Feedback:\n" + lines.join("\n"));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return sections.join("\n\n");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function tryParseValue(json: string): string {
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(json);
|
|
188
|
+
return typeof parsed === "string" ? parsed : JSON.stringify(parsed);
|
|
189
|
+
} catch {
|
|
190
|
+
return json;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export interface ExtractedPreference {
|
|
2
|
+
key: string;
|
|
3
|
+
value: string;
|
|
4
|
+
confidence: "high" | "medium" | "low";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface PatternRule {
|
|
8
|
+
pattern: RegExp;
|
|
9
|
+
key: string;
|
|
10
|
+
value: string;
|
|
11
|
+
confidence: "high" | "medium" | "low";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const PREFERENCE_PATTERNS: PatternRule[] = [
|
|
15
|
+
// Asset scope
|
|
16
|
+
{
|
|
17
|
+
pattern: /\bprefer\s+etfs?\b/i,
|
|
18
|
+
key: "asset_scope",
|
|
19
|
+
value: "etf_focused",
|
|
20
|
+
confidence: "high",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
pattern: /\betf[\s-]*(?:heavy|focused|only)\b/i,
|
|
24
|
+
key: "asset_scope",
|
|
25
|
+
value: "etf_focused",
|
|
26
|
+
confidence: "high",
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// Risk profile
|
|
30
|
+
{
|
|
31
|
+
pattern: /\b(?:i'?m|i\s+am)\s+conservative\b/i,
|
|
32
|
+
key: "risk_profile",
|
|
33
|
+
value: "conservative",
|
|
34
|
+
confidence: "high",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
pattern: /\brisk\s*averse\b/i,
|
|
38
|
+
key: "risk_profile",
|
|
39
|
+
value: "conservative",
|
|
40
|
+
confidence: "high",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
pattern: /\b(?:i'?m|i\s+am)\s+aggressive\b/i,
|
|
44
|
+
key: "risk_profile",
|
|
45
|
+
value: "aggressive",
|
|
46
|
+
confidence: "high",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
pattern: /\baggressive\s+growth\b/i,
|
|
50
|
+
key: "risk_profile",
|
|
51
|
+
value: "aggressive",
|
|
52
|
+
confidence: "high",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
pattern: /\b(?:i'?m|i\s+am)\s+(?:moderate|balanced)\b/i,
|
|
56
|
+
key: "risk_profile",
|
|
57
|
+
value: "balanced",
|
|
58
|
+
confidence: "high",
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// Time horizon
|
|
62
|
+
{
|
|
63
|
+
pattern: /\b(?:12\s*month|one\s*year|1\s*year)\s*horizon/i,
|
|
64
|
+
key: "time_horizon",
|
|
65
|
+
value: "1y_plus",
|
|
66
|
+
confidence: "high",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
pattern: /\blong[\s-]*term\s+(?:invest|hold|horizon)/i,
|
|
70
|
+
key: "time_horizon",
|
|
71
|
+
value: "long",
|
|
72
|
+
confidence: "medium",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
pattern: /\bshort[\s-]*term\s+(?:trad|invest|horizon)/i,
|
|
76
|
+
key: "time_horizon",
|
|
77
|
+
value: "short",
|
|
78
|
+
confidence: "medium",
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// Options liquidity
|
|
82
|
+
{
|
|
83
|
+
pattern: /\b(?:only|prefer)\s+(?:trade\s+)?liquid\s+options?\b/i,
|
|
84
|
+
key: "options_liquidity",
|
|
85
|
+
value: "high",
|
|
86
|
+
confidence: "high",
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
export function extractPreferences(input: string): ExtractedPreference[] {
|
|
91
|
+
const preferences: ExtractedPreference[] = [];
|
|
92
|
+
const seenKeys = new Set<string>();
|
|
93
|
+
|
|
94
|
+
for (const rule of PREFERENCE_PATTERNS) {
|
|
95
|
+
if (rule.pattern.test(input) && !seenKeys.has(rule.key)) {
|
|
96
|
+
preferences.push({
|
|
97
|
+
key: rule.key,
|
|
98
|
+
value: rule.value,
|
|
99
|
+
confidence: rule.confidence,
|
|
100
|
+
});
|
|
101
|
+
seenKeys.add(rule.key);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return preferences;
|
|
106
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { MemoryStorage } from "./storage.js";
|
|
2
|
+
|
|
3
|
+
const MAX_PREFERENCE_LINES = 15;
|
|
4
|
+
const MAX_WORKFLOW_SUMMARY_LINES = 12;
|
|
5
|
+
|
|
6
|
+
/** Slot name → preference key(s) mapping for suppression. */
|
|
7
|
+
const SLOT_TO_PREF_KEYS: Record<string, string[]> = {
|
|
8
|
+
riskProfile: ["risk_profile"],
|
|
9
|
+
assetScope: ["asset_scope"],
|
|
10
|
+
timeHorizon: ["time_horizon"],
|
|
11
|
+
dteTarget: ["dte_target"],
|
|
12
|
+
moneynessPreference: ["moneyness_preference"],
|
|
13
|
+
liquidityMinimum: ["liquidity_minimum", "options_liquidity"],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build compact memory context for agent injection.
|
|
18
|
+
* @param overriddenSlots Slot names whose values were overridden by current-turn user input.
|
|
19
|
+
* The corresponding preference keys will be excluded from memory context to avoid
|
|
20
|
+
* conflicting provenance signals.
|
|
21
|
+
*/
|
|
22
|
+
export function buildMemoryContext(
|
|
23
|
+
storage: MemoryStorage,
|
|
24
|
+
overriddenSlots?: string[],
|
|
25
|
+
): string {
|
|
26
|
+
const sections: string[] = [];
|
|
27
|
+
|
|
28
|
+
// Build set of preference keys to suppress
|
|
29
|
+
const suppressedKeys = new Set<string>();
|
|
30
|
+
if (overriddenSlots) {
|
|
31
|
+
for (const slot of overriddenSlots) {
|
|
32
|
+
const keys = SLOT_TO_PREF_KEYS[slot];
|
|
33
|
+
if (keys) keys.forEach((k) => suppressedKeys.add(k));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Preferences
|
|
38
|
+
const prefs = storage.getPreferencesByNamespace("global");
|
|
39
|
+
if (prefs.length > 0) {
|
|
40
|
+
const filtered = prefs.filter((p) => !suppressedKeys.has(String(p.key)));
|
|
41
|
+
if (filtered.length > 0) {
|
|
42
|
+
const lines = filtered.slice(0, MAX_PREFERENCE_LINES).map((p) => {
|
|
43
|
+
const value = tryParseJson(p.value_json as string);
|
|
44
|
+
return `- ${p.key}: ${value}`;
|
|
45
|
+
});
|
|
46
|
+
sections.push("User Preferences:\n" + lines.join("\n"));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Recent workflow runs
|
|
51
|
+
const runs = storage.getRecentWorkflowRuns(3);
|
|
52
|
+
if (runs.length > 0) {
|
|
53
|
+
const lines = runs.slice(0, MAX_WORKFLOW_SUMMARY_LINES).map((r) => {
|
|
54
|
+
const summary = r.output_summary ? ` — ${r.output_summary}` : "";
|
|
55
|
+
return `- ${r.workflow_type} (${r.created_at})${summary}`;
|
|
56
|
+
});
|
|
57
|
+
sections.push("Recent Workflows:\n" + lines.join("\n"));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return sections.join("\n\n");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function tryParseJson(json: string): string {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(json);
|
|
66
|
+
return typeof parsed === "string" ? parsed : JSON.stringify(parsed);
|
|
67
|
+
} catch {
|
|
68
|
+
return json;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
4
|
+
import { getStateDbPath } from "../infra/opencandle-paths.js";
|
|
5
|
+
|
|
6
|
+
const CURRENT_SCHEMA_VERSION = 4;
|
|
7
|
+
|
|
8
|
+
const CURRENT_SCHEMA = `
|
|
9
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
10
|
+
version INTEGER NOT NULL
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
CREATE TABLE IF NOT EXISTS user_preferences (
|
|
14
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
15
|
+
namespace TEXT NOT NULL DEFAULT 'global',
|
|
16
|
+
key TEXT NOT NULL,
|
|
17
|
+
value_json TEXT NOT NULL,
|
|
18
|
+
confidence TEXT DEFAULT 'medium',
|
|
19
|
+
source TEXT DEFAULT 'explicit',
|
|
20
|
+
created_at TEXT NOT NULL,
|
|
21
|
+
updated_at TEXT NOT NULL,
|
|
22
|
+
UNIQUE(namespace, key)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
26
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27
|
+
session_id TEXT NOT NULL,
|
|
28
|
+
workflow_type TEXT NOT NULL,
|
|
29
|
+
input_slots_json TEXT,
|
|
30
|
+
resolved_slots_json TEXT,
|
|
31
|
+
defaults_used_json TEXT,
|
|
32
|
+
output_summary TEXT,
|
|
33
|
+
created_at TEXT NOT NULL,
|
|
34
|
+
turn_type TEXT NOT NULL DEFAULT 'workflow'
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE TABLE IF NOT EXISTS recommendations (
|
|
38
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
39
|
+
workflow_run_id INTEGER NOT NULL,
|
|
40
|
+
recommendation_type TEXT NOT NULL,
|
|
41
|
+
symbol TEXT,
|
|
42
|
+
payload_json TEXT,
|
|
43
|
+
created_at TEXT NOT NULL,
|
|
44
|
+
FOREIGN KEY (workflow_run_id) REFERENCES workflow_runs(id)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS workflow_events (
|
|
48
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
49
|
+
run_id TEXT NOT NULL,
|
|
50
|
+
step_index INTEGER NOT NULL,
|
|
51
|
+
event_type TEXT NOT NULL,
|
|
52
|
+
payload_json TEXT,
|
|
53
|
+
timestamp TEXT NOT NULL
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_events_run_id ON workflow_events(run_id);
|
|
57
|
+
|
|
58
|
+
CREATE TABLE IF NOT EXISTS tool_defaults (
|
|
59
|
+
tool_name TEXT NOT NULL,
|
|
60
|
+
param_path TEXT NOT NULL,
|
|
61
|
+
value_json TEXT NOT NULL,
|
|
62
|
+
set_at TEXT NOT NULL,
|
|
63
|
+
PRIMARY KEY (tool_name, param_path)
|
|
64
|
+
);
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
export function initDatabase(path: string): Database.Database {
|
|
68
|
+
if (path !== ":memory:") {
|
|
69
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
const db = new Database(path);
|
|
72
|
+
db.pragma("journal_mode = WAL");
|
|
73
|
+
db.pragma("foreign_keys = ON");
|
|
74
|
+
ensureCurrentSchema(db);
|
|
75
|
+
|
|
76
|
+
return db;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function initDefaultDatabase(): Database.Database {
|
|
80
|
+
return initDatabase(getStateDbPath());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function ensureCurrentSchema(db: Database.Database): void {
|
|
84
|
+
const currentVersion = readSchemaVersion(db);
|
|
85
|
+
|
|
86
|
+
if (currentVersion === CURRENT_SCHEMA_VERSION) {
|
|
87
|
+
// Up to date — still run CREATE TABLE IF NOT EXISTS for any missing auxiliary
|
|
88
|
+
// tables (e.g. workflow_events added out-of-band).
|
|
89
|
+
db.exec(CURRENT_SCHEMA);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (currentVersion === 3) {
|
|
94
|
+
migrateV3ToV4(db);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Additive v2 → v3 → v4 migration without dropping data.
|
|
99
|
+
if (currentVersion === 2) {
|
|
100
|
+
migrateV2ToV3(db);
|
|
101
|
+
migrateV3ToV4(db);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Any other mismatch (null first-run, or a foreign schema): reset.
|
|
106
|
+
resetSchema(db);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function migrateV2ToV3(db: Database.Database): void {
|
|
110
|
+
const cols = (db.pragma("table_info(workflow_runs)") as Array<{ name: string }>).map(
|
|
111
|
+
(c) => c.name,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (!cols.includes("turn_type")) {
|
|
115
|
+
db.exec(
|
|
116
|
+
`ALTER TABLE workflow_runs ADD COLUMN turn_type TEXT NOT NULL DEFAULT 'workflow'`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Ensure any tables or indexes added between versions are present.
|
|
121
|
+
db.exec(CURRENT_SCHEMA);
|
|
122
|
+
|
|
123
|
+
db.prepare("DELETE FROM schema_version").run();
|
|
124
|
+
db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(3);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function migrateV3ToV4(db: Database.Database): void {
|
|
128
|
+
db.exec(CURRENT_SCHEMA);
|
|
129
|
+
|
|
130
|
+
db.prepare("DELETE FROM schema_version").run();
|
|
131
|
+
db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(CURRENT_SCHEMA_VERSION);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function readSchemaVersion(db: Database.Database): number | null {
|
|
135
|
+
const table = db
|
|
136
|
+
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'schema_version'")
|
|
137
|
+
.get() as { name: string } | undefined;
|
|
138
|
+
if (!table) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const row = db.prepare("SELECT version FROM schema_version LIMIT 1").get() as
|
|
143
|
+
| { version: number }
|
|
144
|
+
| undefined;
|
|
145
|
+
return row?.version ?? null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resetSchema(db: Database.Database): void {
|
|
149
|
+
db.exec(`
|
|
150
|
+
DROP TABLE IF EXISTS recommendations;
|
|
151
|
+
DROP TABLE IF EXISTS workflow_runs;
|
|
152
|
+
DROP TABLE IF EXISTS user_preferences;
|
|
153
|
+
DROP TABLE IF EXISTS tool_defaults;
|
|
154
|
+
DROP TABLE IF EXISTS schema_version;
|
|
155
|
+
`);
|
|
156
|
+
db.exec(CURRENT_SCHEMA);
|
|
157
|
+
db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(CURRENT_SCHEMA_VERSION);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function getTableNames(db: Database.Database): string[] {
|
|
161
|
+
const rows = db
|
|
162
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
|
163
|
+
.all() as Array<{ name: string }>;
|
|
164
|
+
return rows.map((r) => r.name);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function getSchemaVersion(db: Database.Database): number {
|
|
168
|
+
const row = db.prepare("SELECT version FROM schema_version LIMIT 1").get() as
|
|
169
|
+
| { version: number }
|
|
170
|
+
| undefined;
|
|
171
|
+
return row?.version ?? 0;
|
|
172
|
+
}
|