@serkanalgur/opencode-nexus 2.7.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +63 -28
  2. package/dist/index.js +754 -120
  3. package/dist/tui.js +111 -23
  4. package/package.json +1 -1
package/dist/tui.js CHANGED
@@ -121,6 +121,9 @@ var DEFAULT_CONFIG = {
121
121
  enabled: true,
122
122
  port: 4747,
123
123
  host: "127.0.0.1"
124
+ },
125
+ notifications: {
126
+ enabled: true
124
127
  }
125
128
  };
126
129
 
@@ -130,10 +133,12 @@ class NexusConfigManager {
130
133
  storageConfig = null;
131
134
  loadInfo = null;
132
135
  dashboardBase;
133
- constructor(dashboardBase) {
136
+ notificationsBase;
137
+ constructor(dashboardBase, notificationsBase) {
134
138
  this.projectConfig = null;
135
139
  this.globalConfig = null;
136
140
  this.dashboardBase = { ...DEFAULT_CONFIG.dashboard, ...dashboardBase };
141
+ this.notificationsBase = { ...DEFAULT_CONFIG.notifications, ...notificationsBase };
137
142
  }
138
143
  loadConfigs(basePath) {
139
144
  if (this.loadInfo === null)
@@ -200,6 +205,9 @@ class NexusConfigManager {
200
205
  enabled: this.storageConfig?.dashboard?.enabled ?? this.projectConfig?.dashboard?.enabled ?? this.globalConfig?.dashboard?.enabled ?? this.dashboardBase.enabled,
201
206
  port: this.storageConfig?.dashboard?.port ?? this.projectConfig?.dashboard?.port ?? this.globalConfig?.dashboard?.port ?? this.dashboardBase.port,
202
207
  host: this.storageConfig?.dashboard?.host ?? this.projectConfig?.dashboard?.host ?? this.globalConfig?.dashboard?.host ?? this.dashboardBase.host
208
+ },
209
+ notifications: {
210
+ enabled: this.storageConfig?.notifications?.enabled ?? this.projectConfig?.notifications?.enabled ?? this.globalConfig?.notifications?.enabled ?? this.notificationsBase.enabled
203
211
  }
204
212
  };
205
213
  }
@@ -321,6 +329,7 @@ class NexusConfigManager {
321
329
  result.budget = { ...current.budget };
322
330
  result.selfHealing = { ...current.selfHealing };
323
331
  result.dashboard = { ...current.dashboard };
332
+ result.notifications = { ...current.notifications };
324
333
  return result;
325
334
  }
326
335
  resetToDefaults() {
@@ -597,6 +606,11 @@ function parseWebDashboardTarget(input, fallback) {
597
606
  }
598
607
  return { target: { port, host } };
599
608
  }
609
+ var LISTEN_CONFIRM_ATTEMPTS = 15;
610
+ var LISTEN_CONFIRM_INTERVAL_MS = 200;
611
+ function defaultWait(ms) {
612
+ return new Promise((resolve) => setTimeout(resolve, ms));
613
+ }
600
614
  async function handleWebDashboard(input, deps) {
601
615
  const parsed = parseWebDashboardTarget(input, {
602
616
  port: deps.dashboard.port,
@@ -629,7 +643,7 @@ async function handleWebDashboard(input, deps) {
629
643
  await deps.openBrowser(url);
630
644
  deps.showToast({
631
645
  title: "\u26A1 Nexus Web Dashboard",
632
- message: `Opened ${url} \u2014 a dashboard is already running there.`,
646
+ message: `Opened ${url} \u2014 a dashboard is already running there, so nothing was started.`,
633
647
  variant: "success",
634
648
  duration: 6000
635
649
  });
@@ -650,22 +664,62 @@ async function handleWebDashboard(input, deps) {
650
664
  });
651
665
  return;
652
666
  }
667
+ let submitted;
668
+ try {
669
+ await deps.submitCommand(`/nexus dashboard ${port} ${host}`);
670
+ submitted = `/nexus dashboard ${port} ${host}`;
671
+ } catch (error) {
672
+ const detail = error instanceof Error ? error.message : String(error);
673
+ deps.showToast({
674
+ title: "Nexus Web Dashboard \u2014 could not start",
675
+ message: [
676
+ `The dashboard start command never reached the OpenCode server: ${detail}`,
677
+ "",
678
+ "No server is listening on that port and no browser was opened.",
679
+ "The dashboard runs in the server process, so this needs the server's",
680
+ "HTTP endpoint to be reachable from the TUI."
681
+ ].join(`
682
+ `),
683
+ variant: "error",
684
+ duration: 15000
685
+ });
686
+ return;
687
+ }
688
+ const wait = deps.waitImpl ?? defaultWait;
689
+ const attempts = deps.confirmAttempts ?? LISTEN_CONFIRM_ATTEMPTS;
690
+ const intervalMs = deps.confirmIntervalMs ?? LISTEN_CONFIRM_INTERVAL_MS;
691
+ for (let attempt = 0;attempt < attempts; attempt++) {
692
+ if (attempt > 0)
693
+ await wait(intervalMs);
694
+ const check = await probeDashboard(host, port, deps.fetchImpl);
695
+ if (check.isNexusDashboard) {
696
+ await deps.openBrowser(url);
697
+ deps.showToast({
698
+ title: "\u26A1 Nexus Web Dashboard",
699
+ message: `Started and opened ${url} \u2014 a dashboard is serving there now.`,
700
+ variant: "success",
701
+ duration: 8000
702
+ });
703
+ return;
704
+ }
705
+ }
653
706
  deps.showToast({
654
- title: "\u26A1 Nexus Web Dashboard",
707
+ title: "Nexus Web Dashboard \u2014 not started",
655
708
  message: [
656
- `No dashboard is running on ${url} (${probe.detail}), and no browser was opened.`,
657
- "",
658
- "The dashboard server cannot be started from the TUI: it runs in the OpenCode",
659
- "server process, next to the orchestrator that feeds it, and the TUI has no",
660
- "handle on either. So start it from the agent \u2014 this is the one call:",
709
+ `Ran \`${submitted}\`, but nothing is serving on ${host}:${port} after ${Math.round(attempts * intervalMs / 1000)}s,`,
710
+ "so no browser was opened and there is no URL to give you.",
661
711
  "",
662
- ` nexus.dashboard.start(port=${port}, host="${host}")`,
663
- "",
664
- `Then re-run /nexus web and it will open ${url} for you.`
712
+ "The command runs in the OpenCode server process, which is the only place",
713
+ "the dashboard can start. If a Nexus plugin is active in that process, its",
714
+ `result is in this session as the reply to \`${submitted}\` \u2014 that text names`,
715
+ "the reason (a port already in use, or `dashboard.enabled: false`). If no",
716
+ "plugin is active there, the command was never handled at all. If the port",
717
+ "is held by another process, free it or pass a different one:",
718
+ "/nexus web 4748."
665
719
  ].join(`
666
720
  `),
667
- variant: "info",
668
- duration: 15000
721
+ variant: "error",
722
+ duration: 20000
669
723
  });
670
724
  }
671
725
  async function openInBrowser(url) {
@@ -766,14 +820,28 @@ var tui_default = define({
766
820
  variant: "success"
767
821
  });
768
822
  };
823
+ const activeSessionID = () => {
824
+ const route = context.ui.router.current();
825
+ if (route.type === "session")
826
+ return route.sessionID;
827
+ return context.ui.tabs.list().find((tab) => tab.active)?.sessionID;
828
+ };
829
+ const submitServerCommand = async (text) => {
830
+ const sessionID = activeSessionID();
831
+ if (!sessionID) {
832
+ throw new Error("no session is open to run the command in");
833
+ }
834
+ await context.client.session.prompt({ sessionID, text });
835
+ };
769
836
  const runWebDashboard = async (input) => {
770
837
  await handleWebDashboard(input, {
771
838
  dashboard: configManager.getConfig().dashboard,
772
839
  showToast: (options) => context.ui.toast.show(options),
773
- openBrowser: openInBrowser
840
+ openBrowser: openInBrowser,
841
+ submitCommand: submitServerCommand
774
842
  });
775
843
  };
776
- const handleDashboard = async () => {
844
+ const handleOverview = async () => {
777
845
  const config = configManager.getConfig();
778
846
  const lines = [
779
847
  "\u26A1 Nexus Overview",
@@ -800,7 +868,9 @@ var tui_default = define({
800
868
  lines.push(" /nexus status - Show config summary");
801
869
  lines.push(" /nexus config - Configure models & budget");
802
870
  lines.push(" /nexus model - Select model for role");
803
- lines.push(" /nexus web - Open the web dashboard, if one is running");
871
+ lines.push(" /nexus dashboard - Start the web dashboard and open it");
872
+ lines.push(" /nexus web - Same: start it if needed, then open it");
873
+ lines.push(" /nexus overview - Show this overview");
804
874
  lines.push(" /nexus reset - Reset to defaults");
805
875
  lines.push("");
806
876
  lines.push("\uD83D\uDD27 Tools (use in agent prompt):");
@@ -813,7 +883,7 @@ var tui_default = define({
813
883
  lines.push("\uD83D\uDCCA Web Dashboard:");
814
884
  lines.push(` Enabled: ${config.dashboard.enabled ? "\u2705" : "\u274C"}`);
815
885
  lines.push(` Address: http://${config.dashboard.host}:${config.dashboard.port} (when running)`);
816
- lines.push(" Not running? Ask the agent for nexus.dashboard.start, then /nexus web.");
886
+ lines.push(" Not running? /nexus dashboard starts it and opens it.");
817
887
  context.ui.toast.show({
818
888
  title: "Nexus Overview",
819
889
  message: lines.join(`
@@ -858,7 +928,10 @@ var tui_default = define({
858
928
  break;
859
929
  case "dashboard":
860
930
  case "d":
861
- await handleDashboard();
931
+ await runWebDashboard(parts.slice(1).join(" "));
932
+ break;
933
+ case "overview":
934
+ await handleOverview();
862
935
  break;
863
936
  case "web":
864
937
  case "w":
@@ -900,7 +973,7 @@ var tui_default = define({
900
973
  default:
901
974
  context.ui.toast.show({
902
975
  title: "Nexus",
903
- message: "Commands: config, status, dashboard, web [port], model <role>, reset",
976
+ message: "Commands: config, status, dashboard [port], web [port], overview, model <role>, reset",
904
977
  variant: "info"
905
978
  });
906
979
  }
@@ -922,21 +995,34 @@ var tui_default = define({
922
995
  }
923
996
  },
924
997
  {
925
- id: "nexus.dashboard",
998
+ id: "nexus.overview",
926
999
  title: "Nexus Overview (config, budget, dashboard status)",
927
1000
  group: "Nexus",
928
1001
  palette: true,
929
- slash: { name: "nexus-dashboard", aliases: ["nd"], arguments: true },
1002
+ slash: { name: "nexus-overview", aliases: ["no"], arguments: true },
930
1003
  enabled: () => true,
931
1004
  suggested: true,
932
1005
  run: async () => {
933
- await handleDashboard();
1006
+ await handleOverview();
1007
+ }
1008
+ },
1009
+ {
1010
+ id: "nexus.dashboard",
1011
+ title: "Start the Nexus Web Dashboard",
1012
+ description: "Starts the dashboard server if it is not already running, then opens it in your browser",
1013
+ group: "Nexus",
1014
+ palette: true,
1015
+ slash: { name: "nexus-dashboard", aliases: ["nd"], arguments: true },
1016
+ enabled: () => true,
1017
+ suggested: true,
1018
+ run: async (input) => {
1019
+ await runWebDashboard(input);
934
1020
  }
935
1021
  },
936
1022
  {
937
1023
  id: "nexus.web",
938
1024
  title: "Open the Nexus Web Dashboard",
939
- description: "Open the dashboard in your browser if one is already serving; otherwise say how to start it",
1025
+ description: "Alias of Nexus Dashboard: starts it if needed, then opens it in your browser",
940
1026
  group: "Nexus",
941
1027
  palette: true,
942
1028
  slash: { name: "nexus-web", aliases: ["nw"], arguments: true },
@@ -1186,6 +1272,8 @@ var tui_default = define({
1186
1272
  }
1187
1273
  });
1188
1274
  export {
1275
+ LISTEN_CONFIRM_ATTEMPTS,
1276
+ LISTEN_CONFIRM_INTERVAL_MS,
1189
1277
  collectSidebarAgents,
1190
1278
  tui_default as default,
1191
1279
  handleWebDashboard,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serkanalgur/opencode-nexus",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "Adaptive Multi-Agent Orchestration with Cost Intelligence for OpenCode V2",
5
5
  "keywords": [
6
6
  "opencode",