tao-shen Claude Opus 4.6 commited on
Commit
121b371
·
1 Parent(s): 21e2a50

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
- return `<div class="chat-msg"><span class="chat-speaker ${cls}">${m.speaker}:</span> ${text}</div>`;
 
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 from the child's Space (fixes CONFIG_ERROR collisions)."""
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
- entry = {"speaker": "Adam", "text": en, "text_zh": zh}
 
 
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
- history.append({"speaker": speaker, "text": f"{en} {labels}", "text_zh": f"{zh} {labels}"})
909
  else:
910
- history.append({"speaker": speaker, "text": en, "text_zh": zh})
 
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)