openbroker 1.3.1 → 1.4.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 +12 -0
- package/SKILL.md +7 -4
- package/dist/auto/audit.d.ts +57 -0
- package/dist/auto/audit.d.ts.map +1 -0
- package/dist/auto/audit.js +407 -0
- package/dist/auto/cli.d.ts +2 -0
- package/dist/auto/cli.d.ts.map +1 -0
- package/dist/auto/cli.js +423 -0
- package/dist/auto/events.d.ts +11 -0
- package/dist/auto/events.d.ts.map +1 -0
- package/dist/auto/events.js +36 -0
- package/dist/auto/examples/dca.d.ts +4 -0
- package/dist/auto/examples/dca.d.ts.map +1 -0
- package/dist/auto/examples/dca.js +60 -0
- package/dist/auto/examples/funding-arb.d.ts +4 -0
- package/dist/auto/examples/funding-arb.d.ts.map +1 -0
- package/dist/auto/examples/funding-arb.js +81 -0
- package/dist/auto/examples/grid.d.ts +4 -0
- package/dist/auto/examples/grid.d.ts.map +1 -0
- package/dist/auto/examples/grid.js +114 -0
- package/dist/auto/examples/mm-maker.d.ts +4 -0
- package/dist/auto/examples/mm-maker.d.ts.map +1 -0
- package/dist/auto/examples/mm-maker.js +131 -0
- package/dist/auto/examples/mm-spread.d.ts +4 -0
- package/dist/auto/examples/mm-spread.d.ts.map +1 -0
- package/dist/auto/examples/mm-spread.js +119 -0
- package/dist/auto/examples/price-alert.d.ts +4 -0
- package/dist/auto/examples/price-alert.d.ts.map +1 -0
- package/dist/auto/examples/price-alert.js +85 -0
- package/dist/auto/keep-awake.d.ts +11 -0
- package/dist/auto/keep-awake.d.ts.map +1 -0
- package/dist/auto/keep-awake.js +70 -0
- package/dist/auto/loader.d.ts +22 -0
- package/dist/auto/loader.d.ts.map +1 -0
- package/dist/auto/loader.js +127 -0
- package/dist/auto/prune.d.ts +40 -0
- package/dist/auto/prune.d.ts.map +1 -0
- package/dist/auto/prune.js +204 -0
- package/dist/auto/registry.d.ts +24 -0
- package/dist/auto/registry.d.ts.map +1 -0
- package/dist/auto/registry.js +93 -0
- package/dist/auto/report.d.ts +3 -0
- package/dist/auto/report.d.ts.map +1 -0
- package/dist/auto/report.js +385 -0
- package/dist/auto/runtime.d.ts +33 -0
- package/dist/auto/runtime.d.ts.map +1 -0
- package/dist/auto/runtime.js +844 -0
- package/dist/auto/types.d.ts +236 -0
- package/dist/auto/types.d.ts.map +1 -0
- package/dist/auto/types.js +3 -0
- package/dist/core/client.d.ts +684 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +2040 -0
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +143 -0
- package/dist/core/types.d.ts +221 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/utils.d.ts +61 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +142 -0
- package/dist/core/ws.d.ts +121 -0
- package/dist/core/ws.d.ts.map +1 -0
- package/dist/core/ws.js +222 -0
- package/dist/info/account.d.ts +3 -0
- package/dist/info/account.d.ts.map +1 -0
- package/dist/info/account.js +198 -0
- package/dist/info/all-markets.d.ts +3 -0
- package/dist/info/all-markets.d.ts.map +1 -0
- package/dist/info/all-markets.js +272 -0
- package/dist/info/candles.d.ts +3 -0
- package/dist/info/candles.d.ts.map +1 -0
- package/dist/info/candles.js +120 -0
- package/dist/info/fees.d.ts +3 -0
- package/dist/info/fees.d.ts.map +1 -0
- package/dist/info/fees.js +87 -0
- package/dist/info/fills.d.ts +3 -0
- package/dist/info/fills.d.ts.map +1 -0
- package/dist/info/fills.js +105 -0
- package/dist/info/funding-history.d.ts +3 -0
- package/dist/info/funding-history.d.ts.map +1 -0
- package/dist/info/funding-history.js +98 -0
- package/dist/info/funding-scan.d.ts +3 -0
- package/dist/info/funding-scan.d.ts.map +1 -0
- package/dist/info/funding-scan.js +178 -0
- package/dist/info/funding.d.ts +3 -0
- package/dist/info/funding.d.ts.map +1 -0
- package/dist/info/funding.js +158 -0
- package/dist/info/markets.d.ts +3 -0
- package/dist/info/markets.d.ts.map +1 -0
- package/dist/info/markets.js +178 -0
- package/dist/info/order-status.d.ts +3 -0
- package/dist/info/order-status.d.ts.map +1 -0
- package/dist/info/order-status.js +85 -0
- package/dist/info/orders.d.ts +3 -0
- package/dist/info/orders.d.ts.map +1 -0
- package/dist/info/orders.js +162 -0
- package/dist/info/outcomes.d.ts +3 -0
- package/dist/info/outcomes.d.ts.map +1 -0
- package/dist/info/outcomes.js +175 -0
- package/dist/info/positions.d.ts +3 -0
- package/dist/info/positions.d.ts.map +1 -0
- package/dist/info/positions.js +127 -0
- package/dist/info/rate-limit.d.ts +3 -0
- package/dist/info/rate-limit.d.ts.map +1 -0
- package/dist/info/rate-limit.js +58 -0
- package/dist/info/search-markets.d.ts +3 -0
- package/dist/info/search-markets.d.ts.map +1 -0
- package/dist/info/search-markets.js +296 -0
- package/dist/info/spot.d.ts +3 -0
- package/dist/info/spot.d.ts.map +1 -0
- package/dist/info/spot.js +192 -0
- package/dist/info/trades.d.ts +3 -0
- package/dist/info/trades.d.ts.map +1 -0
- package/dist/info/trades.js +97 -0
- package/dist/lib.d.ts +14 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +17 -0
- package/dist/operations/bracket.d.ts +28 -0
- package/dist/operations/bracket.d.ts.map +1 -0
- package/dist/operations/bracket.js +266 -0
- package/dist/operations/cancel.d.ts +3 -0
- package/dist/operations/cancel.d.ts.map +1 -0
- package/dist/operations/cancel.js +107 -0
- package/dist/operations/chase.d.ts +25 -0
- package/dist/operations/chase.d.ts.map +1 -0
- package/dist/operations/chase.js +215 -0
- package/dist/operations/limit-order.d.ts +3 -0
- package/dist/operations/limit-order.d.ts.map +1 -0
- package/dist/operations/limit-order.js +144 -0
- package/dist/operations/market-order.d.ts +3 -0
- package/dist/operations/market-order.d.ts.map +1 -0
- package/dist/operations/market-order.js +153 -0
- package/dist/operations/outcome-order.d.ts +3 -0
- package/dist/operations/outcome-order.d.ts.map +1 -0
- package/dist/operations/outcome-order.js +171 -0
- package/dist/operations/scale.d.ts +3 -0
- package/dist/operations/scale.d.ts.map +1 -0
- package/dist/operations/scale.js +212 -0
- package/dist/operations/set-tpsl.d.ts +3 -0
- package/dist/operations/set-tpsl.d.ts.map +1 -0
- package/dist/operations/set-tpsl.js +277 -0
- package/dist/operations/spot-order.d.ts +3 -0
- package/dist/operations/spot-order.d.ts.map +1 -0
- package/dist/operations/spot-order.js +173 -0
- package/dist/operations/trigger-order.d.ts +3 -0
- package/dist/operations/trigger-order.d.ts.map +1 -0
- package/dist/operations/trigger-order.js +177 -0
- package/dist/operations/twap-cancel.d.ts +3 -0
- package/dist/operations/twap-cancel.d.ts.map +1 -0
- package/dist/operations/twap-cancel.js +57 -0
- package/dist/operations/twap-status.d.ts +3 -0
- package/dist/operations/twap-status.d.ts.map +1 -0
- package/dist/operations/twap-status.js +81 -0
- package/dist/operations/twap.d.ts +3 -0
- package/dist/operations/twap.d.ts.map +1 -0
- package/dist/operations/twap.js +124 -0
- package/dist/setup/approve-builder.d.ts +3 -0
- package/dist/setup/approve-builder.d.ts.map +1 -0
- package/dist/setup/approve-builder.js +155 -0
- package/dist/setup/env.d.ts +4 -0
- package/dist/setup/env.d.ts.map +1 -0
- package/dist/setup/env.js +8 -0
- package/dist/setup/onboard.d.ts +10 -0
- package/dist/setup/onboard.d.ts.map +1 -0
- package/dist/setup/onboard.js +462 -0
- package/package.json +10 -4
- package/scripts/core/client.ts +13 -3
- package/scripts/info/all-markets.ts +18 -2
- package/scripts/info/search-markets.ts +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Open Broker will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.4.0] - 2026-05-31
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Library is now runtime-importable by external consumers.** The package entry
|
|
9
|
+
previously pointed at raw TypeScript source (`main: ./scripts/lib.ts`), so any
|
|
10
|
+
in-process consumer running outside a TS loader (or under a runner that doesn't
|
|
11
|
+
transpile `node_modules`, e.g. tsx) imported an **empty module**. Added a build
|
|
12
|
+
(`npm run build` → `tsc`) that emits compiled JS + type declarations to `dist/`,
|
|
13
|
+
and pointed `main`/`types`/`exports` at `dist/lib.js` / `dist/lib.d.ts`. The CLI
|
|
14
|
+
still runs from source via tsx; only the library entry changed. `prepublishOnly`
|
|
15
|
+
now builds before publishing. No public API changes.
|
|
16
|
+
|
|
5
17
|
## [1.3.0] - 2026-05-07
|
|
6
18
|
|
|
7
19
|
### Added
|
package/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Hyperliquid trading CLI skill for agents. Use when an agent needs t
|
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires Node.js 22+, network access to api.hyperliquid.xyz
|
|
6
6
|
homepage: https://www.npmjs.com/package/openbroker
|
|
7
|
-
metadata: {"author": "monemetrics", "version": "1.3.
|
|
7
|
+
metadata: {"author": "monemetrics", "version": "1.3.2"}
|
|
8
8
|
allowed-tools: Bash(openbroker:*)
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -52,12 +52,15 @@ Common globals:
|
|
|
52
52
|
openbroker search --query GOLD --json
|
|
53
53
|
openbroker search --query BTC --type perp --json
|
|
54
54
|
openbroker all-markets --type hip3 --json
|
|
55
|
+
openbroker all-markets --type outcome --json
|
|
55
56
|
openbroker outcomes --query BTC --json
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
- HIP-3 perps use `dex:COIN`, e.g. `xyz:CL`, not bare `CL`.
|
|
59
60
|
- `assetId` is the canonical identifier for comparisons and persisted agent state; order placement still uses `--coin`.
|
|
60
|
-
- HIP-4
|
|
61
|
+
- For HIP-4 discovery, use `outcomes --json` for grouped market metadata and `all-markets --type outcome --json` for flattened side rows.
|
|
62
|
+
- HIP-4 outcome orders use `--outcome <id|#encoding|+encoding>` plus `--outcome-side yes|no` when the reference is a plain ID. Encoded sides use `encoding = 10 * outcomeId + side`, where side `0` is the first side and side `1` is the second side.
|
|
63
|
+
- HIP-4 order books use `#<encoding>` coins; spot balances may show `+<encoding>` token names.
|
|
61
64
|
- On testnet, HIP-3 metadata may need an explicit prefixed coin such as `dex:COIN`.
|
|
62
65
|
|
|
63
66
|
## CLI command map
|
|
@@ -72,7 +75,7 @@ Most info commands accept `--json`. Use `--coin`, `--top`, and `--address` where
|
|
|
72
75
|
| `positions` | Open perp positions and liquidation distance | `--coin`, `--address` |
|
|
73
76
|
| `funding` | Funding rates | `--coin`, `--top`, `--sort annualized|hourly|oi`, `--all`, `--include-hip3` |
|
|
74
77
|
| `markets` | Perp market data | `--coin`, `--top`, `--sort volume|oi|change`, `--include-hip3` |
|
|
75
|
-
| `all-markets` | Browse every venue type | `--type perp|hip3|spot|outcome|all`, `--top` |
|
|
78
|
+
| `all-markets` | Browse every venue type | `--type perp|hip3|spot|outcome|all`, `--top`, `--json` |
|
|
76
79
|
| `search` | Find markets across providers | `--query`, `--type` |
|
|
77
80
|
| `spot` | Spot markets or balances | `--coin`, `--balances`, `--address`, `--top` |
|
|
78
81
|
| `fills` | Recent fills | `--coin`, `--side buy|sell`, `--top`, `--address` |
|
|
@@ -84,7 +87,7 @@ Most info commands accept `--json`. Use `--coin`, `--top`, and `--address` where
|
|
|
84
87
|
| `trades` | Recent tape | `--coin`, `--top` |
|
|
85
88
|
| `rate-limit` | API usage | — |
|
|
86
89
|
| `funding-scan` | Cross-dex scan | `--threshold`, `--main-only`, `--hip3-only`, `--pairs`, `--watch`, `--interval`, `--top` |
|
|
87
|
-
| `outcomes` | HIP-4 discovery/balances | `--query`, `--outcome`, `--side`, `--balances`, `--top` |
|
|
90
|
+
| `outcomes` | HIP-4 discovery/balances | `--query`, `--outcome`, `--side`, `--balances`, `--top`, `--json` |
|
|
88
91
|
|
|
89
92
|
### Perp trading
|
|
90
93
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface AutomationAuditSink {
|
|
2
|
+
readonly runId: string;
|
|
3
|
+
readonly dbPath: string;
|
|
4
|
+
recordLog(level: 'info' | 'warn' | 'error' | 'debug', message: string, timestamp?: number): void;
|
|
5
|
+
recordEvent(eventType: string, source: 'poll' | 'ws' | 'manual', payload: unknown, timestamp?: number): void;
|
|
6
|
+
recordAction(args: {
|
|
7
|
+
actionId?: string;
|
|
8
|
+
phase: 'request' | 'response' | 'error';
|
|
9
|
+
method: string;
|
|
10
|
+
payload?: unknown;
|
|
11
|
+
result?: unknown;
|
|
12
|
+
error?: unknown;
|
|
13
|
+
dryRun?: boolean;
|
|
14
|
+
timestamp?: number;
|
|
15
|
+
}): void;
|
|
16
|
+
recordSnapshot(snapshot: {
|
|
17
|
+
pollCount: number;
|
|
18
|
+
equity: number;
|
|
19
|
+
marginUsed: number;
|
|
20
|
+
marginUsedPct: number;
|
|
21
|
+
positions: unknown[];
|
|
22
|
+
timestamp?: number;
|
|
23
|
+
}): void;
|
|
24
|
+
recordOrderUpdate(payload: unknown, timestamp?: number): void;
|
|
25
|
+
recordFill(payload: unknown, timestamp?: number): void;
|
|
26
|
+
recordUserEvent(payload: unknown, timestamp?: number): void;
|
|
27
|
+
recordStateChange(op: 'set' | 'delete' | 'clear', key: string | null, value?: unknown, timestamp?: number): void;
|
|
28
|
+
recordPublish(message: string, options: unknown, delivered: boolean, timestamp?: number): void;
|
|
29
|
+
recordError(stage: string, error: unknown, timestamp?: number): void;
|
|
30
|
+
recordNote(kind: string, payload?: unknown, timestamp?: number): void;
|
|
31
|
+
recordMetric(name: string, value: number, tags?: Record<string, unknown>, timestamp?: number): void;
|
|
32
|
+
stop(args: {
|
|
33
|
+
status: 'stopped' | 'error';
|
|
34
|
+
stopReason: string;
|
|
35
|
+
pollCount: number;
|
|
36
|
+
eventsEmitted: number;
|
|
37
|
+
timestamp?: number;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export interface AuditStartOptions {
|
|
41
|
+
automationId: string;
|
|
42
|
+
scriptPath: string;
|
|
43
|
+
dryRun: boolean;
|
|
44
|
+
verbose: boolean;
|
|
45
|
+
pollIntervalMs: number;
|
|
46
|
+
useWebSocket: boolean;
|
|
47
|
+
accountAddress: string;
|
|
48
|
+
walletAddress: string;
|
|
49
|
+
isApiWallet: boolean;
|
|
50
|
+
initialState?: Record<string, unknown>;
|
|
51
|
+
persistedState?: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
export declare const AUDIT_DB_PATH: string;
|
|
54
|
+
export declare const AUDIT_SOCKET_PATH: string;
|
|
55
|
+
export declare function toSerializable<T = unknown>(value: T): T;
|
|
56
|
+
export declare function createAutomationAudit(options: AuditStartOptions): AutomationAuditSink;
|
|
57
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../scripts/auto/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { once } from 'events';
|
|
4
|
+
import net from 'net';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
import { setTimeout as delay } from 'timers/promises';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { ensureConfigDir } from '../core/config.js';
|
|
10
|
+
export const AUDIT_DB_PATH = process.env.OPENBROKER_AUDIT_DB_PATH
|
|
11
|
+
|| path.join(ensureConfigDir(), 'automation-audit.sqlite');
|
|
12
|
+
export const AUDIT_SOCKET_PATH = process.env.OPENBROKER_AUDIT_SOCKET_PATH
|
|
13
|
+
|| (process.platform === 'win32'
|
|
14
|
+
? '\\\\.\\pipe\\openbroker-automation-audit-v2'
|
|
15
|
+
: path.join(ensureConfigDir(), 'automation-audit.v2.sock'));
|
|
16
|
+
function internalWarn(automationId, message) {
|
|
17
|
+
console.error(`[auto:${automationId}:audit] ${message}`);
|
|
18
|
+
}
|
|
19
|
+
export function toSerializable(value) {
|
|
20
|
+
const seen = new WeakSet();
|
|
21
|
+
const encoded = JSON.stringify(value, (_key, currentValue) => {
|
|
22
|
+
if (typeof currentValue === 'bigint') {
|
|
23
|
+
return currentValue.toString();
|
|
24
|
+
}
|
|
25
|
+
if (currentValue instanceof Error) {
|
|
26
|
+
return {
|
|
27
|
+
name: currentValue.name,
|
|
28
|
+
message: currentValue.message,
|
|
29
|
+
stack: currentValue.stack,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (currentValue instanceof Map) {
|
|
33
|
+
return Object.fromEntries(currentValue.entries());
|
|
34
|
+
}
|
|
35
|
+
if (currentValue instanceof Set) {
|
|
36
|
+
return [...currentValue.values()];
|
|
37
|
+
}
|
|
38
|
+
if (typeof currentValue === 'object' && currentValue !== null) {
|
|
39
|
+
if (seen.has(currentValue)) {
|
|
40
|
+
return '[Circular]';
|
|
41
|
+
}
|
|
42
|
+
seen.add(currentValue);
|
|
43
|
+
}
|
|
44
|
+
return currentValue;
|
|
45
|
+
});
|
|
46
|
+
if (encoded === undefined) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return JSON.parse(encoded);
|
|
50
|
+
}
|
|
51
|
+
class NoopAuditSink {
|
|
52
|
+
runId = randomUUID();
|
|
53
|
+
dbPath = AUDIT_DB_PATH;
|
|
54
|
+
recordLog() { }
|
|
55
|
+
recordEvent() { }
|
|
56
|
+
recordAction() { }
|
|
57
|
+
recordSnapshot() { }
|
|
58
|
+
recordOrderUpdate() { }
|
|
59
|
+
recordFill() { }
|
|
60
|
+
recordUserEvent() { }
|
|
61
|
+
recordStateChange() { }
|
|
62
|
+
recordPublish() { }
|
|
63
|
+
recordError() { }
|
|
64
|
+
recordNote() { }
|
|
65
|
+
recordMetric() { }
|
|
66
|
+
async stop() { }
|
|
67
|
+
}
|
|
68
|
+
class DaemonAuditSink {
|
|
69
|
+
automationId;
|
|
70
|
+
runId = randomUUID();
|
|
71
|
+
dbPath = AUDIT_DB_PATH;
|
|
72
|
+
socketPath = AUDIT_SOCKET_PATH;
|
|
73
|
+
socket = null;
|
|
74
|
+
lineReader = null;
|
|
75
|
+
connectPromise = null;
|
|
76
|
+
flushPromise = null;
|
|
77
|
+
closed = false;
|
|
78
|
+
daemonSpawnedAt = 0;
|
|
79
|
+
pendingQueue = [];
|
|
80
|
+
inFlight = new Map();
|
|
81
|
+
ackWaiters = new Map();
|
|
82
|
+
constructor(automationId, options) {
|
|
83
|
+
this.automationId = automationId;
|
|
84
|
+
this.enqueue({
|
|
85
|
+
type: 'init',
|
|
86
|
+
payload: {
|
|
87
|
+
runId: this.runId,
|
|
88
|
+
automationId: options.automationId,
|
|
89
|
+
scriptPath: options.scriptPath,
|
|
90
|
+
dryRun: options.dryRun,
|
|
91
|
+
verbose: options.verbose,
|
|
92
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
93
|
+
useWebSocket: options.useWebSocket,
|
|
94
|
+
accountAddress: options.accountAddress,
|
|
95
|
+
walletAddress: options.walletAddress,
|
|
96
|
+
isApiWallet: options.isApiWallet,
|
|
97
|
+
initialState: toSerializable(options.initialState ?? {}),
|
|
98
|
+
persistedState: toSerializable(options.persistedState ?? {}),
|
|
99
|
+
pid: process.pid,
|
|
100
|
+
startedAt: Date.now(),
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
handleSocketClose() {
|
|
105
|
+
if (this.lineReader) {
|
|
106
|
+
this.lineReader.close();
|
|
107
|
+
this.lineReader = null;
|
|
108
|
+
}
|
|
109
|
+
const inflight = [...this.inFlight.values()];
|
|
110
|
+
this.inFlight.clear();
|
|
111
|
+
if (inflight.length > 0) {
|
|
112
|
+
this.pendingQueue = inflight.concat(this.pendingQueue);
|
|
113
|
+
}
|
|
114
|
+
this.socket = null;
|
|
115
|
+
if (!this.closed) {
|
|
116
|
+
void this.ensureConnected();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
handleResponse(line) {
|
|
120
|
+
let response;
|
|
121
|
+
try {
|
|
122
|
+
response = JSON.parse(line);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
internalWarn(this.automationId, `failed to parse audit daemon response: ${error instanceof Error ? error.message : String(error)}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.inFlight.delete(response.messageId);
|
|
129
|
+
const waiter = this.ackWaiters.get(response.messageId);
|
|
130
|
+
if (!waiter)
|
|
131
|
+
return;
|
|
132
|
+
this.ackWaiters.delete(response.messageId);
|
|
133
|
+
if (response.ok) {
|
|
134
|
+
waiter.resolve();
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
waiter.reject(new Error(response.error || 'audit daemon returned an error'));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async openConnection() {
|
|
141
|
+
const socket = net.createConnection(this.socketPath);
|
|
142
|
+
await new Promise((resolve, reject) => {
|
|
143
|
+
let settled = false;
|
|
144
|
+
const onConnect = () => {
|
|
145
|
+
if (settled)
|
|
146
|
+
return;
|
|
147
|
+
settled = true;
|
|
148
|
+
socket.off('error', onError);
|
|
149
|
+
resolve();
|
|
150
|
+
};
|
|
151
|
+
const onError = (error) => {
|
|
152
|
+
if (settled)
|
|
153
|
+
return;
|
|
154
|
+
settled = true;
|
|
155
|
+
socket.off('connect', onConnect);
|
|
156
|
+
socket.destroy();
|
|
157
|
+
reject(error);
|
|
158
|
+
};
|
|
159
|
+
socket.once('connect', onConnect);
|
|
160
|
+
socket.once('error', onError);
|
|
161
|
+
});
|
|
162
|
+
socket.setEncoding('utf8');
|
|
163
|
+
socket.on('close', () => this.handleSocketClose());
|
|
164
|
+
socket.on('error', (error) => {
|
|
165
|
+
if (!this.closed) {
|
|
166
|
+
internalWarn(this.automationId, `audit socket error: ${error.message}`);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
this.lineReader = readline.createInterface({
|
|
170
|
+
input: socket,
|
|
171
|
+
crlfDelay: Infinity,
|
|
172
|
+
});
|
|
173
|
+
this.lineReader.on('line', (line) => this.handleResponse(line));
|
|
174
|
+
this.socket = socket;
|
|
175
|
+
}
|
|
176
|
+
async spawnDaemon() {
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
if (now - this.daemonSpawnedAt < 1_000)
|
|
179
|
+
return;
|
|
180
|
+
this.daemonSpawnedAt = now;
|
|
181
|
+
const daemonPath = fileURLToPath(new URL('./audit-daemon.js', import.meta.url));
|
|
182
|
+
const child = spawn(process.execPath, ['--no-warnings', '--experimental-sqlite', daemonPath, this.dbPath, this.socketPath], {
|
|
183
|
+
detached: true,
|
|
184
|
+
stdio: 'ignore',
|
|
185
|
+
env: { ...process.env },
|
|
186
|
+
});
|
|
187
|
+
child.unref();
|
|
188
|
+
}
|
|
189
|
+
async ensureConnected() {
|
|
190
|
+
if (this.closed)
|
|
191
|
+
return;
|
|
192
|
+
if (this.socket && !this.socket.destroyed)
|
|
193
|
+
return;
|
|
194
|
+
if (this.connectPromise)
|
|
195
|
+
return this.connectPromise;
|
|
196
|
+
this.connectPromise = (async () => {
|
|
197
|
+
try {
|
|
198
|
+
await this.openConnection();
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
await this.spawnDaemon();
|
|
202
|
+
let lastError = null;
|
|
203
|
+
for (let attempt = 0; attempt < 30 && !this.closed; attempt++) {
|
|
204
|
+
try {
|
|
205
|
+
await delay(100 + (attempt * 50));
|
|
206
|
+
await this.openConnection();
|
|
207
|
+
lastError = null;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (lastError) {
|
|
215
|
+
throw lastError;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
await this.flushQueue();
|
|
219
|
+
})().catch((error) => {
|
|
220
|
+
internalWarn(this.automationId, `audit daemon unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
221
|
+
}).finally(() => {
|
|
222
|
+
this.connectPromise = null;
|
|
223
|
+
});
|
|
224
|
+
return this.connectPromise;
|
|
225
|
+
}
|
|
226
|
+
enqueue(message) {
|
|
227
|
+
const payload = message.type === 'init'
|
|
228
|
+
? message.payload
|
|
229
|
+
: { runId: this.runId, ...message.payload };
|
|
230
|
+
const wire = {
|
|
231
|
+
messageId: randomUUID(),
|
|
232
|
+
type: message.type,
|
|
233
|
+
payload,
|
|
234
|
+
};
|
|
235
|
+
this.pendingQueue.push(wire);
|
|
236
|
+
void this.ensureConnected();
|
|
237
|
+
if (this.socket && !this.socket.destroyed) {
|
|
238
|
+
void this.flushQueue();
|
|
239
|
+
}
|
|
240
|
+
return wire;
|
|
241
|
+
}
|
|
242
|
+
async flushQueue() {
|
|
243
|
+
if (this.flushPromise)
|
|
244
|
+
return this.flushPromise;
|
|
245
|
+
this.flushPromise = (async () => {
|
|
246
|
+
while (!this.closed && this.socket && !this.socket.destroyed && this.pendingQueue.length > 0) {
|
|
247
|
+
const message = this.pendingQueue.shift();
|
|
248
|
+
this.inFlight.set(message.messageId, message);
|
|
249
|
+
const line = `${JSON.stringify(message)}\n`;
|
|
250
|
+
const writable = this.socket.write(line);
|
|
251
|
+
if (!writable && this.socket) {
|
|
252
|
+
await once(this.socket, 'drain');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
})().finally(() => {
|
|
256
|
+
this.flushPromise = null;
|
|
257
|
+
});
|
|
258
|
+
return this.flushPromise;
|
|
259
|
+
}
|
|
260
|
+
send(message, waitForAck = false) {
|
|
261
|
+
if (this.closed)
|
|
262
|
+
return Promise.resolve();
|
|
263
|
+
const wire = this.enqueue(message);
|
|
264
|
+
if (!waitForAck)
|
|
265
|
+
return Promise.resolve();
|
|
266
|
+
return new Promise((resolve, reject) => {
|
|
267
|
+
this.ackWaiters.set(wire.messageId, { resolve, reject });
|
|
268
|
+
void this.flushQueue();
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
recordLog(level, message, timestamp = Date.now()) {
|
|
272
|
+
void this.send({ type: 'log', payload: { timestamp, level, message } });
|
|
273
|
+
}
|
|
274
|
+
recordEvent(eventType, source, payload, timestamp = Date.now()) {
|
|
275
|
+
void this.send({ type: 'event', payload: { timestamp, eventType, source, payload: toSerializable(payload) } });
|
|
276
|
+
}
|
|
277
|
+
recordAction(args) {
|
|
278
|
+
void this.send({
|
|
279
|
+
type: 'action',
|
|
280
|
+
payload: {
|
|
281
|
+
timestamp: args.timestamp ?? Date.now(),
|
|
282
|
+
actionId: args.actionId ?? randomUUID(),
|
|
283
|
+
phase: args.phase,
|
|
284
|
+
method: args.method,
|
|
285
|
+
payload: toSerializable(args.payload),
|
|
286
|
+
result: toSerializable(args.result),
|
|
287
|
+
error: toSerializable(args.error),
|
|
288
|
+
dryRun: args.dryRun ?? false,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
recordSnapshot(snapshot) {
|
|
293
|
+
void this.send({
|
|
294
|
+
type: 'snapshot',
|
|
295
|
+
payload: {
|
|
296
|
+
timestamp: snapshot.timestamp ?? Date.now(),
|
|
297
|
+
pollCount: snapshot.pollCount,
|
|
298
|
+
equity: snapshot.equity,
|
|
299
|
+
marginUsed: snapshot.marginUsed,
|
|
300
|
+
marginUsedPct: snapshot.marginUsedPct,
|
|
301
|
+
positions: toSerializable(snapshot.positions),
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
recordOrderUpdate(payload, timestamp = Date.now()) {
|
|
306
|
+
void this.send({ type: 'order_update', payload: { timestamp, payload: toSerializable(payload) } });
|
|
307
|
+
}
|
|
308
|
+
recordFill(payload, timestamp = Date.now()) {
|
|
309
|
+
void this.send({ type: 'fill', payload: { timestamp, payload: toSerializable(payload) } });
|
|
310
|
+
}
|
|
311
|
+
recordUserEvent(payload, timestamp = Date.now()) {
|
|
312
|
+
void this.send({ type: 'user_event', payload: { timestamp, payload: toSerializable(payload) } });
|
|
313
|
+
}
|
|
314
|
+
recordStateChange(op, key, value, timestamp = Date.now()) {
|
|
315
|
+
void this.send({
|
|
316
|
+
type: 'state_change',
|
|
317
|
+
payload: {
|
|
318
|
+
timestamp,
|
|
319
|
+
op,
|
|
320
|
+
key,
|
|
321
|
+
value: toSerializable(value),
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
recordPublish(message, options, delivered, timestamp = Date.now()) {
|
|
326
|
+
void this.send({
|
|
327
|
+
type: 'publish',
|
|
328
|
+
payload: {
|
|
329
|
+
timestamp,
|
|
330
|
+
message,
|
|
331
|
+
options: toSerializable(options),
|
|
332
|
+
delivered,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
recordError(stage, error, timestamp = Date.now()) {
|
|
337
|
+
void this.send({
|
|
338
|
+
type: 'error',
|
|
339
|
+
payload: {
|
|
340
|
+
timestamp,
|
|
341
|
+
stage,
|
|
342
|
+
error: toSerializable(error),
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
recordNote(kind, payload, timestamp = Date.now()) {
|
|
347
|
+
void this.send({
|
|
348
|
+
type: 'note',
|
|
349
|
+
payload: {
|
|
350
|
+
timestamp,
|
|
351
|
+
kind,
|
|
352
|
+
payload: toSerializable(payload),
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
recordMetric(name, value, tags, timestamp = Date.now()) {
|
|
357
|
+
void this.send({
|
|
358
|
+
type: 'metric',
|
|
359
|
+
payload: {
|
|
360
|
+
timestamp,
|
|
361
|
+
name,
|
|
362
|
+
value,
|
|
363
|
+
tags: toSerializable(tags ?? {}),
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
async stop(args) {
|
|
368
|
+
if (this.closed)
|
|
369
|
+
return;
|
|
370
|
+
try {
|
|
371
|
+
await this.send({
|
|
372
|
+
type: 'stop',
|
|
373
|
+
payload: {
|
|
374
|
+
timestamp: args.timestamp ?? Date.now(),
|
|
375
|
+
status: args.status,
|
|
376
|
+
stopReason: args.stopReason,
|
|
377
|
+
pollCount: args.pollCount,
|
|
378
|
+
eventsEmitted: args.eventsEmitted,
|
|
379
|
+
},
|
|
380
|
+
}, true);
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
internalWarn(this.automationId, `failed to flush stop audit message: ${error instanceof Error ? error.message : String(error)}`);
|
|
384
|
+
}
|
|
385
|
+
this.closed = true;
|
|
386
|
+
if (this.lineReader) {
|
|
387
|
+
this.lineReader.close();
|
|
388
|
+
this.lineReader = null;
|
|
389
|
+
}
|
|
390
|
+
if (this.socket && !this.socket.destroyed) {
|
|
391
|
+
this.socket.end();
|
|
392
|
+
}
|
|
393
|
+
this.socket = null;
|
|
394
|
+
this.pendingQueue = [];
|
|
395
|
+
this.inFlight.clear();
|
|
396
|
+
this.ackWaiters.clear();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
export function createAutomationAudit(options) {
|
|
400
|
+
try {
|
|
401
|
+
return new DaemonAuditSink(options.automationId, options);
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
internalWarn(options.automationId, `audit disabled: ${error instanceof Error ? error.message : String(error)}`);
|
|
405
|
+
return new NoopAuditSink();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../scripts/auto/cli.ts"],"names":[],"mappings":""}
|