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
|
@@ -6,18 +6,25 @@ import {
|
|
|
6
6
|
import { buildComprehensiveAnalysisDefinition } from "../analysts/orchestrator.js";
|
|
7
7
|
import { getConfig } from "../config.js";
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
classifyWithLegacyRules,
|
|
10
10
|
createPiAiRouterClient,
|
|
11
11
|
resolveOptionsScreenerSlots,
|
|
12
12
|
resolvePortfolioSlots,
|
|
13
13
|
route as routeLlm,
|
|
14
|
+
buildResolvedTurnContext,
|
|
14
15
|
} from "../routing/index.js";
|
|
15
16
|
import type {
|
|
16
17
|
RouterInputContext,
|
|
17
18
|
RouterLlmClient,
|
|
18
19
|
RouterOutput,
|
|
19
20
|
} from "../routing/router-types.js";
|
|
20
|
-
import type {
|
|
21
|
+
import type { ResolvedTurnContext } from "../routing/turn-context.js";
|
|
22
|
+
import type {
|
|
23
|
+
CompareAssetsSlots,
|
|
24
|
+
ExtractedEntities,
|
|
25
|
+
SlotResolution,
|
|
26
|
+
SlotSource,
|
|
27
|
+
} from "../routing/types.js";
|
|
21
28
|
import { buildAssumptionsBlockFromRouter } from "../prompts/workflow-prompts.js";
|
|
22
29
|
import {
|
|
23
30
|
buildPortfolioWorkflowDefinition,
|
|
@@ -76,6 +83,8 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
76
83
|
const sessionPromptedSet = new Set<ProviderId>();
|
|
77
84
|
let hardPromptFiredInWorkflow = false;
|
|
78
85
|
const degradationAccumulator = createDegradationAccumulator();
|
|
86
|
+
let activeToolSnapshot: string[] | null = null;
|
|
87
|
+
let currentRouteToolContext: ResolvedTurnContext | null = null;
|
|
79
88
|
|
|
80
89
|
// Register tools
|
|
81
90
|
for (const tool of getOpenCandleToolDefinitions()) {
|
|
@@ -261,6 +270,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
261
270
|
msg.role === "assistant" && msg.stopReason === "stop";
|
|
262
271
|
if (isFinalAssistantTurn) {
|
|
263
272
|
pi.appendEntry("opencandle-disclaimer", { text: DISCLAIMER_TEXT });
|
|
273
|
+
restoreRouteToolScope();
|
|
264
274
|
}
|
|
265
275
|
|
|
266
276
|
if (degradationAccumulator.isEmpty()) return;
|
|
@@ -454,6 +464,29 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
454
464
|
return undefined;
|
|
455
465
|
});
|
|
456
466
|
|
|
467
|
+
pi.on("tool_call", async (event) => {
|
|
468
|
+
if (!currentRouteToolContext) return undefined;
|
|
469
|
+
const allowed = new Set(currentRouteToolContext.activeToolNames);
|
|
470
|
+
if (allowed.has(event.toolName)) return undefined;
|
|
471
|
+
|
|
472
|
+
const diagnostic = {
|
|
473
|
+
routeKind: currentRouteToolContext.routeKind,
|
|
474
|
+
workflow: currentRouteToolContext.workflow,
|
|
475
|
+
toolName: event.toolName,
|
|
476
|
+
toolBundles: currentRouteToolContext.toolBundles,
|
|
477
|
+
activeToolNames: currentRouteToolContext.activeToolNames,
|
|
478
|
+
};
|
|
479
|
+
pi.appendEntry("opencandle-tool-scope-violation", diagnostic);
|
|
480
|
+
|
|
481
|
+
if (getConfig().toolScopeMode === "enforce") {
|
|
482
|
+
return {
|
|
483
|
+
block: true,
|
|
484
|
+
reason: `Tool ${event.toolName} is outside the route-selected OpenCandle tool bundle.`,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
return undefined;
|
|
488
|
+
});
|
|
489
|
+
|
|
457
490
|
// Input handling — branches on OPENCANDLE_ROUTER_MODE.
|
|
458
491
|
pi.on("input", async (event, ctx) => {
|
|
459
492
|
if (event.source === "extension") return;
|
|
@@ -462,8 +495,8 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
462
495
|
const analysis = isAnalysisRequest(event.text);
|
|
463
496
|
if (analysis.match && analysis.symbol) {
|
|
464
497
|
const definition = buildComprehensiveAnalysisDefinition(analysis.symbol, { debate: getConfig().debate });
|
|
465
|
-
coordinator.
|
|
466
|
-
return { action: "handled" };
|
|
498
|
+
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
499
|
+
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
467
500
|
}
|
|
468
501
|
|
|
469
502
|
const mode = getConfig().routerMode;
|
|
@@ -473,25 +506,25 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
473
506
|
// the workflow's queued prompts; tell Pi not to also forward it.
|
|
474
507
|
// Fallback path (no dispatch) → let Pi pass the user turn through to the
|
|
475
508
|
// main agent, which will run under the router-supplied fallback context.
|
|
476
|
-
return dispatched
|
|
509
|
+
return dispatched || undefined;
|
|
477
510
|
}
|
|
478
511
|
|
|
479
|
-
// --- rules mode (
|
|
512
|
+
// --- explicit legacy rules mode (`OPENCANDLE_ROUTER_MODE=rules`) ---
|
|
480
513
|
// Extract and persist user preferences (legacy regex path)
|
|
481
514
|
coordinator.extractAndStorePreferences(event.text);
|
|
482
515
|
const storage = coordinator.getStorage();
|
|
483
516
|
const workflowPrefs = storage?.getWorkflowPreferences("global") ?? {};
|
|
484
517
|
|
|
485
518
|
// Classify intent
|
|
486
|
-
const classification =
|
|
519
|
+
const classification = classifyWithLegacyRules(event.text);
|
|
487
520
|
|
|
488
521
|
if (classification.workflow === "portfolio_builder") {
|
|
489
522
|
const resolution = resolvePortfolioSlots(classification.entities, workflowPrefs);
|
|
490
523
|
coordinator.recordWorkflowRun("portfolio_builder", classification.entities, resolution.resolved, resolution.defaultsUsed);
|
|
491
524
|
pi.appendEntry("opencandle-workflow", { workflow: "portfolio_builder", entities: classification.entities, resolved: resolution.resolved });
|
|
492
525
|
const definition = buildPortfolioWorkflowDefinition(resolution);
|
|
493
|
-
coordinator.
|
|
494
|
-
return { action: "handled" };
|
|
526
|
+
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
527
|
+
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
495
528
|
}
|
|
496
529
|
|
|
497
530
|
if (classification.workflow === "options_screener") {
|
|
@@ -500,23 +533,31 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
500
533
|
coordinator.recordWorkflowRun("options_screener", classification.entities, resolution.resolved, resolution.defaultsUsed);
|
|
501
534
|
pi.appendEntry("opencandle-workflow", { workflow: "options_screener", entities: classification.entities, resolved: resolution.resolved });
|
|
502
535
|
const definition = buildOptionsScreenerWorkflowDefinition(resolution);
|
|
503
|
-
coordinator.
|
|
504
|
-
return { action: "handled" };
|
|
536
|
+
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
537
|
+
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
505
538
|
}
|
|
506
539
|
}
|
|
507
540
|
|
|
508
541
|
if (classification.workflow === "compare_assets" && classification.entities.symbols.length >= 2) {
|
|
509
542
|
const resolution: SlotResolution<CompareAssetsSlots> = {
|
|
510
|
-
resolved: {
|
|
511
|
-
|
|
543
|
+
resolved: {
|
|
544
|
+
symbols: classification.entities.symbols,
|
|
545
|
+
metrics: classification.entities.compareMetrics,
|
|
546
|
+
timeHorizon: classification.entities.timeHorizon,
|
|
547
|
+
},
|
|
548
|
+
sources: {
|
|
549
|
+
symbols: "user",
|
|
550
|
+
...(classification.entities.timeHorizon ? { timeHorizon: "user" as const } : {}),
|
|
551
|
+
...(classification.entities.compareMetrics ? { metrics: "user" as const } : {}),
|
|
552
|
+
},
|
|
512
553
|
defaultsUsed: [],
|
|
513
554
|
missingRequired: [],
|
|
514
555
|
};
|
|
515
556
|
coordinator.recordWorkflowRun("compare_assets", classification.entities, resolution.resolved, resolution.defaultsUsed);
|
|
516
557
|
pi.appendEntry("opencandle-workflow", { workflow: "compare_assets", symbols: classification.entities.symbols });
|
|
517
558
|
const definition = buildCompareAssetsWorkflowDefinition(resolution);
|
|
518
|
-
coordinator.
|
|
519
|
-
return { action: "handled" };
|
|
559
|
+
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
560
|
+
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
520
561
|
}
|
|
521
562
|
});
|
|
522
563
|
|
|
@@ -530,7 +571,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
530
571
|
async function handleLlmRouterTurn(
|
|
531
572
|
text: string,
|
|
532
573
|
ctx: Parameters<Parameters<ExtensionAPI["on"]>[1]>[1],
|
|
533
|
-
): Promise<
|
|
574
|
+
): Promise<{ action: "transform"; text: string } | false> {
|
|
534
575
|
const storage = coordinator.getStorage();
|
|
535
576
|
const { profileSnapshot, recentWorkflowRuns, priorTurns } =
|
|
536
577
|
coordinator.buildRouterContextBase(ctx.sessionManager);
|
|
@@ -563,7 +604,33 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
563
604
|
return false;
|
|
564
605
|
}
|
|
565
606
|
|
|
607
|
+
const availableToolNames = safeGetAllToolNames();
|
|
608
|
+
const memory = coordinator.retrieveMemoryForRoute(
|
|
609
|
+
output.routeKind,
|
|
610
|
+
output.workflow,
|
|
611
|
+
Object.keys(output.slots),
|
|
612
|
+
);
|
|
613
|
+
const resolvedTurnContext = buildResolvedTurnContext(input, output, {
|
|
614
|
+
availableToolNames,
|
|
615
|
+
memoryEntries: memory.entries,
|
|
616
|
+
filteredMemory: memory.filtered.map(({ entry, reason }) => ({
|
|
617
|
+
category: entry.category,
|
|
618
|
+
key: entry.key,
|
|
619
|
+
source: entry.source,
|
|
620
|
+
recordedAt: entry.recordedAt,
|
|
621
|
+
confidence: entry.confidence,
|
|
622
|
+
filtered: true,
|
|
623
|
+
filterReason: reason,
|
|
624
|
+
})),
|
|
625
|
+
planning: {
|
|
626
|
+
migrationStatuses: getConfig().planningMigrationStatuses,
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
|
|
566
630
|
pi.appendEntry("opencandle-router", { output });
|
|
631
|
+
pi.appendEntry("opencandle-route-context", resolvedTurnContext);
|
|
632
|
+
coordinator.setPendingResolvedTurnContext(resolvedTurnContext);
|
|
633
|
+
applyRouteToolScope(resolvedTurnContext);
|
|
567
634
|
|
|
568
635
|
// Preference writes: HIGH-confidence only. Medium/low are logged for
|
|
569
636
|
// observability even when no storage is available.
|
|
@@ -585,10 +652,14 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
585
652
|
}
|
|
586
653
|
|
|
587
654
|
// Workflow dispatch for recognised workflows.
|
|
588
|
-
if (output.
|
|
655
|
+
if (output.routeKind === "workflow_dispatch" && output.workflow) {
|
|
589
656
|
return dispatchRouterWorkflow(output, ctx);
|
|
590
657
|
}
|
|
591
658
|
|
|
659
|
+
if (output.routeKind === "pass_through") {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
|
|
592
663
|
// Fallback: record the turn and stash the fallback context for the
|
|
593
664
|
// upcoming `before_agent_start` hook to render into the system prompt.
|
|
594
665
|
coordinator.recordWorkflowRun(
|
|
@@ -596,7 +667,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
596
667
|
output.entities,
|
|
597
668
|
Object.fromEntries(Object.entries(output.slots).map(([k, v]) => [k, v.value])),
|
|
598
669
|
[],
|
|
599
|
-
|
|
670
|
+
output.routeKind,
|
|
600
671
|
);
|
|
601
672
|
|
|
602
673
|
const assumptionsBlock = buildAssumptionsBlockFromRouter(output.slots);
|
|
@@ -605,6 +676,7 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
605
676
|
missingRequired: output.missing_required,
|
|
606
677
|
extraContext: output.entities.symbols.length > 0
|
|
607
678
|
? `Router-extracted symbols: ${output.entities.symbols.join(", ")}.`
|
|
679
|
+
+ ` Route kind: ${output.routeKind}. Tool bundles: ${output.tool_bundles.join(", ") || "(none)"}.`
|
|
608
680
|
: undefined,
|
|
609
681
|
});
|
|
610
682
|
return false;
|
|
@@ -613,73 +685,88 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
613
685
|
function dispatchRouterWorkflow(
|
|
614
686
|
output: RouterOutput,
|
|
615
687
|
ctx: Parameters<Parameters<ExtensionAPI["on"]>[1]>[1],
|
|
616
|
-
):
|
|
688
|
+
): { action: "transform"; text: string } | false {
|
|
617
689
|
const workflow = output.workflow!;
|
|
618
690
|
const storage = coordinator.getStorage();
|
|
619
691
|
const workflowPrefs = storage?.getWorkflowPreferences("global") ?? {};
|
|
692
|
+
const entities = mergeRouterSlotsIntoEntities(output);
|
|
620
693
|
|
|
621
694
|
if (workflow === "portfolio_builder") {
|
|
622
|
-
const resolution =
|
|
695
|
+
const resolution = withRouterSlotSources(
|
|
696
|
+
resolvePortfolioSlots(entities, workflowPrefs),
|
|
697
|
+
output,
|
|
698
|
+
);
|
|
623
699
|
coordinator.recordWorkflowRun(
|
|
624
700
|
"portfolio_builder",
|
|
625
|
-
|
|
701
|
+
entities,
|
|
626
702
|
resolution.resolved,
|
|
627
703
|
resolution.defaultsUsed,
|
|
628
|
-
|
|
704
|
+
output.routeKind,
|
|
629
705
|
);
|
|
630
706
|
pi.appendEntry("opencandle-workflow", {
|
|
631
707
|
workflow: "portfolio_builder",
|
|
632
|
-
entities
|
|
708
|
+
entities,
|
|
633
709
|
resolved: resolution.resolved,
|
|
634
710
|
});
|
|
635
711
|
const definition = buildPortfolioWorkflowDefinition(resolution);
|
|
636
|
-
coordinator.
|
|
637
|
-
return
|
|
712
|
+
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
713
|
+
return prompt ? { action: "transform", text: prompt } : false;
|
|
638
714
|
}
|
|
639
715
|
if (workflow === "options_screener") {
|
|
640
|
-
const resolution =
|
|
716
|
+
const resolution = withRouterSlotSources(
|
|
717
|
+
resolveOptionsScreenerSlots(entities, workflowPrefs),
|
|
718
|
+
output,
|
|
719
|
+
);
|
|
641
720
|
// Router may emit missing_required; main agent handles via ask_user.
|
|
642
721
|
// Still dispatch the workflow when symbol is present.
|
|
643
722
|
if (resolution.missingRequired.length === 0) {
|
|
644
723
|
coordinator.recordWorkflowRun(
|
|
645
724
|
"options_screener",
|
|
646
|
-
|
|
725
|
+
entities,
|
|
647
726
|
resolution.resolved,
|
|
648
727
|
resolution.defaultsUsed,
|
|
649
|
-
|
|
728
|
+
output.routeKind,
|
|
650
729
|
);
|
|
651
730
|
pi.appendEntry("opencandle-workflow", {
|
|
652
731
|
workflow: "options_screener",
|
|
653
|
-
entities
|
|
732
|
+
entities,
|
|
654
733
|
resolved: resolution.resolved,
|
|
655
734
|
});
|
|
656
735
|
const definition = buildOptionsScreenerWorkflowDefinition(resolution);
|
|
657
|
-
coordinator.
|
|
658
|
-
return
|
|
736
|
+
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
737
|
+
return prompt ? { action: "transform", text: prompt } : false;
|
|
659
738
|
}
|
|
660
739
|
// Missing required symbol — treat as fallback with ask_user directive.
|
|
661
740
|
}
|
|
662
|
-
if (workflow === "compare_assets" &&
|
|
741
|
+
if (workflow === "compare_assets" && entities.symbols.length >= 2) {
|
|
663
742
|
const resolution: SlotResolution<CompareAssetsSlots> = {
|
|
664
|
-
resolved: {
|
|
665
|
-
|
|
743
|
+
resolved: {
|
|
744
|
+
symbols: entities.symbols,
|
|
745
|
+
metrics: entities.compareMetrics,
|
|
746
|
+
timeHorizon: entities.timeHorizon,
|
|
747
|
+
},
|
|
748
|
+
sources: {
|
|
749
|
+
symbols: sourceForRouterSlot(output, "symbols", "user"),
|
|
750
|
+
...(entities.timeHorizon ? { timeHorizon: "user" as const } : {}),
|
|
751
|
+
...(entities.compareMetrics ? { metrics: "user" as const } : {}),
|
|
752
|
+
},
|
|
666
753
|
defaultsUsed: [],
|
|
667
754
|
missingRequired: [],
|
|
668
755
|
};
|
|
669
756
|
coordinator.recordWorkflowRun(
|
|
670
757
|
"compare_assets",
|
|
671
|
-
|
|
758
|
+
entities,
|
|
672
759
|
resolution.resolved,
|
|
673
760
|
[],
|
|
674
|
-
|
|
761
|
+
output.routeKind,
|
|
675
762
|
);
|
|
676
763
|
pi.appendEntry("opencandle-workflow", {
|
|
677
764
|
workflow: "compare_assets",
|
|
678
|
-
symbols:
|
|
765
|
+
symbols: entities.symbols,
|
|
679
766
|
});
|
|
680
767
|
const definition = buildCompareAssetsWorkflowDefinition(resolution);
|
|
681
|
-
coordinator.
|
|
682
|
-
return
|
|
768
|
+
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
769
|
+
return prompt ? { action: "transform", text: prompt } : false;
|
|
683
770
|
}
|
|
684
771
|
|
|
685
772
|
// single_asset_analysis / watchlist / general_qa + any workflow with
|
|
@@ -690,17 +777,155 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
690
777
|
output.entities,
|
|
691
778
|
Object.fromEntries(Object.entries(output.slots).map(([k, v]) => [k, v.value])),
|
|
692
779
|
[],
|
|
693
|
-
|
|
780
|
+
output.routeKind,
|
|
694
781
|
);
|
|
695
782
|
const assumptionsBlock = buildAssumptionsBlockFromRouter(output.slots);
|
|
696
783
|
coordinator.setPendingFallbackContext({
|
|
697
784
|
assumptionsBlock,
|
|
698
785
|
missingRequired: output.missing_required,
|
|
699
|
-
extraContext: `Router classified as ${workflow} but declined to dispatch. Symbols: ${
|
|
786
|
+
extraContext: `Router classified as ${workflow} but declined to dispatch. Symbols: ${entities.symbols.join(", ") || "(none)"}.`,
|
|
700
787
|
});
|
|
701
788
|
return false;
|
|
702
789
|
}
|
|
703
790
|
|
|
791
|
+
function mergeRouterSlotsIntoEntities(output: RouterOutput): ExtractedEntities {
|
|
792
|
+
const entities: ExtractedEntities = {
|
|
793
|
+
...output.entities,
|
|
794
|
+
symbols: output.entities.symbols,
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
if (entities.budget === undefined && typeof output.slots.budget?.value === "number") {
|
|
798
|
+
entities.budget = output.slots.budget.value;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const slotSymbols = symbolsFromRouterSlots(output);
|
|
802
|
+
if (slotSymbols.length > 0 && slotSymbols.length > entities.symbols.length) {
|
|
803
|
+
entities.symbols = mergeSymbols(slotSymbols, entities.symbols);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return entities;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function withRouterSlotSources<T extends object>(
|
|
810
|
+
resolution: SlotResolution<T>,
|
|
811
|
+
output: RouterOutput,
|
|
812
|
+
): SlotResolution<T> {
|
|
813
|
+
const sources: Record<string, SlotSource | undefined> = { ...resolution.sources };
|
|
814
|
+
if (output.entities.budget === undefined && output.slots.budget) {
|
|
815
|
+
sources.budget = output.slots.budget.source;
|
|
816
|
+
}
|
|
817
|
+
if (output.entities.symbols.length === 0 && output.slots.symbol) {
|
|
818
|
+
sources.symbol = output.slots.symbol.source;
|
|
819
|
+
}
|
|
820
|
+
if (output.entities.symbols.length < 2 && output.slots.symbols) {
|
|
821
|
+
sources.symbols = output.slots.symbols.source;
|
|
822
|
+
}
|
|
823
|
+
return { ...resolution, sources: sources as SlotResolution<T>["sources"] };
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function sourceForRouterSlot(
|
|
827
|
+
output: RouterOutput,
|
|
828
|
+
slotName: "symbol" | "symbols" | "budget",
|
|
829
|
+
fallback: SlotSource,
|
|
830
|
+
): SlotSource {
|
|
831
|
+
return output.slots[slotName]?.source ?? fallback;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function symbolsFromRouterSlots(output: RouterOutput): string[] {
|
|
835
|
+
const symbols: string[] = [];
|
|
836
|
+
const symbol = output.slots.symbol?.value;
|
|
837
|
+
if (typeof symbol === "string" && symbol.trim() !== "") {
|
|
838
|
+
symbols.push(symbol.toUpperCase());
|
|
839
|
+
}
|
|
840
|
+
const symbolList = output.slots.symbols?.value;
|
|
841
|
+
if (Array.isArray(symbolList)) {
|
|
842
|
+
for (const value of symbolList) {
|
|
843
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
844
|
+
symbols.push(value.toUpperCase());
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return symbols;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function mergeSymbols(primary: string[], secondary: string[]): string[] {
|
|
852
|
+
const merged: string[] = [];
|
|
853
|
+
for (const symbol of [...primary, ...secondary]) {
|
|
854
|
+
if (!merged.includes(symbol)) merged.push(symbol);
|
|
855
|
+
}
|
|
856
|
+
return merged;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function safeGetAllToolNames(): string[] {
|
|
860
|
+
try {
|
|
861
|
+
return pi.getAllTools().map((tool) => tool.name);
|
|
862
|
+
} catch {
|
|
863
|
+
return [];
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function applyRouteToolScope(context: ResolvedTurnContext): void {
|
|
868
|
+
const mode = getConfig().toolScopeMode;
|
|
869
|
+
currentRouteToolContext = context;
|
|
870
|
+
pi.appendEntry("opencandle-tool-scope", {
|
|
871
|
+
mode,
|
|
872
|
+
routeKind: context.routeKind,
|
|
873
|
+
workflow: context.workflow,
|
|
874
|
+
toolBundles: context.toolBundles,
|
|
875
|
+
activeToolNames: context.activeToolNames,
|
|
876
|
+
enforced: false,
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
if (mode !== "enforce") return;
|
|
880
|
+
if (context.activeToolNames.length === 0) return;
|
|
881
|
+
|
|
882
|
+
try {
|
|
883
|
+
if (activeToolSnapshot === null) {
|
|
884
|
+
activeToolSnapshot = pi.getActiveTools();
|
|
885
|
+
}
|
|
886
|
+
pi.setActiveTools(context.activeToolNames);
|
|
887
|
+
pi.appendEntry("opencandle-tool-scope", {
|
|
888
|
+
mode,
|
|
889
|
+
routeKind: context.routeKind,
|
|
890
|
+
workflow: context.workflow,
|
|
891
|
+
toolBundles: context.toolBundles,
|
|
892
|
+
activeToolNames: context.activeToolNames,
|
|
893
|
+
enforced: true,
|
|
894
|
+
});
|
|
895
|
+
} catch (err) {
|
|
896
|
+
pi.appendEntry("opencandle-tool-scope", {
|
|
897
|
+
mode,
|
|
898
|
+
routeKind: context.routeKind,
|
|
899
|
+
workflow: context.workflow,
|
|
900
|
+
toolBundles: context.toolBundles,
|
|
901
|
+
activeToolNames: context.activeToolNames,
|
|
902
|
+
enforced: false,
|
|
903
|
+
diagnostic: err instanceof Error ? err.message : String(err),
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function restoreRouteToolScope(): void {
|
|
909
|
+
currentRouteToolContext = null;
|
|
910
|
+
if (activeToolSnapshot === null) return;
|
|
911
|
+
try {
|
|
912
|
+
pi.setActiveTools(activeToolSnapshot);
|
|
913
|
+
pi.appendEntry("opencandle-tool-scope", {
|
|
914
|
+
mode: getConfig().toolScopeMode,
|
|
915
|
+
restored: true,
|
|
916
|
+
activeToolNames: activeToolSnapshot,
|
|
917
|
+
});
|
|
918
|
+
} catch (err) {
|
|
919
|
+
pi.appendEntry("opencandle-tool-scope", {
|
|
920
|
+
mode: getConfig().toolScopeMode,
|
|
921
|
+
restored: false,
|
|
922
|
+
diagnostic: err instanceof Error ? err.message : String(err),
|
|
923
|
+
});
|
|
924
|
+
} finally {
|
|
925
|
+
activeToolSnapshot = null;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
704
929
|
function resolveRouterLlmClient(
|
|
705
930
|
ctx: Parameters<Parameters<ExtensionAPI["on"]>[1]>[1],
|
|
706
931
|
): RouterLlmClient | null {
|
|
@@ -717,8 +942,14 @@ export default function openCandleExtension(pi: ExtensionAPI, options?: OpenCand
|
|
|
717
942
|
// is pending (router-mode fallback turns), inject it into the prompt.
|
|
718
943
|
pi.on("before_agent_start", async (event) => {
|
|
719
944
|
const fallbackContext = coordinator.consumePendingFallbackContext() ?? undefined;
|
|
945
|
+
const resolvedTurnContext = coordinator.consumePendingResolvedTurnContext() ?? undefined;
|
|
720
946
|
return {
|
|
721
|
-
systemPrompt: coordinator.buildSystemPrompt(
|
|
947
|
+
systemPrompt: coordinator.buildSystemPrompt(
|
|
948
|
+
event.systemPrompt,
|
|
949
|
+
undefined,
|
|
950
|
+
fallbackContext,
|
|
951
|
+
resolvedTurnContext,
|
|
952
|
+
),
|
|
722
953
|
};
|
|
723
954
|
});
|
|
724
955
|
}
|
package/src/pi/setup.ts
CHANGED
|
@@ -158,8 +158,18 @@ async function runLoginDialog(ctx: ExtensionContext, providerId: string): Promis
|
|
|
158
158
|
dialog.showWaiting("Waiting for browser authentication...");
|
|
159
159
|
}
|
|
160
160
|
},
|
|
161
|
+
onDeviceCode: (info) => {
|
|
162
|
+
dialog.showDeviceCode(info);
|
|
163
|
+
dialog.showWaiting("Waiting for authentication...");
|
|
164
|
+
},
|
|
161
165
|
onPrompt: async (prompt) => dialog.showPrompt(prompt.message, prompt.placeholder),
|
|
162
166
|
onProgress: (message) => dialog.showProgress(message),
|
|
167
|
+
onSelect: async (prompt) => {
|
|
168
|
+
const options = prompt.options.map((option, index) => `${index + 1}. ${option.label}`).join("\n");
|
|
169
|
+
const answer = await dialog.showPrompt(`${prompt.message}\n\n${options}`, "Enter a number");
|
|
170
|
+
const selectedIndex = Number.parseInt(answer.trim(), 10) - 1;
|
|
171
|
+
return prompt.options[selectedIndex]?.id;
|
|
172
|
+
},
|
|
163
173
|
onManualCodeInput: () => manualCodePromise,
|
|
164
174
|
signal: dialog.signal,
|
|
165
175
|
})
|