opencandle 0.11.0 → 0.11.1
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 +5 -4
- package/dist/analysts/orchestrator.d.ts +2 -0
- package/dist/analysts/orchestrator.js +19 -0
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/doctor/report.js +1 -1
- package/dist/doctor/report.js.map +1 -1
- package/dist/infra/cache.d.ts +6 -0
- package/dist/infra/cache.js +16 -6
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/freshness.d.ts +21 -0
- package/dist/infra/freshness.js +119 -0
- package/dist/infra/freshness.js.map +1 -0
- package/dist/infra/market-calendar.d.ts +14 -0
- package/dist/infra/market-calendar.js +137 -0
- package/dist/infra/market-calendar.js.map +1 -0
- package/dist/infra/rate-limiter.js +1 -0
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/market-state/summaries.d.ts +8 -0
- package/dist/market-state/summaries.js +70 -0
- package/dist/market-state/summaries.js.map +1 -0
- package/dist/onboarding/providers.d.ts +25 -1
- package/dist/onboarding/providers.js +26 -0
- package/dist/onboarding/providers.js.map +1 -1
- package/dist/pi/opencandle-extension.js +44 -5
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/providers/alpha-vantage.js +6 -0
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/coingecko.js +1 -0
- package/dist/providers/coingecko.js.map +1 -1
- package/dist/providers/polymarket.d.ts +2 -0
- package/dist/providers/polymarket.js +163 -0
- package/dist/providers/polymarket.js.map +1 -0
- package/dist/providers/wrap-provider.js +5 -3
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +3 -0
- package/dist/providers/yahoo-finance.js +206 -10
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/entity-extractor.js +16 -0
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/route-manifest.js +1 -1
- package/dist/routing/route-manifest.js.map +1 -1
- package/dist/routing/router-llm-client.js +18 -3
- package/dist/routing/router-llm-client.js.map +1 -1
- package/dist/routing/router.d.ts +1 -1
- package/dist/routing/router.js +274 -5
- package/dist/routing/router.js.map +1 -1
- package/dist/routing/slot-resolver.d.ts +1 -0
- package/dist/routing/slot-resolver.js +1 -1
- package/dist/routing/slot-resolver.js.map +1 -1
- package/dist/runtime/evidence.d.ts +1 -0
- package/dist/runtime/evidence.js.map +1 -1
- package/dist/runtime/numeric-claims.d.ts +23 -0
- package/dist/runtime/numeric-claims.js +100 -0
- package/dist/runtime/numeric-claims.js.map +1 -0
- package/dist/runtime/planning-evidence.js +1 -76
- package/dist/runtime/planning-evidence.js.map +1 -1
- package/dist/runtime/prompt-step.d.ts +10 -1
- package/dist/runtime/prompt-step.js +116 -2
- package/dist/runtime/prompt-step.js.map +1 -1
- package/dist/runtime/session-coordinator.d.ts +11 -1
- package/dist/runtime/session-coordinator.js +352 -49
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/validation.d.ts +3 -1
- package/dist/runtime/workflow-events.d.ts +1 -1
- package/dist/runtime/workflow-events.js.map +1 -1
- package/dist/runtime/workflow-types.d.ts +3 -0
- package/dist/runtime/workflow-types.js.map +1 -1
- package/dist/tools/fundamentals/comps.d.ts +1 -0
- package/dist/tools/fundamentals/comps.js +20 -9
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +183 -112
- package/dist/tools/fundamentals/dcf.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/event-probabilities.d.ts +8 -0
- package/dist/tools/macro/event-probabilities.js +121 -0
- package/dist/tools/macro/event-probabilities.js.map +1 -0
- package/dist/tools/market/crypto-price.d.ts +4 -1
- package/dist/tools/market/crypto-price.js +10 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/screen-stocks.js +14 -3
- package/dist/tools/market/screen-stocks.js.map +1 -1
- package/dist/tools/market/stock-quote.d.ts +4 -1
- package/dist/tools/market/stock-quote.js +9 -4
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/option-chain.d.ts +4 -1
- package/dist/tools/options/option-chain.js +12 -1
- package/dist/tools/options/option-chain.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/technical/indicators.js +1 -1
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/fundamentals.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/market.d.ts +2 -0
- package/dist/types/options.d.ts +1 -0
- package/dist/types/prediction-markets.d.ts +13 -0
- package/dist/types/prediction-markets.js +2 -0
- package/dist/types/prediction-markets.js.map +1 -0
- package/gui/server/chat-event-adapter.ts +89 -5
- package/gui/server/http-routes.ts +285 -37
- package/gui/server/invoke-tool.ts +14 -13
- package/gui/server/live-chat-event-adapter.ts +37 -2
- package/gui/server/market-state-api.ts +63 -5
- package/gui/server/projector.ts +132 -3
- package/gui/server/session-actions.ts +32 -40
- package/gui/server/session-entry-wait.ts +22 -3
- package/gui/server/ws-hub.ts +16 -4
- package/gui/shared/chat-events.ts +10 -1
- package/gui/shared/event-reducer.ts +1 -0
- package/gui/web/dist/assets/CatalogOverlay-DZ1niyQm.js +1 -0
- package/gui/web/dist/assets/index-D4F9AJnn.css +2 -0
- package/gui/web/dist/assets/index-DtqMMBTr.js +65 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +8 -13
- package/src/analysts/orchestrator.ts +20 -0
- package/src/doctor/report.ts +1 -1
- package/src/infra/cache.ts +24 -11
- package/src/infra/freshness.ts +165 -0
- package/src/infra/market-calendar.ts +193 -0
- package/src/infra/rate-limiter.ts +1 -0
- package/src/market-state/summaries.ts +75 -0
- package/src/onboarding/providers.ts +29 -1
- package/src/pi/opencandle-extension.ts +55 -5
- package/src/providers/alpha-vantage.ts +6 -0
- package/src/providers/coingecko.ts +2 -0
- package/src/providers/polymarket.ts +214 -0
- package/src/providers/wrap-provider.ts +5 -3
- package/src/providers/yahoo-finance.ts +339 -27
- package/src/routing/entity-extractor.ts +17 -0
- package/src/routing/route-manifest.ts +1 -1
- package/src/routing/router-llm-client.ts +31 -19
- package/src/routing/router.ts +340 -5
- package/src/routing/slot-resolver.ts +1 -1
- package/src/runtime/evidence.ts +1 -0
- package/src/runtime/numeric-claims.ts +108 -0
- package/src/runtime/planning-evidence.ts +6 -91
- package/src/runtime/prompt-step.ts +127 -2
- package/src/runtime/session-coordinator.ts +462 -57
- package/src/runtime/validation.ts +1 -1
- package/src/runtime/workflow-events.ts +2 -0
- package/src/runtime/workflow-types.ts +3 -0
- package/src/tools/fundamentals/comps.ts +25 -14
- package/src/tools/fundamentals/dcf.ts +204 -124
- package/src/tools/index.ts +3 -0
- package/src/tools/macro/event-probabilities.ts +141 -0
- package/src/tools/market/crypto-price.ts +14 -2
- package/src/tools/market/screen-stocks.ts +14 -3
- package/src/tools/market/stock-quote.ts +49 -43
- package/src/tools/options/option-chain.ts +16 -2
- package/src/tools/portfolio/risk-analysis.ts +1 -1
- package/src/tools/technical/indicators.ts +1 -1
- package/src/types/fundamentals.ts +1 -0
- package/src/types/index.ts +1 -0
- package/src/types/market.ts +2 -0
- package/src/types/options.ts +1 -0
- package/src/types/prediction-markets.ts +13 -0
- package/gui/server/chat-run-session.ts +0 -16
- package/gui/web/dist/assets/CatalogOverlay-ChRTKNlf.js +0 -1
- package/gui/web/dist/assets/index-BzyqyVnd.css +0 -2
- package/gui/web/dist/assets/index-DvMjkOP9.js +0 -65
|
@@ -15,13 +15,95 @@ export function promptStep(stepType, description, prompt, options = {}) {
|
|
|
15
15
|
* Create a StepOutput for a prompt-based step (no structured evidence yet).
|
|
16
16
|
* Evidence will be captured separately via tool call hooks.
|
|
17
17
|
*/
|
|
18
|
-
export function promptStepOutput(stepIndex, stepType) {
|
|
18
|
+
export function promptStepOutput(stepIndex, stepType, options = {}) {
|
|
19
19
|
return {
|
|
20
20
|
stepIndex,
|
|
21
21
|
stepType,
|
|
22
|
-
evidence: [],
|
|
22
|
+
evidence: options.evidence ?? [],
|
|
23
|
+
...(options.rawText ? { rawText: options.rawText } : {}),
|
|
23
24
|
};
|
|
24
25
|
}
|
|
26
|
+
/** Capture compact evidence records from session entries produced by one step. */
|
|
27
|
+
export function captureToolEvidence(entries) {
|
|
28
|
+
const calls = new Map();
|
|
29
|
+
const evidence = [];
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.type !== "message")
|
|
32
|
+
continue;
|
|
33
|
+
const message = entry.message;
|
|
34
|
+
if (message.role === "assistant") {
|
|
35
|
+
for (const part of Array.isArray(message.content) ? message.content : []) {
|
|
36
|
+
const record = asRecord(part);
|
|
37
|
+
if (record.type !== "toolCall")
|
|
38
|
+
continue;
|
|
39
|
+
const id = stringValue(record.id);
|
|
40
|
+
const tool = stringValue(record.name);
|
|
41
|
+
if (!id || !tool)
|
|
42
|
+
continue;
|
|
43
|
+
calls.set(id, {
|
|
44
|
+
tool,
|
|
45
|
+
args: record.arguments ?? record.args ?? {},
|
|
46
|
+
startedAt: entry.timestamp,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (message.role !== "toolResult")
|
|
52
|
+
continue;
|
|
53
|
+
const toolCallId = stringValue(message.toolCallId);
|
|
54
|
+
const pending = toolCallId ? calls.get(toolCallId) : undefined;
|
|
55
|
+
const tool = pending?.tool ?? stringValue(message.toolName);
|
|
56
|
+
if (!tool)
|
|
57
|
+
continue;
|
|
58
|
+
const resultValue = message.details ?? message.content ?? "";
|
|
59
|
+
const freshness = extractFreshness(resultValue);
|
|
60
|
+
const serializedResult = serialize(resultValue);
|
|
61
|
+
evidence.push({
|
|
62
|
+
label: `tool:${tool}`,
|
|
63
|
+
value: {
|
|
64
|
+
tool,
|
|
65
|
+
args: truncate(serialize(pending?.args ?? {}), 500),
|
|
66
|
+
...(freshness ? { freshness } : {}),
|
|
67
|
+
resultDigest: {
|
|
68
|
+
preview: truncate(serializedResult, 500),
|
|
69
|
+
totalLength: serializedResult.length,
|
|
70
|
+
},
|
|
71
|
+
startedAt: pending?.startedAt ?? entry.timestamp,
|
|
72
|
+
completedAt: entry.timestamp,
|
|
73
|
+
},
|
|
74
|
+
provenance: {
|
|
75
|
+
source: "computed",
|
|
76
|
+
timestamp: freshness?.providerDataAt ?? freshness?.fetchedAt ?? entry.timestamp,
|
|
77
|
+
provider: tool,
|
|
78
|
+
confidence: message.isError === true ? 0.5 : undefined,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return evidence;
|
|
83
|
+
}
|
|
84
|
+
/** Extract assistant text emitted during a step, preserving final response order. */
|
|
85
|
+
export function extractAssistantText(entries) {
|
|
86
|
+
return entries
|
|
87
|
+
.filter((entry) => entry.type === "message")
|
|
88
|
+
.flatMap((entry) => {
|
|
89
|
+
const message = entry
|
|
90
|
+
.message;
|
|
91
|
+
if (message.role !== "assistant")
|
|
92
|
+
return [];
|
|
93
|
+
const content = message.content;
|
|
94
|
+
if (typeof content === "string")
|
|
95
|
+
return [content];
|
|
96
|
+
if (!Array.isArray(content))
|
|
97
|
+
return [];
|
|
98
|
+
return content
|
|
99
|
+
.map((part) => asRecord(part))
|
|
100
|
+
.filter((part) => part.type === "text")
|
|
101
|
+
.map((part) => stringValue(part.text) ?? "")
|
|
102
|
+
.filter(Boolean);
|
|
103
|
+
})
|
|
104
|
+
.join("\n")
|
|
105
|
+
.trim();
|
|
106
|
+
}
|
|
25
107
|
/**
|
|
26
108
|
* Extract just the WorkflowStep metadata from PromptStep definitions
|
|
27
109
|
* (dropping the prompt field) for passing to WorkflowRunner.
|
|
@@ -29,4 +111,36 @@ export function promptStepOutput(stepIndex, stepType) {
|
|
|
29
111
|
export function toStepDefinitions(steps) {
|
|
30
112
|
return steps.map(({ prompt: _prompt, ...step }) => step);
|
|
31
113
|
}
|
|
114
|
+
function serialize(value) {
|
|
115
|
+
if (typeof value === "string")
|
|
116
|
+
return value;
|
|
117
|
+
try {
|
|
118
|
+
return JSON.stringify(value);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return String(value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function truncate(value, maxLength) {
|
|
125
|
+
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
126
|
+
}
|
|
127
|
+
function asRecord(value) {
|
|
128
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
129
|
+
? value
|
|
130
|
+
: {};
|
|
131
|
+
}
|
|
132
|
+
function stringValue(value) {
|
|
133
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
134
|
+
}
|
|
135
|
+
function extractFreshness(value) {
|
|
136
|
+
const record = asRecord(value);
|
|
137
|
+
return isFreshnessStamp(record.freshness) ? record.freshness : undefined;
|
|
138
|
+
}
|
|
139
|
+
function isFreshnessStamp(value) {
|
|
140
|
+
const record = asRecord(value);
|
|
141
|
+
return (typeof record.fetchedAt === "string" &&
|
|
142
|
+
typeof record.cacheStatus === "string" &&
|
|
143
|
+
typeof record.marketSession === "string" &&
|
|
144
|
+
typeof record.isStaleForSession === "boolean");
|
|
145
|
+
}
|
|
32
146
|
//# sourceMappingURL=prompt-step.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-step.js","sourceRoot":"","sources":["../../src/runtime/prompt-step.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prompt-step.js","sourceRoot":"","sources":["../../src/runtime/prompt-step.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { MemoryStorage } from "../memory/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* Alias for the session-manager handle extensions receive via
|
|
@@ -21,6 +21,9 @@ type QueueContext = ExtensionCommandContext | {
|
|
|
21
21
|
ui?: {
|
|
22
22
|
notify(message: string, level?: string): void;
|
|
23
23
|
};
|
|
24
|
+
sessionManager?: {
|
|
25
|
+
getEntries(): SessionEntry[];
|
|
26
|
+
};
|
|
24
27
|
};
|
|
25
28
|
/**
|
|
26
29
|
* Coordinates session lifecycle, memory, workflow execution,
|
|
@@ -35,6 +38,8 @@ export declare class SessionCoordinator {
|
|
|
35
38
|
private providerTracker;
|
|
36
39
|
private activeWorkflowRunRef;
|
|
37
40
|
private activeWorkflowType;
|
|
41
|
+
private activeStepCapture;
|
|
42
|
+
private workflowEventCaptureInstalled;
|
|
38
43
|
private tickerValidationCache;
|
|
39
44
|
private sessionId;
|
|
40
45
|
constructor();
|
|
@@ -129,5 +134,10 @@ export declare class SessionCoordinator {
|
|
|
129
134
|
private startWorkflowRun;
|
|
130
135
|
/** Cancel any active workflow. */
|
|
131
136
|
cancelActiveWorkflow(): void;
|
|
137
|
+
private startStepCapture;
|
|
138
|
+
private installWorkflowEventCapture;
|
|
139
|
+
private prepareWorkflowPrompt;
|
|
140
|
+
private emitSynthesisValidation;
|
|
141
|
+
private appendWorkflowEvent;
|
|
132
142
|
}
|
|
133
143
|
export {};
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isAnalystSplit, parseAnalystOutput, parseDebateOutput } from "../analysts/contracts.js";
|
|
2
|
+
import { buildAnalystVoteTallyBlock } from "../analysts/orchestrator.js";
|
|
2
3
|
import { MarketStateService } from "../market-state/service.js";
|
|
4
|
+
import { formatJsonSummary, formatLatestReportSummary, formatPortfolioSummary, formatWatchlistSummary, } from "../market-state/summaries.js";
|
|
5
|
+
import { getAllDefaults, initDefaultDatabase, MemoryStorage } from "../memory/index.js";
|
|
3
6
|
import { MemoryManager } from "../memory/manager.js";
|
|
4
7
|
import { extractPreferences } from "../memory/preference-extractor.js";
|
|
5
8
|
import { runOpenCandleSetup } from "../pi/setup.js";
|
|
6
9
|
import { PromptContextBuilder } from "../prompts/context-builder.js";
|
|
7
10
|
import { getAddonToolDescriptions } from "../tool-kit.js";
|
|
8
|
-
import {
|
|
11
|
+
import { collectToolNumbers, extractNumericClaims } from "./numeric-claims.js";
|
|
12
|
+
import { captureToolEvidence, extractAssistantText, promptStepOutput, toStepDefinitions, } from "./prompt-step.js";
|
|
9
13
|
import { ProviderTracker } from "./provider-tracker.js";
|
|
10
14
|
import { clearRunContext, setRunContext } from "./run-context.js";
|
|
15
|
+
import { checkNumberMatch } from "./validation.js";
|
|
11
16
|
import { WorkflowEventLogger } from "./workflow-events.js";
|
|
12
17
|
import { WorkflowRunner } from "./workflow-runner.js";
|
|
13
18
|
const PROMPT_SETTLE_POLL_MS = 25;
|
|
@@ -31,6 +36,10 @@ function hasPendingMessages(ctx) {
|
|
|
31
36
|
function isReadyForNextPrompt(ctx) {
|
|
32
37
|
return ctx.isIdle() && !hasPendingMessages(ctx);
|
|
33
38
|
}
|
|
39
|
+
function readSessionEntries(ctx) {
|
|
40
|
+
const manager = "sessionManager" in ctx ? ctx.sessionManager : undefined;
|
|
41
|
+
return manager?.getEntries?.() ?? [];
|
|
42
|
+
}
|
|
34
43
|
function sleep(ms) {
|
|
35
44
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
45
|
}
|
|
@@ -68,6 +77,8 @@ export class SessionCoordinator {
|
|
|
68
77
|
providerTracker;
|
|
69
78
|
activeWorkflowRunRef = null;
|
|
70
79
|
activeWorkflowType;
|
|
80
|
+
activeStepCapture = null;
|
|
81
|
+
workflowEventCaptureInstalled = false;
|
|
71
82
|
tickerValidationCache = new Map();
|
|
72
83
|
sessionId = "unknown";
|
|
73
84
|
constructor() {
|
|
@@ -297,6 +308,7 @@ export class SessionCoordinator {
|
|
|
297
308
|
startWorkflowRun(pi, definition, ctx, firstPromptMode) {
|
|
298
309
|
if (definition.steps.length === 0)
|
|
299
310
|
return;
|
|
311
|
+
this.installWorkflowEventCapture(pi);
|
|
300
312
|
const runner = this.runner;
|
|
301
313
|
if (this.activeWorkflowRunRef) {
|
|
302
314
|
this.activeWorkflowRunRef.active = false;
|
|
@@ -321,27 +333,74 @@ export class SessionCoordinator {
|
|
|
321
333
|
// Start the runner in the background for state tracking
|
|
322
334
|
const stepDefs = toStepDefinitions(definition.steps);
|
|
323
335
|
void runner
|
|
324
|
-
.start(definition.workflowType, stepDefs, async (step, stepIndex) => {
|
|
325
|
-
|
|
336
|
+
.start(definition.workflowType, stepDefs, async (step, stepIndex, _priorEvidence, context) => {
|
|
337
|
+
let entriesBeforeStep = readSessionEntries(ctx).length;
|
|
338
|
+
const eventCapture = this.startStepCapture();
|
|
339
|
+
// First step was already sent above — later steps are sent here.
|
|
326
340
|
if (stepIndex > 0) {
|
|
327
341
|
const settled = await waitForPromptSettlement(ctx, () => runRef.active);
|
|
328
342
|
if (!settled || !runRef.active) {
|
|
329
343
|
throw new Error("run_cancelled");
|
|
330
344
|
}
|
|
331
|
-
|
|
345
|
+
if (shouldSkipRebuttal(runner.getActiveRun(), step.stepType)) {
|
|
346
|
+
this.appendWorkflowEvent(pi, "step_skipped", {
|
|
347
|
+
stepType: step.stepType,
|
|
348
|
+
reason: "analyst_consensus",
|
|
349
|
+
});
|
|
350
|
+
throw new Error("analyst_consensus");
|
|
351
|
+
}
|
|
352
|
+
entriesBeforeStep = readSessionEntries(ctx).length;
|
|
353
|
+
const prompt = this.prepareWorkflowPrompt(pi, definition.steps[stepIndex].prompt, step.stepType, runner.getActiveRun());
|
|
354
|
+
pi.sendUserMessage(prompt);
|
|
332
355
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
356
|
+
const settled = await waitForPromptSettlement(ctx, () => runRef.active, {
|
|
357
|
+
requireActivity: stepIndex === 0 && firstPromptMode === "transform",
|
|
358
|
+
});
|
|
359
|
+
if (!settled || !runRef.active) {
|
|
360
|
+
throw new Error("run_cancelled");
|
|
361
|
+
}
|
|
362
|
+
let stepEntries = readSessionEntries(ctx).slice(entriesBeforeStep);
|
|
363
|
+
let rawText = capturedText(eventCapture, stepEntries);
|
|
364
|
+
let output = promptStepOutput(stepIndex, step.stepType, {
|
|
365
|
+
evidence: capturedEvidence(eventCapture, stepEntries),
|
|
366
|
+
rawText,
|
|
367
|
+
});
|
|
368
|
+
if (isStructuredAnalystStep(step.stepType) &&
|
|
369
|
+
!hasStructuredContract(step.stepType, rawText)) {
|
|
370
|
+
pi.sendUserMessage("Please revise your previous response to include the exact required final output format from the stage prompt. Do not add new tool calls.");
|
|
371
|
+
const retrySettled = await waitForPromptSettlement(ctx, () => runRef.active);
|
|
372
|
+
if (!retrySettled || !runRef.active) {
|
|
339
373
|
throw new Error("run_cancelled");
|
|
340
374
|
}
|
|
375
|
+
stepEntries = readSessionEntries(ctx).slice(entriesBeforeStep);
|
|
376
|
+
rawText = capturedText(eventCapture, stepEntries);
|
|
377
|
+
output = promptStepOutput(stepIndex, step.stepType, {
|
|
378
|
+
evidence: capturedEvidence(eventCapture, stepEntries),
|
|
379
|
+
rawText,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
for (const record of output.evidence) {
|
|
383
|
+
const value = isPlainObject(record.value) ? record.value : {};
|
|
384
|
+
this.eventLogger?.log(context.runId, stepIndex, "tool_called", {
|
|
385
|
+
stepType: step.stepType,
|
|
386
|
+
tool: value.tool,
|
|
387
|
+
args: value.args,
|
|
388
|
+
resultDigest: value.resultDigest,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
if (this.activeStepCapture === eventCapture) {
|
|
392
|
+
this.activeStepCapture = null;
|
|
393
|
+
}
|
|
394
|
+
const structuredOutput = attachStructuredOutput(pi, output);
|
|
395
|
+
if (step.stepType === "synthesis") {
|
|
396
|
+
this.emitSynthesisValidation(pi, context.runId, stepIndex, runner.getActiveRun(), structuredOutput);
|
|
341
397
|
}
|
|
342
|
-
return
|
|
398
|
+
return structuredOutput;
|
|
343
399
|
})
|
|
344
400
|
.finally(() => {
|
|
401
|
+
if (this.activeWorkflowRunRef === runRef) {
|
|
402
|
+
this.activeStepCapture = null;
|
|
403
|
+
}
|
|
345
404
|
clearRunContext(runRef.contextToken);
|
|
346
405
|
if (this.activeWorkflowRunRef === runRef) {
|
|
347
406
|
this.activeWorkflowRunRef = null;
|
|
@@ -356,8 +415,216 @@ export class SessionCoordinator {
|
|
|
356
415
|
clearRunContext(activeRef.contextToken);
|
|
357
416
|
this.activeWorkflowRunRef = null;
|
|
358
417
|
}
|
|
418
|
+
this.activeStepCapture = null;
|
|
359
419
|
this.runner?.cancel();
|
|
360
420
|
}
|
|
421
|
+
startStepCapture() {
|
|
422
|
+
const capture = {
|
|
423
|
+
evidence: [],
|
|
424
|
+
pendingTools: new Map(),
|
|
425
|
+
rawText: "",
|
|
426
|
+
};
|
|
427
|
+
this.activeStepCapture = capture;
|
|
428
|
+
return capture;
|
|
429
|
+
}
|
|
430
|
+
installWorkflowEventCapture(pi) {
|
|
431
|
+
const on = pi.on;
|
|
432
|
+
if (this.workflowEventCaptureInstalled || typeof on !== "function")
|
|
433
|
+
return;
|
|
434
|
+
this.workflowEventCaptureInstalled = true;
|
|
435
|
+
pi.on("message_update", (event) => {
|
|
436
|
+
const capture = this.activeStepCapture;
|
|
437
|
+
if (!capture || event.assistantMessageEvent.type !== "text_delta")
|
|
438
|
+
return;
|
|
439
|
+
capture.rawText += event.assistantMessageEvent.delta;
|
|
440
|
+
});
|
|
441
|
+
pi.on("tool_execution_start", (event) => {
|
|
442
|
+
const capture = this.activeStepCapture;
|
|
443
|
+
if (!capture)
|
|
444
|
+
return;
|
|
445
|
+
capture.pendingTools.set(event.toolCallId, {
|
|
446
|
+
tool: event.toolName,
|
|
447
|
+
args: event.args ?? {},
|
|
448
|
+
startedAt: new Date().toISOString(),
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
pi.on("tool_execution_end", (event) => {
|
|
452
|
+
const capture = this.activeStepCapture;
|
|
453
|
+
if (!capture)
|
|
454
|
+
return;
|
|
455
|
+
const completedAt = new Date().toISOString();
|
|
456
|
+
const pending = capture.pendingTools.get(event.toolCallId);
|
|
457
|
+
capture.pendingTools.delete(event.toolCallId);
|
|
458
|
+
const tool = pending?.tool ?? event.toolName;
|
|
459
|
+
if (!tool)
|
|
460
|
+
return;
|
|
461
|
+
capture.evidence.push(toolEvidenceRecord({
|
|
462
|
+
tool,
|
|
463
|
+
args: pending?.args ?? {},
|
|
464
|
+
result: event.result,
|
|
465
|
+
startedAt: pending?.startedAt ?? completedAt,
|
|
466
|
+
completedAt,
|
|
467
|
+
isError: event.isError === true,
|
|
468
|
+
}));
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
prepareWorkflowPrompt(pi, prompt, stepType, run) {
|
|
472
|
+
if (stepType !== "synthesis")
|
|
473
|
+
return prompt;
|
|
474
|
+
const parsedAnalysts = collectParsedAnalystOutputs(run);
|
|
475
|
+
const tallyBlock = buildAnalystVoteTallyBlock(parsedAnalysts);
|
|
476
|
+
if (!tallyBlock) {
|
|
477
|
+
this.eventLogger?.log(run?.runId ?? "unknown", run?.currentStepIndex ?? 0, "tally_skipped", {
|
|
478
|
+
reason: "tally_skipped",
|
|
479
|
+
parsedAnalystCount: parsedAnalysts.length,
|
|
480
|
+
});
|
|
481
|
+
this.appendWorkflowEvent(pi, "tally_skipped", {
|
|
482
|
+
reason: "fewer_than_2_parsed_analysts",
|
|
483
|
+
parsedAnalystCount: parsedAnalysts.length,
|
|
484
|
+
});
|
|
485
|
+
return prompt;
|
|
486
|
+
}
|
|
487
|
+
this.eventLogger?.log(run?.runId ?? "unknown", run?.currentStepIndex ?? 0, "tally_injected", {
|
|
488
|
+
tallyBlock,
|
|
489
|
+
parsedAnalystCount: parsedAnalysts.length,
|
|
490
|
+
});
|
|
491
|
+
this.appendWorkflowEvent(pi, "tally_injected", {
|
|
492
|
+
tallyBlock,
|
|
493
|
+
parsedAnalystCount: parsedAnalysts.length,
|
|
494
|
+
});
|
|
495
|
+
return `${tallyBlock}\n\n${prompt}`;
|
|
496
|
+
}
|
|
497
|
+
emitSynthesisValidation(pi, runId, stepIndex, run, currentOutput) {
|
|
498
|
+
const validationInput = collectValidationInput(run, currentOutput);
|
|
499
|
+
const mismatches = checkNumberMatch(validationInput.evidence, validationInput.toolResults).filter((entry) => entry.type === "failure");
|
|
500
|
+
const passed = mismatches.length === 0;
|
|
501
|
+
const payload = {
|
|
502
|
+
passed,
|
|
503
|
+
mismatches,
|
|
504
|
+
skipped_unparsed: validationInput.skippedUnparsed,
|
|
505
|
+
};
|
|
506
|
+
this.eventLogger?.log(runId, stepIndex, passed ? "validation_passed" : "validation_failed", {
|
|
507
|
+
mismatches,
|
|
508
|
+
skipped_unparsed: validationInput.skippedUnparsed,
|
|
509
|
+
});
|
|
510
|
+
pi.appendEntry("opencandle-validation", payload);
|
|
511
|
+
this.appendWorkflowEvent(pi, passed ? "validation_passed" : "validation_failed", {
|
|
512
|
+
mismatches,
|
|
513
|
+
skipped_unparsed: validationInput.skippedUnparsed,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
appendWorkflowEvent(pi, eventType, payload) {
|
|
517
|
+
pi.appendEntry("opencandle-workflow-event", {
|
|
518
|
+
eventType,
|
|
519
|
+
...payload,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function capturedText(capture, entries) {
|
|
524
|
+
return capture.rawText.trim() || extractAssistantText(entries);
|
|
525
|
+
}
|
|
526
|
+
function capturedEvidence(capture, entries) {
|
|
527
|
+
return capture.evidence.length > 0 ? [...capture.evidence] : captureToolEvidence(entries);
|
|
528
|
+
}
|
|
529
|
+
function collectParsedAnalystOutputs(run) {
|
|
530
|
+
if (!run)
|
|
531
|
+
return [];
|
|
532
|
+
const outputs = [];
|
|
533
|
+
for (const output of run.stepOutputs.values()) {
|
|
534
|
+
if (output.parsed === true && output.analystOutput) {
|
|
535
|
+
outputs.push(output.analystOutput);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return outputs;
|
|
539
|
+
}
|
|
540
|
+
function shouldSkipRebuttal(run, stepType) {
|
|
541
|
+
if (stepType !== "debate_rebuttal")
|
|
542
|
+
return false;
|
|
543
|
+
const parsedAnalysts = collectParsedAnalystOutputs(run);
|
|
544
|
+
// Degradation rule: with fewer than two parsed analyst outputs there is no
|
|
545
|
+
// trustworthy consensus signal to gate on — run the rebuttal exactly as the
|
|
546
|
+
// status quo instead of skipping because isAnalystSplit([]) is false.
|
|
547
|
+
if (parsedAnalysts.length < 2)
|
|
548
|
+
return false;
|
|
549
|
+
return !isAnalystSplit(parsedAnalysts);
|
|
550
|
+
}
|
|
551
|
+
function collectValidationInput(run, currentOutput) {
|
|
552
|
+
const evidence = [];
|
|
553
|
+
const toolResults = new Map();
|
|
554
|
+
const skippedUnparsed = [];
|
|
555
|
+
const outputs = run ? [...run.stepOutputs.values()] : [];
|
|
556
|
+
if (currentOutput) {
|
|
557
|
+
outputs.push(currentOutput);
|
|
558
|
+
}
|
|
559
|
+
for (const output of outputs) {
|
|
560
|
+
if (output.stepType.startsWith("analyst_") && output.parsed === false) {
|
|
561
|
+
skippedUnparsed.push(output.stepType);
|
|
562
|
+
}
|
|
563
|
+
for (const record of output.evidence) {
|
|
564
|
+
collectToolNumbers(record, toolResults);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
for (const output of outputs) {
|
|
568
|
+
if (!output.rawText)
|
|
569
|
+
continue;
|
|
570
|
+
evidence.push(...extractNumericClaims(output.rawText, toolResults));
|
|
571
|
+
}
|
|
572
|
+
return { evidence, toolResults, skippedUnparsed };
|
|
573
|
+
}
|
|
574
|
+
function toolEvidenceRecord(input) {
|
|
575
|
+
const serializedResult = serialize(input.result);
|
|
576
|
+
const freshness = extractFreshness(input.result);
|
|
577
|
+
return {
|
|
578
|
+
label: `tool:${input.tool}`,
|
|
579
|
+
value: {
|
|
580
|
+
tool: input.tool,
|
|
581
|
+
args: truncate(serialize(input.args), 500),
|
|
582
|
+
...(freshness ? { freshness } : {}),
|
|
583
|
+
resultDigest: {
|
|
584
|
+
preview: truncate(serializedResult, 500),
|
|
585
|
+
totalLength: serializedResult.length,
|
|
586
|
+
},
|
|
587
|
+
startedAt: input.startedAt,
|
|
588
|
+
completedAt: input.completedAt,
|
|
589
|
+
},
|
|
590
|
+
provenance: {
|
|
591
|
+
source: "computed",
|
|
592
|
+
timestamp: freshness?.providerDataAt ?? freshness?.fetchedAt ?? input.completedAt,
|
|
593
|
+
provider: input.tool,
|
|
594
|
+
confidence: input.isError ? 0.5 : undefined,
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
function summarizeStepEvidence(evidence) {
|
|
599
|
+
return evidence.map((record) => (isPlainObject(record.value) ? record.value : record));
|
|
600
|
+
}
|
|
601
|
+
function serialize(value) {
|
|
602
|
+
if (typeof value === "string")
|
|
603
|
+
return value;
|
|
604
|
+
try {
|
|
605
|
+
return JSON.stringify(value);
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
return String(value);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function truncate(value, maxLength) {
|
|
612
|
+
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
613
|
+
}
|
|
614
|
+
function extractFreshness(value) {
|
|
615
|
+
const record = isPlainObject(value) ? value : {};
|
|
616
|
+
const direct = record.freshness;
|
|
617
|
+
if (isFreshnessStamp(direct))
|
|
618
|
+
return direct;
|
|
619
|
+
const details = isPlainObject(record.details) ? record.details : {};
|
|
620
|
+
return isFreshnessStamp(details.freshness) ? details.freshness : undefined;
|
|
621
|
+
}
|
|
622
|
+
function isFreshnessStamp(value) {
|
|
623
|
+
const record = isPlainObject(value) ? value : {};
|
|
624
|
+
return (typeof record.fetchedAt === "string" &&
|
|
625
|
+
typeof record.cacheStatus === "string" &&
|
|
626
|
+
typeof record.marketSession === "string" &&
|
|
627
|
+
typeof record.isStaleForSession === "boolean");
|
|
361
628
|
}
|
|
362
629
|
function formatToolDefaultsForPrompt() {
|
|
363
630
|
try {
|
|
@@ -387,6 +654,76 @@ function flattenDefaults(defaults, prefix = "") {
|
|
|
387
654
|
}
|
|
388
655
|
return out;
|
|
389
656
|
}
|
|
657
|
+
function isStructuredAnalystStep(stepType) {
|
|
658
|
+
return stepType.startsWith("analyst_") || stepType.startsWith("debate_");
|
|
659
|
+
}
|
|
660
|
+
function hasStructuredContract(stepType, text) {
|
|
661
|
+
if (stepType.startsWith("analyst_")) {
|
|
662
|
+
// The conviction range check must match extractConviction's 1-10
|
|
663
|
+
// contract: parseAnalystOutput silently defaults out-of-range values to
|
|
664
|
+
// 5, so accepting them here would record a fabricated conviction.
|
|
665
|
+
const conviction = text.match(/CONVICTION:\s*(\d+)/i);
|
|
666
|
+
const convictionInRange = conviction !== null && Number(conviction[1]) >= 1 && Number(conviction[1]) <= 10;
|
|
667
|
+
return (/SIGNAL:\s*(BUY|HOLD|SELL)/i.test(text) && convictionInRange && /THESIS:\s*(.+)/i.test(text));
|
|
668
|
+
}
|
|
669
|
+
if (stepType === "debate_bull") {
|
|
670
|
+
return (/BULL THESIS:\s*(.+)/i.test(text) && /KEY RISK(?:\s+TO THIS THESIS)?:\s*(.+)/i.test(text));
|
|
671
|
+
}
|
|
672
|
+
if (stepType === "debate_bear") {
|
|
673
|
+
return /BEAR THESIS:\s*(.+)/i.test(text) && /WHAT WOULD CHANGE MY MIND:\s*(.+)/i.test(text);
|
|
674
|
+
}
|
|
675
|
+
if (stepType === "debate_rebuttal") {
|
|
676
|
+
return (/^REBUTTAL SKIPPED/i.test(text.trim()) ||
|
|
677
|
+
(/CONCESSIONS:\s*[\s\S]+/i.test(text) && /REMAINING CONVICTION:\s*(\d+)/i.test(text)));
|
|
678
|
+
}
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
function attachStructuredOutput(pi, output) {
|
|
682
|
+
if (!isStructuredAnalystStep(output.stepType))
|
|
683
|
+
return output;
|
|
684
|
+
const rawText = output.rawText ?? "";
|
|
685
|
+
const evidence = summarizeStepEvidence(output.evidence);
|
|
686
|
+
const evidenceCount = output.evidence.length;
|
|
687
|
+
if (!hasStructuredContract(output.stepType, rawText)) {
|
|
688
|
+
const role = analystRoleFromStep(output.stepType);
|
|
689
|
+
pi.appendEntry("opencandle-analyst-step", {
|
|
690
|
+
stage: output.stepType,
|
|
691
|
+
...(role ? { role } : {}),
|
|
692
|
+
parsed: false,
|
|
693
|
+
evidenceCount,
|
|
694
|
+
evidence,
|
|
695
|
+
});
|
|
696
|
+
return { ...output, parsed: false };
|
|
697
|
+
}
|
|
698
|
+
if (output.stepType.startsWith("analyst_")) {
|
|
699
|
+
const role = analystRoleFromStep(output.stepType) ?? "analyst";
|
|
700
|
+
const analystOutput = parseAnalystOutput(role, rawText);
|
|
701
|
+
const parsedOutput = { ...output, analystOutput, parsed: true };
|
|
702
|
+
pi.appendEntry("opencandle-analyst-step", {
|
|
703
|
+
stage: output.stepType,
|
|
704
|
+
role,
|
|
705
|
+
signal: analystOutput.signal,
|
|
706
|
+
conviction: analystOutput.conviction,
|
|
707
|
+
parsed: true,
|
|
708
|
+
evidenceCount,
|
|
709
|
+
evidence,
|
|
710
|
+
});
|
|
711
|
+
return parsedOutput;
|
|
712
|
+
}
|
|
713
|
+
const side = output.stepType === "debate_bear" ? "bear" : "bull";
|
|
714
|
+
const debateOutput = parseDebateOutput(side, rawText);
|
|
715
|
+
pi.appendEntry("opencandle-analyst-step", {
|
|
716
|
+
stage: output.stepType,
|
|
717
|
+
side,
|
|
718
|
+
parsed: true,
|
|
719
|
+
evidenceCount,
|
|
720
|
+
evidence,
|
|
721
|
+
});
|
|
722
|
+
return { ...output, debateOutput, parsed: true };
|
|
723
|
+
}
|
|
724
|
+
function analystRoleFromStep(stepType) {
|
|
725
|
+
return stepType.startsWith("analyst_") ? stepType.slice("analyst_".length) : undefined;
|
|
726
|
+
}
|
|
390
727
|
function buildSavedMarketStateContext(db) {
|
|
391
728
|
try {
|
|
392
729
|
const service = new MarketStateService(db);
|
|
@@ -409,29 +746,10 @@ function buildSavedMarketStateContext(db) {
|
|
|
409
746
|
'If the question concerns a sector, industry, event, company, or competitor connected to any saved position or watchlist symbol, end the answer with a short "Your positions" section explaining how it affects those specific holdings. Skip that section only when no saved symbol is plausibly affected.',
|
|
410
747
|
];
|
|
411
748
|
if (lots.length > 0) {
|
|
412
|
-
lines.push(
|
|
413
|
-
for (const lot of lots.slice(0, 8)) {
|
|
414
|
-
const costBasis = formatMoney(lot.quantity * lot.avgCost, lot.currency);
|
|
415
|
-
const name = lot.name ? ` (${lot.name})` : "";
|
|
416
|
-
lines.push(`- ${lot.symbol}: ${lot.quantity} @ ${formatMoney(lot.avgCost, lot.currency)}, cost basis ${costBasis}${name}`);
|
|
417
|
-
}
|
|
749
|
+
lines.push(...formatPortfolioSummary(lots));
|
|
418
750
|
}
|
|
419
751
|
if (watchlist.length > 0) {
|
|
420
|
-
lines.push(
|
|
421
|
-
for (const item of watchlist.slice(0, 8)) {
|
|
422
|
-
const parts = [
|
|
423
|
-
item.targetPrice == null
|
|
424
|
-
? null
|
|
425
|
-
: `target ${formatMoney(item.targetPrice, item.priceCurrency ?? item.currency ?? "USD")}`,
|
|
426
|
-
item.stopPrice == null
|
|
427
|
-
? null
|
|
428
|
-
: `stop ${formatMoney(item.stopPrice, item.priceCurrency ?? item.currency ?? "USD")}`,
|
|
429
|
-
item.thesis ? `thesis: ${item.thesis}` : null,
|
|
430
|
-
item.tags && item.tags.length > 0 ? `tags: ${item.tags.join(", ")}` : null,
|
|
431
|
-
item.notes ? `notes: ${item.notes}` : null,
|
|
432
|
-
].filter((part) => part != null);
|
|
433
|
-
lines.push(`- ${item.symbol}${item.name ? ` (${item.name})` : ""}${parts.length > 0 ? ` — ${parts.join("; ")}` : ""}`);
|
|
434
|
-
}
|
|
752
|
+
lines.push(...formatWatchlistSummary(watchlist));
|
|
435
753
|
}
|
|
436
754
|
if (alerts.length > 0) {
|
|
437
755
|
lines.push("Alert rules:");
|
|
@@ -447,8 +765,7 @@ function buildSavedMarketStateContext(db) {
|
|
|
447
765
|
}
|
|
448
766
|
}
|
|
449
767
|
if (reportRuns.length > 0) {
|
|
450
|
-
|
|
451
|
-
lines.push(`Latest report run: ${latest.status} at ${latest.completedAt ?? latest.startedAt}`);
|
|
768
|
+
lines.push(...formatLatestReportSummary(reportRuns[0], { includeSummary: false }));
|
|
452
769
|
}
|
|
453
770
|
return lines.join("\n");
|
|
454
771
|
}
|
|
@@ -464,20 +781,6 @@ function shouldIncludeSavedMarketStateContext(workflowType, resolvedTurnContext,
|
|
|
464
781
|
// general finance or LLM-router fallback), never for pass-through prompts.
|
|
465
782
|
return workflowType != null || fallbackContext != null;
|
|
466
783
|
}
|
|
467
|
-
function formatMoney(value, currency) {
|
|
468
|
-
const normalized = currency.toUpperCase();
|
|
469
|
-
if (normalized === "USD")
|
|
470
|
-
return `$${value.toFixed(2)}`;
|
|
471
|
-
return `${normalized} ${value.toFixed(2)}`;
|
|
472
|
-
}
|
|
473
|
-
function formatJsonSummary(value) {
|
|
474
|
-
if (value == null)
|
|
475
|
-
return "";
|
|
476
|
-
const json = JSON.stringify(value);
|
|
477
|
-
if (json.length <= 90)
|
|
478
|
-
return json;
|
|
479
|
-
return `${json.slice(0, 87)}...`;
|
|
480
|
-
}
|
|
481
784
|
function isPlainObject(value) {
|
|
482
785
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
483
786
|
}
|