@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.
Files changed (46) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/README.md +9 -7
  3. package/dist/agent/act.d.ts +2 -2
  4. package/dist/agent/act.js +24 -3
  5. package/dist/agent/cli.js +59 -18
  6. package/dist/agent/client.d.ts +33 -0
  7. package/dist/agent/client.js +34 -7
  8. package/dist/agent/decision.d.ts +3 -0
  9. package/dist/agent/decision.js +26 -3
  10. package/dist/agent/decisionValidator.js +2 -1
  11. package/dist/agent/deploymentOverlay.js +25 -5
  12. package/dist/agent/engine.d.ts +2 -1
  13. package/dist/agent/engine.js +4 -1
  14. package/dist/agent/extract.js +3 -1
  15. package/dist/agent/gate.js +25 -5
  16. package/dist/agent/index.js +0 -1
  17. package/dist/agent/indicators.js +4 -2
  18. package/dist/agent/manifest.js +1 -1
  19. package/dist/agent/mechanical.d.ts +36 -0
  20. package/dist/agent/mechanical.js +286 -0
  21. package/dist/agent/observe.js +120 -52
  22. package/dist/agent/prompt.d.ts +3 -1
  23. package/dist/agent/prompt.js +17 -6
  24. package/dist/agent/providers.js +39 -4
  25. package/dist/agent/resolve.js +23 -6
  26. package/dist/agent/resolvePm.js +14 -3
  27. package/dist/agent/runEvidence.js +6 -2
  28. package/dist/agent/runner.d.ts +8 -2
  29. package/dist/agent/runner.js +363 -59
  30. package/dist/agent/scorecard.js +12 -4
  31. package/dist/agent/setups.js +57 -9
  32. package/dist/agent/skill.js +1 -1
  33. package/dist/agent/state.js +9 -4
  34. package/dist/agent/types.d.ts +17 -2
  35. package/dist/agent/types.js +2 -1
  36. package/dist/agent/util.js +11 -4
  37. package/dist/agent/version.d.ts +1 -1
  38. package/dist/agent/version.js +1 -1
  39. package/dist/client.d.ts +33 -0
  40. package/dist/client.js +12 -3
  41. package/dist/executionPolicy.d.ts +2 -0
  42. package/dist/executionPolicy.js +21 -0
  43. package/dist/http.js +10 -2
  44. package/dist/tools.d.ts +1 -0
  45. package/dist/tools.js +214 -29
  46. package/package.json +9 -1
@@ -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"}', '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.');
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('{"type":"pm_open","ref":"pmN","stakeMusd","confidence":0..1} (set "ref" to the `ref` of the ONE observation.pmMarkets entry you are betting e.g. "pm3" copied EXACTLY; stakeMusd >= 10)');
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. Leaving a clearly mispriced crypto market untraded is the same mistake as ignoring a flagged setup. (For non-crypto events you have no special edge; skip unless the odds are obviously off.)",
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
- '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.',
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
- '- 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.',
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.",
@@ -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) ? `detailed thinking off\n\n${system}` : system;
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 ? { ok: true, text, usage } : { ok: false, error: "anthropic returned empty content" };
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
- { role: "system", content: applyReasoningToggle(this.model, input.system) },
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 ? { ok: true, text, usage } : { ok: false, error: "provider returned empty content" };
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"
@@ -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({ code: "unsafe_ref", path: ref, message: `${label} "${ref}": ${syn}` });
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 = { dir, issues, hashes: {}, mergeOrder: [], seenLower: new Map() };
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 = { dir, issues: [], hashes: {}, mergeOrder: [], seenLower: new Map() };
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.map((p) => `<!-- ${p.source} -->\n${p.text.trim()}`).join("\n\n");
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);
@@ -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 known = pmMarkets.length
39
- ? `pm1..pm${pmMarkets.length}`
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").replace(/[^a-z0-9-]+/gi, "-").toLowerCase();
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 ? r.data : { error: `run-evidence export failed (HTTP ${r.status})` };
24
+ return r.ok
25
+ ? r.data
26
+ : { error: `run-evidence export failed (HTTP ${r.status})` };
23
27
  }
@@ -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;