nyxora 26.7.22 → 26.7.23

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 (186) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/bin/nyxora.mjs +7 -1
  3. package/dist/launcher.js +2 -2
  4. package/dist/packages/core/src/agent/osAgent.js +16 -45
  5. package/dist/packages/core/src/agent/web3Agent.js +9 -4
  6. package/dist/packages/core/src/gateway/server.js +44 -1
  7. package/dist/packages/core/src/system/skills/executeShell.js +4 -0
  8. package/dist/packages/core/src/system/skills/executeShellPTY.js +2 -1
  9. package/dist/packages/core/src/utils/dynamicTokenUpdater.js +14 -1
  10. package/dist/packages/core/src/utils/historySanitizer.js +17 -2
  11. package/dist/packages/core/src/web3/skills/checkPortfolio.js +41 -38
  12. package/launcher.ts +2 -2
  13. package/package.json +2 -9
  14. package/packages/core/package.json +1 -1
  15. package/packages/core/src/agent/osAgent.ts +18 -45
  16. package/packages/core/src/agent/web3Agent.ts +9 -4
  17. package/packages/core/src/gateway/server.ts +46 -1
  18. package/packages/core/src/system/skills/executeShell.ts +5 -0
  19. package/packages/core/src/system/skills/executeShellPTY.ts +2 -1
  20. package/packages/core/src/utils/dynamicTokenUpdater.ts +16 -2
  21. package/packages/core/src/utils/historySanitizer.ts +16 -2
  22. package/packages/core/src/web3/skills/checkPortfolio.ts +45 -41
  23. package/packages/dashboard/dist/assets/index-Cxh73Rml.js +87 -0
  24. package/packages/dashboard/dist/assets/index-DLBxY6G0.css +1 -0
  25. package/packages/dashboard/dist/index.html +2 -2
  26. package/packages/dashboard/package.json +1 -1
  27. package/packages/mcp-server/package.json +1 -1
  28. package/packages/policy/package.json +1 -1
  29. package/packages/signer/package.json +1 -1
  30. package/packages/tui/dist/app/App.js +28 -0
  31. package/packages/tui/dist/components/MainArea.js +110 -0
  32. package/packages/tui/dist/components/Sidebar.js +5 -0
  33. package/packages/tui/dist/components/logo.js +50 -0
  34. package/packages/tui/dist/index.js +11 -53
  35. package/packages/tui/package.json +9 -20
  36. package/packages/dashboard/dist/assets/index-AGu4iXxs.css +0 -1
  37. package/packages/dashboard/dist/assets/index-C-jQzkzL.js +0 -87
  38. package/packages/nyxora-ink/ambient.d.ts +0 -92
  39. package/packages/nyxora-ink/dist/entry-exports.js +0 -14294
  40. package/packages/nyxora-ink/index.d.ts +0 -41
  41. package/packages/nyxora-ink/index.js +0 -1
  42. package/packages/nyxora-ink/package.json +0 -59
  43. package/packages/nyxora-ink/text-input.d.ts +0 -2
  44. package/packages/nyxora-ink/text-input.js +0 -1
  45. package/packages/tui/dist/app/createGatewayEventHandler.js +0 -728
  46. package/packages/tui/dist/app/createSlashHandler.js +0 -114
  47. package/packages/tui/dist/app/delegationStore.js +0 -47
  48. package/packages/tui/dist/app/gatewayContext.js +0 -13
  49. package/packages/tui/dist/app/gatewayRecovery.js +0 -19
  50. package/packages/tui/dist/app/inputSelectionStore.js +0 -4
  51. package/packages/tui/dist/app/interfaces.js +0 -6
  52. package/packages/tui/dist/app/overlayStore.js +0 -56
  53. package/packages/tui/dist/app/petFlashStore.js +0 -16
  54. package/packages/tui/dist/app/scroll.js +0 -44
  55. package/packages/tui/dist/app/setupHandoff.js +0 -28
  56. package/packages/tui/dist/app/slash/commands/billing.js +0 -248
  57. package/packages/tui/dist/app/slash/commands/core.js +0 -534
  58. package/packages/tui/dist/app/slash/commands/credits.js +0 -44
  59. package/packages/tui/dist/app/slash/commands/debug.js +0 -40
  60. package/packages/tui/dist/app/slash/commands/ops.js +0 -531
  61. package/packages/tui/dist/app/slash/commands/session.js +0 -481
  62. package/packages/tui/dist/app/slash/commands/setup.js +0 -16
  63. package/packages/tui/dist/app/slash/registry.js +0 -18
  64. package/packages/tui/dist/app/slash/types.js +0 -1
  65. package/packages/tui/dist/app/spawnHistoryStore.js +0 -117
  66. package/packages/tui/dist/app/submissionCore.js +0 -80
  67. package/packages/tui/dist/app/turnController.js +0 -802
  68. package/packages/tui/dist/app/turnStore.js +0 -44
  69. package/packages/tui/dist/app/uiStore.js +0 -36
  70. package/packages/tui/dist/app/useComposerState.js +0 -264
  71. package/packages/tui/dist/app/useConfigSync.js +0 -203
  72. package/packages/tui/dist/app/useInputHandlers.js +0 -483
  73. package/packages/tui/dist/app/useLongRunToolCharms.js +0 -46
  74. package/packages/tui/dist/app/useMainApp.js +0 -851
  75. package/packages/tui/dist/app/usePet.js +0 -219
  76. package/packages/tui/dist/app/useSessionLifecycle.js +0 -268
  77. package/packages/tui/dist/app/useSubmission.js +0 -238
  78. package/packages/tui/dist/app.js +0 -11
  79. package/packages/tui/dist/banner.js +0 -55
  80. package/packages/tui/dist/components/activeSessionSwitcher.js +0 -503
  81. package/packages/tui/dist/components/agentsOverlay.js +0 -443
  82. package/packages/tui/dist/components/appChrome.js +0 -425
  83. package/packages/tui/dist/components/appLayout.js +0 -189
  84. package/packages/tui/dist/components/appOverlays.js +0 -76
  85. package/packages/tui/dist/components/billingOverlay.js +0 -375
  86. package/packages/tui/dist/components/branding.js +0 -143
  87. package/packages/tui/dist/components/fpsOverlay.js +0 -18
  88. package/packages/tui/dist/components/helpHint.js +0 -17
  89. package/packages/tui/dist/components/journey.js +0 -286
  90. package/packages/tui/dist/components/markdown.js +0 -722
  91. package/packages/tui/dist/components/maskedPrompt.js +0 -8
  92. package/packages/tui/dist/components/messageLine.js +0 -98
  93. package/packages/tui/dist/components/modelPicker.js +0 -368
  94. package/packages/tui/dist/components/overlayControls.js +0 -26
  95. package/packages/tui/dist/components/overlayScrollbar.js +0 -41
  96. package/packages/tui/dist/components/petPicker.js +0 -97
  97. package/packages/tui/dist/components/petSprite.js +0 -49
  98. package/packages/tui/dist/components/pluginsHub.js +0 -127
  99. package/packages/tui/dist/components/prompts.js +0 -133
  100. package/packages/tui/dist/components/queuedMessages.js +0 -20
  101. package/packages/tui/dist/components/skillsHub.js +0 -161
  102. package/packages/tui/dist/components/streamingAssistant.js +0 -59
  103. package/packages/tui/dist/components/streamingMarkdown.js +0 -140
  104. package/packages/tui/dist/components/textInput.js +0 -1080
  105. package/packages/tui/dist/components/themed.js +0 -8
  106. package/packages/tui/dist/components/thinking.js +0 -501
  107. package/packages/tui/dist/components/todoPanel.js +0 -36
  108. package/packages/tui/dist/config/env.js +0 -56
  109. package/packages/tui/dist/config/limits.js +0 -22
  110. package/packages/tui/dist/config/timing.js +0 -13
  111. package/packages/tui/dist/content/charms.js +0 -1
  112. package/packages/tui/dist/content/faces.js +0 -17
  113. package/packages/tui/dist/content/fortunes.js +0 -25
  114. package/packages/tui/dist/content/hotkeys.js +0 -34
  115. package/packages/tui/dist/content/placeholders.js +0 -11
  116. package/packages/tui/dist/content/setup.js +0 -14
  117. package/packages/tui/dist/content/verbs.js +0 -37
  118. package/packages/tui/dist/domain/blockLayout.js +0 -95
  119. package/packages/tui/dist/domain/details.js +0 -53
  120. package/packages/tui/dist/domain/messages.js +0 -59
  121. package/packages/tui/dist/domain/paths.js +0 -27
  122. package/packages/tui/dist/domain/providers.js +0 -7
  123. package/packages/tui/dist/domain/roles.js +0 -6
  124. package/packages/tui/dist/domain/slash.js +0 -43
  125. package/packages/tui/dist/domain/usage.js +0 -1
  126. package/packages/tui/dist/domain/viewport.js +0 -33
  127. package/packages/tui/dist/entry.js +0 -124
  128. package/packages/tui/dist/gatewayClient.js +0 -634
  129. package/packages/tui/dist/gatewayTypes.js +0 -1
  130. package/packages/tui/dist/hooks/useCompletion.js +0 -82
  131. package/packages/tui/dist/hooks/useGitBranch.js +0 -54
  132. package/packages/tui/dist/hooks/useInputHistory.js +0 -8
  133. package/packages/tui/dist/hooks/useQueue.js +0 -53
  134. package/packages/tui/dist/hooks/useVirtualHistory.js +0 -416
  135. package/packages/tui/dist/lib/circularBuffer.js +0 -39
  136. package/packages/tui/dist/lib/clipboard.js +0 -137
  137. package/packages/tui/dist/lib/editor.js +0 -57
  138. package/packages/tui/dist/lib/editor.test.js +0 -54
  139. package/packages/tui/dist/lib/emoji.js +0 -45
  140. package/packages/tui/dist/lib/externalCli.js +0 -7
  141. package/packages/tui/dist/lib/externalLink.js +0 -335
  142. package/packages/tui/dist/lib/forceTruecolor.js +0 -46
  143. package/packages/tui/dist/lib/fpsStore.js +0 -32
  144. package/packages/tui/dist/lib/fuzzy.js +0 -129
  145. package/packages/tui/dist/lib/fuzzy.test.js +0 -88
  146. package/packages/tui/dist/lib/gracefulExit.js +0 -36
  147. package/packages/tui/dist/lib/history.js +0 -65
  148. package/packages/tui/dist/lib/inputMetrics.js +0 -158
  149. package/packages/tui/dist/lib/liveProgress.js +0 -51
  150. package/packages/tui/dist/lib/liveProgress.test.js +0 -85
  151. package/packages/tui/dist/lib/mathUnicode.js +0 -685
  152. package/packages/tui/dist/lib/memory.js +0 -159
  153. package/packages/tui/dist/lib/memory.test.js +0 -135
  154. package/packages/tui/dist/lib/memoryMonitor.js +0 -130
  155. package/packages/tui/dist/lib/messages.js +0 -3
  156. package/packages/tui/dist/lib/messages.test.js +0 -21
  157. package/packages/tui/dist/lib/openExternalUrl.js +0 -133
  158. package/packages/tui/dist/lib/openExternalUrl.test.js +0 -162
  159. package/packages/tui/dist/lib/osc52.js +0 -49
  160. package/packages/tui/dist/lib/parentLog.js +0 -53
  161. package/packages/tui/dist/lib/perfPane.js +0 -90
  162. package/packages/tui/dist/lib/platform.js +0 -309
  163. package/packages/tui/dist/lib/precisionWheel.js +0 -21
  164. package/packages/tui/dist/lib/prompt.js +0 -23
  165. package/packages/tui/dist/lib/reasoning.js +0 -40
  166. package/packages/tui/dist/lib/resizeCoalescer.js +0 -46
  167. package/packages/tui/dist/lib/resizeCoalescer.test.js +0 -65
  168. package/packages/tui/dist/lib/rpc.js +0 -33
  169. package/packages/tui/dist/lib/starmapPalette.js +0 -104
  170. package/packages/tui/dist/lib/subagentTree.js +0 -284
  171. package/packages/tui/dist/lib/syntax.js +0 -89
  172. package/packages/tui/dist/lib/terminalModes.js +0 -42
  173. package/packages/tui/dist/lib/terminalParity.js +0 -44
  174. package/packages/tui/dist/lib/terminalSetup.js +0 -317
  175. package/packages/tui/dist/lib/termux.js +0 -24
  176. package/packages/tui/dist/lib/text.js +0 -258
  177. package/packages/tui/dist/lib/text.test.js +0 -39
  178. package/packages/tui/dist/lib/todo.js +0 -2
  179. package/packages/tui/dist/lib/todo.test.js +0 -18
  180. package/packages/tui/dist/lib/viewportStore.js +0 -82
  181. package/packages/tui/dist/lib/virtualHeights.js +0 -113
  182. package/packages/tui/dist/lib/wheelAccel.js +0 -139
  183. package/packages/tui/dist/protocol/interpolation.js +0 -2
  184. package/packages/tui/dist/protocol/paste.js +0 -1
  185. package/packages/tui/dist/theme.js +0 -396
  186. package/packages/tui/dist/types.js +0 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
