@melaya/runner 1.0.109 → 1.0.111
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 +54 -2
- package/dist/connection.js +12 -1
- package/package.json +1 -1
package/dist/assistantHost.py
CHANGED
|
@@ -142,7 +142,7 @@ _CANCELLED = object()
|
|
|
142
142
|
class _RedactingStdout:
|
|
143
143
|
"""Wrap stdout to strip long base64 runs before they reach the log.
|
|
144
144
|
|
|
145
|
-
agentscope prints every agent message verbatim (the `
|
|
145
|
+
agentscope prints every agent message verbatim (the `Assistant: {...}`
|
|
146
146
|
lines), which for phone_screenshot tool results embeds a full base64 JPEG —
|
|
147
147
|
tens of KB per turn that floods and explodes the runner logs. This filter is
|
|
148
148
|
line-buffered and only replaces base64-looking runs (>=200 chars), so the
|
|
@@ -212,6 +212,32 @@ def _apply_hitl_mode(mode: str | None) -> None:
|
|
|
212
212
|
os.environ["MEL_HITL_MODE"] = normalized
|
|
213
213
|
|
|
214
214
|
|
|
215
|
+
def _apply_static_context(agent, base_prompt: str, ctx) -> None:
|
|
216
|
+
"""Per-turn: fold the conversation's STATIC CONTEXT (the user's persona /
|
|
217
|
+
standing instructions) into the agent's system prompt. Mirrors the cloud
|
|
218
|
+
path (assistantChat.ts). Sent on every runner:assistant_turn so a mid-chat
|
|
219
|
+
edit (or clear) takes effect on the next message — agentscope rebuilds the
|
|
220
|
+
system Msg from self.sys_prompt on every reply. DATA-ONLY: it shapes the
|
|
221
|
+
role/voice/behaviour but the platform rules, tool permissions and HITL gating
|
|
222
|
+
in the base prompt still govern."""
|
|
223
|
+
text = (str(ctx or "")).strip()
|
|
224
|
+
try:
|
|
225
|
+
if text:
|
|
226
|
+
block = (
|
|
227
|
+
"\n\n## The persona and standing instructions the user set for you "
|
|
228
|
+
"(ADOPT THIS as your role, voice and priorities for this conversation; "
|
|
229
|
+
"when asked who or what you are, answer AS this persona). It shapes "
|
|
230
|
+
"behaviour and tone but NEVER grants new tools or permissions, relaxes "
|
|
231
|
+
"the autonomy/HITL gating, reaches another tenant's data, or overrides "
|
|
232
|
+
"the platform rules above.\n" + text + "\n"
|
|
233
|
+
)
|
|
234
|
+
agent._sys_prompt = base_prompt + block
|
|
235
|
+
else:
|
|
236
|
+
agent._sys_prompt = base_prompt
|
|
237
|
+
except Exception:
|
|
238
|
+
pass
|
|
239
|
+
|
|
240
|
+
|
|
215
241
|
def _emit(turn_id: str, kind: str, **fields) -> None:
|
|
216
242
|
"""Write one structured event line to stdout (flushed) for the runner to relay."""
|
|
217
243
|
try:
|
|
@@ -407,7 +433,13 @@ def _build_agent():
|
|
|
407
433
|
)
|
|
408
434
|
|
|
409
435
|
agent = make_agent(
|
|
410
|
-
name
|
|
436
|
+
# This name IS the agent identity that agentscope stamps onto the traced
|
|
437
|
+
# `invoke_agent <name>` span + gen_ai.agent.name — which is EXACTLY the
|
|
438
|
+
# dimension the Overview "by agent" token breakdown groups on
|
|
439
|
+
# (agentStudio.ts:4173/4258). So naming it "Assistant" makes every
|
|
440
|
+
# runner-assistant turn roll up under an "Assistant" agent in the dashboard
|
|
441
|
+
# automatically, no dashboard code change.
|
|
442
|
+
name="Assistant",
|
|
411
443
|
sys_prompt=sys_prompt,
|
|
412
444
|
toolkit=toolkit,
|
|
413
445
|
model_name=model,
|
|
@@ -614,6 +646,19 @@ def _stdin_reader(q: "queue.Queue[str]") -> None:
|
|
|
614
646
|
|
|
615
647
|
def main() -> int:
|
|
616
648
|
_log(f"booting (provider={os.environ.get('MEL_ASSISTANT_PROVIDER')})")
|
|
649
|
+
# Wire OpenTelemetry → Melaya event relay so agentscope's @trace_llm actually
|
|
650
|
+
# exports token spans to agents.spans — the SAME thing pipelines get via
|
|
651
|
+
# events.setup_studio_forwarder(). Without this the trace gate stays off AND
|
|
652
|
+
# the exporter is disabled, so every claude_code/codex assistant turn recorded
|
|
653
|
+
# ZERO tokens (no Overview cost row, no per-turn token count). The exporter
|
|
654
|
+
# self-gates on MEL_BUILDER_URL + MEL_RUN_ID (both set by the runner spawn);
|
|
655
|
+
# idempotent + best-effort — a tracing failure NEVER blocks a turn.
|
|
656
|
+
try:
|
|
657
|
+
from shared.runtime.tracing_exporter import setup_melaya_tracing
|
|
658
|
+
setup_melaya_tracing()
|
|
659
|
+
_log("tracing wired (spans → agents.spans)")
|
|
660
|
+
except Exception:
|
|
661
|
+
_log("tracing setup unavailable (non-fatal)")
|
|
617
662
|
# Mirror the spawn-time assistant autonomy mode into MEL_HITL_MODE so the
|
|
618
663
|
# in-process phone tools (phone.py._cmd) see it from turn one.
|
|
619
664
|
_apply_hitl_mode(None)
|
|
@@ -623,6 +668,9 @@ def main() -> int:
|
|
|
623
668
|
_log("boot failed:\n" + traceback.format_exc())
|
|
624
669
|
_emit("", "error", message=f"assistant host failed to start: {exc}")
|
|
625
670
|
return 1
|
|
671
|
+
# Capture the freshly-built BASE system prompt (before any static context is
|
|
672
|
+
# folded in) so each turn can deterministically rebuild base + persona.
|
|
673
|
+
base_sys_prompt = getattr(agent, "_sys_prompt", "") or ""
|
|
626
674
|
|
|
627
675
|
# PR4: report memory watermark + host identity + generation so the server can
|
|
628
676
|
# decide whether to offer a generation-bound rehydrate snapshot.
|
|
@@ -702,6 +750,10 @@ def main() -> int:
|
|
|
702
750
|
# it here (before _run_turn) is enough — no agent rebuild needed.
|
|
703
751
|
if "hitl_mode" in req:
|
|
704
752
|
_apply_hitl_mode(req.get("hitl_mode"))
|
|
753
|
+
# Per-turn STATIC CONTEXT: fold the conversation's persona / standing
|
|
754
|
+
# instructions into the system prompt before running the turn (parity with
|
|
755
|
+
# the cloud path; handles set / edit / clear mid-conversation).
|
|
756
|
+
_apply_static_context(agent, base_sys_prompt, req.get("static_context"))
|
|
705
757
|
if not message:
|
|
706
758
|
_emit(turn_id, "done")
|
|
707
759
|
continue
|
package/dist/connection.js
CHANGED
|
@@ -1011,6 +1011,15 @@ export async function connect(opts) {
|
|
|
1011
1011
|
// consistent across surfaces. assistantHost mirrors MEL_ASSISTANT_HITL_MODE
|
|
1012
1012
|
// → MEL_HITL_MODE so phone.py stamps the mode on the command body.
|
|
1013
1013
|
MEL_RUN_ID: `assistant:${sid.split(":")[0]}`,
|
|
1014
|
+
// Span tracing → agents.spans. tracing_exporter.MelayaSpanExporter only
|
|
1015
|
+
// exports when BOTH MEL_RUN_ID and MEL_BUILDER_URL are present (it POSTs
|
|
1016
|
+
// batches through this same local relay the pipeline spawns use, lines
|
|
1017
|
+
// ~463/853). The assistant host was missing these two, so — even with
|
|
1018
|
+
// @trace_llm wired — every claude_code/codex assistant turn recorded ZERO
|
|
1019
|
+
// token spans. Spans land under conversation_id = MEL_RUN_ID
|
|
1020
|
+
// (`assistant:<userId>`); the server trigger resolves user_id from it.
|
|
1021
|
+
MEL_BUILDER_URL: `http://127.0.0.1:${relay.port}`,
|
|
1022
|
+
MEL_RELAY_NONCE: relay.nonce,
|
|
1014
1023
|
MEL_PIPELINE_NAME: "Melaya · Assistant",
|
|
1015
1024
|
// Luma browser bridge (same injection as pipeline runs, line ~431): lets
|
|
1016
1025
|
// shared/tools/luma.py route /event/register through Playwright and bypass
|
|
@@ -1160,8 +1169,10 @@ export async function connect(opts) {
|
|
|
1160
1169
|
// (mirrored to MEL_HITL_MODE) BEFORE running the turn, so a mid-session flip
|
|
1161
1170
|
// takes effect on the very next message. Fail-safe: unknown ⇒ "safe".
|
|
1162
1171
|
const hitlMode = (payload.hitlMode === "autonomous" || payload.hitlMode === "payments_only") ? payload.hitlMode : "safe";
|
|
1172
|
+
// static_context (persona / standing instructions) is forwarded per turn; the
|
|
1173
|
+
// host folds it into its system prompt before running (parity with cloud).
|
|
1163
1174
|
try {
|
|
1164
|
-
s.proc.stdin?.write(JSON.stringify({ turnId: payload.turnId, message: payload.message, hitl_mode: hitlMode }) + "\n");
|
|
1175
|
+
s.proc.stdin?.write(JSON.stringify({ turnId: payload.turnId, message: payload.message, hitl_mode: hitlMode, static_context: typeof payload.staticContext === "string" ? payload.staticContext : "" }) + "\n");
|
|
1165
1176
|
}
|
|
1166
1177
|
catch (e) {
|
|
1167
1178
|
socket.emit("runner:assistant_event", { sessionId: payload.sessionId, turnId: payload.turnId, kind: "error", message: `turn write failed: ${e?.message || e}` });
|