opencandle 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -87
- package/assets/logo.svg +187 -0
- package/dist/analysts/orchestrator.js +1 -2
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +38 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +34 -5
- package/dist/config.js +29 -8
- package/dist/config.js.map +1 -1
- package/dist/infra/browser.d.ts +10 -0
- package/dist/infra/browser.js +1 -0
- package/dist/infra/browser.js.map +1 -1
- package/dist/infra/cache.d.ts +4 -0
- package/dist/infra/cache.js +4 -0
- package/dist/infra/cache.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.js +6 -0
- 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/sqlite.js +42 -4
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/memory/storage.d.ts +6 -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/onboarding/connect.d.ts +35 -0
- package/dist/onboarding/connect.js +118 -0
- package/dist/onboarding/connect.js.map +1 -0
- package/dist/onboarding/credential-interceptor.d.ts +44 -0
- package/dist/onboarding/credential-interceptor.js +72 -0
- package/dist/onboarding/credential-interceptor.js.map +1 -0
- package/dist/onboarding/degradation-accumulator.d.ts +21 -0
- package/dist/onboarding/degradation-accumulator.js +55 -0
- package/dist/onboarding/degradation-accumulator.js.map +1 -0
- package/dist/onboarding/prompt-user.d.ts +23 -0
- package/dist/onboarding/prompt-user.js +61 -0
- package/dist/onboarding/prompt-user.js.map +1 -0
- package/dist/onboarding/providers.d.ts +116 -0
- package/dist/onboarding/providers.js +239 -0
- package/dist/onboarding/providers.js.map +1 -0
- package/dist/onboarding/state.d.ts +31 -2
- package/dist/onboarding/state.js +141 -13
- package/dist/onboarding/state.js.map +1 -1
- package/dist/onboarding/tool-helpers.d.ts +34 -0
- package/dist/onboarding/tool-helpers.js +80 -0
- package/dist/onboarding/tool-helpers.js.map +1 -0
- package/dist/onboarding/tool-tags.d.ts +37 -0
- package/dist/onboarding/tool-tags.js +149 -0
- package/dist/onboarding/tool-tags.js.map +1 -0
- package/dist/onboarding/validation.d.ts +19 -0
- package/dist/onboarding/validation.js +117 -0
- package/dist/onboarding/validation.js.map +1 -0
- package/dist/pi/opencandle-extension.d.ts +7 -1
- package/dist/pi/opencandle-extension.js +488 -13
- 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 -2
- package/dist/pi/setup.js +67 -120
- 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 +22 -0
- package/dist/prompts/context-builder.js +47 -11
- 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/workflow-prompts.d.ts +8 -0
- package/dist/prompts/workflow-prompts.js +39 -5
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/alpha-vantage.js +20 -1
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/exa-search.d.ts +39 -0
- package/dist/providers/exa-search.js +276 -0
- package/dist/providers/exa-search.js.map +1 -0
- package/dist/providers/finnhub.d.ts +17 -0
- package/dist/providers/finnhub.js +94 -0
- package/dist/providers/finnhub.js.map +1 -0
- package/dist/providers/fred.js +13 -1
- package/dist/providers/fred.js.map +1 -1
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/provider-credential-error.d.ts +8 -0
- package/dist/providers/provider-credential-error.js +22 -0
- package/dist/providers/provider-credential-error.js.map +1 -0
- package/dist/providers/reddit.d.ts +8 -0
- package/dist/providers/reddit.js +36 -9
- package/dist/providers/reddit.js.map +1 -1
- package/dist/providers/twitter.js +2 -8
- package/dist/providers/twitter.js.map +1 -1
- package/dist/providers/web-search.d.ts +17 -0
- package/dist/providers/web-search.js +224 -0
- package/dist/providers/web-search.js.map +1 -0
- package/dist/providers/wrap-provider.d.ts +7 -0
- package/dist/providers/wrap-provider.js +15 -0
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/providers/yahoo-finance.js +70 -33
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.js +22 -0
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/defaults.js +1 -1
- package/dist/routing/defaults.js.map +1 -1
- package/dist/routing/index.d.ts +4 -0
- package/dist/routing/index.js +3 -0
- package/dist/routing/index.js.map +1 -1
- 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 +138 -0
- package/dist/routing/router-prompt.js.map +1 -0
- package/dist/routing/router-types.d.ts +62 -0
- package/dist/routing/router-types.js +2 -0
- package/dist/routing/router-types.js.map +1 -0
- package/dist/routing/router.d.ts +10 -0
- package/dist/routing/router.js +194 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/runtime/session-coordinator.d.ts +63 -4
- package/dist/runtime/session-coordinator.js +155 -4
- 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/adapters/finnhub.d.ts +7 -0
- package/dist/sentiment/adapters/finnhub.js +39 -0
- package/dist/sentiment/adapters/finnhub.js.map +1 -0
- package/dist/sentiment/adapters/reddit.d.ts +11 -0
- package/dist/sentiment/adapters/reddit.js +54 -0
- package/dist/sentiment/adapters/reddit.js.map +1 -0
- package/dist/sentiment/adapters/twitter.d.ts +9 -0
- package/dist/sentiment/adapters/twitter.js +32 -0
- package/dist/sentiment/adapters/twitter.js.map +1 -0
- package/dist/sentiment/adapters/web.d.ts +9 -0
- package/dist/sentiment/adapters/web.js +40 -0
- package/dist/sentiment/adapters/web.js.map +1 -0
- package/dist/sentiment/index.d.ts +16 -0
- package/dist/sentiment/index.js +44 -0
- package/dist/sentiment/index.js.map +1 -0
- package/dist/sentiment/keywords.d.ts +2 -0
- package/dist/sentiment/keywords.js +9 -0
- package/dist/sentiment/keywords.js.map +1 -0
- package/dist/sentiment/pipeline.d.ts +9 -0
- package/dist/sentiment/pipeline.js +57 -0
- package/dist/sentiment/pipeline.js.map +1 -0
- package/dist/sentiment/scorer.d.ts +9 -0
- package/dist/sentiment/scorer.js +64 -0
- package/dist/sentiment/scorer.js.map +1 -0
- package/dist/sentiment/store.d.ts +24 -0
- package/dist/sentiment/store.js +182 -0
- package/dist/sentiment/store.js.map +1 -0
- package/dist/sentiment/trends.d.ts +13 -0
- package/dist/sentiment/trends.js +73 -0
- package/dist/sentiment/trends.js.map +1 -0
- package/dist/sentiment/types.d.ts +66 -0
- package/dist/sentiment/types.js +54 -0
- package/dist/sentiment/types.js.map +1 -0
- package/dist/system-prompt.js +29 -13
- package/dist/system-prompt.js.map +1 -1
- package/dist/tool-kit.d.ts +4 -4
- package/dist/tools/fundamentals/company-overview.d.ts +4 -2
- package/dist/tools/fundamentals/company-overview.js +27 -27
- 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 +45 -45
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.d.ts +1 -1
- package/dist/tools/fundamentals/dcf.js +82 -82
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.d.ts +4 -2
- package/dist/tools/fundamentals/earnings.js +25 -25
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.d.ts +4 -2
- package/dist/tools/fundamentals/financials.js +23 -23
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.d.ts +1 -1
- package/dist/tools/index.d.ts +28 -1
- package/dist/tools/index.js +35 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.d.ts +1 -1
- package/dist/tools/interaction/ask-user.js +28 -64
- package/dist/tools/interaction/ask-user.js.map +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/fred-data.d.ts +4 -2
- package/dist/tools/macro/fred-data.js +26 -26
- 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-price.d.ts +1 -1
- package/dist/tools/market/search-ticker.d.ts +1 -1
- package/dist/tools/market/stock-history.d.ts +1 -1
- package/dist/tools/market/stock-quote.d.ts +1 -1
- package/dist/tools/options/option-chain.d.ts +1 -1
- package/dist/tools/options/option-chain.js +4 -1
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/correlation.d.ts +1 -1
- package/dist/tools/portfolio/predictions.d.ts +1 -1
- package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
- package/dist/tools/portfolio/tracker.d.ts +1 -1
- package/dist/tools/portfolio/watchlist.d.ts +1 -1
- package/dist/tools/sentiment/reddit-sentiment.d.ts +4 -2
- package/dist/tools/sentiment/reddit-sentiment.js +107 -22
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.d.ts +7 -0
- package/dist/tools/sentiment/sentiment-summary.js +230 -0
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -0
- package/dist/tools/sentiment/sentiment-trend.d.ts +22 -0
- package/dist/tools/sentiment/sentiment-trend.js +39 -0
- package/dist/tools/sentiment/sentiment-trend.js.map +1 -0
- package/dist/tools/sentiment/twitter-sentiment.d.ts +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js +17 -0
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/web-search.d.ts +11 -0
- package/dist/tools/sentiment/web-search.js +115 -0
- package/dist/tools/sentiment/web-search.js.map +1 -0
- package/dist/tools/sentiment/web-sentiment.d.ts +8 -0
- package/dist/tools/sentiment/web-sentiment.js +66 -0
- package/dist/tools/sentiment/web-sentiment.js.map +1 -0
- package/dist/tools/technical/backtest.d.ts +1 -1
- package/dist/tools/technical/indicators.d.ts +1 -1
- package/dist/tools/technical/indicators.js +7 -1
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/sentiment.d.ts +21 -0
- package/dist/workflows/options-screener.js +7 -2
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.js +3 -3
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/background-quotes.ts +31 -0
- package/gui/server/chat-event-adapter.ts +142 -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 +212 -0
- package/gui/server/server.ts +592 -0
- package/gui/server/session-actions.ts +31 -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-D1ImSJTe.js +1 -0
- package/gui/web/dist/assets/index-DBrWq43L.css +1 -0
- package/gui/web/dist/assets/index-RflHaj0y.js +67 -0
- package/gui/web/dist/assets/logo-CWpt6Y2a.svg +187 -0
- package/gui/web/dist/index.html +17 -0
- package/package.json +62 -20
- package/src/analysts/contracts.ts +189 -0
- package/src/analysts/orchestrator.ts +300 -0
- package/src/cli.ts +205 -0
- package/src/config.ts +161 -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 +64 -0
- package/src/memory/index.ts +10 -0
- package/src/memory/manager.ts +159 -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 +204 -0
- package/src/memory/tool-defaults.ts +87 -0
- package/src/memory/types.ts +67 -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 +724 -0
- package/src/pi/session-storage.ts +5 -0
- package/src/pi/session.ts +81 -0
- package/src/pi/setup.ts +371 -0
- package/src/pi/tool-adapter.ts +36 -0
- package/src/prompts/context-builder.ts +204 -0
- package/src/prompts/disclaimer.ts +9 -0
- package/src/prompts/sections.ts +46 -0
- package/src/prompts/workflow-prompts.ts +279 -0
- package/src/providers/alpha-vantage.ts +292 -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 +96 -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 +367 -0
- package/src/routing/classify-intent.ts +194 -0
- package/src/routing/defaults.ts +29 -0
- package/src/routing/entity-extractor.ts +140 -0
- package/src/routing/index.ts +26 -0
- package/src/routing/router-llm-client.ts +51 -0
- package/src/routing/router-prompt.ts +159 -0
- package/src/routing/router-types.ts +66 -0
- package/src/routing/router.ts +213 -0
- package/src/routing/slot-resolver.ts +152 -0
- package/src/routing/types.ts +63 -0
- package/src/runtime/evidence.ts +77 -0
- package/src/runtime/index.ts +55 -0
- package/src/runtime/prompt-step.ts +75 -0
- package/src/runtime/provider-ids.ts +15 -0
- package/src/runtime/provider-tracker.ts +40 -0
- package/src/runtime/run-context.ts +22 -0
- package/src/runtime/session-coordinator.ts +406 -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 +115 -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 +61 -0
- package/src/tools/index.ts +88 -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 +54 -0
- package/src/tools/market/crypto-history.ts +51 -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 +82 -0
- package/src/tools/options/option-chain.ts +91 -0
- package/src/tools/portfolio/correlation.ts +162 -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 +153 -0
- package/src/tools/sentiment/reddit-sentiment.ts +164 -0
- package/src/tools/sentiment/sentiment-summary.ts +256 -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 +150 -0
- package/src/tools/sentiment/web-sentiment.ts +76 -0
- package/src/tools/technical/backtest.ts +246 -0
- package/src/tools/technical/indicators.ts +258 -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 +35 -0
- package/src/types/portfolio.ts +41 -0
- package/src/types/sentiment.ts +70 -0
- package/src/workflows/compare-assets.ts +39 -0
- package/src/workflows/index.ts +4 -0
- package/src/workflows/options-screener.ts +49 -0
- package/src/workflows/portfolio-builder.ts +52 -0
- package/src/workflows/types.ts +4 -0
- package/dist/tools/sentiment/news-sentiment.d.ts +0 -7
- package/dist/tools/sentiment/news-sentiment.js +0 -55
- package/dist/tools/sentiment/news-sentiment.js.map +0 -1
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
|
|
3
|
+
interface PreferenceInput {
|
|
4
|
+
namespace?: string;
|
|
5
|
+
key: string;
|
|
6
|
+
valueJson: string;
|
|
7
|
+
confidence?: string;
|
|
8
|
+
source?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface WorkflowPreferences {
|
|
12
|
+
riskProfile?: string;
|
|
13
|
+
timeHorizon?: string;
|
|
14
|
+
assetScope?: string;
|
|
15
|
+
positionCount?: number;
|
|
16
|
+
maxSinglePositionPct?: number;
|
|
17
|
+
dteTarget?: string;
|
|
18
|
+
objective?: string;
|
|
19
|
+
moneynessPreference?: string;
|
|
20
|
+
liquidityMinimum?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface WorkflowRunInput {
|
|
24
|
+
sessionId: string;
|
|
25
|
+
workflowType: string;
|
|
26
|
+
inputSlotsJson: string;
|
|
27
|
+
resolvedSlotsJson: string;
|
|
28
|
+
defaultsUsedJson: string;
|
|
29
|
+
outputSummary?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Router route verbatim. One of `"workflow"` or `"fallback"`. Defaults to
|
|
32
|
+
* `"workflow"` at the schema layer so legacy callers (rules-mode cascade)
|
|
33
|
+
* don't need to pass anything. Router-mode callers MUST pass this explicitly.
|
|
34
|
+
*/
|
|
35
|
+
turnType?: "workflow" | "fallback";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface RecommendationInput {
|
|
39
|
+
workflowRunId: number;
|
|
40
|
+
recommendationType: string;
|
|
41
|
+
symbol?: string;
|
|
42
|
+
payloadJson?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class MemoryStorage {
|
|
46
|
+
constructor(private readonly db: Database.Database) {}
|
|
47
|
+
|
|
48
|
+
// --- Preferences ---
|
|
49
|
+
|
|
50
|
+
upsertPreference(input: PreferenceInput): void {
|
|
51
|
+
const now = new Date().toISOString();
|
|
52
|
+
const ns = input.namespace ?? "global";
|
|
53
|
+
const confidence = input.confidence ?? "medium";
|
|
54
|
+
const source = input.source ?? "explicit";
|
|
55
|
+
|
|
56
|
+
this.db
|
|
57
|
+
.prepare(
|
|
58
|
+
`INSERT INTO user_preferences (namespace, key, value_json, confidence, source, created_at, updated_at)
|
|
59
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
60
|
+
ON CONFLICT(namespace, key) DO UPDATE SET
|
|
61
|
+
value_json = excluded.value_json,
|
|
62
|
+
confidence = excluded.confidence,
|
|
63
|
+
source = excluded.source,
|
|
64
|
+
updated_at = excluded.updated_at`,
|
|
65
|
+
)
|
|
66
|
+
.run(ns, input.key, input.valueJson, confidence, source, now, now);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getPreference(
|
|
70
|
+
namespace: string,
|
|
71
|
+
key: string,
|
|
72
|
+
): Record<string, string | number | null> | null {
|
|
73
|
+
return (
|
|
74
|
+
(this.db
|
|
75
|
+
.prepare("SELECT * FROM user_preferences WHERE namespace = ? AND key = ?")
|
|
76
|
+
.get(namespace, key) as Record<string, string | number | null>) ?? null
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getPreferencesByNamespace(
|
|
81
|
+
namespace: string,
|
|
82
|
+
): Array<Record<string, string | number | null>> {
|
|
83
|
+
return this.db
|
|
84
|
+
.prepare("SELECT * FROM user_preferences WHERE namespace = ?")
|
|
85
|
+
.all(namespace) as Array<Record<string, string | number | null>>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getWorkflowPreferences(namespace: string = "global"): WorkflowPreferences {
|
|
89
|
+
const prefs = this.getPreferencesByNamespace(namespace);
|
|
90
|
+
const out: WorkflowPreferences = {};
|
|
91
|
+
|
|
92
|
+
for (const pref of prefs) {
|
|
93
|
+
const key = String(pref.key);
|
|
94
|
+
const raw = pref.value_json == null ? undefined : safeParseJson(String(pref.value_json));
|
|
95
|
+
|
|
96
|
+
switch (key) {
|
|
97
|
+
case "risk_profile":
|
|
98
|
+
if (typeof raw === "string") out.riskProfile = raw;
|
|
99
|
+
break;
|
|
100
|
+
case "time_horizon":
|
|
101
|
+
if (typeof raw === "string") out.timeHorizon = raw;
|
|
102
|
+
break;
|
|
103
|
+
case "asset_scope":
|
|
104
|
+
if (typeof raw === "string") out.assetScope = raw;
|
|
105
|
+
break;
|
|
106
|
+
case "position_count":
|
|
107
|
+
if (typeof raw === "number") out.positionCount = raw;
|
|
108
|
+
break;
|
|
109
|
+
case "max_single_position_pct":
|
|
110
|
+
if (typeof raw === "number") out.maxSinglePositionPct = raw;
|
|
111
|
+
break;
|
|
112
|
+
case "dte_target":
|
|
113
|
+
if (typeof raw === "string") out.dteTarget = raw;
|
|
114
|
+
break;
|
|
115
|
+
case "objective":
|
|
116
|
+
if (typeof raw === "string") out.objective = raw;
|
|
117
|
+
break;
|
|
118
|
+
case "moneyness_preference":
|
|
119
|
+
if (typeof raw === "string") out.moneynessPreference = raw;
|
|
120
|
+
break;
|
|
121
|
+
case "options_liquidity":
|
|
122
|
+
case "liquidity_minimum":
|
|
123
|
+
if (typeof raw === "string") {
|
|
124
|
+
out.liquidityMinimum =
|
|
125
|
+
raw === "high" ? "high_open_interest_and_tight_spread" : raw;
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- Workflow Runs ---
|
|
135
|
+
|
|
136
|
+
insertWorkflowRun(input: WorkflowRunInput): number {
|
|
137
|
+
const now = new Date().toISOString();
|
|
138
|
+
const result = this.db
|
|
139
|
+
.prepare(
|
|
140
|
+
`INSERT INTO workflow_runs (session_id, workflow_type, input_slots_json, resolved_slots_json, defaults_used_json, output_summary, created_at, turn_type)
|
|
141
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
142
|
+
)
|
|
143
|
+
.run(
|
|
144
|
+
input.sessionId,
|
|
145
|
+
input.workflowType,
|
|
146
|
+
input.inputSlotsJson,
|
|
147
|
+
input.resolvedSlotsJson,
|
|
148
|
+
input.defaultsUsedJson,
|
|
149
|
+
input.outputSummary ?? null,
|
|
150
|
+
now,
|
|
151
|
+
input.turnType ?? "workflow",
|
|
152
|
+
);
|
|
153
|
+
return Number(result.lastInsertRowid);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getRecentWorkflowRuns(
|
|
157
|
+
limit: number,
|
|
158
|
+
): Array<Record<string, string | number | null>> {
|
|
159
|
+
return this.db
|
|
160
|
+
.prepare("SELECT * FROM workflow_runs ORDER BY id DESC LIMIT ?")
|
|
161
|
+
.all(limit) as Array<Record<string, string | number | null>>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
updateWorkflowRunOutputSummary(workflowRunId: number, outputSummary: string): void {
|
|
165
|
+
this.db
|
|
166
|
+
.prepare("UPDATE workflow_runs SET output_summary = ? WHERE id = ?")
|
|
167
|
+
.run(outputSummary, workflowRunId);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// --- Recommendations ---
|
|
171
|
+
|
|
172
|
+
insertRecommendation(input: RecommendationInput): number {
|
|
173
|
+
const now = new Date().toISOString();
|
|
174
|
+
const result = this.db
|
|
175
|
+
.prepare(
|
|
176
|
+
`INSERT INTO recommendations (workflow_run_id, recommendation_type, symbol, payload_json, created_at)
|
|
177
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
178
|
+
)
|
|
179
|
+
.run(
|
|
180
|
+
input.workflowRunId,
|
|
181
|
+
input.recommendationType,
|
|
182
|
+
input.symbol ?? null,
|
|
183
|
+
input.payloadJson ?? null,
|
|
184
|
+
now,
|
|
185
|
+
);
|
|
186
|
+
return Number(result.lastInsertRowid);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
getRecommendationsByRun(
|
|
190
|
+
workflowRunId: number,
|
|
191
|
+
): Array<Record<string, string | number | null>> {
|
|
192
|
+
return this.db
|
|
193
|
+
.prepare("SELECT * FROM recommendations WHERE workflow_run_id = ? ORDER BY id")
|
|
194
|
+
.all(workflowRunId) as Array<Record<string, string | number | null>>;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function safeParseJson(json: string): unknown {
|
|
199
|
+
try {
|
|
200
|
+
return JSON.parse(json);
|
|
201
|
+
} catch {
|
|
202
|
+
return json;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { initDefaultDatabase } from "./sqlite.js";
|
|
2
|
+
|
|
3
|
+
export type ToolDefaults = Record<string, unknown>;
|
|
4
|
+
type SqliteDb = ReturnType<typeof initDefaultDatabase>;
|
|
5
|
+
|
|
6
|
+
export function getDefaults(toolName: string, db: SqliteDb = initDefaultDatabase()): ToolDefaults {
|
|
7
|
+
const rows = db
|
|
8
|
+
.prepare(
|
|
9
|
+
`SELECT param_path, value_json FROM tool_defaults WHERE tool_name = ? ORDER BY param_path`,
|
|
10
|
+
)
|
|
11
|
+
.all(toolName) as Array<{ param_path: string; value_json: string }>;
|
|
12
|
+
|
|
13
|
+
const defaults: ToolDefaults = {};
|
|
14
|
+
for (const row of rows) {
|
|
15
|
+
setPath(defaults, row.param_path, parseStoredValue(row.value_json));
|
|
16
|
+
}
|
|
17
|
+
return defaults;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getAllDefaults(db: SqliteDb = initDefaultDatabase()): Map<string, ToolDefaults> {
|
|
21
|
+
const rows = db
|
|
22
|
+
.prepare(
|
|
23
|
+
`SELECT tool_name, param_path, value_json FROM tool_defaults ORDER BY tool_name, param_path`,
|
|
24
|
+
)
|
|
25
|
+
.all() as Array<{ tool_name: string; param_path: string; value_json: string }>;
|
|
26
|
+
|
|
27
|
+
const groups = new Map<string, ToolDefaults>();
|
|
28
|
+
for (const row of rows) {
|
|
29
|
+
const defaults = groups.get(row.tool_name) ?? {};
|
|
30
|
+
setPath(defaults, row.param_path, parseStoredValue(row.value_json));
|
|
31
|
+
groups.set(row.tool_name, defaults);
|
|
32
|
+
}
|
|
33
|
+
return groups;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function setDefault(
|
|
37
|
+
toolName: string,
|
|
38
|
+
paramPath: string,
|
|
39
|
+
value: unknown,
|
|
40
|
+
db: SqliteDb = initDefaultDatabase(),
|
|
41
|
+
): void {
|
|
42
|
+
db.prepare(
|
|
43
|
+
`INSERT INTO tool_defaults (tool_name, param_path, value_json, set_at)
|
|
44
|
+
VALUES (?, ?, ?, ?)
|
|
45
|
+
ON CONFLICT(tool_name, param_path) DO UPDATE SET
|
|
46
|
+
value_json = excluded.value_json,
|
|
47
|
+
set_at = excluded.set_at`,
|
|
48
|
+
).run(toolName, paramPath, JSON.stringify(value), new Date().toISOString());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function clearDefault(
|
|
52
|
+
toolName: string,
|
|
53
|
+
paramPath: string,
|
|
54
|
+
db: SqliteDb = initDefaultDatabase(),
|
|
55
|
+
): void {
|
|
56
|
+
db.prepare(`DELETE FROM tool_defaults WHERE tool_name = ? AND param_path = ?`).run(
|
|
57
|
+
toolName,
|
|
58
|
+
paramPath,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseStoredValue(valueJson: string): unknown {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(valueJson);
|
|
65
|
+
} catch {
|
|
66
|
+
return valueJson;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function setPath(target: ToolDefaults, path: string, value: unknown): void {
|
|
71
|
+
const parts = path.split(".").filter(Boolean);
|
|
72
|
+
if (parts.length === 0) return;
|
|
73
|
+
|
|
74
|
+
let cursor: Record<string, unknown> = target;
|
|
75
|
+
for (const part of parts.slice(0, -1)) {
|
|
76
|
+
const existing = cursor[part];
|
|
77
|
+
if (!isPlainObject(existing)) {
|
|
78
|
+
cursor[part] = {};
|
|
79
|
+
}
|
|
80
|
+
cursor = cursor[part] as Record<string, unknown>;
|
|
81
|
+
}
|
|
82
|
+
cursor[parts[parts.length - 1]] = value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
86
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
87
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Memory categories for typed, selective retrieval. */
|
|
2
|
+
export type MemoryCategory =
|
|
3
|
+
| "investor_profile"
|
|
4
|
+
| "interaction_feedback"
|
|
5
|
+
| "workflow_history"
|
|
6
|
+
| "references";
|
|
7
|
+
|
|
8
|
+
/** A memory entry with category and freshness metadata. */
|
|
9
|
+
export interface MemoryEntry {
|
|
10
|
+
key: string;
|
|
11
|
+
value: string;
|
|
12
|
+
category: MemoryCategory;
|
|
13
|
+
recordedAt: string;
|
|
14
|
+
confidence?: string;
|
|
15
|
+
source?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Staleness thresholds in milliseconds per category. */
|
|
19
|
+
export const STALENESS_THRESHOLDS: Record<MemoryCategory, number> = {
|
|
20
|
+
investor_profile: 90 * 24 * 60 * 60 * 1000, // 90 days
|
|
21
|
+
interaction_feedback: 14 * 24 * 60 * 60 * 1000, // 14 days
|
|
22
|
+
workflow_history: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
23
|
+
references: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Map preference keys to memory categories. */
|
|
27
|
+
export const KEY_TO_CATEGORY: Record<string, MemoryCategory> = {
|
|
28
|
+
risk_profile: "investor_profile",
|
|
29
|
+
time_horizon: "investor_profile",
|
|
30
|
+
asset_scope: "investor_profile",
|
|
31
|
+
position_count: "investor_profile",
|
|
32
|
+
max_single_position_pct: "investor_profile",
|
|
33
|
+
account_type: "investor_profile",
|
|
34
|
+
income_vs_growth: "investor_profile",
|
|
35
|
+
dte_target: "investor_profile",
|
|
36
|
+
objective: "investor_profile",
|
|
37
|
+
moneyness_preference: "investor_profile",
|
|
38
|
+
liquidity_minimum: "investor_profile",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Categories relevant to each workflow type. */
|
|
42
|
+
export const WORKFLOW_RELEVANT_CATEGORIES: Record<string, MemoryCategory[]> = {
|
|
43
|
+
portfolio_builder: ["investor_profile", "interaction_feedback", "workflow_history"],
|
|
44
|
+
options_screener: ["investor_profile", "interaction_feedback", "workflow_history"],
|
|
45
|
+
compare_assets: ["investor_profile", "workflow_history"],
|
|
46
|
+
comprehensive_analysis: ["investor_profile", "workflow_history"],
|
|
47
|
+
single_asset_analysis: ["investor_profile"],
|
|
48
|
+
general_finance_qa: ["investor_profile"],
|
|
49
|
+
unclassified: ["investor_profile"],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Check whether a memory entry is stale. */
|
|
53
|
+
export function isStale(entry: MemoryEntry, now: Date = new Date()): boolean {
|
|
54
|
+
const threshold = STALENESS_THRESHOLDS[entry.category];
|
|
55
|
+
const age = now.getTime() - new Date(entry.recordedAt).getTime();
|
|
56
|
+
return age > threshold;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Keys whose values are market-sensitive and must never be trusted from memory. */
|
|
60
|
+
export const NEVER_TRUST_FROM_MEMORY = new Set([
|
|
61
|
+
"stock_price",
|
|
62
|
+
"crypto_price",
|
|
63
|
+
"market_thesis",
|
|
64
|
+
"target_price",
|
|
65
|
+
"entry_price",
|
|
66
|
+
"stop_loss",
|
|
67
|
+
]);
|