@hoox-sh/hoox-shared 1.2.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/dist/agent-config-fields.js +2 -0
- package/dist/d1/index.js +4 -2
- package/dist/d1/repository.js +4 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -2
- package/dist/health.js +4 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +432 -139
- package/dist/kill-switch.js +2 -0
- package/dist/kvKeys.d.ts +3 -0
- package/dist/kvKeys.d.ts.map +1 -1
- package/dist/kvKeys.js +3 -0
- package/dist/kvUtils.d.ts +34 -1
- package/dist/kvUtils.d.ts.map +1 -1
- package/dist/kvUtils.js +56 -3
- package/dist/middleware/auth.d.ts +6 -3
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +19 -5
- package/dist/middleware/index.d.ts +3 -2
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +88 -8
- package/dist/middleware/logger.js +4 -2
- package/dist/middleware/validate.d.ts +16 -0
- package/dist/middleware/validate.d.ts.map +1 -1
- package/dist/middleware/validate.js +37 -1
- package/dist/middleware/wait-until.d.ts +32 -0
- package/dist/middleware/wait-until.d.ts.map +1 -0
- package/dist/middleware/wait-until.js +46 -0
- package/dist/path-utils.d.ts +35 -4
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +72 -5
- package/dist/queue-handler.d.ts +6 -0
- package/dist/queue-handler.d.ts.map +1 -1
- package/dist/queue-handler.js +51 -20
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +12 -2
- package/dist/schemas/index.d.ts +2 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +58 -13
- package/dist/schemas/registry.d.ts +13 -0
- package/dist/schemas/registry.d.ts.map +1 -1
- package/dist/schemas/registry.js +55 -12
- package/dist/schemas/types.d.ts +5 -0
- package/dist/schemas/types.d.ts.map +1 -1
- package/dist/schemas/validators.d.ts.map +1 -1
- package/dist/schemas/validators.js +3 -1
- package/dist/service-bindings.js +18 -5
- package/dist/session.js +69 -5
- package/dist/stores/config-store.d.ts +2 -0
- package/dist/stores/config-store.d.ts.map +1 -1
- package/dist/stores/config-store.js +267 -16
- package/dist/stores/service-store.d.ts +12 -0
- package/dist/stores/service-store.d.ts.map +1 -1
- package/dist/stores/service-store.js +101 -40
- package/dist/stores/ui-store.d.ts +14 -0
- package/dist/stores/ui-store.d.ts.map +1 -1
- package/dist/stores/ui-store.js +8 -1
- package/dist/types.d.ts +22 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -4
- package/dist/wizard/engine.d.ts.map +1 -1
- package/dist/wizard/engine.js +18 -35
- package/dist/wizard/index.js +18 -35
- package/dist/wizard/presets.d.ts.map +1 -1
- package/dist/wizard/presets.js +6 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -269,6 +269,7 @@ var KV_BOT_ENABLED = "bot:enabled";
|
|
|
269
269
|
var KV_BOT_DEFAULT_CHAT_ID = "bot:default_chat_id";
|
|
270
270
|
var KV_BOT_NOTIFY_ON_EXECUTION = "bot:notify_on_execution";
|
|
271
271
|
var KV_BOT_NOTIFY_ON_ERROR = "bot:notify_on_error";
|
|
272
|
+
var KV_TELEGRAM_ALLOWED_CHAT_IDS = "telegram:allowed_chat_ids";
|
|
272
273
|
var KV_WEBHOOK_IP_CHECK_ENABLED = "webhook:tradingview:ip_check_enabled";
|
|
273
274
|
var KV_WEBHOOK_ALLOWED_IPS = "webhook:tradingview:allowed_ips";
|
|
274
275
|
var KV_WEBHOOK_QUEUE_MODE = "webhook:queue_mode";
|
|
@@ -299,6 +300,7 @@ var KVKeys = {
|
|
|
299
300
|
KV_BOT_DEFAULT_CHAT_ID,
|
|
300
301
|
KV_BOT_NOTIFY_ON_EXECUTION,
|
|
301
302
|
KV_BOT_NOTIFY_ON_ERROR,
|
|
303
|
+
KV_TELEGRAM_ALLOWED_CHAT_IDS,
|
|
302
304
|
KV_WEBHOOK_IP_CHECK_ENABLED,
|
|
303
305
|
KV_WEBHOOK_ALLOWED_IPS,
|
|
304
306
|
KV_WEBHOOK_QUEUE_MODE,
|
|
@@ -686,8 +688,10 @@ function toError(err, fallback = "Unknown error") {
|
|
|
686
688
|
return err.message;
|
|
687
689
|
if (typeof err === "string")
|
|
688
690
|
return err;
|
|
689
|
-
if (err && typeof err === "object" && "message" in err
|
|
690
|
-
|
|
691
|
+
if (err && typeof err === "object" && "message" in err) {
|
|
692
|
+
const message = err.message;
|
|
693
|
+
if (typeof message === "string")
|
|
694
|
+
return message;
|
|
691
695
|
}
|
|
692
696
|
if (err === null || err === undefined)
|
|
693
697
|
return fallback;
|
|
@@ -1773,14 +1777,16 @@ var TradeActionSchema = z2.enum([
|
|
|
1773
1777
|
"CLOSE_LONG",
|
|
1774
1778
|
"CLOSE_SHORT"
|
|
1775
1779
|
]);
|
|
1780
|
+
var MAX_WEBHOOK_LEVERAGE = 125;
|
|
1781
|
+
var MAX_WEBHOOK_QUANTITY = 1e6;
|
|
1776
1782
|
var WebhookPayloadSchema = z2.object({
|
|
1777
1783
|
exchange: z2.string().min(1),
|
|
1778
1784
|
action: TradeActionSchema,
|
|
1779
1785
|
symbol: z2.string().min(1).max(20),
|
|
1780
|
-
quantity: z2.number().positive().finite(),
|
|
1786
|
+
quantity: z2.number().positive().finite().max(MAX_WEBHOOK_QUANTITY),
|
|
1781
1787
|
price: z2.number().positive().finite().optional(),
|
|
1782
1788
|
orderType: z2.string().optional(),
|
|
1783
|
-
leverage: z2.number().int().positive().optional(),
|
|
1789
|
+
leverage: z2.number().int().positive().max(MAX_WEBHOOK_LEVERAGE).optional(),
|
|
1784
1790
|
test: z2.boolean().optional()
|
|
1785
1791
|
}).strict();
|
|
1786
1792
|
var TradeQueueMessageSchema = z2.object({
|
|
@@ -1788,9 +1794,9 @@ var TradeQueueMessageSchema = z2.object({
|
|
|
1788
1794
|
exchange: z2.string().min(1).max(64),
|
|
1789
1795
|
action: TradeActionSchema,
|
|
1790
1796
|
symbol: z2.string().min(1).max(32),
|
|
1791
|
-
quantity: z2.number().positive().finite(),
|
|
1797
|
+
quantity: z2.number().positive().finite().max(MAX_WEBHOOK_QUANTITY),
|
|
1792
1798
|
price: z2.number().positive().finite().optional(),
|
|
1793
|
-
leverage: z2.number().int().positive().max(
|
|
1799
|
+
leverage: z2.number().int().positive().max(MAX_WEBHOOK_LEVERAGE).optional(),
|
|
1794
1800
|
test: z2.boolean().optional(),
|
|
1795
1801
|
idempotencyKey: z2.string().min(1).max(512).optional(),
|
|
1796
1802
|
queuedAt: z2.string().min(1).max(64)
|
|
@@ -1880,6 +1886,53 @@ async function logKvTimestamp(env, prefix = "timestamp") {
|
|
|
1880
1886
|
console.error(`Failed to log timestamp to KV: ${toError(error, "Unknown error")}`);
|
|
1881
1887
|
}
|
|
1882
1888
|
}
|
|
1889
|
+
var KV_BULK_GET_MAX_KEYS = 100;
|
|
1890
|
+
async function tryBulkGetChunk(kv, chunk) {
|
|
1891
|
+
try {
|
|
1892
|
+
const result = await kv.get([...chunk]);
|
|
1893
|
+
if (!(result instanceof Map)) {
|
|
1894
|
+
return null;
|
|
1895
|
+
}
|
|
1896
|
+
return chunk.map((key) => result.get(key) ?? null);
|
|
1897
|
+
} catch {
|
|
1898
|
+
return null;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
async function kvGetMany(kv, keys) {
|
|
1902
|
+
if (keys.length === 0)
|
|
1903
|
+
return [];
|
|
1904
|
+
const out = new Array(keys.length);
|
|
1905
|
+
let offset = 0;
|
|
1906
|
+
while (offset < keys.length) {
|
|
1907
|
+
const chunk = keys.slice(offset, offset + KV_BULK_GET_MAX_KEYS);
|
|
1908
|
+
const bulk = await tryBulkGetChunk(kv, chunk);
|
|
1909
|
+
if (bulk === null) {
|
|
1910
|
+
const rest = await Promise.all(keys.slice(offset).map((key) => kv.get(key)));
|
|
1911
|
+
for (let i = 0;i < rest.length; i++) {
|
|
1912
|
+
out[offset + i] = rest[i] ?? null;
|
|
1913
|
+
}
|
|
1914
|
+
return out;
|
|
1915
|
+
}
|
|
1916
|
+
for (let i = 0;i < bulk.length; i++) {
|
|
1917
|
+
out[offset + i] = bulk[i] ?? null;
|
|
1918
|
+
}
|
|
1919
|
+
offset += chunk.length;
|
|
1920
|
+
}
|
|
1921
|
+
return out;
|
|
1922
|
+
}
|
|
1923
|
+
async function kvGetManyAsRecord(kv, keys) {
|
|
1924
|
+
const values = await kvGetMany(kv, keys);
|
|
1925
|
+
const out = {};
|
|
1926
|
+
for (let i = 0;i < keys.length; i++) {
|
|
1927
|
+
out[keys[i]] = values[i] ?? null;
|
|
1928
|
+
}
|
|
1929
|
+
return out;
|
|
1930
|
+
}
|
|
1931
|
+
async function kvPutMany(kv, entries) {
|
|
1932
|
+
if (entries.length === 0)
|
|
1933
|
+
return;
|
|
1934
|
+
await Promise.all(entries.map((entry) => kv.put(entry.key, entry.value, entry.options)));
|
|
1935
|
+
}
|
|
1883
1936
|
function headersToObject(headers) {
|
|
1884
1937
|
const result = {};
|
|
1885
1938
|
if (!headers)
|
|
@@ -1895,29 +1948,60 @@ function headersToObject(headers) {
|
|
|
1895
1948
|
}
|
|
1896
1949
|
|
|
1897
1950
|
// src/queue-handler.ts
|
|
1951
|
+
async function mapPool(items, concurrency, fn) {
|
|
1952
|
+
if (items.length === 0)
|
|
1953
|
+
return;
|
|
1954
|
+
const limit = Math.min(Math.max(1, concurrency), items.length);
|
|
1955
|
+
if (limit === 1) {
|
|
1956
|
+
for (const item of items) {
|
|
1957
|
+
await fn(item);
|
|
1958
|
+
}
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
let nextIndex = 0;
|
|
1962
|
+
async function worker() {
|
|
1963
|
+
while (true) {
|
|
1964
|
+
const i = nextIndex++;
|
|
1965
|
+
if (i >= items.length)
|
|
1966
|
+
return;
|
|
1967
|
+
await fn(items[i]);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
await Promise.all(Array.from({ length: limit }, () => worker()));
|
|
1971
|
+
}
|
|
1898
1972
|
function createQueueHandler(options) {
|
|
1899
|
-
const {
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1973
|
+
const {
|
|
1974
|
+
maxRetries,
|
|
1975
|
+
backoffDelays,
|
|
1976
|
+
onMessage,
|
|
1977
|
+
onRetry,
|
|
1978
|
+
onDLQ,
|
|
1979
|
+
logger,
|
|
1980
|
+
concurrency: concurrencyOpt
|
|
1981
|
+
} = options;
|
|
1982
|
+
const concurrency = typeof concurrencyOpt === "number" && Number.isFinite(concurrencyOpt) && concurrencyOpt >= 1 ? Math.floor(concurrencyOpt) : 1;
|
|
1983
|
+
async function processMessage(msg) {
|
|
1984
|
+
const attemptNumber = msg.attempts || 0;
|
|
1985
|
+
const logId = `[${msg.id}]`;
|
|
1986
|
+
try {
|
|
1987
|
+
logger?.info(`${logId} Processing message (attempt ${attemptNumber + 1})`);
|
|
1988
|
+
await onMessage(msg.body, attemptNumber);
|
|
1989
|
+
logger?.info(`${logId} Message processed successfully`);
|
|
1990
|
+
} catch (error) {
|
|
1991
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1992
|
+
if (attemptNumber < maxRetries) {
|
|
1993
|
+
const delaySeconds = backoffDelays[attemptNumber] ?? backoffDelays[backoffDelays.length - 1] ?? 0;
|
|
1994
|
+
logger?.info(`${logId} Retrying in ${delaySeconds}s (attempt ${attemptNumber + 2}/${maxRetries + 1})`);
|
|
1995
|
+
await onRetry?.(msg.body, attemptNumber, errorMsg, delaySeconds);
|
|
1996
|
+
msg.retry({ delaySeconds });
|
|
1997
|
+
} else {
|
|
1998
|
+
logger?.error(`${logId} Max retries exceeded (${maxRetries + 1} attempts), moving to DLQ`);
|
|
1999
|
+
await onDLQ?.(msg.body, attemptNumber, errorMsg);
|
|
1919
2000
|
}
|
|
1920
2001
|
}
|
|
2002
|
+
}
|
|
2003
|
+
return async (batch) => {
|
|
2004
|
+
await mapPool(batch.messages, concurrency, processMessage);
|
|
1921
2005
|
};
|
|
1922
2006
|
}
|
|
1923
2007
|
|
|
@@ -1929,12 +2013,24 @@ function timingSafeEqual(a, b) {
|
|
|
1929
2013
|
const len = Math.max(aBuf.length, bBuf.length);
|
|
1930
2014
|
let result = aBuf.length === bBuf.length ? 0 : 1;
|
|
1931
2015
|
for (let i = 0;i < len; i++) {
|
|
1932
|
-
const av =
|
|
1933
|
-
const bv =
|
|
2016
|
+
const av = aBuf[i] ?? 0;
|
|
2017
|
+
const bv = bBuf[i] ?? 0;
|
|
1934
2018
|
result |= av ^ bv;
|
|
1935
2019
|
}
|
|
1936
2020
|
return result === 0;
|
|
1937
2021
|
}
|
|
2022
|
+
async function timingSafeEqualAsync(a, b) {
|
|
2023
|
+
const subtle = globalThis.crypto?.subtle;
|
|
2024
|
+
if (!subtle || typeof subtle.digest !== "function" || typeof subtle.timingSafeEqual !== "function") {
|
|
2025
|
+
return timingSafeEqual(a, b);
|
|
2026
|
+
}
|
|
2027
|
+
const encoder = new TextEncoder;
|
|
2028
|
+
const [providedHash, expectedHash] = await Promise.all([
|
|
2029
|
+
subtle.digest("SHA-256", encoder.encode(a)),
|
|
2030
|
+
subtle.digest("SHA-256", encoder.encode(b))
|
|
2031
|
+
]);
|
|
2032
|
+
return subtle.timingSafeEqual(providedHash, expectedHash);
|
|
2033
|
+
}
|
|
1938
2034
|
function resolveOperatorApiKey(env) {
|
|
1939
2035
|
const preferred = env.OPERATOR_API_KEY;
|
|
1940
2036
|
if (typeof preferred === "string" && preferred.length > 0) {
|
|
@@ -1959,7 +2055,7 @@ async function requireOperatorAuth(request, env) {
|
|
|
1959
2055
|
}
|
|
1960
2056
|
const authHeader = request.headers.get("Authorization");
|
|
1961
2057
|
const expectedHeader = `Bearer ${apiKey}`;
|
|
1962
|
-
if (!authHeader || !
|
|
2058
|
+
if (!authHeader || !await timingSafeEqualAsync(authHeader, expectedHeader)) {
|
|
1963
2059
|
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
1964
2060
|
status: 401,
|
|
1965
2061
|
headers: { "Content-Type": "application/json" }
|
|
@@ -1992,12 +2088,13 @@ function collectInternalAuthKeys(env, keyNames) {
|
|
|
1992
2088
|
return keys;
|
|
1993
2089
|
}
|
|
1994
2090
|
function matchesAnyInternalAuthKey(providedKey, expectedKeys) {
|
|
2091
|
+
let matched = false;
|
|
1995
2092
|
for (const expected of expectedKeys) {
|
|
1996
2093
|
if (timingSafeEqual(providedKey, expected)) {
|
|
1997
|
-
|
|
2094
|
+
matched = true;
|
|
1998
2095
|
}
|
|
1999
2096
|
}
|
|
2000
|
-
return
|
|
2097
|
+
return matched;
|
|
2001
2098
|
}
|
|
2002
2099
|
function internalAuthNotConfiguredResponse(keyNames) {
|
|
2003
2100
|
const label = normalizeKeyNames(keyNames).join(" | ");
|
|
@@ -2236,7 +2333,13 @@ function createMockEnv(overrides) {
|
|
|
2236
2333
|
}
|
|
2237
2334
|
|
|
2238
2335
|
// src/path-utils.ts
|
|
2239
|
-
import {
|
|
2336
|
+
import {
|
|
2337
|
+
existsSync as existsSync3,
|
|
2338
|
+
mkdirSync as mkdirSync2,
|
|
2339
|
+
readFileSync as readFileSync3,
|
|
2340
|
+
writeFileSync as writeFileSync2,
|
|
2341
|
+
chmodSync as chmodSync2
|
|
2342
|
+
} from "fs";
|
|
2240
2343
|
import { homedir as homedir2 } from "os";
|
|
2241
2344
|
import { dirname, join as join3, resolve } from "path";
|
|
2242
2345
|
function createHooxPath(path) {
|
|
@@ -2262,7 +2365,55 @@ function isHooxSetupRoot(dir) {
|
|
|
2262
2365
|
return false;
|
|
2263
2366
|
try {
|
|
2264
2367
|
const root = resolve(dir);
|
|
2265
|
-
|
|
2368
|
+
if (!existsSync3(join3(root, "packages", "cli", "package.json"))) {
|
|
2369
|
+
return false;
|
|
2370
|
+
}
|
|
2371
|
+
return existsSync3(join3(root, "wrangler.jsonc")) || existsSync3(join3(root, "wrangler.jsonc.example")) || existsSync3(join3(root, "workers")) || existsSync3(join3(root, ".gitmodules"));
|
|
2372
|
+
} catch {
|
|
2373
|
+
return false;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
function getRememberedMonorepoPath() {
|
|
2377
|
+
return resolveHooxPath("config/monorepo.json");
|
|
2378
|
+
}
|
|
2379
|
+
function getRememberedMonorepoRoot() {
|
|
2380
|
+
try {
|
|
2381
|
+
const path = getRememberedMonorepoPath();
|
|
2382
|
+
if (!existsSync3(path))
|
|
2383
|
+
return null;
|
|
2384
|
+
const raw = readFileSync3(path, "utf-8");
|
|
2385
|
+
const data = JSON.parse(raw);
|
|
2386
|
+
if (!data.root || typeof data.root !== "string")
|
|
2387
|
+
return null;
|
|
2388
|
+
const resolved = resolve(data.root);
|
|
2389
|
+
return isHooxSetupRoot(resolved) ? resolved : null;
|
|
2390
|
+
} catch {
|
|
2391
|
+
return null;
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
function rememberMonorepoRoot(root) {
|
|
2395
|
+
try {
|
|
2396
|
+
const resolved = resolve(root);
|
|
2397
|
+
if (!isHooxSetupRoot(resolved))
|
|
2398
|
+
return false;
|
|
2399
|
+
const configDir = getHooxConfigDir();
|
|
2400
|
+
mkdirSync2(configDir, { recursive: true, mode: 448 });
|
|
2401
|
+
try {
|
|
2402
|
+
chmodSync2(configDir, 448);
|
|
2403
|
+
} catch {}
|
|
2404
|
+
const payload = {
|
|
2405
|
+
root: resolved,
|
|
2406
|
+
updatedAt: new Date().toISOString()
|
|
2407
|
+
};
|
|
2408
|
+
const path = getRememberedMonorepoPath();
|
|
2409
|
+
writeFileSync2(path, JSON.stringify(payload, null, 2) + `
|
|
2410
|
+
`, {
|
|
2411
|
+
mode: 384
|
|
2412
|
+
});
|
|
2413
|
+
try {
|
|
2414
|
+
chmodSync2(path, 384);
|
|
2415
|
+
} catch {}
|
|
2416
|
+
return true;
|
|
2266
2417
|
} catch {
|
|
2267
2418
|
return false;
|
|
2268
2419
|
}
|
|
@@ -2282,6 +2433,7 @@ function resolveHooxRuntimeRoot(options) {
|
|
|
2282
2433
|
const env = options?.env ?? process.env;
|
|
2283
2434
|
const cwd = resolve(options?.cwd ?? process.cwd());
|
|
2284
2435
|
const globalRepo = getHooxRepoPath();
|
|
2436
|
+
const remembered = options?.skipRemembered ? null : getRememberedMonorepoRoot();
|
|
2285
2437
|
const envRepo = env.HOOX_REPO?.trim();
|
|
2286
2438
|
if (envRepo) {
|
|
2287
2439
|
const resolved = resolve(envRepo);
|
|
@@ -2292,6 +2444,7 @@ function resolveHooxRuntimeRoot(options) {
|
|
|
2292
2444
|
checked: {
|
|
2293
2445
|
env: resolved,
|
|
2294
2446
|
cwd: findHooxSetupRoot(cwd),
|
|
2447
|
+
remembered,
|
|
2295
2448
|
global: globalRepo
|
|
2296
2449
|
}
|
|
2297
2450
|
};
|
|
@@ -2302,6 +2455,7 @@ function resolveHooxRuntimeRoot(options) {
|
|
|
2302
2455
|
checked: {
|
|
2303
2456
|
env: resolved,
|
|
2304
2457
|
cwd: findHooxSetupRoot(cwd),
|
|
2458
|
+
remembered,
|
|
2305
2459
|
global: globalRepo
|
|
2306
2460
|
}
|
|
2307
2461
|
};
|
|
@@ -2311,20 +2465,27 @@ function resolveHooxRuntimeRoot(options) {
|
|
|
2311
2465
|
return {
|
|
2312
2466
|
root: local,
|
|
2313
2467
|
source: "cwd",
|
|
2314
|
-
checked: { cwd: local, global: globalRepo }
|
|
2468
|
+
checked: { cwd: local, remembered, global: globalRepo }
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
if (remembered) {
|
|
2472
|
+
return {
|
|
2473
|
+
root: remembered,
|
|
2474
|
+
source: "remembered",
|
|
2475
|
+
checked: { cwd: null, remembered, global: globalRepo }
|
|
2315
2476
|
};
|
|
2316
2477
|
}
|
|
2317
2478
|
if (isHooxSetupRoot(globalRepo)) {
|
|
2318
2479
|
return {
|
|
2319
2480
|
root: globalRepo,
|
|
2320
2481
|
source: "global",
|
|
2321
|
-
checked: { cwd: null, global: globalRepo }
|
|
2482
|
+
checked: { cwd: null, remembered: null, global: globalRepo }
|
|
2322
2483
|
};
|
|
2323
2484
|
}
|
|
2324
2485
|
return {
|
|
2325
2486
|
root: null,
|
|
2326
2487
|
source: "none",
|
|
2327
|
-
checked: { cwd: null, global: globalRepo }
|
|
2488
|
+
checked: { cwd: null, remembered: null, global: globalRepo }
|
|
2328
2489
|
};
|
|
2329
2490
|
}
|
|
2330
2491
|
function getTuiEntryCandidates(runtimeRoot) {
|
|
@@ -2496,7 +2657,7 @@ var PRESETS = [
|
|
|
2496
2657
|
"analytics-worker",
|
|
2497
2658
|
"telegram-worker"
|
|
2498
2659
|
],
|
|
2499
|
-
integrations: ["
|
|
2660
|
+
integrations: ["exchange", "telegram"]
|
|
2500
2661
|
},
|
|
2501
2662
|
{
|
|
2502
2663
|
name: "full",
|
|
@@ -2513,15 +2674,7 @@ var PRESETS = [
|
|
|
2513
2674
|
"web3-wallet-worker",
|
|
2514
2675
|
"pyne-worker"
|
|
2515
2676
|
],
|
|
2516
|
-
integrations: [
|
|
2517
|
-
"binance",
|
|
2518
|
-
"bybit",
|
|
2519
|
-
"mexc",
|
|
2520
|
-
"telegram",
|
|
2521
|
-
"openai",
|
|
2522
|
-
"wallet",
|
|
2523
|
-
"pyne"
|
|
2524
|
-
]
|
|
2677
|
+
integrations: ["exchange", "telegram", "openai", "wallet", "pyne"]
|
|
2525
2678
|
}
|
|
2526
2679
|
];
|
|
2527
2680
|
var WORKER_DEPENDENCIES = {
|
|
@@ -2553,30 +2706,12 @@ function resolveDependencies(selected) {
|
|
|
2553
2706
|
}
|
|
2554
2707
|
var INTEGRATIONS = [
|
|
2555
2708
|
{
|
|
2556
|
-
key: "
|
|
2557
|
-
label: "Binance
|
|
2558
|
-
workerName: "trade-worker",
|
|
2559
|
-
secrets: {
|
|
2560
|
-
BINANCE_KEY_BINDING: "Binance API Key",
|
|
2561
|
-
BINANCE_SECRET_BINDING: "Binance API Secret"
|
|
2562
|
-
}
|
|
2563
|
-
},
|
|
2564
|
-
{
|
|
2565
|
-
key: "mexc",
|
|
2566
|
-
label: "MEXC Exchange",
|
|
2709
|
+
key: "exchange",
|
|
2710
|
+
label: "Exchange API (Binance / Bybit / MEXC)",
|
|
2567
2711
|
workerName: "trade-worker",
|
|
2568
2712
|
secrets: {
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
}
|
|
2572
|
-
},
|
|
2573
|
-
{
|
|
2574
|
-
key: "bybit",
|
|
2575
|
-
label: "Bybit Exchange",
|
|
2576
|
-
workerName: "trade-worker",
|
|
2577
|
-
secrets: {
|
|
2578
|
-
BYBIT_KEY_BINDING: "Bybit API Key",
|
|
2579
|
-
BYBIT_SECRET_BINDING: "Bybit API Secret"
|
|
2713
|
+
EXCHANGE_KEY_BINDING: "Exchange API Key",
|
|
2714
|
+
EXCHANGE_SECRET_BINDING: "Exchange API Secret"
|
|
2580
2715
|
}
|
|
2581
2716
|
},
|
|
2582
2717
|
{
|
|
@@ -2806,7 +2941,11 @@ class WizardEngine {
|
|
|
2806
2941
|
return { ...this.state };
|
|
2807
2942
|
}
|
|
2808
2943
|
getCurrentStep() {
|
|
2809
|
-
|
|
2944
|
+
const step = STEPS.find((s) => s.id === this.state.step) ?? STEPS[0];
|
|
2945
|
+
if (!step) {
|
|
2946
|
+
throw new Error("Wizard has no steps defined");
|
|
2947
|
+
}
|
|
2948
|
+
return step;
|
|
2810
2949
|
}
|
|
2811
2950
|
getCompletedSteps() {
|
|
2812
2951
|
return [...this.state.completedSteps];
|
|
@@ -2829,8 +2968,11 @@ class WizardEngine {
|
|
|
2829
2968
|
this.state.completedSteps.push(this.state.step);
|
|
2830
2969
|
}
|
|
2831
2970
|
const currentIdx = STEP_ORDER.indexOf(this.state.step);
|
|
2832
|
-
if (currentIdx < STEP_ORDER.length - 1) {
|
|
2833
|
-
|
|
2971
|
+
if (currentIdx >= 0 && currentIdx < STEP_ORDER.length - 1) {
|
|
2972
|
+
const nextStep = STEP_ORDER[currentIdx + 1];
|
|
2973
|
+
if (nextStep !== undefined) {
|
|
2974
|
+
this.state.step = nextStep;
|
|
2975
|
+
}
|
|
2834
2976
|
}
|
|
2835
2977
|
this.state.updatedAt = Date.now();
|
|
2836
2978
|
return [];
|
|
@@ -2841,6 +2983,8 @@ class WizardEngine {
|
|
|
2841
2983
|
const currentIdx = STEP_ORDER.indexOf(this.state.step);
|
|
2842
2984
|
if (currentIdx > 0) {
|
|
2843
2985
|
const previousStep = STEP_ORDER[currentIdx - 1];
|
|
2986
|
+
if (previousStep === undefined)
|
|
2987
|
+
return;
|
|
2844
2988
|
this.state.step = previousStep;
|
|
2845
2989
|
this.state.completedSteps = this.state.completedSteps.filter((s) => s !== this.state.step || STEP_ORDER.indexOf(s) <= STEP_ORDER.indexOf(previousStep));
|
|
2846
2990
|
}
|
|
@@ -2998,9 +3142,12 @@ function deriveCalledBy(manifests) {
|
|
|
2998
3142
|
if (!calledBy[workerName])
|
|
2999
3143
|
calledBy[workerName] = new Set;
|
|
3000
3144
|
for (const svc of manifest.services) {
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3145
|
+
let callers = calledBy[svc.service];
|
|
3146
|
+
if (!callers) {
|
|
3147
|
+
callers = new Set;
|
|
3148
|
+
calledBy[svc.service] = callers;
|
|
3149
|
+
}
|
|
3150
|
+
callers.add(workerName);
|
|
3004
3151
|
}
|
|
3005
3152
|
}
|
|
3006
3153
|
const result = {};
|
|
@@ -3013,6 +3160,7 @@ var manifests = {
|
|
|
3013
3160
|
hoox: {
|
|
3014
3161
|
name: "hoox",
|
|
3015
3162
|
path: "workers/hoox-worker",
|
|
3163
|
+
defaultEnabled: true,
|
|
3016
3164
|
vars: {
|
|
3017
3165
|
WEBHOOK_API_KEY_BINDING: {
|
|
3018
3166
|
type: "secret",
|
|
@@ -3022,7 +3170,11 @@ var manifests = {
|
|
|
3022
3170
|
type: "secret",
|
|
3023
3171
|
description: "Inter-worker auth key"
|
|
3024
3172
|
},
|
|
3025
|
-
HA_TOKEN_BINDING: { type: "secret", description: "Home Assistant token" }
|
|
3173
|
+
HA_TOKEN_BINDING: { type: "secret", description: "Home Assistant token" },
|
|
3174
|
+
TELEGRAM_ALLOWED_CHAT_IDS: {
|
|
3175
|
+
type: "secret",
|
|
3176
|
+
description: "Comma-separated chat IDs for webhook notify (fail-closed when unset); alias AUTHORIZED_CHAT_IDS"
|
|
3177
|
+
}
|
|
3026
3178
|
},
|
|
3027
3179
|
services: [
|
|
3028
3180
|
{
|
|
@@ -3048,7 +3200,8 @@ var manifests = {
|
|
|
3048
3200
|
ai: true,
|
|
3049
3201
|
queues: { producer: ["trade-execution"] },
|
|
3050
3202
|
durableObjects: [
|
|
3051
|
-
{ name: "IDEMPOTENCY_STORE", className: "IdempotencyStore" }
|
|
3203
|
+
{ name: "IDEMPOTENCY_STORE", className: "IdempotencyStore" },
|
|
3204
|
+
{ name: "RATE_LIMITER", className: "RateLimiterStore" }
|
|
3052
3205
|
]
|
|
3053
3206
|
},
|
|
3054
3207
|
middleware: [
|
|
@@ -3063,6 +3216,7 @@ var manifests = {
|
|
|
3063
3216
|
"trade-worker": {
|
|
3064
3217
|
name: "trade-worker",
|
|
3065
3218
|
path: "workers/trade-worker",
|
|
3219
|
+
defaultEnabled: true,
|
|
3066
3220
|
vars: {
|
|
3067
3221
|
INTERNAL_KEY_BINDING: {
|
|
3068
3222
|
type: "secret",
|
|
@@ -3072,15 +3226,22 @@ var manifests = {
|
|
|
3072
3226
|
type: "secret",
|
|
3073
3227
|
description: "Telegram outbound auth"
|
|
3074
3228
|
},
|
|
3075
|
-
|
|
3076
|
-
|
|
3229
|
+
EXCHANGE_KEY_BINDING: {
|
|
3230
|
+
type: "secret",
|
|
3231
|
+
description: "Unified exchange API key (Binance / Bybit / MEXC)"
|
|
3232
|
+
},
|
|
3233
|
+
EXCHANGE_SECRET_BINDING: {
|
|
3234
|
+
type: "secret",
|
|
3235
|
+
description: "Unified exchange API secret"
|
|
3236
|
+
},
|
|
3237
|
+
EXCHANGE_TESTNET_KEY_BINDING: {
|
|
3077
3238
|
type: "secret",
|
|
3078
|
-
description: "
|
|
3239
|
+
description: "Optional unified testnet API key"
|
|
3079
3240
|
},
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3241
|
+
EXCHANGE_TESTNET_SECRET_BINDING: {
|
|
3242
|
+
type: "secret",
|
|
3243
|
+
description: "Optional unified testnet API secret"
|
|
3244
|
+
}
|
|
3084
3245
|
},
|
|
3085
3246
|
services: [
|
|
3086
3247
|
{
|
|
@@ -3122,6 +3283,7 @@ var manifests = {
|
|
|
3122
3283
|
"telegram-worker": {
|
|
3123
3284
|
name: "telegram-worker",
|
|
3124
3285
|
path: "workers/telegram-worker",
|
|
3286
|
+
defaultEnabled: true,
|
|
3125
3287
|
vars: {
|
|
3126
3288
|
INTERNAL_KEY_BINDING: {
|
|
3127
3289
|
type: "secret",
|
|
@@ -3135,6 +3297,10 @@ var manifests = {
|
|
|
3135
3297
|
TELEGRAM_SECRET_TOKEN: {
|
|
3136
3298
|
type: "secret",
|
|
3137
3299
|
description: "Webhook verification token"
|
|
3300
|
+
},
|
|
3301
|
+
AUTHORIZED_CHAT_IDS: {
|
|
3302
|
+
type: "secret",
|
|
3303
|
+
description: "Comma-separated chat IDs; inbound webhook fail-closed when unset; outbound /alert allowlist"
|
|
3138
3304
|
}
|
|
3139
3305
|
},
|
|
3140
3306
|
services: [],
|
|
@@ -3155,6 +3321,7 @@ var manifests = {
|
|
|
3155
3321
|
"d1-worker": {
|
|
3156
3322
|
name: "d1-worker",
|
|
3157
3323
|
path: "workers/d1-worker",
|
|
3324
|
+
defaultEnabled: true,
|
|
3158
3325
|
vars: {
|
|
3159
3326
|
INTERNAL_KEY_BINDING: {
|
|
3160
3327
|
type: "secret",
|
|
@@ -3177,6 +3344,7 @@ var manifests = {
|
|
|
3177
3344
|
"web3-wallet-worker": {
|
|
3178
3345
|
name: "web3-wallet-worker",
|
|
3179
3346
|
path: "workers/web3-wallet-worker",
|
|
3347
|
+
defaultEnabled: false,
|
|
3180
3348
|
vars: {
|
|
3181
3349
|
WALLET_PK_SECRET: { type: "secret", description: "Wallet private key" },
|
|
3182
3350
|
WALLET_MNEMONIC_SECRET: {
|
|
@@ -3197,6 +3365,7 @@ var manifests = {
|
|
|
3197
3365
|
"agent-worker": {
|
|
3198
3366
|
name: "agent-worker",
|
|
3199
3367
|
path: "workers/agent-worker",
|
|
3368
|
+
defaultEnabled: true,
|
|
3200
3369
|
vars: {
|
|
3201
3370
|
AGENT_INTERNAL_KEY: { type: "secret", description: "Agent worker auth" },
|
|
3202
3371
|
INTERNAL_KEY_BINDING: {
|
|
@@ -3233,6 +3402,7 @@ var manifests = {
|
|
|
3233
3402
|
"email-worker": {
|
|
3234
3403
|
name: "email-worker",
|
|
3235
3404
|
path: "workers/email-worker",
|
|
3405
|
+
defaultEnabled: false,
|
|
3236
3406
|
vars: {
|
|
3237
3407
|
INTERNAL_KEY_BINDING: {
|
|
3238
3408
|
type: "secret",
|
|
@@ -3270,6 +3440,7 @@ var manifests = {
|
|
|
3270
3440
|
"analytics-worker": {
|
|
3271
3441
|
name: "analytics-worker",
|
|
3272
3442
|
path: "workers/analytics-worker",
|
|
3443
|
+
defaultEnabled: true,
|
|
3273
3444
|
vars: {
|
|
3274
3445
|
CLOUDFLARE_API_TOKEN: {
|
|
3275
3446
|
type: "secret",
|
|
@@ -3289,6 +3460,7 @@ var manifests = {
|
|
|
3289
3460
|
"report-worker": {
|
|
3290
3461
|
name: "report-worker",
|
|
3291
3462
|
path: "workers/report-worker",
|
|
3463
|
+
defaultEnabled: false,
|
|
3292
3464
|
vars: {
|
|
3293
3465
|
CF_API_TOKEN_BINDING: {
|
|
3294
3466
|
type: "secret",
|
|
@@ -3323,6 +3495,7 @@ var manifests = {
|
|
|
3323
3495
|
"pyne-worker": {
|
|
3324
3496
|
name: "pyne-worker",
|
|
3325
3497
|
path: "workers/pyne-worker",
|
|
3498
|
+
defaultEnabled: true,
|
|
3326
3499
|
vars: {
|
|
3327
3500
|
API_KEY: {
|
|
3328
3501
|
type: "secret",
|
|
@@ -3360,6 +3533,7 @@ var manifests = {
|
|
|
3360
3533
|
dashboard: {
|
|
3361
3534
|
name: "dashboard",
|
|
3362
3535
|
path: "workers/dashboard",
|
|
3536
|
+
defaultEnabled: true,
|
|
3363
3537
|
vars: {
|
|
3364
3538
|
DASHBOARD_USER: {
|
|
3365
3539
|
type: "secret",
|
|
@@ -3402,6 +3576,17 @@ var manifests = {
|
|
|
3402
3576
|
var CALLED_BY = deriveCalledBy(manifests);
|
|
3403
3577
|
var WORKER_MANIFESTS = manifests;
|
|
3404
3578
|
var WORKER_NAMES = Object.keys(manifests).sort();
|
|
3579
|
+
var WORKER_CATALOG = WORKER_NAMES.map((name) => {
|
|
3580
|
+
const m = manifests[name];
|
|
3581
|
+
return {
|
|
3582
|
+
name: m.name,
|
|
3583
|
+
path: m.path,
|
|
3584
|
+
defaultEnabled: m.defaultEnabled
|
|
3585
|
+
};
|
|
3586
|
+
});
|
|
3587
|
+
function getWorkerDefaultEnabled(name) {
|
|
3588
|
+
return manifests[name]?.defaultEnabled;
|
|
3589
|
+
}
|
|
3405
3590
|
|
|
3406
3591
|
// src/schemas/validators.ts
|
|
3407
3592
|
import { parse } from "jsonc-parser";
|
|
@@ -3441,7 +3626,7 @@ function validateWranglerJsonc(workerName, manifest, jsoncContent) {
|
|
|
3441
3626
|
}
|
|
3442
3627
|
}
|
|
3443
3628
|
for (const [name, def] of Object.entries(manifest.vars)) {
|
|
3444
|
-
if (def.type === "secret" && !name.endsWith("_BINDING") && !name.endsWith("_SECRET") && !name.startsWith("CLOUDFLARE_") && name !== "TELEGRAM_SECRET_TOKEN" && name !== "MAILGUN_API_KEY" && name !== "EMAIL_SCAN_SUBJECT" && name !== "DASHBOARD_USER" && name !== "DASHBOARD_PASS" && name !== "SESSION_SECRET" && name !== "AGENT_INTERNAL_KEY") {
|
|
3629
|
+
if (def.type === "secret" && !name.endsWith("_BINDING") && !name.endsWith("_SECRET") && !name.startsWith("CLOUDFLARE_") && name !== "TELEGRAM_SECRET_TOKEN" && name !== "TELEGRAM_ALLOWED_CHAT_IDS" && name !== "AUTHORIZED_CHAT_IDS" && name !== "MAILGUN_API_KEY" && name !== "EMAIL_SCAN_SUBJECT" && name !== "DASHBOARD_USER" && name !== "DASHBOARD_PASS" && name !== "SESSION_SECRET" && name !== "AGENT_INTERNAL_KEY") {
|
|
3445
3630
|
errors.push({
|
|
3446
3631
|
worker: workerName,
|
|
3447
3632
|
severity: "warning",
|
|
@@ -3558,6 +3743,8 @@ function generateWranglerJsonc(manifest) {
|
|
|
3558
3743
|
parts.push(` "vars": {`);
|
|
3559
3744
|
for (const [i, name] of varKeys.entries()) {
|
|
3560
3745
|
const def = manifest.vars[name];
|
|
3746
|
+
if (!def)
|
|
3747
|
+
continue;
|
|
3561
3748
|
const comma = i < varKeys.length - 1 ? "," : "";
|
|
3562
3749
|
const val = def.type === "secret" ? "__SECRET__" : JSON.stringify(def.default ?? "");
|
|
3563
3750
|
parts.push(` "${name}": ${val}${comma}`);
|
|
@@ -3677,7 +3864,8 @@ var initialState = {
|
|
|
3677
3864
|
sidebarExpanded: true,
|
|
3678
3865
|
modal: null,
|
|
3679
3866
|
commandPaletteOpen: false,
|
|
3680
|
-
previousView: null
|
|
3867
|
+
previousView: null,
|
|
3868
|
+
statusErrorExpanded: false
|
|
3681
3869
|
};
|
|
3682
3870
|
var useUIStore = create()(immer((set) => ({
|
|
3683
3871
|
...initialState,
|
|
@@ -3708,6 +3896,12 @@ var useUIStore = create()(immer((set) => ({
|
|
|
3708
3896
|
state.activeView = state.previousView;
|
|
3709
3897
|
state.previousView = null;
|
|
3710
3898
|
}
|
|
3899
|
+
}),
|
|
3900
|
+
toggleStatusErrorExpanded: () => set((state) => {
|
|
3901
|
+
state.statusErrorExpanded = !state.statusErrorExpanded;
|
|
3902
|
+
}),
|
|
3903
|
+
setStatusErrorExpanded: (expanded) => set((state) => {
|
|
3904
|
+
state.statusErrorExpanded = expanded;
|
|
3711
3905
|
})
|
|
3712
3906
|
})));
|
|
3713
3907
|
|
|
@@ -3730,6 +3924,23 @@ function ringPush(arr, item, max) {
|
|
|
3730
3924
|
var tradeStreamAbort = null;
|
|
3731
3925
|
var logStreamAbort = null;
|
|
3732
3926
|
var fetchWorkersGeneration = 0;
|
|
3927
|
+
var reconnectTimer = null;
|
|
3928
|
+
function clearReconnectTimer() {
|
|
3929
|
+
if (reconnectTimer !== null) {
|
|
3930
|
+
clearTimeout(reconnectTimer);
|
|
3931
|
+
reconnectTimer = null;
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
function scheduleReconnect(delayMs) {
|
|
3935
|
+
clearReconnectTimer();
|
|
3936
|
+
if (delayMs < 0)
|
|
3937
|
+
return;
|
|
3938
|
+
reconnectTimer = setTimeout(() => {
|
|
3939
|
+
reconnectTimer = null;
|
|
3940
|
+
scheduledReconnectTick();
|
|
3941
|
+
}, delayMs);
|
|
3942
|
+
}
|
|
3943
|
+
var scheduledReconnectTick = async () => {};
|
|
3733
3944
|
var MAX_TRADES = 500;
|
|
3734
3945
|
var MAX_ALERTS = 100;
|
|
3735
3946
|
var MAX_LOGS = 1000;
|
|
@@ -3753,7 +3964,7 @@ function getBackoffDelay(retryCount) {
|
|
|
3753
3964
|
if (retryCount <= 0)
|
|
3754
3965
|
return 0;
|
|
3755
3966
|
const index = Math.min(retryCount - 1, BACKOFF_SEQUENCE.length - 1);
|
|
3756
|
-
return BACKOFF_SEQUENCE[index];
|
|
3967
|
+
return BACKOFF_SEQUENCE[index] ?? 0;
|
|
3757
3968
|
}
|
|
3758
3969
|
var useServiceStore = create2()(immer2((set, get) => ({
|
|
3759
3970
|
...initialState2,
|
|
@@ -3764,6 +3975,7 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
3764
3975
|
const data = await hooxFetch2("/v1/workers");
|
|
3765
3976
|
if (gen !== fetchWorkersGeneration)
|
|
3766
3977
|
return;
|
|
3978
|
+
clearReconnectTimer();
|
|
3767
3979
|
set((state) => {
|
|
3768
3980
|
state.workers = data;
|
|
3769
3981
|
state.lastUpdated = Date.now();
|
|
@@ -3789,6 +4001,7 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
3789
4001
|
timestamp: Date.now(),
|
|
3790
4002
|
acknowledged: false
|
|
3791
4003
|
});
|
|
4004
|
+
let nextDelay = 0;
|
|
3792
4005
|
set((state) => {
|
|
3793
4006
|
state.lastError = msg;
|
|
3794
4007
|
state.disconnectedAt = state.disconnectedAt ?? Date.now();
|
|
@@ -3797,21 +4010,26 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
3797
4010
|
state.retryCount = state.retryCount + 1;
|
|
3798
4011
|
if (state.retryCount >= MAX_RETRIES2) {
|
|
3799
4012
|
state.connectionStatus = "offline";
|
|
3800
|
-
state.lastError =
|
|
4013
|
+
state.lastError = `${msg} (gave up after ${MAX_RETRIES2} retries)`;
|
|
4014
|
+
state.reconnectDelay = 0;
|
|
3801
4015
|
} else {
|
|
3802
4016
|
state.reconnectDelay = getBackoffDelay(state.retryCount);
|
|
4017
|
+
nextDelay = state.reconnectDelay;
|
|
3803
4018
|
}
|
|
3804
4019
|
} else if (state.connectionStatus === "reconnecting") {
|
|
3805
4020
|
state.retryCount = state.retryCount + 1;
|
|
3806
4021
|
if (state.retryCount >= MAX_RETRIES2) {
|
|
3807
4022
|
state.connectionStatus = "offline";
|
|
3808
|
-
state.lastError =
|
|
4023
|
+
state.lastError = `${msg} (gave up after ${MAX_RETRIES2} retries)`;
|
|
3809
4024
|
state.reconnectDelay = 0;
|
|
3810
4025
|
} else {
|
|
3811
4026
|
state.reconnectDelay = getBackoffDelay(state.retryCount);
|
|
4027
|
+
nextDelay = state.reconnectDelay;
|
|
3812
4028
|
}
|
|
3813
4029
|
}
|
|
3814
4030
|
});
|
|
4031
|
+
if (nextDelay > 0)
|
|
4032
|
+
scheduleReconnect(nextDelay);
|
|
3815
4033
|
}
|
|
3816
4034
|
},
|
|
3817
4035
|
streamTrades: async () => {
|
|
@@ -3873,6 +4091,17 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
3873
4091
|
const merged = [...state.alerts, ...alerts];
|
|
3874
4092
|
state.alerts = merged.length > MAX_ALERTS ? merged.slice(merged.length - MAX_ALERTS) : merged;
|
|
3875
4093
|
}),
|
|
4094
|
+
acknowledgeAlert: (id) => set((state) => {
|
|
4095
|
+
const hit = state.alerts.find((a) => a.id === id);
|
|
4096
|
+
if (hit)
|
|
4097
|
+
hit.acknowledged = true;
|
|
4098
|
+
}),
|
|
4099
|
+
dismissAlert: (id) => set((state) => {
|
|
4100
|
+
state.alerts = state.alerts.filter((a) => a.id !== id);
|
|
4101
|
+
}),
|
|
4102
|
+
clearLogs: () => set((state) => {
|
|
4103
|
+
state.logs = [];
|
|
4104
|
+
}),
|
|
3876
4105
|
setConnectionStatus: (status) => set((state) => {
|
|
3877
4106
|
if (status === "connected") {
|
|
3878
4107
|
state.retryCount = 0;
|
|
@@ -3903,49 +4132,69 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
3903
4132
|
state.metrics = metrics;
|
|
3904
4133
|
state.lastUpdated = metrics.lastUpdated;
|
|
3905
4134
|
}),
|
|
3906
|
-
handleConnectionSuccess: () =>
|
|
3907
|
-
|
|
3908
|
-
state
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
state.
|
|
3922
|
-
|
|
3923
|
-
state.
|
|
3924
|
-
|
|
3925
|
-
state.
|
|
3926
|
-
state.
|
|
3927
|
-
state.reconnectDelay
|
|
3928
|
-
} else {
|
|
3929
|
-
state.
|
|
4135
|
+
handleConnectionSuccess: () => {
|
|
4136
|
+
clearReconnectTimer();
|
|
4137
|
+
set((state) => {
|
|
4138
|
+
state.lastSuccessfulFetch = Date.now();
|
|
4139
|
+
state.retryCount = 0;
|
|
4140
|
+
state.lastError = null;
|
|
4141
|
+
state.lastErrorDetails = null;
|
|
4142
|
+
state.reconnectDelay = 0;
|
|
4143
|
+
state.disconnectedAt = null;
|
|
4144
|
+
state.connectionStatus = "connected";
|
|
4145
|
+
});
|
|
4146
|
+
},
|
|
4147
|
+
handleConnectionFailure: (errorMessage) => {
|
|
4148
|
+
let nextDelay = 0;
|
|
4149
|
+
set((state) => {
|
|
4150
|
+
state.lastError = errorMessage;
|
|
4151
|
+
state.disconnectedAt = state.disconnectedAt ?? Date.now();
|
|
4152
|
+
if (state.connectionStatus === "connected" || state.connectionStatus === "polling") {
|
|
4153
|
+
state.connectionStatus = "reconnecting";
|
|
4154
|
+
state.retryCount = 1;
|
|
4155
|
+
state.reconnectDelay = getBackoffDelay(1);
|
|
4156
|
+
nextDelay = state.reconnectDelay;
|
|
4157
|
+
} else if (state.connectionStatus === "reconnecting") {
|
|
4158
|
+
state.retryCount = state.retryCount + 1;
|
|
4159
|
+
if (state.retryCount >= MAX_RETRIES2) {
|
|
4160
|
+
state.connectionStatus = "offline";
|
|
4161
|
+
state.lastError = `${errorMessage} (gave up after ${MAX_RETRIES2} retries)`;
|
|
4162
|
+
state.reconnectDelay = 0;
|
|
4163
|
+
} else {
|
|
4164
|
+
state.reconnectDelay = getBackoffDelay(state.retryCount);
|
|
4165
|
+
nextDelay = state.reconnectDelay;
|
|
4166
|
+
}
|
|
3930
4167
|
}
|
|
3931
|
-
}
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
state
|
|
3938
|
-
state.disconnectedAt = null;
|
|
3939
|
-
}),
|
|
3940
|
-
forceRetry: () => set((state) => {
|
|
3941
|
-
if (state.connectionStatus === "offline") {
|
|
3942
|
-
state.connectionStatus = "polling";
|
|
4168
|
+
});
|
|
4169
|
+
if (nextDelay > 0)
|
|
4170
|
+
scheduleReconnect(nextDelay);
|
|
4171
|
+
},
|
|
4172
|
+
resetRetries: () => {
|
|
4173
|
+
clearReconnectTimer();
|
|
4174
|
+
set((state) => {
|
|
3943
4175
|
state.retryCount = 0;
|
|
3944
4176
|
state.lastError = null;
|
|
3945
4177
|
state.lastErrorDetails = null;
|
|
3946
4178
|
state.reconnectDelay = 0;
|
|
4179
|
+
state.disconnectedAt = null;
|
|
4180
|
+
});
|
|
4181
|
+
},
|
|
4182
|
+
forceRetry: () => {
|
|
4183
|
+
clearReconnectTimer();
|
|
4184
|
+
const wasOffline = get().connectionStatus === "offline";
|
|
4185
|
+
set((state) => {
|
|
4186
|
+
if (state.connectionStatus === "offline") {
|
|
4187
|
+
state.connectionStatus = "polling";
|
|
4188
|
+
state.retryCount = 0;
|
|
4189
|
+
state.lastError = null;
|
|
4190
|
+
state.lastErrorDetails = null;
|
|
4191
|
+
state.reconnectDelay = 0;
|
|
4192
|
+
}
|
|
4193
|
+
});
|
|
4194
|
+
if (wasOffline) {
|
|
4195
|
+
scheduleReconnect(0);
|
|
3947
4196
|
}
|
|
3948
|
-
}
|
|
4197
|
+
},
|
|
3949
4198
|
setLastErrorDetails: (details) => set((state) => {
|
|
3950
4199
|
state.lastErrorDetails = details;
|
|
3951
4200
|
state.lastError = details === null ? null : summarizeCliError(details);
|
|
@@ -3968,38 +4217,73 @@ var useServiceStore = create2()(immer2((set, get) => ({
|
|
|
3968
4217
|
}, MAX_ALERTS);
|
|
3969
4218
|
})
|
|
3970
4219
|
})));
|
|
4220
|
+
scheduledReconnectTick = async () => {
|
|
4221
|
+
const status = useServiceStore.getState().connectionStatus;
|
|
4222
|
+
if (status === "reconnecting" || status === "polling") {
|
|
4223
|
+
await useServiceStore.getState().fetchWorkers();
|
|
4224
|
+
}
|
|
4225
|
+
};
|
|
3971
4226
|
|
|
3972
4227
|
// src/stores/config-store.ts
|
|
3973
4228
|
import { create as create3 } from "zustand";
|
|
3974
4229
|
import { immer as immer3 } from "zustand/middleware/immer";
|
|
3975
4230
|
import { persist, createJSONStorage } from "zustand/middleware";
|
|
3976
|
-
import { chmodSync as
|
|
3977
|
-
import { homedir as homedir3 } from "os";
|
|
4231
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync3, existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
3978
4232
|
import { join as join4 } from "path";
|
|
4233
|
+
function getTuiPreferencesPath() {
|
|
4234
|
+
return join4(getHooxHome(), ".tui-state", "preferences.json");
|
|
4235
|
+
}
|
|
4236
|
+
function getLegacyConfigJsonPath() {
|
|
4237
|
+
return join4(getHooxHome(), "config.json");
|
|
4238
|
+
}
|
|
4239
|
+
function extractZustandPrefsPayload(raw) {
|
|
4240
|
+
try {
|
|
4241
|
+
const parsed = JSON.parse(raw);
|
|
4242
|
+
if (parsed && typeof parsed === "object" && parsed.state && typeof parsed.state === "object" && !Array.isArray(parsed.state) && (("theme" in parsed.state) || ("refreshIntervalMs" in parsed.state)) && !("apiToken" in parsed)) {
|
|
4243
|
+
return raw;
|
|
4244
|
+
}
|
|
4245
|
+
} catch {}
|
|
4246
|
+
return null;
|
|
4247
|
+
}
|
|
3979
4248
|
var bunConfigStorage = {
|
|
3980
4249
|
getItem: async (_name) => {
|
|
3981
4250
|
try {
|
|
3982
|
-
const
|
|
3983
|
-
const
|
|
3984
|
-
if (
|
|
3985
|
-
return
|
|
3986
|
-
|
|
4251
|
+
const prefsPath = getTuiPreferencesPath();
|
|
4252
|
+
const prefsFile = Bun.file(prefsPath);
|
|
4253
|
+
if (await prefsFile.exists()) {
|
|
4254
|
+
return await prefsFile.text();
|
|
4255
|
+
}
|
|
4256
|
+
const legacyPath = getLegacyConfigJsonPath();
|
|
4257
|
+
if (existsSync4(legacyPath)) {
|
|
4258
|
+
const legacyRaw = readFileSync4(legacyPath, "utf-8");
|
|
4259
|
+
const envelope = extractZustandPrefsPayload(legacyRaw);
|
|
4260
|
+
if (envelope) {
|
|
4261
|
+
await bunConfigStorage.setItem(_name, envelope);
|
|
4262
|
+
return envelope;
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
return null;
|
|
3987
4266
|
} catch {
|
|
3988
4267
|
return null;
|
|
3989
4268
|
}
|
|
3990
4269
|
},
|
|
3991
4270
|
setItem: async (_name, value) => {
|
|
3992
|
-
const
|
|
3993
|
-
|
|
3994
|
-
|
|
4271
|
+
const home = getHooxHome();
|
|
4272
|
+
const stateDir = join4(home, ".tui-state");
|
|
4273
|
+
if (!existsSync4(home)) {
|
|
4274
|
+
mkdirSync3(home, { recursive: true, mode: HOOX_DIR_MODE });
|
|
4275
|
+
}
|
|
4276
|
+
if (!existsSync4(stateDir)) {
|
|
4277
|
+
mkdirSync3(stateDir, { recursive: true, mode: HOOX_DIR_MODE });
|
|
3995
4278
|
}
|
|
3996
4279
|
try {
|
|
3997
|
-
|
|
4280
|
+
chmodSync3(home, HOOX_DIR_MODE);
|
|
4281
|
+
chmodSync3(stateDir, HOOX_DIR_MODE);
|
|
3998
4282
|
} catch {}
|
|
3999
|
-
const filePath =
|
|
4283
|
+
const filePath = getTuiPreferencesPath();
|
|
4000
4284
|
await Bun.write(filePath, value);
|
|
4001
4285
|
try {
|
|
4002
|
-
|
|
4286
|
+
chmodSync3(filePath, HOOX_CONFIG_FILE_MODE);
|
|
4003
4287
|
} catch {}
|
|
4004
4288
|
},
|
|
4005
4289
|
removeItem: async (_name) => {}
|
|
@@ -4071,7 +4355,7 @@ var useConfigStore = create3()(persist(immer3((set) => ({
|
|
|
4071
4355
|
state.notifications[channel] = !state.notifications[channel];
|
|
4072
4356
|
})
|
|
4073
4357
|
})), {
|
|
4074
|
-
name: "hoox-
|
|
4358
|
+
name: "hoox-tui-prefs",
|
|
4075
4359
|
storage: createJSONStorage(() => bunConfigStorage),
|
|
4076
4360
|
partialize: (state) => ({
|
|
4077
4361
|
theme: state.theme,
|
|
@@ -4120,6 +4404,7 @@ export {
|
|
|
4120
4404
|
resolveHooxRuntimeRoot,
|
|
4121
4405
|
resolveHooxPath,
|
|
4122
4406
|
resolveDependencies,
|
|
4407
|
+
rememberMonorepoRoot,
|
|
4123
4408
|
readConfigSync,
|
|
4124
4409
|
readConfig,
|
|
4125
4410
|
parseDashboardManifest,
|
|
@@ -4128,7 +4413,10 @@ export {
|
|
|
4128
4413
|
logKvTimestamp,
|
|
4129
4414
|
loadDashboardManifestsFromRoot,
|
|
4130
4415
|
loadDashboardKvManifestFromRoot,
|
|
4416
|
+
kvPutMany,
|
|
4131
4417
|
kvManifestFromDashboardManifests,
|
|
4418
|
+
kvGetManyAsRecord,
|
|
4419
|
+
kvGetMany,
|
|
4132
4420
|
isWithinHooxHome,
|
|
4133
4421
|
isUnixModeGroupOrWorldAccessible,
|
|
4134
4422
|
isTruthyKillSwitchFlag,
|
|
@@ -4143,7 +4431,10 @@ export {
|
|
|
4143
4431
|
healthCheck,
|
|
4144
4432
|
headersToObject,
|
|
4145
4433
|
hasOperatorClientCredentials,
|
|
4434
|
+
getWorkerDefaultEnabled,
|
|
4146
4435
|
getTuiEntryCandidates,
|
|
4436
|
+
getRememberedMonorepoRoot,
|
|
4437
|
+
getRememberedMonorepoPath,
|
|
4147
4438
|
getRelativeHooxPath,
|
|
4148
4439
|
getHooxWranglerPath,
|
|
4149
4440
|
getHooxStatePath,
|
|
@@ -4203,6 +4494,7 @@ export {
|
|
|
4203
4494
|
WORKER_NAMES,
|
|
4204
4495
|
WORKER_MANIFESTS,
|
|
4205
4496
|
WORKER_DEPENDENCIES,
|
|
4497
|
+
WORKER_CATALOG,
|
|
4206
4498
|
WIZARD_STATE_PATH,
|
|
4207
4499
|
WALLET_EXECUTE_AUTH_KEY_FIELDS,
|
|
4208
4500
|
TradeSignalSchema,
|
|
@@ -4217,6 +4509,7 @@ export {
|
|
|
4217
4509
|
PositionSchema,
|
|
4218
4510
|
PRESETS,
|
|
4219
4511
|
LogLevelColor,
|
|
4512
|
+
KV_BULK_GET_MAX_KEYS,
|
|
4220
4513
|
KVKeys,
|
|
4221
4514
|
KILL_SWITCH_KEYS,
|
|
4222
4515
|
KILL_SWITCH_ACTIVE_PREFIX,
|