@coinrithm/mcp-trading 0.2.0 → 0.3.0
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 +32 -0
- package/README.md +2 -2
- package/dist/agent/act.d.ts +4 -0
- package/dist/agent/act.js +58 -9
- package/dist/agent/capabilityGuard.d.ts +2 -0
- package/dist/agent/capabilityGuard.js +131 -0
- package/dist/agent/cli.d.ts +21 -0
- package/dist/agent/cli.js +28 -6
- package/dist/agent/client.d.ts +107 -0
- package/dist/agent/client.js +26 -0
- package/dist/agent/decision.d.ts +137 -0
- package/dist/agent/decision.js +37 -4
- package/dist/agent/decisionValidator.d.ts +16 -0
- package/dist/agent/decisionValidator.js +118 -10
- package/dist/agent/engine.d.ts +10 -0
- package/dist/agent/engine.js +16 -0
- package/dist/agent/extract.d.ts +4 -0
- package/dist/agent/frontmatter.d.ts +5 -0
- package/dist/agent/index.d.ts +2 -0
- package/dist/agent/indicators.d.ts +44 -0
- package/dist/agent/indicators.js +135 -0
- package/dist/agent/manifest.d.ts +15 -0
- package/dist/agent/mergeRules.d.ts +11 -0
- package/dist/agent/observe.d.ts +7 -0
- package/dist/agent/observe.js +142 -8
- package/dist/agent/prompt.d.ts +3 -0
- package/dist/agent/prompt.js +39 -8
- package/dist/agent/providers.d.ts +25 -0
- package/dist/agent/providers.js +10 -1
- package/dist/agent/resolve.d.ts +11 -0
- package/dist/agent/resolve.js +12 -3
- package/dist/agent/runEvidence.d.ts +6 -0
- package/dist/agent/runner.d.ts +19 -0
- package/dist/agent/runner.js +102 -19
- package/dist/agent/skill.d.ts +12 -0
- package/dist/agent/skill.js +6 -2
- package/dist/agent/skillValidator.d.ts +7 -0
- package/dist/agent/state.d.ts +7 -0
- package/dist/agent/state.js +3 -1
- package/dist/agent/strictLint.d.ts +3 -0
- package/dist/agent/strictLint.js +2 -1
- package/dist/agent/templates.d.ts +14 -0
- package/dist/agent/types.d.ts +286 -0
- package/dist/agent/types.js +39 -1
- package/dist/agent/util.d.ts +13 -0
- package/dist/agent/util.js +4 -0
- package/dist/agent/version.d.ts +11 -0
- package/dist/agent/version.js +1 -1
- package/dist/client.d.ts +162 -0
- package/dist/http.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/tools.d.ts +3 -0
- package/dist/version.d.ts +1 -0
- package/package.json +7 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@coinrithm/mcp-trading` are documented here. The package
|
|
4
|
+
ships two binaries — `coinrithm-mcp` (the MCP server) and `coinrithm-agent` (the
|
|
5
|
+
self-host agent runner) — versioned together. The CoinRithm **API contract** is
|
|
6
|
+
versioned separately (see `openapi.yaml` `info.version`, currently `1.4.0`).
|
|
7
|
+
|
|
8
|
+
## 0.3.0
|
|
9
|
+
|
|
10
|
+
- **Agent risk config: coin deny-list (`blocklist`).** `risk.blocklist` lets an
|
|
11
|
+
agent name symbols it must never open, even if they are on the watchlist —
|
|
12
|
+
deny wins over allow. Enforced in the runner's decision validator (rejects
|
|
13
|
+
`futures_open` / `spot_order` on a denied symbol) and surfaced in the system
|
|
14
|
+
prompt, so the model is told the boundary and the runner re-checks it.
|
|
15
|
+
- **Docs: Open Knowledge Format positioning.** Clarified that a CoinRithm agent
|
|
16
|
+
is an OKF bundle — a portable directory of markdown + frontmatter that is
|
|
17
|
+
model-agnostic (run the same definition on any model). Develop and prove it
|
|
18
|
+
free on paper, then run it anywhere.
|
|
19
|
+
- npm keywords refreshed (`open-knowledge-format`, `okf`, `model-agnostic`,
|
|
20
|
+
`gemini`) for registry discovery.
|
|
21
|
+
|
|
22
|
+
## 0.2.0
|
|
23
|
+
|
|
24
|
+
- Added the **`coinrithm-agent`** self-host runner binary alongside the MCP
|
|
25
|
+
server: a folder-as-architecture (OKF) agent you bring your own model key to,
|
|
26
|
+
with caps enforced by the runner (not the model), dry-run by default,
|
|
27
|
+
paper-only.
|
|
28
|
+
|
|
29
|
+
## 0.1.x
|
|
30
|
+
|
|
31
|
+
- Initial `coinrithm-mcp` MCP server: reads, quotes, scoped spot/futures/PM
|
|
32
|
+
writes, ledger export, and Agent Arena integration over a user-minted API key.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ CoinRithm paper-trading toolkit. Two binaries:
|
|
|
6
6
|
CoinRithm (spot, futures, prediction markets) using a personal API key.
|
|
7
7
|
- **`coinrithm-agent`** — a self-host **agent runner**: author an agent as a
|
|
8
8
|
folder and run an observe→decide→validate→act loop with your own model key
|
|
9
|
-
(paper, futures
|
|
9
|
+
(paper: spot, futures, prediction markets). See [Agent runner](#agent-runner-coinrithm-agent) below.
|
|
10
10
|
|
|
11
11
|
> **Paper trading only** — virtual funds (50,000 mUSD). Not financial advice.
|
|
12
12
|
|
|
@@ -23,7 +23,7 @@ This package also ships a **self-host agent runner**. You write an agent as a
|
|
|
23
23
|
folder (strategy + hard caps in markdown/YAML); the runner compiles it and runs
|
|
24
24
|
an `observe → decide → validate → act` loop, asking *your* model (bring-your-own
|
|
25
25
|
key) for structured decisions and executing only the ones that pass your caps —
|
|
26
|
-
**dry-run by default**, paper-only, futures
|
|
26
|
+
**dry-run by default**, paper-only across spot, futures, and prediction markets.
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
coinrithm-agent new my-agent --preset conservative
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { CoinRithmClient } from "./client.js";
|
|
2
|
+
import { ProposedAction, AgentTrace, ApiResult, Observation, QuoteEvidence } from "./types.js";
|
|
3
|
+
export declare function fetchQuote(client: CoinRithmClient, action: ProposedAction, observation: Observation, trace?: AgentTrace): Promise<QuoteEvidence | undefined>;
|
|
4
|
+
export declare function executeAction(client: CoinRithmClient, action: ProposedAction, observation: Observation, trace: AgentTrace, idempotencyKey: string): Promise<ApiResult>;
|
package/dist/agent/act.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Act phase: fetch the quote evidence for an open (the runner does this, never
|
|
2
|
-
// the model) and execute a validated futures
|
|
2
|
+
// the model) and execute a validated action (futures / spot / PM) with an
|
|
3
|
+
// idempotency key.
|
|
3
4
|
import { asObj, asNum, asStr } from "./extract.js";
|
|
4
5
|
function coinIdFor(observation, symbol) {
|
|
5
6
|
return observation.watch.find((w) => w.symbol.toUpperCase() === symbol.toUpperCase())?.coinId ?? undefined;
|
|
@@ -11,20 +12,40 @@ function freshnessOf(block) {
|
|
|
11
12
|
}
|
|
12
13
|
// Read-only quote BEFORE any open. Returns ineligible (never throws) on error.
|
|
13
14
|
export async function fetchQuote(client, action, observation, trace) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
let r;
|
|
16
|
+
if (action.type === "futures_open") {
|
|
17
|
+
const coinId = coinIdFor(observation, action.symbol);
|
|
18
|
+
if (!coinId)
|
|
19
|
+
return { eligible: false, blockReasons: ["unresolved_symbol"] };
|
|
20
|
+
r = await client.futuresQuote({ coinId, side: action.side, leverage: action.leverage, marginMusd: action.marginMusd }, trace);
|
|
21
|
+
}
|
|
22
|
+
else if (action.type === "spot_order") {
|
|
23
|
+
const coinId = coinIdFor(observation, action.symbol);
|
|
24
|
+
if (!coinId)
|
|
25
|
+
return { eligible: false, blockReasons: ["unresolved_symbol"] };
|
|
26
|
+
r = await client.spotQuote({ coinId, side: action.side, quantity: action.quantity }, trace);
|
|
27
|
+
}
|
|
28
|
+
else if (action.type === "pm_open") {
|
|
29
|
+
r = await client.pmQuote({
|
|
30
|
+
source: action.source,
|
|
31
|
+
slug: action.slug,
|
|
32
|
+
outcomeExternalMarketId: action.outcomeExternalMarketId,
|
|
33
|
+
stakeMusd: action.stakeMusd,
|
|
34
|
+
}, trace);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return undefined; // close / set-sltp / cancel need no quote
|
|
38
|
+
}
|
|
20
39
|
if (!r.ok)
|
|
21
40
|
return { eligible: false, blockReasons: [`quote_http_${r.status}`] };
|
|
22
41
|
const d = asObj(r.data);
|
|
23
42
|
return {
|
|
24
43
|
eligible: d.eligible === true,
|
|
25
44
|
blockReasons: d.blockReasons,
|
|
26
|
-
entryPrice: asNum(d.entryPrice),
|
|
27
|
-
liquidationPrice: asNum(d.liquidationPrice),
|
|
45
|
+
entryPrice: asNum(d.entryPrice), // futures
|
|
46
|
+
liquidationPrice: asNum(d.liquidationPrice), // futures
|
|
47
|
+
executionPrice: asNum(d.executionPrice), // spot live fill price
|
|
48
|
+
estimatedCostMusd: asNum(d.estimatedCostMusd), // spot gross notional
|
|
28
49
|
// Freshness lives in the response's `observation` block (anti-look-ahead).
|
|
29
50
|
freshness: freshnessOf(asObj(d.observation)),
|
|
30
51
|
};
|
|
@@ -61,5 +82,33 @@ export async function executeAction(client, action, observation, trace, idempote
|
|
|
61
82
|
agentTrace: trace,
|
|
62
83
|
});
|
|
63
84
|
}
|
|
85
|
+
if (action.type === "spot_order") {
|
|
86
|
+
const coinId = coinIdFor(observation, action.symbol);
|
|
87
|
+
if (!coinId)
|
|
88
|
+
return { ok: false, status: 0, data: { error: "unresolved_symbol" } };
|
|
89
|
+
return client.placeSpotOrder({
|
|
90
|
+
coinId,
|
|
91
|
+
side: action.side,
|
|
92
|
+
orderType: action.orderType,
|
|
93
|
+
quantity: action.quantity,
|
|
94
|
+
limitPrice: action.limitPrice,
|
|
95
|
+
stopPrice: action.stopPrice,
|
|
96
|
+
idempotencyKey,
|
|
97
|
+
agentTrace: trace,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (action.type === "spot_cancel") {
|
|
101
|
+
return client.cancelSpotOrder(action.orderId, trace);
|
|
102
|
+
}
|
|
103
|
+
if (action.type === "pm_open") {
|
|
104
|
+
return client.openPmPosition({
|
|
105
|
+
source: action.source,
|
|
106
|
+
slug: action.slug,
|
|
107
|
+
outcomeExternalMarketId: action.outcomeExternalMarketId,
|
|
108
|
+
stakeMusd: action.stakeMusd,
|
|
109
|
+
idempotencyKey,
|
|
110
|
+
agentTrace: trace,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
64
113
|
return { ok: false, status: 0, data: { error: "unsupported_action" } };
|
|
65
114
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Capability drift guard.
|
|
2
|
+
//
|
|
3
|
+
// An agent's PROSE (thesis, persona, tactic skills) is authored separately from
|
|
4
|
+
// the runner's code, so a skill can name a venue, an action, or a cap that the
|
|
5
|
+
// runner does NOT support — and rot silently, exactly the way a stale doc drifts
|
|
6
|
+
// from a live enum. (We watched this happen in a sibling system: a knowledge file
|
|
7
|
+
// kept naming post types the live enum had dropped, and a test pinned the stale
|
|
8
|
+
// names so the suite stayed green while the model was fed a wrong taxonomy.)
|
|
9
|
+
//
|
|
10
|
+
// This guard asserts that every capability IDENTIFIER referenced in authored
|
|
11
|
+
// prose is in the runner's LIVE capability set AND — for actions/venues — in THIS
|
|
12
|
+
// agent's enabled venues. It is deliberately deterministic and low-false-positive:
|
|
13
|
+
// it inspects only identifier-shaped tokens (snake_case `<venue>_<verb>` actions
|
|
14
|
+
// scanned anywhere; cap/venue identifiers scanned only inside markdown code
|
|
15
|
+
// spans), never free English. Runtime memory (journal/notes.md) is excluded — it
|
|
16
|
+
// records history, and may legitimately recall a venue later disabled.
|
|
17
|
+
import { VENUES, ACTION_TYPES, } from "./types.js";
|
|
18
|
+
import { levenshtein } from "./strictLint.js";
|
|
19
|
+
import { RISK_CAPS, LIMIT_CAPS } from "./mergeRules.js";
|
|
20
|
+
const KNOWN_ACTIONS = new Set(ACTION_TYPES);
|
|
21
|
+
// Every enforced cap the runner knows: risk + limits + killSwitch + abstention.
|
|
22
|
+
const LIVE_CAPS = [
|
|
23
|
+
...Object.keys(RISK_CAPS),
|
|
24
|
+
...Object.keys(LIMIT_CAPS),
|
|
25
|
+
"maxDrawdownMusd",
|
|
26
|
+
"maxConsecutiveRejects",
|
|
27
|
+
"maxConsecutiveModelFailures",
|
|
28
|
+
"onRateLimitPressure",
|
|
29
|
+
"onStaleData",
|
|
30
|
+
"onWeakSignal",
|
|
31
|
+
"onMissingQuote",
|
|
32
|
+
"onInsufficientBalance",
|
|
33
|
+
"minConfidence",
|
|
34
|
+
];
|
|
35
|
+
const LIVE_CAP_SET = new Set(LIVE_CAPS);
|
|
36
|
+
// `<venue>_<verb>` action references. The verb is restricted to the runner's
|
|
37
|
+
// real verbs, so descriptive tokens (spot_price, futures_curve) never match —
|
|
38
|
+
// only action-shaped references (real OR plausibly-fake) do.
|
|
39
|
+
const ACTION_RE = /\b(spot|futures|pm)_(open|close|order|cancel|set_sltp)\b/g;
|
|
40
|
+
// camelCase cap-shaped identifiers (maxLeverage, minConfidence, requireStopLoss…).
|
|
41
|
+
const CAP_RE = /\b(?:max|min|require|on|per)[A-Z][A-Za-z0-9]+\b/g;
|
|
42
|
+
// markdown inline code spans — where authors put identifiers.
|
|
43
|
+
const CODESPAN_RE = /`([^`\n]+)`/g;
|
|
44
|
+
// Levenshtein threshold for "this is a typo/rename of a real cap" — matches the
|
|
45
|
+
// strictLint "did you mean" threshold (< 3, i.e. edit distance <= 2).
|
|
46
|
+
const CAP_NEARMISS_MAX = 3;
|
|
47
|
+
function venueOf(action) {
|
|
48
|
+
return action.split("_")[0];
|
|
49
|
+
}
|
|
50
|
+
// Authored knowledge only: keystone + character/* + skills/*. journal/notes.md is
|
|
51
|
+
// runtime memory and is intentionally NOT checked.
|
|
52
|
+
function authoredProse(resolved) {
|
|
53
|
+
return resolved.proseParts.filter((p) => p.source !== "journal/notes.md");
|
|
54
|
+
}
|
|
55
|
+
// Returns one issue per (token, source) drift found. Empty = no drift.
|
|
56
|
+
export function checkCapabilityDrift(resolved, spec) {
|
|
57
|
+
const issues = [];
|
|
58
|
+
const enabled = new Set(spec.venues);
|
|
59
|
+
const seen = new Set(); // dedupe "kind|token|source"
|
|
60
|
+
const once = (key) => {
|
|
61
|
+
if (seen.has(key))
|
|
62
|
+
return false;
|
|
63
|
+
seen.add(key);
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
for (const part of authoredProse(resolved)) {
|
|
67
|
+
const where = part.source;
|
|
68
|
+
// ── action references (anywhere in the body) ──
|
|
69
|
+
for (const m of part.text.matchAll(ACTION_RE)) {
|
|
70
|
+
const action = m[0];
|
|
71
|
+
if (!once(`a|${action}|${where}`))
|
|
72
|
+
continue;
|
|
73
|
+
if (!KNOWN_ACTIONS.has(action)) {
|
|
74
|
+
issues.push({
|
|
75
|
+
code: "drift_unknown_action",
|
|
76
|
+
path: where,
|
|
77
|
+
message: `prose references action \`${action}\`, which the runner does not support (allowed: ${ACTION_TYPES.join(", ")})`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const venue = venueOf(action);
|
|
82
|
+
if (!enabled.has(venue)) {
|
|
83
|
+
issues.push({
|
|
84
|
+
code: "drift_venue_not_enabled",
|
|
85
|
+
path: where,
|
|
86
|
+
message: `prose references \`${action}\` but venue \`${venue}\` is not in this agent's venues [${spec.venues.join(", ")}]`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ── venue + cap references inside code spans only ──
|
|
92
|
+
for (const span of part.text.matchAll(CODESPAN_RE)) {
|
|
93
|
+
const inner = span[1];
|
|
94
|
+
// An exact venue identifier in a code span (e.g. `pm`) that the agent has
|
|
95
|
+
// not enabled — a prose promise the runner cannot keep.
|
|
96
|
+
if (VENUES.includes(inner) &&
|
|
97
|
+
!enabled.has(inner) &&
|
|
98
|
+
once(`v|${inner}|${where}`)) {
|
|
99
|
+
issues.push({
|
|
100
|
+
code: "drift_venue_not_enabled",
|
|
101
|
+
path: where,
|
|
102
|
+
message: `prose references venue \`${inner}\` but it is not in this agent's venues [${spec.venues.join(", ")}]`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// cap-shaped identifiers — flag only a near-miss of a REAL cap (a typo or
|
|
106
|
+
// rename); unrelated identifiers are left alone to avoid false positives.
|
|
107
|
+
for (const cm of inner.matchAll(CAP_RE)) {
|
|
108
|
+
const token = cm[0];
|
|
109
|
+
if (LIVE_CAP_SET.has(token))
|
|
110
|
+
continue;
|
|
111
|
+
let nearest = null;
|
|
112
|
+
let best = CAP_NEARMISS_MAX;
|
|
113
|
+
for (const cap of LIVE_CAPS) {
|
|
114
|
+
const d = levenshtein(token.toLowerCase(), cap.toLowerCase());
|
|
115
|
+
if (d < best) {
|
|
116
|
+
best = d;
|
|
117
|
+
nearest = cap;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (nearest && once(`c|${token}|${where}`)) {
|
|
121
|
+
issues.push({
|
|
122
|
+
code: "drift_unknown_cap",
|
|
123
|
+
path: where,
|
|
124
|
+
message: `prose references cap \`${token}\`, which is not a runner cap — did you mean \`${nearest}\`?`,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return issues;
|
|
131
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SkillValidationMode } from "./skillValidator.js";
|
|
2
|
+
export interface CmdResult {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
code: number;
|
|
5
|
+
lines: string[];
|
|
6
|
+
data?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare function cmdNew(targetPath: string, opts?: {
|
|
9
|
+
template?: string;
|
|
10
|
+
preset?: string;
|
|
11
|
+
}): CmdResult;
|
|
12
|
+
export declare function cmdValidate(path: string, mode?: SkillValidationMode): CmdResult;
|
|
13
|
+
export declare function cmdLock(path: string): CmdResult;
|
|
14
|
+
export declare function cmdEject(path: string): CmdResult;
|
|
15
|
+
export declare function cmdInspect(path: string, json?: boolean): CmdResult;
|
|
16
|
+
export declare function cmdRun(path: string, opts?: {
|
|
17
|
+
once?: boolean;
|
|
18
|
+
live?: boolean;
|
|
19
|
+
stateFile?: string;
|
|
20
|
+
}): Promise<CmdResult>;
|
|
21
|
+
export declare function main(argv: string[]): Promise<number>;
|
package/dist/agent/cli.js
CHANGED
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
import { mkdirSync, writeFileSync, existsSync, statSync, readFileSync, openSync, closeSync, unlinkSync, } from "node:fs";
|
|
8
8
|
import { resolve as resolvePath, dirname, join, basename } from "node:path";
|
|
9
9
|
import { parse as parseYaml } from "yaml";
|
|
10
|
-
import { resolveAgent, ResolveError } from "./resolve.js";
|
|
10
|
+
import { resolveAgent, ResolveError, mergeProseParts, isSkillProseSource } from "./resolve.js";
|
|
11
11
|
import { buildSpec, loadAgent } from "./skill.js";
|
|
12
12
|
import { validateSkill } from "./skillValidator.js";
|
|
13
13
|
import { strictLint } from "./strictLint.js";
|
|
14
|
+
import { checkCapabilityDrift } from "./capabilityGuard.js";
|
|
14
15
|
import { buildManifest, writeManifest } from "./manifest.js";
|
|
15
16
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
16
17
|
import { renderFolderOfOne, ejectFiles, PRESET_NAMES } from "./templates.js";
|
|
17
18
|
import { COINRITHM_API } from "./version.js";
|
|
18
|
-
import { stableStringify } from "./util.js";
|
|
19
|
+
import { stableStringify, envFlag } from "./util.js";
|
|
19
20
|
import { CoinRithmClient } from "./client.js";
|
|
20
21
|
import { selectProvider } from "./providers.js";
|
|
21
22
|
import { runLoop } from "./runner.js";
|
|
@@ -91,8 +92,8 @@ export function cmdValidate(path, mode = "self-host") {
|
|
|
91
92
|
throw e;
|
|
92
93
|
}
|
|
93
94
|
const raw = resolved.rawFrontmatter;
|
|
94
|
-
const lint = strictLint(raw);
|
|
95
95
|
const spec = buildSpec(raw);
|
|
96
|
+
const lint = [...strictLint(raw), ...checkCapabilityDrift(resolved, spec)];
|
|
96
97
|
const v = validateSkill({ spec, body: resolved.mergedProse, raw }, mode);
|
|
97
98
|
const lintFatal = mode === "hosted";
|
|
98
99
|
const lines = [];
|
|
@@ -114,7 +115,17 @@ export function cmdLock(path) {
|
|
|
114
115
|
const spec = buildSpec(resolved.rawFrontmatter);
|
|
115
116
|
const manifest = buildManifest(resolved, spec);
|
|
116
117
|
const out = writeManifest(agentDirOf(path), manifest);
|
|
117
|
-
|
|
118
|
+
// Self-host treats capability drift as advisory (not fatal), but locking past
|
|
119
|
+
// it silently would hide it — surface it so the author isn't surprised when
|
|
120
|
+
// `validate --hosted` later rejects the same folder.
|
|
121
|
+
const drift = (v.data?.lint ?? []).filter((i) => i.code.startsWith("drift_"));
|
|
122
|
+
const warn = drift.length
|
|
123
|
+
? [
|
|
124
|
+
`⚠ locked with ${drift.length} advisory capability-drift note(s) — \`validate --hosted\` would reject these:`,
|
|
125
|
+
...drift.map((i) => ` [${i.code}] ${i.path ? `${i.path}: ` : ""}${i.message}`),
|
|
126
|
+
]
|
|
127
|
+
: [];
|
|
128
|
+
return { ok: true, code: 0, lines: [`wrote ${out}`, `configHash ${manifest.configHash}`, ...warn] };
|
|
118
129
|
}
|
|
119
130
|
export function cmdEject(path) {
|
|
120
131
|
const agentDir = agentDirOf(path);
|
|
@@ -159,7 +170,7 @@ export function cmdInspect(path, json = false) {
|
|
|
159
170
|
throw e;
|
|
160
171
|
}
|
|
161
172
|
const spec = buildSpec(resolved.rawFrontmatter);
|
|
162
|
-
const lint = strictLint(resolved.rawFrontmatter);
|
|
173
|
+
const lint = [...strictLint(resolved.rawFrontmatter), ...checkCapabilityDrift(resolved, spec)];
|
|
163
174
|
const v = validateSkill({ spec, body: resolved.mergedProse, raw: resolved.rawFrontmatter }, "self-host");
|
|
164
175
|
const output = {
|
|
165
176
|
resolvedConfig: resolved.rawFrontmatter,
|
|
@@ -258,11 +269,22 @@ export async function cmdRun(path, opts = {}) {
|
|
|
258
269
|
const lines = [
|
|
259
270
|
`run ${live ? "LIVE (paper trades WILL be placed)" : "DRY-RUN (no writes; set --live or LIVE=1)"} — ${loaded.spec.name}`,
|
|
260
271
|
];
|
|
272
|
+
// Skills ablation kill-switch: drop tactic-skill prose from the prompt for
|
|
273
|
+
// token-cost control or A/B testing. Affects ONLY the run-time prompt — the
|
|
274
|
+
// resolver, manifest, and caps are untouched (the spec is still enforced).
|
|
275
|
+
const disableSkills = envFlag(process.env.COINRITHM_AGENT_DISABLE_SKILLS);
|
|
276
|
+
const mergedProse = disableSkills
|
|
277
|
+
? mergeProseParts(loaded.resolved.proseParts.filter((p) => !isSkillProseSource(p.source)))
|
|
278
|
+
: loaded.body;
|
|
279
|
+
if (disableSkills) {
|
|
280
|
+
const dropped = loaded.resolved.proseParts.filter((p) => isSkillProseSource(p.source)).length;
|
|
281
|
+
lines.push(`skills DISABLED via COINRITHM_AGENT_DISABLE_SKILLS — ${dropped} tactic skill(s) dropped from the prompt (caps unchanged)`);
|
|
282
|
+
}
|
|
261
283
|
const deps = {
|
|
262
284
|
client,
|
|
263
285
|
provider,
|
|
264
286
|
spec: loaded.spec,
|
|
265
|
-
mergedProse
|
|
287
|
+
mergedProse,
|
|
266
288
|
state,
|
|
267
289
|
live,
|
|
268
290
|
stateFile,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { AgentTrace, ApiResult } from "./types.js";
|
|
2
|
+
export declare const DEFAULT_BASE_URL = "https://api.coinrithm.com";
|
|
3
|
+
export interface ClientConfig {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
fetchFn?: typeof fetch;
|
|
7
|
+
sleepFn?: (ms: number) => Promise<void>;
|
|
8
|
+
maxRetries?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare class CoinRithmClient {
|
|
11
|
+
private readonly apiKey;
|
|
12
|
+
private readonly baseUrl;
|
|
13
|
+
private readonly fetchFn;
|
|
14
|
+
private readonly sleepFn;
|
|
15
|
+
private readonly maxRetries;
|
|
16
|
+
rateLimitHits: number;
|
|
17
|
+
constructor(cfg: ClientConfig);
|
|
18
|
+
private request;
|
|
19
|
+
me(trace?: AgentTrace): Promise<ApiResult>;
|
|
20
|
+
portfolio(trace?: AgentTrace): Promise<ApiResult>;
|
|
21
|
+
wallet(query?: {
|
|
22
|
+
coinId?: string;
|
|
23
|
+
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
24
|
+
resolve(q: string, trace?: AgentTrace): Promise<ApiResult>;
|
|
25
|
+
market(coinId: string, trace?: AgentTrace): Promise<ApiResult>;
|
|
26
|
+
candles(coinId: string, range: string, trace?: AgentTrace): Promise<ApiResult>;
|
|
27
|
+
trades(query?: {
|
|
28
|
+
venue?: string;
|
|
29
|
+
limit?: number;
|
|
30
|
+
updatedSince?: string;
|
|
31
|
+
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
32
|
+
futuresPositions(query?: {
|
|
33
|
+
updatedSince?: string;
|
|
34
|
+
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
35
|
+
futuresQuote(body: {
|
|
36
|
+
coinId: string;
|
|
37
|
+
side: string;
|
|
38
|
+
leverage: number;
|
|
39
|
+
marginMusd: number;
|
|
40
|
+
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
41
|
+
openOrders(query?: {
|
|
42
|
+
coinId?: string;
|
|
43
|
+
updatedSince?: string;
|
|
44
|
+
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
45
|
+
spotQuote(body: {
|
|
46
|
+
coinId: string;
|
|
47
|
+
side: string;
|
|
48
|
+
quantity: number;
|
|
49
|
+
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
50
|
+
discoverPmMarkets(query?: {
|
|
51
|
+
q?: string;
|
|
52
|
+
source?: string;
|
|
53
|
+
limit?: number;
|
|
54
|
+
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
55
|
+
pmPositions(query?: {
|
|
56
|
+
updatedSince?: string;
|
|
57
|
+
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
58
|
+
pmQuote(body: {
|
|
59
|
+
source: string;
|
|
60
|
+
slug: string;
|
|
61
|
+
outcomeExternalMarketId: string;
|
|
62
|
+
stakeMusd: number;
|
|
63
|
+
}, trace?: AgentTrace): Promise<ApiResult>;
|
|
64
|
+
openFutures(body: {
|
|
65
|
+
coinId: string;
|
|
66
|
+
side: string;
|
|
67
|
+
leverage: number;
|
|
68
|
+
marginMusd: number;
|
|
69
|
+
idempotencyKey: string;
|
|
70
|
+
stopLossPrice?: number | null;
|
|
71
|
+
takeProfitPrice?: number | null;
|
|
72
|
+
agentTrace?: AgentTrace;
|
|
73
|
+
}): Promise<ApiResult>;
|
|
74
|
+
closeFutures(body: {
|
|
75
|
+
positionId: number;
|
|
76
|
+
fraction?: number;
|
|
77
|
+
idempotencyKey: string;
|
|
78
|
+
agentTrace?: AgentTrace;
|
|
79
|
+
}): Promise<ApiResult>;
|
|
80
|
+
setFuturesSlTp(body: {
|
|
81
|
+
positionId: number;
|
|
82
|
+
stopLossPrice?: number | null;
|
|
83
|
+
takeProfitPrice?: number | null;
|
|
84
|
+
agentTrace?: AgentTrace;
|
|
85
|
+
}): Promise<ApiResult>;
|
|
86
|
+
placeSpotOrder(body: {
|
|
87
|
+
coinId: string;
|
|
88
|
+
side: string;
|
|
89
|
+
orderType: string;
|
|
90
|
+
quantity: number;
|
|
91
|
+
limitPrice?: number;
|
|
92
|
+
stopPrice?: number;
|
|
93
|
+
idempotencyKey: string;
|
|
94
|
+
agentTrace?: AgentTrace;
|
|
95
|
+
}): Promise<ApiResult>;
|
|
96
|
+
cancelSpotOrder(orderId: number, trace?: AgentTrace): Promise<ApiResult>;
|
|
97
|
+
openPmPosition(body: {
|
|
98
|
+
source: string;
|
|
99
|
+
slug: string;
|
|
100
|
+
outcomeExternalMarketId: string;
|
|
101
|
+
stakeMusd: number;
|
|
102
|
+
idempotencyKey: string;
|
|
103
|
+
agentTrace?: AgentTrace;
|
|
104
|
+
}): Promise<ApiResult>;
|
|
105
|
+
exportRunEvidence(runId: string): Promise<ApiResult>;
|
|
106
|
+
}
|
|
107
|
+
export declare function isFailClosed(status: number): boolean;
|
package/dist/agent/client.js
CHANGED
|
@@ -125,6 +125,23 @@ export class CoinRithmClient {
|
|
|
125
125
|
futuresQuote(body, trace) {
|
|
126
126
|
return this.request("POST", "/api/agent/futures/quote", { body: { ...body, agentTrace: trace } });
|
|
127
127
|
}
|
|
128
|
+
// ── spot ─────────────────────────────────────────────────────────────────
|
|
129
|
+
openOrders(query, trace) {
|
|
130
|
+
return this.request("GET", "/api/agent/orders/open", { query, trace });
|
|
131
|
+
}
|
|
132
|
+
spotQuote(body, trace) {
|
|
133
|
+
return this.request("POST", "/api/agent/spot/quote", { body: { ...body, agentTrace: trace } });
|
|
134
|
+
}
|
|
135
|
+
// ── prediction markets ───────────────────────────────────────────────────
|
|
136
|
+
discoverPmMarkets(query, trace) {
|
|
137
|
+
return this.request("GET", "/api/agent/pm/discover", { query, trace });
|
|
138
|
+
}
|
|
139
|
+
pmPositions(query, trace) {
|
|
140
|
+
return this.request("GET", "/api/agent/positions/pm", { query, trace });
|
|
141
|
+
}
|
|
142
|
+
pmQuote(body, trace) {
|
|
143
|
+
return this.request("POST", "/api/agent/pm/quote", { body: { ...body, agentTrace: trace } });
|
|
144
|
+
}
|
|
128
145
|
// ── writes ─────────────────────────────────────────────────────────────────
|
|
129
146
|
openFutures(body) {
|
|
130
147
|
return this.request("POST", "/api/agent/futures/open", { body });
|
|
@@ -135,6 +152,15 @@ export class CoinRithmClient {
|
|
|
135
152
|
setFuturesSlTp(body) {
|
|
136
153
|
return this.request("POST", "/api/agent/futures/sl-tp", { body });
|
|
137
154
|
}
|
|
155
|
+
placeSpotOrder(body) {
|
|
156
|
+
return this.request("POST", "/api/agent/spot/order", { body });
|
|
157
|
+
}
|
|
158
|
+
cancelSpotOrder(orderId, trace) {
|
|
159
|
+
return this.request("POST", `/api/agent/spot/order/${orderId}/cancel`, { trace });
|
|
160
|
+
}
|
|
161
|
+
openPmPosition(body) {
|
|
162
|
+
return this.request("POST", "/api/agent/pm/open", { body });
|
|
163
|
+
}
|
|
138
164
|
// Run-evidence export — runId is URL-encoded into the query.
|
|
139
165
|
exportRunEvidence(runId) {
|
|
140
166
|
return this.request("GET", "/api/agent/ledger/export", { query: { runId } });
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Decision } from "./types.js";
|
|
3
|
+
export declare const actionSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
4
|
+
type: z.ZodLiteral<"futures_open">;
|
|
5
|
+
symbol: z.ZodString;
|
|
6
|
+
side: z.ZodEnum<["long", "short"]>;
|
|
7
|
+
leverage: z.ZodNumber;
|
|
8
|
+
marginMusd: z.ZodNumber;
|
|
9
|
+
stopLossPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
10
|
+
takeProfitPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
11
|
+
confidence: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
rationaleSummary: z.ZodOptional<z.ZodString>;
|
|
13
|
+
}, "strict", z.ZodTypeAny, {
|
|
14
|
+
symbol: string;
|
|
15
|
+
side: "long" | "short";
|
|
16
|
+
leverage: number;
|
|
17
|
+
marginMusd: number;
|
|
18
|
+
type: "futures_open";
|
|
19
|
+
stopLossPrice?: number | null | undefined;
|
|
20
|
+
takeProfitPrice?: number | null | undefined;
|
|
21
|
+
confidence?: number | undefined;
|
|
22
|
+
rationaleSummary?: string | undefined;
|
|
23
|
+
}, {
|
|
24
|
+
symbol: string;
|
|
25
|
+
side: "long" | "short";
|
|
26
|
+
leverage: number;
|
|
27
|
+
marginMusd: number;
|
|
28
|
+
type: "futures_open";
|
|
29
|
+
stopLossPrice?: number | null | undefined;
|
|
30
|
+
takeProfitPrice?: number | null | undefined;
|
|
31
|
+
confidence?: number | undefined;
|
|
32
|
+
rationaleSummary?: string | undefined;
|
|
33
|
+
}>, z.ZodObject<{
|
|
34
|
+
type: z.ZodLiteral<"futures_close">;
|
|
35
|
+
positionId: z.ZodNumber;
|
|
36
|
+
fraction: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
confidence: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
rationaleSummary: z.ZodOptional<z.ZodString>;
|
|
39
|
+
}, "strict", z.ZodTypeAny, {
|
|
40
|
+
positionId: number;
|
|
41
|
+
type: "futures_close";
|
|
42
|
+
fraction?: number | undefined;
|
|
43
|
+
confidence?: number | undefined;
|
|
44
|
+
rationaleSummary?: string | undefined;
|
|
45
|
+
}, {
|
|
46
|
+
positionId: number;
|
|
47
|
+
type: "futures_close";
|
|
48
|
+
fraction?: number | undefined;
|
|
49
|
+
confidence?: number | undefined;
|
|
50
|
+
rationaleSummary?: string | undefined;
|
|
51
|
+
}>, z.ZodObject<{
|
|
52
|
+
type: z.ZodLiteral<"futures_set_sltp">;
|
|
53
|
+
positionId: z.ZodNumber;
|
|
54
|
+
stopLossPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
55
|
+
takeProfitPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
56
|
+
}, "strict", z.ZodTypeAny, {
|
|
57
|
+
positionId: number;
|
|
58
|
+
type: "futures_set_sltp";
|
|
59
|
+
stopLossPrice?: number | null | undefined;
|
|
60
|
+
takeProfitPrice?: number | null | undefined;
|
|
61
|
+
}, {
|
|
62
|
+
positionId: number;
|
|
63
|
+
type: "futures_set_sltp";
|
|
64
|
+
stopLossPrice?: number | null | undefined;
|
|
65
|
+
takeProfitPrice?: number | null | undefined;
|
|
66
|
+
}>, z.ZodObject<{
|
|
67
|
+
type: z.ZodLiteral<"spot_order">;
|
|
68
|
+
symbol: z.ZodString;
|
|
69
|
+
side: z.ZodEnum<["buy", "sell"]>;
|
|
70
|
+
orderType: z.ZodEnum<["market", "limit", "stop"]>;
|
|
71
|
+
quantity: z.ZodNumber;
|
|
72
|
+
limitPrice: z.ZodOptional<z.ZodNumber>;
|
|
73
|
+
stopPrice: z.ZodOptional<z.ZodNumber>;
|
|
74
|
+
confidence: z.ZodOptional<z.ZodNumber>;
|
|
75
|
+
rationaleSummary: z.ZodOptional<z.ZodString>;
|
|
76
|
+
}, "strict", z.ZodTypeAny, {
|
|
77
|
+
symbol: string;
|
|
78
|
+
side: "buy" | "sell";
|
|
79
|
+
orderType: "limit" | "market" | "stop";
|
|
80
|
+
quantity: number;
|
|
81
|
+
type: "spot_order";
|
|
82
|
+
limitPrice?: number | undefined;
|
|
83
|
+
stopPrice?: number | undefined;
|
|
84
|
+
confidence?: number | undefined;
|
|
85
|
+
rationaleSummary?: string | undefined;
|
|
86
|
+
}, {
|
|
87
|
+
symbol: string;
|
|
88
|
+
side: "buy" | "sell";
|
|
89
|
+
orderType: "limit" | "market" | "stop";
|
|
90
|
+
quantity: number;
|
|
91
|
+
type: "spot_order";
|
|
92
|
+
limitPrice?: number | undefined;
|
|
93
|
+
stopPrice?: number | undefined;
|
|
94
|
+
confidence?: number | undefined;
|
|
95
|
+
rationaleSummary?: string | undefined;
|
|
96
|
+
}>, z.ZodObject<{
|
|
97
|
+
type: z.ZodLiteral<"spot_cancel">;
|
|
98
|
+
orderId: z.ZodNumber;
|
|
99
|
+
}, "strict", z.ZodTypeAny, {
|
|
100
|
+
type: "spot_cancel";
|
|
101
|
+
orderId: number;
|
|
102
|
+
}, {
|
|
103
|
+
type: "spot_cancel";
|
|
104
|
+
orderId: number;
|
|
105
|
+
}>, z.ZodObject<{
|
|
106
|
+
type: z.ZodLiteral<"pm_open">;
|
|
107
|
+
source: z.ZodString;
|
|
108
|
+
slug: z.ZodString;
|
|
109
|
+
outcomeExternalMarketId: z.ZodString;
|
|
110
|
+
stakeMusd: z.ZodNumber;
|
|
111
|
+
confidence: z.ZodOptional<z.ZodNumber>;
|
|
112
|
+
rationaleSummary: z.ZodOptional<z.ZodString>;
|
|
113
|
+
}, "strict", z.ZodTypeAny, {
|
|
114
|
+
source: string;
|
|
115
|
+
slug: string;
|
|
116
|
+
outcomeExternalMarketId: string;
|
|
117
|
+
stakeMusd: number;
|
|
118
|
+
type: "pm_open";
|
|
119
|
+
confidence?: number | undefined;
|
|
120
|
+
rationaleSummary?: string | undefined;
|
|
121
|
+
}, {
|
|
122
|
+
source: string;
|
|
123
|
+
slug: string;
|
|
124
|
+
outcomeExternalMarketId: string;
|
|
125
|
+
stakeMusd: number;
|
|
126
|
+
type: "pm_open";
|
|
127
|
+
confidence?: number | undefined;
|
|
128
|
+
rationaleSummary?: string | undefined;
|
|
129
|
+
}>]>;
|
|
130
|
+
export type ParseDecisionResult = {
|
|
131
|
+
ok: true;
|
|
132
|
+
decision: Decision;
|
|
133
|
+
} | {
|
|
134
|
+
ok: false;
|
|
135
|
+
error: string;
|
|
136
|
+
};
|
|
137
|
+
export declare function parseDecision(text: string): ParseDecisionResult;
|