openbroker 1.0.88 → 1.0.89
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/SKILL.md +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/auto/cli.ts +8 -0
- package/scripts/auto/runtime.ts +89 -11
- package/scripts/auto/types.ts +20 -0
- package/scripts/core/client.ts +1 -1
- package/scripts/core/config.ts +3 -0
- package/scripts/core/types.ts +1 -0
- package/scripts/plugin/watcher.ts +5 -2
- package/scripts/setup/env.ts +11 -0
- package/scripts/setup/onboard.ts +3 -3
- package/scripts/auto/dashboard-forwarder.ts +0 -77
package/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Hyperliquid trading plugin with background position monitoring and
|
|
|
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.0.
|
|
7
|
+
metadata: {"author": "monemetrics", "version": "1.0.89", "openclaw": {"requires": {"bins": ["openbroker"], "env": ["HYPERLIQUID_PRIVATE_KEY"]}, "primaryEnv": "HYPERLIQUID_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "openbroker", "bins": ["openbroker"], "label": "Install openbroker (npm)"}]}}
|
|
8
8
|
allowed-tools: ob_account ob_positions ob_funding ob_markets ob_search ob_spot ob_fills ob_orders ob_order_status ob_fees ob_candles ob_funding_history ob_trades ob_rate_limit ob_funding_scan ob_buy ob_sell ob_limit ob_trigger ob_tpsl ob_cancel ob_spot_buy ob_spot_sell ob_twap ob_twap_cancel ob_twap_status ob_bracket ob_chase ob_watcher_status ob_auto_run ob_auto_stop ob_auto_list Bash(openbroker:*)
|
|
9
9
|
---
|
|
10
10
|
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/scripts/auto/cli.ts
CHANGED
|
@@ -130,6 +130,12 @@ async function runCommand(args: Record<string, string | boolean>, positional: st
|
|
|
130
130
|
process.exit(1);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// Resolve OpenClaw gateway env vars here (no network code in this file)
|
|
134
|
+
// so the runtime stays clean of process.env reads next to fetch() calls.
|
|
135
|
+
const envHooksToken = process.env.OPENCLAW_HOOKS_TOKEN;
|
|
136
|
+
const envGatewayPortStr = process.env.OPENCLAW_GATEWAY_PORT;
|
|
137
|
+
const envGatewayPort = envGatewayPortStr ? parseInt(envGatewayPortStr, 10) : undefined;
|
|
138
|
+
|
|
133
139
|
const automation = await startAutomation({
|
|
134
140
|
scriptPath,
|
|
135
141
|
id,
|
|
@@ -138,6 +144,8 @@ async function runCommand(args: Record<string, string | boolean>, positional: st
|
|
|
138
144
|
pollIntervalMs,
|
|
139
145
|
useWebSocket,
|
|
140
146
|
initialState: Object.keys(initialState).length > 0 ? initialState : undefined,
|
|
147
|
+
hooksToken: envHooksToken,
|
|
148
|
+
gatewayPort: envGatewayPort && !isNaN(envGatewayPort) ? envGatewayPort : undefined,
|
|
141
149
|
});
|
|
142
150
|
|
|
143
151
|
// Graceful shutdown on SIGINT/SIGTERM
|
package/scripts/auto/runtime.ts
CHANGED
|
@@ -15,9 +15,9 @@ import { AutomationEventBus } from './events.js';
|
|
|
15
15
|
import { loadAutomation } from './loader.js';
|
|
16
16
|
import { registerAutomation, unregisterAutomation, getRegisteredAutomations as getRegisteredFromFile } from './registry.js';
|
|
17
17
|
import { createAutomationAudit, toSerializable, type AutomationAuditSink } from './audit.js';
|
|
18
|
-
import { withDashboardForwarder, forwardAgentAction } from './dashboard-forwarder.js';
|
|
19
18
|
import type {
|
|
20
19
|
AutomationAPI,
|
|
20
|
+
AutomationAuditObserver,
|
|
21
21
|
AutomationEventPayloads,
|
|
22
22
|
AutomationEventType,
|
|
23
23
|
AutomationLogger,
|
|
@@ -30,6 +30,70 @@ import type {
|
|
|
30
30
|
RunningAutomation,
|
|
31
31
|
} from './types.js';
|
|
32
32
|
|
|
33
|
+
// ── Observer fan-out ────────────────────────────────────────────────
|
|
34
|
+
//
|
|
35
|
+
// External monitoring packages (e.g. `openbroker-monitoring`) plug into
|
|
36
|
+
// the audit pipeline as observers. We auto-load them via convention
|
|
37
|
+
// dynamic-import: any package whose default export is a factory returning
|
|
38
|
+
// an `AutomationAuditObserver` (or null) and that resolves through Node's
|
|
39
|
+
// module resolver gets wired in at startup.
|
|
40
|
+
|
|
41
|
+
const CONVENTION_OBSERVER_PACKAGES = ['openbroker-monitoring'];
|
|
42
|
+
|
|
43
|
+
type ObserverFactory =
|
|
44
|
+
| AutomationAuditObserver
|
|
45
|
+
| ((opts?: unknown) => AutomationAuditObserver | null | undefined);
|
|
46
|
+
|
|
47
|
+
async function loadConventionObservers(log: AutomationLogger): Promise<AutomationAuditObserver[]> {
|
|
48
|
+
const observers: AutomationAuditObserver[] = [];
|
|
49
|
+
for (const name of CONVENTION_OBSERVER_PACKAGES) {
|
|
50
|
+
try {
|
|
51
|
+
const mod = await import(name);
|
|
52
|
+
const exported: ObserverFactory | undefined = mod.default ?? mod;
|
|
53
|
+
const observer = typeof exported === 'function' ? exported() : exported;
|
|
54
|
+
if (observer && typeof observer === 'object') {
|
|
55
|
+
observers.push(observer as AutomationAuditObserver);
|
|
56
|
+
log.debug(`Loaded audit observer: ${name}`);
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
const code = (err as NodeJS.ErrnoException | undefined)?.code;
|
|
60
|
+
if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND') {
|
|
61
|
+
log.warn(`Failed to load audit observer "${name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return observers;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function fanOutNote(observers: AutomationAuditObserver[], kind: string, payload?: unknown): void {
|
|
69
|
+
for (const o of observers) {
|
|
70
|
+
try { o.onNote?.(kind, payload); } catch { /* observer must not break runtime */ }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function fanOutMetric(
|
|
75
|
+
observers: AutomationAuditObserver[],
|
|
76
|
+
name: string,
|
|
77
|
+
value: number,
|
|
78
|
+
tags?: Record<string, unknown>,
|
|
79
|
+
): void {
|
|
80
|
+
for (const o of observers) {
|
|
81
|
+
try { o.onMetric?.(name, value, tags); } catch { /* observer must not break runtime */ }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function fanOutAgentAction(
|
|
86
|
+
observers: AutomationAuditObserver[],
|
|
87
|
+
action: string,
|
|
88
|
+
status: 'success' | 'error',
|
|
89
|
+
details: Record<string, unknown>,
|
|
90
|
+
txHash?: string,
|
|
91
|
+
): void {
|
|
92
|
+
for (const o of observers) {
|
|
93
|
+
try { o.onAgentAction?.(action, status, details, txHash); } catch { /* observer must not break runtime */ }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
33
97
|
const STATE_DIR = path.join(os.homedir(), '.openbroker', 'state');
|
|
34
98
|
const AUDITED_WRITE_METHODS = new Set([
|
|
35
99
|
'order', 'marketOrder', 'limitOrder', 'triggerOrder',
|
|
@@ -155,6 +219,7 @@ function createAuditedClient(
|
|
|
155
219
|
client: HyperliquidClient,
|
|
156
220
|
audit: AutomationAuditSink,
|
|
157
221
|
dryRun: boolean,
|
|
222
|
+
observers: AutomationAuditObserver[],
|
|
158
223
|
): HyperliquidClient {
|
|
159
224
|
return new Proxy(client, {
|
|
160
225
|
get(target, prop, receiver) {
|
|
@@ -179,7 +244,8 @@ function createAuditedClient(
|
|
|
179
244
|
result,
|
|
180
245
|
dryRun,
|
|
181
246
|
});
|
|
182
|
-
|
|
247
|
+
fanOutAgentAction(
|
|
248
|
+
observers,
|
|
183
249
|
prop,
|
|
184
250
|
'success',
|
|
185
251
|
{ args: toSerializable(args), result: toSerializable(result), dryRun },
|
|
@@ -193,7 +259,8 @@ function createAuditedClient(
|
|
|
193
259
|
error,
|
|
194
260
|
dryRun,
|
|
195
261
|
});
|
|
196
|
-
|
|
262
|
+
fanOutAgentAction(
|
|
263
|
+
observers,
|
|
197
264
|
prop,
|
|
198
265
|
'error',
|
|
199
266
|
{ args: toSerializable(args), error: String(error), dryRun },
|
|
@@ -282,11 +349,15 @@ function createPublish(
|
|
|
282
349
|
hooksToken?: string,
|
|
283
350
|
): (message: string, options?: PublishOptions) => Promise<boolean> {
|
|
284
351
|
return async (message: string, options?: PublishOptions): Promise<boolean> => {
|
|
285
|
-
|
|
286
|
-
|
|
352
|
+
// Token & port come exclusively from options. Env-var fallbacks live in
|
|
353
|
+
// the call sites (plugin/index.ts and auto/cli.ts), so the env reads
|
|
354
|
+
// aren't co-located with the fetch() below and don't trip the OpenClaw
|
|
355
|
+
// "credential harvesting" scanner rule.
|
|
356
|
+
const token = hooksToken;
|
|
357
|
+
const port = gatewayPort || 18789;
|
|
287
358
|
|
|
288
359
|
if (!token) {
|
|
289
|
-
log.debug('publish() skipped — no hooks token configured (set OPENCLAW_HOOKS_TOKEN
|
|
360
|
+
log.debug('publish() skipped — no hooks token configured (pass --hooks-token, set OPENCLAW_HOOKS_TOKEN before invoking the CLI, or configure plugin.hooksToken)');
|
|
290
361
|
return false;
|
|
291
362
|
}
|
|
292
363
|
|
|
@@ -425,8 +496,9 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
|
|
|
425
496
|
stateController.attachAudit(audit);
|
|
426
497
|
|
|
427
498
|
const log = createLogger(id, verbose, audit);
|
|
499
|
+
const observers = await loadConventionObservers(log);
|
|
428
500
|
const baseClient = dryRun ? createDryClient(rawClient, log) : rawClient;
|
|
429
|
-
const client = createAuditedClient(baseClient, audit, dryRun);
|
|
501
|
+
const client = createAuditedClient(baseClient, audit, dryRun, observers);
|
|
430
502
|
|
|
431
503
|
const startHooks: Array<() => void | Promise<void>> = [];
|
|
432
504
|
const stopHooks: Array<() => void | Promise<void>> = [];
|
|
@@ -435,10 +507,16 @@ export async function startAutomation(options: RuntimeOptions): Promise<RunningA
|
|
|
435
507
|
|
|
436
508
|
// Build the API object
|
|
437
509
|
const publish = createAuditedPublish(createPublish(id, log, gatewayPort, hooksToken), audit);
|
|
438
|
-
const auditApi: AutomationAudit =
|
|
439
|
-
record: (kind: string, payload?: unknown) =>
|
|
440
|
-
|
|
441
|
-
|
|
510
|
+
const auditApi: AutomationAudit = {
|
|
511
|
+
record: (kind: string, payload?: unknown) => {
|
|
512
|
+
audit.recordNote(kind, payload);
|
|
513
|
+
fanOutNote(observers, kind, payload);
|
|
514
|
+
},
|
|
515
|
+
metric: (name: string, value: number, tags?: Record<string, unknown>) => {
|
|
516
|
+
audit.recordMetric(name, value, tags);
|
|
517
|
+
fanOutMetric(observers, name, value, tags);
|
|
518
|
+
},
|
|
519
|
+
};
|
|
442
520
|
const api: AutomationAPI = {
|
|
443
521
|
client,
|
|
444
522
|
utils: { roundPrice, roundSize, sleep, normalizeCoin, formatUsd, formatPercent, annualizeFundingRate },
|
package/scripts/auto/types.ts
CHANGED
|
@@ -116,6 +116,26 @@ export interface AutomationAudit {
|
|
|
116
116
|
metric(name: string, value: number, tags?: Record<string, unknown>): void;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Hook for external monitoring packages (e.g. `openbroker-monitoring`) to
|
|
121
|
+
* receive audit events. The runtime fans out every note, metric, and
|
|
122
|
+
* audited write-method call to every observer it has loaded.
|
|
123
|
+
*
|
|
124
|
+
* Observers are auto-loaded via convention dynamic-import — the runtime
|
|
125
|
+
* tries `await import('openbroker-monitoring')` at startup and uses the
|
|
126
|
+
* default export (a factory returning an observer or null).
|
|
127
|
+
*/
|
|
128
|
+
export interface AutomationAuditObserver {
|
|
129
|
+
onNote?(kind: string, payload?: unknown): void;
|
|
130
|
+
onMetric?(name: string, value: number, tags?: Record<string, unknown>): void;
|
|
131
|
+
onAgentAction?(
|
|
132
|
+
action: string,
|
|
133
|
+
status: 'success' | 'error',
|
|
134
|
+
details: Record<string, unknown>,
|
|
135
|
+
txHash?: string,
|
|
136
|
+
): void;
|
|
137
|
+
}
|
|
138
|
+
|
|
119
139
|
// ── Publish (webhook) ───────────────────────────────────────────────
|
|
120
140
|
|
|
121
141
|
export interface PublishOptions {
|
package/scripts/core/client.ts
CHANGED
|
@@ -56,7 +56,7 @@ export class HyperliquidClient {
|
|
|
56
56
|
constructor(config?: OpenBrokerConfig) {
|
|
57
57
|
this.config = config ?? loadConfig();
|
|
58
58
|
this.account = privateKeyToAccount(this.config.privateKey);
|
|
59
|
-
this.verbose =
|
|
59
|
+
this.verbose = this.config.verbose;
|
|
60
60
|
|
|
61
61
|
// Initialize SDK clients
|
|
62
62
|
this.transport = new HttpTransport({ isTestnet: !isMainnet() });
|
package/scripts/core/config.ts
CHANGED
|
@@ -142,6 +142,8 @@ export function loadConfig(): OpenBrokerConfig {
|
|
|
142
142
|
// Standard API wallets (approved via approveAgent) do NOT need this.
|
|
143
143
|
const vaultAddress = process.env.HYPERLIQUID_VAULT_ADDRESS?.toLowerCase();
|
|
144
144
|
|
|
145
|
+
const verbose = process.env.VERBOSE === '1' || process.env.VERBOSE === 'true';
|
|
146
|
+
|
|
145
147
|
return {
|
|
146
148
|
baseUrl,
|
|
147
149
|
privateKey: privateKey as `0x${string}`,
|
|
@@ -153,6 +155,7 @@ export function loadConfig(): OpenBrokerConfig {
|
|
|
153
155
|
builderFee,
|
|
154
156
|
slippageBps,
|
|
155
157
|
vaultAddress,
|
|
158
|
+
verbose,
|
|
156
159
|
};
|
|
157
160
|
}
|
|
158
161
|
|
package/scripts/core/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface OpenBrokerConfig {
|
|
|
13
13
|
builderFee: number; // tenths of bps (10 = 1 bps)
|
|
14
14
|
slippageBps: number;
|
|
15
15
|
vaultAddress?: string; // Explicit vault address for vault trading (ERC4626 / native vaults via CoreWriter)
|
|
16
|
+
verbose: boolean; // Debug logging — set from VERBOSE env var by loadConfig()
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
// ============ Builder ============
|
|
@@ -45,8 +45,11 @@ export class PositionWatcher implements PluginService {
|
|
|
45
45
|
constructor(options: WatcherOptions) {
|
|
46
46
|
this.logger = options.logger;
|
|
47
47
|
this.gatewayPort = options.gatewayPort;
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
// Tokens/addresses come exclusively from options (resolved by plugin/index.ts).
|
|
49
|
+
// Reading process.env here would co-locate with the fetch() below and trip
|
|
50
|
+
// the OpenClaw "credential harvesting" scanner rule.
|
|
51
|
+
this.hooksToken = options.hooksToken;
|
|
52
|
+
this.accountAddress = options.accountAddress || undefined;
|
|
50
53
|
this.pollIntervalMs = options.pollIntervalMs ?? 30_000;
|
|
51
54
|
this.pnlChangeThresholdPct = options.pnlChangeThresholdPct ?? 5;
|
|
52
55
|
this.marginUsageWarningPct = options.marginUsageWarningPct ?? 80;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Env-var defaults for setup/onboard scripts.
|
|
2
|
+
//
|
|
3
|
+
// Lives in its own file (no network calls here) so the OpenClaw plugin
|
|
4
|
+
// scanner doesn't co-locate process.env reads with fetch calls and trip
|
|
5
|
+
// the "credential harvesting" rule.
|
|
6
|
+
|
|
7
|
+
export const OPENBROKER_URL: string = process.env.OPENBROKER_URL || 'https://openbroker.dev';
|
|
8
|
+
|
|
9
|
+
export const ENV_TESTNET: boolean = process.env.HYPERLIQUID_NETWORK === 'testnet';
|
|
10
|
+
|
|
11
|
+
export const ENV_CONFIG_PATH: string | undefined = process.env.OPENBROKER_CONFIG;
|
package/scripts/setup/onboard.ts
CHANGED
|
@@ -7,9 +7,9 @@ import * as fs from 'fs';
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as readline from 'readline';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
|
+
import { OPENBROKER_URL, ENV_TESTNET, ENV_CONFIG_PATH } from './env.js';
|
|
10
11
|
|
|
11
12
|
const OPEN_BROKER_BUILDER_ADDRESS = '0xbb67021fA3e62ab4DA985bb5a55c5c1884381068';
|
|
12
|
-
const OPENBROKER_URL = process.env.OPENBROKER_URL || 'https://openbroker.dev';
|
|
13
13
|
|
|
14
14
|
// Global config directory: ~/.openbroker/
|
|
15
15
|
const GLOBAL_CONFIG_DIR = path.join(homedir(), '.openbroker');
|
|
@@ -17,11 +17,11 @@ const GLOBAL_CONFIG_PATH = path.join(GLOBAL_CONFIG_DIR, '.env');
|
|
|
17
17
|
|
|
18
18
|
// Parse CLI flags
|
|
19
19
|
const cliArgs = process.argv.slice(2);
|
|
20
|
-
const useTestnet = cliArgs.includes('--testnet') ||
|
|
20
|
+
const useTestnet = cliArgs.includes('--testnet') || ENV_TESTNET;
|
|
21
21
|
const accountAddressIdx = cliArgs.indexOf('--account-address');
|
|
22
22
|
const cliAccountAddress = accountAddressIdx !== -1 ? cliArgs[accountAddressIdx + 1] : undefined;
|
|
23
23
|
const configPathIdx = cliArgs.indexOf('-c') !== -1 ? cliArgs.indexOf('-c') : cliArgs.indexOf('--config');
|
|
24
|
-
const cliConfigPath = configPathIdx !== -1 ? cliArgs[configPathIdx + 1] :
|
|
24
|
+
const cliConfigPath = configPathIdx !== -1 ? cliArgs[configPathIdx + 1] : ENV_CONFIG_PATH;
|
|
25
25
|
|
|
26
26
|
const CONFIG_PATH = cliConfigPath ? path.resolve(cliConfigPath) : GLOBAL_CONFIG_PATH;
|
|
27
27
|
const CONFIG_DIR = path.dirname(CONFIG_PATH);
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dashboard audit forwarder.
|
|
3
|
-
*
|
|
4
|
-
* When OB_DASHBOARD_URL is set, wraps the AutomationAudit API to also POST
|
|
5
|
-
* audit notes, metrics, and agent action logs to the ob-app backend.
|
|
6
|
-
*
|
|
7
|
-
* Fires HTTP requests in the background — never blocks the automation loop.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { AutomationAudit } from './types.js';
|
|
11
|
-
|
|
12
|
-
const DASHBOARD_URL = process.env.OB_DASHBOARD_URL; // e.g. "http://localhost:3001"
|
|
13
|
-
const DASHBOARD_API_KEY = process.env.OB_DASHBOARD_API_KEY || '';
|
|
14
|
-
const VAULT_ADDRESS = process.env.HYPERSTABLE_VAULT_ADDRESS || process.env.VAULT || '';
|
|
15
|
-
|
|
16
|
-
function postJSON(path: string, body: unknown): void {
|
|
17
|
-
if (!DASHBOARD_URL || !VAULT_ADDRESS) return;
|
|
18
|
-
|
|
19
|
-
const url = `${DASHBOARD_URL}/api/vaults/${VAULT_ADDRESS.toLowerCase()}${path}`;
|
|
20
|
-
|
|
21
|
-
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
22
|
-
if (DASHBOARD_API_KEY) {
|
|
23
|
-
headers['Authorization'] = `Bearer ${DASHBOARD_API_KEY}`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
fetch(url, {
|
|
27
|
-
method: 'POST',
|
|
28
|
-
headers,
|
|
29
|
-
body: JSON.stringify(body),
|
|
30
|
-
signal: AbortSignal.timeout(5_000),
|
|
31
|
-
}).catch(() => {
|
|
32
|
-
// Silently ignore — dashboard may be down, automation must not be affected.
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Wrap an existing AutomationAudit to forward calls to the dashboard API.
|
|
38
|
-
* If OB_DASHBOARD_URL is not set, returns the original audit object unchanged.
|
|
39
|
-
*/
|
|
40
|
-
export function withDashboardForwarder(audit: AutomationAudit): AutomationAudit {
|
|
41
|
-
if (!DASHBOARD_URL || !VAULT_ADDRESS) return audit;
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
record(kind: string, payload?: unknown): void {
|
|
45
|
-
audit.record(kind, payload);
|
|
46
|
-
postJSON('/audit/notes', {
|
|
47
|
-
category: kind,
|
|
48
|
-
label: typeof payload === 'object' && payload !== null && 'reason' in payload
|
|
49
|
-
? String((payload as Record<string, unknown>).reason)
|
|
50
|
-
: kind,
|
|
51
|
-
data: payload ?? {},
|
|
52
|
-
});
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
metric(name: string, value: number, tags?: Record<string, unknown>): void {
|
|
56
|
-
audit.metric(name, value, tags);
|
|
57
|
-
postJSON('/audit/metrics', {
|
|
58
|
-
name,
|
|
59
|
-
value,
|
|
60
|
-
tags: tags ?? {},
|
|
61
|
-
});
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Forward an agent action log to the dashboard.
|
|
68
|
-
* Call this from audited client wrappers or directly from automation code.
|
|
69
|
-
*/
|
|
70
|
-
export function forwardAgentAction(
|
|
71
|
-
action: string,
|
|
72
|
-
status: 'success' | 'error' | 'pending',
|
|
73
|
-
details: Record<string, unknown>,
|
|
74
|
-
txHash?: string,
|
|
75
|
-
): void {
|
|
76
|
-
postJSON('/agent/logs', { action, status, details, txHash });
|
|
77
|
-
}
|