opencandle 0.4.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/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +19 -3
- package/dist/config.js +61 -2
- package/dist/config.js.map +1 -1
- package/dist/infra/browser.d.ts +1 -3
- package/dist/infra/browser.js +1 -1
- package/dist/infra/browser.js.map +1 -1
- 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/manager.d.ts +9 -0
- package/dist/memory/manager.js +28 -11
- package/dist/memory/manager.js.map +1 -1
- package/dist/memory/storage.d.ts +3 -2
- package/dist/memory/storage.js.map +1 -1
- package/dist/memory/types.js +4 -0
- package/dist/memory/types.js.map +1 -1
- package/dist/pi/opencandle-extension.js +230 -36
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/setup.js +10 -0
- package/dist/pi/setup.js.map +1 -1
- package/dist/prompts/context-builder.d.ts +18 -3
- package/dist/prompts/context-builder.js +102 -16
- 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.js +3 -3
- package/dist/prompts/sections.js.map +1 -1
- package/dist/prompts/workflow-prompts.js +170 -18
- 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 +134 -3
- 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 +3 -3
- 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 +7 -1
- package/dist/routing/index.js +4 -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-prompt.js +45 -42
- package/dist/routing/router-prompt.js.map +1 -1
- package/dist/routing/router-types.d.ts +9 -0
- package/dist/routing/router.d.ts +1 -0
- package/dist/routing/router.js +456 -12
- package/dist/routing/router.js.map +1 -1
- 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 +20 -2
- package/dist/runtime/session-coordinator.js +47 -14
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/system-prompt.js +4 -1
- package/dist/system-prompt.js.map +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.js +1 -1
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +1 -1
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.js +1 -1
- package/dist/tools/fundamentals/earnings.js.map +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 +1 -0
- 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 +1 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.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.js +29 -5
- package/dist/tools/macro/fred-data.js.map +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.js +1 -1
- package/dist/tools/market/crypto-price.js.map +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.js +1 -1
- package/dist/tools/market/stock-history.js.map +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.js +9 -4
- package/dist/tools/options/option-chain.js.map +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.js +1 -1
- package/dist/tools/portfolio/predictions.js.map +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.js +1 -1
- package/dist/tools/portfolio/tracker.js.map +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.js +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js.map +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/twitter-sentiment.js +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js.map +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.js +1 -1
- package/dist/tools/sentiment/web-sentiment.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +2 -2
- package/dist/tools/technical/backtest.js +41 -27
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +1 -3
- 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 +88 -7
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.js +7 -3
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/ask-user-bridge.ts +82 -0
- package/gui/server/gui-session-manager.ts +5 -0
- package/gui/server/projector.ts +47 -5
- package/gui/server/prompt-observation.ts +61 -0
- package/gui/server/server.ts +119 -8
- package/gui/server/session-entry-wait.ts +81 -0
- package/gui/web/dist/assets/{CatalogOverlay-D1ImSJTe.js → CatalogOverlay-Bmp6Knu7.js} +1 -1
- package/gui/web/dist/assets/index-Bxt9QpLX.css +1 -0
- package/gui/web/dist/assets/index-CZ9DHZYy.js +67 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +18 -12
- package/src/cli.ts +2 -1
- package/src/config.ts +89 -5
- package/src/infra/browser.ts +1 -1
- package/src/infra/rate-limiter.ts +10 -1
- package/src/memory/manager.ts +43 -10
- package/src/memory/storage.ts +3 -2
- package/src/memory/types.ts +4 -0
- package/src/pi/opencandle-extension.ts +273 -42
- package/src/pi/setup.ts +10 -0
- package/src/prompts/context-builder.ts +128 -17
- package/src/prompts/disclaimer.ts +1 -1
- package/src/prompts/policy-cards.ts +220 -0
- package/src/prompts/sections.ts +3 -3
- package/src/prompts/workflow-prompts.ts +172 -18
- package/src/providers/alpha-vantage.ts +24 -1
- package/src/providers/sec-edgar.ts +220 -4
- package/src/providers/web-search.ts +1 -1
- package/src/providers/yahoo-finance.ts +171 -4
- package/src/routing/classify-intent.ts +94 -3
- package/src/routing/defaults.ts +3 -3
- package/src/routing/entity-extractor.ts +164 -13
- package/src/routing/index.ts +44 -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-prompt.ts +50 -46
- package/src/routing/router-types.ts +21 -0
- package/src/routing/router.ts +511 -12
- package/src/routing/slot-resolver.ts +44 -6
- package/src/routing/turn-context.ts +111 -0
- package/src/routing/types.ts +13 -1
- package/src/runtime/answer-contracts.ts +633 -0
- package/src/runtime/artifact-contracts.ts +76 -0
- package/src/runtime/planning-evidence.ts +591 -0
- package/src/runtime/session-coordinator.ts +78 -12
- package/src/system-prompt.ts +4 -1
- package/src/tools/fundamentals/company-overview.ts +1 -1
- package/src/tools/fundamentals/comps.ts +1 -1
- package/src/tools/fundamentals/dcf.ts +1 -1
- package/src/tools/fundamentals/earnings.ts +1 -1
- package/src/tools/fundamentals/financials.ts +1 -1
- package/src/tools/fundamentals/sec-filings.ts +25 -2
- package/src/tools/index.ts +3 -0
- package/src/tools/macro/fear-greed.ts +1 -1
- package/src/tools/macro/fred-data.ts +31 -5
- package/src/tools/market/crypto-history.ts +18 -2
- package/src/tools/market/crypto-price.ts +1 -1
- package/src/tools/market/search-ticker.ts +1 -1
- package/src/tools/market/stock-history.ts +1 -1
- package/src/tools/market/stock-quote.ts +1 -1
- package/src/tools/options/greeks.ts +0 -1
- package/src/tools/options/option-chain.ts +9 -4
- package/src/tools/portfolio/correlation.ts +1 -1
- package/src/tools/portfolio/holdings-overlap.ts +123 -0
- package/src/tools/portfolio/predictions.ts +1 -1
- package/src/tools/portfolio/risk-analysis.ts +1 -1
- package/src/tools/portfolio/tracker.ts +1 -1
- package/src/tools/portfolio/watchlist.ts +10 -4
- package/src/tools/sentiment/reddit-sentiment.ts +1 -1
- package/src/tools/sentiment/sentiment-summary.ts +62 -2
- package/src/tools/sentiment/twitter-sentiment.ts +1 -1
- package/src/tools/sentiment/web-search.ts +36 -3
- package/src/tools/sentiment/web-sentiment.ts +1 -1
- package/src/tools/technical/backtest.ts +50 -29
- package/src/tools/technical/indicators.ts +1 -3
- package/src/types/options.ts +17 -0
- package/src/types/portfolio.ts +32 -0
- package/src/workflows/compare-assets.ts +38 -2
- package/src/workflows/options-screener.ts +85 -7
- package/src/workflows/portfolio-builder.ts +7 -3
- 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/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/gui/web/dist/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-CZ9DHZYy.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Bxt9QpLX.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencandle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Financial trading & investing agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/Kahtaf/OpenCandle#readme",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
},
|
|
14
14
|
"type": "module",
|
|
15
15
|
"workspaces": [
|
|
16
|
-
"gui
|
|
16
|
+
"gui/server",
|
|
17
|
+
"gui/web"
|
|
17
18
|
],
|
|
18
19
|
"bin": {
|
|
19
20
|
"opencandle": "dist/cli.js"
|
|
@@ -85,6 +86,8 @@
|
|
|
85
86
|
"prestart": "npm run check:node",
|
|
86
87
|
"start": "tsx src/cli.ts",
|
|
87
88
|
"gui:web:build": "npm --workspace @opencandle/gui-web run build",
|
|
89
|
+
"docs:site:build": "node website/build.mjs",
|
|
90
|
+
"docs:site:serve": "node website/serve.mjs",
|
|
88
91
|
"gui": "tsx gui/server/server.ts",
|
|
89
92
|
"gui:dev": "tsx gui/server/server.ts",
|
|
90
93
|
"pretest": "npm run check:node",
|
|
@@ -101,6 +104,9 @@
|
|
|
101
104
|
"test:evals": "vitest run --config vitest.config.evals.ts",
|
|
102
105
|
"eval:router-live": "tsx tests/scripts/run-live-router-eval.ts",
|
|
103
106
|
"test:evals:usually": "EVAL_TIER=usually vitest run --config vitest.config.evals.ts",
|
|
107
|
+
"test:evals:product": "tsx tests/scripts/run-product-evals.ts",
|
|
108
|
+
"test:evals:competitive": "tsx tests/scripts/run-competitive-finance-eval.ts",
|
|
109
|
+
"eval:competitive:analyze": "tsx tests/scripts/analyze-competitive-finance-report.ts",
|
|
104
110
|
"version:patch": "npm version patch --no-git-tag-version",
|
|
105
111
|
"version:minor": "npm version minor --no-git-tag-version",
|
|
106
112
|
"version:major": "npm version major --no-git-tag-version",
|
|
@@ -115,29 +121,29 @@
|
|
|
115
121
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0 <27"
|
|
116
122
|
},
|
|
117
123
|
"dependencies": {
|
|
118
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
119
|
-
"@earendil-works/pi-ai": "^0.
|
|
120
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
124
|
+
"@earendil-works/pi-agent-core": "^0.75.5",
|
|
125
|
+
"@earendil-works/pi-ai": "^0.75.5",
|
|
126
|
+
"@earendil-works/pi-coding-agent": "^0.75.5",
|
|
121
127
|
"@the-convocation/twitter-scraper": "^0.22.3",
|
|
122
128
|
"better-sqlite3": "^12.10.0",
|
|
123
129
|
"camoufox-js": "^0.10.2",
|
|
124
130
|
"duck-duck-scrape": "^2.2.7",
|
|
125
131
|
"playwright-core": "^1.60.0",
|
|
126
|
-
"tsx": "^4.22.
|
|
132
|
+
"tsx": "^4.22.3"
|
|
127
133
|
},
|
|
128
134
|
"peerDependencies": {
|
|
129
135
|
"@sinclair/typebox": "*"
|
|
130
136
|
},
|
|
131
137
|
"devDependencies": {
|
|
132
|
-
"@
|
|
133
|
-
"@mariozechner/pi-ai": "^0.73.1",
|
|
134
|
-
"@mariozechner/pi-coding-agent": "^0.73.1",
|
|
138
|
+
"@agentclientprotocol/claude-agent-acp": "^0.37.0",
|
|
135
139
|
"@sinclair/typebox": "^0.34.0",
|
|
136
140
|
"@types/better-sqlite3": "^7.6.13",
|
|
137
141
|
"@types/node": "^22.19.19",
|
|
142
|
+
"@zed-industries/codex-acp": "^0.15.0",
|
|
143
|
+
"acpx": "^0.10.0",
|
|
138
144
|
"typescript": "^6.0.3",
|
|
139
|
-
"vite": "^8.0.
|
|
140
|
-
"vitest": "^4.1.
|
|
141
|
-
"vitest-evals": "^0.
|
|
145
|
+
"vite": "^8.0.14",
|
|
146
|
+
"vitest": "^4.1.7",
|
|
147
|
+
"vitest-evals": "^0.11.0"
|
|
142
148
|
}
|
|
143
149
|
}
|
package/src/cli.ts
CHANGED
|
@@ -140,6 +140,7 @@ async function handleGuiCommand(args: string[], cwd: string): Promise<boolean> {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
async function main(): Promise<void> {
|
|
143
|
+
const rawArgs = process.argv.slice(2);
|
|
143
144
|
const { positionals } = parseArgs({ allowPositionals: true, strict: false });
|
|
144
145
|
const cwd = process.cwd();
|
|
145
146
|
const agentDir = getAgentDir();
|
|
@@ -148,7 +149,7 @@ async function main(): Promise<void> {
|
|
|
148
149
|
return;
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
if (await handlePackageCommand(
|
|
152
|
+
if (await handlePackageCommand(rawArgs, cwd, agentDir)) {
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
154
155
|
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { ensureParentDir, getConfigPath } from "./infra/opencandle-paths.js";
|
|
3
|
+
import type { PlanningBehaviorMode, TaskFamily } from "./routing/planning.js";
|
|
3
4
|
|
|
4
5
|
export interface SentimentConfig {
|
|
5
6
|
retentionDays: number;
|
|
@@ -9,6 +10,8 @@ export interface SentimentConfig {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export type RouterMode = "rules" | "llm";
|
|
13
|
+
export type ToolScopeMode = "observe" | "enforce";
|
|
14
|
+
export type PlanningMigrationStatuses = Partial<Record<TaskFamily, PlanningBehaviorMode>>;
|
|
12
15
|
|
|
13
16
|
export interface Config {
|
|
14
17
|
alphaVantageApiKey?: string;
|
|
@@ -19,11 +22,24 @@ export interface Config {
|
|
|
19
22
|
/** Enable adversarial bull/bear debate in comprehensive analysis. Default: true. */
|
|
20
23
|
debate?: boolean;
|
|
21
24
|
/**
|
|
22
|
-
* Intent-router
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
+
* Intent-router mode. `"llm"` (default) runs the LLM router ahead of prompt
|
|
26
|
+
* assembly. `"rules"` is the explicit legacy rule-router rollback path
|
|
27
|
+
* (`classifyIntent` + `extractPreferences`). Controlled by
|
|
28
|
+
* `OPENCANDLE_ROUTER_MODE`.
|
|
25
29
|
*/
|
|
26
30
|
routerMode: RouterMode;
|
|
31
|
+
/**
|
|
32
|
+
* Route-selected tool scope mode. `"observe"` (default) records selected
|
|
33
|
+
* bundles and active-tool candidates. `"enforce"` applies Pi active tools
|
|
34
|
+
* for the turn via `pi.setActiveTools`.
|
|
35
|
+
*/
|
|
36
|
+
toolScopeMode: ToolScopeMode;
|
|
37
|
+
/**
|
|
38
|
+
* Per-task planning behavior rollback/activation overrides. Controlled by
|
|
39
|
+
* `OPENCANDLE_PLANNING_MIGRATION_STATUSES`, e.g.
|
|
40
|
+
* `asset_compare=dual_run,single_asset_decision=observe_only`.
|
|
41
|
+
*/
|
|
42
|
+
planningMigrationStatuses?: PlanningMigrationStatuses;
|
|
27
43
|
sentiment?: SentimentConfig;
|
|
28
44
|
}
|
|
29
45
|
|
|
@@ -84,15 +100,81 @@ const SENTIMENT_DEFAULTS: SentimentConfig = {
|
|
|
84
100
|
divergenceThreshold: 0.4,
|
|
85
101
|
};
|
|
86
102
|
|
|
103
|
+
const PLANNING_TASK_FAMILIES = [
|
|
104
|
+
"single_asset_decision",
|
|
105
|
+
"asset_compare",
|
|
106
|
+
"portfolio_build",
|
|
107
|
+
"portfolio_review",
|
|
108
|
+
"macro_allocation_review",
|
|
109
|
+
"options_strategy",
|
|
110
|
+
"current_event_explanation",
|
|
111
|
+
"ticker_disambiguation",
|
|
112
|
+
"filing_thesis_review",
|
|
113
|
+
"sentiment_snapshot",
|
|
114
|
+
"concept_explainer",
|
|
115
|
+
"retail_finance_tradeoff",
|
|
116
|
+
"stateful_tracking_update",
|
|
117
|
+
"backtest_review",
|
|
118
|
+
"general_fallback",
|
|
119
|
+
] as const satisfies readonly TaskFamily[];
|
|
120
|
+
|
|
121
|
+
const PLANNING_BEHAVIOR_MODES = [
|
|
122
|
+
"observe_only",
|
|
123
|
+
"dual_run",
|
|
124
|
+
"replacement_active",
|
|
125
|
+
] as const satisfies readonly PlanningBehaviorMode[];
|
|
126
|
+
|
|
87
127
|
function resolveRouterMode(): RouterMode {
|
|
88
128
|
const raw = process.env.OPENCANDLE_ROUTER_MODE;
|
|
89
|
-
if (raw === undefined || raw === "") return "
|
|
129
|
+
if (raw === undefined || raw === "") return "llm";
|
|
90
130
|
if (raw === "rules" || raw === "llm") return raw;
|
|
91
131
|
throw new Error(
|
|
92
|
-
`Invalid OPENCANDLE_ROUTER_MODE="${raw}". Allowed values: "
|
|
132
|
+
`Invalid OPENCANDLE_ROUTER_MODE="${raw}". Allowed values: "llm" (default) or "rules".`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function resolveToolScopeMode(): ToolScopeMode {
|
|
137
|
+
const raw = process.env.OPENCANDLE_TOOL_SCOPE_MODE;
|
|
138
|
+
if (raw === undefined || raw === "") return "observe";
|
|
139
|
+
if (raw === "observe" || raw === "enforce") return raw;
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Invalid OPENCANDLE_TOOL_SCOPE_MODE="${raw}". Allowed values: "observe" (default) or "enforce".`,
|
|
93
142
|
);
|
|
94
143
|
}
|
|
95
144
|
|
|
145
|
+
function resolvePlanningMigrationStatuses(): PlanningMigrationStatuses | undefined {
|
|
146
|
+
const raw = process.env.OPENCANDLE_PLANNING_MIGRATION_STATUSES;
|
|
147
|
+
if (raw === undefined || raw.trim() === "") return undefined;
|
|
148
|
+
|
|
149
|
+
const statuses: PlanningMigrationStatuses = {};
|
|
150
|
+
for (const entry of raw.split(",")) {
|
|
151
|
+
const trimmed = entry.trim();
|
|
152
|
+
if (!trimmed) continue;
|
|
153
|
+
|
|
154
|
+
const parts = trimmed.split("=");
|
|
155
|
+
const taskFamily = parts[0]?.trim();
|
|
156
|
+
const behaviorMode = parts[1]?.trim();
|
|
157
|
+
if (
|
|
158
|
+
parts.length !== 2 ||
|
|
159
|
+
!isPlanningTaskFamily(taskFamily) ||
|
|
160
|
+
!isPlanningBehaviorMode(behaviorMode)
|
|
161
|
+
) {
|
|
162
|
+
throw new Error(`Invalid OPENCANDLE_PLANNING_MIGRATION_STATUSES entry "${trimmed}".`);
|
|
163
|
+
}
|
|
164
|
+
statuses[taskFamily] = behaviorMode;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return Object.keys(statuses).length > 0 ? statuses : undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isPlanningTaskFamily(value: string | undefined): value is TaskFamily {
|
|
171
|
+
return PLANNING_TASK_FAMILIES.includes(value as TaskFamily);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isPlanningBehaviorMode(value: string | undefined): value is PlanningBehaviorMode {
|
|
175
|
+
return PLANNING_BEHAVIOR_MODES.includes(value as PlanningBehaviorMode);
|
|
176
|
+
}
|
|
177
|
+
|
|
96
178
|
function resolveConfig(fileConfig: OpenCandleFileConfig): Config {
|
|
97
179
|
const debateEnv = process.env.OPENCANDLE_DEBATE;
|
|
98
180
|
const fileSentiment = fileConfig.sentiment;
|
|
@@ -105,6 +187,8 @@ function resolveConfig(fileConfig: OpenCandleFileConfig): Config {
|
|
|
105
187
|
finnhubApiKey: process.env.FINNHUB_API_KEY ?? fileConfig.providers?.finnhub?.apiKey,
|
|
106
188
|
debate: debateEnv !== undefined ? debateEnv !== "false" && debateEnv !== "0" : fileConfig.debate ?? true,
|
|
107
189
|
routerMode: resolveRouterMode(),
|
|
190
|
+
toolScopeMode: resolveToolScopeMode(),
|
|
191
|
+
planningMigrationStatuses: resolvePlanningMigrationStatuses(),
|
|
108
192
|
sentiment: {
|
|
109
193
|
retentionDays: fileSentiment?.retentionDays ?? SENTIMENT_DEFAULTS.retentionDays,
|
|
110
194
|
defaultSubreddits: fileSentiment?.defaultSubreddits ?? SENTIMENT_DEFAULTS.defaultSubreddits,
|
package/src/infra/browser.ts
CHANGED
|
@@ -65,7 +65,7 @@ export const StealthBrowser = {
|
|
|
65
65
|
* Fetch JSON from a URL using the browser's session (cookies, TLS fingerprint).
|
|
66
66
|
* Useful for APIs that block Node.js fetch but allow real browsers.
|
|
67
67
|
*/
|
|
68
|
-
async fetchJson<T>(url: string
|
|
68
|
+
async fetchJson<T>(url: string): Promise<T> {
|
|
69
69
|
return withPage(async (p) => {
|
|
70
70
|
const result = await p.evaluate(async (fetchUrl: string) => {
|
|
71
71
|
const res = await fetch(fetchUrl, { credentials: "include" });
|
|
@@ -9,6 +9,11 @@ interface Bucket {
|
|
|
9
9
|
config: BucketConfig;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export const ALPHA_VANTAGE_RATE_LIMIT = {
|
|
13
|
+
maxTokens: 5,
|
|
14
|
+
refillRate: 0.083,
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
12
17
|
export class RateLimiter {
|
|
13
18
|
private buckets = new Map<string, Bucket>();
|
|
14
19
|
|
|
@@ -53,7 +58,11 @@ export class RateLimiter {
|
|
|
53
58
|
export const rateLimiter = new RateLimiter();
|
|
54
59
|
rateLimiter.configure("yahoo", 5, 5); // 5 req/s
|
|
55
60
|
rateLimiter.configure("coingecko", 10, 0.167); // 10 req/min
|
|
56
|
-
rateLimiter.configure(
|
|
61
|
+
rateLimiter.configure(
|
|
62
|
+
"alphavantage",
|
|
63
|
+
ALPHA_VANTAGE_RATE_LIMIT.maxTokens,
|
|
64
|
+
ALPHA_VANTAGE_RATE_LIMIT.refillRate,
|
|
65
|
+
); // 5 req/min (free tier)
|
|
57
66
|
rateLimiter.configure("fred", 120, 2); // 120 req/min
|
|
58
67
|
rateLimiter.configure("twitter", 5, 0.167); // 5 req, ~10 req/min
|
|
59
68
|
rateLimiter.configure("reddit", 5, 0.167); // 5 req, ~10 req/min
|
package/src/memory/manager.ts
CHANGED
|
@@ -7,6 +7,16 @@ import {
|
|
|
7
7
|
isStale,
|
|
8
8
|
} from "./types.js";
|
|
9
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
|
+
|
|
10
20
|
/** Slot name → preference key(s) mapping for suppression. */
|
|
11
21
|
const SLOT_TO_PREF_KEYS: Record<string, string[]> = {
|
|
12
22
|
riskProfile: ["risk_profile"],
|
|
@@ -36,6 +46,14 @@ export class MemoryManager {
|
|
|
36
46
|
overriddenSlots?: string[],
|
|
37
47
|
now: Date = new Date(),
|
|
38
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 {
|
|
39
57
|
const relevantCategories = WORKFLOW_RELEVANT_CATEGORIES[workflowType] ??
|
|
40
58
|
WORKFLOW_RELEVANT_CATEGORIES["unclassified"];
|
|
41
59
|
|
|
@@ -49,18 +67,14 @@ export class MemoryManager {
|
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
const entries: MemoryEntry[] = [];
|
|
70
|
+
const filtered: FilteredMemoryEntry[] = [];
|
|
52
71
|
|
|
53
72
|
// Preferences as investor_profile entries
|
|
54
73
|
if (relevantCategories.includes("investor_profile")) {
|
|
55
74
|
const prefs = this.storage.getPreferencesByNamespace("global");
|
|
56
75
|
for (const pref of prefs) {
|
|
57
76
|
const key = String(pref.key);
|
|
58
|
-
if (suppressedKeys.has(key)) continue;
|
|
59
|
-
if (NEVER_TRUST_FROM_MEMORY.has(key)) continue;
|
|
60
|
-
|
|
61
77
|
const category = KEY_TO_CATEGORY[key] ?? "investor_profile";
|
|
62
|
-
if (!relevantCategories.includes(category)) continue;
|
|
63
|
-
|
|
64
78
|
const entry: MemoryEntry = {
|
|
65
79
|
key,
|
|
66
80
|
value: tryParseValue(String(pref.value_json ?? "")),
|
|
@@ -70,9 +84,23 @@ export class MemoryManager {
|
|
|
70
84
|
source: pref.source != null ? String(pref.source) : undefined,
|
|
71
85
|
};
|
|
72
86
|
|
|
73
|
-
if (
|
|
74
|
-
|
|
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;
|
|
75
102
|
}
|
|
103
|
+
entries.push(entry);
|
|
76
104
|
}
|
|
77
105
|
}
|
|
78
106
|
|
|
@@ -97,13 +125,18 @@ export class MemoryManager {
|
|
|
97
125
|
recordedAt,
|
|
98
126
|
};
|
|
99
127
|
|
|
100
|
-
if (
|
|
101
|
-
|
|
128
|
+
if (isStale(entry, now)) {
|
|
129
|
+
filtered.push({ entry, reason: "stale" });
|
|
130
|
+
continue;
|
|
102
131
|
}
|
|
132
|
+
entries.push(entry);
|
|
103
133
|
}
|
|
104
134
|
}
|
|
105
135
|
|
|
106
|
-
return
|
|
136
|
+
return {
|
|
137
|
+
entries: entries.slice(0, MAX_PREFERENCE_LINES + MAX_WORKFLOW_HISTORY_PER_TYPE * 4),
|
|
138
|
+
filtered,
|
|
139
|
+
};
|
|
107
140
|
}
|
|
108
141
|
|
|
109
142
|
/**
|
package/src/memory/storage.ts
CHANGED
|
@@ -28,11 +28,12 @@ interface WorkflowRunInput {
|
|
|
28
28
|
defaultsUsedJson: string;
|
|
29
29
|
outputSummary?: string;
|
|
30
30
|
/**
|
|
31
|
-
* Router route verbatim.
|
|
31
|
+
* Router route verbatim. Legacy rows contain `"workflow"` or `"fallback"`;
|
|
32
|
+
* typed-router rows may contain canonical route kinds. Defaults to
|
|
32
33
|
* `"workflow"` at the schema layer so legacy callers (rules-mode cascade)
|
|
33
34
|
* don't need to pass anything. Router-mode callers MUST pass this explicitly.
|
|
34
35
|
*/
|
|
35
|
-
turnType?:
|
|
36
|
+
turnType?: string;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
interface RecommendationInput {
|
package/src/memory/types.ts
CHANGED
|
@@ -46,6 +46,10 @@ export const WORKFLOW_RELEVANT_CATEGORIES: Record<string, MemoryCategory[]> = {
|
|
|
46
46
|
comprehensive_analysis: ["investor_profile", "workflow_history"],
|
|
47
47
|
single_asset_analysis: ["investor_profile"],
|
|
48
48
|
general_finance_qa: ["investor_profile"],
|
|
49
|
+
workflow_dispatch: ["investor_profile", "workflow_history"],
|
|
50
|
+
agent_task: ["investor_profile", "workflow_history"],
|
|
51
|
+
clarification: ["investor_profile", "workflow_history"],
|
|
52
|
+
pass_through: [],
|
|
49
53
|
unclassified: ["investor_profile"],
|
|
50
54
|
};
|
|
51
55
|
|