Skip to content

Commit 3a7c012

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/docs/npm_and_yarn-e4e91a86d1
2 parents c7706b9 + b85feee commit 3a7c012

3 files changed

Lines changed: 63 additions & 42 deletions

File tree

components/runners/ambient-runner/ag_ui_claude_sdk/adapter.py

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,55 +1310,62 @@ def flush_pending_msg():
13101310
# Emit MESSAGES_SNAPSHOT with input messages + new messages from this run.
13111311
# Enrich tool result messages with tool names so the frontend can
13121312
# reconstruct parent-child hierarchy with proper display names.
1313-
if run_messages:
1314-
enriched: list[Any] = []
1315-
for msg in run_messages:
1316-
# Check if this is a tool result message that needs a name
1317-
msg_role = getattr(msg, "role", None)
1318-
msg_tcid = getattr(msg, "tool_call_id", None)
1319-
if msg_role == "tool" and msg_tcid and msg_tcid in tool_name_by_id:
1320-
# Convert to dict so we can add the name field
1321-
if hasattr(msg, "model_dump"):
1322-
d = msg.model_dump(exclude_none=True)
1323-
elif hasattr(msg, "dict"):
1324-
d = msg.dict(exclude_none=True)
1325-
else:
1326-
d = {
1327-
"id": getattr(msg, "id", ""),
1328-
"role": msg_role,
1329-
"content": getattr(msg, "content", ""),
1330-
"tool_call_id": msg_tcid,
1331-
}
1332-
d["name"] = tool_name_by_id[msg_tcid]
1333-
enriched.append(d)
1334-
else:
1335-
enriched.append(msg)
1336-
1337-
# Stamp input messages with the run-start timestamp so they
1338-
# survive a page refresh (the frontend's local timestamp is
1339-
# lost when reconnecting to the SSE stream).
1340-
run_start_iso = (
1341-
datetime.fromtimestamp(run_start_ts / 1000, tz=timezone.utc).isoformat()
1342-
if run_start_ts
1343-
else None
1344-
)
1345-
stamped_inputs: list[Any] = []
1346-
for msg in (input_data.messages if input_data else None) or []:
1313+
#
1314+
# Always emit MESSAGES_SNAPSHOT regardless of whether run_messages is
1315+
# populated — compactFinishedRun requires it to succeed. Runs that
1316+
# produce no assistant output (interrupted, state-tool-only, halted)
1317+
# still need a snapshot so the JSONL can be compacted and prior
1318+
# history is not lost. When run_messages is empty the snapshot is
1319+
# just the stamped input history.
1320+
enriched: list[Any] = []
1321+
for msg in run_messages:
1322+
# Check if this is a tool result message that needs a name
1323+
msg_role = getattr(msg, "role", None)
1324+
msg_tcid = getattr(msg, "tool_call_id", None)
1325+
if msg_role == "tool" and msg_tcid and msg_tcid in tool_name_by_id:
1326+
# Convert to dict so we can add the name field
13471327
if hasattr(msg, "model_dump"):
13481328
d = msg.model_dump(exclude_none=True)
1349-
elif isinstance(msg, dict):
1350-
d = dict(msg)
1329+
elif hasattr(msg, "dict"):
1330+
d = msg.dict(exclude_none=True)
13511331
else:
13521332
d = {
13531333
"id": getattr(msg, "id", ""),
1354-
"role": getattr(msg, "role", ""),
1334+
"role": msg_role,
13551335
"content": getattr(msg, "content", ""),
1336+
"tool_call_id": msg_tcid,
13561337
}
1357-
if "timestamp" not in d and run_start_iso:
1358-
d["timestamp"] = run_start_iso
1359-
stamped_inputs.append(d)
1338+
d["name"] = tool_name_by_id[msg_tcid]
1339+
enriched.append(d)
1340+
else:
1341+
enriched.append(msg)
1342+
1343+
# Stamp input messages with the run-start timestamp so they
1344+
# survive a page refresh (the frontend's local timestamp is
1345+
# lost when reconnecting to the SSE stream).
1346+
run_start_iso = (
1347+
datetime.fromtimestamp(run_start_ts / 1000, tz=timezone.utc).isoformat()
1348+
if run_start_ts
1349+
else None
1350+
)
1351+
stamped_inputs: list[Any] = []
1352+
for msg in (input_data.messages if input_data else None) or []:
1353+
if hasattr(msg, "model_dump"):
1354+
d = msg.model_dump(exclude_none=True)
1355+
elif isinstance(msg, dict):
1356+
d = dict(msg)
1357+
else:
1358+
d = {
1359+
"id": getattr(msg, "id", ""),
1360+
"role": getattr(msg, "role", ""),
1361+
"content": getattr(msg, "content", ""),
1362+
}
1363+
if "timestamp" not in d and run_start_iso:
1364+
d["timestamp"] = run_start_iso
1365+
stamped_inputs.append(d)
13601366

1361-
all_messages = stamped_inputs + enriched
1367+
all_messages = stamped_inputs + enriched
1368+
if all_messages:
13621369
logger.debug(
13631370
f"MESSAGES_SNAPSHOT: {len(all_messages)} msgs ({message_count} SDK messages processed)"
13641371
)

components/runners/ambient-runner/ambient_runner/bridges/claude/grpc_transport.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,12 @@ async def _handle_user_message(self, msg: Any) -> None:
294294
try:
295295
runner_input = RunnerInput.model_validate_json(msg.payload)
296296
except Exception:
297+
if not msg.payload or not msg.payload.strip():
298+
logger.warning(
299+
"[GRPC LISTENER] Empty payload for seq=%d, skipping to avoid empty-run with no MESSAGES_SNAPSHOT",
300+
msg.seq,
301+
)
302+
return
297303
runner_input = RunnerInput(
298304
messages=[
299305
{"id": str(uuid.uuid4()), "role": "user", "content": msg.payload}

components/runners/ambient-runner/ambient_runner/endpoints/run.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,19 @@ def to_run_agent_input(self) -> RunAgentInput:
4141
parent_run_id = self.parentRunId or self.parent_run_id
4242
context_list = self.context if isinstance(self.context, list) else []
4343

44+
# Ensure every message dict has an id so upsert_message deduplication
45+
# works correctly in the adapter for multi-turn sessions.
46+
messages = []
47+
for m in self.messages:
48+
if isinstance(m, dict) and not m.get("id"):
49+
m = {**m, "id": str(uuid.uuid4())}
50+
messages.append(m)
51+
4452
return RunAgentInput(
4553
thread_id=thread_id,
4654
run_id=run_id,
4755
parent_run_id=parent_run_id,
48-
messages=self.messages,
56+
messages=messages,
4957
state=self.state or {},
5058
tools=self.tools or [],
5159
context=context_list,

0 commit comments

Comments
 (0)