@coinrithm/mcp-trading 0.7.2 → 0.7.3
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/CHANGELOG.md +46 -1
- package/README.md +9 -7
- package/dist/agent/act.d.ts +2 -2
- package/dist/agent/act.js +24 -3
- package/dist/agent/cli.js +59 -18
- package/dist/agent/client.d.ts +33 -0
- package/dist/agent/client.js +34 -7
- package/dist/agent/decision.d.ts +3 -0
- package/dist/agent/decision.js +26 -3
- package/dist/agent/decisionValidator.js +2 -1
- package/dist/agent/deploymentOverlay.js +25 -5
- package/dist/agent/engine.d.ts +2 -1
- package/dist/agent/engine.js +4 -1
- package/dist/agent/extract.js +3 -1
- package/dist/agent/gate.js +25 -5
- package/dist/agent/index.js +0 -1
- package/dist/agent/indicators.js +4 -2
- package/dist/agent/manifest.js +1 -1
- package/dist/agent/mechanical.d.ts +36 -0
- package/dist/agent/mechanical.js +286 -0
- package/dist/agent/observe.js +120 -52
- package/dist/agent/prompt.d.ts +3 -1
- package/dist/agent/prompt.js +17 -6
- package/dist/agent/providers.js +39 -4
- package/dist/agent/resolve.js +23 -6
- package/dist/agent/resolvePm.js +14 -3
- package/dist/agent/runEvidence.js +6 -2
- package/dist/agent/runner.d.ts +8 -2
- package/dist/agent/runner.js +363 -59
- package/dist/agent/scorecard.js +12 -4
- package/dist/agent/setups.js +57 -9
- package/dist/agent/skill.js +1 -1
- package/dist/agent/state.js +9 -4
- package/dist/agent/types.d.ts +17 -2
- package/dist/agent/types.js +2 -1
- package/dist/agent/util.js +11 -4
- package/dist/agent/version.d.ts +1 -1
- package/dist/agent/version.js +1 -1
- package/dist/client.d.ts +33 -0
- package/dist/client.js +12 -3
- package/dist/executionPolicy.d.ts +2 -0
- package/dist/executionPolicy.js +21 -0
- package/dist/http.js +10 -2
- package/dist/tools.d.ts +1 -0
- package/dist/tools.js +214 -29
- package/package.json +9 -1
package/dist/agent/prompt.js
CHANGED
|
@@ -33,18 +33,24 @@ export function formatPmResolutions(resolutions) {
|
|
|
33
33
|
`Resolved since last cycle: ${items.join("; ")}.`,
|
|
34
34
|
];
|
|
35
35
|
}
|
|
36
|
-
export function buildSystemPrompt(spec, mergedProse
|
|
36
|
+
export function buildSystemPrompt(spec, mergedProse,
|
|
37
|
+
// includeForecast (default OFF here; the runner passes the house-agent flag):
|
|
38
|
+
// when true, pm_open asks the model for its OWN independent forecastProbability
|
|
39
|
+
// (1..99) — its probability the backed side wins, judged from the question, NOT
|
|
40
|
+
// echoed from the market price. This feeds the agent's public calibration record.
|
|
41
|
+
opts = {}) {
|
|
37
42
|
const r = spec.risk;
|
|
38
43
|
const v = spec.venues;
|
|
44
|
+
const includeForecast = opts.includeForecast === true;
|
|
39
45
|
const actions = [];
|
|
40
46
|
if (v.includes("futures")) {
|
|
41
|
-
actions.push('{"type":"futures_open","symbol","side":"long"|"short","leverage","marginMusd","stopLossPrice","takeProfitPrice","confidence":0..1}', '{"type":"futures_close","positionId","fraction"}', '{"type":"futures_set_sltp","positionId","stopLossPrice","takeProfitPrice"}',
|
|
47
|
+
actions.push('{"type":"futures_open","symbol","side":"long"|"short","leverage","marginMusd","stopLossPrice","takeProfitPrice","confidence":0..1}', '{"type":"futures_close","positionId","fraction"}', '{"type":"futures_set_sltp","positionId","stopLossPrice","takeProfitPrice"}', "FUTURES TRIGGER RULES (the server rejects the WHOLE open otherwise): a LONG's takeProfitPrice must be ABOVE the current mark and stopLossPrice BELOW it (and above liquidationPrice); a SHORT is inverted (TP below mark, SL above). Every open position in observation.openPositions shows entryPrice, markPrice, liquidationPrice, stopLossPrice, takeProfitPrice — read them and place triggers on the correct side. NEVER attach stopLossPrice/takeProfitPrice to a futures_open for a symbol you ALREADY hold (the server treats it as an add and rejects it) — adjust that position with futures_set_sltp on its positionId instead.");
|
|
42
48
|
}
|
|
43
49
|
if (v.includes("spot")) {
|
|
44
50
|
actions.push('{"type":"spot_order","symbol","side":"buy"|"sell","orderType":"market"|"limit"|"stop","quantity","limitPrice","stopPrice","confidence":0..1}', '{"type":"spot_cancel","orderId"}');
|
|
45
51
|
}
|
|
46
52
|
if (v.includes("pm")) {
|
|
47
|
-
actions.push(
|
|
53
|
+
actions.push(`{"type":"pm_open","ref":"pmN","stakeMusd","confidence":0..1${includeForecast ? ',"forecastProbability":1..99' : ""}} (set "ref" to one of the refs listed THIS cycle (pm1..pmN) — the \`ref\` of the ONE observation.pmMarkets entry you are betting, e.g. "pm3", copied EXACTLY; a ref NOT in this cycle's list is rejected as pm_ref_unknown and wastes the cycle; stakeMusd >= 10${includeForecast ? '; set "forecastProbability" to YOUR OWN probability 1-99 that this outcome wins — see the forecast rule below' : ""})`);
|
|
48
54
|
}
|
|
49
55
|
return [
|
|
50
56
|
"You operate a CoinRithm PAPER-TRADING agent (simulated 50,000 mUSD; not real money, not financial advice).",
|
|
@@ -65,7 +71,12 @@ export function buildSystemPrompt(spec, mergedProse) {
|
|
|
65
71
|
: []),
|
|
66
72
|
"- prediction markets are a FIRST-CLASS venue for you — a pm_open is as real a trade as a futures/spot open, not an afterthought. Each observation.pmMarkets entry carries a short `ref` (pm1, pm2, …), an `outcome` label, and `prob` (0..1, the market's CURRENT odds). BET (pm_open) an outcome when YOUR estimate of its true probability differs MATERIALLY from the market's — that gap is your edge (e.g. prob 0.35 but you think it's really ~0.55 -> buy). Skip only markets pinned near 0 or 1 (no edge left). Every entry in observation.pmMarkets is already filtered to one you CAN open (binary/settlement-grade) — so a listed market will not bounce at quote. Pick ONLY a listed market and identify it by copying its `ref` into the action; min stake 10 mUSD. Do NOT re-bet a market+outcome you ALREADY hold (check observation.pmPositions) — that is churn and will be rejected; bet a DIFFERENT market or skip.",
|
|
67
73
|
"- PM stake is a SEPARATE budget from your futures margin: the futures margin cap (maxOpenMarginMusd) does NOT limit pm_open. So when your futures are at the margin/position cap — you hold the max, or a futures_open keeps getting REJECTED with open_margin_exceeds_cap — prediction markets are STILL fully open to you. PIVOT to pm_open on a mispriced market instead of re-proposing a futures_open that will just be rejected: a rejected open wastes the entire cycle, an eligible PM bet does not.",
|
|
68
|
-
"- YOUR SHARPEST PM EDGE is the crypto price view you JUST formed: crypto PM markets resolve on the very prices you analyse, so you have a genuine information edge there that you do NOT have on coin futures alone. EVERY cycle you reach a price conviction, it is REQUIRED that you scan observation.pmMarkets for a crypto market that same view prices wrong and, if one is materially mispriced, open it with pm_open — treat that mispricing exactly like a flagged coin setup (an ACT, not a skip). If you are bearish BTC, a 'BTC above $X by <date>' priced high is a NO; if bullish ETH, an 'ETH above $Y' priced low is a YES.
|
|
74
|
+
"- YOUR SHARPEST PM EDGE is the crypto price view you JUST formed: crypto PM markets resolve on the very prices you analyse, so you have a genuine information edge there that you do NOT have on coin futures alone. EVERY cycle you reach a price conviction, it is REQUIRED that you scan observation.pmMarkets for a LISTED crypto market that same view prices wrong and, if one is materially mispriced, open it with pm_open by its `ref` — treat that mispricing exactly like a flagged coin setup (an ACT, not a skip). If you are bearish BTC, a 'BTC above $X by <date>' priced high is a NO; if bullish ETH, an 'ETH above $Y' priced low is a YES. ESCAPE HATCH — only the markets actually listed in observation.pmMarkets THIS cycle (pm1..pmN) are bettable: if NONE of them matches the coin or view you formed, that is a legitimate SKIP for PM (say so in one clause and move on) — do NOT invent, guess, or increment a ref for a market you wish existed, because a made-up ref is rejected (pm_ref_unknown) and wastes the whole cycle exactly like a rejected open. The mistake to avoid is leaving a LISTED, clearly mispriced crypto market untraded — a mispricing that is NOT on this cycle's board is simply not actionable now, not a miss. (For non-crypto events you have no special edge; skip unless the odds are obviously off.)",
|
|
75
|
+
...(includeForecast
|
|
76
|
+
? [
|
|
77
|
+
"- FORECAST RULE (pm_open forecastProbability): before you look at what the market is pricing, decide YOUR OWN probability the outcome you are backing actually WINS — reason ONLY from the question, its resolution criteria, and the deadline. Put that number (1-99, whole or one decimal) in `forecastProbability`. This is graded against reality as your PUBLIC calibration record, so it must be YOUR judgement, NOT the market's: do NOT copy, round, or anchor it to the observation.pmMarkets `prob`. It is FINE if your honest forecast happens to land on the market's number — but reaching that by echoing the price defeats the point. If you genuinely cannot form an independent view, OMIT the field rather than parroting the market (an absent forecast is better than a fake one, and it never blocks the bet).",
|
|
78
|
+
]
|
|
79
|
+
: []),
|
|
69
80
|
`- abstention.minConfidence ${spec.abstention.minConfidence}: opens below this are rejected, so act with genuine conviction — but routine caution is no reason to sit out a clear setup`,
|
|
70
81
|
...(spec.capabilities.includes("indicators")
|
|
71
82
|
? [
|
|
@@ -95,14 +106,14 @@ export function buildSystemPrompt(spec, mergedProse) {
|
|
|
95
106
|
"",
|
|
96
107
|
"## How to act — a decisive trader in character, not a bystander",
|
|
97
108
|
"You ARE the character in the strategy above; trade like it. When you have a clear read — even a moderate-confidence one — TAKE THE POSITION, sized within your caps and protected with a stop. You wake every cycle and people watch you live: an agent that watches forever and never commits is useless to them and to itself.",
|
|
98
|
-
|
|
109
|
+
"Skip ONLY when the read is genuinely contradictory (signals fight each other), the data is stale, or you truly have no edge this cycle. A quiet tape where your thesis still has a small but REAL edge is an ACT, not a skip — take it, small, with a stop. Do not confuse caution with paralysis.",
|
|
99
110
|
'In "rationale" (shown LIVE in your public terminal) speak in YOUR voice and commit to a view in 1-2 vivid, specific sentences — what you see and what you are DOING about it, like a trader posting their move, not a risk report. Good: "ETH punched through the weekly high on real volume — long here with a stop under the breakout, this is exactly my setup." Weak: "conditions are mixed, waiting for clarity." Keep "reason" a short label.',
|
|
100
111
|
"",
|
|
101
112
|
"## Flagged setups this cycle — your wake-up list (observation.setups)",
|
|
102
113
|
"A deterministic scan already checked every watchlist coin and put the ones with real, tradeable structure RIGHT NOW into observation.setups — each has symbol, kind, bias, strength, and a factual note (trend / RSI / breakout / ATR reads). This is your shortlist; you do NOT need to re-derive whether a setup exists.",
|
|
103
114
|
'- If observation.setups is NON-EMPTY: act on the strongest one that fits YOUR strategy. The `bias` is the trend-following read; if you are a contrarian / mean-reversion trader, FADE it with the same facts (e.g. a downtrend that is also "RSI oversold" is YOUR long). Skipping a flagged setup needs a SPECIFIC reason tied to your thesis — "no clear setup" is NOT a valid skip when setups are listed.',
|
|
104
115
|
"- If observation.setups is EMPTY: no coin has a flagged structure right now — but BEFORE you skip, check observation.pmMarkets for a crypto market your current read prices wrong (a PM mispricing is a valid ACT even with zero coin setups). Only then, if nothing is mispriced, skip new entries and just manage any open positions.",
|
|
105
|
-
|
|
116
|
+
"- A setup tagged `held` (held: long|short) is a position you ALREADY hold. Do NOT propose a new open on it — that only hits the margin cap and wastes the cycle. MANAGE it instead: trail the stop toward your target, ADD only if you have margin room AND fresh conviction, or cut if the thesis broke.",
|
|
106
117
|
"",
|
|
107
118
|
"## After you act — hold with conviction, do not churn",
|
|
108
119
|
"A position is a thesis that needs TIME to work. Once you are in WITH a stop, let the stop or your target close it: do NOT bail on the next cycle over a small adverse tick, and do NOT manually close a fresh position unless the thesis is structurally invalidated (the level broke, the trend flipped) — not merely because price wiggled against you. A trade opened and closed minutes later just donates the round-trip fee + spread to noise.",
|
package/dist/agent/providers.js
CHANGED
|
@@ -32,7 +32,9 @@ const DEFAULT_TIMEOUT_MS = 300_000;
|
|
|
32
32
|
// instruct mode (measured ~3-4s, clean JSON). Apply it automatically for any
|
|
33
33
|
// nemotron model so a per-cadence decision never blows the cadence.
|
|
34
34
|
function applyReasoningToggle(model, system) {
|
|
35
|
-
return /nemotron/i.test(model)
|
|
35
|
+
return /nemotron/i.test(model)
|
|
36
|
+
? `detailed thinking off\n\n${system}`
|
|
37
|
+
: system;
|
|
36
38
|
}
|
|
37
39
|
// fetch with a hard timeout via AbortController. A custom fetchFn (tests) that
|
|
38
40
|
// ignores `signal` still works — the timer just never fires for it.
|
|
@@ -66,6 +68,8 @@ function envKey(provider, env) {
|
|
|
66
68
|
return env.GEMINI_API_KEY ?? env.MODEL_API_KEY;
|
|
67
69
|
case "openai-compatible":
|
|
68
70
|
return env.MODEL_API_KEY ?? env.OPENAI_API_KEY;
|
|
71
|
+
case "mechanical":
|
|
72
|
+
return undefined; // no LLM, no key — selectProvider short-circuits before this matters
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
function baseUrlFor(provider, configured) {
|
|
@@ -82,6 +86,25 @@ function baseUrlFor(provider, configured) {
|
|
|
82
86
|
return (configured ?? "").replace(/\/+$/, "");
|
|
83
87
|
case "anthropic":
|
|
84
88
|
return "https://api.anthropic.com/v1";
|
|
89
|
+
case "mechanical":
|
|
90
|
+
return ""; // never used — mechanical agents make no HTTP call
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Non-LLM stub for the mechanical BENCHMARK agents. It satisfies the Provider
|
|
94
|
+
// contract so the deps stay non-null, but decide() is NEVER invoked: runCycle
|
|
95
|
+
// detects a mechanical agent and computes the decision deterministically before
|
|
96
|
+
// the provider is asked. If it is ever called, it fails closed (loudly) rather
|
|
97
|
+
// than silently pretending to reason.
|
|
98
|
+
class MechanicalProvider {
|
|
99
|
+
label;
|
|
100
|
+
constructor(strategy) {
|
|
101
|
+
this.label = `mechanical/${strategy}`;
|
|
102
|
+
}
|
|
103
|
+
async decide() {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
error: "mechanical benchmark provider has no model — runCycle must short-circuit before decide() (this call is a bug)",
|
|
107
|
+
};
|
|
85
108
|
}
|
|
86
109
|
}
|
|
87
110
|
class AnthropicProvider {
|
|
@@ -127,7 +150,9 @@ class AnthropicProvider {
|
|
|
127
150
|
completionTokens: json.usage.output_tokens ?? 0,
|
|
128
151
|
}
|
|
129
152
|
: undefined;
|
|
130
|
-
return text
|
|
153
|
+
return text
|
|
154
|
+
? { ok: true, text, usage }
|
|
155
|
+
: { ok: false, error: "anthropic returned empty content" };
|
|
131
156
|
}
|
|
132
157
|
catch (err) {
|
|
133
158
|
return { ok: false, error: callError(err, timeoutMs) };
|
|
@@ -162,7 +187,10 @@ class OpenAiCompatProvider {
|
|
|
162
187
|
max_tokens: input.maxTokens ?? 1024,
|
|
163
188
|
response_format: { type: "json_object" },
|
|
164
189
|
messages: [
|
|
165
|
-
{
|
|
190
|
+
{
|
|
191
|
+
role: "system",
|
|
192
|
+
content: applyReasoningToggle(this.model, input.system),
|
|
193
|
+
},
|
|
166
194
|
{ role: "user", content: input.user },
|
|
167
195
|
],
|
|
168
196
|
}),
|
|
@@ -182,7 +210,9 @@ class OpenAiCompatProvider {
|
|
|
182
210
|
completionTokens: json.usage.completion_tokens ?? 0,
|
|
183
211
|
}
|
|
184
212
|
: undefined;
|
|
185
|
-
return text
|
|
213
|
+
return text
|
|
214
|
+
? { ok: true, text, usage }
|
|
215
|
+
: { ok: false, error: "provider returned empty content" };
|
|
186
216
|
}
|
|
187
217
|
catch (err) {
|
|
188
218
|
return { ok: false, error: callError(err, timeoutMs) };
|
|
@@ -197,6 +227,11 @@ export function selectProvider(spec, env, fetchFn = fetch) {
|
|
|
197
227
|
throw new Error("no model configured: set model.provider + model.name in the agent (self-host needs an explicit model)");
|
|
198
228
|
}
|
|
199
229
|
const { provider, name, baseUrl } = spec.model;
|
|
230
|
+
// Mechanical benchmark agents need NO model key: they never call a model. Return
|
|
231
|
+
// the stub before the env-key requirement so a benchmark can be constructed with
|
|
232
|
+
// no ANTHROPIC/NVIDIA/etc. key present.
|
|
233
|
+
if (provider === "mechanical")
|
|
234
|
+
return new MechanicalProvider(name);
|
|
200
235
|
const key = envKey(provider, env);
|
|
201
236
|
if (!key) {
|
|
202
237
|
const varName = provider === "anthropic"
|
package/dist/agent/resolve.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// YAML/frontmatter feeds rawFrontmatter; only markdown bodies feed
|
|
16
16
|
// mergedProse.
|
|
17
17
|
import { readFileSync, existsSync, statSync, lstatSync } from "node:fs";
|
|
18
|
-
import { resolve as resolvePath, relative as relativePath, join, isAbsolute } from "node:path";
|
|
18
|
+
import { resolve as resolvePath, relative as relativePath, join, isAbsolute, } from "node:path";
|
|
19
19
|
import { parse as parseYaml } from "yaml";
|
|
20
20
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
21
21
|
import { sha256, toPosix, isPathInside, boundTail, scanForSecrets, } from "./util.js";
|
|
@@ -43,7 +43,6 @@ const CONFIG_BLOCKS = [
|
|
|
43
43
|
"objective",
|
|
44
44
|
"capabilities",
|
|
45
45
|
];
|
|
46
|
-
const IDENTITY_KEYS = ["name", "description", "spec", "mode"];
|
|
47
46
|
const JOURNAL_MAX_LINES = 200;
|
|
48
47
|
const JOURNAL_MAX_BYTES = 8_000;
|
|
49
48
|
// Optional prose files (markdown the LLM reads), in assembly order.
|
|
@@ -81,7 +80,11 @@ function rel(dir, abs) {
|
|
|
81
80
|
function safePath(ctx, ref, label) {
|
|
82
81
|
const syn = refSyntaxIssue(ref);
|
|
83
82
|
if (syn) {
|
|
84
|
-
ctx.issues.push({
|
|
83
|
+
ctx.issues.push({
|
|
84
|
+
code: "unsafe_ref",
|
|
85
|
+
path: ref,
|
|
86
|
+
message: `${label} "${ref}": ${syn}`,
|
|
87
|
+
});
|
|
85
88
|
return null;
|
|
86
89
|
}
|
|
87
90
|
const target = resolvePath(ctx.dir, ref);
|
|
@@ -221,7 +224,13 @@ function resolveSingleFile(abs) {
|
|
|
221
224
|
},
|
|
222
225
|
]);
|
|
223
226
|
}
|
|
224
|
-
const ctx = {
|
|
227
|
+
const ctx = {
|
|
228
|
+
dir,
|
|
229
|
+
issues,
|
|
230
|
+
hashes: {},
|
|
231
|
+
mergeOrder: [],
|
|
232
|
+
seenLower: new Map(),
|
|
233
|
+
};
|
|
225
234
|
const r = toPosix(abs);
|
|
226
235
|
ctx.hashes[r] = sha256(content);
|
|
227
236
|
ctx.mergeOrder.push(r);
|
|
@@ -269,7 +278,13 @@ function loadSkillList(ctx, frontmatter) {
|
|
|
269
278
|
return [];
|
|
270
279
|
}
|
|
271
280
|
function resolveDirectory(dir) {
|
|
272
|
-
const ctx = {
|
|
281
|
+
const ctx = {
|
|
282
|
+
dir,
|
|
283
|
+
issues: [],
|
|
284
|
+
hashes: {},
|
|
285
|
+
mergeOrder: [],
|
|
286
|
+
seenLower: new Map(),
|
|
287
|
+
};
|
|
273
288
|
const keystoneAbs = findKeystone(dir);
|
|
274
289
|
if (!keystoneAbs) {
|
|
275
290
|
throw new ResolveError([
|
|
@@ -507,7 +522,9 @@ export function isSkillProseSource(source) {
|
|
|
507
522
|
// blank line. The resolver uses this for mergedProse; the run path reuses it to
|
|
508
523
|
// re-assemble a skills-ablated prompt deterministically.
|
|
509
524
|
export function mergeProseParts(parts) {
|
|
510
|
-
return parts
|
|
525
|
+
return parts
|
|
526
|
+
.map((p) => `<!-- ${p.source} -->\n${p.text.trim()}`)
|
|
527
|
+
.join("\n\n");
|
|
511
528
|
}
|
|
512
529
|
export function resolveAgent(inputPath) {
|
|
513
530
|
const abs = resolvePath(inputPath);
|
package/dist/agent/resolvePm.js
CHANGED
|
@@ -35,13 +35,24 @@ export function resolvePmRef(action, pmMarkets) {
|
|
|
35
35
|
if (ref) {
|
|
36
36
|
const mkt = pmMarkets.find((m) => trimLower(m.ref) === ref);
|
|
37
37
|
if (!mkt) {
|
|
38
|
-
const
|
|
39
|
-
|
|
38
|
+
const listed = pmMarkets.length;
|
|
39
|
+
const known = listed
|
|
40
|
+
? `pm1..pm${listed}`
|
|
40
41
|
: "(none discovered this cycle)";
|
|
42
|
+
// Trace-reading diagnostic (no behaviour change — still pm_ref_unknown). The
|
|
43
|
+
// refs are stamped contiguous pm1..pmN every cycle, so an unmatched ref is
|
|
44
|
+
// either INVENTED — its index runs past what was listed (or nothing was
|
|
45
|
+
// listed at all), i.e. the model made up a market that was never on the board
|
|
46
|
+
// (the pm_ref hallucination this guard exists for) — or STALE: an in-range ref
|
|
47
|
+
// that still doesn't match (e.g. carried over from a prior cycle's board).
|
|
48
|
+
const n = Number.parseInt(ref.replace(/^pm/i, ""), 10);
|
|
49
|
+
const diag = listed === 0 || !Number.isFinite(n) || n < 1 || n > listed
|
|
50
|
+
? "invented (ref index is beyond the markets listed this cycle)"
|
|
51
|
+
: "stale (in-range ref does not match this cycle's listed markets)";
|
|
41
52
|
return {
|
|
42
53
|
ok: false,
|
|
43
54
|
code: "pm_ref_unknown",
|
|
44
|
-
reason: `ref ${ref} is not one of this cycle's listed PM markets ${known}`,
|
|
55
|
+
reason: `ref ${ref} is not one of this cycle's listed PM markets ${known} — ${diag}`,
|
|
45
56
|
};
|
|
46
57
|
}
|
|
47
58
|
// Canonicalise: take the triple from the matched market, drop the ref.
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// agentTrace stamped on every traced call, and the export at the end.
|
|
3
3
|
import { shortId } from "./util.js";
|
|
4
4
|
export function makeRunId(spec) {
|
|
5
|
-
const slug = (spec.name || "agent")
|
|
5
|
+
const slug = (spec.name || "agent")
|
|
6
|
+
.replace(/[^a-z0-9-]+/gi, "-")
|
|
7
|
+
.toLowerCase();
|
|
6
8
|
return `${slug}-${shortId()}`;
|
|
7
9
|
}
|
|
8
10
|
export function makeDecisionId(cycle) {
|
|
@@ -19,5 +21,7 @@ export function makeTrace(runId, decisionId, spec, confidence, rationaleSummary)
|
|
|
19
21
|
}
|
|
20
22
|
export async function exportRunEvidence(client, runId) {
|
|
21
23
|
const r = await client.exportRunEvidence(runId);
|
|
22
|
-
return r.ok
|
|
24
|
+
return r.ok
|
|
25
|
+
? r.data
|
|
26
|
+
: { error: `run-evidence export failed (HTTP ${r.status})` };
|
|
23
27
|
}
|
package/dist/agent/runner.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CoinRithmClient } from "./client.js";
|
|
1
|
+
import { CoinRithmClient, ProvenanceReport } from "./client.js";
|
|
2
2
|
import { Provider } from "./providers.js";
|
|
3
|
-
import { AgentSpec, RunState, CycleResult, ProposedAction, QuoteEvidence } from "./types.js";
|
|
3
|
+
import { AgentSpec, RunState, CycleResult, Decision, ProposedAction, PmMarket, PostedOpportunity, QuoteEvidence } from "./types.js";
|
|
4
4
|
export interface RunnerDeps {
|
|
5
5
|
client: CoinRithmClient;
|
|
6
6
|
provider: Provider;
|
|
@@ -11,11 +11,17 @@ export interface RunnerDeps {
|
|
|
11
11
|
stateFile?: string;
|
|
12
12
|
log?: (line: string) => void;
|
|
13
13
|
}
|
|
14
|
+
export declare function houseAgentForecastEnabled(): boolean;
|
|
15
|
+
export declare function agentOpportunityCaptureEnabled(): boolean;
|
|
16
|
+
export declare function runnerRuntimeKind(): ProvenanceReport["runtimeKind"];
|
|
17
|
+
export declare function buildRunnerProvenance(spec: AgentSpec): ProvenanceReport;
|
|
18
|
+
export declare function sanitizeForecastProbability(raw: unknown): number | undefined;
|
|
14
19
|
export declare function repairFuturesTakeProfit(action: ProposedAction, quote?: QuoteEvidence): {
|
|
15
20
|
action: ProposedAction;
|
|
16
21
|
repaired: boolean;
|
|
17
22
|
};
|
|
18
23
|
export declare function rationaleForAction(a: ProposedAction, decisionRationale: string | undefined, perActionSummary: string | undefined, totalActions: number): string | undefined;
|
|
24
|
+
export declare function buildSkipOpportunity(decision: Decision, pmMarkets: PmMarket[], forecastEnabled: boolean): PostedOpportunity | null;
|
|
19
25
|
export declare function runCycle(deps: RunnerDeps): Promise<CycleResult>;
|
|
20
26
|
export interface LoopOptions {
|
|
21
27
|
once?: boolean;
|