@senpi/runtime 1.0.95-dev.runtime-phase-2.20260506193916 → 1.0.95-dev.runtime-phase-2-api.20260507130312

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 (95) hide show
  1. package/README.md +13 -0
  2. package/dist/index.d.ts +35 -100
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +33 -930
  5. package/dist/index.js.map +1 -1
  6. package/dist/plugin.d.ts +110 -0
  7. package/dist/plugin.d.ts.map +1 -0
  8. package/dist/plugin.js +1055 -0
  9. package/dist/plugin.js.map +1 -0
  10. package/dist/runtime/__tests__/helpers/runtime-gateway-harness.d.ts +3 -3
  11. package/dist/runtime/audit-query.types.d.ts +113 -0
  12. package/dist/runtime/audit-query.types.d.ts.map +1 -0
  13. package/dist/runtime/audit-query.types.js +35 -0
  14. package/dist/runtime/audit-query.types.js.map +1 -0
  15. package/dist/runtime/auto-update/coordinator.d.ts +12 -8
  16. package/dist/runtime/auto-update/coordinator.d.ts.map +1 -1
  17. package/dist/runtime/auto-update/coordinator.js +18 -17
  18. package/dist/runtime/auto-update/coordinator.js.map +1 -1
  19. package/dist/runtime/auto-update/gateway-restart.d.ts +9 -2
  20. package/dist/runtime/auto-update/gateway-restart.d.ts.map +1 -1
  21. package/dist/runtime/auto-update/gateway-restart.js +23 -13
  22. package/dist/runtime/auto-update/gateway-restart.js.map +1 -1
  23. package/dist/runtime/create-actions.d.ts.map +1 -1
  24. package/dist/runtime/create-actions.js +13 -2
  25. package/dist/runtime/create-actions.js.map +1 -1
  26. package/dist/runtime/run.d.ts +52 -1
  27. package/dist/runtime/run.d.ts.map +1 -1
  28. package/dist/runtime/run.js +9 -0
  29. package/dist/runtime/run.js.map +1 -1
  30. package/dist/runtime/runtime.d.ts +56 -0
  31. package/dist/runtime/runtime.d.ts.map +1 -1
  32. package/dist/runtime/runtime.js +169 -1
  33. package/dist/runtime/runtime.js.map +1 -1
  34. package/dist/runtime-api/config.d.ts +47 -0
  35. package/dist/runtime-api/config.d.ts.map +1 -0
  36. package/dist/runtime-api/config.js +74 -0
  37. package/dist/runtime-api/config.js.map +1 -0
  38. package/dist/runtime-api/handlers/audit.d.ts +78 -0
  39. package/dist/runtime-api/handlers/audit.d.ts.map +1 -0
  40. package/dist/runtime-api/handlers/audit.js +65 -0
  41. package/dist/runtime-api/handlers/audit.js.map +1 -0
  42. package/dist/runtime-api/handlers/group-by-scanner.d.ts +43 -0
  43. package/dist/runtime-api/handlers/group-by-scanner.d.ts.map +1 -0
  44. package/dist/runtime-api/handlers/group-by-scanner.js +43 -0
  45. package/dist/runtime-api/handlers/group-by-scanner.js.map +1 -0
  46. package/dist/runtime-api/handlers/ingest-signals.d.ts +54 -0
  47. package/dist/runtime-api/handlers/ingest-signals.d.ts.map +1 -0
  48. package/dist/runtime-api/handlers/ingest-signals.js +141 -0
  49. package/dist/runtime-api/handlers/ingest-signals.js.map +1 -0
  50. package/dist/runtime-api/handlers/lookup.d.ts +36 -0
  51. package/dist/runtime-api/handlers/lookup.d.ts.map +1 -0
  52. package/dist/runtime-api/handlers/lookup.js +31 -0
  53. package/dist/runtime-api/handlers/lookup.js.map +1 -0
  54. package/dist/runtime-api/handlers/types.d.ts +85 -0
  55. package/dist/runtime-api/handlers/types.d.ts.map +1 -0
  56. package/dist/runtime-api/handlers/types.js +8 -0
  57. package/dist/runtime-api/handlers/types.js.map +1 -0
  58. package/dist/runtime-api/lifecycle.d.ts +98 -0
  59. package/dist/runtime-api/lifecycle.d.ts.map +1 -0
  60. package/dist/runtime-api/lifecycle.js +137 -0
  61. package/dist/runtime-api/lifecycle.js.map +1 -0
  62. package/dist/runtime-api/readiness.d.ts +37 -0
  63. package/dist/runtime-api/readiness.d.ts.map +1 -0
  64. package/dist/runtime-api/readiness.js +35 -0
  65. package/dist/runtime-api/readiness.js.map +1 -0
  66. package/dist/runtime-api/routes/audit.d.ts +40 -0
  67. package/dist/runtime-api/routes/audit.d.ts.map +1 -0
  68. package/dist/runtime-api/routes/audit.js +90 -0
  69. package/dist/runtime-api/routes/audit.js.map +1 -0
  70. package/dist/runtime-api/routes/audit.schema.d.ts +75 -0
  71. package/dist/runtime-api/routes/audit.schema.d.ts.map +1 -0
  72. package/dist/runtime-api/routes/audit.schema.js +78 -0
  73. package/dist/runtime-api/routes/audit.schema.js.map +1 -0
  74. package/dist/runtime-api/routes/health.d.ts +53 -0
  75. package/dist/runtime-api/routes/health.d.ts.map +1 -0
  76. package/dist/runtime-api/routes/health.js +48 -0
  77. package/dist/runtime-api/routes/health.js.map +1 -0
  78. package/dist/runtime-api/routes/signals.d.ts +50 -0
  79. package/dist/runtime-api/routes/signals.d.ts.map +1 -0
  80. package/dist/runtime-api/routes/signals.js +90 -0
  81. package/dist/runtime-api/routes/signals.js.map +1 -0
  82. package/dist/runtime-api/routes/signals.schema.d.ts +94 -0
  83. package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -0
  84. package/dist/runtime-api/routes/signals.schema.js +58 -0
  85. package/dist/runtime-api/routes/signals.schema.js.map +1 -0
  86. package/dist/runtime-api/server.d.ts +79 -0
  87. package/dist/runtime-api/server.d.ts.map +1 -0
  88. package/dist/runtime-api/server.js +109 -0
  89. package/dist/runtime-api/server.js.map +1 -0
  90. package/dist/senpi/client.d.ts +92 -2
  91. package/dist/senpi/client.d.ts.map +1 -1
  92. package/dist/senpi/client.js +181 -8
  93. package/dist/senpi/client.js.map +1 -1
  94. package/openclaw.plugin.json +24 -0
  95. package/package.json +6 -2
