@1mancompany/onemancompany 0.7.48 → 0.7.49

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1mancompany/onemancompany",
3
- "version": "0.7.48",
3
+ "version": "0.7.49",
4
4
  "description": "The AI Operating System for One-Person Companies",
5
5
  "bin": {
6
6
  "onemancompany": "bin/cli.js"
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "onemancompany"
3
- version = "0.7.48"
3
+ version = "0.7.49"
4
4
  description = "A one-man company simulation with pixel art visualization and LangChain AI agents"
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -91,6 +91,10 @@ class TaskNode:
91
91
  # Used to cap infinite retry loops (e.g. EA keeps retrying a failing child).
92
92
  retry_count: int = 0
93
93
 
94
+ # How many times this node was retried due to stall detection
95
+ # (agent promised action but didn't call tools). Capped at MAX_STALL_RETRIES.
96
+ stall_retry_count: int = 0
97
+
94
98
  # --- Content externalization tracking (not part of equality/repr) ---
95
99
  _content_dirty: bool = field(default=False, init=False, repr=False, compare=False)
96
100
  _content_loaded: bool = field(default=False, init=False, repr=False, compare=False)
@@ -210,6 +214,7 @@ class TaskNode:
210
214
  "hold_reason": self.hold_reason,
211
215
  "hold_started_at": self.hold_started_at,
212
216
  "retry_count": self.retry_count,
217
+ "stall_retry_count": self.stall_retry_count,
213
218
  "directives_count": len(self.directives),
214
219
  }
215
220
 
@@ -139,11 +139,14 @@ _PROMISE_PATTERNS = _re.compile(
139
139
  r"我将|接下来|下一步|现在开始|马上开始|即将开始|准备开始"
140
140
  r"|我会(?:立即|马上|开始)"
141
141
  r"|下面我(?:来|将|要)"
142
+ r"|分配给\w+处理|派遣给\w+负责"
142
143
  # English future-action phrases
143
144
  r"|I will (?:now |start |begin )|I'll (?:now |start |begin )"
144
145
  r"|Let me (?:start|begin|proceed)"
145
146
  r"|Next,? I'?(?:ll| will)"
146
147
  r"|I'?m going to (?:start|begin)"
148
+ r"|Going to dispatch"
149
+ r"|I need to dispatch_child"
147
150
  r")",
148
151
  _re.IGNORECASE,
149
152
  )
@@ -160,6 +163,27 @@ def detect_unfulfilled_promises(output: str | None) -> bool:
160
163
  return bool(_PROMISE_PATTERNS.search(output))
161
164
 
162
165
 
166
+ MAX_STALL_RETRIES: int = 2 # max times to re-run a stalled agent before giving up
167
+
168
+
169
+ def _should_retry_stall(node) -> bool:
170
+ """Check if a completed node should be retried due to stall detection.
171
+
172
+ Returns True if:
173
+ - Node is not a system node
174
+ - Node has no children (didn't actually dispatch)
175
+ - Node output contains promise patterns
176
+ - stall_retry_count < MAX_STALL_RETRIES
177
+ """
178
+ if node.node_type in SYSTEM_NODE_TYPES:
179
+ return False
180
+ if node.children_ids:
181
+ return False
182
+ if not detect_unfulfilled_promises(node.result):
183
+ return False
184
+ return getattr(node, 'stall_retry_count', 0) < MAX_STALL_RETRIES
185
+
186
+
163
187
  # ---------------------------------------------------------------------------
164
188
  # Dependency context builder
165
189
  # ---------------------------------------------------------------------------
@@ -1732,18 +1756,42 @@ class EmployeeManager:
1732
1756
  employee_id, entry.node_id)
1733
1757
 
1734
1758
  # Stall detection: agent said "I will do X" but dispatched no children
1735
- if (node.node_type not in SYSTEM_NODE_TYPES
1759
+ if _should_retry_stall(node):
1760
+ node.stall_retry_count = getattr(node, 'stall_retry_count', 0) + 1
1761
+ logger.warning(
1762
+ "[STALL] employee={} node={}: output contains action promises "
1763
+ "but no subtasks dispatched. Retrying ({}/{}).",
1764
+ employee_id, entry.node_id,
1765
+ node.stall_retry_count, MAX_STALL_RETRIES,
1766
+ )
1767
+ # Revert to PROCESSING and re-schedule with explicit nudge
1768
+ node.set_status(TaskPhase.PROCESSING)
1769
+ nudge = (
1770
+ "\n\n[SYSTEM] You said you would dispatch tasks but did NOT "
1771
+ "actually call dispatch_child(). You MUST call the tool now. "
1772
+ "Do NOT describe what you plan to do — invoke dispatch_child() directly."
1773
+ )
1774
+ node.result = (node.result or "") + nudge
1775
+ save_tree_async(entry.tree_path)
1776
+ self.schedule_node(employee_id, entry.node_id, entry.tree_path)
1777
+ self._schedule_next(employee_id)
1778
+ self._log_node(employee_id, entry.node_id, "stall_retry",
1779
+ f"Retrying stalled task (attempt {node.stall_retry_count})")
1780
+ return # skip normal completion flow
1781
+ elif (node.node_type not in SYSTEM_NODE_TYPES
1736
1782
  and not node.children_ids
1737
1783
  and detect_unfulfilled_promises(node.result)):
1738
- logger.warning( # pragma: no cover
1739
- "[STALL] employee={} node={}: output contains action promises "
1740
- "but no subtasks were dispatched. Agent may have stalled.",
1784
+ # Max retries exhausted — warn CEO
1785
+ logger.warning(
1786
+ "[STALL] employee={} node={}: stall retries exhausted ({}/{}). "
1787
+ "Marking COMPLETED with warning.",
1741
1788
  employee_id, entry.node_id,
1789
+ getattr(node, 'stall_retry_count', 0), MAX_STALL_RETRIES,
1742
1790
  )
1743
- self._push_to_conversation( # pragma: no cover
1791
+ self._push_to_conversation(
1744
1792
  node,
1745
- "⚠️ Agent claimed it would execute follow-up work but did not "
1746
- "create any tasks. It may have stalled. Please review and re-dispatch.",
1793
+ "⚠️ Agent repeatedly claimed it would execute follow-up work but "
1794
+ "did not create any tasks after multiple retries. Please review and re-dispatch manually.",
1747
1795
  )
1748
1796
 
1749
1797
  save_tree_async(entry.tree_path)
@@ -1799,6 +1847,11 @@ class EmployeeManager:
1799
1847
 
1800
1848
  # Unschedule completed node
1801
1849
  self.unschedule(employee_id, entry.node_id)
1850
+
1851
+ # Drain any deferred schedules — ensures child tasks dispatched
1852
+ # by tools (e.g. dispatch_child) that hit a sync/async boundary
1853
+ # actually start executing (I1: prevents silent defer).
1854
+ self.drain_pending()
1802
1855
  else:
1803
1856
  self._publish_node_update(employee_id, node)
1804
1857