agentopia 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -92,7 +92,18 @@ Agentopia creates a **shared workspace** where multiple AI agents operate as a t
92
92
 
93
93
  - Node.js >= 18
94
94
 
95
- ### Install & Run
95
+ ### Option 1: Global Install (recommended)
96
+
97
+ ```bash
98
+ npm install -g agentopia
99
+
100
+ # Start the server (database created in current directory)
101
+ agentopia
102
+ ```
103
+
104
+ That's it. Open `http://localhost:4567`.
105
+
106
+ ### Option 2: Clone & Run
96
107
 
97
108
  ```bash
98
109
  git clone https://github.com/fuzihaofzh/agentopia.git
@@ -106,7 +117,7 @@ npm run dev
106
117
  npm run build && npm start
107
118
  ```
108
119
 
109
- Open `http://localhost:4567` you'll be prompted to create an admin account on first visit.
120
+ On first visit, you'll be prompted to create an admin account.
110
121
 
111
122
  ### Configuration
112
123
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentopia",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Multi-Agent Collaboration Platform",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1888,79 +1888,92 @@ function renderStarAgentGraph(container, graphContext) {
1888
1888
 
1889
1889
  function renderHierarchyAgentGraph(container, graphContext) {
1890
1890
  const byId = getAgentMap();
1891
- const controller = getControllerAgent();
1892
- const levelMap = new Map();
1893
1891
  const visited = new Set();
1894
- const syntheticLinks = [];
1892
+ const childrenMap = new Map(); // parentId -> [agent]
1895
1893
 
1896
- function walk(agent, depth) {
1897
- if (!agent || visited.has(agent.id)) return;
1898
- visited.add(agent.id);
1899
- if (!levelMap.has(depth)) levelMap.set(depth, []);
1900
- levelMap.get(depth).push(agent);
1901
-
1902
- const children = agentsData.filter((candidate) => getGraphParentId(candidate) === agent.id);
1903
- children.forEach((child) => {
1904
- if (!child.parent_agent_id) {
1905
- syntheticLinks.push(child.id);
1906
- }
1907
- walk(child, depth + 1);
1908
- });
1909
- }
1910
-
1911
- const roots = [];
1912
- if (controller) {
1913
- roots.push(controller);
1914
- }
1894
+ // Build children map using only explicit parent_agent_id
1915
1895
  agentsData.forEach((agent) => {
1916
- if (agent.is_controller) return;
1917
- const parentId = getGraphParentId(agent);
1918
- if (!parentId || !byId.has(parentId)) {
1919
- roots.push(agent);
1896
+ const pid = agent.parent_agent_id;
1897
+ if (pid && byId.has(pid)) {
1898
+ if (!childrenMap.has(pid)) childrenMap.set(pid, []);
1899
+ childrenMap.get(pid).push(agent);
1920
1900
  }
1921
1901
  });
1922
1902
 
1903
+ // Identify roots: agents with no explicit parent or whose parent doesn't exist
1904
+ const roots = agentsData.filter((agent) => {
1905
+ const pid = agent.parent_agent_id;
1906
+ return !pid || !byId.has(pid);
1907
+ });
1908
+
1909
+ // Build subtree sizes for proper horizontal spacing
1910
+ const subtreeSize = new Map();
1911
+ function calcSize(agent) {
1912
+ if (subtreeSize.has(agent.id)) return subtreeSize.get(agent.id);
1913
+ const children = childrenMap.get(agent.id) || [];
1914
+ const size = children.length === 0 ? 1 : children.reduce((sum, c) => sum + calcSize(c), 0);
1915
+ subtreeSize.set(agent.id, size);
1916
+ return size;
1917
+ }
1918
+ roots.forEach((r) => calcSize(r));
1919
+
1920
+ // Walk tree to assign depth levels
1921
+ const depthMap = new Map();
1922
+ function walk(agent, depth) {
1923
+ if (!agent || visited.has(agent.id)) return;
1924
+ visited.add(agent.id);
1925
+ depthMap.set(agent.id, depth);
1926
+ (childrenMap.get(agent.id) || []).forEach((child) => walk(child, depth + 1));
1927
+ }
1923
1928
  roots.forEach((root) => walk(root, 0));
1929
+ // Safety: visit any unvisited agents as roots
1924
1930
  agentsData.forEach((agent) => {
1925
1931
  if (!visited.has(agent.id)) walk(agent, 0);
1926
1932
  });
1927
1933
 
1928
- const levels = Array.from(levelMap.entries())
1929
- .sort((a, b) => a[0] - b[0])
1930
- .map(([, items]) => items);
1931
-
1934
+ const maxDepth = Math.max(0, ...Array.from(depthMap.values()));
1932
1935
  const W = Math.min(Math.max(container.clientWidth || 760, 640), 960);
1933
1936
  const levelGap = 112;
1934
1937
  const topPadding = 56;
1935
- const H = Math.max(280, topPadding + Math.max(levels.length - 1, 1) * levelGap + 96);
1938
+ const H = Math.max(280, topPadding + maxDepth * levelGap + 96);
1936
1939
  const nodeRadius = 28;
1937
1940
  const positions = new Map();
1938
1941
 
1939
- levels.forEach((level, depth) => {
1940
- const spacing = W / (level.length + 1);
1942
+ // Position nodes: center children under their parent using subtree sizes
1943
+ const totalLeaves = roots.reduce((sum, r) => sum + (subtreeSize.get(r.id) || 1), 0);
1944
+ const leafWidth = W / (totalLeaves + 1);
1945
+
1946
+ let leafCounter = 0;
1947
+ function positionSubtree(agent, depth) {
1948
+ const children = childrenMap.get(agent.id) || [];
1941
1949
  const y = topPadding + depth * levelGap;
1942
- level.forEach((agent, index) => {
1943
- positions.set(agent.id, {
1944
- x: spacing * (index + 1),
1945
- y,
1946
- });
1947
- });
1948
- });
1950
+ if (children.length === 0) {
1951
+ leafCounter++;
1952
+ positions.set(agent.id, { x: leafCounter * leafWidth, y });
1953
+ } else {
1954
+ children.forEach((child) => positionSubtree(child, depth + 1));
1955
+ // Center parent over its children
1956
+ const childPositions = children.map((c) => positions.get(c.id)).filter(Boolean);
1957
+ const minX = Math.min(...childPositions.map((p) => p.x));
1958
+ const maxX = Math.max(...childPositions.map((p) => p.x));
1959
+ positions.set(agent.id, { x: (minX + maxX) / 2, y });
1960
+ }
1961
+ }
1962
+ roots.forEach((root) => positionSubtree(root, 0));
1949
1963
 
1950
1964
  let svg = '<svg width="' + W + '" height="' + H + '" viewBox="0 0 ' + W + ' ' + H + '" style="display:block;margin:0 auto">';
1951
1965
 
1966
+ // Draw edges using only explicit parent_agent_id
1952
1967
  agentsData.forEach((agent) => {
1953
- const parentId = getGraphParentId(agent);
1954
- if (!parentId) return;
1955
- const parentPos = positions.get(parentId);
1968
+ const pid = agent.parent_agent_id;
1969
+ if (!pid || !byId.has(pid)) return;
1970
+ const parentPos = positions.get(pid);
1956
1971
  const childPos = positions.get(agent.id);
1957
1972
  if (!parentPos || !childPos) return;
1958
1973
 
1959
1974
  const dispatched = graphContext.dispatchedAgents.has(agent.id);
1960
- const synthetic = !agent.parent_agent_id;
1961
1975
  svg += '<line x1="' + parentPos.x + '" y1="' + (parentPos.y + nodeRadius) + '" x2="' + childPos.x + '" y2="' + (childPos.y - nodeRadius) + '" stroke="' + (dispatched ? 'var(--accent)' : 'var(--border)') + '" stroke-width="' + (dispatched ? 2.2 : 1.2) + '"' +
1962
- (synthetic ? ' stroke-dasharray="6,4"' : '') +
1963
- ' opacity="' + (dispatched ? 0.95 : synthetic ? 0.45 : 0.7) + '"/>';
1976
+ ' opacity="' + (dispatched ? 0.95 : 0.7) + '"/>';
1964
1977
 
1965
1978
  if (dispatched) {
1966
1979
  const mx = (parentPos.x + childPos.x) / 2;
@@ -1969,6 +1982,7 @@ function renderHierarchyAgentGraph(container, graphContext) {
1969
1982
  }
1970
1983
  });
1971
1984
 
1985
+ // Draw nodes
1972
1986
  agentsData.forEach((agent) => {
1973
1987
  const position = positions.get(agent.id);
1974
1988
  if (!position) return;
@@ -1977,7 +1991,7 @@ function renderHierarchyAgentGraph(container, graphContext) {
1977
1991
  ? '<animate attributeName="r" values="' + nodeRadius + ';' + (nodeRadius + 4) + ';' + nodeRadius + '" dur="2s" repeatCount="indefinite"/>'
1978
1992
  : '';
1979
1993
  const assignedCount = (window._dashboardIssues || []).filter((issue) => issue.assigned_to === agent.id && ['open', 'in_progress', 'pending'].includes(issue.status)).length;
1980
- const childCount = getDirectChildAgents(agent.id).length;
1994
+ const childCount = (childrenMap.get(agent.id) || []).length;
1981
1995
  const dispatched = graphContext.dispatchedAgents.has(agent.id);
1982
1996
  const reason = graphContext.actionReasonByAgent.get(agent.id);
1983
1997
  const statusLabel = agent.paused ? 'paused' : agent.status;
@@ -1993,13 +2007,14 @@ function renderHierarchyAgentGraph(container, graphContext) {
1993
2007
 
1994
2008
  svg += '</svg>';
1995
2009
 
1996
- const syntheticNote = syntheticLinks.length > 0
1997
- ? 'Agents without a parent are shown as direct controller children with dashed links.'
1998
- : 'Links in the graph follow the configured direct parent-child hierarchy.';
2010
+ const hasOrphans = roots.some((r) => !r.is_controller);
2011
+ const note = hasOrphans
2012
+ ? 'Top-level agents (no parent) are shown as independent roots.'
2013
+ : 'Links in the graph follow the configured parent-child hierarchy.';
1999
2014
 
2000
2015
  return {
2001
2016
  title: 'Agent Collaboration · Tree',
2002
- note: syntheticNote,
2017
+ note,
2003
2018
  svg,
2004
2019
  };
2005
2020
  }