Spaces:
Running
Running
fix: strip redundant speaker labels, add timestamps, protect delete_env
Browse files- Strip "**Parent (Adam):**" etc. from message text (already labeled)
- Add HH:MM timestamps to chatlog entries, display in frontend
- delete_env now requires actual variable/secret collision before deleting
(prevents agents from blindly deleting legitimate variables)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
frontend/electron-standalone.html
CHANGED
|
@@ -468,6 +468,7 @@
|
|
| 468 |
}
|
| 469 |
#chatlog-content .chat-msg .chat-speaker.adam { color: #f87171; }
|
| 470 |
#chatlog-content .chat-msg .chat-speaker.eve { color: #a78bfa; }
|
|
|
|
| 471 |
#game-container {
|
| 472 |
position: relative;
|
| 473 |
border: 0;
|
|
@@ -5647,7 +5648,8 @@ function toggleBrokerPanel() {
|
|
| 5647 |
el.innerHTML = chatlogCache.slice().reverse().map(m => {
|
| 5648 |
const cls = (m.speaker || '').toLowerCase();
|
| 5649 |
const text = chatLang === 'zh' ? (m.text_zh || m.text || '') : (m.text || '');
|
| 5650 |
-
|
|
|
|
| 5651 |
}).join('');
|
| 5652 |
el.scrollTop = 0;
|
| 5653 |
}
|
|
|
|
| 468 |
}
|
| 469 |
#chatlog-content .chat-msg .chat-speaker.adam { color: #f87171; }
|
| 470 |
#chatlog-content .chat-msg .chat-speaker.eve { color: #a78bfa; }
|
| 471 |
+
#chatlog-content .chat-msg .chat-time { color: #6b7280; font-size: 0.85em; }
|
| 472 |
#game-container {
|
| 473 |
position: relative;
|
| 474 |
border: 0;
|
|
|
|
| 5648 |
el.innerHTML = chatlogCache.slice().reverse().map(m => {
|
| 5649 |
const cls = (m.speaker || '').toLowerCase();
|
| 5650 |
const text = chatLang === 'zh' ? (m.text_zh || m.text || '') : (m.text || '');
|
| 5651 |
+
const timeStr = m.time ? `<span class="chat-time">${m.time}</span> ` : '';
|
| 5652 |
+
return `<div class="chat-msg">${timeStr}<span class="chat-speaker ${cls}">${m.speaker}:</span> ${text}</div>`;
|
| 5653 |
}).join('');
|
| 5654 |
el.scrollTop = 0;
|
| 5655 |
}
|
scripts/conversation-loop.py
CHANGED
|
@@ -237,10 +237,20 @@ def action_restart():
|
|
| 237 |
|
| 238 |
|
| 239 |
def action_delete_env(key):
|
| 240 |
-
"""Delete an environment variable
|
| 241 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
hf_api.delete_space_variable(CHILD_SPACE_ID, key)
|
| 243 |
-
return f"Deleted variable {key} from {CHILD_NAME}'s Space. Use [ACTION: restart] to apply."
|
| 244 |
except Exception as e:
|
| 245 |
return f"Error deleting variable {key}: {e}"
|
| 246 |
|
|
@@ -504,6 +514,15 @@ def call_llm(system_prompt, user_prompt):
|
|
| 504 |
def _has_chinese(s):
|
| 505 |
return bool(re.search(r'[\u4e00-\u9fff]', s))
|
| 506 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
def parse_bilingual(text):
|
| 508 |
"""Parse bilingual response into (en, zh)."""
|
| 509 |
display = re.sub(r'\[TASK\].*?\[/TASK\]', '', text, flags=re.DOTALL)
|
|
@@ -831,6 +850,7 @@ if reply:
|
|
| 831 |
clean, actions = parse_and_execute_turn(reply, ctx)
|
| 832 |
last_action_results = actions
|
| 833 |
en, zh = parse_bilingual(clean)
|
|
|
|
| 834 |
print(f"[Adam/EN] {en}")
|
| 835 |
if zh != en:
|
| 836 |
print(f"[Adam/ZH] {zh}")
|
|
@@ -839,7 +859,9 @@ if reply:
|
|
| 839 |
if ar['action'] == 'claude_code':
|
| 840 |
result_preview = ar['result'][:800].replace('\n', '\n ')
|
| 841 |
print(f" [CC-RESULT] {result_preview}")
|
| 842 |
-
|
|
|
|
|
|
|
| 843 |
if actions:
|
| 844 |
labels = " ".join(f"🔧{ar['action'].split(':')[0]}" for ar in actions)
|
| 845 |
entry["text"] = f"{en} {labels}"
|
|
@@ -888,6 +910,7 @@ def do_turn(speaker, other, space_url):
|
|
| 888 |
last_action_results = action_results
|
| 889 |
|
| 890 |
en, zh = parse_bilingual(clean_text)
|
|
|
|
| 891 |
print(f"[{speaker}/EN] {en}")
|
| 892 |
if zh != en:
|
| 893 |
print(f"[{speaker}/ZH] {zh}")
|
|
@@ -902,12 +925,16 @@ def do_turn(speaker, other, space_url):
|
|
| 902 |
else:
|
| 903 |
print(f"[{speaker}] Turn #{turn_count}: no task assigned ({elapsed:.1f}s)")
|
| 904 |
|
| 905 |
-
# Add to history
|
|
|
|
|
|
|
|
|
|
| 906 |
if action_results:
|
| 907 |
labels = " ".join(f"🔧{ar['action'].split(':')[0]}" for ar in action_results)
|
| 908 |
-
|
| 909 |
else:
|
| 910 |
-
|
|
|
|
| 911 |
|
| 912 |
set_bubble(space_url, en, zh)
|
| 913 |
post_chatlog(history)
|
|
|
|
| 237 |
|
| 238 |
|
| 239 |
def action_delete_env(key):
|
| 240 |
+
"""Delete an environment variable — ONLY if it collides with a secret (safety check)."""
|
| 241 |
try:
|
| 242 |
+
# Safety: only allow deleting variables that collide with secrets
|
| 243 |
+
vars_dict = hf_api.get_space_variables(CHILD_SPACE_ID)
|
| 244 |
+
if key not in (vars_dict or {}):
|
| 245 |
+
return f"BLOCKED: Variable '{key}' does not exist. Nothing to delete."
|
| 246 |
+
info = hf_api.space_info(CHILD_SPACE_ID)
|
| 247 |
+
secret_names = set()
|
| 248 |
+
if hasattr(info, 'runtime') and info.runtime and hasattr(info.runtime, 'secrets'):
|
| 249 |
+
secret_names = set(info.runtime.secrets or [])
|
| 250 |
+
if key not in secret_names:
|
| 251 |
+
return f"BLOCKED: Variable '{key}' does NOT collide with a secret. Refusing to delete a non-colliding variable."
|
| 252 |
hf_api.delete_space_variable(CHILD_SPACE_ID, key)
|
| 253 |
+
return f"Deleted colliding variable '{key}' from {CHILD_NAME}'s Space. Use [ACTION: restart] to apply."
|
| 254 |
except Exception as e:
|
| 255 |
return f"Error deleting variable {key}: {e}"
|
| 256 |
|
|
|
|
| 514 |
def _has_chinese(s):
|
| 515 |
return bool(re.search(r'[\u4e00-\u9fff]', s))
|
| 516 |
|
| 517 |
+
def _strip_speaker_labels(text):
|
| 518 |
+
"""Remove redundant speaker self-references like **Parent (Adam):** or **Eve:** etc."""
|
| 519 |
+
# Patterns: **Parent (Adam):**, **Adam:**, **父亲 (Adam):**, **Eve:**, **母亲:**, etc.
|
| 520 |
+
text = re.sub(r'\*\*(?:Parent|Father|Mother|Dad|Mom|父亲|母亲|父级|亲爱的|伴侣)?\s*\(?(?:Adam|Eve|亚当|夏娃)?\)?\s*[::]\*\*\s*', '', text)
|
| 521 |
+
# Also: "Adam:" or "Eve:" at the very start of text
|
| 522 |
+
text = re.sub(r'^(?:Adam|Eve|亚当|夏娃)\s*[::]\s*', '', text.strip())
|
| 523 |
+
return text.strip()
|
| 524 |
+
|
| 525 |
+
|
| 526 |
def parse_bilingual(text):
|
| 527 |
"""Parse bilingual response into (en, zh)."""
|
| 528 |
display = re.sub(r'\[TASK\].*?\[/TASK\]', '', text, flags=re.DOTALL)
|
|
|
|
| 850 |
clean, actions = parse_and_execute_turn(reply, ctx)
|
| 851 |
last_action_results = actions
|
| 852 |
en, zh = parse_bilingual(clean)
|
| 853 |
+
en, zh = _strip_speaker_labels(en), _strip_speaker_labels(zh)
|
| 854 |
print(f"[Adam/EN] {en}")
|
| 855 |
if zh != en:
|
| 856 |
print(f"[Adam/ZH] {zh}")
|
|
|
|
| 859 |
if ar['action'] == 'claude_code':
|
| 860 |
result_preview = ar['result'][:800].replace('\n', '\n ')
|
| 861 |
print(f" [CC-RESULT] {result_preview}")
|
| 862 |
+
import datetime
|
| 863 |
+
ts = datetime.datetime.utcnow().strftime("%H:%M")
|
| 864 |
+
entry = {"speaker": "Adam", "time": ts, "text": en, "text_zh": zh}
|
| 865 |
if actions:
|
| 866 |
labels = " ".join(f"🔧{ar['action'].split(':')[0]}" for ar in actions)
|
| 867 |
entry["text"] = f"{en} {labels}"
|
|
|
|
| 910 |
last_action_results = action_results
|
| 911 |
|
| 912 |
en, zh = parse_bilingual(clean_text)
|
| 913 |
+
en, zh = _strip_speaker_labels(en), _strip_speaker_labels(zh)
|
| 914 |
print(f"[{speaker}/EN] {en}")
|
| 915 |
if zh != en:
|
| 916 |
print(f"[{speaker}/ZH] {zh}")
|
|
|
|
| 925 |
else:
|
| 926 |
print(f"[{speaker}] Turn #{turn_count}: no task assigned ({elapsed:.1f}s)")
|
| 927 |
|
| 928 |
+
# Add to history with timestamp
|
| 929 |
+
import datetime
|
| 930 |
+
ts = datetime.datetime.utcnow().strftime("%H:%M")
|
| 931 |
+
entry = {"speaker": speaker, "time": ts}
|
| 932 |
if action_results:
|
| 933 |
labels = " ".join(f"🔧{ar['action'].split(':')[0]}" for ar in action_results)
|
| 934 |
+
entry.update({"text": f"{en} {labels}", "text_zh": f"{zh} {labels}"})
|
| 935 |
else:
|
| 936 |
+
entry.update({"text": en, "text_zh": zh})
|
| 937 |
+
history.append(entry)
|
| 938 |
|
| 939 |
set_bubble(space_url, en, zh)
|
| 940 |
post_chatlog(history)
|