feat(worklog): 支援 README 定義的摘要 CLI
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
# ==============================================================================
|
||||
# 用途:worklog 的 transcript 處理工具。負責 (1) 從 Claude Code transcript
|
||||
# 用途:worklog 的 transcript 處理工具。負責 (1) 從 Claude Code/Codex
|
||||
# JSONL 抽出「本輪」對話片段(最後一筆使用者訊息之後的全部內容),
|
||||
# (2) 估算本輪花費時間,(3) 對文字做機密遮蔽(token/密碼/PII),
|
||||
# 作為寫入 wiki 前的第二道防線。
|
||||
# 更新時間:2026/07/27 15:09:00
|
||||
# 更新時間:2026/07/27 22:16:00
|
||||
# 相依:Python 3 標準庫。全程僅走 stdin/stdout,不寫任何檔案。
|
||||
# ==============================================================================
|
||||
|
||||
@@ -42,7 +42,11 @@ def redact(text):
|
||||
|
||||
|
||||
def _is_real_user_message(entry):
|
||||
"""判斷 transcript 條目是否為真正的使用者輸入(排除 tool_result 回填的 user 條目)。"""
|
||||
"""判斷 transcript 條目是否為真正的使用者輸入(排除工具回填與環境注入)。"""
|
||||
payload = entry.get("payload")
|
||||
if isinstance(payload, dict) and entry.get("type") == "event_msg":
|
||||
return payload.get("type") == "user_message" and bool(str(payload.get("message") or "").strip())
|
||||
|
||||
if entry.get("type") != "user":
|
||||
return False
|
||||
content = entry.get("message", {}).get("content")
|
||||
@@ -61,8 +65,77 @@ def _blocks(entry):
|
||||
return content if isinstance(content, list) else []
|
||||
|
||||
|
||||
def _payload_text_blocks(content):
|
||||
"""把 Codex response_item 的 content blocks 轉成純文字片段。"""
|
||||
if isinstance(content, str):
|
||||
return [content]
|
||||
if not isinstance(content, list):
|
||||
return []
|
||||
texts = []
|
||||
for block in content:
|
||||
if not isinstance(block, dict):
|
||||
continue
|
||||
if block.get("type") in ("input_text", "output_text", "text"):
|
||||
text = (block.get("text") or "").strip()
|
||||
if text:
|
||||
texts.append(text)
|
||||
return texts
|
||||
|
||||
|
||||
def _render_codex_payload(entry):
|
||||
"""將 Codex session JSONL 的 payload 格式轉為摘要輸入用純文字。"""
|
||||
payload = entry.get("payload")
|
||||
if not isinstance(payload, dict):
|
||||
return []
|
||||
|
||||
lines = []
|
||||
entry_type = entry.get("type")
|
||||
payload_type = payload.get("type")
|
||||
|
||||
if entry_type == "event_msg":
|
||||
if payload_type == "user_message":
|
||||
message = (payload.get("message") or "").strip()
|
||||
if message:
|
||||
lines.append(f"[user] {message}")
|
||||
elif payload_type == "agent_message":
|
||||
message = (payload.get("message") or "").strip()
|
||||
if message:
|
||||
phase = payload.get("phase") or "assistant"
|
||||
lines.append(f"[assistant:{phase}] {message}")
|
||||
return lines
|
||||
|
||||
if entry_type != "response_item":
|
||||
return lines
|
||||
|
||||
if payload_type == "message":
|
||||
role = payload.get("role") or "assistant"
|
||||
if role in ("system", "developer"):
|
||||
return lines
|
||||
for text in _payload_text_blocks(payload.get("content")):
|
||||
# Codex 會把 skill 內容以 user role 注入;避免把整份 SKILL.md 當成本輪工作。
|
||||
if role == "user" and text.lstrip().startswith("<skill>"):
|
||||
continue
|
||||
if role == "user" and text.lstrip().startswith("<environment_context>"):
|
||||
continue
|
||||
lines.append(f"[{role}] {text}")
|
||||
elif payload_type == "function_call":
|
||||
name = payload.get("name") or "?"
|
||||
raw = str(payload.get("arguments") or "").strip().replace("\n", " ")
|
||||
lines.append(f"[tool:{name}] {raw[:TOOL_INPUT_LIMIT]}")
|
||||
elif payload_type == "function_call_output":
|
||||
raw = str(payload.get("output") or "").strip().replace("\n", " ")
|
||||
if raw:
|
||||
lines.append(f"[result] {raw[:TOOL_RESULT_LIMIT]}")
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def _render(entry):
|
||||
"""將單一 transcript 條目轉為摘要輸入用的純文字行(工具結果僅取前段)。"""
|
||||
codex_lines = _render_codex_payload(entry)
|
||||
if codex_lines:
|
||||
return codex_lines
|
||||
|
||||
role = entry.get("type")
|
||||
lines = []
|
||||
for block in _blocks(entry):
|
||||
|
||||
Reference in New Issue
Block a user