@melaya/runner 1.1.40 → 1.1.42

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.
@@ -523,9 +523,26 @@ def _extract_text(result) -> str:
523
523
  def _build_agent():
524
524
  """Build the single long-lived ReActAgent for this session."""
525
525
  from shared.runtime.agent_factory import make_agent
526
+ try:
527
+ from shared.orchestration.delegation import make_delegating_agent, DelegateSpec
528
+ _HAS_DELEGATION = True
529
+ except Exception:
530
+ # A runner on the NEW assistantHost.py against a STALE shared bundle that
531
+ # predates delegation.py must NOT brick — degrade to the plain agent
532
+ # build (delegates ignored, model_provider mapped back to provider=).
533
+ _HAS_DELEGATION = False
534
+ DelegateSpec = None # sentinel; only referenced when _HAS_DELEGATION is True
535
+
536
+ def make_delegating_agent(*, model_provider="", delegates=None, **_kw): # type: ignore[misc]
537
+ return make_agent(provider=model_provider, **_kw)
526
538
  from shared.runtime.registry import build_toolkit
527
539
 
528
540
  provider = os.environ.get("MEL_ASSISTANT_PROVIDER", "claude_code")
541
+ # Weak local models orchestrate sub-agents poorly — gate delegation OFF for
542
+ # them (mirrors the cloud SUBAGENT_INCAPABLE_PROVIDERS). When off, delegates
543
+ # is None and make_delegating_agent is byte-identical to the old make_agent
544
+ # build (no delegate_task tool, no prompt note).
545
+ _delegate_capable = _HAS_DELEGATION and provider.strip().lower() not in ("ollama", "lmstudio")
529
546
  model = os.environ.get("MEL_ASSISTANT_MODEL", "") or None
530
547
  language = os.environ.get("MEL_ASSISTANT_LANGUAGE", "en")
531
548
 
@@ -774,9 +791,22 @@ def _build_agent():
774
791
  "- CHARTS: when you use the `chart` tool, the graph ONLY renders if you paste the EXACT ```chart ...``` fenced block it returns into your reply. ALWAYS output that block verbatim where you want the chart shown; NEVER say 'the chart is above/below' or describe it instead. If you made several charts, include each block; if you regenerated one, include ONLY the final good version.\n"
775
792
  "- If a question is outside the platform, answer normally without tools.\n"
776
793
  + (f"- Answer in the user's language: {language}.\n" if language and language != "en" else "")
794
+ # Provider-agnostic sub-agent delegation. The delegate_task tool is
795
+ # registered by make_delegating_agent below (only when _delegate_capable);
796
+ # tell the model it exists and how to use it well. Sub-agents share this
797
+ # same toolkit (writes hit the SAME HITL gate) and cannot delegate further.
798
+ # Gated on _delegate_capable so a weak-model turn's prompt is unchanged.
799
+ + ((
800
+ "- You MAY call delegate_task(\"Specialist\", \"<scoped sub-task>\") to hand a "
801
+ "self-contained sub-analysis to a specialist sub-agent. Use it to fan out "
802
+ "INDEPENDENT sub-tasks (they can run in parallel and, when useful, on a "
803
+ "different model), then SYNTHESISE the results yourself. Do not over-delegate, "
804
+ "never delegate the whole request, and remember sub-agents cannot delegate "
805
+ "further.\n"
806
+ ) if _delegate_capable else "")
777
807
  )
778
808
 