@@ -0,0 +1,109 @@
1
+ /**
2
+ * `createApi()` — Fastify server factory for the runtime HTTP API.
3
+ *
4
+ * Wires the Fastify instance with:
5
+ * - `POST /signals` — ingest signals for one or more wallets.
6
+ * - `GET /audit` — MCP audit-query proxy scoped to one wallet.
7
+ * - `GET /health` — three-state readiness + per-runtime queue depth.
8
+ *
9
+ * Returns the Fastify instance, the mutable readiness flag, and the boot
10
+ * timestamp. The lifecycle layer (S5) calls `fastify.listen()` and flips
11
+ * `readiness.setOk()` / `readiness.setDraining()` around start/stop.
12
+ *
13
+ * This module owns only wiring — no business logic. All route logic
14
+ * delegates to the shared handlers in `handlers/`.
15
+ */
16
+ import Fastify from "fastify";
17
+ import { registerSignalsRoute } from "./routes/signals.js";
18
+ import { registerAuditRoute } from "./routes/audit.js";
19
+ import { registerHealthRoute } from "./routes/health.js";
20
+ import { createReadiness } from "./readiness.js";
21
+ /**
22
+ * Create and configure the Fastify HTTP server.
23
+ *
24
+ * Does NOT start listening. Call `artifacts.fastify.listen(...)` from the
25
+ * lifecycle layer (S5) after `createApi()` returns.
26
+ *
27
+ * Error handler behaviour:
28
+ * - `400` (schema validation), `413` (body too large), `415` (wrong
29
+ * content-type) are passed through as `INVALID_REQUEST` with the original
30
+ * Fastify message so callers can read the validation detail.
31
+ * - All other errors are logged and returned as `500 / UNAVAILABLE`.
32
+ *
33
+ * Request timeout strategy (resolved in S5):
34
+ * A single server-wide `requestTimeout: 30_000` covers all routes.
35
+ * The plan called for per-route values (10s for `/signals` + `/health`,
36
+ * 30s for `/audit`), but Fastify v5 does not expose a declarative per-route
37
+ * `requestTimeout` and wiring an `onRequest` hook with manual `setTimeout`
38
+ * calls adds meaningful complexity for a localhost-only server where requests
39
+ * that genuinely hang are almost always MCP-side. The 30s ceiling is driven
40
+ * entirely by `/audit` (MCP round-trip); `/signals` and `/health` both
41
+ * complete in well under 1s in practice. If per-route enforcement becomes
42
+ * necessary, add an `onRequest` hook in this file.
43
+ *
44
+ * @param deps Dependencies injected by the plugin entry point.
45
+ * @returns `{ fastify, readiness, bootedAt }`.
46
+ */
47
+ export function createApi(deps) {
48
+ const bootedAt = Date.now();
49
+ const readiness = createReadiness();
50
+ const fastify = Fastify({
51
+ logger: { level: "info" },
52
+ bodyLimit: 256 * 1024, // 256 KB — generous for signal batches, hard cap on abuse.
53
+ disableRequestLogging: false,
54
+ /**
55
+ * 30s request ceiling — driven by `/audit` (MCP round-trip).
56
+ * `/signals` and `/health` complete far faster; we accept the unified
57
+ * ceiling to avoid per-route hook complexity on a localhost-only server.
58
+ * Documented in docs/runtime-docs/runtime-api.md.
59
+ */
60
+ requestTimeout: 30_000,
61
+ });
62
+ // Custom error handler — normalises Fastify validation / payload errors into
63
+ // the senpi-stack envelope `{ success: false, error: { code, message } }`.
64
+ fastify.setErrorHandler((error, _req, reply) => {
65
+ // Fastify v5 types setErrorHandler with `unknown`; cast once for the handler.
66
+ const err = error;
67
+ const statusCode = err.statusCode;
68
+ if (statusCode === 400 ||
69
+ statusCode === 413 ||
70
+ statusCode === 415) {
71
+ return reply.status(statusCode).send({
72
+ success: false,
73
+ error: {
74
+ code: "INVALID_REQUEST",
75
+ message: err.message,
76
+ },
77
+ });
78
+ }
79
+ deps.logger.error("[senpi-core][runtime-api] Unhandled error", {
80
+ err,
81
+ });
82
+ return reply.status(500).send({
83
+ success: false,
84
+ error: {
85
+ code: "UNAVAILABLE",
86
+ message: err.message,
87
+ },
88
+ });
89
+ });
90
+ registerSignalsRoute(fastify, {
91
+ runtimeHandles: deps.runtimeHandles,
92
+ logger: deps.logger,
93
+ maxItems: deps.apiConfig.maxItemsPerSignalsRequest,
94
+ transport: "http",
95
+ });
96
+ registerAuditRoute(fastify, {
97
+ runtimeHandles: deps.runtimeHandles,
98
+ logger: deps.logger,
99
+ });
100
+ registerHealthRoute(fastify, {
101
+ runtimeHandles: deps.runtimeHandles,
102
+ apiConfig: { host: deps.apiConfig.host, port: deps.apiConfig.port },
103
+ readiness,
104
+ version: deps.version,
105
+ bootedAt,
106
+ });
107
+ return { fastify, readiness, bootedAt };
108
+ }
109
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/runtime-api/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,OAAiC,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAsCjD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,SAAS,CAAC,IAAmB;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC5B,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;IAEpC,MAAM,OAAO,GAAG,OAAO,CAAC;QACtB,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;QACzB,SAAS,EAAE,GAAG,GAAG,IAAI,EAAE,2DAA2D;QAClF,qBAAqB,EAAE,KAAK;QAC5B;;;;;WAKG;QACH,cAAc,EAAE,MAAM;KACvB,CAAC,CAAC;IAEH,6EAA6E;IAC7E,2EAA2E;IAC3E,OAAO,CAAC,eAAe,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QAC7C,8EAA8E;QAC9E,MAAM,GAAG,GAAG,KAAwC,CAAC;QACrD,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;QAClC,IACE,UAAU,KAAK,GAAG;YAClB,UAAU,KAAK,GAAG;YAClB,UAAU,KAAK,GAAG,EAClB,CAAC;YACD,OAAO,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;gBACnC,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE;oBACL,IAAI,EAAE,iBAAiB;oBACvB,OAAO,EAAE,GAAG,CAAC,OAAO;iBACrB;aACF,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2CAA2C,EAAE;YAC7D,GAAG;SACJ,CAAC,CAAC;QACH,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC5B,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,GAAG,CAAC,OAAO;aACrB;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,oBAAoB,CAAC,OAAO,EAAE;QAC5B,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,yBAAyB;QAClD,SAAS,EAAE,MAAM;KAClB,CAAC,CAAC;IAEH,kBAAkB,CAAC,OAAO,EAAE;QAC1B,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC,CAAC;IAEH,mBAAmB,CAAC,OAAO,EAAE;QAC3B,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;QACnE,SAAS;QACT,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ;KACT,CAAC,CAAC;IAEH,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AAC1C,CAAC"}
@@ -1,4 +1,22 @@
1
1
  import type { McpTransport, SenpiClientConfig, CreatePositionRequest, CreatePositionResult, EditPositionResult, GetPositionResult, OpenPosition, GetStrategyResult, GetOpenOrdersResult, GetTraderHistoryOptions, TraderHistoryEntry, GetTraderStateOptions, TraderStateResult, StrategyFundResult, ClosePositionsRequest, GetPnlHistoryOptions, ListStrategiesFilters, StrategySummary, RatchetStopPosition, RatchetStopEvent, AddRatchetStopInput, EditRatchetStopInput, DeleteRatchetStopInput, QueryRatchetStopsInput, QueryRatchetStopEventsInput } from './types.js';