+ ## [26.7.23]
8
+ ### Dashboard Features & UI
9
+ - **System & Maintenance Module**: Engineered the `System.tsx` component from scratch. The interface now features a sleek, edge-to-edge stacked row list design, completely abandoning the legacy card-based layout for a more professional, native application feel.
10
+ - **Native System Operations**: Implemented deep integration between the React frontend and the Node.js daemon. Users can now natively trigger **Restart Server**, **Shutdown Server**, and **Clear System Cache** directly from the UI without touching the CLI.
11
+ - **OS Autostart Controller**: Introduced a cross-platform Auto-Start toggle in the dashboard. The system dynamically queries and manages `.desktop` (Linux) and `.plist` (macOS) configurations via `GET/POST /api/system/autostart`.
12
+ - **Wallet Balances Fix**: Resolved a data mapping issue in `Wallets.tsx`. The component now correctly utilizes `viem`'s `formatUnits` combined with backend `decimals` and `priceUsd` to flawlessly compute and render real-time fiat values for multi-chain assets. Added a functional Refresh button to sync the latest portfolio states.
13
+ - **Minimalist Aesthetic**: Purged redundant icons/emojis across the System settings to achieve a clean, text-heavy minimalist aesthetic, reserving visual icons strictly for the page header and footer.
14
+
15
+ ### Bug Fixes & Agent Enhancements
16
+ - **Windows CLI Spawn Fix (Critical)**: Resolved a major UX issue for global Windows users where running `nyxora start` would rapidly spawn multiple visible `cmd.exe` and `node.exe` console windows. Implemented `windowsHide: true` across all detached daemon spawns and Cloudflared sub-processes in `nyxora.mjs` and `launcher.ts`, ensuring Nyxora boots perfectly in headless mode on Windows without spamming the user's desktop.
17
+ - **Windows CLI Stop/Restart Fix**: Fixed a critical crash where `nyxora stop` and `nyxora restart` failed on Windows systems. Switched process termination from POSIX-only `-pid` (Process Group Signal) to native Windows `taskkill /pid <PID> /t /f` via `spawnSync`, ensuring all daemon and microservice child processes (like Python ML Engine) are cleanly forcefully terminated without leaving zombie processes.
18
+ - **Smart Anti-Loop Breaker V2.0**: Completely overhauled the agent's anti-loop safety mechanism. Removed rigid hard-limits on specific tool usage (e.g. `write_local_file`) to allow unconstrained complex coding sessions. Replaced it with an intelligent "Soft Warning" system that injects a self-reflection prompt into the LLM's context every 5 repetitions, allowing the AI to organically realize if it's hallucinating and ask the user for help. Also increased global `MAX_TURNS` from 30 to 50 for extended autonomous workflows.
19
+ - **Auto-Scroll Chat Snap Fix**: Resolved a Dashboard UI glitch where LLM text streaming would scroll the container to the top instead of the bottom. Fixed ResizeObserver spacer calculation timing and exact layout recalculation bounds, ensuring smooth and flawless chat autoscrolling identical to the Desktop app.
20
+ ## [26.7.22]
21
+ ### Bug Fixes & Monorepo Enhancements
22
+ - **Global `npm install` Crash Fix (Critical Fix)**: Resolved a severe issue where `nyxora start` would crash immediately after global installation (`npm install -g nyxora`) due to missing dependencies. All required workspace dependencies (e.g., `systeminformation`, messaging bot SDKs) have now been synced and hoisted to the root `package.json`, ensuring the background daemon can boot properly for global users without throwing `Cannot find module` errors.
23
+ - **Dependency Overrides Synchronization**: Fixed dependency conflict warnings during installation by aligning `lodash-es` and `undici` versions with the root workspace `overrides`.
24
+
7
25
  ## [26.7.21]
