@melaya/runner 1.1.40 → 1.1.41
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/dist/assistantHost.py +74 -2
- package/package.json +1 -1
package/dist/assistantHost.py
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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.
|