2
+ /**
3
+ * Typed error thrown by `SenpiClient` mutation methods when the response
4
+ * reports a failure at either layer (outer MCP envelope or inner engine
5
+ * result). Callers that today only catch `throw` will fire on real
6
+ * failures instead of silently treating phantom-success responses as
7
+ * success — preventing the orphaned-DSL-state class of bug.
8
+ *
9
+ * Codes:
10
+ * - `UNEXPECTED_RESPONSE` — response shape is not the expected object.
11
+ * - `UPSTREAM_ERROR` — outer MCP envelope `success === false`.
12
+ * - `ENGINE_FAILURE` — outer envelope ok, but inner engine result
13
+ * reported `success === false` (e.g. CREATE_ORDER_RESTING).
14
+ */
15
+ export declare class SenpiClientError extends Error {
16
+ readonly code: "UNEXPECTED_RESPONSE" | "UPSTREAM_ERROR" | "ENGINE_FAILURE";
17
+ readonly details: unknown;
18
+ constructor(code: "UNEXPECTED_RESPONSE" | "UPSTREAM_ERROR" | "ENGINE_FAILURE", message: string, details?: unknown);
19
+ }
2
20
  export declare class SenpiClient {
3
21
  private readonly injectedTransport;
4
22
  private readonly config;
@@ -10,6 +28,27 @@ export declare class SenpiClient {
10
28
  connect(): Promise<void>;
11
29
  disconnect(): Promise<void>;
12
30
  private callTool;
31
+ /**
32
+ * Run an MCP `audit_query` and return the unwrapped response payload.
33
+ *
34
+ * Stays a thin pass-through so the runtime layer (which owns the
35
+ * wallet→strategyId resolution and error mapping) can compose the call
36
+ * with full type-safety. Callers must already have set
37
+ * `resource_type` / `resource_id` if scoping is desired — this method
38
+ * does not interpret arguments.
39
+ *
40
+ * @param args Arbitrary key/value map forwarded verbatim as the MCP
41
+ * tool input. Caller is responsible for matching the
42
+ * MCP `audit_query` schema.
43
+ * @param opts Optional AbortSignal; when fired, the underlying MCP
44
+ * `callTool` is cancelled (the SDK rejects with an
45
+ * AbortError).
46
+ * @returns The `.data` field of the MCP envelope when present;
47
+ * otherwise the raw response.
48
+ */
49
+ auditQuery(args: Record<string, unknown>, opts?: {
50
+ signal?: AbortSignal;
51
+ }): Promise<unknown>;
13
52
  /**
14
53
  * Returns an adapter compatible with SenpiToolClient so the same connection
15
54
  * can be used for scanner provider MCP tool calls (e.g. leaderboard_get_markets).
@@ -17,14 +56,36 @@ export declare class SenpiClient {
17
56
  asToolClient(): {
18
57
  callTool(name: string, args: Record<string, unknown>): Promise<unknown>;
19
58
  };
59
+ /**
60
+ * Register a backend ratchet-stop (DSL Phase 2 handoff).
61
+ *
62
+ * Throws `SenpiClientError` on outer envelope failure or inner engine
63
+ * `success: false`. Callers' existing `if (result.success && result.position)`
64
+ * branches now treat the success path only — failures land in the
65
+ * surrounding try/catch instead of falling through.
66
+ */
20
67
  addRatchetStop(input: AddRatchetStopInput): Promise<{
21
68
  success: boolean;
22
69
  position?: RatchetStopPosition;
23
70
  }>;
71
+ /**
72
+ * Update an existing backend ratchet-stop.
73
+ *
74
+ * Throws `SenpiClientError` on outer envelope failure or inner engine
75
+ * `success: false`.
76
+ */
24
77
  editRatchetStop(input: EditRatchetStopInput): Promise<{
25
78
  success: boolean;
26
79
  position?: RatchetStopPosition;
27
80
  }>;
81
+ /**
82
+ * Delete a backend ratchet-stop. Used during DSL teardown / handoff close.
83
+ *
84
+ * Throws `SenpiClientError` on outer envelope failure or inner engine
85
+ * `success: false`. Previously this method's `{ success: boolean }` return
86
+ * was ignored by every caller (only `throw` was caught), which left
87
+ * zombie ratchet-stops on the backend when the engine reported failure.
88
+ */
28
89
  deleteRatchetStop(input: DeleteRatchetStopInput): Promise<{
29
90
  success: boolean;
30
91
  }>;
@@ -44,6 +105,21 @@ export declare class SenpiClient {
44
105
  * Set/update SL (and optionally TP) on an open position. Uses MCP edit_position.
45
106
  * For XYZ DEX pass dex: "xyz" so coin is sent as "xyz:COIN"; otherwise main DEX uses plain coin.
46
107
  */
108
+ /**
109
+ * Set / update an exchange-side stop loss (or perform other position
110
+ * edits) on an open position.
111
+ *
112
+ * Throws `SenpiClientError` when either response layer reports failure:
113
+ * - `UPSTREAM_ERROR` — MCP envelope `success: false`.
114
+ * - `ENGINE_FAILURE` — outer ok, inner engine `success: false`
115
+ * (e.g. position not found, SL placement rejected).
116
+ *
117
+ * Returning normally means the engine accepted the edit. The returned
118
+ * `EditPositionResult` carries `slOrderId` when an SL was placed/updated;
119
+ * a `null` slOrderId on a non-throwing return indicates the engine reported
120
+ * success without the expected SL artifact — caller should treat that as
121
+ * a soft failure (logged as a warn) and re-attempt on the next sync tick.
122
+ */
47
123
  editPosition(wallet: string, payload: {
48
124
  coin: string;
49
125
  dex?: string;
@@ -105,8 +181,22 @@ export declare class SenpiClient {
105
181
  topUp(wallet: string, amount: number): Promise<StrategyFundResult>;
106
182
  withdrawFunds(wallet: string, amount: number): Promise<StrategyFundResult>;
107
183
  closeStrategy(wallet: string): Promise<StrategyFundResult>;
108
- closePositions(wallet: string, positions: ClosePositionsRequest[]): Promise<unknown>;
109
- cancelOrder(wallet: string, orderId: number): Promise<unknown>;
184
+ /**
185
+ * Submit a batch close_positions MCP call.
186
+ *
187
+ * Throws `SenpiClientError` on outer envelope or inner engine failure
188
+ * (same shape as {@link closePosition}). Successful return means the
189
+ * batch was accepted; per-position confirmation must still come from
190
+ * position-tracker scanner events.
191
+ */
192
+ closePositions(wallet: string, positions: ClosePositionsRequest[]): Promise<Record<string, unknown>>;
193
+ /**
194
+ * Cancel an outstanding order by id.
195
+ *
196
+ * Throws `SenpiClientError` on outer envelope or inner engine failure
197
+ * (same shape as {@link closePosition}).
198
+ */
199
+ cancelOrder(wallet: string, orderId: number): Promise<Record<string, unknown>>;
110
200
  getAssetTradingLimits(wallet: string, coin: string, dex?: string): Promise<{
111
201
  maxLeverage: number;
112
202
  } | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/senpi/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/senpi/client.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,99 @@ import { extractMinClearingHouseTime, createClearingHouseStalenessGuard } from "
6
6
  import { normalizeDslCoinDex, reconcileDexFromClearingHouseKey } from "../utils/perp-coin.js";
7
7
  import { createLogger } from "../utils/logger.js";
8
8
  const logger = createLogger("[senpi][client]");
9
+ /**
10
+ * Typed error thrown by `SenpiClient` mutation methods when the response
11
+ * reports a failure at either layer (outer MCP envelope or inner engine
12
+ * result). Callers that today only catch `throw` will fire on real
13
+ * failures instead of silently treating phantom-success responses as
14
+ * success — preventing the orphaned-DSL-state class of bug.
15
+ *
16
+ * Codes:
17
+ * - `UNEXPECTED_RESPONSE` — response shape is not the expected object.
18
+ * - `UPSTREAM_ERROR` — outer MCP envelope `success === false`.
19
+ * - `ENGINE_FAILURE` — outer envelope ok, but inner engine result
20
+ * reported `success === false` (e.g. CREATE_ORDER_RESTING).
21
+ */
22
+ export class SenpiClientError extends Error {
23
+ code;
24
+ details;
25
+ constructor(code, message, details) {
26
+ super(message);
27
+ this.name = "SenpiClientError";
28
+ this.code = code;
29
+ this.details = details;
30
+ }
31
+ }
32
+ /**
33
+ * Best-effort engine error message extraction. Mirrors the resolver used
34
+ * inside the MCP server's mutation handlers (Task 2 fix shape) so DSL/
35
+ * action logs surface the same string the operator sees in audit logs.
36
+ */
37
+ function extractEngineMessage(data) {
38
+ const err = data.error;
39
+ if (err && typeof err === "object") {
40
+ const errObj = err;
41
+ const description = typeof errObj.description === "string" ? errObj.description : undefined;
42
+ const errMessage = typeof errObj.message === "string" ? errObj.message : undefined;
43
+ const errCode = typeof errObj.code === "string" ? errObj.code : undefined;
44
+ const text = description ?? errMessage;
45
+ if (text && errCode)
46
+ return `${errCode}: ${text}`;
47
+ if (text)
48
+ return text;
49
+ if (errCode)
50
+ return errCode;
51
+ }
52
+ else if (typeof err === "string" && err.length > 0) {
53
+ return err;
54
+ }
55
+ if (typeof data.message === "string" && data.message.length > 0)
56
+ return data.message;
57
+ if (typeof data.errorCode === "string" && data.errorCode.length > 0)
58
+ return data.errorCode;
59
+ return "Trading engine reported failure";
60
+ }
61
+ /**
62
+ * Enforces success at both response layers and returns the unwrapped
63
+ * inner data. Throws `SenpiClientError` on any reported failure.
64
+ *
65
+ * Resolution order:
66
+ * 1. `raw` is not an object → `UNEXPECTED_RESPONSE`.
67
+ * 2. Outer envelope `success === false` → `UPSTREAM_ERROR` with the
68
+ * envelope's error.code / error.message preserved.
69
+ * 3. Unwrap envelope to inner data; if not an object → `UNEXPECTED_RESPONSE`.
70
+ * 4. Inner data carries a boolean `success === false` → `ENGINE_FAILURE`
71
+ * with a best-effort engine message.
72
+ * 5. Otherwise return the inner data.
73
+ *
74
+ * Used by every mutation method (createPosition, editPosition,
75
+ * closePosition, etc.) so DSL/actions can rely on `throw` semantics
76
+ * matching real failures, instead of trusting an outer-envelope-only
77
+ * "success" that may mask an engine rejection.
78
+ */
79
+ function requireMutationSuccess(tool, raw) {
80
+ if (!isRecord(raw)) {
81
+ throw new SenpiClientError("UNEXPECTED_RESPONSE", `${tool}: response is not an object`, raw);
82
+ }
83
+ // Outer envelope check — only present on direct MCP responses.
84
+ if (raw.success === false) {
85
+ const errObj = isRecord(raw.error) ? raw.error : undefined;
86
+ const code = (errObj && typeof errObj.code === "string") ? errObj.code : "UPSTREAM_ERROR";
87
+ const message = (errObj && typeof errObj.message === "string")
88
+ ? errObj.message
89
+ : `${tool}: outer envelope reported failure`;
90
+ throw new SenpiClientError("UPSTREAM_ERROR", `${tool}: ${code}: ${message}`, raw);
91
+ }
92
+ const data = unwrapEnvelope(raw) ?? raw;
93
+ if (!isRecord(data)) {
94
+ throw new SenpiClientError("UNEXPECTED_RESPONSE", `${tool}: inner data is not an object`, raw);
95
+ }
96
+ // Inner engine-level success check (only when the field is explicitly boolean).
97
+ if (typeof data.success === "boolean" && data.success === false) {
98
+ throw new SenpiClientError("ENGINE_FAILURE", `${tool}: ${extractEngineMessage(data)}`, data);
99
+ }
100
+ return data;
101
+ }
9
102
  /**
10
103
  * execution_get_order_status payloads vary: flat `{ filled, status }` vs Hyperliquid-style
11
104
  * `{ status: "order", order: { status: "filled", order: {...} } }`. Top-level `status` may be a
@@ -125,7 +218,7 @@ export class SenpiClient {
125
218
  this.mcpClient = null;
126
219
  }
127
220
  }
128
- async callTool(toolName, args) {
221
+ async callTool(toolName, args, opts) {
129
222
  // Injected mode: delegate directly
130
223
  if (this.injectedTransport) {
131
224
  return this.injectedTransport(toolName, args);
@@ -134,7 +227,9 @@ export class SenpiClient {
134
227
  if (!this.mcpClient) {
135
228
  throw new Error('SenpiClient not connected — call connect() first');
136
229
  }
137
- const result = await this.mcpClient.callTool({ name: toolName, arguments: args });
230
+ const result = opts?.signal
231
+ ? await this.mcpClient.callTool({ name: toolName, arguments: args }, undefined, { signal: opts.signal })
232
+ : await this.mcpClient.callTool({ name: toolName, arguments: args });
138
233
  if (result.content && Array.isArray(result.content) && result.content.length > 0) {
139
234
  const part = result.content[0];
140
235
  if (part && 'text' in part && typeof part.text === 'string') {
@@ -148,6 +243,28 @@ export class SenpiClient {
148
243
  }
149
244
  return result;
150
245
  }
246
+ /**
247
+ * Run an MCP `audit_query` and return the unwrapped response payload.
248
+ *
249
+ * Stays a thin pass-through so the runtime layer (which owns the
250
+ * wallet→strategyId resolution and error mapping) can compose the call
251
+ * with full type-safety. Callers must already have set
252
+ * `resource_type` / `resource_id` if scoping is desired — this method
253
+ * does not interpret arguments.
254
+ *
255
+ * @param args Arbitrary key/value map forwarded verbatim as the MCP
256
+ * tool input. Caller is responsible for matching the
257
+ * MCP `audit_query` schema.
258
+ * @param opts Optional AbortSignal; when fired, the underlying MCP
259
+ * `callTool` is cancelled (the SDK rejects with an
260
+ * AbortError).
261
+ * @returns The `.data` field of the MCP envelope when present;
262
+ * otherwise the raw response.
263
+ */
264
+ async auditQuery(args, opts) {
265
+ const raw = await this.callTool('audit_query', args, opts);
266
+ return unwrapEnvelope(raw) ?? raw;
267
+ }
151
268
  /**
152
269
  * Returns an adapter compatible with SenpiToolClient so the same connection
153
270
  * can be used for scanner provider MCP tool calls (e.g. leaderboard_get_markets).
@@ -158,26 +275,48 @@ export class SenpiClient {
158
275
  };
159
276
  }
160
277
  // ─── Ratchet stop (DSL backend engine) ──────────────────────────────────────
278
+ /**
279
+ * Register a backend ratchet-stop (DSL Phase 2 handoff).
280
+ *
281
+ * Throws `SenpiClientError` on outer envelope failure or inner engine
282
+ * `success: false`. Callers' existing `if (result.success && result.position)`
283
+ * branches now treat the success path only — failures land in the
284
+ * surrounding try/catch instead of falling through.
285
+ */
161
286
  async addRatchetStop(input) {
162
287
  const raw = await this.callTool("ratchet_stop_add", {
163
288
  ...input,
164
289
  asset: dexQualifiedAsset(input.asset, input.dex),
165
290
  });
166
- return this.unwrapRatchetStopResult(raw, "ratchet_stop_add");
291
+ return requireMutationSuccess("ratchet_stop_add", raw);
167
292
  }
293
+ /**
294
+ * Update an existing backend ratchet-stop.
295
+ *
296
+ * Throws `SenpiClientError` on outer envelope failure or inner engine
297
+ * `success: false`.
298
+ */
168
299
  async editRatchetStop(input) {
169
300
  const raw = await this.callTool("ratchet_stop_edit", {
170
301
  ...input,
171
302
  asset: dexQualifiedAsset(input.asset, input.dex),
172
303
  });
173
- return this.unwrapRatchetStopResult(raw, "ratchet_stop_edit");
304
+ return requireMutationSuccess("ratchet_stop_edit", raw);
174
305
  }
306
+ /**
307
+ * Delete a backend ratchet-stop. Used during DSL teardown / handoff close.
308
+ *
309
+ * Throws `SenpiClientError` on outer envelope failure or inner engine
310
+ * `success: false`. Previously this method's `{ success: boolean }` return
311
+ * was ignored by every caller (only `throw` was caught), which left
312
+ * zombie ratchet-stops on the backend when the engine reported failure.
313
+ */
175
314
  async deleteRatchetStop(input) {
176
315
  const raw = await this.callTool("ratchet_stop_delete", {
177
316
  ...input,
178
317
  asset: dexQualifiedAsset(input.asset, input.dex),
179
318
  });
180
- return this.unwrapRatchetStopResult(raw, "ratchet_stop_delete");
319
+ return requireMutationSuccess("ratchet_stop_delete", raw);
181
320
  }
182
321
  async queryRatchetStops(input) {
183
322
  const qualifiedAsset = input.asset ? dexQualifiedAsset(input.asset, input.dex) : undefined;
@@ -247,6 +386,21 @@ export class SenpiClient {
247
386
  * Set/update SL (and optionally TP) on an open position. Uses MCP edit_position.
248
387
  * For XYZ DEX pass dex: "xyz" so coin is sent as "xyz:COIN"; otherwise main DEX uses plain coin.
249
388
  */
389
+ /**
390
+ * Set / update an exchange-side stop loss (or perform other position
391
+ * edits) on an open position.
392
+ *
393
+ * Throws `SenpiClientError` when either response layer reports failure:
394
+ * - `UPSTREAM_ERROR` — MCP envelope `success: false`.
395
+ * - `ENGINE_FAILURE` — outer ok, inner engine `success: false`
396
+ * (e.g. position not found, SL placement rejected).
397
+ *
398
+ * Returning normally means the engine accepted the edit. The returned
399
+ * `EditPositionResult` carries `slOrderId` when an SL was placed/updated;
400
+ * a `null` slOrderId on a non-throwing return indicates the engine reported
401
+ * success without the expected SL artifact — caller should treat that as
402
+ * a soft failure (logged as a warn) and re-attempt on the next sync tick.
403
+ */
250
404
  async editPosition(wallet, payload) {
251
405
  const coin = payload.dex === "xyz" && payload.coin && !payload.coin.startsWith("xyz:")
252
406
  ? `xyz:${payload.coin}`
@@ -266,7 +420,10 @@ export class SenpiClient {
266
420
  rawKeys: isRecord(raw) ? Object.keys(raw) : [],
267
421
  rawShape: isRecord(raw) ? JSON.stringify(raw).slice(0, 500) : String(raw),
268
422
  });
269
- const result = (unwrapEnvelope(raw) ?? raw);
423
+ // Throws on outer/inner success: false. Returning means the engine
424
+ // accepted the edit. Older callers that only catch `throw` will now
425
+ // see real failures instead of silently treating them as success.
426
+ const result = requireMutationSuccess("edit_position", raw);
270
427
  if (result && typeof result === "object" && !Array.isArray(result)) {
271
428
  const ro = result;
272
429
  const slOrderId = extractSlOrderId(ro, raw);
@@ -537,17 +694,33 @@ export class SenpiClient {
537
694
  return { success: false };
538
695
  return { success: data.success === true, message: asString(data.message) ?? undefined };
539
696
  }
697
+ /**
698
+ * Submit a batch close_positions MCP call.
699
+ *
700
+ * Throws `SenpiClientError` on outer envelope or inner engine failure
701
+ * (same shape as {@link closePosition}). Successful return means the
702
+ * batch was accepted; per-position confirmation must still come from
703
+ * position-tracker scanner events.
704
+ */
540
705
  async closePositions(wallet, positions) {
541
- return this.callTool("strategy_close_positions", {
706
+ const raw = await this.callTool("strategy_close_positions", {
542
707
  strategy_wallet: wallet,
543
708
  positions,
544
709
  });
710
+ return requireMutationSuccess("strategy_close_positions", raw);
545
711
  }
712
+ /**
713
+ * Cancel an outstanding order by id.
714
+ *
715
+ * Throws `SenpiClientError` on outer envelope or inner engine failure
716
+ * (same shape as {@link closePosition}).
717
+ */
546
718
  async cancelOrder(wallet, orderId) {
547
- return this.callTool("cancel_order", {
719
+ const raw = await this.callTool("cancel_order", {
548
720
  strategyWalletAddress: wallet,
549
721
  orderId,
550
722
  });
723
+ return requireMutationSuccess("cancel_order", raw);
551
724
  }
552
725
  async getAssetTradingLimits(wallet, coin, dex) {
553
726
  try {