779
- agent = make_agent(
809
+ agent = make_delegating_agent(
780
810
  # This name IS the agent identity that agentscope stamps onto the traced
781
811
  # `invoke_agent <name>` span + gen_ai.agent.name — which is EXACTLY the
782
812
  # dimension the Overview "by agent" token breakdown groups on
@@ -787,7 +817,28 @@ def _build_agent():
787
817
  sys_prompt=sys_prompt,
788
818
  toolkit=toolkit,
789
819
  model_name=model,
790
- provider=provider,
820
+ # make_delegating_agent forwards model_provider both to make_agent (as the
821
+ # provider alias -> identical resolution to the old provider=) AND to the
822
+ # delegate factory, so a specialist defaults to the SAME provider as the
823
+ # lead. Pass provider here as model_provider (not provider=) so the sub-
824
+ # agent inherits it instead of falling back to the anthropic env default.
825
+ model_provider=provider,
826
+ # Provider-agnostic sub-agent delegation (UNCONDITIONAL — always on). The
827
+ # ONLY behavioural change vs the previous make_agent build is that the
828
+ # agent now also carries a delegate_task tool; every other kwarg below is
829
+ # unchanged. The Specialist shares THIS toolkit (HITL gating preserved),
830
+ # is built via make_agent (no delegate_task -> depth capped at 1), and can
831
+ # be run on a different model via MEL_ASSISTANT_DELEGATE_MODEL/PROVIDER.
832
+ delegates=([
833
+ DelegateSpec(
834
+ name="Specialist",
835
+ crew="assistant_delegate",
836
+ factory="make_assistant_specialist",
837
+ model_name=os.environ.get("MEL_ASSISTANT_DELEGATE_MODEL", "") or "",
838
+ model_provider=os.environ.get("MEL_ASSISTANT_DELEGATE_PROVIDER", "") or "",
839
+ max_iters=6,
840
+ ),
841
+ ] if _delegate_capable else None),
791
842
  # Cloud providers on the runner path (browser control turns pinned to the
792
843
  # runner even for a cloud model) get their key passed down from the server
793
844
  # as MEL_ASSISTANT_PROVIDER_API_KEY. Empty for local providers (claude_code
@@ -873,6 +924,14 @@ def _register_stream_hooks(agent) -> None:
873
924
  # the post-hook's trailing `output` arg so ONE shape works for both.
874
925
  def _pre_print_hook(_self, kw, *_rest) -> None:
875
926
  try:
927
+ # Sub-agents (Specialist) share this CLASS hook, but only the LEAD
928
+ # ("Assistant") streams to the client. A delegate's deltas / tool
929
+ # chips must NOT leak into the lead's answer bubble — its result comes
930
+ # back via the delegate_task tool result. (Specialist name is
931
+ # "Assistant:Specialist", so an exact-match on the lead name filters
932
+ # every sub-agent.)
933
+ if getattr(_self, "name", "") != "Assistant":
934
+ return
876
935
  tid = _stream.get("turnId")
877
936
  if not tid or not isinstance(kw, dict):
878
937
  return
@@ -911,6 +970,10 @@ def _register_stream_hooks(agent) -> None:
911
970
 
912
971
  def _post_acting_hook(_self, kw, *_rest) -> None:
913
972
  try:
973
+ # Only the LEAD streams tool chips; a delegate's tool calls must not
974
+ # surface in the lead's turn (see _pre_print_hook).
975
+ if getattr(_self, "name", "") != "Assistant":
976
+ return
914
977
  tid = _stream.get("turnId")
915
978
  if not tid or not isinstance(kw, dict):
916
979
  return
@@ -993,6 +1056,15 @@ def _run_turn(agent, turn_id: str, message: str, browser_turn: bool = False, ima
993
1056
  _stream["cancel"] = False # fresh turn — clear any stale STOP
994
1057
  _stream["usedBrowser"] = False
995
1058
 
1059
+ # Reset the per-turn sub-agent delegation budget so the breadth cap is per
1060
+ # turn, not per session. Best-effort: never let a stale/absent bundle break a
1061
+ # turn (recursion is still capped structurally regardless of this reset).
1062
+ try:
1063
+ from crews.assistant_delegate.main import reset_delegation_budget
1064
+ reset_delegation_budget()
1065
+ except Exception:
1066
+ pass
1067
+
996
1068
  # Browser turns carry a WALL-CLOCK budget on top of the round budget
997
1069
  # (plan 0.3): the per-turn grant is short-lived and a wedged page must not
998
1070
  # pin the host. Cancellation uses the same task.cancel() path as STOP.
package/dist/pythonEnv.js CHANGED
@@ -204,6 +204,17 @@ const PIP_DEPS = [
204
204
  // via the anthropic/openai wheels; pinned explicitly so a future dep
205
205
  // shuffle in those SDKs can't silently break the crew risk watcher.
206
206
  "httpx",
207
+ // ── Database connectors + "Test via runner" (localDbProbe.py) ──────────
208
+ // asyncpg + PyMySQL power the PostgreSQL / MySQL tools (shared.tools.database)
209
+ // AND the runner-side connection probe. Without them the DB tools 401 at
210
+ // runtime and the "Test via runner" button returns "asyncpg/PyMySQL not
211
+ // installed on this runner". cryptography (also a common transitive dep) is
212
+ // pinned so the Snowflake key-pair JWT path in the probe always resolves.
213
+ // These are the runner twin of the requirements.lock entries — the runner
214
+ // venv installs from THIS list, not the lock, so they must be listed here too.
215
+ "asyncpg",
216
+ "PyMySQL",
217
+ "cryptography",
207
218
  ];
208
219
  export function venvPython() {
209
220
  return platform() === "win32"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melaya/runner",
3
- "version": "1.1.40",
3
+ "version": "1.1.42",
4
4
  "description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,