@hyperflow.fun/ghost 0.0.3 → 0.0.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.
Files changed (39) hide show
  1. package/README.md +4 -20
  2. package/dist/index.js +1328 -716
  3. package/dist/package.json +1 -1
  4. package/dist/skills/builtin/ask-user-questions/SKILL.md +48 -0
  5. package/dist/skills/builtin/event-judge/SKILL.md +52 -7
  6. package/dist/skills/builtin/trade-executor/SKILL.md +1 -1
  7. package/dist/web/dist/assets/{Chart-BOyhyITw.js → Chart-cnNd398J.js} +1 -1
  8. package/dist/web/dist/assets/{Config-CQyc8yqV.js → Config-Bui5kcW0.js} +1 -1
  9. package/dist/web/dist/assets/{Cost-BoAvnlDi.js → Cost-BDSIn5ub.js} +1 -1
  10. package/dist/web/dist/assets/{Cron-5mIZVsft.js → Cron-C7gaZhMu.js} +1 -1
  11. package/dist/web/dist/assets/{Dashboard-BRxSDtCD.js → Dashboard-qwYsK3Gi.js} +1 -1
  12. package/dist/web/dist/assets/{Logs-BpXqElSx.js → Logs-DyfS6kEe.js} +1 -1
  13. package/dist/web/dist/assets/{Memory-CcgSgCqJ.js → Memory-BPMmbFbx.js} +1 -1
  14. package/dist/web/dist/assets/SVN-RethinkSans-Bold-BIisIh3U.otf +0 -0
  15. package/dist/web/dist/assets/SVN-RethinkSans-Italic-8Q-lLPN5.otf +0 -0
  16. package/dist/web/dist/assets/SVN-RethinkSans-Medium-yLh4RBRv.otf +0 -0
  17. package/dist/web/dist/assets/SVN-RethinkSans-Regular-CVuIF6ni.otf +0 -0
  18. package/dist/web/dist/assets/{Sessions-BEIP55fW.js → Sessions-nANl57gZ.js} +1 -1
  19. package/dist/web/dist/assets/{Skills-4s4LDpzh.js → Skills-BPXH_m1K.js} +1 -1
  20. package/dist/web/dist/assets/{Tools-C-NcyONi.js → Tools-CypplonI.js} +1 -1
  21. package/dist/web/dist/assets/{activity-DJy9m_N9.js → activity-BLl_txfw.js} +1 -1
  22. package/dist/web/dist/assets/{clock-CFVWqojO.js → clock-CMLb2b5z.js} +1 -1
  23. package/dist/web/dist/assets/empty-notifications-C88Hm9lC.svg +62 -0
  24. package/dist/web/dist/assets/empty-token-search-aBFBwedz.svg +5 -0
  25. package/dist/web/dist/assets/{highlighted-body-OFNGDK62-y5VVuvTf.js → highlighted-body-OFNGDK62-CKxBaGN5.js} +1 -1
  26. package/dist/web/dist/assets/index-CKAK_IGv.js +59 -0
  27. package/dist/web/dist/assets/index-PX2emSv9.css +1 -0
  28. package/dist/web/dist/assets/{mermaid-GHXKKRXX-BvdvTI5r.js → mermaid-GHXKKRXX-BuHBmaw6.js} +3 -3
  29. package/dist/web/dist/assets/{refresh-cw-DlbQ5OE4.js → refresh-cw-DHCgoaOK.js} +1 -1
  30. package/dist/web/dist/assets/welcome-globe-analytics-sF4OWku9.svg +201 -0
  31. package/dist/web/dist/assets/welcome-globe-bars-COA7MJUP.svg +305 -0
  32. package/dist/web/dist/assets/welcome-news-card-BfM47ZZ3.svg +59 -0
  33. package/dist/web/dist/index.html +2 -2
  34. package/package.json +1 -1
  35. package/dist/web/dist/assets/index-B1ED3P_d.css +0 -1
  36. package/dist/web/dist/assets/index-C6QRLmO2.js +0 -54
  37. package/dist/web/dist/assets/rethink-sans-latin-ext-CuoiHPIp.woff2 +0 -0
  38. package/dist/web/dist/assets/rethink-sans-latin-v3CgWhBT.woff2 +0 -0
  39. package/dist/web/dist/assets/welcome-illustration-B8_SJ7In.svg +0 -319
package/dist/index.js CHANGED
@@ -4094,7 +4094,6 @@ var init_schema = __esm(() => {
4094
4094
  });
4095
4095
  cronSchema = exports_external.object({
4096
4096
  enableScheduler: exports_external.boolean().default(true),
4097
- timezone: exports_external.string().default("UTC"),
4098
4097
  maxConcurrentJobs: exports_external.coerce.number().int().positive().default(5)
4099
4098
  });
