@melaya/runner 1.0.78 → 1.0.79
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 +89 -11
- package/dist/connection.js +1 -0
- package/package.json +1 -1
package/dist/assistantHost.py
CHANGED
|
@@ -37,6 +37,10 @@ import traceback
|
|
|
37
37
|
_EVENT_PREFIX = "MELASSIST "
|
|
38
38
|
_IDLE_EXIT_SECONDS = int(os.environ.get("MEL_ASSISTANT_IDLE_SECONDS", "900")) # 15 min
|
|
39
39
|
|
|
40
|
+
# Streaming state for the CURRENT turn — the pre_print / post_acting hooks read
|
|
41
|
+
# this to emit delta / tool events keyed to the turn in flight.
|
|
42
|
+
_stream = {"turnId": "", "lens": {}}
|
|
43
|
+
|
|
40
44
|
|
|
41
45
|
def _emit(turn_id: str, kind: str, **fields) -> None:
|
|
42
46
|
"""Write one structured event line to stdout (flushed) for the runner to relay."""
|
|
@@ -96,24 +100,45 @@ def _build_agent():
|
|
|
96
100
|
model = os.environ.get("MEL_ASSISTANT_MODEL", "") or None
|
|
97
101
|
language = os.environ.get("MEL_ASSISTANT_LANGUAGE", "en")
|
|
98
102
|
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
103
|
+
# Phone control is offered ONLY on the native mobile app surface — the same
|
|
104
|
+
# gate as the server-side melaya_run_phone_task tool. A desktop / web chat
|
|
105
|
+
# never gets the phone_* tools, even if a phone is paired.
|
|
106
|
+
phone_enabled = os.environ.get("MEL_ASSISTANT_SURFACE", "") == "mobile-native"
|
|
107
|
+
|
|
108
|
+
# Read-only platform tools (melaya_agent); phone control (Device Control) on
|
|
109
|
+
# mobile only. These POST to /api/v1/private/assistant-tool + /phone/command
|
|
110
|
+
# with MELAYA_API_KEY; tenant scope is enforced server-side.
|
|
111
|
+
categories = ["melaya_agent"] + (["phone"] if phone_enabled else [])
|
|
102
112
|
try:
|
|
103
|
-
toolkit = build_toolkit(categories=
|
|
113
|
+
toolkit = build_toolkit(categories=categories)
|
|
104
114
|
except Exception as exc:
|
|
105
|
-
_log(f"build_toolkit(
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
_log(f"build_toolkit({categories}) failed: {exc}; retrying melaya_agent only")
|
|
116
|
+
try:
|
|
117
|
+
toolkit = build_toolkit(categories=["melaya_agent"])
|
|
118
|
+
except Exception:
|
|
119
|
+
toolkit = build_toolkit(names=[])
|
|
120
|
+
|
|
121
|
+
phone_rule = (
|
|
122
|
+
"- If the user asks you to DO something on their phone (open an app, browse, "
|
|
123
|
+
"tap, type, comment, post), use the phone_* tools. ALWAYS call "
|
|
124
|
+
"phone_get_screen_tree before you tap or type. Publish comments/posts with "
|
|
125
|
+
"phone_post_comment / phone_create_post — every publish is approved by the "
|
|
126
|
+
"user ON THEIR PHONE, so never refuse for safety. Only touch apps the user "
|
|
127
|
+
"asked for; the phone enforces an allowlist and blocks the rest.\n"
|
|
128
|
+
if phone_enabled else ""
|
|
129
|
+
)
|
|
108
130
|
sys_prompt = (
|
|
109
131
|
"You are the Melaya Assistant, the in-app copilot of the Melaya "
|
|
110
132
|
"agent-orchestration platform. You have READ-ONLY tools over the data "
|
|
111
133
|
"this user is allowed to see: pipelines, runs, LLM costs, plan usage, "
|
|
112
|
-
"validated workflow templates and eval results
|
|
113
|
-
"
|
|
134
|
+
"validated workflow templates and eval results"
|
|
135
|
+
+ (" — plus phone-control tools to drive the user's paired phone." if phone_enabled else ".")
|
|
136
|
+
+ "\nRules:\n"
|
|
114
137
|
"- Use tools to answer questions about the user's pipelines, spend, usage, "
|
|
115
|
-
"templates or evals — never invent numbers
|
|
116
|
-
"
|
|
138
|
+
"templates or evals — never invent numbers. For cost, melaya_cost_summary "
|
|
139
|
+
"supports dimension='pipeline' to find which pipeline cost the most.\n"
|
|
140
|
+
+ phone_rule
|
|
141
|
+
+ "- Be concise and concrete; format small tables or bullet lists when comparing items.\n"
|
|
117
142
|
"- If a question is outside the platform, answer normally without tools.\n"
|
|
118
143
|
+ (f"- Answer in the user's language: {language}.\n" if language and language != "en" else "")
|
|
119
144
|
)
|
|
@@ -129,14 +154,63 @@ def _build_agent():
|
|
|
129
154
|
reliability=True,
|
|
130
155
|
bounded_memory=True,
|
|
131
156
|
)
|
|
157
|
+
_register_stream_hooks(agent)
|
|
132
158
|
return agent
|
|
133
159
|
|
|
134
160
|
|
|
161
|
+
def _register_stream_hooks(agent) -> None:
|
|
162
|
+
"""Emit delta / tool events live as the agent produces output (pre_print gives
|
|
163
|
+
cumulative snapshots per message; we forward the new suffix as a delta)."""
|
|
164
|
+
try:
|
|
165
|
+
from shared.runtime.events import _flatten_agent_text
|
|
166
|
+
except Exception:
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
def _pre_print(*_args, **kwargs) -> None:
|
|
170
|
+
try:
|
|
171
|
+
tid = _stream.get("turnId")
|
|
172
|
+
if not tid:
|
|
173
|
+
return
|
|
174
|
+
msg = kwargs.get("msg") or kwargs.get("message")
|
|
175
|
+
if msg is None:
|
|
176
|
+
return
|
|
177
|
+
mid = str(getattr(msg, "id", "") or id(msg))
|
|
178
|
+
text = _flatten_agent_text(getattr(msg, "content", msg))
|
|
179
|
+
if not text:
|
|
180
|
+
return
|
|
181
|
+
prev = _stream["lens"].get(mid, 0)
|
|
182
|
+
if len(text) > prev:
|
|
183
|
+
_emit(tid, "delta", content=text[prev:])
|
|
184
|
+
_stream["lens"][mid] = len(text)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
def _post_acting(*_args, **kwargs) -> None:
|
|
189
|
+
try:
|
|
190
|
+
tid = _stream.get("turnId")
|
|
191
|
+
if not tid:
|
|
192
|
+
return
|
|
193
|
+
for tc in (kwargs.get("tool_calls") or []):
|
|
194
|
+
name = tc.get("name", "") if isinstance(tc, dict) else getattr(tc, "name", "")
|
|
195
|
+
if name:
|
|
196
|
+
_emit(tid, "tool", name=str(name))
|
|
197
|
+
except Exception:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
for hook_type, fn in (("pre_print", _pre_print), ("post_acting", _post_acting)):
|
|
201
|
+
try:
|
|
202
|
+
agent.register_instance_hook(hook_type, fn)
|
|
203
|
+
except Exception:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
|
|
135
207
|
def _run_turn(agent, turn_id: str, message: str) -> None:
|
|
136
208
|
import asyncio
|
|
137
209
|
from agentscope.message import Msg
|
|
138
210
|
|
|
139
211
|
_emit(turn_id, "round", n=1)
|
|
212
|
+
_stream["turnId"] = turn_id
|
|
213
|
+
_stream["lens"] = {}
|
|
140
214
|
|
|
141
215
|
async def _go():
|
|
142
216
|
return await agent(Msg("user", message, "user"))
|
|
@@ -145,10 +219,14 @@ def _run_turn(agent, turn_id: str, message: str) -> None:
|
|
|
145
219
|
result = asyncio.run(_go())
|
|
146
220
|
except Exception as exc:
|
|
147
221
|
_log("turn error:\n" + traceback.format_exc())
|
|
222
|
+
_stream["turnId"] = ""
|
|
148
223
|
_emit(turn_id, "error", message=str(exc) or "assistant turn failed")
|
|
149
224
|
_emit(turn_id, "done")
|
|
150
225
|
return
|
|
151
226
|
|
|
227
|
+
_stream["turnId"] = ""
|
|
228
|
+
# Authoritative final answer — the client swaps the streamed plain text for
|
|
229
|
+
# this markdown-rendered version.
|
|
152
230
|
text = _extract_text(result)
|
|
153
231
|
_emit(turn_id, "text", content=text)
|
|
154
232
|
_emit(turn_id, "done")
|
package/dist/connection.js
CHANGED
|
@@ -850,6 +850,7 @@ export async function connect(opts) {
|
|
|
850
850
|
MEL_ASSISTANT_PROVIDER: String(payload.provider || "claude_code"),
|
|
851
851
|
MEL_ASSISTANT_MODEL: String(payload.model || ""),
|
|
852
852
|
MEL_ASSISTANT_LANGUAGE: String(payload.language || "en"),
|
|
853
|
+
MEL_ASSISTANT_SURFACE: String(payload.surface || ""),
|
|
853
854
|
// Per-user creds (incl. MELAYA_API_KEY for the tenant-gated tool bridge).
|
|
854
855
|
...(payload.credentials || {}),
|
|
855
856
|
};
|