8
26
  ### Dashboard Features & UI
9
27
  - **Control Panel Expansion**: Engineered 4 brand new native dashboard menus (`Memory`, `Security`, `Wallets`, `Workflows`) transforming the dashboard into a full-fledged agent control panel.
package/bin/nyxora.mjs CHANGED
@@ -73,6 +73,7 @@ async function start() {
73
73
  const child = spawn(cmd, args, {
74
74
  cwd: projectRoot,
75
75
  detached: true,
76
+ windowsHide: true,
76
77
  stdio: ['ignore', out, err],
77
78
  env: { ...process.env, TS_NODE_CACHE: 'false' }
78
79
  });
@@ -91,7 +92,12 @@ async function stop(preserveTracker = false) {
91
92
  if (pid) {
92
93
  console.log(`Stopping Nyxora daemon (PID: ${pid})...`);
93
94
  try {
94
- process.kill(-pid, 'SIGTERM');
95
+ if (process.platform === 'win32') {
96
+ const { spawnSync } = await import('child_process');
97
+ spawnSync('taskkill', ['/pid', pid.toString(), '/t', '/f']);
98
+ } else {
99
+ process.kill(-pid, 'SIGTERM');
100
+ }
95
101
  let attempts = 0;
96
102
  while (isDaemonRunning(pid.toString()) && attempts < 20) {
97
103
  await new Promise(r => setTimeout(r, 100));
package/dist/launcher.js CHANGED
@@ -35,7 +35,7 @@ const spawnService = (name, command, args, env, inheritStdio = false, cwd) => {
35
35
  let crashWindowStart = Date.now();
36
36
  let isShuttingDown = false;
37
37
  const startProcess = () => {
38
- const spawnOpts = { env, stdio: inheritStdio ? 'inherit' : 'pipe' };
38
+ const spawnOpts = { env, stdio: inheritStdio ? 'inherit' : 'pipe', windowsHide: true };
39
39
  if (cwd)
40
40
  spawnOpts.cwd = cwd;
41
41
  child = (0, child_process_1.spawn)(command, args, spawnOpts);
@@ -190,7 +190,7 @@ setTimeout(() => {
190
190
  catch (e) { }
191
191
  if (cfEnabled) {
192
192
  console.log('[Launcher] Starting Auto-Tunnel (Cloudflare) on port 3000...');
193
- const cf = (0, child_process_1.spawn)('npx', ['cloudflared', 'tunnel', '--url', 'http://localhost:3000'], { env, shell: true });
193
+ const cf = (0, child_process_1.spawn)('npx', ['cloudflared', 'tunnel', '--url', 'http://localhost:3000'], { env, shell: true, windowsHide: true });
194
194
  children.push({
195
195
  kill: () => { try {
196
196
  process.kill(cf.pid, 'SIGTERM');
@@ -190,7 +190,7 @@ The user explicitly stated your previous response was WRONG, STALE, or INACCURAT
190
190
  const cachedSystemPrompt = await getSystemPrompt('os', input, sessionId);
191
191
  try {
192
192
  let turnCount = 0;
193
- const MAX_TURNS = 30;
193
+ const MAX_TURNS = 50; // Increased to 50 to allow limitless tool usage without hard crashing
194
194
  let consecutiveToolErrors = 0;
195
195
  let criticHasFired = false; // Critic Pass hanya aktif 1x per request
196
196
  // Anti-loop: track (toolName+argsHash) pairs from the PREVIOUS turn to
@@ -199,15 +199,7 @@ The user explicitly stated your previous response was WRONG, STALE, or INACCURAT
199
199
  // ── Per-tool-name call count tracker ──────────────────────────────────────────────
200
200
  // Catches loops where args differ per turn (e.g. search_web different query each time).
201
201
  const osToolCallCounts = new Map();
202
- const OS_TOOL_CALL_LIMITS = {
203
- 'search_web': 5,
204
- 'search_files': 8,
205
- 'read_local_file': 10,
206
- 'run_terminal_command': 8,
207
- 'edit_local_file': 6,
208
- 'write_local_file': 6,
209
- '__default__': 12,
210
- };
202
+ // OS_TOOL_CALL_LIMITS removed: We now use soft-warnings (self-reflection) instead of hard limits.
211
203
  // P6: Compress history ONCE before loop starts (not per iteration)
212
204
  const rawHistory = exports.logger.getHistory(sessionId);
213
205
  const initialHistory = (0, historySanitizer_1.pruneLoopedHistory)(rawHistory); // Collapse any failed loop runs
@@ -523,19 +515,15 @@ The user explicitly stated your previous response was WRONG, STALE, or INACCURAT
523
515
  }, sessionId);
524
516
  console.log(picocolors_1.default.red('[Anti-Loop] Repeat tool call signature detected across turns. Injecting block.'));
525
517
  }
526
- // Fix B: Per-tool-name count guard stops loops even when args change each turn
518
+ // Fix B: Per-tool-name count soft-warning (prevents hallucination)
527
519
  for (const tc of responseMessage.tool_calls) {
528
520
  const tn = tc.function.name;
529
521
  const count = osToolCallCounts.get(tn) || 0;
530
- const limit = OS_TOOL_CALL_LIMITS[tn] ?? OS_TOOL_CALL_LIMITS['__default__'];
531
- if (count >= limit) {
532
- const stopMsg = `[SYSTEM: LOOP DETECTED] '${tn}' has been called ${count} times this session (limit: ${limit}). ` +
533
- `You are stuck in a loop. STOP calling this tool and produce your final answer using data already collected.`;
534
- exports.logger.addEntry({ role: 'system', content: stopMsg }, sessionId);
535
- const forceStop = `⚠️ Loop breaker: '${tn}' called ${count}x (limit ${limit}). Stopping.`;
536
- exports.logger.addEntry({ role: 'assistant', content: forceStop }, sessionId);
537
- triggerBackgroundReview(sessionId);
538
- return forceStop;
522
+ // Soft Warning every 5 times a tool is used
523
+ if (count > 0 && count % 5 === 0) {
524
+ const warningMsg = `[SYSTEM WARNING] You have used the tool '${tn}' ${count} times in this session. Are you making progress? If you are stuck in a loop of errors or hallucinating fixes without success, STOP using this tool and ask the user for clarification.`;
525
+ exports.logger.addEntry({ role: 'system', content: warningMsg }, sessionId);
526
+ console.warn(picocolors_1.default.yellow(`[OsAgent] ⚠️ ANTI-LOOP SOFT WARNING: '${tn}' called ${count}x. Injected self-reflection prompt.`));
539
527
  }
540
528
  }
541
529
  // ── Partition into parallel (read-only) and serial (write) batches ───
@@ -706,7 +694,7 @@ The user explicitly stated your previous response was WRONG, STALE, or INACCURAT
706
694
  try {
707
695
  let turnCount = 0;
708
696
  let nudgeCount = 0;
709
- const MAX_TURNS = 30; // Increased to 30 to support long loops like multi-chain portfolios
697
+ const MAX_TURNS = 50; // Increased to 50 to allow limitless tool usage without hard crashing
710
698
  let thinkingPrefillRetries = 0; // Prefill-continuation retries for think-only silent stops
711
699
  let antiLoopStrikes = 0;
712
700
  let fullResponse = '';
@@ -715,18 +703,7 @@ The user explicitly stated your previous response was WRONG, STALE, or INACCURAT
715
703
  // ── Per-tool-name call count tracker (stream) ─────────────────────────────────────
716
704
  // Catches loops where args differ per turn (e.g. search_web with different queries).
717
705
  const osToolCallCountsStream = new Map();
718
- const OS_TOOL_CALL_LIMITS_STREAM = {
719
- 'search_web': 5,
720
- 'search_files': 8,
721
- 'read_local_file': 10,
722
- 'run_terminal_command': 8,
723
- 'edit_local_file': 6,
724
- 'write_local_file': 6,
725
- 'search_playbook': 2,
726
- 'read_gmail_inbox': 3,
727
- 'search_gmail': 3,
728
- '__default__': 12,
729
- };
706
+ // OS_TOOL_CALL_LIMITS_STREAM removed: We now use soft-warnings (self-reflection) instead of hard limits.
730
707
  // P6: Compress history ONCE before loop starts (not per iteration)
731
708
  const rawHistoryStream = exports.logger.getHistory(sessionId);
732
709
  const initialHistoryStream = (0, historySanitizer_1.pruneLoopedHistory)(rawHistoryStream); // Collapse any failed loop runs
@@ -886,20 +863,14 @@ Do NOT output filler text like "Wait, I will check". Act now.`;
886
863
  for (const tc of responseMessage.tool_calls) {
887
864
  const normalizedArgs = normalizeArgs(tc.function.arguments);
888
865
  const sig = `${tc.function.name}:${normalizedArgs}`;
889
- // Per-tool-name count guard stop before executing if limit reached
866
+ // Per-tool-name count soft warning (prevents hallucination)
890
867
  const tnS = tc.function.name;
891
868
  const countS = osToolCallCountsStream.get(tnS) || 0;
892
- const limitS = OS_TOOL_CALL_LIMITS_STREAM[tnS] ?? OS_TOOL_CALL_LIMITS_STREAM['__default__'];
893
- if (countS >= limitS) {
894
- console.warn(`[OsAgentStream] ⚠️ ANTI-LOOP: '${tnS}' called ${countS}x (limit: ${limitS}). Blocking.`);
895
- const stopMsg = `[SYSTEM: LOOP DETECTED] '${tnS}' has been called ${countS} times (limit: ${limitS}). ` +
896
- `STOP calling this tool. Produce your final answer using data already collected.`;
897
- exports.logger.addEntry({ role: 'system', content: stopMsg }, sessionId);
898
- const forceMsg = `⚠️ Loop breaker: '${tnS}' called ${countS}x (limit ${limitS}). Stopping.`;
899
- exports.logger.addEntry({ role: 'assistant', content: forceMsg }, sessionId);
900
- await onChunk(forceMsg);
901
- triggerBackgroundReview(sessionId);
902
- return forceMsg;
869
+ // Soft Warning every 5 times a tool is used
870
+ if (countS > 0 && countS % 5 === 0) {
871
+ console.warn(`[OsAgentStream] ⚠️ ANTI-LOOP SOFT WARNING: '${tnS}' called ${countS}x. Injected self-reflection prompt.`);
872
+ const warningMsg = `[SYSTEM WARNING] You have used the tool '${tnS}' ${countS} times in this session. Are you making progress? If you are stuck in a loop of errors or hallucinating fixes without success, STOP using this tool and ask the user for clarification.`;
873
+ exports.logger.addEntry({ role: 'system', content: warningMsg }, sessionId);
903
874
  }
904
875
  if (historicalToolCallSigs.has(sig)) {
905
876
  // Exact-sig repeat across turns
@@ -749,10 +749,15 @@ Do NOT output filler text like "Wait, I will check". Act now.`;
749
749
  fullResponse = cleanNarrative;
750
750
  }
751
751
  catch {
752
- // Fallback to raw data if summary call fails
753
- await onChunk(rawData);
754
- exports.logger.addEntry({ role: 'assistant', content: rawData }, sessionId);
755
- fullResponse = rawData;
752
+ // Fallback to raw data if summary call fails — clean it first
753
+ const cleanRawData = rawData
754
+ .replace(/\[SILENT_FAST_RETURN\] /g, '')
755
+ .replace(/\[System Error\][^\n]*/g, '')
756
+ .trim();
757
+ onChunk('[CLEAR_STREAM]');
758
+ await onChunk(cleanRawData);
759
+ exports.logger.addEntry({ role: 'assistant', content: cleanRawData }, sessionId);
760
+ fullResponse = cleanRawData;
756
761
  }
757
762
  // ────────────────────────────────────────────────────────────────
758
763
  break;
@@ -91,7 +91,7 @@ app.use((0, helmet_1.default)({
91
91
  contentSecurityPolicy: {
92
92
  directives: {
93
93
  defaultSrc: ["'self'"],
94
- imgSrc: ["'self'", 'data:', 'https://raw.githubusercontent.com', 'https://logos.covalenthq.com'],
94
+ imgSrc: ["'self'", 'data:', 'https://raw.githubusercontent.com', 'https://logos.covalenthq.com', 'https://cdn.simpleicons.org'],
95
95
  scriptSrc: ["'self'", "'unsafe-inline'"],
96
96
  styleSrc: ["'self'", "'unsafe-inline'"],
97
97
  connectSrc: ["'self'", 'https://*']
@@ -379,6 +379,49 @@ app.get('/api/system/pick-folder', (req, res) => {
379
379
  res.status(400).json({ error: 'Canceled' });
380
380
  }
381
381
  });
382
+ app.post('/api/system/restart', (req, res) => {
383
+ res.json({ success: true, message: 'Restarting daemon...' });
384
+ const childProcess = require('child_process');
385
+ setTimeout(() => {
386
+ childProcess.spawn('node', ['./bin/nyxora.mjs', 'restart'], { cwd: process.cwd(), detached: true, stdio: 'ignore' }).unref();
387
+ }, 500);
388
+ });
389
+ app.post('/api/system/shutdown', (req, res) => {
390
+ res.json({ success: true, message: 'Shutting down daemon...' });
391
+ const childProcess = require('child_process');
392
+ setTimeout(() => {
393
+ childProcess.spawn('node', ['./bin/nyxora.mjs', 'stop'], { cwd: process.cwd(), detached: true, stdio: 'ignore' }).unref();
394
+ }, 500);
395
+ });
396
+ app.post('/api/system/clear-cache', (req, res) => {
397
+ const childProcess = require('child_process');
398
+ childProcess.exec('node ./bin/nyxora.mjs clean-logs', { cwd: process.cwd() }, (err) => {
399
+ if (err)
400
+ return res.status(500).json({ error: 'Failed to clear cache' });
401
+ res.json({ success: true });
402
+ });
403
+ });
404
+ app.get('/api/system/autostart', (req, res) => {
405
+ const isLinux = process.platform === 'linux';
406
+ const isMac = process.platform === 'darwin';
407
+ let enabled = false;
408
+ if (isLinux) {
409
+ enabled = require('fs').existsSync(require('path').join(require('os').homedir(), '.config', 'autostart', 'nyxora.desktop'));
410
+ }
411
+ else if (isMac) {
412
+ enabled = require('fs').existsSync(require('path').join(require('os').homedir(), 'Library', 'LaunchAgents', 'com.nyxora.gateway.plist'));
413
+ }
414
+ res.json({ enabled });
415
+ });
416
+ app.post('/api/system/autostart', (req, res) => {
417
+ const action = req.body.enabled ? 'enable' : 'disable';
418
+ const childProcess = require('child_process');
419
+ childProcess.exec(`node ./bin/nyxora.mjs autostart ${action}`, { cwd: process.cwd() }, (err) => {
420
+ if (err)
421
+ return res.status(500).json({ error: 'Failed to configure autostart' });
422
+ res.json({ success: true });
423
+ });
424
+ });
382
425
  app.put('/api/sessions/:id', (req, res) => {
383
426
  try {
384
427
  const { title } = req.body;
@@ -89,6 +89,10 @@ async function runTerminalCommand(command, cwd, envType = 'local', dockerImage =
89
89
  output += `ERROR:\n${error.message}\n`;
90
90
  if (!output)
91
91
  output = "Command executed successfully with no output.";
92
+ // Clean ANSI escape codes and control characters for LLM readability
93
+ output = output
94
+ .replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
95
+ .replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '');
92
96
  // If sudo failed due to missing password, give a helpful hint
93
97
  if (needsSudo && (output.includes('sudo: a password is required') || output.includes('no password supplied'))) {
94
98
  output += `\n[NYXORA HINT] To allow Nyxora to run sudo commands automatically, add the following to your ~/.nyxora/config.yaml:\n security:\n sudo_password: YOUR_SUDO_PASSWORD\nAlternatively, run this command yourself in a terminal: ${command}`;
@@ -105,7 +105,8 @@ async function runTerminalCommandPTY(command, autoSudoPassword, cwd) {
105
105
  ptyProcess.onExit(({ exitCode }) => {
106
106
  // Clean ANSI escape codes for LLM readability
107
107
  const cleanOutput = output
108
- .replace(/\x1b\[[0-9;]*m/g, '') // Remove color codes
108
+ .replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '') // Remove ALL ANSI escape sequences (colors, cursor movements, etc.)
109
+ .replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '') // Remove all non-printable control characters except \n, \r, \t
109
110
  .replace(/\r\n/g, '\n') // Normalize line endings
110
111
  .trim();
111
112
  if (exitCode === 0) {
@@ -17,6 +17,14 @@ const TOKEN_LIST_URLS = {
17
17
  optimism: 'https://static.optimism.io/optimism.tokenlist.json',
18
18
  bsc: 'https://tokens.pancakeswap.finance/pancakeswap-extended.json'
19
19
  };
20
+ // Numeric chainId for each chain so we can filter token lists by chainId field
21
+ const CHAIN_ID_MAP = {
22
+ ethereum: 1,
23
+ base: 8453,
24
+ arbitrum: 42161,
25
+ optimism: 10,
26
+ bsc: 56,
27
+ };
20
28
  async function fetchDynamicTokens() {
21
29
  // Check cache first
22
30
  try {
@@ -35,9 +43,14 @@ async function fetchDynamicTokens() {
35
43
  const res = await fetch(url);
36
44
  const data = await res.json();
37
45
  let tokens = [];
46
+ const targetChainId = CHAIN_ID_MAP[chain];
38
47
  if (data && data.tokens && Array.isArray(data.tokens)) {
48
+ // Filter strictly by chainId to prevent cross-chain token bleed
49
+ const filtered = targetChainId
50
+ ? data.tokens.filter((t) => t.chainId === targetChainId)
51
+ : data.tokens;
39
52
  // Limit to top 50 to avoid massive multicall
40
- const topTokens = data.tokens.slice(0, 50);
53
+ const topTokens = filtered.slice(0, 50);
41
54
  tokens = topTokens.map((t) => ({
42
55
  symbol: t.symbol,
43
56
  address: t.address
@@ -29,8 +29,23 @@ function sanitizeHistoryForLLM(history, activeTools, provider = 'openai') {
29
29
  // If it IS a summary, keep role as 'system'.
30
30
  }
31
31
  let content = m.content || "";
32
- if (role === 'assistant' && typeof content === 'string') {
33
- content = content.replace(/No response generated\.\s*(---)?\s*/g, '').trim();
32
+ if (typeof content === 'string') {
33
+ if (role === 'assistant') {
34
+ content = content.replace(/No response generated\.\s*(---)?\s*/g, '').trim();
35
+ }
36
+ // Scrub ANSI escape codes and control characters to prevent LLM JSON parser crashes from old corrupted history
37
+ content = content
38
+ .replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
39
+ .replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '');
40
+ }
41
+ else if (Array.isArray(content)) {
42
+ for (const block of content) {
43
+ if (block.type === 'text' && typeof block.text === 'string') {
44
+ block.text = block.text
45
+ .replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
46
+ .replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '');
47
+ }
48
+ }
34
49
  }
35
50
  let msg = { ...m, role, content };
36
51
  if (m.tool_calls && m.tool_calls.length > 0) {
@@ -42,9 +42,11 @@ const tokens_1 = require("../utils/tokens");
42
42
  const httpClient_1 = require("../../utils/httpClient");
43
43
  const portfolioCache = {};
44
44
  const CACHE_TTL = 5000; // 5 seconds TTL
45
+ const TESTNET_CHAINS = new Set(['sepolia', 'base_sepolia', 'arbitrum_sepolia', 'optimism_sepolia', 'robinhood_testnet']);
45
46
  async function checkPortfolio(chainName, address) {
46
47
  try {
47
48
  chainName = (0, chains_1.normalizeChainName)(chainName);
49
+ const isTestnet = TESTNET_CHAINS.has(chainName);
48
50
  const client = (0, config_1.getPublicClient)(chainName);
49
51
  let targetAddress = address;
50
52
  if (!targetAddress) {
@@ -86,30 +88,34 @@ async function checkPortfolio(chainName, address) {
86
88
  }
87
89
  }
88
90
  // Merge Dynamic Trending Whitelist (CoinGecko lists)
89
- const { getDynamicTokensForChain } = await Promise.resolve().then(() => __importStar(require('../../utils/dynamicTokenUpdater')));
90
- const dynamicTokens = await getDynamicTokensForChain(chainName);
91
- for (const dToken of dynamicTokens) {
92
- if (!tokensToScan.find(t => String(t.address).toLowerCase() === String(dToken.address).toLowerCase())) {
93
- tokensToScan.push({ symbol: dToken.symbol, address: dToken.address, isNative: false });
91
+ // ⚠️ Skip on testnets: token lists are mainnet-only and would cause false positives
92
+ if (!isTestnet) {
93
+ const { getDynamicTokensForChain } = await Promise.resolve().then(() => __importStar(require('../../utils/dynamicTokenUpdater')));
94
+ const dynamicTokens = await getDynamicTokensForChain(chainName);
95
+ for (const dToken of dynamicTokens) {
96
+ if (!tokensToScan.find(t => String(t.address).toLowerCase() === String(dToken.address).toLowerCase())) {
97
+ tokensToScan.push({ symbol: dToken.symbol, address: dToken.address, isNative: false });
98
+ }
94
99
  }
95
100
  }
96
- let report = `📊 **Portfolio for ${targetAddress} on ${chainName.toUpperCase()}**\n\n`;
101
+ const testnetWarning = isTestnet
102
+ ? `\n> ⚠️ **Testnet Mode** — USD prices are not available for testnet tokens. Only native ETH balances are real.\n`
103
+ : '';
104
+ let report = `📊 **Portfolio for ${targetAddress} on ${chainName.toUpperCase()}**${testnetWarning}\n\n`;
97
105
  let totalUsdValue = 0;
98
- // We will do True On-Chain Multicall with Chunking (max 30 tokens / 60 calls per batch)
99
- const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
100
- const MULTICALL3_ABI = [{
101
- inputs: [{ name: 'addr', type: 'address' }],
102
- name: 'getEthBalance',
103
- outputs: [{ name: 'balance', type: 'uint256' }],
104
- stateMutability: 'view',
105
- type: 'function',
106
- }];
106
+ // 1. Fetch Native Balance directly (works on all chains even without Multicall3)
107
+ let nativeBalanceNum = 0;
108
+ try {
109
+ const nativeRaw = await client.getBalance({ address: targetAddress });
110
+ nativeBalanceNum = parseFloat((0, viem_1.formatEther)(nativeRaw));
111
+ }
112
+ catch (e) {
113
+ console.error(`Failed to get native balance for ${chainName}:`, e);
114
+ }
115
+ // 2. Prepare Multicall for ERC20s only
107
116
  const contracts = [];
108
117
  for (const t of tokensToScan) {
109
- if (t.isNative) {
110
- contracts.push({ address: MULTICALL3_ADDRESS, abi: MULTICALL3_ABI, functionName: 'getEthBalance', args: [targetAddress] });
111
- }
112
- else {
118
+ if (!t.isNative) {
113
119
  contracts.push({ address: t.address, abi: tokens_1.ERC20_ABI, functionName: 'balanceOf', args: [targetAddress] });
114
120
  contracts.push({ address: t.address, abi: tokens_1.ERC20_ABI, functionName: 'decimals' });
115
121
  }
@@ -117,29 +123,28 @@ async function checkPortfolio(chainName, address) {
117
123
  const CHUNK_SIZE = 60; // 30 tokens (2 calls per token)
118
124
  const multicallResults = [];
119
125
  try {
120
- // Create a timeout promise for 5 seconds (more tolerant for large portfolios)
121
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('RPC request timed out')), 5000));
122
- const executionPromise = (async () => {
123
- for (let i = 0; i < contracts.length; i += CHUNK_SIZE) {
124
- const chunk = contracts.slice(i, i + CHUNK_SIZE);
125
- const res = await client.multicall({ contracts: chunk, allowFailure: true });
126
- multicallResults.push(...res);
127
- }
128
- })();
129
- await Promise.race([executionPromise, timeoutPromise]);
126
+ if (contracts.length > 0) {
127
+ // Create a timeout promise for 5 seconds (more tolerant for large portfolios)
128
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('RPC request timed out')), 5000));
129
+ const executionPromise = (async () => {
130
+ for (let i = 0; i < contracts.length; i += CHUNK_SIZE) {
131
+ const chunk = contracts.slice(i, i + CHUNK_SIZE);
132
+ const res = await client.multicall({ contracts: chunk, allowFailure: true });
133
+ multicallResults.push(...res);
134
+ }
135
+ })();
136
+ await Promise.race([executionPromise, timeoutPromise]);
137
+ }
130
138
  }
131
139
  catch (e) {
132
- return `⚠️ **${chainName.toUpperCase()} Network is experiencing high latency.**\nThe public RPC failed to respond. Please try again later.`;
140
+ console.warn(`[checkPortfolio] Multicall timeout or fail on ${chainName}, proceeding with native balance only.`);
133
141
  }
134
142
  // Map results back to tokens
135
143
  let resultIndex = 0;
136
144
  const balances = tokensToScan.map((t) => {
137
145
  let balanceNum = 0;
138
146
  if (t.isNative) {
139
- const balResult = multicallResults[resultIndex++];
140
- if (balResult?.status === 'success' && balResult.result) {
141
- balanceNum = parseFloat((0, viem_1.formatEther)(balResult.result));
142
- }
147
+ balanceNum = nativeBalanceNum;
143
148
  }
144
149
  else {
145
150
  const balResult = multicallResults[resultIndex++];
@@ -196,10 +201,8 @@ async function checkPortfolio(chainName, address) {
196
201
  const usdValue = b.balanceNum * price;
197
202
  totalUsdValue += usdValue;
198
203
  const formattedUsd = usdValue > 0 && usdValue < 0.01 ? usdValue.toFixed(4) : usdValue.toFixed(2);
199
- const pnlIndicator = usdValue > 0 ? '🟢' : '⚪';
200
- report += `${pnlIndicator} **$${b.symbol}** | ${b.balanceNum.toFixed(4)} ${b.symbol} ($${formattedUsd})\n`;
201
- report += `📈 **PnL:** +0.00% ($0.00) _(Simulation)_\n`;
202
- report += `🤖 *Analysis:* Asset tracking active. Awaiting historical entry price data.\n\n`;
204
+ const pnlIndicator = usdValue > 0 ? '🟢' : (b.isNative ? '🔵' : '⚪');
205
+ report += `${pnlIndicator} **$${b.symbol}** | ${b.balanceNum.toFixed(4)} ${b.symbol}${!isTestnet && usdValue > 0 ? ` ($${formattedUsd})` : ''}\n`;
203
206
  }
204
207
  report += `\n💰 **Estimated Net Worth: $${totalUsdValue.toFixed(2)}**`;
205
208
  portfolioCache[cacheKey] = { data: report, timestamp: Date.now() };
package/launcher.ts CHANGED
@@ -36,7 +36,7 @@ const spawnService = (name: string, command: string, args: string[], env: any, i
36
36
  let isShuttingDown = false;
37
37
 
38
38
  const startProcess = () => {
39
- const spawnOpts: any = { env, stdio: inheritStdio ? 'inherit' : 'pipe' };
39
+ const spawnOpts: any = { env, stdio: inheritStdio ? 'inherit' : 'pipe', windowsHide: true };
40
40
  if (cwd) spawnOpts.cwd = cwd;
41
41
  child = spawn(command, args, spawnOpts);
42
42
  child.on('error', (err) => {
@@ -199,7 +199,7 @@ setTimeout(() => {
199
199
 
200
200
  if (cfEnabled) {
201
201
  console.log('[Launcher] Starting Auto-Tunnel (Cloudflare) on port 3000...');
202
- const cf = spawn('npx', ['cloudflared', 'tunnel', '--url', 'http://localhost:3000'], { env, shell: true });
202
+ const cf = spawn('npx', ['cloudflared', 'tunnel', '--url', 'http://localhost:3000'], { env, shell: true, windowsHide: true });
203
203
 
204
204
  children.push({
205
205
  kill: () => { try { process.kill(cf.pid!, 'SIGTERM'); } catch(e){} },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora",
3
- "version": "26.7.22",
3
+ "version": "26.7.23",
4
4
  "description": "Your Personal Web3 Assistant",
5
5
  "keywords": [
6
6
  "web3",
@@ -37,13 +37,6 @@
37
37
  "packages/ml-engine",
38
38
  "packages/tui/dist",
39
39
  "packages/tui/package.json",
40
- "packages/nyxora-ink/dist",
41
- "packages/nyxora-ink/index.js",
42
- "packages/nyxora-ink/index.d.ts",
43
- "packages/nyxora-ink/text-input.js",
44
- "packages/nyxora-ink/text-input.d.ts",
45
- "packages/nyxora-ink/ambient.d.ts",
46
- "packages/nyxora-ink/package.json",
47
40
  "scripts"
48
41
  ],
49
42
  "bin": {
@@ -63,7 +56,7 @@
63
56
  "tui": "node ./bin/nyxora.mjs tui",
64
57
  "set-key": "node ./bin/nyxora.mjs set-key",
65
58
  "doctor": "node ./bin/nyxora.mjs doctor",
66
- "build": "tsc && npm run build --workspace=@nyxora/ink && npm run build --workspace=@nyxora/tui && npm run build --workspace=nyxora-mcp-server && npm run build --workspace=nyxora-dashboard",
59
+ "build": "tsc && npm run build --workspace=@nyxora/tui && npm run build --workspace=nyxora-mcp-server && npm run build --workspace=nyxora-dashboard",
67
60
  "test": "npm run test --workspace=packages/core",
68
61
  "docs:dev": "vitepress dev docs",
69
62
  "docs:build": "vitepress build docs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-agent-core",
3
- "version": "26.7.22",
3
+ "version": "26.7.23",
4
4
  "private": true,
5
5
  "main": "src/gateway/server.ts",
6
6
  "dependencies": {