@aiaiaichain/agent 0.1.3 → 0.1.5
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/README.md +2 -2
- package/dist/cli.js +274 -26
- package/dist/core/ChainConfig.js +1 -1
- package/dist/core/SystemMonitor.d.ts +32 -0
- package/dist/core/SystemMonitor.js +89 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +18 -1
- package/dist/models/ModelRegistry.js +12 -4
- package/dist/runner/AgentRunner.d.ts +2 -0
- package/dist/runner/AgentRunner.js +18 -1
- package/dist/runner/ModelClient.js +109 -48
- package/dist/session/SessionManager.d.ts +1 -0
- package/dist/session/SessionManager.js +8 -2
- package/dist/session/SessionStore.d.ts +45 -0
- package/dist/session/SessionStore.js +128 -0
- package/dist/tools/CrossTools.d.ts +52 -0
- package/dist/tools/CrossTools.js +190 -0
- package/dist/tools/MarketSentiment.js +22 -13
- package/dist/tools/NewsSentiment.js +9 -3
- package/dist/tools/PriceFeed.js +11 -4
- package/dist/tools/TechnicalAnalysis.js +2 -1
- package/dist/tools/TokenCalendar.d.ts +24 -0
- package/dist/tools/TokenCalendar.js +81 -0
- package/dist/tools/TokenSecurityScanner.d.ts +22 -0
- package/dist/tools/TokenSecurityScanner.js +102 -0
- package/dist/tools/TransactionSim.d.ts +17 -0
- package/dist/tools/TransactionSim.js +78 -0
- package/dist/tui/App.d.ts +4 -3
- package/dist/tui/App.js +371 -118
- package/dist/tui/REPL.d.ts +2 -1
- package/dist/tui/REPL.js +190 -16
- package/dist/tui/Sparkline.d.ts +21 -0
- package/dist/tui/Sparkline.js +44 -0
- package/dist/tui/StatusBar.d.ts +5 -1
- package/dist/tui/StatusBar.js +6 -4
- package/dist/tui/ThemePresets.d.ts +25 -0
- package/dist/tui/ThemePresets.js +117 -0
- package/dist/util/clipboard.d.ts +9 -0
- package/dist/util/clipboard.js +26 -0
- package/dist/util/commandSuggest.d.ts +7 -0
- package/dist/util/commandSuggest.js +44 -0
- package/dist/util/confirmation.d.ts +6 -0
- package/dist/util/confirmation.js +16 -0
- package/dist/util/errorHandler.d.ts +3 -0
- package/dist/util/errorHandler.js +28 -0
- package/dist/util/logger.d.ts +11 -0
- package/dist/util/logger.js +43 -0
- package/dist/util/processManager.d.ts +5 -0
- package/dist/util/processManager.js +39 -0
- package/dist/util/resilientFetch.d.ts +21 -0
- package/dist/util/resilientFetch.js +94 -0
- package/dist/util/responseCache.d.ts +27 -0
- package/dist/util/responseCache.js +54 -0
- package/dist/util/safeLog.d.ts +4 -5
- package/dist/util/safeLog.js +68 -30
- package/dist/util/scheduler.d.ts +14 -0
- package/dist/util/scheduler.js +75 -0
- package/dist/util/webhooks.d.ts +9 -0
- package/dist/util/webhooks.js +75 -0
- package/dist/wallet/ActionFeed.js +12 -4
- package/dist/wallet/AgentWallet.d.ts +2 -0
- package/dist/wallet/AgentWallet.js +31 -5
- package/dist/wallet/ProfitTracker.d.ts +30 -0
- package/dist/wallet/ProfitTracker.js +93 -0
- package/docs/COMMANDS.md +40 -9
- package/docs/README.md +2 -0
- package/package.json +5 -3
- package/scripts/postinstall.js +34 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/util/logger.ts — Structured logging with levels for AIAIAI Agent
|
|
3
|
+
*/
|
|
4
|
+
const LEVEL_PRIORITY = {
|
|
5
|
+
debug: 0,
|
|
6
|
+
info: 1,
|
|
7
|
+
warn: 2,
|
|
8
|
+
error: 3,
|
|
9
|
+
};
|
|
10
|
+
function getMinLevel() {
|
|
11
|
+
const env = process.env.AIAIAI_LOG_LEVEL?.toLowerCase();
|
|
12
|
+
if (env && env in LEVEL_PRIORITY)
|
|
13
|
+
return env;
|
|
14
|
+
return 'info';
|
|
15
|
+
}
|
|
16
|
+
function shouldLog(level) {
|
|
17
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[getMinLevel()];
|
|
18
|
+
}
|
|
19
|
+
function formatMessage(level, context, message, meta) {
|
|
20
|
+
const ts = new Date().toISOString();
|
|
21
|
+
const prefix = `[${ts}] [${level.toUpperCase()}] [${context}]`;
|
|
22
|
+
const metaStr = meta ? ` ${JSON.stringify(meta)}` : '';
|
|
23
|
+
return `${prefix} ${message}${metaStr}`;
|
|
24
|
+
}
|
|
25
|
+
export const logger = {
|
|
26
|
+
debug(context, message, meta) {
|
|
27
|
+
if (shouldLog('debug'))
|
|
28
|
+
console.error(formatMessage('debug', context, message, meta));
|
|
29
|
+
},
|
|
30
|
+
info(context, message, meta) {
|
|
31
|
+
if (shouldLog('info'))
|
|
32
|
+
console.error(formatMessage('info', context, message, meta));
|
|
33
|
+
},
|
|
34
|
+
warn(context, message, meta) {
|
|
35
|
+
if (shouldLog('warn'))
|
|
36
|
+
console.error(formatMessage('warn', context, message, meta));
|
|
37
|
+
},
|
|
38
|
+
error(context, message, meta) {
|
|
39
|
+
if (shouldLog('error'))
|
|
40
|
+
console.error(formatMessage('error', context, message, meta));
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process management utilities for AIAIAI Agent
|
|
3
|
+
*/
|
|
4
|
+
import { safeLog } from "./safeLog.js";
|
|
5
|
+
export class ProcessManager {
|
|
6
|
+
static setupGlobalHandlers() {
|
|
7
|
+
// Handle uncaught exceptions
|
|
8
|
+
process.on('uncaughtException', (error) => {
|
|
9
|
+
safeLog('[UNCAUGHT_EXCEPTION] ' + error.message);
|
|
10
|
+
if (error.stack) {
|
|
11
|
+
safeLog('[STACK_TRACE] ' + error.stack);
|
|
12
|
+
}
|
|
13
|
+
// Gracefully shutdown
|
|
14
|
+
this.shutdown(1);
|
|
15
|
+
});
|
|
16
|
+
// Handle unhandled promises
|
|
17
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
18
|
+
safeLog('[UNHANDLED_REJECTION] ' + reason?.message || String(reason));
|
|
19
|
+
// Attempt graceful shutdown
|
|
20
|
+
this.shutdown(1);
|
|
21
|
+
});
|
|
22
|
+
// Handle graceful shutdown signals
|
|
23
|
+
process.on('SIGTERM', () => {
|
|
24
|
+
safeLog('[SIGTERM] Received shutdown signal');
|
|
25
|
+
this.shutdown(0);
|
|
26
|
+
});
|
|
27
|
+
process.on('SIGINT', () => {
|
|
28
|
+
safeLog('[SIGINT] Received shutdown signal');
|
|
29
|
+
this.shutdown(0);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
static shutdown(exitCode) {
|
|
33
|
+
// Allow cleanup time
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
process.exit(exitCode);
|
|
36
|
+
}, 100);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=processManager.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/util/resilientFetch.ts - Resilient HTTP client with timeout, retry, circuit breaker
|
|
3
|
+
*/
|
|
4
|
+
export interface FetchOptions {
|
|
5
|
+
timeout?: number;
|
|
6
|
+
retries?: number;
|
|
7
|
+
backoffMs?: number;
|
|
8
|
+
onRetry?: (attempt: number, error: Error) => void;
|
|
9
|
+
method?: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
body?: string;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
}
|
|
14
|
+
export interface CircuitState {
|
|
15
|
+
failures: number;
|
|
16
|
+
lastFailureTime: number;
|
|
17
|
+
state: "closed" | "open" | "half-open";
|
|
18
|
+
}
|
|
19
|
+
export declare function resilientFetch(url: string, options?: FetchOptions): Promise<Response>;
|
|
20
|
+
export declare function isReachable(url: string, timeout?: number): Promise<boolean>;
|
|
21
|
+
//# sourceMappingURL=resilientFetch.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/util/resilientFetch.ts - Resilient HTTP client with timeout, retry, circuit breaker
|
|
3
|
+
*/
|
|
4
|
+
const circuits = new Map();
|
|
5
|
+
const CIRCUIT_THRESHOLD = 3;
|
|
6
|
+
const CIRCUIT_RESET_MS = 60_000;
|
|
7
|
+
function getCircuit(key) {
|
|
8
|
+
let c = circuits.get(key);
|
|
9
|
+
if (!c) {
|
|
10
|
+
c = { failures: 0, lastFailureTime: 0, state: "closed" };
|
|
11
|
+
circuits.set(key, c);
|
|
12
|
+
}
|
|
13
|
+
return c;
|
|
14
|
+
}
|
|
15
|
+
function canExecute(key) {
|
|
16
|
+
const c = getCircuit(key);
|
|
17
|
+
if (c.state === "closed")
|
|
18
|
+
return true;
|
|
19
|
+
if (c.state === "open" && Date.now() - c.lastFailureTime > CIRCUIT_RESET_MS) {
|
|
20
|
+
c.state = "half-open";
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return c.state === "half-open";
|
|
24
|
+
}
|
|
25
|
+
function recordSuccess(key) {
|
|
26
|
+
const c = getCircuit(key);
|
|
27
|
+
c.failures = 0;
|
|
28
|
+
c.state = "closed";
|
|
29
|
+
}
|
|
30
|
+
function recordFailure(key) {
|
|
31
|
+
const c = getCircuit(key);
|
|
32
|
+
c.failures++;
|
|
33
|
+
c.lastFailureTime = Date.now();
|
|
34
|
+
if (c.failures >= CIRCUIT_THRESHOLD) {
|
|
35
|
+
c.state = "open";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function resilientFetch(url, options = {}) {
|
|
39
|
+
const { timeout = 8_000, retries = 2, backoffMs = 1_000, onRetry, method = "GET", headers, body, signal: externalSignal, } = options;
|
|
40
|
+
const circuitKey = new URL(url).hostname;
|
|
41
|
+
if (!canExecute(circuitKey)) {
|
|
42
|
+
throw new Error("Circuit open for " + circuitKey + " - service unavailable, retry later");
|
|
43
|
+
}
|
|
44
|
+
let lastError = null;
|
|
45
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
48
|
+
// Combine external abort signal with timeout
|
|
49
|
+
if (externalSignal) {
|
|
50
|
+
if (externalSignal.aborted)
|
|
51
|
+
controller.abort();
|
|
52
|
+
else
|
|
53
|
+
externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
signal: controller.signal,
|
|
58
|
+
method,
|
|
59
|
+
headers,
|
|
60
|
+
body,
|
|
61
|
+
});
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new Error("HTTP " + response.status + ": " + response.statusText);
|
|
65
|
+
}
|
|
66
|
+
recordSuccess(circuitKey);
|
|
67
|
+
return response;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
72
|
+
if (attempt < retries) {
|
|
73
|
+
onRetry?.(attempt + 1, lastError);
|
|
74
|
+
const delay = backoffMs * Math.pow(2, attempt);
|
|
75
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
recordFailure(circuitKey);
|
|
80
|
+
throw lastError;
|
|
81
|
+
}
|
|
82
|
+
export async function isReachable(url, timeout = 5_000) {
|
|
83
|
+
try {
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
86
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
return res.ok;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=resilientFetch.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/util/responseCache.ts — TTL-based response cache for external APIs
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get cached value or undefined if expired/missing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getCached<T>(key: string): T | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Set value in cache with TTL (default 60s).
|
|
10
|
+
*/
|
|
11
|
+
export declare function setCache<T>(key: string, data: T, ttlMs?: number): void;
|
|
12
|
+
/**
|
|
13
|
+
* Get or compute: return cached value, or compute, cache, and return.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getOrCompute<T>(key: string, compute: () => Promise<T>, ttlMs?: number): Promise<T>;
|
|
16
|
+
/**
|
|
17
|
+
* Clear all cached values.
|
|
18
|
+
*/
|
|
19
|
+
export declare function clearCache(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Get cache stats for diagnostics.
|
|
22
|
+
*/
|
|
23
|
+
export declare function cacheStats(): {
|
|
24
|
+
size: number;
|
|
25
|
+
entries: string[];
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=responseCache.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/util/responseCache.ts — TTL-based response cache for external APIs
|
|
3
|
+
*/
|
|
4
|
+
const cache = new Map();
|
|
5
|
+
const DEFAULT_TTL_MS = 60_000;
|
|
6
|
+
/**
|
|
7
|
+
* Get cached value or undefined if expired/missing.
|
|
8
|
+
*/
|
|
9
|
+
export function getCached(key) {
|
|
10
|
+
const entry = cache.get(key);
|
|
11
|
+
if (!entry)
|
|
12
|
+
return undefined;
|
|
13
|
+
if (Date.now() > entry.expiresAt) {
|
|
14
|
+
cache.delete(key);
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return entry.data;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Set value in cache with TTL (default 60s).
|
|
21
|
+
*/
|
|
22
|
+
export function setCache(key, data, ttlMs = DEFAULT_TTL_MS) {
|
|
23
|
+
cache.set(key, { data, expiresAt: Date.now() + ttlMs });
|
|
24
|
+
// Evict oldest if cache grows too large
|
|
25
|
+
if (cache.size > 200) {
|
|
26
|
+
const firstKey = cache.keys().next().value;
|
|
27
|
+
if (firstKey)
|
|
28
|
+
cache.delete(firstKey);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get or compute: return cached value, or compute, cache, and return.
|
|
33
|
+
*/
|
|
34
|
+
export async function getOrCompute(key, compute, ttlMs = DEFAULT_TTL_MS) {
|
|
35
|
+
const cached = getCached(key);
|
|
36
|
+
if (cached !== undefined)
|
|
37
|
+
return cached;
|
|
38
|
+
const result = await compute();
|
|
39
|
+
setCache(key, result, ttlMs);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Clear all cached values.
|
|
44
|
+
*/
|
|
45
|
+
export function clearCache() {
|
|
46
|
+
cache.clear();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get cache stats for diagnostics.
|
|
50
|
+
*/
|
|
51
|
+
export function cacheStats() {
|
|
52
|
+
return { size: cache.size, entries: [...cache.keys()] };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=responseCache.js.map
|
package/dist/util/safeLog.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* safeLog —
|
|
2
|
+
* src/util/safeLog.ts — Intercepts console output, masks secrets, routes to file
|
|
3
3
|
*/
|
|
4
|
-
export declare function wireNotify(fn
|
|
5
|
-
declare function
|
|
6
|
-
export declare
|
|
7
|
-
export {};
|
|
4
|
+
export declare function wireNotify(fn?: (msg: string) => void): void;
|
|
5
|
+
export declare function safeLog(...args: unknown[]): void;
|
|
6
|
+
export declare function installSafeLogging(): void;
|
|
8
7
|
//# sourceMappingURL=safeLog.d.ts.map
|
package/dist/util/safeLog.js
CHANGED
|
@@ -1,38 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* safeLog —
|
|
2
|
+
* src/util/safeLog.ts — Intercepts console output, masks secrets, routes to file
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
import { existsSync, appendFileSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
const AIAIAI_HOME = process.env.AIAIAI_HOME ?? join(homedir(), '.aiaiai');
|
|
8
|
+
const LOG_DIR = join(AIAIAI_HOME, '.logs');
|
|
9
|
+
const LOG_FILE = join(LOG_DIR, 'agent.log');
|
|
10
|
+
// Patterns that indicate sensitive values
|
|
11
|
+
const SENSITIVE_PATTERNS = [
|
|
12
|
+
/sk-[a-zA-Z0-9]{20,}/g, // API keys
|
|
13
|
+
/[A-Za-z0-9]{32,}/g, // Hex keys
|
|
14
|
+
/Bearer\s+[A-Za-z0-9\-._~+/]+/g, // Bearer tokens
|
|
15
|
+
/api[_-]?key["\s:=]+["']?[^\s"']+/gi, // Key assignments
|
|
16
|
+
/secret["\s:=]+["']?[^\s"']+/gi, // Secrets
|
|
17
|
+
];
|
|
18
|
+
const SENSITIVE_ENV_NAMES = [
|
|
19
|
+
'OPENROUTER_API_KEY', 'ANTHROPIC_API_KEY', 'CRYPTOPANIC_API_KEY',
|
|
20
|
+
'GMGN_API_KEY', 'SOLANA_RPC_URL', 'HELIUS_API_KEY', 'PRIVY_KEY',
|
|
21
|
+
];
|
|
22
|
+
const REDACTED = '[REDACTED]';
|
|
23
|
+
function maskSensitive(text) {
|
|
24
|
+
let masked = text;
|
|
25
|
+
for (const pattern of SENSITIVE_PATTERNS) {
|
|
26
|
+
masked = masked.replace(pattern, (match) => {
|
|
27
|
+
// Keep first 4 chars for identification
|
|
28
|
+
if (match.length > 8)
|
|
29
|
+
return match.slice(0, 4) + '…' + REDACTED;
|
|
30
|
+
return REDACTED;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return masked;
|
|
34
|
+
}
|
|
35
|
+
function ensureLogDir() {
|
|
36
|
+
if (!existsSync(LOG_DIR)) {
|
|
37
|
+
mkdirSync(LOG_DIR, { recursive: true, mode: 0o700 });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function writeToFile(text) {
|
|
41
|
+
try {
|
|
42
|
+
ensureLogDir();
|
|
43
|
+
appendFileSync(LOG_FILE, text + '\n', { encoding: 'utf-8' });
|
|
14
44
|
}
|
|
45
|
+
catch { /* non-fatal — prevents logging failures from crashing agent */ }
|
|
15
46
|
}
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
47
|
+
export function wireNotify(fn) {
|
|
48
|
+
// Wire to notification callback if provided
|
|
49
|
+
if (fn) {
|
|
50
|
+
// placeholder for notification integration
|
|
20
51
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
process.stderr.write(m + "\n");
|
|
30
|
-
queue.length = 0;
|
|
31
|
-
}
|
|
32
|
-
scheduled = false;
|
|
33
|
-
});
|
|
34
|
-
}
|
|
52
|
+
}
|
|
53
|
+
export function safeLog(...args) {
|
|
54
|
+
const raw = args.map(a => typeof a === 'string' ? a : String(a)).join(' ');
|
|
55
|
+
const masked = maskSensitive(raw);
|
|
56
|
+
const timestamp = new Date().toISOString();
|
|
57
|
+
const line = `[${timestamp}] ${masked}`;
|
|
58
|
+
try {
|
|
59
|
+
writeToFile(line);
|
|
35
60
|
}
|
|
61
|
+
catch { /* non-fatal */ }
|
|
62
|
+
process.stdout.write(line + '\n');
|
|
63
|
+
}
|
|
64
|
+
// Override console methods
|
|
65
|
+
export function installSafeLogging() {
|
|
66
|
+
console.log = (...args) => safeLog(...args);
|
|
67
|
+
console.warn = (...args) => {
|
|
68
|
+
const masked = maskSensitive(args.map(String).join(' '));
|
|
69
|
+
process.stderr.write(`[WARN] ${masked}\n`);
|
|
70
|
+
};
|
|
71
|
+
console.error = (...args) => {
|
|
72
|
+
const masked = maskSensitive(args.map(String).join(' '));
|
|
73
|
+
process.stderr.write(`[ERROR] ${masked}\n`);
|
|
74
|
+
};
|
|
36
75
|
}
|
|
37
|
-
export const safeLog = safeLogFn;
|
|
38
76
|
//# sourceMappingURL=safeLog.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/util/scheduler.ts — Centralized interval scheduler with pause/resume
|
|
3
|
+
*/
|
|
4
|
+
export declare function addTask(name: string, intervalMs: number, fn: () => void): void;
|
|
5
|
+
export declare function removeTask(name: string): void;
|
|
6
|
+
export declare function pauseTask(name: string): void;
|
|
7
|
+
export declare function resumeTask(name: string): void;
|
|
8
|
+
export declare function pauseAll(): void;
|
|
9
|
+
export declare function resumeAll(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Start the scheduler loop. Returns a stop function.
|
|
12
|
+
*/
|
|
13
|
+
export declare function startScheduler(): () => void;
|
|
14
|
+
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/util/scheduler.ts — Centralized interval scheduler with pause/resume
|
|
3
|
+
*/
|
|
4
|
+
const MAX_TASKS = 50;
|
|
5
|
+
const tasks = [];
|
|
6
|
+
let running = false;
|
|
7
|
+
export function addTask(name, intervalMs, fn) {
|
|
8
|
+
// Replace existing task with same name
|
|
9
|
+
const existing = tasks.findIndex(t => t.name === name);
|
|
10
|
+
if (existing >= 0) {
|
|
11
|
+
tasks[existing] = { name, intervalMs, fn, lastRun: 0, paused: false };
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
if (tasks.length >= MAX_TASKS) {
|
|
15
|
+
// Evict oldest non-paused task
|
|
16
|
+
const evictIdx = tasks.findIndex(t => !t.paused);
|
|
17
|
+
if (evictIdx >= 0)
|
|
18
|
+
tasks.splice(evictIdx, 1);
|
|
19
|
+
else
|
|
20
|
+
return; // All tasks paused, refuse to add
|
|
21
|
+
}
|
|
22
|
+
tasks.push({ name, intervalMs, fn, lastRun: 0, paused: false });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function removeTask(name) {
|
|
26
|
+
const idx = tasks.findIndex(t => t.name === name);
|
|
27
|
+
if (idx >= 0)
|
|
28
|
+
tasks.splice(idx, 1);
|
|
29
|
+
}
|
|
30
|
+
export function pauseTask(name) {
|
|
31
|
+
const task = tasks.find(t => t.name === name);
|
|
32
|
+
if (task)
|
|
33
|
+
task.paused = true;
|
|
34
|
+
}
|
|
35
|
+
export function resumeTask(name) {
|
|
36
|
+
const task = tasks.find(t => t.name === name);
|
|
37
|
+
if (task)
|
|
38
|
+
task.paused = false;
|
|
39
|
+
}
|
|
40
|
+
export function pauseAll() {
|
|
41
|
+
tasks.forEach(t => (t.paused = true));
|
|
42
|
+
}
|
|
43
|
+
export function resumeAll() {
|
|
44
|
+
tasks.forEach(t => (t.paused = false));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Start the scheduler loop. Returns a stop function.
|
|
48
|
+
*/
|
|
49
|
+
export function startScheduler() {
|
|
50
|
+
if (running)
|
|
51
|
+
return () => { };
|
|
52
|
+
running = true;
|
|
53
|
+
const tick = () => {
|
|
54
|
+
if (!running)
|
|
55
|
+
return;
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
for (const task of tasks) {
|
|
58
|
+
if (task.paused)
|
|
59
|
+
continue;
|
|
60
|
+
if (now - task.lastRun >= task.intervalMs) {
|
|
61
|
+
task.lastRun = now;
|
|
62
|
+
try {
|
|
63
|
+
task.fn();
|
|
64
|
+
}
|
|
65
|
+
catch { /* task errors don't crash scheduler */ }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
setTimeout(tick, 1000);
|
|
69
|
+
};
|
|
70
|
+
tick();
|
|
71
|
+
return () => {
|
|
72
|
+
running = false;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/util/webhooks.ts — Discord & Telegram notification webhooks.
|
|
3
|
+
* Configure via ~/.aiaiai/.env with WEBHOOK_DISCORD and/or WEBHOOK_TELEGRAM.
|
|
4
|
+
*/
|
|
5
|
+
export declare function sendDiscord(message: string): Promise<boolean>;
|
|
6
|
+
export declare function sendTelegram(message: string): Promise<boolean>;
|
|
7
|
+
export declare function notify(message: string): Promise<void>;
|
|
8
|
+
export declare function hasWebhooks(): boolean;
|
|
9
|
+
//# sourceMappingURL=webhooks.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/util/webhooks.ts — Discord & Telegram notification webhooks.
|
|
3
|
+
* Configure via ~/.aiaiai/.env with WEBHOOK_DISCORD and/or WEBHOOK_TELEGRAM.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { logger } from "./logger.js";
|
|
9
|
+
const AIAIAI_HOME = process.env.AIAIAI_HOME ?? join(homedir(), ".aiaiai");
|
|
10
|
+
function getWebhookUrl(service) {
|
|
11
|
+
const envFile = join(AIAIAI_HOME, ".env");
|
|
12
|
+
if (!existsSync(envFile))
|
|
13
|
+
return null;
|
|
14
|
+
const content = readFileSync(envFile, "utf-8");
|
|
15
|
+
const key = service === "discord" ? "WEBHOOK_DISCORD" : "WEBHOOK_TELEGRAM";
|
|
16
|
+
const match = new RegExp(`^${key}=(.+)$`, "m").exec(content);
|
|
17
|
+
return match?.[1] ?? null;
|
|
18
|
+
}
|
|
19
|
+
export async function sendDiscord(message) {
|
|
20
|
+
const url = getWebhookUrl("discord");
|
|
21
|
+
if (!url)
|
|
22
|
+
return false;
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch(url, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: { "Content-Type": "application/json" },
|
|
27
|
+
body: JSON.stringify({ content: `🤖 **AIAIAI Agent**\n${message}` }),
|
|
28
|
+
});
|
|
29
|
+
return res.ok;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger.warn("Webhooks", "Discord send failed", { error: error.message });
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function sendTelegram(message) {
|
|
37
|
+
const url = getWebhookUrl("telegram");
|
|
38
|
+
if (!url)
|
|
39
|
+
return false;
|
|
40
|
+
// Telegram Bot API format: https://api.telegram.org/bot<TOKEN>/sendMessage
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(url, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "Content-Type": "application/json" },
|
|
45
|
+
body: JSON.stringify({
|
|
46
|
+
chat_id: extractTelegramChatId(url),
|
|
47
|
+
text: `🤖 AIAIAI Agent\n${message}`,
|
|
48
|
+
parse_mode: "HTML",
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
return res.ok;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
logger.warn("Webhooks", "Telegram send failed", { error: error.message });
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function extractTelegramChatId(url) {
|
|
59
|
+
// URL format: https://api.telegram.org/bot<TOKEN>/sendMessage?chat_id=<ID>
|
|
60
|
+
const match = /chat_id=([^&]+)/.exec(url);
|
|
61
|
+
return match?.[1] ?? "";
|
|
62
|
+
}
|
|
63
|
+
export async function notify(message) {
|
|
64
|
+
const [discordOk, telegramOk] = await Promise.all([
|
|
65
|
+
sendDiscord(message),
|
|
66
|
+
sendTelegram(message),
|
|
67
|
+
]);
|
|
68
|
+
if (!discordOk && !telegramOk) {
|
|
69
|
+
logger.debug("Webhooks", "No webhooks configured or all failed");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function hasWebhooks() {
|
|
73
|
+
return !!(getWebhookUrl("discord") || getWebhookUrl("telegram"));
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=webhooks.js.map
|
|
@@ -5,18 +5,22 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Type } from "@sinclair/typebox";
|
|
7
7
|
import { ACTION_WALLET, AIAIAI_MINT } from "./AgentWallet.js";
|
|
8
|
+
import { resilientFetch } from "../util/resilientFetch.js";
|
|
9
|
+
import { logger } from "../util/logger.js";
|
|
8
10
|
function getRpcUrl() {
|
|
9
11
|
return process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";
|
|
10
12
|
}
|
|
11
13
|
async function rpcCall(method, params = []) {
|
|
12
|
-
const response = await
|
|
14
|
+
const response = await resilientFetch(getRpcUrl(), {
|
|
15
|
+
timeout: 15_000,
|
|
16
|
+
retries: 2,
|
|
13
17
|
method: "POST",
|
|
14
18
|
headers: { "Content-Type": "application/json" },
|
|
15
19
|
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
16
20
|
});
|
|
17
21
|
const data = await response.json();
|
|
18
22
|
if (data.error)
|
|
19
|
-
throw new Error(data.error.message);
|
|
23
|
+
throw new Error(data.error.message || 'Unknown RPC error');
|
|
20
24
|
return data.result;
|
|
21
25
|
}
|
|
22
26
|
export class ActionFeed {
|
|
@@ -46,7 +50,9 @@ export class ActionFeed {
|
|
|
46
50
|
this.lastSignature = signatures[0].signature;
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
|
-
catch {
|
|
53
|
+
catch (error) {
|
|
54
|
+
logger.debug('ActionFeed', 'refresh failed', { error: error.message });
|
|
55
|
+
}
|
|
50
56
|
}
|
|
51
57
|
async processTransaction(signature) {
|
|
52
58
|
try {
|
|
@@ -100,7 +106,9 @@ export class ActionFeed {
|
|
|
100
106
|
if (this.actions.length > 50)
|
|
101
107
|
this.actions = this.actions.slice(0, 50);
|
|
102
108
|
}
|
|
103
|
-
catch {
|
|
109
|
+
catch (error) {
|
|
110
|
+
logger.debug('ActionFeed', 'Failed to parse tx', { error: error.message });
|
|
111
|
+
}
|
|
104
112
|
}
|
|
105
113
|
setPrice(p) {
|
|
106
114
|
if (p > 0)
|
|
@@ -13,6 +13,7 @@ export declare const ACTION_WALLET = "BygDYM1ZXLQNC1HXLhnd1rHZ7E5XjioqT3vPjJFfjn
|
|
|
13
13
|
export declare const DEPOSIT_WALLET = "FBMDYpG9WXKy4SgxuATQdB2sCyzHsJWPrEr45z3TgL2e";
|
|
14
14
|
export declare const SIGNER = "GmFrDZT2cdrqykgTikVdXbe8EtCgzUDM9VsDhQnwsUsG";
|
|
15
15
|
export declare const AIAIAI_MINT = "AVPJS61gZmWKtaEpb7qYPKo8Fk2xQUsayYQxPiPMpump";
|
|
16
|
+
export declare const AIAIAI_TOKEN = "AVPJS61gZmWKtaEpb7qYPKo8Fk2xQUsayYQxPiPMpump";
|
|
16
17
|
export interface WalletBalance {
|
|
17
18
|
address: string;
|
|
18
19
|
sol: number;
|
|
@@ -32,6 +33,7 @@ export declare class AgentWallet {
|
|
|
32
33
|
getColdWallet(): Promise<WalletBalance>;
|
|
33
34
|
getActionWallet(): Promise<WalletBalance>;
|
|
34
35
|
getDepositWallet(): Promise<WalletBalance>;
|
|
36
|
+
getSolPrice(): Promise<number>;
|
|
35
37
|
getAgentBalanceParams: import("@sinclair/typebox").TObject<{}>;
|
|
36
38
|
getDepositBalanceParams: import("@sinclair/typebox").TObject<{}>;
|
|
37
39
|
getAgentBalanceTool(): Promise<ToolResult>;
|