---
name: codex-session
description: 读取 Codex 会话的上下文。当消息中出现 codex://threads/<id> 链接，或用户要求读取/查看/引用某个 Codex 会话、继续某个 Codex 会话的工作时使用。提取会话标题、工作目录、git 分支、用户提问和最终总结，输出结构化摘要。
---

> 仓库地址: https://github.com/toosean/pi-codex-session

# Codex 会话读取

当用户消息中包含 `codex://threads/<id>` 链接，或明确要求引用某个 Codex 会话时，按以下步骤解析该会话并提取上下文。

## 步骤

### 1. 提取 thread id

从链接中提取 UUIDv7 形式的会话 ID：

```bash
# 从文本中匹配，形如 019fc30b-668e-79c1-a426-84c8acbd56e6
# 示例：codex://threads/019fc30b-668e-79c1-a426-84c8acbd56e6
```

### 2. 定位 rollout 文件

```bash
THREAD_ID="<上一步提取的id>"
ls ~/.codex/sessions/*/*/*/rollout-*-${THREAD_ID}.jsonl 2>/dev/null
ls ~/.codex/archived_sessions/*/*/*/rollout-*-${THREAD_ID}.jsonl 2>/dev/null
```

如果找不到，也可以从 SQLite 元数据查路径：

```bash
python3 -c "
import sqlite3
conn = sqlite3.connect('$HOME/.codex/state_5.sqlite')
print(conn.execute('SELECT rollout_path, title, cwd, git_branch FROM threads WHERE id = ?', ('$THREAD_ID',)).fetchall())
"
```

### 3. 解析会话内容

用下面的 Python 脚本提取结构化信息（把 `THREAD_ID` 换成实际值）：

```bash
python3 - <<'EOF'
import json, glob, os, re

THREAD_ID = "THREAD_ID"
home = os.path.expanduser("~/.codex")

# 定位 rollout 文件
candidates = glob.glob(f"{home}/sessions/*/*/*/rollout-*-{THREAD_ID}.jsonl") \
           + glob.glob(f"{home}/archived_sessions/*/*/*/rollout-*-{THREAD_ID}.jsonl")
if not candidates:
    print("ERROR: 未找到会话文件")
    exit(1)
path = candidates[0]

meta = {}
user_msgs = []
task_completes = []
tool_calls = []

for line in open(path, encoding="utf-8"):
    try:
        obj = json.loads(line)
    except json.JSONDecodeError:
        continue
    p = obj.get("payload", {})
    t = obj.get("type")

    if t == "session_meta":
        meta = {
            "cwd": p.get("cwd", ""),
            "originator": p.get("originator", ""),
            "cli_version": p.get("cli_version", ""),
            "git": (p.get("git") or {}).get("branch", ""),
            "started": obj.get("timestamp", ""),
        }
    elif t == "response_item" and p.get("type") == "message" and p.get("role") == "user":
        for c in p.get("content") or []:
            text = (c.get("text") or "").strip()
            if not text:
                continue
            # 过滤系统注入噪音
            if text.startswith("# AGENTS.md") or text.startswith("<environment_context>") \
               or text.startswith("<app-context>") or "<INSTRUCTIONS>" in text[:200]:
                continue
            user_msgs.append(text[:1000])
    elif t == "event_msg" and p.get("type") == "task_complete":
        task_completes.append((p.get("last_agent_message") or "")[:2000])
    elif t == "response_item" and p.get("type") == "custom_tool_call":
        tool_calls.append(p.get("name") or "")

print("## 会话元信息")
print(f"- 标题/ID: {THREAD_ID}")
print(f"- 工作目录: {meta.get('cwd', '')}")
print(f"- git 分支: {meta.get('git', '')}")
print(f"- 发起端: {meta.get('originator', '')} (cli {meta.get('cli_version', '')})")
print(f"- 开始时间: {meta.get('started', '')}")
print(f"- 用户消息数: {len(user_msgs)} | 完成任务数: {len(task_completes)}")

if tool_calls:
    from collections import Counter
    print("\n## 工具使用概览")
    for name, cnt in Counter(tool_calls).most_common(10):
        print(f"- {name}: {cnt}")

print("\n## 用户提问（原文，按时间顺序）")
for i, m in enumerate(user_msgs, 1):
    print(f"### 提问 {i}")
    print(m[:800])

print("\n## 最终总结")
for t in task_completes[-3:]:
    print(t)
EOF
```

### 4. 输出摘要

把脚本结果整理成简洁摘要回复用户，重点突出：
- 会话在做什么（由标题 + 用户提问推断）
- 关键结论 / 最终总结
- 涉及的工作目录与分支（用户可能想继续这项工作）

## 注意事项

- 用户消息中混杂的 AGENTS.md 注入、`<environment_context>`、`<app-context>` 是系统噪音，**不要**当作真实提问
- 会话可能包含多个 turn（多次提问），全部列出
- 若会话最后没有 `task_complete`（最后停在 `task_started` 或 `turn_aborted`），说明会话可能被中断，摘要里要提示这一点
- rollout 文件可能很大（几 MB），用脚本解析，不要用 read 直接读整个文件
