feat(worklog): 支援 README 定義的摘要 CLI

This commit is contained in:
Jeffery
2026-07-27 17:09:11 +08:00
parent 2abe236c35
commit 01b890c21e
8 changed files with 199 additions and 62 deletions
+76 -3
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python3
# ==============================================================================
# 用途:worklog 的 transcript 處理工具。負責 (1) 從 Claude Code transcript
# 用途:worklog 的 transcript 處理工具。負責 (1) 從 Claude CodeCodex
# 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):