4100
4099
  telegramChannelSchema = exports_external.object({
@@ -4360,7 +4359,6 @@ __export(exports_paths, {
4360
4359
  getEvalConfigPath: () => getEvalConfigPath,
4361
4360
  getDbPath: () => getDbPath,
4362
4361
  getDaemonPidPath: () => getDaemonPidPath,
4363
- getCronStorePath: () => getCronStorePath,
4364
4362
  getCredentialsPath: () => getCredentialsPath,
4365
4363
  getConfigPath: () => getConfigPath,
4366
4364
  getCliWorkspacePath: () => getCliWorkspacePath,
@@ -4405,9 +4403,6 @@ function getModelsConfigPath() {
4405
4403
  function getEvalConfigPath() {
4406
4404
  return join(getGhostDir(), "eval.json");
4407
4405
  }
4408
- function getCronStorePath() {
4409
- return join(getWorkspaceDir(), "cron", "jobs.json");
4410
- }
4411
4406
  function getCliWorkspacePath() {
4412
4407
  return join(getGhostDir(), "cli-workspace");
4413
4408
  }
@@ -4513,7 +4508,6 @@ __export(exports_config, {
4513
4508
  getEvalConfigPath: () => getEvalConfigPath,
4514
4509
  getDbPath: () => getDbPath,
4515
4510
  getDaemonPidPath: () => getDaemonPidPath,
4516
- getCronStorePath: () => getCronStorePath,
4517
4511
  getCredentialsPath: () => getCredentialsPath,
4518
4512
  getConfigPath: () => getConfigPath,
4519
4513
  getCliWorkspacePath: () => getCliWorkspacePath,
@@ -151269,6 +151263,34 @@ function formatOrder(order) {
151269
151263
  return `${order.symbol} ${formatSide(order.side)} ${order.type} ${order.size}${priceStr}${triggerStr}`;
151270
151264
  }
151271
151265
 
151266
+ // src/services/wizard-data.ts
151267
+ function composeOpenPositionWizard(input) {
151268
+ const symbol5 = input.symbol?.toUpperCase();
151269
+ if (!symbol5)
151270
+ return;
151271
+ if (typeof input.size !== "number" || !Number.isFinite(input.size))
151272
+ return;
151273
+ const side = input.side === "buy" || input.side === "long" ? "long" : "short";
151274
+ const leverage = input.leverage && input.leverage > 0 ? input.leverage : 1;
151275
+ const orderType = input.orderType === "limit" ? "limit" : "market";
151276
+ const hasEntry = typeof input.entryPrice === "number" && Number.isFinite(input.entryPrice) && input.entryPrice > 0;
151277
+ const entryPrice = hasEntry ? input.entryPrice : undefined;
151278
+ return {
151279
+ kind: "open_position",
151280
+ symbol: symbol5,
151281
+ side,
151282
+ leverage,
151283
+ size: input.size,
151284
+ orderType,
151285
+ entryPrice,
151286
+ stopLoss: input.stopLoss,
151287
+ takeProfit: input.takeProfit
151288
+ };
151289
+ }
151290
+ function composeGenericWizard(groups) {
151291
+ return { kind: "generic", groups };
151292
+ }
151293
+
151272
151294
  // src/services/confirm-policy.ts
151273
151295
  function isConfirmable(toolName) {
151274
151296
  return CONFIRMABLE_TOOLS.has(toolName);
@@ -151313,15 +151335,25 @@ function describePlaceOrder(params) {
151313
151335
  const sizeStr = size !== undefined ? `${size}` : "";
151314
151336
  const levSuffix = leverage && leverage > 0 ? ` ${leverage}x` : "";
151315
151337
  const sideRow = leverage && leverage > 0 ? `Side: ${side} ${leverage}x` : `Side: ${side}`;
151338
+ const wizard = composeOpenPositionWizard({
151339
+ symbol: getString(params, "symbol") ?? "",
151340
+ side: getString(params, "side") ?? "buy",
151341
+ size: getNumber(params, "size") ?? 0,
151342
+ leverage: getNumber(params, "leverage"),
151343
+ orderType: getString(params, "orderType"),
151344
+ entryPrice: price
151345
+ });
151316
151346
  if (orderType === "limit" && price !== undefined) {
151317
151347
  return {
151318
151348
  title: `Place limit order: ${side} ${sizeStr} ${symbol5} @ ${formatUsd(price)}?`,
151319
- bullets: [sideRow]
151349
+ bullets: [sideRow],
151350
+ wizard
151320
151351
  };
151321
151352
  }
151322
151353
  return {
151323
151354
  title: `Place market order: ${side} ${sizeStr} ${symbol5}?`,
151324
- bullets: [sideRow]
151355
+ bullets: [sideRow],
151356
+ wizard
151325
151357
  };
151326
151358
  }
151327
151359
  function describeBracketOrder(params) {
@@ -151329,8 +151361,9 @@ function describeBracketOrder(params) {
151329
151361
  const side = sideLabel(getString(params, "side"));
151330
151362
  const leverage = getNumber(params, "leverage");
151331
151363
  const size = getNumber(params, "size");
151332
- const orderType = getString(params, "orderType")?.toLowerCase();
151364
+ const rawOrderType = getString(params, "orderType")?.toLowerCase();
151333
151365
  const entryPrice = getNumber(params, "entryPrice") ?? getNumber(params, "price");
151366
+ const orderType = rawOrderType ?? (entryPrice !== undefined ? "limit" : "market");
151334
151367
  const stopLoss = getNumber(params, "stopLoss");
151335
151368
  const takeProfit = getNumber(params, "takeProfit");
151336
151369
  const sizeStr = size !== undefined ? `${size}` : "";
@@ -151343,7 +151376,17 @@ function describeBracketOrder(params) {
151343
151376
  bullets.push(`SL: ${formatUsdCompact(stopLoss)}`);
151344
151377
  if (takeProfit !== undefined)
151345
151378
  bullets.push(`TP: ${formatUsdCompact(takeProfit)}`);
151346
- return { title, bullets };
151379
+ const wizard = composeOpenPositionWizard({
151380
+ symbol: getString(params, "symbol") ?? "",
151381
+ side: getString(params, "side") ?? "buy",
151382
+ size: getNumber(params, "size") ?? 0,
151383
+ leverage: getNumber(params, "leverage"),
151384
+ orderType,
151385
+ entryPrice,
151386
+ stopLoss: getNumber(params, "stopLoss"),
151387
+ takeProfit: getNumber(params, "takeProfit")
151388
+ });
151389
+ return { title, bullets, wizard };
151347
151390
  }
151348
151391
  function describeSetSlTp(params) {
151349
151392
  const symbol5 = upper(getString(params, "symbol"));
@@ -151414,14 +151457,26 @@ function describePartialClose(params) {
151414
151457
  const symbol5 = upper(getString(params, "symbol"));
151415
151458
  const pct = getNumber(params, "percentage");
151416
151459
  const size = getNumber(params, "size");
151460
+ const sizeBefore = getNumber(params, "sizeBefore");
151461
+ const rows = [];
151462
+ if (pct !== undefined)
151463
+ rows.push({ label: "Close %", value: `${pct}%` });
151464
+ if (size !== undefined)
151465
+ rows.push({ label: "Size", value: `${size} ${symbol5}` });
151466
+ if (sizeBefore !== undefined && pct !== undefined) {
151467
+ const sizeAfter = sizeBefore * (1 - pct / 100);
151468
+ rows.push({ label: "Size before", value: `${sizeBefore}`, tone: "muted" });
151469
+ rows.push({ label: "Size after", value: `${sizeAfter}` });
151470
+ }
151471
+ const wizard = rows.length > 0 ? composeGenericWizard([{ label: `${symbol5} partial close`, rows }]) : undefined;
151417
151472
  if (pct !== undefined) {
151418
151473
  const pctStr = Number.isInteger(pct) ? `${pct}` : pct.toFixed(0);
151419
- return { title: `Close ${pctStr}% of ${symbol5} position?`, bullets: [] };
151474
+ return { title: `Close ${pctStr}% of ${symbol5} position?`, bullets: [], wizard };
151420
151475
  }
151421
151476
  if (size !== undefined) {
151422
- return { title: `Close ${size} ${symbol5} position?`, bullets: [] };
151477
+ return { title: `Close ${size} ${symbol5} position?`, bullets: [], wizard };
151423
151478
  }
151424
- return { title: `Close part of ${symbol5} position?`, bullets: [] };
151479
+ return { title: `Close part of ${symbol5} position?`, bullets: [], wizard };
151425
151480
  }
151426
151481
  function describeAdjustMargin(params) {
151427
151482
  const symbol5 = upper(getString(params, "symbol"));
@@ -151429,17 +151484,26 @@ function describeAdjustMargin(params) {
151429
151484
  if (amount === undefined) {
151430
151485
  return { title: `Adjust margin on ${symbol5}?`, bullets: [] };
151431
151486
  }
151487
+ const sign = amount >= 0 ? "+" : "\u2212";
151488
+ const rows = [
151489
+ {
151490
+ label: "Margin change",
151491
+ value: `${sign}${formatUsd(Math.abs(amount))}`,
151492
+ tone: amount >= 0 ? "reward" : "risk"
151493
+ }
151494
+ ];
151495
+ const wizard = composeGenericWizard([{ label: `${symbol5} margin adjust`, rows }]);
151432
151496
  if (amount >= 0) {
151433
- return { title: `Add ${formatUsd(amount)} margin to ${symbol5}?`, bullets: [] };
151497
+ return { title: `Add ${formatUsd(amount)} margin to ${symbol5}?`, bullets: [], wizard };
151434
151498
  }
151435
- return { title: `Reduce ${formatUsd(Math.abs(amount))} margin on ${symbol5}?`, bullets: [] };
151499
+ return { title: `Reduce ${formatUsd(Math.abs(amount))} margin on ${symbol5}?`, bullets: [], wizard };
151436
151500
  }
151437
151501
  function describeConfirm(toolName, params) {
151438
151502
  const safeParams = params && typeof params === "object" ? params : {};
151439
151503
  const describer = CONFIRM_DESCRIBERS[toolName];
151440
151504
  if (describer)
151441
151505
  return describer(safeParams);
151442
- return { title: `Confirm ${toolName}?`, bullets: [] };
151506
+ return { title: `Run ${toolName}?`, bullets: [] };
151443
151507
  }
151444
151508
  var CONFIRMABLE_TOOLS, CONFIRM_DESCRIBERS;
151445
151509
  var init_confirm_policy = __esm(() => {
@@ -151487,7 +151551,7 @@ function createGhostSdkMcpServer(deps) {
151487
151551
  }
151488
151552
  if (decision.decision === "rejected") {
151489
151553
  const reasonMsg = decision.reason && decision.reason.length > 0 ? `User declined. Reason: ${decision.reason}` : "User declined. Do not retry.";
151490
- logger.debug({ tool: agentTool.name, reason: decision.reason }, "tool rejected by user");
151554
+ logger.debug({ tool: agentTool.name, reason: decision.reason }, "tool not approved");
151491
151555
  return { content: [{ type: "text", text: reasonMsg }], isError: true };
151492
151556
  }
151493
151557
  }
@@ -151532,7 +151596,36 @@ var init_constants2 = __esm(() => {
151532
151596
  "read_file",
151533
151597
  "list_dir",
151534
151598
  "web_fetch",
151535
- "web_search"
151599
+ "web_search",
151600
+ "ghost_chat_history",
151601
+ "ghost_get_trade_history",
151602
+ "ghost_get_balance",
151603
+ "ghost_get_positions",
151604
+ "ghost_get_orders",
151605
+ "ghost_list_wallets",
151606
+ "ghost_get_recent_orders",
151607
+ "ghost_get_price",
151608
+ "ghost_get_funding_rates",
151609
+ "ghost_get_orderbook",
151610
+ "ghost_get_klines",
151611
+ "ghost_get_indicators",
151612
+ "ghost_get_levels",
151613
+ "ghost_market_overview",
151614
+ "ghost_pre_trade_check",
151615
+ "ghost_morning_briefing",
151616
+ "ghost_session_info",
151617
+ "ghost_timing_risk",
151618
+ "ghost_cross_exchange_funding",
151619
+ "ghost_liquidation_map",
151620
+ "ghost_get_whale_activity",
151621
+ "ghost_tweets_search",
151622
+ "ghost_news_sources",
151623
+ "ghost_news_search",
151624
+ "ghost_news_discover_rss",
151625
+ "ghost_watchlist_list",
151626
+ "ghost_alert_list",
151627
+ "ghost_alert_history",
151628
+ "ghost_check_alerts"
151536
151629
  ]);
151537
151630
  });
151538
151631
 
@@ -156179,14 +156272,6 @@ function createClaudeCliModel(modelId) {
156179
156272
  maxTokens: known?.maxTokens ?? DEFAULT_SPECS.maxTokens
156180
156273
  };
156181
156274
  }
156182
- function getClaudeCliModels() {
156183
- return [
156184
- { id: "claude-opus-4-7", name: "Claude Opus 4.7" },
156185
- { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
156186
- { id: "claude-haiku-4-5", name: "Claude Haiku 4.5" },
156187
- { id: "claude-opus-4-6", name: "Claude Opus 4.6" }
156188
- ];
156189
- }
156190
156275
  var KNOWN_MODELS, DEFAULT_SPECS;
156191
156276
  var init_models6 = __esm(() => {
156192
156277
  KNOWN_MODELS = {
@@ -156485,7 +156570,7 @@ function assertValidVersions(sorted) {
156485
156570
  }
156486
156571
  }
156487
156572
  }
156488
- var baselineDbMigration, proactiveCooldownsMigration, dropProactiveCooldownsMigration, alertsKindPayloadMigration, addSettingsKvMigration, addTweetsAiRelevantMigration, addObserverStateMigration, splitAlertsMigration, addXFollowsEnabledSourceMigration, DB_MIGRATIONS, CONFIG_MIGRATIONS;
156573
+ var baselineDbMigration, proactiveCooldownsMigration, dropProactiveCooldownsMigration, alertsKindPayloadMigration, addSettingsKvMigration, addTweetsAiRelevantMigration, addObserverStateMigration, splitAlertsMigration, addXFollowsEnabledSourceMigration, addCronJobsMigration, DB_MIGRATIONS, CONFIG_MIGRATIONS;
156489
156574
  var init_registry2 = __esm(() => {
156490
156575
  baselineDbMigration = {
156491
156576
  version: 1,
@@ -156618,6 +156703,42 @@ var init_registry2 = __esm(() => {
156618
156703
  db2.run(`UPDATE x_follows SET user_disabled = 0 WHERE user_disabled IS NULL`);
156619
156704
  }
156620
156705
  };
156706
+ addCronJobsMigration = {
156707
+ version: 10,
156708
+ label: "add_cron_jobs",
156709
+ up: (db2) => {
156710
+ db2.run(`
156711
+ CREATE TABLE IF NOT EXISTS cron_jobs (
156712
+ id TEXT PRIMARY KEY,
156713
+ name TEXT NOT NULL,
156714
+ enabled INTEGER NOT NULL DEFAULT 1,
156715
+ schedule_kind TEXT NOT NULL,
156716
+ schedule_at_ms INTEGER,
156717
+ schedule_every_ms INTEGER,
156718
+ schedule_expr TEXT,
156719
+ schedule_tz TEXT,
156720
+ payload_kind TEXT NOT NULL DEFAULT 'agent_turn',
156721
+ payload_message TEXT NOT NULL,
156722
+ payload_deliver INTEGER NOT NULL DEFAULT 1,
156723
+ payload_channel TEXT,
156724
+ payload_to TEXT,
156725
+ next_run_at_ms INTEGER,
156726
+ last_run_at_ms INTEGER,
156727
+ last_status TEXT,
156728
+ last_error TEXT,
156729
+ run_history TEXT NOT NULL DEFAULT '[]',
156730
+ created_at_ms INTEGER NOT NULL,
156731
+ updated_at_ms INTEGER NOT NULL,
156732
+ delete_after_run INTEGER NOT NULL DEFAULT 0
156733
+ );
156734
+ `);
156735
+ db2.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_cron_jobs_name ON cron_jobs(name);`);
156736
+ db2.run(`
156737
+ CREATE INDEX IF NOT EXISTS idx_cron_jobs_enabled_next
156738
+ ON cron_jobs(enabled, next_run_at_ms) WHERE enabled = 1;
156739
+ `);
156740
+ }
156741
+ };
156621
156742
  DB_MIGRATIONS = [
156622
156743
  baselineDbMigration,
156623
156744
  proactiveCooldownsMigration,
@@ -156627,7 +156748,8 @@ var init_registry2 = __esm(() => {
156627
156748
  addTweetsAiRelevantMigration,
156628
156749
  addObserverStateMigration,
156629
156750
  splitAlertsMigration,
156630
- addXFollowsEnabledSourceMigration
156751
+ addXFollowsEnabledSourceMigration,
156752
+ addCronJobsMigration
156631
156753
  ];
156632
156754
  CONFIG_MIGRATIONS = [];
156633
156755
  });
@@ -157957,6 +158079,9 @@ class ToolRegistry {
157957
158079
  all() {
157958
158080
  return [...this.tools.values()];
157959
158081
  }
158082
+ taskAgentTools() {
158083
+ return [...this.tools.values()].filter((t2) => READ_TOOLS.has(t2.name) || TASK_AGENT_EXTRAS.has(t2.name));
158084
+ }
157960
158085
  names() {
157961
158086
  return [...this.tools.keys()];
157962
158087
  }
@@ -158003,7 +158128,11 @@ class ToolRegistry {
158003
158128
  return { ...result, content: truncated };
158004
158129
  }
158005
158130
  }
158006
- var MAX_RESULT_CHARS = 16000;
158131
+ var MAX_RESULT_CHARS = 16000, TASK_AGENT_EXTRAS;
158132
+ var init_registry3 = __esm(() => {
158133
+ init_constants2();
158134
+ TASK_AGENT_EXTRAS = new Set(["save_memory", "cron"]);
158135
+ });
158007
158136
 
158008
158137
  // src/tools/read-file.ts
158009
158138
  import { readFileSync as readFileSync11, statSync as statSync2 } from "fs";
@@ -158630,7 +158759,7 @@ var init_web_fetch = __esm(() => {
158630
158759
  // src/tools/cron.ts
158631
158760
  class CronTool {
158632
158761
  service;
158633
- defaultTz;
158762
+ tzService;
158634
158763
  name = "cron";
158635
158764
  label = "Cron";
158636
158765
  description = "Schedule reminders and recurring tasks. Actions: add, list, remove.";
@@ -158638,9 +158767,9 @@ class CronTool {
158638
158767
  _channel = null;
158639
158768
  _chatId = null;
158640
158769
  _inCron = false;
158641
- constructor(service, defaultTz = "UTC") {
158770
+ constructor(service, tzService) {
158642
158771
  this.service = service;
158643
- this.defaultTz = defaultTz;
158772
+ this.tzService = tzService;
158644
158773
  }
158645
158774
  setOrigin(channel, chatId) {
158646
158775
  this._channel = channel || null;
@@ -158674,7 +158803,7 @@ class CronTool {
158674
158803
  if (params.every_seconds) {
158675
158804
  schedule = { kind: "every", everyMs: params.every_seconds * 1000 };
158676
158805
  } else if (params.cron_expr) {
158677
- schedule = { kind: "cron", expr: params.cron_expr, tz: params.tz ?? this.defaultTz };
158806
+ schedule = { kind: "cron", expr: params.cron_expr, tz: params.tz ?? this.tzService.get() };
158678
158807
  } else if (params.at) {
158679
158808
  const atMs = new Date(params.at).getTime();
158680
158809
  if (isNaN(atMs))
@@ -158791,11 +158920,12 @@ function createToolRegistry(_security, options) {
158791
158920
  reg(new ExecTool);
158792
158921
  reg(new WebSearchTool(options.webSearchConfig));
158793
158922
  reg(new WebFetchTool);
158794
- reg(new CronTool(options.cronService, options.defaultTimezone));
158923
+ reg(new CronTool(options.cronService, options.timezoneService));
158795
158924
  reg(new SaveMemoryTool(options.memoryStore));
158796
158925
  return registry4;
158797
158926
  }
158798
158927
  var init_tools = __esm(() => {
158928
+ init_registry3();
158799
158929
  init_read_file();
158800
158930
  init_write_file();
158801
158931
  init_edit_file();
@@ -158805,6 +158935,7 @@ var init_tools = __esm(() => {
158805
158935
  init_web_fetch();
158806
158936
  init_cron();
158807
158937
  init_save_memory();
158938
+ init_registry3();
158808
158939
  init_read_file();
158809
158940
  init_write_file();
158810
158941
  init_edit_file();
@@ -159684,7 +159815,7 @@ class ContextBuilder {
159684
159815
  return prompt.toString();
159685
159816
  }
159686
159817
  buildRuntimeContext(channel, chatId) {
159687
- const tz = this.config.timezone ?? "UTC";
159818
+ const tz = this.config.getTimezone?.() ?? "UTC";
159688
159819
  const now = new Date;
159689
159820
  const timeStr = now.toLocaleString("en-US", {
159690
159821
  timeZone: tz,
@@ -168553,45 +168684,63 @@ var require_dist5 = __commonJS((exports) => {
168553
168684
  exports.default = CronExpressionParser_1.CronExpressionParser;
168554
168685
  });
168555
168686
 
168556
- // src/scheduler/defaults.ts
168557
- function detectUserTimezone() {
168558
- try {
168559
- return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
168560
- } catch {
168561
- return "UTC";
168687
+ // src/scheduler/storage.ts
168688
+ function scheduleFromRow(row) {
168689
+ switch (row.schedule_kind) {
168690
+ case "at":
168691
+ return { kind: "at", atMs: row.schedule_at_ms ?? undefined };
168692
+ case "every":
168693
+ return { kind: "every", everyMs: row.schedule_every_ms ?? undefined };
168694
+ case "cron":
168695
+ return {
168696
+ kind: "cron",
168697
+ expr: row.schedule_expr ?? undefined,
168698
+ tz: row.schedule_tz ?? undefined
168699
+ };
168700
+ default:
168701
+ return { kind: "cron" };
168562
168702
  }
168563
168703
  }
168564
- var BRIEFING_PROMPT, RECAP_PROMPT, BUILT_IN_JOBS;
168565
- var init_defaults = __esm(() => {
168566
- BRIEFING_PROMPT = "Run the morning briefing. Call tools to fetch the latest data: open " + "positions, recent fills / trade history, watchlist, news, and market " + "signals (funding, whale activity, fear & greed). Summarize in under 15 " + "sentences, in the language the user has been chatting in.";
168567
- RECAP_PROMPT = "Run the end-of-day recap. Call tools to fetch today's trade history and " + "current open positions. Summarize today's PnL, position changes (opened, " + "closed, scaled), and one notable market note. Brief one-liner if I had no " + "activity. Reply in the language the user has been chatting in.";
168568
- BUILT_IN_JOBS = [
168569
- {
168570
- name: "morning-briefing",
168571
- schedule: {
168572
- kind: "cron",
168573
- expr: "0 8 * * *",
168574
- tz: detectUserTimezone()
168575
- },
168576
- message: BRIEFING_PROMPT,
168577
- deliver: true
168704
+ function rowToJob(row) {
168705
+ let runHistory = [];
168706
+ try {
168707
+ runHistory = JSON.parse(row.run_history);
168708
+ } catch {}
168709
+ return {
168710
+ id: row.id,
168711
+ name: row.name,
168712
+ enabled: row.enabled === 1,
168713
+ schedule: scheduleFromRow(row),
168714
+ payload: {
168715
+ kind: "agent_turn",
168716
+ message: row.payload_message,
168717
+ deliver: row.payload_deliver === 1,
168718
+ channel: row.payload_channel ?? undefined,
168719
+ to: row.payload_to ?? undefined
168578
168720
  },
168579
- {
168580
- name: "evening-recap",
168581
- schedule: {
168582
- kind: "cron",
168583
- expr: "0 21 * * *",
168584
- tz: detectUserTimezone()
168585
- },
168586
- message: RECAP_PROMPT,
168587
- deliver: true
168588
- }
168589
- ];
168590
- });
168721
+ state: {
168722
+ nextRunAtMs: row.next_run_at_ms,
168723
+ lastRunAtMs: row.last_run_at_ms,
168724
+ lastStatus: row.last_status ?? null,
168725
+ lastError: row.last_error,
168726
+ runHistory
168727
+ },
168728
+ createdAtMs: row.created_at_ms,
168729
+ updatedAtMs: row.updated_at_ms,
168730
+ deleteAfterRun: row.delete_after_run === 1
168731
+ };
168732
+ }
168733
+ function scheduleBindings(s2) {
168734
+ return {
168735
+ schedule_kind: s2.kind,
168736
+ schedule_at_ms: s2.atMs ?? null,
168737
+ schedule_every_ms: s2.everyMs ?? null,
168738
+ schedule_expr: s2.expr ?? null,
168739
+ schedule_tz: s2.tz ?? null
168740
+ };
168741
+ }
168591
168742
 
168592
168743
  // src/scheduler/service.ts
168593
- import { existsSync as existsSync17, readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, statSync as statSync4 } from "fs";
168594
- import { dirname as dirname8 } from "path";
168595
168744
  function computeNextRun(schedule, nowMs) {
168596
168745
  switch (schedule.kind) {
168597
168746
  case "at":
@@ -168618,255 +168767,292 @@ function computeNextRun(schedule, nowMs) {
168618
168767
  return null;
168619
168768
  }
168620
168769
  }
168621
-
168622
- class CronService {
168623
- storePath;
168624
- store = { version: 1, jobs: [] };
168625
- lastMtime = 0;
168626
- timerHandle = null;
168627
- _running = false;
168628
- onJob;
168629
- constructor(storePath) {
168630
- this.storePath = storePath;
168631
- }
168632
- setOnJob(fn) {
168633
- this.onJob = fn;
168634
- }
168635
- start(opts = {}) {
168636
- this._running = true;
168637
- this.loadStore();
168638
- this.seedDefaultJobs(opts.defaults ?? BUILT_IN_JOBS);
168639
- const now = Date.now();
168640
- for (const job of this.store.jobs) {
168641
- if (job.enabled && !job.state.nextRunAtMs) {
168642
- job.state.nextRunAtMs = computeNextRun(job.schedule, now);
168770
+ var import_cron_parser, MAX_HISTORY = 20, MIN_INTERVAL_MS = 1e4, CronService;
168771
+ var init_service = __esm(() => {
168772
+ import_cron_parser = __toESM(require_dist5(), 1);
168773
+ CronService = class CronService {
168774
+ db;
168775
+ stmts;
168776
+ timerHandle = null;
168777
+ _running = false;
168778
+ onJob;
168779
+ static DEFAULT_JOB_NAMES = ["morning-briefing", "evening-recap"];
168780
+ constructor(db2) {
168781
+ this.db = db2;
168782
+ this.stmts = {
168783
+ selectAll: db2.prepare("SELECT * FROM cron_jobs"),
168784
+ selectById: db2.prepare("SELECT * FROM cron_jobs WHERE id = ?"),
168785
+ selectByName: db2.prepare("SELECT * FROM cron_jobs WHERE name = ?"),
168786
+ insert: db2.prepare(`
168787
+ INSERT INTO cron_jobs (
168788
+ id, name, enabled,
168789
+ schedule_kind, schedule_at_ms, schedule_every_ms, schedule_expr, schedule_tz,
168790
+ payload_kind, payload_message, payload_deliver, payload_channel, payload_to,
168791
+ next_run_at_ms, last_run_at_ms, last_status, last_error,
168792
+ run_history, created_at_ms, updated_at_ms, delete_after_run
168793
+ ) VALUES (
168794
+ $id, $name, $enabled,
168795
+ $schedule_kind, $schedule_at_ms, $schedule_every_ms, $schedule_expr, $schedule_tz,
168796
+ $payload_kind, $payload_message, $payload_deliver, $payload_channel, $payload_to,
168797
+ $next_run_at_ms, NULL, NULL, NULL,
168798
+ '[]', $created_at_ms, $updated_at_ms, $delete_after_run
168799
+ )
168800
+ `),
168801
+ updateFull: db2.prepare(`
168802
+ UPDATE cron_jobs SET
168803
+ last_run_at_ms = $last_run_at_ms,
168804
+ last_status = $last_status,
168805
+ last_error = $last_error,
168806
+ next_run_at_ms = $next_run_at_ms,
168807
+ run_history = $run_history,
168808
+ enabled = $enabled,
168809
+ updated_at_ms = $updated_at_ms
168810
+ WHERE id = $id
168811
+ `),
168812
+ updateNext: db2.prepare("UPDATE cron_jobs SET next_run_at_ms = ?, updated_at_ms = ? WHERE id = ?"),
168813
+ updateTzAndNext: db2.prepare("UPDATE cron_jobs SET schedule_tz = ?, next_run_at_ms = ?, updated_at_ms = ? WHERE name = ?"),
168814
+ updateEnabled: db2.prepare("UPDATE cron_jobs SET enabled = ?, next_run_at_ms = ?, updated_at_ms = ? WHERE id = ?"),
168815
+ delete: db2.prepare("DELETE FROM cron_jobs WHERE id = ?")
168816
+ };
168817
+ }
168818
+ setOnJob(fn) {
168819
+ this.onJob = fn;
168820
+ }
168821
+ start(opts = {}) {
168822
+ this._running = true;
168823
+ this.seedDefaultJobs(opts.defaults ?? []);
168824
+ const now = Date.now();
168825
+ const rows = this.stmts.selectAll.all();
168826
+ this.db.transaction(() => {
168827
+ for (const row of rows) {
168828
+ if (row.enabled === 1 && row.next_run_at_ms !== null && row.next_run_at_ms <= now) {
168829
+ const schedule = scheduleFromRow(row);
168830
+ const next = computeNextRun(schedule, now);
168831
+ this.stmts.updateNext.run(next, now, row.id);
168832
+ } else if (row.next_run_at_ms === null && row.enabled) {
168833
+ const schedule = scheduleFromRow(row);
168834
+ const next = computeNextRun(schedule, now);
168835
+ this.stmts.updateNext.run(next, now, row.id);
168836
+ }
168837
+ }
168838
+ })();
168839
+ this.scheduleNextTick();
168840
+ }
168841
+ stop() {
168842
+ this._running = false;
168843
+ if (this.timerHandle) {
168844
+ clearTimeout(this.timerHandle);
168845
+ this.timerHandle = null;
168643
168846
  }
168644
168847
  }
168645
- this.saveStore();
168646
- this.armTimer();
168647
- }
168648
- stop() {
168649
- this._running = false;
168650
- if (this.timerHandle) {
168651
- clearTimeout(this.timerHandle);
168652
- this.timerHandle = null;
168848
+ listJobs(includeDisabled = false) {
168849
+ const rows = this.stmts.selectAll.all();
168850
+ const jobs = includeDisabled ? rows.map(rowToJob) : rows.filter((r2) => r2.enabled === 1).map(rowToJob);
168851
+ return jobs.sort((a, b5) => (a.state.nextRunAtMs ?? Infinity) - (b5.state.nextRunAtMs ?? Infinity));
168653
168852
  }
168654
- }
168655
- listJobs(includeDisabled = false) {
168656
- this.loadStore();
168657
- const jobs = includeDisabled ? this.store.jobs : this.store.jobs.filter((j9) => j9.enabled);
168658
- return jobs.sort((a, b5) => (a.state.nextRunAtMs ?? Infinity) - (b5.state.nextRunAtMs ?? Infinity));
168659
- }
168660
- addJob(opts) {
168661
- this.loadStore();
168662
- const now = Date.now();
168663
- const job = {
168664
- id: crypto.randomUUID().slice(0, 8),
168665
- name: opts.name,
168666
- enabled: true,
168667
- schedule: opts.schedule,
168668
- payload: {
168669
- kind: "agent_turn",
168670
- message: opts.message,
168671
- deliver: opts.deliver ?? true,
168672
- channel: opts.channel,
168673
- to: opts.to
168674
- },
168675
- state: {
168676
- nextRunAtMs: computeNextRun(opts.schedule, now),
168677
- lastRunAtMs: null,
168678
- lastStatus: null,
168679
- lastError: null,
168680
- runHistory: []
168681
- },
168682
- createdAtMs: now,
168683
- updatedAtMs: now,
168684
- deleteAfterRun: opts.deleteAfterRun ?? false
168685
- };
168686
- this.store.jobs.push(job);
168687
- this.saveStore();
168688
- this.armTimer();
168689
- return job;
168690
- }
168691
- removeJob(jobId) {
168692
- this.loadStore();
168693
- const idx = this.store.jobs.findIndex((j9) => j9.id === jobId);
168694
- if (idx === -1)
168695
- return false;
168696
- this.store.jobs.splice(idx, 1);
168697
- this.saveStore();
168698
- this.armTimer();
168699
- return true;
168700
- }
168701
- enableJob(jobId, enabled) {
168702
- this.loadStore();
168703
- const job = this.store.jobs.find((j9) => j9.id === jobId);
168704
- if (!job)
168705
- return;
168706
- job.enabled = enabled;
168707
- if (enabled) {
168708
- job.state.nextRunAtMs = computeNextRun(job.schedule, Date.now());
168709
- } else {
168710
- job.state.nextRunAtMs = null;
168853
+ addJob(opts) {
168854
+ const now = Date.now();
168855
+ const id = crypto.randomUUID().slice(0, 12);
168856
+ const sb = scheduleBindings(opts.schedule);
168857
+ try {
168858
+ this.stmts.insert.run({
168859
+ $id: id,
168860
+ $name: opts.name,
168861
+ $enabled: 1,
168862
+ ...Object.fromEntries(Object.entries(sb).map(([k, v4]) => [`$${k}`, v4])),
168863
+ $payload_kind: "agent_turn",
168864
+ $payload_message: opts.message,
168865
+ $payload_deliver: opts.deliver ?? true ? 1 : 0,
168866
+ $payload_channel: opts.channel ?? null,
168867
+ $payload_to: opts.to ?? null,
168868
+ $next_run_at_ms: computeNextRun(opts.schedule, now),
168869
+ $created_at_ms: now,
168870
+ $updated_at_ms: now,
168871
+ $delete_after_run: opts.deleteAfterRun ?? false ? 1 : 0
168872
+ });
168873
+ } catch (err) {
168874
+ const msg = err instanceof Error ? err.message : String(err);
168875
+ if (msg.includes("UNIQUE constraint failed") || msg.includes("SQLITE_CONSTRAINT")) {
168876
+ throw new Error(`Cron name already exists: ${opts.name}`);
168877
+ }
168878
+ throw err;
168879
+ }
168880
+ this.scheduleNextTick();
168881
+ const row = this.stmts.selectById.get(id);
168882
+ return rowToJob(row);
168711
168883
  }
168712
- job.updatedAtMs = Date.now();
168713
- this.saveStore();
168714
- this.armTimer();
168715
- }
168716
- async runJob(jobId, force = false) {
168717
- this.loadStore();
168718
- const job = this.store.jobs.find((j9) => j9.id === jobId);
168719
- if (!job)
168720
- return;
168721
- if (!job.enabled && !force)
168722
- return;
168723
- await this.executeJob(job);
168724
- this.saveStore();
168725
- this.armTimer();
168726
- }
168727
- getJob(jobId) {
168728
- this.loadStore();
168729
- return this.store.jobs.find((j9) => j9.id === jobId);
168730
- }
168731
- status() {
168732
- this.loadStore();
168733
- return {
168734
- enabled: this._running,
168735
- jobs: this.store.jobs.filter((j9) => j9.enabled).length,
168736
- nextWakeAtMs: this.getNextWakeMs()
168737
- };
168738
- }
168739
- seedDefaultJobs(specs) {
168740
- const existingNames = new Set(this.store.jobs.map((j9) => j9.name));
168741
- let dirty = false;
168742
- const now = Date.now();
168743
- for (const spec of specs) {
168744
- if (existingNames.has(spec.name))
168745
- continue;
168746
- const job = {
168747
- id: crypto.randomUUID().slice(0, 8),
168748
- name: spec.name,
168749
- enabled: true,
168750
- schedule: spec.schedule,
168751
- payload: {
168752
- kind: "agent_turn",
168753
- message: spec.message,
168754
- deliver: spec.deliver
168755
- },
168756
- state: {
168757
- nextRunAtMs: computeNextRun(spec.schedule, now),
168758
- lastRunAtMs: null,
168759
- lastStatus: null,
168760
- lastError: null,
168761
- runHistory: []
168762
- },
168763
- createdAtMs: now,
168764
- updatedAtMs: now,
168765
- deleteAfterRun: false
168884
+ removeJob(jobId) {
168885
+ const row = this.stmts.selectById.get(jobId);
168886
+ if (!row)
168887
+ return false;
168888
+ this.stmts.delete.run(jobId);
168889
+ this.scheduleNextTick();
168890
+ return true;
168891
+ }
168892
+ enableJob(jobId, enabled) {
168893
+ const now = Date.now();
168894
+ const row = this.stmts.selectById.get(jobId);
168895
+ if (!row)
168896
+ return;
168897
+ const nextRun = enabled ? computeNextRun(scheduleFromRow(row), now) : null;
168898
+ this.stmts.updateEnabled.run(enabled ? 1 : 0, nextRun, now, jobId);
168899
+ this.scheduleNextTick();
168900
+ }
168901
+ async runJob(jobId, force = false) {
168902
+ const row = this.stmts.selectById.get(jobId);
168903
+ if (!row)
168904
+ return;
168905
+ const job = rowToJob(row);
168906
+ if (!job.enabled && !force)
168907
+ return;
168908
+ await this.executeJob(job);
168909
+ }
168910
+ getJob(jobId) {
168911
+ const row = this.stmts.selectById.get(jobId);
168912
+ return row ? rowToJob(row) : undefined;
168913
+ }
168914
+ status() {
168915
+ const rows = this.stmts.selectAll.all();
168916
+ const enabled = rows.filter((r2) => r2.enabled === 1);
168917
+ return {
168918
+ enabled: this._running,
168919
+ jobs: enabled.length,
168920
+ nextTickAtMs: this.getNextTickMs()
168766
168921
  };
168767
- this.store.jobs.push(job);
168768
- dirty = true;
168769
168922
  }
168770
- if (dirty)
168771
- this.saveStore();
168772
- }
168773
- async executeJob(job) {
168774
- const startMs = Date.now();
168775
- let status = "ok";
168776
- let error45;
168777
- try {
168778
- if (this.onJob) {
168779
- await this.onJob(job);
168923
+ updateBuiltinJobsTimezone(tz) {
168924
+ const updated = [];
168925
+ const now = Date.now();
168926
+ this.db.transaction(() => {
168927
+ for (const name of CronService.DEFAULT_JOB_NAMES) {
168928
+ const row = this.stmts.selectByName.get(name);
168929
+ if (!row || row.schedule_kind !== "cron")
168930
+ continue;
168931
+ const nextRun = computeNextRun({ kind: "cron", expr: row.schedule_expr ?? undefined, tz }, now);
168932
+ this.stmts.updateTzAndNext.run(tz, nextRun, now, name);
168933
+ updated.push(name);
168934
+ }
168935
+ })();
168936
+ this.scheduleNextTick();
168937
+ return updated;
168938
+ }
168939
+ seedDefaultJobs(specs) {
168940
+ const now = Date.now();
168941
+ this.db.transaction(() => {
168942
+ for (const spec of specs) {
168943
+ const existing = this.stmts.selectByName.get(spec.name);
168944
+ if (existing)
168945
+ continue;
168946
+ const id = crypto.randomUUID().slice(0, 12);
168947
+ const sb = scheduleBindings(spec.schedule);
168948
+ this.stmts.insert.run({
168949
+ $id: id,
168950
+ $name: spec.name,
168951
+ $enabled: 1,
168952
+ ...Object.fromEntries(Object.entries(sb).map(([k, v4]) => [`$${k}`, v4])),
168953
+ $payload_kind: "agent_turn",
168954
+ $payload_message: spec.message,
168955
+ $payload_deliver: spec.deliver ? 1 : 0,
168956
+ $payload_channel: null,
168957
+ $payload_to: null,
168958
+ $next_run_at_ms: computeNextRun(spec.schedule, now),
168959
+ $created_at_ms: now,
168960
+ $updated_at_ms: now,
168961
+ $delete_after_run: 0
168962
+ });
168963
+ }
168964
+ })();
168965
+ }
168966
+ async executeJob(job) {
168967
+ const startMs = Date.now();
168968
+ let status = "ok";
168969
+ let error45;
168970
+ try {
168971
+ if (this.onJob) {
168972
+ await this.onJob(job);
168973
+ }
168974
+ } catch (err) {
168975
+ status = "error";
168976
+ error45 = err instanceof Error ? err.message : String(err);
168977
+ }
168978
+ const durationMs = Date.now() - startMs;
168979
+ const record6 = { runAtMs: startMs, status, durationMs, error: error45 };
168980
+ const currentRow = this.stmts.selectById.get(job.id);
168981
+ let runHistory = [];
168982
+ if (currentRow) {
168983
+ try {
168984
+ runHistory = JSON.parse(currentRow.run_history);
168985
+ } catch {}
168780
168986
  }
168781
- } catch (err) {
168782
- status = "error";
168783
- error45 = err instanceof Error ? err.message : String(err);
168784
- }
168785
- const durationMs = Date.now() - startMs;
168786
- const record6 = { runAtMs: startMs, status, durationMs, error: error45 };
168787
- job.state.lastRunAtMs = startMs;
168788
- job.state.lastStatus = status;
168789
- job.state.lastError = error45 ?? null;
168790
- job.state.runHistory.push(record6);
168791
- if (job.state.runHistory.length > MAX_HISTORY) {
168792
- job.state.runHistory = job.state.runHistory.slice(-MAX_HISTORY);
168793
- }
168794
- if (job.schedule.kind === "at") {
168795
- if (job.deleteAfterRun) {
168796
- const idx = this.store.jobs.indexOf(job);
168797
- if (idx >= 0)
168798
- this.store.jobs.splice(idx, 1);
168799
- } else {
168800
- job.enabled = false;
168801
- job.state.nextRunAtMs = null;
168987
+ runHistory.push(record6);
168988
+ if (runHistory.length > MAX_HISTORY) {
168989
+ runHistory = runHistory.slice(-MAX_HISTORY);
168802
168990
  }
168803
- } else {
168804
- job.state.nextRunAtMs = computeNextRun(job.schedule, Date.now());
168991
+ let nextRunAtMs;
168992
+ let enabled = 1;
168993
+ if (job.schedule.kind === "at") {
168994
+ if (job.deleteAfterRun) {
168995
+ this.stmts.delete.run(job.id);
168996
+ this.scheduleNextTick();
168997
+ return;
168998
+ }
168999
+ enabled = 0;
169000
+ nextRunAtMs = null;
169001
+ } else {
169002
+ const freshSchedule = currentRow ? scheduleFromRow(currentRow) : job.schedule;
169003
+ nextRunAtMs = computeNextRun(freshSchedule, Date.now());
169004
+ }
169005
+ this.db.transaction(() => {
169006
+ this.stmts.updateFull.run({
169007
+ $last_run_at_ms: startMs,
169008
+ $last_status: status,
169009
+ $last_error: error45 ?? null,
169010
+ $next_run_at_ms: nextRunAtMs,
169011
+ $run_history: JSON.stringify(runHistory),
169012
+ $enabled: enabled,
169013
+ $updated_at_ms: Date.now(),
169014
+ $id: job.id
169015
+ });
169016
+ })();
169017
+ this.scheduleNextTick();
168805
169018
  }
168806
- job.updatedAtMs = Date.now();
168807
- }
168808
- getNextWakeMs() {
168809
- let earliest = null;
168810
- for (const job of this.store.jobs) {
168811
- if (job.enabled && job.state.nextRunAtMs) {
168812
- if (earliest === null || job.state.nextRunAtMs < earliest) {
168813
- earliest = job.state.nextRunAtMs;
169019
+ getNextTickMs() {
169020
+ const rows = this.stmts.selectAll.all();
169021
+ let earliest = null;
169022
+ for (const row of rows) {
169023
+ if (row.enabled === 1 && row.next_run_at_ms !== null) {
169024
+ if (earliest === null || row.next_run_at_ms < earliest) {
169025
+ earliest = row.next_run_at_ms;
169026
+ }
168814
169027
  }
168815
169028
  }
169029
+ return earliest;
168816
169030
  }
168817
- return earliest;
168818
- }
168819
- armTimer() {
168820
- if (this.timerHandle) {
168821
- clearTimeout(this.timerHandle);
168822
- this.timerHandle = null;
168823
- }
168824
- if (!this._running)
168825
- return;
168826
- const nextMs = this.getNextWakeMs();
168827
- if (nextMs === null)
168828
- return;
168829
- const delayMs = Math.max(nextMs - Date.now(), 0);
168830
- this.timerHandle = setTimeout(() => this.onTimer(), delayMs);
168831
- }
168832
- async onTimer() {
168833
- if (!this._running)
168834
- return;
168835
- this.loadStore();
168836
- const now = Date.now();
168837
- const due = this.store.jobs.filter((j9) => j9.enabled && j9.state.nextRunAtMs !== null && j9.state.nextRunAtMs <= now);
168838
- for (const job of due) {
168839
- await this.executeJob(job);
169031
+ scheduleNextTick() {
169032
+ if (this.timerHandle) {
169033
+ clearTimeout(this.timerHandle);
169034
+ this.timerHandle = null;
169035
+ }
169036
+ if (!this._running)
169037
+ return;
169038
+ const nextMs = this.getNextTickMs();
169039
+ if (nextMs === null)
169040
+ return;
169041
+ const delayMs = Math.max(nextMs - Date.now(), 0);
169042
+ this.timerHandle = setTimeout(() => void this.onTimer(), delayMs);
168840
169043
  }
168841
- this.saveStore();
168842
- this.armTimer();
168843
- }
168844
- loadStore() {
168845
- if (!existsSync17(this.storePath))
168846
- return;
168847
- try {
168848
- const stat2 = statSync4(this.storePath);
168849
- const mtime = stat2.mtimeMs;
168850
- if (mtime === this.lastMtime && this.store.jobs.length > 0)
169044
+ async onTimer() {
169045
+ if (!this._running)
168851
169046
  return;
168852
- const raw = readFileSync16(this.storePath, "utf-8");
168853
- const parsed = JSON.parse(raw);
168854
- this.store = parsed;
168855
- this.lastMtime = mtime;
168856
- } catch {}
168857
- }
168858
- saveStore() {
168859
- mkdirSync13(dirname8(this.storePath), { recursive: true });
168860
- writeFileSync10(this.storePath, JSON.stringify(this.store, null, 2));
168861
- try {
168862
- this.lastMtime = statSync4(this.storePath).mtimeMs;
168863
- } catch {}
168864
- }
168865
- }
168866
- var import_cron_parser, MAX_HISTORY = 20, MIN_INTERVAL_MS = 1e4;
168867
- var init_service = __esm(() => {
168868
- init_defaults();
168869
- import_cron_parser = __toESM(require_dist5(), 1);
169047
+ const now = Date.now();
169048
+ const rows = this.stmts.selectAll.all();
169049
+ const due = rows.filter((r2) => r2.enabled === 1 && r2.next_run_at_ms !== null && r2.next_run_at_ms <= now);
169050
+ for (const row of due) {
169051
+ await this.executeJob(rowToJob(row));
169052
+ }
169053
+ this.scheduleNextTick();
169054
+ }
169055
+ };
168870
169056
  });
168871
169057
 
168872
169058
  // src/bus/queue.ts
@@ -169560,6 +169746,9 @@ class ApprovalManager {
169560
169746
  getReason(approvalId) {
169561
169747
  return this.pending.get(approvalId)?.reason ?? null;
169562
169748
  }
169749
+ getPreview(approvalId) {
169750
+ return this.pending.get(approvalId)?.preview;
169751
+ }
169563
169752
  getPending(sessionKey) {
169564
169753
  const id = this.bySession.get(sessionKey);
169565
169754
  if (!id)
@@ -169893,7 +170082,7 @@ var init_approval_events = __esm(() => {
169893
170082
  });
169894
170083
 
169895
170084
  // src/services/trading-confirm.ts
169896
- function buildPreview(title, body) {
170085
+ function buildPreview(title, body, extras) {
169897
170086
  const lines = (body.lines ?? []).filter((l2) => l2 !== undefined && l2 !== null);
169898
170087
  const steps = (body.steps ?? []).filter((s2) => s2 !== undefined && s2 !== null);
169899
170088
  const details = {};
@@ -169919,7 +170108,10 @@ function buildPreview(title, body) {
169919
170108
  summary,
169920
170109
  details,
169921
170110
  warnings: warnings.length > 0 ? warnings : undefined,
169922
- direction: blob.includes("buy") || blob.includes("long") ? "long" : blob.includes("sell") || blob.includes("short") ? "short" : undefined
170111
+ symbol: extras?.symbol,
170112
+ direction: blob.includes("buy") || blob.includes("long") ? "long" : blob.includes("sell") || blob.includes("short") ? "short" : undefined,
170113
+ wizard: extras?.wizard,
170114
+ suggestedValue: extras?.suggestedValue
169923
170115
  };
169924
170116
  }
169925
170117
 
@@ -169932,8 +170124,8 @@ class DaemonConfirmService {
169932
170124
  this.eventBus = eventBus;
169933
170125
  this.orchestrator = orchestrator;
169934
170126
  }
169935
- async confirm(title, body) {
169936
- const preview = buildPreview(title, body);
170127
+ async confirm(title, body, extras) {
170128
+ const preview = buildPreview(title, body, extras);
169937
170129
  const sessionKey = `trade:${crypto.randomUUID().slice(0, 8)}`;
169938
170130
  const preText = this.orchestrator.getCurrentTurnText();
169939
170131
  const origin = this.orchestrator.getCurrentTurnOrigin();
@@ -180691,6 +180883,50 @@ var init_cloid = __esm(() => {
180691
180883
  SUFFIX_BYTE_LENGTH = SUFFIX_HEX_LENGTH / 2;
180692
180884
  });
180693
180885
 
180886
+ // src/services/live/info-cache.ts
180887
+ class InfoCache {
180888
+ entries = new Map;
180889
+ ttlMs;
180890
+ constructor(ttlMs = 3000) {
180891
+ this.ttlMs = ttlMs;
180892
+ }
180893
+ get(key, fetcher) {
180894
+ const now = Date.now();
180895
+ const existing = this.entries.get(key);
180896
+ if (existing !== undefined && now - existing.insertedAt < this.ttlMs) {
180897
+ return existing.promise;
180898
+ }
180899
+ const promise6 = fetcher().catch((err) => {
180900
+ if (this.entries.get(key)?.promise === promise6) {
180901
+ this.entries.delete(key);
180902
+ }
180903
+ throw err;
180904
+ });
180905
+ this.entries.set(key, { promise: promise6, insertedAt: now });
180906
+ return promise6;
180907
+ }
180908
+ clear() {
180909
+ this.entries.clear();
180910
+ }
180911
+ }
180912
+ async function runWithConcurrency(items, concurrency, fn) {
180913
+ const results = new Array(items.length);
180914
+ let nextIndex = 0;
180915
+ async function worker() {
180916
+ while (nextIndex < items.length) {
180917
+ const index = nextIndex++;
180918
+ try {
180919
+ results[index] = { status: "fulfilled", value: await fn(items[index], index) };
180920
+ } catch (reason) {
180921
+ results[index] = { status: "rejected", reason };
180922
+ }
180923
+ }
180924
+ }
180925
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());
180926
+ await Promise.all(workers);
180927
+ return results;
180928
+ }
180929
+
180694
180930
  // src/services/live/client.ts
180695
180931
  function mapFill(f2) {
180696
180932
  return {
@@ -180734,6 +180970,45 @@ function parseDex(symbol5) {
180734
180970
  return { dex: null, name: trimmed };
180735
180971
  return { dex, name: trimmed };
180736
180972
  }
180973
+ function mapPositionsFromRaw(raw) {
180974
+ const positions = [];
180975
+ for (const ap of raw.assetPositions ?? []) {
180976
+ const pos = ap.position;
180977
+ const szi = parseFloat(pos.szi);
180978
+ if (szi === 0)
180979
+ continue;
180980
+ const entryPx = parseFloat(pos.entryPx);
180981
+ const markPx = pos.positionValue ? Math.abs(parseFloat(pos.positionValue) / szi) : entryPx;
180982
+ positions.push({
180983
+ symbol: pos.coin,
180984
+ side: szi > 0 ? "long" : "short",
180985
+ size: Math.abs(szi),
180986
+ entryPrice: entryPx,
180987
+ markPrice: markPx,
180988
+ liquidationPrice: pos.liquidationPx && pos.liquidationPx !== "0.0" && parseFloat(pos.liquidationPx) > 0 ? parseFloat(pos.liquidationPx) : null,
180989
+ unrealizedPnl: parseFloat(pos.unrealizedPnl),
180990
+ unrealizedPnlPct: parseFloat(pos.returnOnEquity ?? "0") * 100,
180991
+ leverage: parseFloat(pos.leverage?.value ?? "1"),
180992
+ marginMode: pos.leverage?.type === "isolated" ? "isolated" : "cross",
180993
+ margin: parseFloat(pos.marginUsed)
180994
+ });
180995
+ }
180996
+ return positions;
180997
+ }
180998
+ function mapOpenOrderFromRaw(o10) {
180999
+ return {
181000
+ orderId: String(o10.oid),
181001
+ symbol: o10.coin,
181002
+ side: o10.side === "B" ? "buy" : "sell",
181003
+ orderType: o10.orderType ?? "Limit",
181004
+ price: o10.limitPx ? parseFloat(o10.limitPx) : null,
181005
+ triggerPrice: o10.triggerPx && o10.triggerPx !== "0.0" ? parseFloat(o10.triggerPx) : null,
181006
+ size: parseFloat(o10.sz),
181007
+ filled: parseFloat(o10.origSz ?? o10.sz) - parseFloat(o10.sz),
181008
+ reduceOnly: o10.reduceOnly ?? false,
181009
+ timestamp: o10.timestamp ?? Date.now()
181010
+ };
181011
+ }
180737
181012
  function ctxToTicker(ctx, symbol5) {
180738
181013
  const markPx = parseFloat(ctx.markPx ?? "0");
180739
181014
  const prevDay = parseFloat(ctx.prevDayPx ?? "0");
@@ -180759,12 +181034,18 @@ class HyperliquidClient {
180759
181034
  assetMap = new Map;
180760
181035
  szDecimals = new Map;
180761
181036
  maxLeverage = new Map;
180762
- assetNames = [];
181037
+ assets = [];
180763
181038
  metaLoaded = false;
181039
+ metaInFlight = null;
180764
181040
  dexUniverses = new Map;
180765
181041
  dexListCache = null;
180766
181042
  dexListCacheAt = 0;
180767
181043
  DEX_CACHE_TTL_MS = 60 * 60 * 1000;
181044
+ infoCache = new InfoCache(3000);
181045
+ wsTransport = null;
181046
+ wsSubClient = null;
181047
+ wsLifecycle = null;
181048
+ wsDisposed = false;
180768
181049
  constructor(config3, logger) {
180769
181050
  this.defaultAddress = config3?.address ?? "";
180770
181051
  this.testnet = config3?.testnet ?? false;
@@ -180785,6 +181066,7 @@ class HyperliquidClient {
180785
181066
  connect(config3) {
180786
181067
  this.defaultAddress = config3.address || this.defaultAddress;
180787
181068
  this.baseUrl = config3.testnet ? TESTNET_URL : MAINNET_URL;
181069
+ this.wsDisposed = false;
180788
181070
  if (config3.privateKey) {
180789
181071
  const wallet = privateKeyToAccount(config3.privateKey);
180790
181072
  const transport = new HttpTransport({ isTestnet: config3.testnet });
@@ -180794,18 +181076,35 @@ class HyperliquidClient {
180794
181076
  disconnect() {
180795
181077
  this.defaultAddress = "";
180796
181078
  this.exchange = null;
181079
+ this.closeWs();
180797
181080
  }
180798
181081
  requireExchange() {
180799
181082
  if (!this.exchange)
180800
181083
  throw new Error("Write operations require a private key. Use the connect_wallet tool or set hlPrivateKey in config.");
180801
181084
  return this.exchange;
180802
181085
  }
180803
- async info(type5, extra = {}) {
181086
+ async info(type5, extra = {}, options = {}) {
181087
+ if (!options.cache)
181088
+ return this.fetchInfo(type5, extra, 0);
181089
+ const key = `${type5}:${JSON.stringify(extra)}`;
181090
+ return this.infoCache.get(key, () => this.fetchInfo(type5, extra, 0));
181091
+ }
181092
+ async fetchInfo(type5, extra, attempt) {
180804
181093
  const res = await fetch(`${this.baseUrl}/info`, {
180805
181094
  method: "POST",
180806
181095
  headers: { "Content-Type": "application/json" },
180807
181096
  body: JSON.stringify({ type: type5, ...extra })
180808
181097
  });
181098
+ if (res.status === 429 && attempt < 3) {
181099
+ const retryAfterRaw = res.headers.get("retry-after");
181100
+ const retryAfterSec = retryAfterRaw !== null ? Number(retryAfterRaw) : NaN;
181101
+ const MAX_RETRY_AFTER_MS = 5000;
181102
+ const baseMs = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.min(retryAfterSec * 1000, MAX_RETRY_AFTER_MS) : 250 * Math.pow(2, attempt);
181103
+ const jitterMs = Math.random() * 100;
181104
+ this.log.debug({ type: type5, dex: extra.dex, attempt, delayMs: baseMs + jitterMs }, "rate limited \u2014 retrying after backoff");
181105
+ await new Promise((resolve) => setTimeout(resolve, baseMs + jitterMs));
181106
+ return this.fetchInfo(type5, extra, attempt + 1);
181107
+ }
180809
181108
  if (!res.ok) {
180810
181109
  const text = await res.text();
180811
181110
  throw new Error(`Hyperliquid ${type5}: ${res.status} ${text}`);
@@ -180841,8 +181140,16 @@ class HyperliquidClient {
180841
181140
  const hasNewDex = currentDexes.some((d2) => !knownDexNames.has(d2.name));
180842
181141
  if (!hasNewDex)
180843
181142
  return;
180844
- this.metaLoaded = false;
180845
181143
  }
181144
+ if (this.metaInFlight !== null)
181145
+ return this.metaInFlight;
181146
+ this.metaInFlight = this.rebuildMeta().finally(() => {
181147
+ this.metaInFlight = null;
181148
+ });
181149
+ return this.metaInFlight;
181150
+ }
181151
+ async rebuildMeta() {
181152
+ this.metaLoaded = false;
180846
181153
  let nativeUniverse = [];
180847
181154
  try {
180848
181155
  const nativeData = await this.info("meta");
@@ -180851,7 +181158,7 @@ class HyperliquidClient {
180851
181158
  this.log.warn({ err }, "Native meta fetch failed \u2014 daemon starts degraded (HIP-3 may still load)");
180852
181159
  }
180853
181160
  const dexes = await this.listPerpDexes().catch(() => []);
180854
- const dexResults = await Promise.allSettled(dexes.map((d2) => this.info("meta", { dex: d2.name })));
181161
+ const dexResults = await runWithConcurrency(dexes, 4, (d2) => this.info("meta", { dex: d2.name }));
180855
181162
  let merged = [...nativeUniverse];
180856
181163
  const nativeNames = nativeUniverse.map((a) => a.name);
180857
181164
  this.dexUniverses.set("", nativeNames);
@@ -180865,7 +181172,7 @@ class HyperliquidClient {
180865
181172
  this.log.warn({ err: result.reason, dex: dexes[i].name }, "HIP-3 dex meta fetch failed \u2014 skipping");
180866
181173
  }
180867
181174
  }
180868
- this.assetNames = merged.map((a) => a.name);
181175
+ this.assets = merged.map((a) => a.isDelisted ? { symbol: a.name, isDelisted: true } : { symbol: a.name });
180869
181176
  merged.forEach((a, idx) => {
180870
181177
  const key = this.resolveSymbol(a.name);
180871
181178
  this.assetMap.set(key, idx);
@@ -180875,10 +181182,63 @@ class HyperliquidClient {
180875
181182
  }
180876
181183
  });
180877
181184
  this.metaLoaded = true;
181185
+ this.log.info({ dexCount: dexes.length }, "meta rebuild complete");
180878
181186
  }
180879
181187
  getMaxLeverage(symbol5) {
180880
181188
  return this.maxLeverage.get(this.resolveSymbol(symbol5));
180881
181189
  }
181190
+ getAllAssetNames() {
181191
+ return this.assets.map((a) => a.symbol);
181192
+ }
181193
+ getAllAssets() {
181194
+ return this.assets;
181195
+ }
181196
+ isKnownSymbol(symbol5) {
181197
+ return this.assetMap.has(this.resolveSymbol(symbol5));
181198
+ }
181199
+ getDexUniverses() {
181200
+ return this.dexUniverses;
181201
+ }
181202
+ async getSubscriptionClient() {
181203
+ if (this.wsSubClient !== null)
181204
+ return this.wsSubClient;
181205
+ if (this.wsLifecycle !== null)
181206
+ return this.wsLifecycle;
181207
+ this.wsLifecycle = (async () => {
181208
+ if (this.wsDisposed)
181209
+ throw new Error("trading client disposed");
181210
+ const transport = new WebSocketTransport({ isTestnet: this.testnet });
181211
+ const client4 = new SubscriptionClient({ transport });
181212
+ if (this.wsDisposed) {
181213
+ await transport.close().catch(() => {});
181214
+ throw new Error("trading client disposed");
181215
+ }
181216
+ this.wsTransport = transport;
181217
+ this.wsSubClient = client4;
181218
+ return client4;
181219
+ })().finally(() => {
181220
+ this.wsLifecycle = null;
181221
+ });
181222
+ return this.wsLifecycle;
181223
+ }
181224
+ async subscribeAllDexsAssetCtxs(listener) {
181225
+ const client4 = await this.getSubscriptionClient();
181226
+ const sub = await client4.allDexsAssetCtxs((e2) => {
181227
+ listener({ ctxs: e2.ctxs });
181228
+ });
181229
+ return { unsubscribe: () => sub.unsubscribe() };
181230
+ }
181231
+ async closeWs() {
181232
+ this.wsDisposed = true;
181233
+ if (this.wsTransport !== null) {
181234
+ try {
181235
+ await this.wsTransport.close();
181236
+ } catch {}
181237
+ }
181238
+ this.wsTransport = null;
181239
+ this.wsSubClient = null;
181240
+ this.wsLifecycle = null;
181241
+ }
180882
181242
  resolveSymbol(symbol5) {
180883
181243
  const { dex, name } = parseDex(symbol5);
180884
181244
  if (dex !== null) {
@@ -180937,52 +181297,17 @@ class HyperliquidClient {
180937
181297
  async getPositions(address) {
180938
181298
  const user = address ?? this.defaultAddress;
180939
181299
  const data = await this.info("clearinghouseState", { user });
180940
- const positions = [];
180941
- for (const ap of data.assetPositions ?? []) {
180942
- const pos = ap.position;
180943
- const szi = parseFloat(pos.szi);
180944
- if (szi === 0)
180945
- continue;
180946
- const entryPx = parseFloat(pos.entryPx);
180947
- const markPx = pos.positionValue ? Math.abs(parseFloat(pos.positionValue) / szi) : entryPx;
180948
- const upnl = parseFloat(pos.unrealizedPnl);
180949
- const margin = parseFloat(pos.marginUsed);
180950
- positions.push({
180951
- symbol: pos.coin,
180952
- side: szi > 0 ? "long" : "short",
180953
- size: Math.abs(szi),
180954
- entryPrice: entryPx,
180955
- markPrice: markPx,
180956
- liquidationPrice: pos.liquidationPx && pos.liquidationPx !== "0.0" && parseFloat(pos.liquidationPx) > 0 ? parseFloat(pos.liquidationPx) : null,
180957
- unrealizedPnl: upnl,
180958
- unrealizedPnlPct: parseFloat(pos.returnOnEquity ?? "0") * 100,
180959
- leverage: parseFloat(pos.leverage?.value ?? "1"),
180960
- marginMode: pos.leverage?.type === "isolated" ? "isolated" : "cross",
180961
- margin
180962
- });
180963
- }
180964
- return positions;
181300
+ return mapPositionsFromRaw(data);
180965
181301
  }
180966
181302
  async getOpenOrders(address) {
180967
181303
  const user = address ?? this.defaultAddress;
180968
181304
  const data = await this.info("frontendOpenOrders", { user });
180969
- return data.map((o10) => ({
180970
- orderId: String(o10.oid),
180971
- symbol: o10.coin,
180972
- side: o10.side === "B" ? "buy" : "sell",
180973
- orderType: o10.orderType ?? "Limit",
180974
- price: o10.limitPx ? parseFloat(o10.limitPx) : null,
180975
- triggerPrice: o10.triggerPx && o10.triggerPx !== "0.0" ? parseFloat(o10.triggerPx) : null,
180976
- size: parseFloat(o10.sz),
180977
- filled: parseFloat(o10.origSz ?? o10.sz) - parseFloat(o10.sz),
180978
- reduceOnly: o10.reduceOnly ?? false,
180979
- timestamp: o10.timestamp ?? Date.now()
180980
- }));
181305
+ return data.map(mapOpenOrderFromRaw);
180981
181306
  }
180982
181307
  async getFills(address, limit2 = 20) {
180983
181308
  const user = address ?? this.defaultAddress;
180984
181309
  const data = await this.info("userFills", { user });
180985
- return data.slice(0, limit2).map((f2) => mapFill(f2));
181310
+ return data.slice(0, limit2).map(mapFill);
180986
181311
  }
180987
181312
  async getFillsByTime(address, startTime, endTime) {
180988
181313
  const user = address ?? this.defaultAddress;
@@ -180990,7 +181315,7 @@ class HyperliquidClient {
180990
181315
  if (endTime !== undefined)
180991
181316
  params.endTime = endTime;
180992
181317
  const data = await this.info("userFillsByTime", params);
180993
- return data.map((f2) => mapFill(f2));
181318
+ return data.map(mapFill);
180994
181319
  }
180995
181320
  async getHistoricalOrders(address, startTime) {
180996
181321
  const user = address ?? this.defaultAddress;
@@ -181018,7 +181343,7 @@ class HyperliquidClient {
181018
181343
  async getAllTickers() {
181019
181344
  await this.ensureMeta();
181020
181345
  const tickers = [];
181021
- const nativeData = await this.info("metaAndAssetCtxs");
181346
+ const nativeData = await this.info("metaAndAssetCtxs", {}, { cache: true });
181022
181347
  const nativeCtxs = nativeData[1] ?? [];
181023
181348
  const responseNativeUniverse = nativeData[0]?.universe ?? [];
181024
181349
  const nativeNames = responseNativeUniverse.length > 0 ? responseNativeUniverse.map((a) => a.name) : this.dexUniverses.get("") ?? [];
@@ -181029,7 +181354,7 @@ class HyperliquidClient {
181029
181354
  tickers.push(ctxToTicker(nativeCtxs[i], name));
181030
181355
  }
181031
181356
  const dexes = await this.listPerpDexes().catch(() => []);
181032
- const dexCtxResults = await Promise.allSettled(dexes.map((d2) => this.info("metaAndAssetCtxs", { dex: d2.name })));
181357
+ const dexCtxResults = await runWithConcurrency(dexes, 4, (d2) => this.info("metaAndAssetCtxs", { dex: d2.name }, { cache: true }));
181033
181358
  for (let i = 0;i < dexCtxResults.length; i++) {
181034
181359
  const r2 = dexCtxResults[i];
181035
181360
  if (r2.status !== "fulfilled") {
@@ -181077,7 +181402,8 @@ class HyperliquidClient {
181077
181402
  "1w": 604800000
181078
181403
  };
181079
181404
  const ms2 = intervalMs[interval] ?? 3600000;
181080
- const endTime = Date.now();
181405
+ const QUANTISE_MS = 3000;
181406
+ const endTime = Math.floor(Date.now() / QUANTISE_MS) * QUANTISE_MS;
181081
181407
  const startTime = endTime - limit2 * ms2;
181082
181408
  let timer;
181083
181409
  try {
@@ -181085,7 +181411,7 @@ class HyperliquidClient {
181085
181411
  timer = setTimeout(() => reject(new Error("Hyperliquid klines timeout (10s)")), 1e4);
181086
181412
  });
181087
181413
  const data = await Promise.race([
181088
- this.info("candleSnapshot", { req: { coin: resolved, interval, startTime, endTime } }),
181414
+ this.info("candleSnapshot", { req: { coin: resolved, interval, startTime, endTime } }, { cache: true }),
181089
181415
  timeout
181090
181416
  ]);
181091
181417
  return (data ?? []).slice(-limit2).map((c2) => ({
@@ -181118,12 +181444,16 @@ class HyperliquidClient {
181118
181444
  const decimals = this.szDecimals.get(resolved) ?? 0;
181119
181445
  return size2.toFixed(decimals);
181120
181446
  }
181121
- formatPrice(price) {
181122
- return parseFloat(price.toPrecision(5)).toString();
181447
+ formatPrice(symbol5, price) {
181448
+ const resolved = this.resolveSymbol(symbol5);
181449
+ const szDecimals = this.szDecimals.get(resolved) ?? 0;
181450
+ const maxDecimals = Math.max(0, 6 - szDecimals);
181451
+ const trimmed = parseFloat(price.toPrecision(5));
181452
+ return parseFloat(trimmed.toFixed(maxDecimals)).toString();
181123
181453
  }
181124
- slippagePrice(midPrice, isBuy, slippagePct) {
181454
+ slippagePrice(symbol5, midPrice, isBuy, slippagePct) {
181125
181455
  const factor = isBuy ? 1 + slippagePct / 100 : 1 - slippagePct / 100;
181126
- return this.formatPrice(midPrice * factor);
181456
+ return this.formatPrice(symbol5, midPrice * factor);
181127
181457
  }
181128
181458
  async placeOrder(params) {
181129
181459
  const ex = this.requireExchange();
@@ -181139,42 +181469,42 @@ class HyperliquidClient {
181139
181469
  switch (params.orderType) {
181140
181470
  case "market": {
181141
181471
  const ticker = await this.getTicker(params.symbol);
181142
- price = this.slippagePrice(ticker.midPrice, isBuy, params.slippagePct ?? 0.5);
181472
+ price = this.slippagePrice(params.symbol, ticker.midPrice, isBuy, params.slippagePct ?? 0.5);
181143
181473
  orderType = { limit: { tif: "Ioc" } };
181144
181474
  break;
181145
181475
  }
181146
181476
  case "limit": {
181147
181477
  if (!params.price)
181148
181478
  throw new Error("Limit order requires price");
181149
- price = this.formatPrice(params.price);
181479
+ price = this.formatPrice(params.symbol, params.price);
181150
181480
  orderType = { limit: { tif: params.tif ?? "Gtc" } };
181151
181481
  break;
181152
181482
  }
181153
181483
  case "stop_market": {
181154
181484
  if (!params.price)
181155
181485
  throw new Error("Stop market order requires trigger price");
181156
- price = this.formatPrice(params.price);
181486
+ price = this.formatPrice(params.symbol, params.price);
181157
181487
  orderType = { trigger: { isMarket: true, triggerPx: price, tpsl: "sl" } };
181158
181488
  break;
181159
181489
  }
181160
181490
  case "stop_limit": {
181161
181491
  if (!params.price)
181162
181492
  throw new Error("Stop limit order requires trigger price");
181163
- price = this.formatPrice(params.price);
181493
+ price = this.formatPrice(params.symbol, params.price);
181164
181494
  orderType = { trigger: { isMarket: false, triggerPx: price, tpsl: "sl" } };
181165
181495
  break;
181166
181496
  }
181167
181497
  case "take_profit": {
181168
181498
  if (!params.price)
181169
181499
  throw new Error("Take profit order requires trigger price");
181170
- price = this.formatPrice(params.price);
181500
+ price = this.formatPrice(params.symbol, params.price);
181171
181501
  orderType = { trigger: { isMarket: true, triggerPx: price, tpsl: "tp" } };
181172
181502
  break;
181173
181503
  }
181174
181504
  case "take_profit_limit": {
181175
181505
  if (!params.price)
181176
181506
  throw new Error("Take profit limit order requires price");
181177
- price = this.formatPrice(params.price);
181507
+ price = this.formatPrice(params.symbol, params.price);
181178
181508
  orderType = { trigger: { isMarket: false, triggerPx: price, tpsl: "tp" } };
181179
181509
  break;
181180
181510
  }
@@ -181902,6 +182232,24 @@ class PaperTradingClient {
181902
182232
  getMaxLeverage(symbol5) {
181903
182233
  return this.marketClient.getMaxLeverage(symbol5);
181904
182234
  }
182235
+ getAllAssetNames() {
182236
+ return this.marketClient.getAllAssetNames();
182237
+ }
182238
+ isKnownSymbol(symbol5) {
182239
+ return this.marketClient.isKnownSymbol(symbol5);
182240
+ }
182241
+ getAllAssets() {
182242
+ return this.marketClient.getAllAssets();
182243
+ }
182244
+ getDexUniverses() {
182245
+ return this.marketClient.getDexUniverses();
182246
+ }
182247
+ subscribeAllDexsAssetCtxs(_listener) {
182248
+ return Promise.resolve(NOOP_SUB);
182249
+ }
182250
+ closeWs() {
182251
+ return Promise.resolve();
182252
+ }
181905
182253
  getBalance(_address) {
181906
182254
  return this.engine.getBalance();
181907
182255
  }
@@ -181948,8 +182296,10 @@ class PaperTradingClient {
181948
182296
  this.engine.reset(newBalance);
181949
182297
  }
181950
182298
  }
182299
+ var NOOP_SUB;
181951
182300
  var init_client7 = __esm(() => {
181952
182301
  init_engine();
182302
+ NOOP_SUB = { unsubscribe: async () => {} };
181953
182303
  });
181954
182304
 
181955
182305
  // src/services/intel.ts
@@ -182234,7 +182584,7 @@ var init_alert_rules = __esm(() => {
182234
182584
 
182235
182585
  // src/services/notifications.ts
182236
182586
  function isKind(raw) {
182237
- return raw === "price_target" || raw === "liquidation_risk" || raw === "position_closed" || raw === "tp_hit" || raw === "sl_hit" || raw === "order_filled" || raw === "order_canceled" || raw === "proactive";
182587
+ return raw === "price_target" || raw === "liquidation_risk" || raw === "position_closed" || raw === "tp_hit" || raw === "sl_hit" || raw === "order_filled" || raw === "order_canceled" || raw === "news" || raw === "proactive";
182238
182588
  }
182239
182589
  function rowToNotification(r2) {
182240
182590
  let payload;
@@ -182304,8 +182654,8 @@ class NotificationsService {
182304
182654
  // src/services/price-cache.ts
182305
182655
  class PriceCache {
182306
182656
  prices = new Map;
182307
- set(symbol5, price) {
182308
- this.prices.set(symbol5, { price, timestamp: Date.now() });
182657
+ set(symbol5, price, prevDayPrice) {
182658
+ this.prices.set(symbol5, { price, timestamp: Date.now(), prevDayPrice });
182309
182659
  }
182310
182660
  get(symbol5, maxAgeMs = 30000) {
182311
182661
  const entry = this.prices.get(symbol5);
@@ -182905,6 +183255,23 @@ class NewsService {
182905
183255
  ];
182906
183256
  return this.db.prepare(sql).all(...params).map((r2) => mapRow(r2));
182907
183257
  }
183258
+ listRecentRelevant(sinceTs, limit2 = 20) {
183259
+ const sql = `
183260
+ SELECT id, source_id, external_id, url, title, snippet, image_url, coins,
183261
+ importance, published_at, fetched_at, expires_at, full_summary,
183262
+ ai_relevant, ai_duplicate_of
183263
+ FROM articles
183264
+ WHERE ai_relevant = 1
183265
+ AND full_summary IS NOT NULL
183266
+ AND ai_duplicate_of IS NULL
183267
+ AND dismissed_at IS NULL
183268
+ AND expires_at > unixepoch()
183269
+ AND published_at > ?
183270
+ ORDER BY published_at DESC, id DESC
183271
+ LIMIT ?
183272
+ `;
183273
+ return this.db.prepare(sql).all(sinceTs, limit2).map((r2) => mapRow(r2));
183274
+ }
182908
183275
  searchArticles(opts = {}) {
182909
183276
  const limit2 = Math.min(opts.limit ?? 50, 100);
182910
183277
  const conditions = ["ai_duplicate_of IS NULL"];
@@ -183640,8 +184007,90 @@ class PreferenceStore {
183640
184007
  else
183641
184008
  this.set(NEWS_FILTER_PROMPT_KEY, prompt);
183642
184009
  }
184010
+ getTimezone() {
184011
+ return this.get(USER_TIMEZONE_KEY);
184012
+ }
184013
+ setTimezone(tz) {
184014
+ if (tz.length === 0)
184015
+ this.delete(USER_TIMEZONE_KEY);
184016
+ else
184017
+ this.set(USER_TIMEZONE_KEY, tz);
184018
+ }
184019
+ }
184020
+ var TWEET_FILTER_PROMPT_KEY = "tweets.filter_prompt", NEWS_FILTER_PROMPT_KEY = "news.filter_prompt", USER_TIMEZONE_KEY = "user.timezone";
184021
+
184022
+ // src/scheduler/defaults.ts
184023
+ function detectUserTimezone() {
184024
+ try {
184025
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
184026
+ } catch {
184027
+ return "UTC";
184028
+ }
184029
+ }
184030
+ function buildBuiltInJobs(tz) {
184031
+ return [
184032
+ {
184033
+ name: "morning-briefing",
184034
+ schedule: { kind: "cron", expr: "0 8 * * *", tz },
184035
+ message: BRIEFING_PROMPT,
184036
+ deliver: true
184037
+ },
184038
+ {
184039
+ name: "evening-recap",
184040
+ schedule: { kind: "cron", expr: "0 21 * * *", tz },
184041
+ message: RECAP_PROMPT,
184042
+ deliver: true
184043
+ }
184044
+ ];
183643
184045
  }
183644
- var TWEET_FILTER_PROMPT_KEY = "tweets.filter_prompt", NEWS_FILTER_PROMPT_KEY = "news.filter_prompt";
184046
+ var BRIEFING_PROMPT, RECAP_PROMPT;
184047
+ var init_defaults = __esm(() => {
184048
+ BRIEFING_PROMPT = "Run the morning briefing. Call tools to fetch the latest data: open " + "positions, recent fills / trade history, watchlist, news, and market " + "signals (funding, whale activity, fear & greed). Summarize in under 15 " + "sentences, in the language the user has been chatting in.";
184049
+ RECAP_PROMPT = "Run the end-of-day recap. Call tools to fetch today's trade history and " + "current open positions. Summarize today's PnL, position changes (opened, " + "closed, scaled), and one notable market note. Brief one-liner if I had no " + "activity. Reply in the language the user has been chatting in.";
184050
+ });
184051
+
184052
+ // src/services/timezone.ts
184053
+ function validateTimezone(input) {
184054
+ if (typeof input !== "string") {
184055
+ return { ok: false, error: "Timezone must be a string" };
184056
+ }
184057
+ const trimmed = input.trim();
184058
+ if (!trimmed) {
184059
+ return { ok: false, error: "Timezone cannot be empty" };
184060
+ }
184061
+ if (trimmed.includes("\x00")) {
184062
+ return { ok: false, error: "Timezone contains invalid characters" };
184063
+ }
184064
+ if (trimmed.length > 64) {
184065
+ return { ok: false, error: "Timezone exceeds maximum length" };
184066
+ }
184067
+ try {
184068
+ const resolved = new Intl.DateTimeFormat(undefined, { timeZone: trimmed }).resolvedOptions().timeZone;
184069
+ return { ok: true, tz: resolved };
184070
+ } catch {
184071
+ return { ok: false, error: "Unknown timezone" };
184072
+ }
184073
+ }
184074
+ function detectHostTimezone() {
184075
+ return detectUserTimezone();
184076
+ }
184077
+ function createTimezoneService(prefs) {
184078
+ return {
184079
+ get() {
184080
+ return prefs.getTimezone() ?? "UTC";
184081
+ },
184082
+ set(input) {
184083
+ const result = validateTimezone(input);
184084
+ if (!result.ok)
184085
+ return result;
184086
+ prefs.setTimezone(result.tz);
184087
+ return result;
184088
+ }
184089
+ };
184090
+ }
184091
+ var init_timezone = __esm(() => {
184092
+ init_defaults();
184093
+ });
183645
184094
 
183646
184095
  // src/services/x-follows.ts
183647
184096
  function sleep8(ms2) {
@@ -187422,11 +187871,8 @@ function createAdvancedTradingTools(hl2, watchlist, alerts, priceCache) {
187422
187871
  async execute(_toolCallId, params) {
187423
187872
  try {
187424
187873
  const upper2 = params.symbol.toUpperCase();
187425
- try {
187426
- await hl2.getTicker(upper2);
187427
- } catch {
187874
+ if (!hl2.isKnownSymbol(upper2))
187428
187875
  return errorResult(`Symbol ${upper2} not found on Hyperliquid`);
187429
- }
187430
187876
  const item = await watchlist.add(upper2, params.notes);
187431
187877
  return textResult(`Added ${item.symbol} to watchlist.${item.notes ? ` Notes: ${item.notes}` : ""}`);
187432
187878
  } catch (e2) {
@@ -187493,11 +187939,8 @@ function createAdvancedTradingTools(hl2, watchlist, alerts, priceCache) {
187493
187939
  try {
187494
187940
  const cond = params.condition;
187495
187941
  const upper2 = params.symbol.toUpperCase();
187496
- try {
187497
- await hl2.getTicker(upper2);
187498
- } catch {
187942
+ if (!hl2.isKnownSymbol(upper2))
187499
187943
  return errorResult(`Symbol ${upper2} not found on Hyperliquid.`);
187500
- }
187501
187944
  const lookup2 = await getCurrentPrice(hl2, priceCache, upper2);
187502
187945
  if (lookup2.price !== undefined) {
187503
187946
  const past = cond === "above" ? lookup2.price >= params.price : lookup2.price <= params.price;
@@ -188384,7 +188827,7 @@ class Runner {
188384
188827
  }
188385
188828
  async call(opts) {
188386
188829
  const next = this.inFlight.then(async () => {
188387
- const tools = this.registry.all();
188830
+ const tools = this.registry.taskAgentTools();
188388
188831
  this.agent.state.tools = tools;
188389
188832
  for (const tool2 of tools) {
188390
188833
  if (isOriginAware(tool2))
@@ -188576,7 +189019,9 @@ function emptySnapshot() {
188576
189019
  openOrderIds: [],
188577
189020
  lastRestSyncAtMs: 0,
188578
189021
  recentCancelOids: [],
188579
- recentEmittedFillIds: []
189022
+ recentEmittedFillIds: [],
189023
+ recentEmittedNewsIds: [],
189024
+ lastNewsScanTs: 0
188580
189025
  };
188581
189026
  }
188582
189027
 
@@ -188601,7 +189046,9 @@ class ObserverStateStore {
188601
189046
  openOrderIds: Array.isArray(parsed.openOrderIds) ? parsed.openOrderIds : [],
188602
189047
  lastRestSyncAtMs: typeof parsed.lastRestSyncAtMs === "number" ? parsed.lastRestSyncAtMs : 0,
188603
189048
  recentCancelOids: Array.isArray(parsed.recentCancelOids) ? parsed.recentCancelOids.filter((o10) => typeof o10 === "string") : [],
188604
- recentEmittedFillIds: Array.isArray(parsed.recentEmittedFillIds) ? parsed.recentEmittedFillIds.filter((o10) => typeof o10 === "string") : []
189049
+ recentEmittedFillIds: Array.isArray(parsed.recentEmittedFillIds) ? parsed.recentEmittedFillIds.filter((o10) => typeof o10 === "string") : [],
189050
+ recentEmittedNewsIds: Array.isArray(parsed.recentEmittedNewsIds) ? parsed.recentEmittedNewsIds.filter((o10) => typeof o10 === "string") : [],
189051
+ lastNewsScanTs: typeof parsed.lastNewsScanTs === "number" ? parsed.lastNewsScanTs : 0
188605
189052
  };
188606
189053
  } catch {
188607
189054
  return emptySnapshot();
@@ -188614,7 +189061,7 @@ class ObserverStateStore {
188614
189061
  this.write.run(KEY_SNAPSHOT, JSON.stringify(emptySnapshot()));
188615
189062
  }
188616
189063
  }
188617
- var RECENT_CANCEL_OIDS_CAP = 500, RECENT_FILL_IDS_CAP = 500, KEY_SNAPSHOT = "snapshot";
189064
+ var RECENT_CANCEL_OIDS_CAP = 500, RECENT_FILL_IDS_CAP = 500, RECENT_NEWS_IDS_CAP = 200, KEY_SNAPSHOT = "snapshot";
188618
189065
 
188619
189066
  // src/observer/detect/positions.ts
188620
189067
  function posKey(symbol5, side) {
@@ -188902,6 +189349,36 @@ function detectCanceledOrders(input) {
188902
189349
  return { events, emittedOids };
188903
189350
  }
188904
189351
 
189352
+ // src/observer/detect/news.ts
189353
+ function detectNews(input) {
189354
+ const events = [];
189355
+ const emittedIds = [];
189356
+ const seenThisTick = new Set;
189357
+ for (const article of input.articles) {
189358
+ if (input.priorEmittedIds.has(article.id))
189359
+ continue;
189360
+ if (seenThisTick.has(article.id))
189361
+ continue;
189362
+ if (article.fullSummary === null)
189363
+ continue;
189364
+ events.push({
189365
+ type: "news",
189366
+ detectedAt: input.nowMs,
189367
+ articleId: article.id,
189368
+ title: article.title,
189369
+ summary: article.fullSummary,
189370
+ source: article.sourceId,
189371
+ url: article.url,
189372
+ publishedAt: article.publishedAt * 1000,
189373
+ importance: article.importance,
189374
+ coins: [...article.coins]
189375
+ });
189376
+ emittedIds.push(article.id);
189377
+ seenThisTick.add(article.id);
189378
+ }
189379
+ return { events, emittedIds };
189380
+ }
189381
+
188905
189382
  // src/observer/diff.ts
188906
189383
  function diffSnapshot(input) {
188907
189384
  const positionsR = detectPositions({
@@ -188933,18 +189410,25 @@ function diffSnapshot(input) {
188933
189410
  prices: input.prices,
188934
189411
  nowMs: input.nowMs
188935
189412
  });
189413
+ const newsR = detectNews({
189414
+ articles: input.articles,
189415
+ priorEmittedIds: new Set(input.prior.recentEmittedNewsIds),
189416
+ nowMs: input.nowMs
189417
+ });
188936
189418
  return {
188937
189419
  events: [
188938
189420
  ...positionsR.events,
188939
189421
  ...fillsR.events,
188940
189422
  ...fallbackEvents,
188941
189423
  ...cancelR.events,
188942
- ...priceTargetR.events
189424
+ ...priceTargetR.events,
189425
+ ...newsR.events
188943
189426
  ],
188944
189427
  nextPositions: positionsR.nextPositions,
188945
189428
  firedAlertIds: priceTargetR.firedIds,
188946
189429
  emittedCancelOids: cancelR.emittedOids,
188947
- emittedFillIds: fillsR.emittedFillIds
189430
+ emittedFillIds: fillsR.emittedFillIds,
189431
+ emittedNewsIds: newsR.emittedIds
188948
189432
  };
188949
189433
  }
188950
189434
  var init_diff = () => {};
@@ -189060,7 +189544,8 @@ var init_judge2 = __esm(() => {
189060
189544
  Type.Literal("order_canceled"),
189061
189545
  Type.Literal("liquidation_risk"),
189062
189546
  Type.Literal("pnl_snapshot"),
189063
- Type.Literal("price_alert")
189547
+ Type.Literal("price_alert"),
189548
+ Type.Literal("news")
189064
189549
  ];
189065
189550
  JudgeResponseSchema = Type.Object({
189066
189551
  decision: Type.Union([Type.Literal("fire"), Type.Literal("silent")]),
@@ -189072,6 +189557,42 @@ var init_judge2 = __esm(() => {
189072
189557
  });
189073
189558
  });
189074
189559
 
189560
+ // src/observer/pnl-drift.ts
189561
+ function decidePnlDrift(input) {
189562
+ const { state, currentPnl, lastUserActivityMs, nowMs } = input;
189563
+ if (state.lastSnapshotPnl === null) {
189564
+ return { fire: false, nextSnapshotPnl: currentPnl, reason: "seed_baseline" };
189565
+ }
189566
+ const base = Math.max(Math.abs(state.lastSnapshotPnl), PNL_DRIFT_MIN_BASE);
189567
+ const deltaPct = (currentPnl - state.lastSnapshotPnl) / base;
189568
+ const absDeltaPct = Math.abs(deltaPct);
189569
+ if (absDeltaPct < PNL_DRIFT_THRESHOLD_PCT) {
189570
+ return { fire: false, nextSnapshotPnl: state.lastSnapshotPnl, reason: "below_threshold" };
189571
+ }
189572
+ const idleMs = lastUserActivityMs === null ? SENTINEL_MAX_IDLE_MS : nowMs - lastUserActivityMs;
189573
+ if (idleMs < PNL_DRIFT_IDLE_GATE_MS) {
189574
+ return { fire: false, nextSnapshotPnl: state.lastSnapshotPnl, reason: "user_active" };
189575
+ }
189576
+ if (state.lastSentAtMs !== null && nowMs - state.lastSentAtMs < PNL_DRIFT_COOLDOWN_MS) {
189577
+ return { fire: false, nextSnapshotPnl: state.lastSnapshotPnl, reason: "cooldown" };
189578
+ }
189579
+ const event2 = {
189580
+ type: "portfolio_pnl_drift",
189581
+ detectedAt: nowMs,
189582
+ fromPnl: state.lastSnapshotPnl,
189583
+ toPnl: currentPnl,
189584
+ deltaPct,
189585
+ idleMs
189586
+ };
189587
+ return { fire: true, event: event2, nextSnapshotPnl: currentPnl };
189588
+ }
189589
+ var PNL_DRIFT_THRESHOLD_PCT = 0.15, PNL_DRIFT_IDLE_GATE_MS, PNL_DRIFT_COOLDOWN_MS, PNL_DRIFT_MIN_BASE = 10, SENTINEL_MAX_IDLE_MS;
189590
+ var init_pnl_drift = __esm(() => {
189591
+ PNL_DRIFT_IDLE_GATE_MS = 2 * 60 * 60 * 1000;
189592
+ PNL_DRIFT_COOLDOWN_MS = 6 * 60 * 60 * 1000;
189593
+ SENTINEL_MAX_IDLE_MS = 7 * 24 * 60 * 60 * 1000;
189594
+ });
189595
+
189075
189596
  // src/channels/base.ts
189076
189597
  class BaseChannel {
189077
189598
  config;
@@ -193876,6 +194397,7 @@ function stripCustomTags(text) {
193876
194397
  out = out.replace(/<verdict\s*[^>]*>([\s\S]*?)<\/verdict>/gi, `${SENTINEL_I_OPEN}$1${SENTINEL_I_CLOSE}`);
193877
194398
  out = out.replace(/(\n)(\x00I_OPEN\x00(?:\uD83D\uDC02 |\uD83D\uDC3B |\u3030\uFE0F )?)/g, `$1
193878
194399
  $2`);
194400
+ out = out.replace(/<ask_user_question\s*>([\s\S]*?)<\/ask_user_question>/gi, (_full, inner) => formatAskFallback(inner));
193879
194401
  const chartHint = (attrs) => {
193880
194402
  const symMatch = /\bsymbol\s*=\s*"([^"]+)"/i.exec(attrs);
193881
194403
  const intMatch = /\binterval\s*=\s*"([^"]+)"/i.exec(attrs);
@@ -193896,6 +194418,43 @@ $2`);
193896
194418
  out = out.replace(/<[a-zA-Z][^>]*$/g, "");
193897
194419
  return out;
193898
194420
  }
194421
+ function formatAskFallback(inner) {
194422
+ const questionRe = /<question>([\s\S]*?)<\/question>/gi;
194423
+ const titleRe = /<title>([\s\S]*?)<\/title>/i;
194424
+ const optionsRe = /<options>([\s\S]*?)<\/options>/i;
194425
+ const optionRe = /<option>([\s\S]*?)<\/option>/gi;
194426
+ const lines = [];
194427
+ let m6;
194428
+ let i = 1;
194429
+ while ((m6 = questionRe.exec(inner)) !== null) {
194430
+ const body = m6[1];
194431
+ const title = titleRe.exec(body)?.[1]?.trim();
194432
+ if (!title)
194433
+ continue;
194434
+ let entry = `${i}. ${title}`;
194435
+ const optionsBlock = optionsRe.exec(body)?.[1];
194436
+ if (optionsBlock) {
194437
+ const opts = [];
194438
+ optionRe.lastIndex = 0;
194439
+ let om2;
194440
+ while ((om2 = optionRe.exec(optionsBlock)) !== null) {
194441
+ const v4 = om2[1].trim();
194442
+ if (v4)
194443
+ opts.push(v4);
194444
+ }
194445
+ if (opts.length > 0)
194446
+ entry += `
194447
+ Options: ${opts.join(" / ")}`;
194448
+ }
194449
+ lines.push(entry);
194450
+ i++;
194451
+ }
194452
+ if (lines.length === 0)
194453
+ return "";
194454
+ return `
194455
+ ${lines.join(`
194456
+ `)}`;
194457
+ }
193899
194458
  var CHART_RE_PAIRED, CHART_RE_SELF;
193900
194459
  var init_tags = __esm(() => {
193901
194460
  CHART_RE_PAIRED = /<chart\s*([^>]*)>([\s\S]*?)<\/chart>/gi;
@@ -195305,6 +195864,7 @@ class ObserverLoop {
195305
195864
  store;
195306
195865
  cachedRest = null;
195307
195866
  lastProactiveAtMs;
195867
+ pnlDriftState = { lastSnapshotPnl: null, lastSentAtMs: null };
195308
195868
  constructor(deps) {
195309
195869
  this.deps = deps;
195310
195870
  this.store = new ObserverStateStore(deps.db);
@@ -195341,6 +195901,7 @@ class ObserverLoop {
195341
195901
  const currentOpenOrderIds = rest4.openOrders.map((o10) => o10.orderId);
195342
195902
  const prices = this.deps.priceCache.snapshot();
195343
195903
  const alertRules = this.deps.alertRules.list();
195904
+ const articles = rest4.positions.length === 0 ? [] : this.deps.newsService.listRecentRelevant(newsScanSinceTs(prior.lastNewsScanTs, nowMs));
195344
195905
  const diff = diffSnapshot({
195345
195906
  prior,
195346
195907
  positions: rest4.positions,
@@ -195350,19 +195911,42 @@ class ObserverLoop {
195350
195911
  alertRules,
195351
195912
  prices,
195352
195913
  liqProgressThreshold: this.deps.config.liquidationProgressThreshold,
195914
+ articles,
195353
195915
  nowMs
195354
195916
  });
195355
195917
  for (const id of diff.firedAlertIds) {
195356
195918
  this.deps.alertRules.markFired(id, Math.floor(nowMs / 1000));
195357
195919
  }
195358
195920
  const gatedEvents = filterPnlSnapshots(diff.events, prior.positions, nowMs);
195921
+ if (rest4.positions.length === 0) {
195922
+ this.pnlDriftState = { lastSnapshotPnl: null, lastSentAtMs: null };
195923
+ } else {
195924
+ const totalUnrealizedPnl = rest4.positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
195925
+ const session = this.deps.sessionManager.getOrCreate(MAIN_SESSION_KEY);
195926
+ const lastUserActivityMs = session.lastActiveAt?.getTime() ?? null;
195927
+ const driftDecision = decidePnlDrift({
195928
+ state: this.pnlDriftState,
195929
+ currentPnl: totalUnrealizedPnl,
195930
+ lastUserActivityMs,
195931
+ nowMs
195932
+ });
195933
+ if (driftDecision.fire) {
195934
+ gatedEvents.push(driftDecision.event);
195935
+ }
195936
+ this.pnlDriftState = {
195937
+ lastSnapshotPnl: driftDecision.nextSnapshotPnl,
195938
+ lastSentAtMs: driftDecision.fire ? nowMs : this.pnlDriftState.lastSentAtMs
195939
+ };
195940
+ }
195359
195941
  const nextSnapshot = {
195360
195942
  positions: diff.nextPositions,
195361
195943
  lastFillTimestamp: rest4.latestFillTimestamp,
195362
195944
  openOrderIds: currentOpenOrderIds,
195363
195945
  lastRestSyncAtMs: synced ? nowMs : prior.lastRestSyncAtMs,
195364
195946
  recentCancelOids: mergeCancelOids(prior.recentCancelOids, diff.emittedCancelOids),
195365
- recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP)
195947
+ recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP),
195948
+ recentEmittedNewsIds: mergeBoundedIds(prior.recentEmittedNewsIds, diff.emittedNewsIds, RECENT_NEWS_IDS_CAP),
195949
+ lastNewsScanTs: maxArticlePublishedAtSec(articles, prior.lastNewsScanTs)
195366
195950
  };
195367
195951
  const scanCounts = {
195368
195952
  positions: rest4.positions.length,
@@ -195451,6 +196035,7 @@ class ObserverLoop {
195451
196035
  alertRules: this.deps.alertRules.list(),
195452
196036
  prices: this.deps.priceCache.snapshot(),
195453
196037
  liqProgressThreshold: this.deps.config.liquidationProgressThreshold,
196038
+ articles: [],
195454
196039
  nowMs
195455
196040
  });
195456
196041
  this.store.save({
@@ -195459,7 +196044,9 @@ class ObserverLoop {
195459
196044
  openOrderIds: rest4.openOrders.map((o10) => o10.orderId),
195460
196045
  lastRestSyncAtMs: syncDue ? nowMs : prior.lastRestSyncAtMs,
195461
196046
  recentCancelOids: mergeCancelOids(prior.recentCancelOids, diff.emittedCancelOids),
195462
- recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP)
196047
+ recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP),
196048
+ recentEmittedNewsIds: prior.recentEmittedNewsIds,
196049
+ lastNewsScanTs: prior.lastNewsScanTs
195463
196050
  });
195464
196051
  } catch (err) {
195465
196052
  this.deps.logger.warn({ err }, "observer: baseline refresh failed under confirm gate");
@@ -195522,12 +196109,14 @@ class ObserverLoop {
195522
196109
  const m6 = msg;
195523
196110
  if (m6.role !== "user" && m6.role !== "assistant")
195524
196111
  continue;
195525
- if (!Array.isArray(m6.content))
195526
- continue;
195527
196112
  let text = "";
195528
- for (const block of m6.content) {
195529
- if (block?.type === "text" && typeof block.text === "string") {
195530
- text += block.text;
196113
+ if (typeof m6.content === "string") {
196114
+ text = m6.content;
196115
+ } else if (Array.isArray(m6.content)) {
196116
+ for (const block of m6.content) {
196117
+ if (block?.type === "text" && typeof block.text === "string") {
196118
+ text += block.text;
196119
+ }
195531
196120
  }
195532
196121
  }
195533
196122
  if (text.trim().length === 0)
@@ -195597,22 +196186,38 @@ function mapKindFromEventType(t2) {
195597
196186
  return "order_canceled";
195598
196187
  case "order_filled":
195599
196188
  return "order_filled";
196189
+ case "news":
196190
+ return "news";
195600
196191
  default:
195601
196192
  return "proactive";
195602
196193
  }
195603
196194
  }
195604
- var RECENT_CHAT_MAX = 20, MIN_PRICE_PCT_DELTA = 0.5, MIN_COOLDOWN_MS;
196195
+ function newsScanSinceTs(lastScanTs, nowMs) {
196196
+ const slidingFloor = Math.floor(nowMs / 1000) - NEWS_LOOKBACK_FLOOR_SEC;
196197
+ return lastScanTs > slidingFloor ? lastScanTs : slidingFloor;
196198
+ }
196199
+ function maxArticlePublishedAtSec(articles, prior) {
196200
+ let max = prior;
196201
+ for (const a of articles) {
196202
+ if (a.publishedAt > max)
196203
+ max = a.publishedAt;
196204
+ }
196205
+ return max;
196206
+ }
196207
+ var RECENT_CHAT_MAX = 20, MIN_PRICE_PCT_DELTA = 0.5, MIN_COOLDOWN_MS, NEWS_LOOKBACK_FLOOR_SEC;
195605
196208
  var init_loop = __esm(() => {
195606
196209
  init_diff();
195607
196210
  init_judge2();
196211
+ init_pnl_drift();
195608
196212
  init_channels();
195609
196213
  init_types5();
195610
196214
  MIN_COOLDOWN_MS = 60 * 60 * 1000;
196215
+ NEWS_LOOKBACK_FLOOR_SEC = 30 * 60;
195611
196216
  });
195612
196217
 
195613
196218
  // src/runtime.ts
195614
196219
  import { join as join14 } from "path";
195615
- import { existsSync as existsSync18, mkdirSync as mkdirSync14, copyFileSync as copyFileSync2, readdirSync as readdirSync7 } from "fs";
196220
+ import { existsSync as existsSync17, mkdirSync as mkdirSync13, copyFileSync as copyFileSync2, readdirSync as readdirSync7 } from "fs";
195616
196221
  async function createRuntime(options) {
195617
196222
  const configPath = options.configPath ?? getConfigPath();
195618
196223
  const rawConfig = loadConfig(configPath);
@@ -195652,12 +196257,14 @@ async function createRuntime(options) {
195652
196257
  const skillsLoader = new SkillsLoader(workspaceDir, builtinSkillsDir);
195653
196258
  const skillService = new SkillService(db2, skillsLoader);
195654
196259
  skillService.syncState();
195655
- const contextBuilder = new ContextBuilder({ workspaceDir, model: config3.model }, memoryStore, skillsLoader);
196260
+ const preferenceStore = new PreferenceStore(db2, logger.child({ module: "prefs" }));
196261
+ const timezoneService = createTimezoneService(preferenceStore);
196262
+ const contextBuilder = new ContextBuilder({ workspaceDir, model: config3.model, getTimezone: () => timezoneService.get() }, memoryStore, skillsLoader);
195656
196263
  contextBuilder.setDisabledSkillsProvider(() => skillService.getDisabledNames());
195657
- const cronService = new CronService(getCronStorePath());
196264
+ const cronService = new CronService(db2);
195658
196265
  const tools = createToolRegistry(security2, {
195659
196266
  cronService,
195660
- defaultTimezone: config3.cron.timezone,
196267
+ timezoneService,
195661
196268
  memoryStore,
195662
196269
  logger: logger.child({ module: "tool" })
195663
196270
  });
@@ -195668,7 +196275,6 @@ async function createRuntime(options) {
195668
196275
  const watchlistService = new WatchlistService(db2);
195669
196276
  const newsService = new NewsService(db2, watchlistService, credentials2, logger.child({ module: "news" }));
195670
196277
  const tweetService = new TweetService(db2, logger.child({ module: "tweets" }));
195671
- const preferenceStore = new PreferenceStore(db2, logger.child({ module: "prefs" }));
195672
196278
  const xQueryIdCache = new XQueryIdCache;
195673
196279
  const xFollowService = new XFollowService(db2, credentials2, xQueryIdCache, logger.child({ module: "x-follows" }));
195674
196280
  const taIndicators = new TaIndicatorService(tradingClient);
@@ -195707,7 +196313,7 @@ async function createRuntime(options) {
195707
196313
  eventBus,
195708
196314
  logger,
195709
196315
  customModelRegistry,
195710
- confirmDeps: { getConfirmService: () => confirmServiceRef }
196316
+ confirmDeps: { getConfirmService: () => confirmServiceRef, approvalManager }
195711
196317
  });
195712
196318
  const taskAgent = createAgent({
195713
196319
  config: config3,
@@ -195724,7 +196330,8 @@ async function createRuntime(options) {
195724
196330
  logger: logger.child({ module: "task-agent" }),
195725
196331
  customModelRegistry,
195726
196332
  confirmDeps: { getConfirmService: () => null },
195727
- bypassConfirm: true
196333
+ bypassConfirm: true,
196334
+ taskMode: true
195728
196335
  });
195729
196336
  const runner = new Runner(taskAgent, sessionManager, tools, logger.child({ module: "runner" }));
195730
196337
  const rssDiscoveryService = new RssDiscoveryService(runner, logger.child({ module: "rss-discovery" }));
@@ -195791,7 +196398,7 @@ async function createRuntime(options) {
195791
196398
  tools.register(t2);
195792
196399
  contextBuilder.setTools(tools.all().map((t2) => ({ name: t2.name, description: t2.description })));
195793
196400
  agent3.state.tools = tools.all();
195794
- taskAgent.state.tools = tools.all();
196401
+ taskAgent.state.tools = tools.taskAgentTools();
195795
196402
  setupClaudeCliProvider({
195796
196403
  config: config3,
195797
196404
  logger,
@@ -195831,6 +196438,7 @@ async function createRuntime(options) {
195831
196438
  config: config3.observer,
195832
196439
  tradingClient,
195833
196440
  alertRules,
196441
+ newsService,
195834
196442
  notifications,
195835
196443
  priceCache,
195836
196444
  approvalManager,
@@ -195876,6 +196484,7 @@ async function createRuntime(options) {
195876
196484
  rssDiscoveryService,
195877
196485
  tweetService,
195878
196486
  preferenceStore,
196487
+ timezoneService,
195879
196488
  xFollowService,
195880
196489
  skillService,
195881
196490
  chartSeries,
@@ -195909,16 +196518,16 @@ function getApiKey(oauthManager, credentials2, customModelRegistry) {
195909
196518
  };
195910
196519
  }
195911
196520
  function createAgent(opts) {
195912
- const initialOptions = buildAgentOptions(opts.config, opts.model, opts.security, opts.leakDetector, opts.oauthManager, opts.tools, opts.systemPrompt, opts.credentials, opts.extraReadDirs, opts.approvalManager, opts.eventBus, opts.logger, opts.customModelRegistry, opts.confirmDeps, opts.bypassConfirm ?? false);
196521
+ const initialOptions = buildAgentOptions(opts.config, opts.model, opts.security, opts.leakDetector, opts.oauthManager, opts.tools, opts.systemPrompt, opts.credentials, opts.extraReadDirs, opts.approvalManager, opts.eventBus, opts.logger, opts.customModelRegistry, opts.confirmDeps, opts.bypassConfirm ?? false, opts.taskMode ?? false);
195913
196522
  return new Agent(initialOptions);
195914
196523
  }
195915
- function buildAgentOptions(config3, model, security2, leakDetector, oauthManager, tools, systemPrompt, credentials2, extraReadDirs, approvalManager, eventBus, logger, customModelRegistry, confirmDeps, bypassConfirm = false) {
196524
+ function buildAgentOptions(config3, model, security2, leakDetector, oauthManager, tools, systemPrompt, credentials2, extraReadDirs, approvalManager, eventBus, logger, customModelRegistry, confirmDeps, bypassConfirm = false, taskMode = false) {
195916
196525
  const effectiveThinkingLevel = shouldForceThinkingOff({ id: model.id, baseUrl: model.baseUrl ?? "" }) ? "off" : config3.agent.thinkingLevel;
195917
196526
  return {
195918
196527
  initialState: {
195919
196528
  systemPrompt,
195920
196529
  model,
195921
- tools: config3.provider === "claude-cli" ? [] : tools.all(),
196530
+ tools: config3.provider === "claude-cli" ? [] : taskMode ? tools.taskAgentTools() : tools.all(),
195922
196531
  thinkingLevel: effectiveThinkingLevel
195923
196532
  },
195924
196533
  getApiKey: config3.provider === "claude-cli" ? async () => "claude-cli-no-key-needed" : getApiKey(oauthManager, credentials2, customModelRegistry),
@@ -196007,6 +196616,7 @@ async function runBatchedConfirm(assistantMessage, callName, callArgs, batchCach
196007
196616
  let title;
196008
196617
  const lines = [];
196009
196618
  let steps;
196619
+ let extras;
196010
196620
  if (isMulti) {
196011
196621
  title = `Confirm ${confirmable.length} actions?`;
196012
196622
  const stepList = [];
@@ -196017,14 +196627,22 @@ async function runBatchedConfirm(assistantMessage, callName, callArgs, batchCach
196017
196627
  stepList.push(`${head}${tail}`);
196018
196628
  }
196019
196629
  steps = stepList;
196630
+ extras = undefined;
196020
196631
  } else {
196021
196632
  const only = confirmable[0];
196633
+ const args = only.args ?? {};
196634
+ const symbol5 = typeof args.symbol === "string" ? args.symbol : undefined;
196022
196635
  const desc = describeConfirm(only.name, only.args);
196023
196636
  title = desc.title;
196024
196637
  lines.push(...desc.bullets);
196638
+ extras = {
196639
+ wizard: desc.wizard,
196640
+ suggestedValue: desc.suggestedValue,
196641
+ symbol: symbol5
196642
+ };
196025
196643
  }
196026
196644
  try {
196027
- const res = await confirmService.confirm(title, { lines, steps });
196645
+ const res = await confirmService.confirm(title, { lines, steps }, extras);
196028
196646
  return { decision: res.decision, reason: res.reason };
196029
196647
  } catch (err) {
196030
196648
  logger.warn({ err }, "confirm service threw; treating as rejected");
@@ -196173,19 +196791,19 @@ function makeTransformContext(maxTokens) {
196173
196791
  }
196174
196792
  function resolveDefaultBuiltinSkillsDir() {
196175
196793
  const candidate = join14(import.meta.dir, "skills", "builtin");
196176
- return existsSync18(candidate) ? candidate : undefined;
196794
+ return existsSync17(candidate) ? candidate : undefined;
196177
196795
  }
196178
196796
  function seedWorkspaceTemplates(workspaceDir) {
196179
196797
  const templatesDir = join14(import.meta.dir, "templates");
196180
- if (!existsSync18(templatesDir))
196798
+ if (!existsSync17(templatesDir))
196181
196799
  return;
196182
196800
  try {
196183
- mkdirSync14(workspaceDir, { recursive: true });
196801
+ mkdirSync13(workspaceDir, { recursive: true });
196184
196802
  for (const entry of readdirSync7(templatesDir, { withFileTypes: true })) {
196185
196803
  if (!entry.isFile())
196186
196804
  continue;
196187
196805
  const target = join14(workspaceDir, entry.name);
196188
- if (!existsSync18(target))
196806
+ if (!existsSync17(target))
196189
196807
  copyFileSync2(join14(templatesDir, entry.name), target);
196190
196808
  }
196191
196809
  } catch {}
@@ -196237,6 +196855,7 @@ var init_runtime2 = __esm(() => {
196237
196855
  init_news();
196238
196856
  init_rss_discovery();
196239
196857
  init_tweets();
196858
+ init_timezone();
196240
196859
  init_x_follows();
196241
196860
  init_x_query_ids();
196242
196861
  init_ta_indicators();
@@ -196420,14 +197039,6 @@ function getProviderList() {
196420
197039
  });
196421
197040
  }
196422
197041
  }
196423
- list.push({
196424
- id: "claude-cli",
196425
- label: "Claude Code",
196426
- description: "Use Claude Code subscription (no API key)",
196427
- tier: 0,
196428
- tierLabel: TIER_LABELS[0] ?? "Other",
196429
- supportsOAuth: false
196430
- });
196431
197042
  list.push({
196432
197043
  id: "custom",
196433
197044
  label: "Custom",
@@ -196458,9 +197069,6 @@ function getProviderList() {
196458
197069
  return list;
196459
197070
  }
196460
197071
  function getModelList(providerId) {
196461
- if (providerId === "claude-cli") {
196462
- return getClaudeCliModels();
196463
- }
196464
197072
  try {
196465
197073
  const raw = getModels(providerId).map((m6) => ({ id: m6.id, name: m6.name || m6.id }));
196466
197074
  return filterModelCatalog(providerId, raw);
@@ -196471,7 +197079,6 @@ function getModelList(providerId) {
196471
197079
  var OAUTH_PROVIDERS2, PROVIDER_META, TIER_LABELS;
196472
197080
  var init_providers = __esm(() => {
196473
197081
  init_dist();
196474
- init_models6();
196475
197082
  init_model_catalog();
196476
197083
  OAUTH_PROVIDERS2 = new Set(["anthropic", "openai-codex", "github-copilot", "google-gemini-cli", "google-antigravity"]);
196477
197084
  PROVIDER_META = {
@@ -196479,7 +197086,6 @@ var init_providers = __esm(() => {
196479
197086
  anthropic: { label: "Anthropic", description: "Claude Sonnet & Opus (direct)", tier: 0, apiKeyUrl: "https://console.anthropic.com/settings/keys" },
196480
197087
  openai: { label: "OpenAI", description: "GPT-4o, GPT-5 (direct)", tier: 0, apiKeyUrl: "https://platform.openai.com/api-keys" },
196481
197088
  "openai-codex": { label: "OpenAI Codex", description: "ChatGPT subscription (OAuth, no API key)", tier: 0 },
196482
- "claude-cli": { label: "Claude Code", description: "Use Claude Code subscription (no API key)", tier: 0 },
196483
197089
  google: { label: "Google Gemini", description: "Gemini 2.0 Flash & Pro", tier: 0, apiKeyUrl: "https://aistudio.google.com/app/apikey" },
196484
197090
  "google-gemini-cli": { label: "Google Gemini CLI", description: "Gemini via CLI auth", tier: 0 },
196485
197091
  xai: { label: "xAI", description: "Grok 3 & 4", tier: 0, apiKeyUrl: "https://console.x.ai" },
@@ -196516,17 +197122,15 @@ __export(exports_cli_providers, {
196516
197122
  listModels: () => listModels
196517
197123
  });
196518
197124
  function requiresApiKey(provider) {
196519
- if (provider.id === "claude-cli")
196520
- return false;
196521
- if (provider.supportsOAuth)
196522
- return false;
196523
- return true;
197125
+ return !provider.supportsOAuth;
196524
197126
  }
196525
197127
  function listProviders() {
196526
197128
  const builtIn = getProviderList().map((p) => ({
196527
197129
  id: p.id,
196528
197130
  label: p.label,
196529
197131
  description: p.description,
197132
+ tier: p.tier,
197133
+ tierLabel: p.tierLabel,
196530
197134
  requiresApiKey: requiresApiKey(p),
196531
197135
  supportsOAuth: p.supportsOAuth,
196532
197136
  apiKeyUrl: p.apiKeyUrl ?? null
@@ -196537,6 +197141,8 @@ function listProviders() {
196537
197141
  id: name,
196538
197142
  label: name,
196539
197143
  description: "Custom provider (from ~/.ghost/models.json)",
197144
+ tier: 4,
197145
+ tierLabel: "\uD83D\uDD27 Custom",
196540
197146
  requiresApiKey: true,
196541
197147
  supportsOAuth: false,
196542
197148
  apiKeyUrl: null,
@@ -198590,11 +199196,11 @@ ${je2}${r2.trimStart()}`), s2 = 3 + ne(r2.trimStart()).length);
198590
199196
  });
198591
199197
 
198592
199198
  // src/services/os/utils.ts
198593
- import { accessSync, constants, existsSync as existsSync19, mkdirSync as mkdirSync15, realpathSync as realpathSync2 } from "fs";
199199
+ import { accessSync, constants, existsSync as existsSync18, mkdirSync as mkdirSync14, realpathSync as realpathSync2 } from "fs";
198594
199200
  import { join as join15 } from "path";
198595
199201
  import { homedir as homedir3 } from "os";
198596
199202
  function isExecutable(filePath) {
198597
- if (!existsSync19(filePath))
199203
+ if (!existsSync18(filePath))
198598
199204
  return false;
198599
199205
  try {
198600
199206
  accessSync(filePath, constants.X_OK);
@@ -198641,8 +199247,8 @@ function resolveBunPath() {
198641
199247
  return candidates[0];
198642
199248
  }
198643
199249
  function ensureLogDir(dir) {
198644
- if (!existsSync19(dir)) {
198645
- mkdirSync15(dir, { recursive: true });
199250
+ if (!existsSync18(dir)) {
199251
+ mkdirSync14(dir, { recursive: true });
198646
199252
  }
198647
199253
  }
198648
199254
  function defaultLogDir() {
@@ -198708,7 +199314,7 @@ __export(exports_launchd, {
198708
199314
  LaunchdController: () => LaunchdController
198709
199315
  });
198710
199316
  import { spawnSync } from "child_process";
198711
- import { existsSync as existsSync20, unlinkSync as unlinkSync6, writeFileSync as writeFileSync11, rmSync as rmSync5 } from "fs";
199317
+ import { existsSync as existsSync19, unlinkSync as unlinkSync6, writeFileSync as writeFileSync10, rmSync as rmSync5 } from "fs";
198712
199318
  import { join as join16 } from "path";
198713
199319
  import { homedir as homedir4 } from "os";
198714
199320
  function plistPath() {
@@ -198758,7 +199364,7 @@ class LaunchdController {
198758
199364
  stderrLog,
198759
199365
  env: opts.env ?? {}
198760
199366
  });
198761
- writeFileSync11(definitionPath, plist, { encoding: "utf8", mode: 420 });
199367
+ writeFileSync10(definitionPath, plist, { encoding: "utf8", mode: 420 });
198762
199368
  const domain2 = guiDomain();
198763
199369
  const uid = process.getuid?.() ?? 0;
198764
199370
  const serviceTarget = `${domain2}/${LABEL}`;
@@ -198808,12 +199414,12 @@ class LaunchdController {
198808
199414
  const domain2 = guiDomain();
198809
199415
  const definition = plistPath();
198810
199416
  launchctl(["bootout", domain2, definition]);
198811
- if (existsSync20(definition)) {
199417
+ if (existsSync19(definition)) {
198812
199418
  unlinkSync6(definition);
198813
199419
  }
198814
199420
  if (opts.purgeLogs) {
198815
199421
  const logDir = defaultLogDir();
198816
- if (existsSync20(logDir)) {
199422
+ if (existsSync19(logDir)) {
198817
199423
  rmSync5(logDir, { recursive: true, force: true });
198818
199424
  }
198819
199425
  }
@@ -198897,8 +199503,8 @@ __export(exports_systemd, {
198897
199503
  SystemdController: () => SystemdController
198898
199504
  });
198899
199505
  import { spawnSync as spawnSync2 } from "child_process";
198900
- import { copyFileSync as copyFileSync3, existsSync as existsSync21, mkdirSync as mkdirSync16, rmSync as rmSync6, unlinkSync as unlinkSync7, writeFileSync as writeFileSync12 } from "fs";
198901
- import { dirname as dirname9, join as join17 } from "path";
199506
+ import { copyFileSync as copyFileSync3, existsSync as existsSync20, mkdirSync as mkdirSync15, rmSync as rmSync6, unlinkSync as unlinkSync7, writeFileSync as writeFileSync11 } from "fs";
199507
+ import { dirname as dirname8, join as join17 } from "path";
198902
199508
  import { homedir as homedir5 } from "os";
198903
199509
  function unitPath() {
198904
199510
  return join17(homedir5(), ".config", "systemd", "user", SERVICE_NAME);
@@ -198942,11 +199548,11 @@ class SystemdController {
198942
199548
  assertSystemdAvailable();
198943
199549
  ensureLogDir(opts.logDir);
198944
199550
  const path4 = unitPath();
198945
- const dir = dirname9(path4);
198946
- if (!existsSync21(dir)) {
198947
- mkdirSync16(dir, { recursive: true });
199551
+ const dir = dirname8(path4);
199552
+ if (!existsSync20(dir)) {
199553
+ mkdirSync15(dir, { recursive: true });
198948
199554
  }
198949
- if (existsSync21(path4)) {
199555
+ if (existsSync20(path4)) {
198950
199556
  copyFileSync3(path4, `${path4}.bak`);
198951
199557
  }
198952
199558
  const unit = buildUnit({
@@ -198959,7 +199565,7 @@ class SystemdController {
198959
199565
  GHOST_LOG_DIR: opts.logDir
198960
199566
  }
198961
199567
  });
198962
- writeFileSync12(path4, unit, "utf8");
199568
+ writeFileSync11(path4, unit, "utf8");
198963
199569
  const reload = systemctlStrict("daemon-reload");
198964
199570
  if (!reload.ok) {
198965
199571
  const msg = `daemon-reload failed: ${reload.stderr || reload.stdout}`;
@@ -199005,7 +199611,7 @@ class SystemdController {
199005
199611
  }
199006
199612
  for (const file3 of [path4, `${path4}.bak`]) {
199007
199613
  try {
199008
- if (existsSync21(file3)) {
199614
+ if (existsSync20(file3)) {
199009
199615
  unlinkSync7(file3);
199010
199616
  }
199011
199617
  } catch {
@@ -199016,7 +199622,7 @@ class SystemdController {
199016
199622
  if (opts.purgeLogs) {
199017
199623
  const logDir = defaultLogDir();
199018
199624
  try {
199019
- if (existsSync21(logDir)) {
199625
+ if (existsSync20(logDir)) {
199020
199626
  rmSync6(logDir, { recursive: true, force: true });
199021
199627
  }
199022
199628
  } catch {
@@ -199034,7 +199640,7 @@ class SystemdController {
199034
199640
  if (stdout === "active") {
199035
199641
  return "running";
199036
199642
  }
199037
- if (existsSync21(unitPath())) {
199643
+ if (existsSync20(unitPath())) {
199038
199644
  return "stopped";
199039
199645
  }
199040
199646
  return "not-installed";
@@ -199056,7 +199662,7 @@ __export(exports_schtasks, {
199056
199662
  });
199057
199663
  import { spawnSync as spawnSync3 } from "child_process";
199058
199664
  import { Buffer as Buffer2 } from "buffer";
199059
- import { existsSync as existsSync22, mkdirSync as mkdirSync17, writeFileSync as writeFileSync13, rmSync as rmSync7 } from "fs";
199665
+ import { existsSync as existsSync21, mkdirSync as mkdirSync16, writeFileSync as writeFileSync12, rmSync as rmSync7 } from "fs";
199060
199666
  import { join as join18 } from "path";
199061
199667
  import { homedir as homedir6, tmpdir } from "os";
199062
199668
  function launcherPath() {
@@ -199198,7 +199804,7 @@ function buildScheduledTaskXml(invisibleVbs, userId) {
199198
199804
  function writeTaskXml(xml) {
199199
199805
  const xmlPath = join18(tmpdir(), `ghost-task-${process.pid}.xml`);
199200
199806
  const bom = Buffer2.from([255, 254]);
199201
- writeFileSync13(xmlPath, Buffer2.concat([bom, Buffer2.from(xml, "utf16le")]));
199807
+ writeFileSync12(xmlPath, Buffer2.concat([bom, Buffer2.from(xml, "utf16le")]));
199202
199808
  return xmlPath;
199203
199809
  }
199204
199810
  function killOrphanedSupervisorChain() {
@@ -199232,17 +199838,17 @@ class SchtasksController {
199232
199838
  }
199233
199839
  ensureLogDir(opts.logDir);
199234
199840
  const stateDir = join18(homedir6(), ".ghost", "state");
199235
- if (!existsSync22(stateDir)) {
199236
- mkdirSync17(stateDir, { recursive: true });
199841
+ if (!existsSync21(stateDir)) {
199842
+ mkdirSync16(stateDir, { recursive: true });
199237
199843
  }
199238
199844
  const legacy = legacyStartupPath();
199239
- if (existsSync22(legacy)) {
199845
+ if (existsSync21(legacy)) {
199240
199846
  rmSync7(legacy, RM_RETRY_OPTS);
199241
199847
  }
199242
199848
  const launcher = launcherPath();
199243
- writeFileSync13(launcher, buildLauncherCmd(opts.bunPath, opts.execPath, opts.env), { encoding: "utf8" });
199849
+ writeFileSync12(launcher, buildLauncherCmd(opts.bunPath, opts.execPath, opts.env), { encoding: "utf8" });
199244
199850
  const invisibleVbs = invisibleLauncherPath();
199245
- writeFileSync13(invisibleVbs, buildInvisibleVbs(launcher), { encoding: "utf8" });
199851
+ writeFileSync12(invisibleVbs, buildInvisibleVbs(launcher), { encoding: "utf8" });
199246
199852
  const create4 = createScheduledTask(invisibleVbs, taskUser);
199247
199853
  if (create4.status !== 0) {
199248
199854
  return {
@@ -199287,20 +199893,20 @@ class SchtasksController {
199287
199893
  await new Promise((resolve) => setTimeout(resolve, 500));
199288
199894
  schtasks(["/Delete", "/F", "/TN", TASK_NAME]);
199289
199895
  const launcher = launcherPath();
199290
- if (existsSync22(launcher)) {
199896
+ if (existsSync21(launcher)) {
199291
199897
  rmSync7(launcher, RM_RETRY_OPTS);
199292
199898
  }
199293
199899
  const invisibleVbs = invisibleLauncherPath();
199294
- if (existsSync22(invisibleVbs)) {
199900
+ if (existsSync21(invisibleVbs)) {
199295
199901
  rmSync7(invisibleVbs, RM_RETRY_OPTS);
199296
199902
  }
199297
199903
  const legacy = legacyStartupPath();
199298
- if (existsSync22(legacy)) {
199904
+ if (existsSync21(legacy)) {
199299
199905
  rmSync7(legacy, RM_RETRY_OPTS);
199300
199906
  }
199301
199907
  if (opts.purgeLogs) {
199302
199908
  const logDir = defaultLogDir();
199303
- if (existsSync22(logDir)) {
199909
+ if (existsSync21(logDir)) {
199304
199910
  rmSync7(logDir, { recursive: true, ...RM_RETRY_OPTS });
199305
199911
  }
199306
199912
  }
@@ -212192,7 +212798,7 @@ function handleHealth() {
212192
212798
  }
212193
212799
 
212194
212800
  // src/gateway/static.ts
212195
- import { existsSync as existsSync23 } from "fs";
212801
+ import { existsSync as existsSync22 } from "fs";
212196
212802
  import { join as join20, extname as extname3 } from "path";
212197
212803
  function mime2(path4) {
212198
212804
  return MIME[extname3(path4).toLowerCase()] ?? "application/octet-stream";
@@ -212200,7 +212806,7 @@ function mime2(path4) {
212200
212806
  function resolveWebDist(candidates) {
212201
212807
  const list = candidates ?? defaultWebDistCandidates();
212202
212808
  for (const c4 of list) {
212203
- if (existsSync23(join20(c4, "index.html")))
212809
+ if (existsSync22(join20(c4, "index.html")))
212204
212810
  return c4;
212205
212811
  }
212206
212812
  return null;
@@ -212455,7 +213061,7 @@ function semverGt(a, b5) {
212455
213061
  }
212456
213062
 
212457
213063
  // src/update/version.ts
212458
- import { readFileSync as readFileSync17, existsSync as existsSync24 } from "fs";
213064
+ import { readFileSync as readFileSync16, existsSync as existsSync23 } from "fs";
212459
213065
  import { join as join21 } from "path";
212460
213066
  function resolvePackageJsonPath(candidates) {
212461
213067
  const list = candidates ?? [
@@ -212464,7 +213070,7 @@ function resolvePackageJsonPath(candidates) {
212464
213070
  join21(process.cwd(), "package.json")
212465
213071
  ];
212466
213072
  for (const p2 of list) {
212467
- if (existsSync24(p2))
213073
+ if (existsSync23(p2))
212468
213074
  return p2;
212469
213075
  }
212470
213076
  return null;
@@ -212486,7 +213092,7 @@ function getCurrentVersion(pkgPath) {
212486
213092
  if (!resolved)
212487
213093
  return UNKNOWN_VERSION;
212488
213094
  try {
212489
- const pkg = JSON.parse(readFileSync17(resolved, "utf-8"));
213095
+ const pkg = JSON.parse(readFileSync16(resolved, "utf-8"));
212490
213096
  if (pkg.version && pkg.version.length > 0)
212491
213097
  return pkg.version;
212492
213098
  } catch {}
@@ -212543,14 +213149,14 @@ var init_status = __esm(() => {
212543
213149
  });
212544
213150
 
212545
213151
  // src/gateway/memory.ts
212546
- import { readFileSync as readFileSync18, existsSync as existsSync25 } from "fs";
213152
+ import { readFileSync as readFileSync17, existsSync as existsSync24 } from "fs";
212547
213153
  function registerMemoryMethods(register, deps) {
212548
213154
  register("memory.get", async () => {
212549
213155
  const memory = deps.memoryStore.readLongTerm();
212550
213156
  let history = "";
212551
- if (existsSync25(deps.memoryStore.historyFile)) {
213157
+ if (existsSync24(deps.memoryStore.historyFile)) {
212552
213158
  try {
212553
- history = readFileSync18(deps.memoryStore.historyFile, "utf-8");
213159
+ history = readFileSync17(deps.memoryStore.historyFile, "utf-8");
212554
213160
  } catch {}
212555
213161
  }
212556
213162
  return { memory, history };
@@ -212713,6 +213319,22 @@ function registerCronMethods(register, deps) {
212713
213319
  });
212714
213320
  }
212715
213321
 
213322
+ // src/gateway/config.ts
213323
+ function registerConfigMethods(register, deps) {
213324
+ register("config.timezone.get", async () => ({
213325
+ tz: deps.timezoneService.get()
213326
+ }));
213327
+ register("config.timezone.set", async (_ctx, payload) => {
213328
+ const p2 = payload;
213329
+ const result = deps.timezoneService.set(p2.tz);
213330
+ if (!result.ok) {
213331
+ return { ok: false, error: result.error };
213332
+ }
213333
+ const updatedJobs = deps.cronService.updateBuiltinJobsTimezone(result.tz);
213334
+ return { ok: true, tz: result.tz, updatedJobs };
213335
+ });
213336
+ }
213337
+
212716
213338
  // src/gateway/route-orchestrator-error.ts
212717
213339
  function routeOrchestratorError(runId, classified, emit) {
212718
213340
  if (classified.type === "TOOL_BLOCKED") {
@@ -213083,34 +213705,19 @@ function registerTradingMethods(register, deps) {
213083
213705
  return { fills: filtered, window: { startTime, endTime }, capped };
213084
213706
  });
213085
213707
  register("trading.tokens.list", async () => {
213086
- try {
213087
- const tickers = await deps.tradingClient.getAllTickers();
213088
- const prices = {};
213089
- const prevDayPrices = {};
213090
- const maxLeverages = {};
213091
- const tokens = [];
213092
- for (const t10 of tickers) {
213093
- tokens.push(t10.symbol);
213094
- prices[t10.symbol] = t10.markPrice;
213095
- if (t10.prevDayPrice > 0)
213096
- prevDayPrices[t10.symbol] = t10.prevDayPrice;
213097
- const lev = deps.tradingClient.getMaxLeverage(t10.symbol);
213098
- if (typeof lev === "number" && lev > 0)
213099
- maxLeverages[t10.symbol] = lev;
213100
- }
213101
- tokens.sort();
213102
- return { tokens, prices, prevDayPrices, maxLeverages };
213103
- } catch {
213104
- return { tokens: [], prices: {}, prevDayPrices: {}, maxLeverages: {} };
213105
- }
213708
+ return deps.tokensSnapshot.build();
213106
213709
  });
213107
213710
  register("trading.price", async (_ctx, payload) => {
213108
213711
  const params = typeof payload === "object" && payload !== null ? payload : {};
213109
213712
  const symbol5 = typeof params.symbol === "string" ? params.symbol : undefined;
213110
213713
  if (!symbol5)
213111
213714
  return { price: null, symbol: null };
213715
+ const resolved = deps.tradingClient.resolveSymbol(symbol5);
213716
+ const entry = deps.priceCache.get(resolved, 30000);
213717
+ if (entry)
213718
+ return { symbol: resolved, price: entry.price };
213112
213719
  try {
213113
- const ticker = await deps.tradingClient.getTicker(symbol5);
213720
+ const ticker = await deps.tradingClient.getTicker(resolved);
213114
213721
  return { symbol: ticker.symbol, price: ticker.markPrice };
213115
213722
  } catch {
213116
213723
  return { price: null, symbol: symbol5 };
@@ -213129,9 +213736,7 @@ function registerTradingMethods(register, deps) {
213129
213736
  if (!symbol5)
213130
213737
  return { error: "symbol required" };
213131
213738
  const resolved = deps.tradingClient.resolveSymbol(symbol5);
213132
- try {
213133
- await deps.tradingClient.getTicker(resolved);
213134
- } catch {
213739
+ if (!deps.tradingClient.isKnownSymbol(resolved)) {
213135
213740
  return { error: `Symbol ${resolved} not found on Hyperliquid` };
213136
213741
  }
213137
213742
  try {
@@ -213840,7 +214445,7 @@ class CompositePriceFeed {
213840
214445
  this.log.info({ sources: names }, "composite price feed starting");
213841
214446
  await Promise.all(this.sources.map(async (source2) => {
213842
214447
  try {
213843
- await source2.start((symbol5, price) => this.handleTick(source2, symbol5, price));
214448
+ await source2.start((symbol5, price, prevDayPrice) => this.handleTick(source2, symbol5, price, prevDayPrice));
213844
214449
  } catch (err) {
213845
214450
  this.log.warn({ err, source: source2.name }, "source failed to start");
213846
214451
  }
@@ -213871,7 +214476,7 @@ class CompositePriceFeed {
213871
214476
  this.healthySince.clear();
213872
214477
  this.onPrice = null;
213873
214478
  }
213874
- handleTick(source2, symbol5, price) {
214479
+ handleTick(source2, symbol5, price, prevDayPrice) {
213875
214480
  if (!this.onPrice)
213876
214481
  return;
213877
214482
  if (this.currentPrimary === null) {
@@ -213881,7 +214486,7 @@ class CompositePriceFeed {
213881
214486
  }
213882
214487
  if (source2.name !== this.currentPrimary)
213883
214488
  return;
213884
- this.onPrice(symbol5, price);
214489
+ this.onPrice(symbol5, price, prevDayPrice);
213885
214490
  }
213886
214491
  isHealthy(source2) {
213887
214492
  const last = source2.getLastTickAt();
@@ -213960,44 +214565,6 @@ var init_composite3 = __esm(() => {
213960
214565
  init_types6();
213961
214566
  });
213962
214567
 
213963
- // src/services/price-feed/sources/bun-ws-compat.ts
213964
- function applyShim() {
213965
- const proto = globalThis.WebSocket?.prototype;
213966
- if (!proto || proto.__ghostBlobSetterPatched)
213967
- return proto?.__ghostBlobSetterPatched === true;
213968
- const desc = Object.getOwnPropertyDescriptor(proto, "binaryType");
213969
- if (!desc?.set)
213970
- return false;
213971
- const originalSet = desc.set;
213972
- Object.defineProperty(proto, "binaryType", {
213973
- configurable: true,
213974
- enumerable: desc.enumerable ?? false,
213975
- get: desc.get,
213976
- set(value2) {
213977
- if (value2 === "blob") {
213978
- const g7 = globalThis;
213979
- if (!g7.__ghostBlobWarned) {
213980
- g7.__ghostBlobWarned = true;
213981
- console.warn("[ghost] WebSocket.binaryType='blob' silently dropped (Bun compat shim \u2014 see src/services/price-feed/sources/bun-ws-compat.ts)");
213982
- }
213983
- return;
213984
- }
213985
- originalSet.call(this, value2);
213986
- }
213987
- });
213988
- Object.defineProperty(proto, "__ghostBlobSetterPatched", {
213989
- value: true,
213990
- writable: false,
213991
- enumerable: false,
213992
- configurable: false
213993
- });
213994
- return true;
213995
- }
213996
- var BUN_WS_COMPAT_APPLIED;
213997
- var init_bun_ws_compat = __esm(() => {
213998
- BUN_WS_COMPAT_APPLIED = applyShim();
213999
- });
214000
-
214001
214568
  // src/services/price-feed/sources/hyperliquid.ts
214002
214569
  class HyperliquidSource {
214003
214570
  name = "hyperliquid";
@@ -214011,13 +214578,10 @@ class HyperliquidSource {
214011
214578
  healthCheckIntervalMs;
214012
214579
  onTick = null;
214013
214580
  stopped = true;
214014
- transport = null;
214015
- client = null;
214016
- subscription = null;
214581
+ wsSubscription = null;
214017
214582
  wsRetryTimer = null;
214018
214583
  wsRetryCount = 0;
214019
214584
  lastWsTickAt = 0;
214020
- universe = [];
214021
214585
  restTimer = null;
214022
214586
  restPolling = false;
214023
214587
  lastRestTickAt = 0;
@@ -214052,12 +214616,34 @@ class HyperliquidSource {
214052
214616
  restIntervalMs: this.restIntervalMs
214053
214617
  }, "hyperliquid source starting (WS primary, REST dormant)");
214054
214618
  await this.connectWs();
214619
+ if (this.stopped)
214620
+ return;
214621
+ await this.hydrateFromRest();
214055
214622
  if (this.stopped)
214056
214623
  return;
214057
214624
  this.healthTimer = setInterval(() => {
214058
214625
  this.reconcileTransports();
214059
214626
  }, this.healthCheckIntervalMs);
214060
214627
  }
214628
+ async hydrateFromRest() {
214629
+ try {
214630
+ const tickers = await this.tradingClient.getAllTickers();
214631
+ if (this.stopped)
214632
+ return;
214633
+ const callback = this.onTick;
214634
+ if (!callback)
214635
+ return;
214636
+ for (const t10 of tickers) {
214637
+ if (!Number.isFinite(t10.markPrice))
214638
+ continue;
214639
+ const prev = Number.isFinite(t10.prevDayPrice) && t10.prevDayPrice > 0 ? t10.prevDayPrice : undefined;
214640
+ callback(t10.symbol, t10.markPrice, prev);
214641
+ }
214642
+ this.log.info({ count: tickers.length }, "hyperliquid source: REST hydration complete");
214643
+ } catch (err) {
214644
+ this.log.warn({ err }, "hyperliquid source: REST hydration failed; relying on WS");
214645
+ }
214646
+ }
214061
214647
  async stop() {
214062
214648
  if (this.stopped)
214063
214649
  return;
@@ -214077,89 +214663,53 @@ class HyperliquidSource {
214077
214663
  async connectWs() {
214078
214664
  if (this.stopped)
214079
214665
  return;
214080
- let transport = null;
214081
- let client4 = null;
214082
- let subscription = null;
214083
214666
  try {
214084
- transport = new WebSocketTransport({ isTestnet: this.testnet });
214085
- client4 = new SubscriptionClient({ transport });
214086
- await this.refreshUniverse();
214087
- subscription = await client4.assetCtxs((event2) => this.handleAssetCtxsEvent(event2));
214667
+ await this.tradingClient.ensureMeta();
214668
+ if (this.stopped)
214669
+ return;
214670
+ const sub = await this.tradingClient.subscribeAllDexsAssetCtxs((event2) => this.handleAllDexsAssetCtxsEvent(event2));
214088
214671
  if (this.stopped) {
214089
214672
  try {
214090
- await subscription.unsubscribe();
214091
- } catch {}
214092
- try {
214093
- await transport.close();
214673
+ await sub.unsubscribe();
214094
214674
  } catch {}
214095
214675
  return;
214096
214676
  }
214097
- this.transport = transport;
214098
- this.client = client4;
214099
- this.subscription = subscription;
214677
+ this.wsSubscription = sub;
214100
214678
  this.wsRetryCount = 0;
214101
- this.log.info("hyperliquid source: WS connected");
214102
- this.subscription.failureSignal.addEventListener("abort", () => {
214103
- if (this.stopped)
214104
- return;
214105
- this.log.warn("hyperliquid source: WS subscription failed, scheduling retry");
214106
- this.scheduleWsRetry();
214107
- });
214108
- this.transport.socket.addEventListener("terminate", (ev2) => {
214109
- if (this.stopped)
214110
- return;
214111
- this.log.warn({ reason: ev2.detail?.code, cause: serializeError(ev2.detail?.cause) }, "hyperliquid source: WS transport terminated, scheduling retry");
214112
- this.scheduleWsRetry();
214113
- });
214114
- this.transport.socket.addEventListener("error", (ev2) => {
214115
- if (this.stopped)
214116
- return;
214117
- this.log.warn({ event: serializeWsErrorEvent(ev2) }, "hyperliquid source: WS transport error");
214118
- });
214679
+ this.log.info("hyperliquid source: WS connected (allDexsAssetCtxs)");
214119
214680
  } catch (err) {
214120
214681
  this.log.warn({ err }, "hyperliquid source: WS failed to connect");
214121
- if (subscription) {
214122
- try {
214123
- await subscription.unsubscribe();
214124
- } catch {}
214125
- }
214126
- if (transport) {
214127
- try {
214128
- await transport.close();
214129
- } catch {}
214130
- }
214131
214682
  await this.cleanupWs();
214132
214683
  this.scheduleWsRetry();
214133
214684
  }
214134
214685
  }
214135
- async refreshUniverse() {
214136
- try {
214137
- const tickers = await this.tradingClient.getAllTickers();
214138
- this.universe = tickers.map((t10) => t10.symbol);
214139
- } catch (err) {
214140
- this.log.warn({ err }, "hyperliquid source: universe refresh failed");
214141
- }
214142
- }
214143
- handleAssetCtxsEvent(event2) {
214686
+ handleAllDexsAssetCtxsEvent(event2) {
214144
214687
  if (this.stopped)
214145
214688
  return;
214146
214689
  const callback = this.onTick;
214147
214690
  if (!callback)
214148
214691
  return;
214692
+ const dexUniverses = this.tradingClient.getDexUniverses();
214149
214693
  const now = Date.now();
214150
214694
  let anyEmitted = false;
214151
- for (let i = 0;i < event2.ctxs.length; i++) {
214152
- const symbol5 = this.universe[i];
214153
- if (!symbol5)
214154
- continue;
214155
- const raw = event2.ctxs[i]?.markPx;
214156
- if (raw === null || raw === undefined)
214157
- continue;
214158
- const mark = typeof raw === "string" ? parseFloat(raw) : Number(raw);
214159
- if (!Number.isFinite(mark))
214160
- continue;
214161
- callback(symbol5, mark);
214162
- anyEmitted = true;
214695
+ for (const [dex, ctxs] of event2.ctxs) {
214696
+ const universe = dexUniverses.get(dex) ?? [];
214697
+ for (let i = 0;i < ctxs.length; i++) {
214698
+ const symbol5 = universe[i];
214699
+ if (!symbol5)
214700
+ continue;
214701
+ const ctx = ctxs[i];
214702
+ const raw = ctx?.markPx;
214703
+ if (raw === null || raw === undefined)
214704
+ continue;
214705
+ const mark = typeof raw === "string" ? parseFloat(raw) : Number(raw);
214706
+ if (!Number.isFinite(mark))
214707
+ continue;
214708
+ const prevRaw = ctx?.prevDayPx;
214709
+ const prevDay = prevRaw != null ? typeof prevRaw === "string" ? parseFloat(prevRaw) : Number(prevRaw) : undefined;
214710
+ callback(symbol5, mark, Number.isFinite(prevDay) ? prevDay : undefined);
214711
+ anyEmitted = true;
214712
+ }
214163
214713
  }
214164
214714
  if (anyEmitted)
214165
214715
  this.lastWsTickAt = now;
@@ -214179,19 +214729,12 @@ class HyperliquidSource {
214179
214729
  }, delay4);
214180
214730
  }
214181
214731
  async cleanupWs() {
214182
- if (this.subscription) {
214732
+ if (this.wsSubscription) {
214183
214733
  try {
214184
- await this.subscription.unsubscribe();
214734
+ await this.wsSubscription.unsubscribe();
214185
214735
  } catch {}
214186
- this.subscription = null;
214736
+ this.wsSubscription = null;
214187
214737
  }
214188
- if (this.transport) {
214189
- try {
214190
- await this.transport.close();
214191
- } catch {}
214192
- this.transport = null;
214193
- }
214194
- this.client = null;
214195
214738
  }
214196
214739
  activateRest() {
214197
214740
  if (this.stopped)
@@ -214229,7 +214772,8 @@ class HyperliquidSource {
214229
214772
  return;
214230
214773
  if (!Number.isFinite(t10.markPrice))
214231
214774
  continue;
214232
- callback(t10.symbol, t10.markPrice);
214775
+ const prevDay = Number.isFinite(t10.prevDayPrice) && t10.prevDayPrice > 0 ? t10.prevDayPrice : undefined;
214776
+ callback(t10.symbol, t10.markPrice, prevDay);
214233
214777
  anyEmitted = true;
214234
214778
  }
214235
214779
  if (anyEmitted)
@@ -214274,7 +214818,7 @@ class HyperliquidSource {
214274
214818
  this.deactivateRest();
214275
214819
  }
214276
214820
  maybeForceWsReconnect(wsAge) {
214277
- if (!this.subscription)
214821
+ if (!this.wsSubscription)
214278
214822
  return;
214279
214823
  if (this.wsRetryTimer)
214280
214824
  return;
@@ -214288,27 +214832,7 @@ class HyperliquidSource {
214288
214832
  });
214289
214833
  }
214290
214834
  }
214291
- function serializeWsErrorEvent(ev2) {
214292
- const e2 = ev2;
214293
- const underlying = e2.error;
214294
- return {
214295
- type: ev2.type,
214296
- message: typeof e2.message === "string" ? e2.message : undefined,
214297
- error: underlying instanceof Error ? { name: underlying.name, message: underlying.message } : underlying !== undefined ? String(underlying) : undefined
214298
- };
214299
- }
214300
- function serializeError(cause) {
214301
- if (cause === undefined || cause === null)
214302
- return;
214303
- if (cause instanceof Error)
214304
- return `${cause.name}: ${cause.message}`;
214305
- return String(cause);
214306
- }
214307
214835
  var DEFAULT_REST_INTERVAL_MS = 5000, DEFAULT_WS_STALE_MS = 1e4, DEFAULT_WS_STABILITY_MS = 5000, DEFAULT_HEALTH_CHECK_INTERVAL_MS = 1000;
214308
- var init_hyperliquid = __esm(() => {
214309
- init_bun_ws_compat();
214310
- init_mod6();
214311
- });
214312
214836
 
214313
214837
  // src/services/price-feed/symbol-mapping.ts
214314
214838
  function mapBinanceSymbol(binanceSymbol) {
@@ -214581,7 +215105,7 @@ class BinanceSource {
214581
215105
  ws.addEventListener("error", (ev2) => {
214582
215106
  if (this.stopped)
214583
215107
  return;
214584
- this.log.warn({ event: serializeWsErrorEvent2(ev2) }, "binance source: WS error");
215108
+ this.log.warn({ event: serializeWsErrorEvent(ev2) }, "binance source: WS error");
214585
215109
  });
214586
215110
  } catch (err) {
214587
215111
  this.log.warn({ err }, "binance source: WS failed to open");
@@ -214612,7 +215136,7 @@ class BinanceSource {
214612
215136
  const mark = entry?.p;
214613
215137
  if (typeof sym !== "string" || typeof mark !== "string")
214614
215138
  continue;
214615
- const emitted = this.emitMapped(sym, mark, callback);
215139
+ const emitted = this.emitMapped(sym, mark, entry.o, callback);
214616
215140
  if (emitted)
214617
215141
  anyEmitted = true;
214618
215142
  }
@@ -214685,7 +215209,7 @@ class BinanceSource {
214685
215209
  const mark = row?.markPrice;
214686
215210
  if (typeof sym !== "string" || typeof mark !== "string")
214687
215211
  continue;
214688
- const emitted = this.emitMapped(sym, mark, callback);
215212
+ const emitted = this.emitMapped(sym, mark, row.openPrice, callback);
214689
215213
  if (emitted)
214690
215214
  anyEmitted = true;
214691
215215
  }
@@ -214702,7 +215226,7 @@ class BinanceSource {
214702
215226
  }
214703
215227
  }
214704
215228
  }
214705
- emitMapped(binanceSymbol, priceStr, callback) {
215229
+ emitMapped(binanceSymbol, priceStr, prevDayPriceStr, callback) {
214706
215230
  if (!binanceSymbol.endsWith("USDT"))
214707
215231
  return false;
214708
215232
  const base = binanceSymbol.slice(0, -4);
@@ -214719,7 +215243,14 @@ class BinanceSource {
214719
215243
  if (!Number.isFinite(binancePrice))
214720
215244
  return false;
214721
215245
  const hlPrice = binancePrice * mapping.multiplier;
214722
- callback(mapping.hlSymbol, hlPrice);
215246
+ let prevDayPrice;
215247
+ if (prevDayPriceStr !== undefined) {
215248
+ const rawPrev = Number.parseFloat(prevDayPriceStr);
215249
+ if (Number.isFinite(rawPrev) && rawPrev > 0) {
215250
+ prevDayPrice = rawPrev * mapping.multiplier;
215251
+ }
215252
+ }
215253
+ callback(mapping.hlSymbol, hlPrice, prevDayPrice);
214723
215254
  return true;
214724
215255
  }
214725
215256
  reconcileTransports() {
@@ -214751,7 +215282,7 @@ class BinanceSource {
214751
215282
  this.deactivateRest();
214752
215283
  }
214753
215284
  }
214754
- function serializeWsErrorEvent2(ev2) {
215285
+ function serializeWsErrorEvent(ev2) {
214755
215286
  const e2 = ev2;
214756
215287
  const underlying = e2.error;
214757
215288
  return {
@@ -214778,6 +215309,37 @@ var init_binance2 = __esm(() => {
214778
215309
  LEVERAGED_SUFFIX_RE = /(?:UP|DOWN|BULL|BEAR)$/;
214779
215310
  });
214780
215311
 
215312
+ // src/services/tokens-snapshot.ts
215313
+ class TokensSnapshotService {
215314
+ client;
215315
+ priceCache;
215316
+ constructor(client4, priceCache) {
215317
+ this.client = client4;
215318
+ this.priceCache = priceCache;
215319
+ }
215320
+ build() {
215321
+ const assets = this.client.getAllAssets();
215322
+ const prices = {};
215323
+ const prevDayPrices = {};
215324
+ const maxLeverages = {};
215325
+ const tokens = [];
215326
+ for (const { symbol: symbol5, isDelisted } of assets) {
215327
+ const entry = this.priceCache.get(symbol5, 30000);
215328
+ if (entry) {
215329
+ prices[symbol5] = entry.price;
215330
+ if (entry.prevDayPrice !== undefined)
215331
+ prevDayPrices[symbol5] = entry.prevDayPrice;
215332
+ }
215333
+ const lev = this.client.getMaxLeverage(symbol5);
215334
+ if (typeof lev === "number" && lev > 0)
215335
+ maxLeverages[symbol5] = lev;
215336
+ tokens.push(isDelisted ? { symbol: symbol5, isDelisted: true } : { symbol: symbol5 });
215337
+ }
215338
+ tokens.sort((a, b5) => a.symbol.localeCompare(b5.symbol));
215339
+ return { tokens, prices, prevDayPrices, maxLeverages };
215340
+ }
215341
+ }
215342
+
214781
215343
  // node_modules/viem/_esm/utils/address/isAddressEqual.js
214782
215344
  function isAddressEqual(a, b5) {
214783
215345
  if (!isAddress(a, { strict: false }))
@@ -214901,7 +215463,12 @@ function createGateway(gatewayConfig, deps) {
214901
215463
  registerToolsMethods(registry4.register.bind(registry4), { tools: deps.tools });
214902
215464
  registerSessionsMethods(registry4.register.bind(registry4), { sessionManager: deps.sessionManager });
214903
215465
  registerCronMethods(registry4.register.bind(registry4), { cronService: deps.cronService });
214904
- registerTradingMethods(registry4.register.bind(registry4), { tradingClient: deps.tradingClient, walletStore: deps.walletStore, alertRules: deps.alertRules, notifications: deps.notifications, newsService: deps.newsService, rssDiscovery: deps.rssDiscoveryService, tweetService: deps.tweetService, xFollowService: deps.xFollowService, preferenceStore: deps.preferenceStore, watchlist: deps.watchlistService, logger: deps.logger });
215466
+ registerConfigMethods(registry4.register.bind(registry4), {
215467
+ timezoneService: deps.timezoneService,
215468
+ cronService: deps.cronService
215469
+ });
215470
+ const tokensSnapshot = new TokensSnapshotService(deps.tradingClient, deps.priceCache);
215471
+ registerTradingMethods(registry4.register.bind(registry4), { tradingClient: deps.tradingClient, walletStore: deps.walletStore, alertRules: deps.alertRules, notifications: deps.notifications, newsService: deps.newsService, rssDiscovery: deps.rssDiscoveryService, tweetService: deps.tweetService, xFollowService: deps.xFollowService, preferenceStore: deps.preferenceStore, watchlist: deps.watchlistService, logger: deps.logger, tokensSnapshot, priceCache: deps.priceCache });
214905
215472
  registerApprovalMethods(registry4.register.bind(registry4), { approvalManager: deps.approvalManager });
214906
215473
  registerToolApprovalMethods(registry4.register.bind(registry4), { approvalManager: deps.approvalManager });
214907
215474
  registerSkillsMethods(registry4.register.bind(registry4), { skillService: deps.skillService });
@@ -214930,10 +215497,6 @@ function createGateway(gatewayConfig, deps) {
214930
215497
  deps.eventBus.publish(TradingEvents.watchlistChanged({ action, symbol: symbol5 }));
214931
215498
  });
214932
215499
  const lastPrices = new Map;
214933
- let watchedSymbols = new Set(deps.watchlistService.list().map((i) => i.symbol));
214934
- deps.watchlistService.onChanged(() => {
214935
- watchedSymbols = new Set(deps.watchlistService.list().map((i) => i.symbol));
214936
- });
214937
215500
  const priceFeedConfig = deps.config.priceFeed;
214938
215501
  const priceFeedLogger = deps.logger.child({ module: "price-feed" });
214939
215502
  function buildPriceSources() {
@@ -214956,15 +215519,13 @@ function createGateway(gatewayConfig, deps) {
214956
215519
  staleThresholdMs: priceFeedConfig.staleThresholdMs,
214957
215520
  stabilityWindowMs: priceFeedConfig.stabilityWindowMs
214958
215521
  }, priceFeedLogger);
214959
- function broadcastPrice(symbol5, price) {
215522
+ function broadcastPrice(symbol5, price, prevDayPrice) {
215523
+ deps.priceCache.set(symbol5, price, prevDayPrice);
214960
215524
  const prev = lastPrices.get(symbol5);
214961
- if (prev === price)
215525
+ if (prev === price && prevDayPrice === undefined)
214962
215526
  return;
214963
215527
  lastPrices.set(symbol5, price);
214964
- deps.priceCache.set(symbol5, price);
214965
- if (watchedSymbols.has(symbol5)) {
214966
- deps.eventBus.publish(TradingEvents.priceUpdate({ symbol: symbol5, price }));
214967
- }
215528
+ deps.eventBus.publish(TradingEvents.priceUpdate({ symbol: symbol5, price, prevDayPrice }));
214968
215529
  }
214969
215530
  async function startPriceFeed() {
214970
215531
  if (!priceFeedConfig.enabled) {
@@ -214973,7 +215534,7 @@ function createGateway(gatewayConfig, deps) {
214973
215534
  }
214974
215535
  lastPrices.clear();
214975
215536
  try {
214976
- await compositeFeed.start((symbol5, price) => broadcastPrice(symbol5, price));
215537
+ await compositeFeed.start((symbol5, price, prevDayPrice) => broadcastPrice(symbol5, price, prevDayPrice));
214977
215538
  } catch (err) {
214978
215539
  priceFeedLogger.warn({ err }, "composite price feed failed to start");
214979
215540
  }
@@ -215202,7 +215763,6 @@ var init_server = __esm(() => {
215202
215763
  init_wallet_events();
215203
215764
  init_trading_events();
215204
215765
  init_composite3();
215205
- init_hyperliquid();
215206
215766
  init_binance2();
215207
215767
  init_accounts();
215208
215768
  init__esm();
@@ -215229,12 +215789,16 @@ function snapshotRecentUserMessages(sessionManager, limit2) {
215229
215789
  const out = [];
215230
215790
  for (const msg of session.messages) {
215231
215791
  const m6 = msg;
215232
- if (m6.role !== "user" || !Array.isArray(m6.content))
215792
+ if (m6.role !== "user")
215233
215793
  continue;
215234
215794
  let text = "";
215235
- for (const block of m6.content) {
215236
- if (block?.type === "text" && typeof block.text === "string") {
215237
- text += block.text;
215795
+ if (typeof m6.content === "string") {
215796
+ text = m6.content;
215797
+ } else if (Array.isArray(m6.content)) {
215798
+ for (const block of m6.content) {
215799
+ if (block?.type === "text" && typeof block.text === "string") {
215800
+ text += block.text;
215801
+ }
215238
215802
  }
215239
215803
  }
215240
215804
  const trimmed = text.trim();
@@ -215935,6 +216499,7 @@ async function startDaemon(options) {
215935
216499
  tweetService,
215936
216500
  xFollowService,
215937
216501
  preferenceStore,
216502
+ timezoneService,
215938
216503
  security: security2,
215939
216504
  leakDetector,
215940
216505
  skillService,
@@ -215980,6 +216545,7 @@ async function startDaemon(options) {
215980
216545
  tools,
215981
216546
  sessionManager,
215982
216547
  cronService,
216548
+ timezoneService,
215983
216549
  configPath,
215984
216550
  channels: runtime.channelManager.listChannels().map((ch2) => ({ name: ch2.name })),
215985
216551
  tradingClient,
@@ -216021,7 +216587,7 @@ async function startDaemon(options) {
216021
216587
  }));
216022
216588
  runtime.channelManager.startAllChannels();
216023
216589
  if (config3.cron.enableScheduler) {
216024
- cronService.start();
216590
+ cronService.start({ defaults: buildBuiltInJobs(timezoneService.get()) });
216025
216591
  }
216026
216592
  const runner = new BackgroundJobRunner({
216027
216593
  taskAgent: runtime.taskAgent,
@@ -216035,6 +216601,9 @@ async function startDaemon(options) {
216035
216601
  runner.start();
216036
216602
  runtime.xFollowService.onEnable(() => runner.kick("tweet-fetch"));
216037
216603
  await runtime.walletReady;
216604
+ await tradingClient.ensureMeta().catch((err) => {
216605
+ logger.warn({ err }, "ensureMeta on startup failed \u2014 will retry on first use");
216606
+ });
216038
216607
  app.listen({ port: config3.gateway.port, hostname: config3.gateway.host });
216039
216608
  printDaemonStartupBanner({
216040
216609
  runtime,
@@ -216061,6 +216630,7 @@ var init_daemon = __esm(() => {
216061
216630
  init_plugin();
216062
216631
  init_types5();
216063
216632
  init_pairing_events();
216633
+ init_defaults();
216064
216634
  });
216065
216635
 
216066
216636
  // node_modules/colorette/index.cjs
@@ -218686,7 +219256,7 @@ __export(exports_uninstall, {
218686
219256
  WIN_HANDLE_RELEASE_MS: () => WIN_HANDLE_RELEASE_MS,
218687
219257
  SIGKILL_DELAY_MS: () => SIGKILL_DELAY_MS
218688
219258
  });
218689
- import { existsSync as fsExistsSync, readFileSync as readFileSync20, writeFileSync as writeFileSync15, unlinkSync as unlinkSync9, rmSync as fsRmSync } from "fs";
219259
+ import { existsSync as fsExistsSync, readFileSync as readFileSync19, writeFileSync as writeFileSync14, unlinkSync as unlinkSync9, rmSync as fsRmSync } from "fs";
218690
219260
  import { join as join22 } from "path";
218691
219261
  import { homedir as homedir7 } from "os";
218692
219262
  async function runUninstall(deps) {
@@ -219012,8 +219582,8 @@ async function runUninstallCli() {
219012
219582
  return !isCancel(r2) && r2 === true;
219013
219583
  },
219014
219584
  existsSync: fsExistsSync,
219015
- readFile: (p2) => readFileSync20(p2, "utf8"),
219016
- writeFile: (p2, content) => writeFileSync15(p2, content, "utf8"),
219585
+ readFile: (p2) => readFileSync19(p2, "utf8"),
219586
+ writeFile: (p2, content) => writeFileSync14(p2, content, "utf8"),
219017
219587
  unlink: (p2) => unlinkSync9(p2),
219018
219588
  rmSync: (p2) => fsRmSync(p2, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 }),
219019
219589
  spawn: (cmd, args) => {
@@ -219469,7 +220039,7 @@ init_cli_providers();
219469
220039
 
219470
220040
  // src/onboard/wizard.ts
219471
220041
  init_dist8();
219472
- import { existsSync as existsSync28 } from "fs";
220042
+ import { existsSync as existsSync27 } from "fs";
219473
220043
  init_providers();
219474
220044
  init_oauth3();
219475
220045
  init_secrets();
@@ -219483,7 +220053,7 @@ init_dist8();
219483
220053
  init_dist8();
219484
220054
  init_utils8();
219485
220055
  init_logs();
219486
- import { existsSync as existsSync26 } from "fs";
220056
+ import { existsSync as existsSync25 } from "fs";
219487
220057
 
219488
220058
  // src/onboard/steps/service.ts
219489
220059
  async function runServiceStep(deps) {
@@ -219578,7 +220148,7 @@ async function finalizeOnboard(opts) {
219578
220148
  }
219579
220149
  const execPath = resolveGhostExecPath();
219580
220150
  const bunPath = resolveBunPath();
219581
- if (!existsSync26(execPath)) {
220151
+ if (!existsSync25(execPath)) {
219582
220152
  O2.warn(`Ghost executable not found at ${execPath}. Service may fail to start.`);
219583
220153
  }
219584
220154
  const result = await runServiceStep({
@@ -219654,22 +220224,22 @@ async function startForegroundDaemon(logger) {
219654
220224
  init_models_config();
219655
220225
  import {
219656
220226
  chmodSync,
219657
- existsSync as existsSync27,
219658
- mkdirSync as mkdirSync18,
219659
- readFileSync as readFileSync19,
220227
+ existsSync as existsSync26,
220228
+ mkdirSync as mkdirSync17,
220229
+ readFileSync as readFileSync18,
219660
220230
  renameSync as renameSync4,
219661
220231
  unlinkSync as unlinkSync8,
219662
- writeFileSync as writeFileSync14
220232
+ writeFileSync as writeFileSync13
219663
220233
  } from "fs";
219664
- import { dirname as dirname10 } from "path";
220234
+ import { dirname as dirname9 } from "path";
219665
220235
  var MODELS_FILE_MODE = 384;
219666
220236
  var MODELS_DIR_MODE = 448;
219667
220237
  function readModelsConfig(path4) {
219668
- if (!existsSync27(path4))
220238
+ if (!existsSync26(path4))
219669
220239
  return { kind: "missing" };
219670
220240
  let raw;
219671
220241
  try {
219672
- raw = readFileSync19(path4, "utf-8");
220242
+ raw = readFileSync18(path4, "utf-8");
219673
220243
  } catch (err) {
219674
220244
  return { kind: "malformed", reason: describeError2(err) };
219675
220245
  }
@@ -219730,15 +220300,15 @@ function upsertModel(current, incoming) {
219730
220300
  return next;
219731
220301
  }
219732
220302
  function writeAtomic(path4, contents) {
219733
- mkdirSync18(dirname10(path4), { recursive: true, mode: MODELS_DIR_MODE });
220303
+ mkdirSync17(dirname9(path4), { recursive: true, mode: MODELS_DIR_MODE });
219734
220304
  const tmp = `${path4}.tmp-${process.pid}-${Date.now()}`;
219735
220305
  try {
219736
- writeFileSync14(tmp, contents, { mode: MODELS_FILE_MODE });
220306
+ writeFileSync13(tmp, contents, { mode: MODELS_FILE_MODE });
219737
220307
  chmodSync(tmp, MODELS_FILE_MODE);
219738
220308
  renameSync4(tmp, path4);
219739
220309
  } catch (err) {
219740
220310
  try {
219741
- if (existsSync27(tmp))
220311
+ if (existsSync26(tmp))
219742
220312
  unlinkSync8(tmp);
219743
220313
  } catch {}
219744
220314
  throw err;
@@ -219764,6 +220334,22 @@ function applyUpdateModeChanges(existing, overlay) {
219764
220334
  }
219765
220335
 
219766
220336
  // src/onboard/wizard.ts
220337
+ init_timezone();
220338
+ init_database();
220339
+ init_db();
220340
+ init_registry2();
220341
+ async function persistTimezone(tz, logger) {
220342
+ try {
220343
+ const db2 = initDatabase(getDbPath());
220344
+ await runDbMigrations(db2, DB_MIGRATIONS);
220345
+ const prefs = new PreferenceStore(db2, logger.child({ module: "prefs" }));
220346
+ const tzService = createTimezoneService(prefs);
220347
+ tzService.set(tz);
220348
+ db2.close();
220349
+ } catch (err) {
220350
+ logger.warn({ err }, "onboard: failed to persist timezone");
220351
+ }
220352
+ }
219767
220353
  function validateCustomProviderName(name) {
219768
220354
  if (!name)
219769
220355
  return "Provider name is required.";
@@ -219778,31 +220364,7 @@ function validateCustomProviderName(name) {
219778
220364
  function isLocalBaseUrl(url3) {
219779
220365
  return /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(\/|$)/u.test(url3);
219780
220366
  }
219781
- async function validateClaudeCli(binaryPath) {
219782
- const which = Bun.spawnSync({ cmd: ["which", binaryPath] });
219783
- if (which.exitCode !== 0) {
219784
- return { ok: false, error: `Claude Code not found at "${binaryPath}". Install: curl -fsSL https://claude.ai/install.sh | bash` };
219785
- }
219786
- const versionProc = Bun.spawnSync({ cmd: [binaryPath, "--version"] });
219787
- const version3 = versionProc.stdout.toString().trim();
219788
- const authProc = Bun.spawnSync({ cmd: [binaryPath, "auth", "status", "--json"] });
219789
- if (authProc.exitCode !== 0) {
219790
- return { ok: false, version: version3, error: "Not authenticated. Run: claude login" };
219791
- }
219792
- try {
219793
- const auth = JSON.parse(authProc.stdout.toString());
219794
- const authenticated = auth.authenticated ?? auth.loggedIn ?? false;
219795
- if (!authenticated) {
219796
- return { ok: false, version: version3, error: "Not authenticated. Run: claude login" };
219797
- }
219798
- return { ok: true, version: version3, authStatus: auth.plan ?? auth.subscription ?? "authenticated" };
219799
- } catch {
219800
- return { ok: true, version: version3, authStatus: "authenticated" };
219801
- }
219802
- }
219803
- function providerRequiresApiKey(providerId, supportsOAuth) {
219804
- if (providerId === "claude-cli")
219805
- return false;
220367
+ function providerRequiresApiKey(_providerId, supportsOAuth) {
219806
220368
  if (supportsOAuth)
219807
220369
  return false;
219808
220370
  return true;
@@ -219830,6 +220392,8 @@ async function runHeadless(headless, daemonOptions) {
219830
220392
  if (daemonOptions.paper)
219831
220393
  customConfig.paper = daemonOptions.paper;
219832
220394
  saveConfig(customConfig, configPath);
220395
+ const tzForCustom = resolveHeadlessTz(daemonOptions.logger);
220396
+ await persistTimezone(tzForCustom, daemonOptions.logger);
219833
220397
  console.log(`[ghost] Custom provider: ${headless.provider} (from models.json)`);
219834
220398
  console.log(`[ghost] Model: ${modelTrimmed}`);
219835
220399
  if (daemonOptions?.paper) {
@@ -219905,8 +220469,11 @@ async function runHeadless(headless, daemonOptions) {
219905
220469
  await credentials2.set("api_key", apiKey);
219906
220470
  }
219907
220471
  saveConfig(config3, configPath);
220472
+ const tz = resolveHeadlessTz(daemonOptions.logger);
220473
+ await persistTimezone(tz, daemonOptions.logger);
219908
220474
  console.log(`[ghost] Provider: ${providerInfo.label} (${headless.provider})`);
219909
220475
  console.log(`[ghost] Model: ${model}`);
220476
+ console.log(`[ghost] Timezone: ${tz}`);
219910
220477
  if (daemonOptions?.paper) {
219911
220478
  console.log(`[ghost] Mode: Paper trading (${daemonOptions.paper.initialBalance ?? 1e4} USDC)`);
219912
220479
  }
@@ -219914,6 +220481,18 @@ async function runHeadless(headless, daemonOptions) {
219914
220481
  console.log("[ghost] Config saved. Run 'ghost onboard --service' to register the auto-start service, or 'ghost daemon' to start manually.");
219915
220482
  console.log("[ghost] Onboard complete!");
219916
220483
  }
220484
+ function resolveHeadlessTz(logger) {
220485
+ const envTz = process.env["GHOST_TIMEZONE"];
220486
+ if (envTz) {
220487
+ const result = validateTimezone(envTz);
220488
+ if (!result.ok) {
220489
+ console.error(`[ghost] GHOST_TIMEZONE="${envTz}" is invalid: ${result.error}`);
220490
+ process.exit(1);
220491
+ }
220492
+ return result.tz;
220493
+ }
220494
+ return detectHostTimezone();
220495
+ }
219917
220496
  async function runWizard(daemonOptions) {
219918
220497
  if (daemonOptions.headless) {
219919
220498
  const { headless, ...rest4 } = daemonOptions;
@@ -219926,7 +220505,7 @@ async function runWizard(daemonOptions) {
219926
220505
  const credentials2 = new CredentialStore(getCredentialsPath(), secretStore, daemonOptions.logger.child({ module: "credentials" }));
219927
220506
  let config3 = configSchema.parse({});
219928
220507
  let mode = "full";
219929
- if (existsSync28(configPath)) {
220508
+ if (existsSync27(configPath)) {
219930
220509
  const modeAnswer = await _t({
219931
220510
  message: "Existing config found. What would you like to do?",
219932
220511
  options: [
@@ -219945,7 +220524,7 @@ async function runWizard(daemonOptions) {
219945
220524
  This wizard will configure your agent in under 60 seconds.`);
219946
220525
  if (mode === "full" && !daemonOptions.paper) {
219947
220526
  const tradingMode = await _t({
219948
- message: "Step 1/6 \u2014 Select trading mode",
220527
+ message: "Step 1/5 \u2014 Select trading mode",
219949
220528
  options: [
219950
220529
  { value: "paper", label: "Paper trading (simulated, safe to explore)", hint: "10,000 USDC starting balance" },
219951
220530
  { value: "live", label: "Live trading (real funds on Hyperliquid)" }
@@ -219963,11 +220542,11 @@ This wizard will configure your agent in under 60 seconds.`);
219963
220542
  const providers = getProviderList();
219964
220543
  const providerOptions = providers.map((p2) => ({
219965
220544
  value: p2.id,
219966
- label: p2.label,
220545
+ label: `${p2.label} \u2014 ${p2.tierLabel}`,
219967
220546
  hint: p2.description
219968
220547
  }));
219969
220548
  const providerId = await _t({
219970
- message: "Step 2/6 \u2014 Select your AI provider",
220549
+ message: "Step 2/5 \u2014 Select your AI provider",
219971
220550
  options: providerOptions
219972
220551
  });
219973
220552
  if (q(providerId)) {
@@ -219978,18 +220557,6 @@ This wizard will configure your agent in under 60 seconds.`);
219978
220557
  let authMethod = "apikey";
219979
220558
  let customProviderName = "";
219980
220559
  let customApiKey = "";
219981
- if (providerId === "claude-cli") {
219982
- const s1 = fe2();
219983
- s1.start("Validating Claude Code...");
219984
- const validation2 = await validateClaudeCli("claude");
219985
- if (!validation2.ok) {
219986
- s1.stop(`\u2717 ${validation2.error}`);
219987
- pt("Fix the issue above and try again.");
219988
- process.exit(1);
219989
- }
219990
- s1.stop(`\u2713 Claude Code ${validation2.version ?? ""} (${validation2.authStatus})`);
219991
- authMethod = "skip";
219992
- }
219993
220560
  if (providerId === "custom") {
219994
220561
  const nameAnswer = await Ot({
219995
220562
  message: "Provider name (identifier for ~/.ghost/models.json)",
@@ -220036,7 +220603,7 @@ This wizard will configure your agent in under 60 seconds.`);
220036
220603
  { value: "__custom__", label: "Custom model ID (type manually)" }
220037
220604
  ];
220038
220605
  const selected = await _t({
220039
- message: "Step 3/6 \u2014 Select your default model",
220606
+ message: "Step 3/5 \u2014 Select your default model",
220040
220607
  options: modelOptions
220041
220608
  });
220042
220609
  if (q(selected)) {
@@ -220055,7 +220622,7 @@ This wizard will configure your agent in under 60 seconds.`);
220055
220622
  }
220056
220623
  } else {
220057
220624
  const manual = await Ot({
220058
- message: "Step 3/6 \u2014 Enter model ID",
220625
+ message: "Step 3/5 \u2014 Enter model ID",
220059
220626
  placeholder: "e.g. claude-sonnet-4-6"
220060
220627
  });
220061
220628
  if (q(manual)) {
@@ -220068,7 +220635,7 @@ This wizard will configure your agent in under 60 seconds.`);
220068
220635
  let apiKey = "";
220069
220636
  if (providerInfo?.supportsOAuth && authMethod !== "skip") {
220070
220637
  const auth = await _t({
220071
- message: "Step 4/6 \u2014 How do you want to authenticate?",
220638
+ message: "Step 4/5 \u2014 How do you want to authenticate?",
220072
220639
  options: [
220073
220640
  { value: "oauth", label: "OAuth Login (authenticate in browser)", hint: "recommended" },
220074
220641
  { value: "apikey", label: "API Key (paste your key)" },
@@ -220104,7 +220671,7 @@ This wizard will configure your agent in under 60 seconds.`);
220104
220671
  const keyUrl = providerInfo?.apiKeyUrl ? `
220105
220672
  Get your key at: ${providerInfo.apiKeyUrl}` : "";
220106
220673
  const key = await Ot({
220107
- message: `Step 4/6 \u2014 Paste your ${providerInfo?.label ?? providerId} API key${keyUrl}`,
220674
+ message: `Step 4/5 \u2014 Paste your ${providerInfo?.label ?? providerId} API key${keyUrl}`,
220108
220675
  placeholder: "sk-...",
220109
220676
  validate: (v4) => !v4 || v4.length >= 5 ? undefined : "API key seems too short"
220110
220677
  });
@@ -220114,7 +220681,7 @@ This wizard will configure your agent in under 60 seconds.`);
220114
220681
  }
220115
220682
  apiKey = key;
220116
220683
  }
220117
- if (authMethod === "skip" && providerId !== "claude-cli" && providerId !== "custom") {
220684
+ if (authMethod === "skip" && providerId !== "custom") {
220118
220685
  O2.warn("No API key set. Export GHOST_API_KEY before running ghost daemon.");
220119
220686
  }
220120
220687
  if (mode === "update") {
@@ -220172,14 +220739,59 @@ This wizard will configure your agent in under 60 seconds.`);
220172
220739
  config3.paper = daemonOptions.paper;
220173
220740
  }
220174
220741
  saveConfig(config3, configPath);
220742
+ const chosenTz = await promptTimezone();
220743
+ await persistTimezone(chosenTz, daemonOptions.logger);
220175
220744
  O2.success("Configuration saved.");
220176
220745
  if (providerId === "custom") {
220177
220746
  O2.info(`Custom provider "${customProviderName}" written to ${getModelsConfigPath()}`);
220178
220747
  }
220748
+ O2.info(`Timezone: ${chosenTz}`);
220179
220749
  O2.info("Tip: connect channels from the dashboard after starting the daemon.");
220180
220750
  console.log("");
220181
220751
  await finalizeOnboard({ interactive: true, logger: daemonOptions.logger });
220182
220752
  }
220753
+ function listSupportedTimezones() {
220754
+ const fn = Intl.supportedValuesOf;
220755
+ if (typeof fn !== "function")
220756
+ return [];
220757
+ return [...fn("timeZone")].sort();
220758
+ }
220759
+ async function promptTimezone() {
220760
+ const detected = detectHostTimezone();
220761
+ const allZones = listSupportedTimezones();
220762
+ if (allZones.length === 0) {
220763
+ const entered = await Ot({
220764
+ message: "Step 5/5 \u2014 IANA timezone (e.g. America/New_York):",
220765
+ initialValue: detected,
220766
+ validate(value2) {
220767
+ const v4 = validateTimezone(value2);
220768
+ return v4.ok ? undefined : v4.error;
220769
+ }
220770
+ });
220771
+ if (q(entered)) {
220772
+ pt("Setup cancelled.");
220773
+ process.exit(0);
220774
+ }
220775
+ const result = validateTimezone(entered);
220776
+ return result.ok ? result.tz : detected;
220777
+ }
220778
+ const initialValue = allZones.includes(detected) ? detected : allZones[0];
220779
+ const choice = await Ae2({
220780
+ message: "Step 5/5 \u2014 Select your timezone (type to filter)",
220781
+ placeholder: "e.g. berlin, new_york, utc",
220782
+ options: allZones.map((tz) => ({
220783
+ value: tz,
220784
+ label: tz === detected ? `${tz} (detected)` : tz
220785
+ })),
220786
+ initialValue,
220787
+ maxItems: 10
220788
+ });
220789
+ if (q(choice)) {
220790
+ pt("Setup cancelled.");
220791
+ process.exit(0);
220792
+ }
220793
+ return choice;
220794
+ }
220183
220795
  // src/index.ts
220184
220796
  init_errors2();
220185
220797
  init_logger();
@@ -220410,14 +221022,14 @@ async function runSkills(subArgs, opts) {
220410
221022
  const configPath = opts.config ?? getPath();
220411
221023
  const config3 = loadConfig2(configPath);
220412
221024
  const workspaceDir = getWorkspaceDir2();
220413
- const { existsSync: existsSync29 } = await import("fs");
221025
+ const { existsSync: existsSync28 } = await import("fs");
220414
221026
  const { join: join23 } = await import("path");
220415
221027
  let builtinDir;
220416
221028
  if (config3.skills.builtinSkillsDir) {
220417
221029
  builtinDir = expandHome2(config3.skills.builtinSkillsDir);
220418
221030
  } else {
220419
221031
  const candidate = join23(import.meta.dir, "skills", "builtin");
220420
- if (existsSync29(candidate))
221032
+ if (existsSync28(candidate))
220421
221033
  builtinDir = candidate;
220422
221034
  }
220423
221035
  const loader2 = new SkillsLoader2(workspaceDir, builtinDir);