@coinrithm/mcp-trading 0.7.15 → 0.7.16
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 +127 -8
- package/README.md +18 -3
- package/dist/agent/bench/bench.d.ts +59 -0
- package/dist/agent/bench/bench.js +319 -0
- package/dist/agent/bench/cassette.d.ts +97 -0
- package/dist/agent/bench/cassette.js +181 -0
- package/dist/agent/bench/costs.d.ts +28 -0
- package/dist/agent/bench/costs.js +60 -0
- package/dist/agent/bench/labelBuilder.d.ts +123 -0
- package/dist/agent/bench/labelBuilder.js +282 -0
- package/dist/agent/bench/labels.d.ts +75 -0
- package/dist/agent/bench/labels.js +259 -0
- package/dist/agent/bench/recordingClient.d.ts +51 -0
- package/dist/agent/bench/recordingClient.js +193 -0
- package/dist/agent/bench/replayClient.d.ts +41 -0
- package/dist/agent/bench/replayClient.js +262 -0
- package/dist/agent/bench/report.d.ts +115 -0
- package/dist/agent/bench/report.js +435 -0
- package/dist/agent/cli.d.ts +25 -0
- package/dist/agent/cli.js +300 -2
- package/dist/agent/client.d.ts +1 -0
- package/dist/agent/client.js +6 -0
- package/dist/agent/configurationWarnings.js +11 -0
- package/dist/agent/decision.d.ts +3 -0
- package/dist/agent/decision.js +85 -3
- package/dist/agent/decisionValidator.d.ts +30 -0
- package/dist/agent/decisionValidator.js +147 -2
- package/dist/agent/engine.d.ts +3 -3
- package/dist/agent/engine.js +3 -3
- package/dist/agent/observe.d.ts +27 -2
- package/dist/agent/observe.js +529 -89
- package/dist/agent/prompt.d.ts +4 -1
- package/dist/agent/prompt.js +98 -7
- package/dist/agent/promptTables.d.ts +1 -0
- package/dist/agent/promptTables.js +33 -0
- package/dist/agent/providerCapabilities.d.ts +6 -0
- package/dist/agent/providerCapabilities.js +23 -0
- package/dist/agent/providers.d.ts +25 -7
- package/dist/agent/providers.js +188 -48
- package/dist/agent/resolve.js +3 -0
- package/dist/agent/runner.d.ts +8 -0
- package/dist/agent/runner.js +59 -15
- package/dist/agent/setups.d.ts +2 -1
- package/dist/agent/setups.js +17 -16
- package/dist/agent/signals.d.ts +33 -0
- package/dist/agent/signals.js +89 -0
- package/dist/agent/skill.js +37 -0
- package/dist/agent/skillValidator.js +33 -0
- package/dist/agent/strictLint.js +29 -0
- package/dist/agent/types.d.ts +149 -12
- package/dist/agent/universe.d.ts +78 -0
- package/dist/agent/universe.js +215 -0
- package/package.json +3 -2
package/dist/agent/cli.js
CHANGED
|
@@ -18,12 +18,17 @@ import { buildManifest, writeManifest } from "./manifest.js";
|
|
|
18
18
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
19
19
|
import { renderFolderOfOne, ejectFiles, PRESET_NAMES, } from "./templates.js";
|
|
20
20
|
import { COINRITHM_API } from "./version.js";
|
|
21
|
-
import { stableStringify, envFlag } from "./util.js";
|
|
22
|
-
import { CoinRithmClient } from "./client.js";
|
|
21
|
+
import { stableStringify, envFlag, parseCadenceMs, sleep } from "./util.js";
|
|
22
|
+
import { CoinRithmClient, DEFAULT_BASE_URL } from "./client.js";
|
|
23
23
|
import { selectProvider } from "./providers.js";
|
|
24
24
|
import { runLoop } from "./runner.js";
|
|
25
25
|
import { loadState, saveState } from "./state.js";
|
|
26
26
|
import { makeRunId } from "./runEvidence.js";
|
|
27
|
+
import { readCorpus, writeCassette } from "./bench/cassette.js";
|
|
28
|
+
import { recordCassette } from "./bench/recordingClient.js";
|
|
29
|
+
import { runBench } from "./bench/bench.js";
|
|
30
|
+
import { buildPmLabels, buildPriceLabels, DEFAULT_LABEL_HORIZON_HOURS, } from "./bench/labelBuilder.js";
|
|
31
|
+
import { parseLabelFile } from "./bench/labels.js";
|
|
27
32
|
const fail = (lines) => ({ ok: false, code: 1, lines });
|
|
28
33
|
function issuesResult(issues, header) {
|
|
29
34
|
return {
|
|
@@ -438,8 +443,254 @@ export async function cmdRun(path, opts = {}) {
|
|
|
438
443
|
release();
|
|
439
444
|
}
|
|
440
445
|
}
|
|
446
|
+
const MAX_RECORD_CYCLES = 10_000;
|
|
447
|
+
// Record bench cassettes (see bench/cassette.ts). Read-only by construction:
|
|
448
|
+
// every cycle runs DRY-RUN through a recording transport that refuses all
|
|
449
|
+
// non-GET requests, with a brain that never calls a model, so only
|
|
450
|
+
// COINRITHM_API_KEY is needed and no model key is read. The agent's own state
|
|
451
|
+
// file is not touched; each recorded cycle starts from a fresh run state.
|
|
452
|
+
export async function cmdRecord(path, opts = {}) {
|
|
453
|
+
if (!opts.out)
|
|
454
|
+
return fail(["record needs --out <dir> for the cassettes"]);
|
|
455
|
+
const cycles = opts.cycles ?? 1;
|
|
456
|
+
if (!Number.isInteger(cycles) || cycles < 1 || cycles > MAX_RECORD_CYCLES)
|
|
457
|
+
return fail([`--cycles must be an integer from 1 to ${MAX_RECORD_CYCLES}`]);
|
|
458
|
+
let loaded;
|
|
459
|
+
try {
|
|
460
|
+
loaded = loadAgent(path, "self-host");
|
|
461
|
+
}
|
|
462
|
+
catch (e) {
|
|
463
|
+
if (e instanceof ResolveError)
|
|
464
|
+
return issuesResult(e.issues, "resolve failed");
|
|
465
|
+
throw e;
|
|
466
|
+
}
|
|
467
|
+
const everyMs = opts.every !== undefined
|
|
468
|
+
? parseCadenceMs(opts.every)
|
|
469
|
+
: (parseCadenceMs(loaded.spec.trigger.cadence) ?? 3_600_000);
|
|
470
|
+
if (everyMs === null)
|
|
471
|
+
return fail([`--every "${opts.every}" is not a cadence like 5m or 1h`]);
|
|
472
|
+
const apiKey = process.env.COINRITHM_API_KEY;
|
|
473
|
+
if (!apiKey)
|
|
474
|
+
return fail([
|
|
475
|
+
"COINRITHM_API_KEY is not set (recording reads your paper account; it never writes)",
|
|
476
|
+
]);
|
|
477
|
+
const mergedProse = runtimeProse(loaded.resolved);
|
|
478
|
+
const lines = [
|
|
479
|
+
`record DRY-RUN (reads only, no model call): ${loaded.spec.name}, ${cycles} cycle(s)`,
|
|
480
|
+
];
|
|
481
|
+
const files = [];
|
|
482
|
+
for (let i = 0; i < cycles; i++) {
|
|
483
|
+
let cassette;
|
|
484
|
+
try {
|
|
485
|
+
cassette = await recordCassette({
|
|
486
|
+
spec: loaded.spec,
|
|
487
|
+
mergedProse,
|
|
488
|
+
apiKey,
|
|
489
|
+
baseUrl: process.env.COINRITHM_API_URL || undefined,
|
|
490
|
+
fetchFn: opts.fetchFn,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
catch (e) {
|
|
494
|
+
lines.push(`✗ cycle ${i + 1} failed: ${e.message}`);
|
|
495
|
+
return { ok: false, code: 1, lines, data: files };
|
|
496
|
+
}
|
|
497
|
+
files.push(writeCassette(resolvePath(opts.out), cassette));
|
|
498
|
+
const cycle = cassette.recordCycle;
|
|
499
|
+
lines.push(`recorded ${cassette.id}: ${cassette.responses.length} read(s), asOf ${cassette.asOf}, cycle ${cycle.decisionType ?? cycle.decision}${cycle.skipReason ? ` (${cycle.skipReason})` : ""}` +
|
|
500
|
+
(cassette.refusedRequests.length
|
|
501
|
+
? `, ${cassette.refusedRequests.length} write(s) refused`
|
|
502
|
+
: ""));
|
|
503
|
+
if (i < cycles - 1)
|
|
504
|
+
await (opts.sleepFn ?? sleep)(everyMs);
|
|
505
|
+
}
|
|
506
|
+
return { ok: true, code: 0, lines, data: files };
|
|
507
|
+
}
|
|
508
|
+
// Write outcome labels for a recorded corpus (bench/labelBuilder.ts), reads
|
|
509
|
+
// only: price bars from candles published after each cassette's asOf (with
|
|
510
|
+
// COINRITHM_API_KEY), and PM settlement from the public event verdict
|
|
511
|
+
// (settlementEligible + "settle" + a provider won/lost outcome result; any
|
|
512
|
+
// other state stays unlabelled).
|
|
513
|
+
export async function cmdLabel(opts = {}) {
|
|
514
|
+
if (!opts.corpus)
|
|
515
|
+
return fail(["label needs --corpus <dir>"]);
|
|
516
|
+
const horizonHours = opts.horizonHours ?? DEFAULT_LABEL_HORIZON_HOURS;
|
|
517
|
+
if (!Number.isFinite(horizonHours) || horizonHours <= 0 || horizonHours > 720)
|
|
518
|
+
return fail(["--horizon-hours must be a number in (0, 720]"]);
|
|
519
|
+
const apiKey = process.env.COINRITHM_API_KEY;
|
|
520
|
+
if (!apiKey)
|
|
521
|
+
return fail([
|
|
522
|
+
"COINRITHM_API_KEY is not set (labels read public market candles through your key; nothing is written)",
|
|
523
|
+
]);
|
|
524
|
+
const dir = resolvePath(opts.corpus);
|
|
525
|
+
let corpus;
|
|
526
|
+
try {
|
|
527
|
+
corpus = readCorpus(dir);
|
|
528
|
+
}
|
|
529
|
+
catch (e) {
|
|
530
|
+
return fail([e.message]);
|
|
531
|
+
}
|
|
532
|
+
const client = new CoinRithmClient({
|
|
533
|
+
apiKey,
|
|
534
|
+
baseUrl: process.env.COINRITHM_API_URL || undefined,
|
|
535
|
+
fetchFn: opts.fetchFn,
|
|
536
|
+
});
|
|
537
|
+
const results = await buildPriceLabels(corpus.cassettes, {
|
|
538
|
+
fetchCandles: (coinId, range) => client.candles(coinId, range),
|
|
539
|
+
horizonHours,
|
|
540
|
+
nowMs: opts.nowMs ?? Date.now(),
|
|
541
|
+
existing: corpus.labels,
|
|
542
|
+
overwrite: opts.overwrite,
|
|
543
|
+
});
|
|
544
|
+
const base = (process.env.COINRITHM_API_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
545
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
546
|
+
const pmResults = await buildPmLabels(corpus.cassettes, {
|
|
547
|
+
// The public event detail: the same verdict the event page shows.
|
|
548
|
+
fetchEvent: async (source, slug) => {
|
|
549
|
+
const res = await fetchFn(`${base}/api/prediction-markets/events/${encodeURIComponent(source)}/${encodeURIComponent(slug)}`, { method: "GET", signal: AbortSignal.timeout(15_000) });
|
|
550
|
+
return {
|
|
551
|
+
ok: res.ok,
|
|
552
|
+
status: res.status,
|
|
553
|
+
data: res.ok ? await res.json() : null,
|
|
554
|
+
};
|
|
555
|
+
},
|
|
556
|
+
existing: corpus.labels,
|
|
557
|
+
overwrite: opts.overwrite,
|
|
558
|
+
});
|
|
559
|
+
const lines = [
|
|
560
|
+
`label (reads only): ${corpus.cassettes.length} cassette(s), horizon ${horizonHours} h`,
|
|
561
|
+
];
|
|
562
|
+
let written = 0;
|
|
563
|
+
for (const r of results) {
|
|
564
|
+
const pm = pmResults.find((p) => p.id === r.id);
|
|
565
|
+
const notes = [];
|
|
566
|
+
let file;
|
|
567
|
+
if (r.status === "built") {
|
|
568
|
+
file = { ...r.file };
|
|
569
|
+
notes.push(`${r.range} bars for ${r.symbols.length} symbol(s)${r.missing.length ? `, missing ${r.missing.join(",")}` : ""}`);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
notes.push(`prices ${r.status}${r.status === "not_yet" ? ` (labelable after ${r.labelableAfter})` : ""}${r.status === "horizon_mismatch" ? ` (existing file labels ${r.existingHorizonHours ?? "no"} h, not ${horizonHours} h; rerun with --overwrite)` : ""}`);
|
|
573
|
+
}
|
|
574
|
+
if (pm) {
|
|
575
|
+
notes.push(`pm ${pm.labelled} new settled outcome(s) from ${pm.events} event(s)${pm.failed ? `, ${pm.failed} read(s) failed` : ""}`);
|
|
576
|
+
const hasPm = Object.keys(pm.pm).length > 0;
|
|
577
|
+
if (opts.overwrite) {
|
|
578
|
+
// --overwrite REPLACES the PM part, even with nothing: a label that
|
|
579
|
+
// was settled before but is void/unresolved now must not survive.
|
|
580
|
+
const base = file ?? corpus.labels[r.id];
|
|
581
|
+
if (base || hasPm) {
|
|
582
|
+
const { pm: _replaced, ...rest } = base ?? {};
|
|
583
|
+
file = hasPm ? { ...rest, pm: pm.pm } : rest;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
else if (pm.labelled > 0 || (file && hasPm))
|
|
587
|
+
file = { ...(file ?? corpus.labels[r.id] ?? {}), pm: pm.pm };
|
|
588
|
+
}
|
|
589
|
+
lines.push(`${r.id}: ${notes.join("; ")}`);
|
|
590
|
+
if (!file)
|
|
591
|
+
continue;
|
|
592
|
+
const path = join(dir, "labels", `${r.id}.json`);
|
|
593
|
+
// The bench reads it back through the same validator; fail closed here.
|
|
594
|
+
parseLabelFile(file, `labels/${r.id}.json`);
|
|
595
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
596
|
+
writeFileSync(path, `${JSON.stringify(file, null, 2)}\n`, "utf8");
|
|
597
|
+
written += 1;
|
|
598
|
+
}
|
|
599
|
+
lines.push(`wrote ${written} label file(s); PM outcomes without a settled platform verdict stay unlabelled`);
|
|
600
|
+
return { ok: true, code: 0, lines, data: { prices: results, pm: pmResults } };
|
|
601
|
+
}
|
|
602
|
+
// Compare agent variants on a recorded corpus (see bench/bench.ts). Each
|
|
603
|
+
// variant runs with its own model, built exactly as `run` builds it from the
|
|
604
|
+
// variant's model config and the environment's keys. Never writes.
|
|
605
|
+
export async function cmdBench(opts = {}) {
|
|
606
|
+
if (!opts.corpus)
|
|
607
|
+
return fail(["bench needs --corpus <dir>"]);
|
|
608
|
+
if (!opts.variants || opts.variants.length === 0)
|
|
609
|
+
return fail(["bench needs at least one --variant name=<agentPath>"]);
|
|
610
|
+
if (opts.seed !== undefined &&
|
|
611
|
+
(!Number.isSafeInteger(opts.seed) || opts.seed < 0))
|
|
612
|
+
return fail(["--seed must be a non-negative integer"]);
|
|
613
|
+
let corpus;
|
|
614
|
+
try {
|
|
615
|
+
corpus = readCorpus(resolvePath(opts.corpus));
|
|
616
|
+
}
|
|
617
|
+
catch (e) {
|
|
618
|
+
return fail([e.message]);
|
|
619
|
+
}
|
|
620
|
+
const variants = [];
|
|
621
|
+
for (const arg of opts.variants) {
|
|
622
|
+
const eq = arg.indexOf("=");
|
|
623
|
+
if (eq <= 0 || eq === arg.length - 1)
|
|
624
|
+
return fail([`--variant "${arg}" must look like name=<agentPath>`]);
|
|
625
|
+
const name = arg.slice(0, eq);
|
|
626
|
+
const agentPath = arg.slice(eq + 1);
|
|
627
|
+
let loaded;
|
|
628
|
+
try {
|
|
629
|
+
loaded = loadAgent(agentPath, "self-host");
|
|
630
|
+
}
|
|
631
|
+
catch (e) {
|
|
632
|
+
if (e instanceof ResolveError)
|
|
633
|
+
return issuesResult(e.issues, `variant ${name}: resolve failed`);
|
|
634
|
+
throw e;
|
|
635
|
+
}
|
|
636
|
+
let provider;
|
|
637
|
+
try {
|
|
638
|
+
provider = opts.providerFor
|
|
639
|
+
? opts.providerFor(loaded.spec)
|
|
640
|
+
: selectProvider(loaded.spec, process.env, fetch);
|
|
641
|
+
}
|
|
642
|
+
catch (e) {
|
|
643
|
+
return fail([`variant ${name}: ${e.message}`]);
|
|
644
|
+
}
|
|
645
|
+
variants.push({
|
|
646
|
+
name,
|
|
647
|
+
spec: loaded.spec,
|
|
648
|
+
mergedProse: runtimeProse(loaded.resolved),
|
|
649
|
+
provider,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
const lines = [
|
|
653
|
+
`bench DRY-RUN: ${corpus.cassettes.length} cassette(s) x ${variants.length} variant(s) x ${opts.repeats ?? 3} repeat(s)`,
|
|
654
|
+
];
|
|
655
|
+
if (corpus.ignored.length)
|
|
656
|
+
lines.push(`ignored non-cassette file(s): ${corpus.ignored.join(", ")}`);
|
|
657
|
+
let report;
|
|
658
|
+
try {
|
|
659
|
+
report = await runBench({
|
|
660
|
+
cassettes: corpus.cassettes,
|
|
661
|
+
labels: corpus.labels,
|
|
662
|
+
variants,
|
|
663
|
+
repeats: opts.repeats,
|
|
664
|
+
seed: opts.seed,
|
|
665
|
+
baselines: opts.baselines,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
catch (e) {
|
|
669
|
+
return fail([...lines, `✗ ${e.message}`]);
|
|
670
|
+
}
|
|
671
|
+
const summary = report.variants;
|
|
672
|
+
for (const [name, v] of Object.entries(summary)) {
|
|
673
|
+
const codes = Object.entries(v.all.actions.rejectCodes)
|
|
674
|
+
.map(([c, n]) => `${c} ${n}`)
|
|
675
|
+
.join(", ");
|
|
676
|
+
lines.push(`${name}: ${v.all.cycles} cycle(s), accepted ${v.all.actions.accepted}, rejected ${v.all.actions.rejected}${codes ? ` (${codes})` : ""}, model failures ${v.all.modelFailures}, cycles missing inputs ${v.all.cyclesWithMissingInputs}, repeat consistency ${v.all.repeatConsistency ?? "n/a"}`);
|
|
677
|
+
}
|
|
678
|
+
for (const c of report.comparisons) {
|
|
679
|
+
const accepted = c.all.metrics.acceptedActions;
|
|
680
|
+
lines.push(`${c.a} vs ${c.b}: overlap ${c.all.actionOverlapJaccard ?? "n/a"}, accepted diff ${accepted.meanDiff ?? "n/a"} (95% CI ${accepted.ci95 ? `${accepted.ci95[0]}..${accepted.ci95[1]}` : "n/a"}, n=${accepted.n})`);
|
|
681
|
+
}
|
|
682
|
+
if (opts.out) {
|
|
683
|
+
writeFileSync(resolvePath(opts.out), `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
684
|
+
lines.push(`wrote ${resolvePath(opts.out)}`);
|
|
685
|
+
}
|
|
686
|
+
lines.push(`contentHash ${report.contentHash}`);
|
|
687
|
+
return { ok: true, code: 0, lines, data: report };
|
|
688
|
+
}
|
|
441
689
|
function parseFlags(args) {
|
|
442
690
|
const out = { _: [] };
|
|
691
|
+
// A missing or non-numeric value becomes NaN so the command rejects it,
|
|
692
|
+
// never silently falls back to its default.
|
|
693
|
+
const num = (v) => (v === undefined ? NaN : Number(v));
|
|
443
694
|
for (let i = 0; i < args.length; i++) {
|
|
444
695
|
const a = args[i];
|
|
445
696
|
if (a === "--hosted")
|
|
@@ -462,6 +713,26 @@ function parseFlags(args) {
|
|
|
462
713
|
out.state = args[++i];
|
|
463
714
|
else if (a === "--expect-definition")
|
|
464
715
|
out.expectDefinition = args[++i] ?? "";
|
|
716
|
+
else if (a === "--out")
|
|
717
|
+
out.out = args[++i];
|
|
718
|
+
else if (a === "--cycles")
|
|
719
|
+
out.cycles = num(args[++i]);
|
|
720
|
+
else if (a === "--every")
|
|
721
|
+
out.every = args[++i] ?? "";
|
|
722
|
+
else if (a === "--corpus")
|
|
723
|
+
out.corpus = args[++i];
|
|
724
|
+
else if (a === "--variant")
|
|
725
|
+
out.variant = [...(out.variant ?? []), args[++i] ?? ""];
|
|
726
|
+
else if (a === "--repeats")
|
|
727
|
+
out.repeats = num(args[++i]);
|
|
728
|
+
else if (a === "--seed")
|
|
729
|
+
out.seed = num(args[++i]);
|
|
730
|
+
else if (a === "--no-baselines")
|
|
731
|
+
out.noBaselines = true;
|
|
732
|
+
else if (a === "--horizon-hours")
|
|
733
|
+
out.horizonHours = num(args[++i]);
|
|
734
|
+
else if (a === "--overwrite")
|
|
735
|
+
out.overwrite = true;
|
|
465
736
|
else
|
|
466
737
|
out._.push(a);
|
|
467
738
|
}
|
|
@@ -476,6 +747,9 @@ function usageLines() {
|
|
|
476
747
|
" eject <agent.md | dir>",
|
|
477
748
|
" lock <path>",
|
|
478
749
|
" run <path> [--once] [--live] [--dry-run] [--state <file>] [--expect-definition sha256:...] (dry-run by default)",
|
|
750
|
+
" record <path> --out <dir> [--cycles N] [--every 5m] (bench inputs; reads only, no model call)",
|
|
751
|
+
" bench --corpus <dir> --variant a=<path> --variant b=<path> [--repeats 3] [--seed N] [--no-baselines] [--out report.json]",
|
|
752
|
+
" label --corpus <dir> [--horizon-hours 24] [--overwrite] (price outcome labels after asOf; reads only)",
|
|
479
753
|
];
|
|
480
754
|
}
|
|
481
755
|
export async function main(argv) {
|
|
@@ -513,6 +787,30 @@ export async function main(argv) {
|
|
|
513
787
|
});
|
|
514
788
|
break;
|
|
515
789
|
}
|
|
790
|
+
case "record":
|
|
791
|
+
r = await cmdRecord(pos[0] ?? ".", {
|
|
792
|
+
out: flags.out,
|
|
793
|
+
cycles: flags.cycles,
|
|
794
|
+
every: flags.every,
|
|
795
|
+
});
|
|
796
|
+
break;
|
|
797
|
+
case "label":
|
|
798
|
+
r = await cmdLabel({
|
|
799
|
+
corpus: flags.corpus,
|
|
800
|
+
horizonHours: flags.horizonHours,
|
|
801
|
+
overwrite: flags.overwrite,
|
|
802
|
+
});
|
|
803
|
+
break;
|
|
804
|
+
case "bench":
|
|
805
|
+
r = await cmdBench({
|
|
806
|
+
corpus: flags.corpus,
|
|
807
|
+
variants: flags.variant,
|
|
808
|
+
repeats: flags.repeats,
|
|
809
|
+
seed: flags.seed,
|
|
810
|
+
out: flags.out,
|
|
811
|
+
baselines: !flags.noBaselines,
|
|
812
|
+
});
|
|
813
|
+
break;
|
|
516
814
|
case undefined:
|
|
517
815
|
case "help":
|
|
518
816
|
case "--help":
|
package/dist/agent/client.d.ts
CHANGED
|
@@ -53,6 +53,7 @@ export declare class CoinRithmClient {
|
|
|
53
53
|
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
54
54
|
resolve(q: string, trace?: AgentTrace): Promise<ApiResult>;
|
|
55
55
|
cryptoMovers(direction: "gainers" | "losers", limit: number, trace?: AgentTrace): Promise<ApiResult>;
|
|
56
|
+
agentUniverse(query: Record<string, string | number>, trace?: AgentTrace): Promise<ApiResult>;
|
|
56
57
|
getPublicPmWhales(options?: {
|
|
57
58
|
timeoutMs?: number;
|
|
58
59
|
limit?: number;
|
package/dist/agent/client.js
CHANGED
|
@@ -283,6 +283,12 @@ export class CoinRithmClient {
|
|
|
283
283
|
? "/api/coins/top-losers"
|
|
284
284
|
: "/api/coins/top-gainers", { query: { limit }, trace });
|
|
285
285
|
}
|
|
286
|
+
// Market-boundaries screener (GET /api/agent/universe, read scope): rows
|
|
287
|
+
// inside the agent's declared rank band / volume floor / sectors, sorted by
|
|
288
|
+
// the agent's notion of an opportunity. Shared 60 s server cache.
|
|
289
|
+
agentUniverse(query, trace) {
|
|
290
|
+
return this.request("GET", "/api/agent/universe", { query, trace });
|
|
291
|
+
}
|
|
286
292
|
getPublicPmWhales(options) {
|
|
287
293
|
return this.publicRequest("/api/prediction-markets/whales", { limit: options?.limit }, options?.timeoutMs);
|
|
288
294
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
// Per-day counters roll over on the UTC date (util.ts dayKey), whatever the
|
|
2
|
+
// bundle declares. trigger.timezone is parsed but read by nothing else, so a
|
|
3
|
+
// non-UTC value would silently promise local-day limits.
|
|
4
|
+
const UTC_NAMES = new Set(["utc", "etc/utc", "z", "gmt", "etc/gmt"]);
|
|
1
5
|
// Advisory only: these fields remain loadable for backwards compatibility.
|
|
2
6
|
export function configurationWarnings(raw) {
|
|
3
7
|
const warnings = [];
|
|
8
|
+
const trigger = raw.trigger;
|
|
9
|
+
if (trigger && typeof trigger === "object" && !Array.isArray(trigger)) {
|
|
10
|
+
const tz = trigger.timezone;
|
|
11
|
+
if (typeof tz === "string" && !UTC_NAMES.has(tz.trim().toLowerCase())) {
|
|
12
|
+
warnings.push(`trigger.timezone "${tz}" is informational: maxTradesPerDay and maxDailyLossMusd reset at 00:00 UTC, not in this timezone`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
4
15
|
if (Array.isArray(raw.capabilities) &&
|
|
5
16
|
raw.capabilities.includes("websearch")) {
|
|
6
17
|
warnings.push("capabilities.websearch is reserved: the runner does not perform web searches");
|
package/dist/agent/decision.d.ts
CHANGED
|
@@ -537,11 +537,14 @@ export declare const DECISION_JSON_SCHEMA: {
|
|
|
537
537
|
};
|
|
538
538
|
}];
|
|
539
539
|
};
|
|
540
|
+
export declare const ACTIONS_STRING_DIAGNOSTICS: readonly ["json_array_empty_valid_decision", "json_array_nonempty_valid_decision", "json_array_invalid_decision", "json_nonarray", "invalid_json", "empty_string_valid_skip", "empty_string_other_decision", "non_json_array_text", "json5_array_valid_decision", "json5_array_invalid_decision", "over_size_limit"];
|
|
541
|
+
export type ActionsStringDiagnostic = (typeof ACTIONS_STRING_DIAGNOSTICS)[number];
|
|
540
542
|
export type ParseDecisionResult = {
|
|
541
543
|
ok: true;
|
|
542
544
|
decision: Decision;
|
|
543
545
|
} | {
|
|
544
546
|
ok: false;
|
|
545
547
|
error: string;
|
|
548
|
+
actionsStringDiagnostic?: ActionsStringDiagnostic;
|
|
546
549
|
};
|
|
547
550
|
export declare function parseDecision(text: string): ParseDecisionResult;
|
package/dist/agent/decision.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// JSON, an unknown action type, a free-form endpoint/tool name, extra unknown
|
|
4
4
|
// fields, or a missing required field — fails closed (the runner skips).
|
|
5
5
|
import { z } from "zod";
|
|
6
|
+
import JSON5 from "json5";
|
|
6
7
|
import { coerceThesis } from "./thesis.js";
|
|
7
8
|
// Small models (especially Llama 3.1 8B) frequently emit numbers as JSON strings
|
|
8
9
|
// ("12345", "0.8", "62000"). Coerce a *clean* numeric string to a number before
|
|
@@ -363,15 +364,95 @@ function coerceJson(text) {
|
|
|
363
364
|
}
|
|
364
365
|
return JSON.parse(s); // throws on invalid JSON -> caller treats as fail-closed
|
|
365
366
|
}
|
|
367
|
+
// Fixed, non-content diagnostics only. Never retain model text, identifiers,
|
|
368
|
+
// action values or reasoning to investigate a provider's encoding failures.
|
|
369
|
+
export const ACTIONS_STRING_DIAGNOSTICS = [
|
|
370
|
+
"json_array_empty_valid_decision",
|
|
371
|
+
"json_array_nonempty_valid_decision",
|
|
372
|
+
"json_array_invalid_decision",
|
|
373
|
+
"json_nonarray",
|
|
374
|
+
"invalid_json",
|
|
375
|
+
"empty_string_valid_skip",
|
|
376
|
+
"empty_string_other_decision",
|
|
377
|
+
"non_json_array_text",
|
|
378
|
+
"json5_array_valid_decision",
|
|
379
|
+
"json5_array_invalid_decision",
|
|
380
|
+
"over_size_limit",
|
|
381
|
+
];
|
|
382
|
+
function diagnoseActionsString(obj) {
|
|
383
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj))
|
|
384
|
+
return undefined;
|
|
385
|
+
const record = obj;
|
|
386
|
+
if (typeof record.actions !== "string")
|
|
387
|
+
return undefined;
|
|
388
|
+
if (record.actions.length > 65_536)
|
|
389
|
+
return "over_size_limit";
|
|
390
|
+
const text = record.actions.trim();
|
|
391
|
+
if (text === "") {
|
|
392
|
+
return record.decision === "skip" &&
|
|
393
|
+
decisionSchema.safeParse({ ...record, actions: [] }).success
|
|
394
|
+
? "empty_string_valid_skip"
|
|
395
|
+
: "empty_string_other_decision";
|
|
396
|
+
}
|
|
397
|
+
let actions;
|
|
398
|
+
try {
|
|
399
|
+
actions = JSON.parse(record.actions);
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
if (!text.startsWith("["))
|
|
403
|
+
return "invalid_json";
|
|
404
|
+
// Diagnostic only: identify alternate literal encodings without accepting
|
|
405
|
+
// them or retaining content. JSON5 never evaluates expressions. Requiring
|
|
406
|
+
// finite JSON values keeps Infinity/NaN from looking like usable actions.
|
|
407
|
+
try {
|
|
408
|
+
const candidate = JSON5.parse(text);
|
|
409
|
+
JSON.stringify(candidate, (key, value) => {
|
|
410
|
+
if (["__proto__", "constructor", "prototype"].includes(key) ||
|
|
411
|
+
(typeof value === "number" && !Number.isFinite(value)))
|
|
412
|
+
throw new Error("Unsafe diagnostic value");
|
|
413
|
+
return value;
|
|
414
|
+
});
|
|
415
|
+
return Array.isArray(candidate) &&
|
|
416
|
+
decisionSchema.safeParse({ ...record, actions: candidate }).success
|
|
417
|
+
? "json5_array_valid_decision"
|
|
418
|
+
: "json5_array_invalid_decision";
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
return "non_json_array_text";
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (!Array.isArray(actions))
|
|
425
|
+
return "json_nonarray";
|
|
426
|
+
if (!decisionSchema.safeParse({ ...record, actions }).success)
|
|
427
|
+
return "json_array_invalid_decision";
|
|
428
|
+
return actions.length === 0
|
|
429
|
+
? "json_array_empty_valid_decision"
|
|
430
|
+
: "json_array_nonempty_valid_decision";
|
|
431
|
+
}
|
|
432
|
+
function safeIssueMessage(issue) {
|
|
433
|
+
// These Zod messages echo model-supplied values or unknown field names.
|
|
434
|
+
// Other messages in this fixed schema describe types or contract limits.
|
|
435
|
+
switch (issue.code) {
|
|
436
|
+
case "invalid_enum_value":
|
|
437
|
+
return "Invalid enum value";
|
|
438
|
+
case "invalid_literal":
|
|
439
|
+
return "Invalid literal value";
|
|
440
|
+
case "unrecognized_keys":
|
|
441
|
+
return "Unrecognized field(s)";
|
|
442
|
+
default:
|
|
443
|
+
return issue.message;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
366
446
|
export function parseDecision(text) {
|
|
367
447
|
let obj;
|
|
368
448
|
try {
|
|
369
449
|
obj = normalizeDecisionVerb(coerceJson(text));
|
|
370
450
|
}
|
|
371
|
-
catch
|
|
451
|
+
catch {
|
|
372
452
|
return {
|
|
373
453
|
ok: false,
|
|
374
|
-
|
|
454
|
+
// Node's JSON syntax errors can include excerpts of private model text.
|
|
455
|
+
error: "model output is not valid JSON",
|
|
375
456
|
};
|
|
376
457
|
}
|
|
377
458
|
const res = decisionSchema.safeParse(obj);
|
|
@@ -379,8 +460,9 @@ export function parseDecision(text) {
|
|
|
379
460
|
return {
|
|
380
461
|
ok: false,
|
|
381
462
|
error: res.error.issues
|
|
382
|
-
.map((i) => `${i.path.join(".") || "(root)"}: ${i
|
|
463
|
+
.map((i) => `${i.path.join(".") || "(root)"}: ${safeIssueMessage(i)}`)
|
|
383
464
|
.join("; "),
|
|
465
|
+
actionsStringDiagnostic: diagnoseActionsString(obj),
|
|
384
466
|
};
|
|
385
467
|
}
|
|
386
468
|
const d = res.data;
|
|
@@ -20,7 +20,37 @@ export interface DecisionContext {
|
|
|
20
20
|
realizedLossTodayMusd: number;
|
|
21
21
|
targetedPositionIds: number[];
|
|
22
22
|
targetedOrderIds: number[];
|
|
23
|
+
pmEventsOpenedThisCycle?: string[];
|
|
24
|
+
pmCoinHorizonsOpenedThisCycle?: PmCoinHorizon[];
|
|
25
|
+
nowMs?: number;
|
|
23
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Edge (points) risk.pmMinEdgeGapPct requires at a fee-inclusive entry cost:
|
|
29
|
+
* pct% of the room left to 100. Cost at or above 100 leaves no room, so any
|
|
30
|
+
* positive gap rule makes the requirement unreachable rather than zero.
|
|
31
|
+
*/
|
|
32
|
+
export declare function requiredGapEdgePoints(entryPct: number, gapPct: number): number;
|
|
33
|
+
/** Ends within this of each other share a coin horizon (venue date splits). */
|
|
34
|
+
export declare const PM_COIN_HORIZON_WINDOW_MS: number;
|
|
35
|
+
/** An end beyond this is a venue sentinel, never a real horizon. */
|
|
36
|
+
export declare const PM_PLAUSIBLE_END_MAX_MS: number;
|
|
37
|
+
export interface PmCoinHorizon {
|
|
38
|
+
coins: string[];
|
|
39
|
+
endMs: number;
|
|
40
|
+
}
|
|
41
|
+
/** The later of observation.asOf and the validation clock, when known. */
|
|
42
|
+
export declare function pmReferenceMs(asOf: string, nowMs?: number): number;
|
|
43
|
+
/**
|
|
44
|
+
* The coin horizon of a market or held position, or null when it cannot be
|
|
45
|
+
* grouped: no linked coin, or an end that is missing, unparseable, not after
|
|
46
|
+
* the reference time, or more than 5 years after it (venue sentinels).
|
|
47
|
+
*/
|
|
48
|
+
export declare function pmCoinHorizonOf(item: {
|
|
49
|
+
coins?: string[];
|
|
50
|
+
endDate?: string;
|
|
51
|
+
}, refMs: number): PmCoinHorizon | null;
|
|
52
|
+
/** Event key shared by pm_open actions, held positions and markets. */
|
|
53
|
+
export declare function pmEventKey(source: string, slug: string): string;
|
|
24
54
|
/**
|
|
25
55
|
* True when a thesis says, in so many words, that it is betting AGAINST the
|
|
26
56
|
* outcome the action is buying. Deliberately narrow: it fires only when the
|