@coinrithm/mcp-trading 0.7.2 → 0.7.4

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 (47) hide show
  1. package/CHANGELOG.md +171 -113
  2. package/README.md +277 -238
  3. package/dist/agent/act.d.ts +2 -2
  4. package/dist/agent/act.js +24 -3
  5. package/dist/agent/cli.js +68 -23
  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.d.ts +4 -0
  22. package/dist/agent/observe.js +140 -53
  23. package/dist/agent/prompt.d.ts +3 -1
  24. package/dist/agent/prompt.js +17 -6
  25. package/dist/agent/providers.js +39 -4
  26. package/dist/agent/resolve.js +23 -6
  27. package/dist/agent/resolvePm.js +14 -3
  28. package/dist/agent/runEvidence.js +6 -2
  29. package/dist/agent/runner.d.ts +8 -2
  30. package/dist/agent/runner.js +363 -59
  31. package/dist/agent/scorecard.js +12 -4
  32. package/dist/agent/setups.js +57 -9
  33. package/dist/agent/skill.js +1 -1
  34. package/dist/agent/state.js +9 -4
  35. package/dist/agent/types.d.ts +17 -2
  36. package/dist/agent/types.js +2 -1
  37. package/dist/agent/util.js +11 -4
  38. package/dist/agent/version.d.ts +1 -1
  39. package/dist/agent/version.js +1 -1
  40. package/dist/client.d.ts +51 -0
  41. package/dist/client.js +30 -3
  42. package/dist/executionPolicy.d.ts +2 -0
  43. package/dist/executionPolicy.js +21 -0
  44. package/dist/http.js +13 -3
  45. package/dist/tools.d.ts +23 -0
  46. package/dist/tools.js +796 -39
  47. package/package.json +86 -78
@@ -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;