Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,12 @@ receipt → observed work or actionable failure → readable answer in the same
summary through the shared activity contract; `rawThinking`, planner
completion and tool completion remain separate, and final answers still
replace the draft on the original source-bound post.
- Ordinary Codex Chat explicitly requests a concise native reasoning summary
on each Turn, including resumed and restricted answer-review threads whose
inherited summary setting may be `none`. Execution hosts retain their own
summary configuration. Only actual upstream summaries enter the existing
activity projection; a model that emits none keeps coarse activity labels.
This requests no extra Turn and never exposes raw reasoning content.
- Generate the visible answer once, then append a complete hidden review envelope
with `message: ""`. The production parser reuses the preceding Markdown and
applies the same redaction and typed metadata normalization. Legacy nonempty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,10 @@ CLI/SSH 导出使用 `goal-portfolio --manager-view todos --goal-id GOAL --todo-
命令标题。群聊及未经限定的投影仍只显示粗粒度状态。反重力把经过共享脱敏的
`plannerResponse.thinking` 摘要映射进活动契约;`rawThinking`、planner 完成与
工具完成仍是不同事实。最终答案仍在绑定原请求的同一消息中替换草稿。
- 普通 Codex Chat 在每个 Turn 明确请求简短的原生思考摘要,包括继承了
`summary=none` 的恢复会话和受限答复复核会话。执行宿主保留自己的摘要配置。
现有活动投影只展示上游实际返回的摘要;模型没有输出摘要时仍显示粗粒度状态。
该请求不增加 Turn,也不暴露原始推理正文。
- 可见答案只生成一次,随后附加完整隐藏 review envelope,`message: ""` 表示
复用前面的 Markdown,仍经过相同脱敏与 typed 元数据归一化。旧的非空 message
保持优先;缺失或 null 不启用该约定。Codex 采用完成的最终答案 item,排除之前
Expand Down
4 changes: 4 additions & 0 deletions loopx/chat_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -1754,6 +1754,10 @@ def send(
"cwd": str(self.work_dir),
**({"model": self.model} if self.model else {}),
**({"effort": self.reasoning_effort} if self.reasoning_effort else {}),
# Chat renders native summaries, but a resumed thread may have
# disabled them. Request the public summary on each Chat Turn;
# execution hosts retain their own verbosity configuration.
**({"summary": "concise"} if not self.execution_mode else {}),
"approvalPolicy": "never",
**({"permissions": self.permissions_profile} if self.permissions_profile else {}),
**(
Expand Down
20 changes: 16 additions & 4 deletions tests/test_chat_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -1718,35 +1718,47 @@ def test_turn_activity_does_not_invent_goal_reads_or_successful_checks(
assert any(kind == "answer.delta" and p["text"] == "Ready." for kind, p in events)


def test_turn_activity_steps_merge_started_and_completed_items(monkeypatch, tmp_path):
@pytest.mark.parametrize("execution_mode", [False, True])
def test_turn_activity_steps_merge_started_and_completed_items(monkeypatch, tmp_path, execution_mode):
session = chat_agent.CodexChatAgentSession(
process=_FakeAppServerProcess(),
messages=queue.Queue(),
thread_id="thread-fixture",
work_dir=tmp_path,
execution_mode=execution_mode,
)
command = {"type": "commandExecution", "id": "exec-1", "status": "inProgress",
"command": f"/bin/zsh -lc 'cat {tmp_path}/notes.md'",
"commandActions": [{"type": "read", "command": "cat notes.md", "name": "notes.md", "path": str(tmp_path / "notes.md")}]}
upstream = iter([
{"method": "turn/started", "params": {"turn": {"id": "turn-fixture"}}},
{"method": "item/started", "params": {"item": {"type": "reasoning", "id": "rs_1", "summary": [], "content": []}}},
{"method": "item/reasoning/summaryTextDelta", "params": {"itemId": "rs_1", "delta": "Checking the available notes.", "summaryIndex": 0}},
{"method": "item/reasoning/textDelta", "params": {"itemId": "rs_1", "delta": "Read the notes first.", "contentIndex": 0}},
{"method": "item/completed", "params": {"item": {"type": "reasoning", "id": "rs_1", "summary": [], "content": ["Read the notes first."]}}},
{"method": "item/completed", "params": {"item": {"type": "reasoning", "id": "rs_1", "summary": ["Checking the available notes."], "content": ["Read the notes first."]}}},
{"method": "item/started", "params": {"item": command}},
{"method": "item/completed", "params": {"item": {**command, "status": "completed", "exitCode": 0, "durationMs": 5,
"aggregatedOutput": "private-output-fixture"}}},
{"method": "item/agentMessage/delta", "params": {"delta": "Ready."}},
{"method": "turn/completed", "params": {"turn": {"status": "completed"}}},
])
monkeypatch.setattr(session, "_request", lambda *a, **kw: {"turn": {"id": "turn-fixture"}})
requests = []
monkeypatch.setattr(session, "_request", lambda method, params, **kw:
requests.append((method, params)) or {"turn": {"id": "turn-fixture"}})
monkeypatch.setattr(session, "_next_event", lambda **kw: next(upstream))
events = []
session.send("Reply briefly.", on_event=lambda kind, payload: events.append((kind, payload)))
# The versioned app-server API accepts a per-Turn summary override, which
# also replaces an inherited `none` on resumed Chat threads. Worker hosts
# must keep their own summary policy, independent of this Chat default.
assert len(requests) == 1 and requests[0][0] == "turn/start"
assert requests[0][1].get("summary") == (None if execution_mode else "concise")
steps = [p["step"] for kind, p in events if kind == "agent.phase" and "step" in p]
assert [(s["id"], s["state"]) for s in steps] == [
("rs_1", "running"), ("rs_1", "completed"), ("exec-1", "running"), ("exec-1", "completed")]
assert steps[1]["title"] == "" and "detail" not in steps[1]
# This immediate delta stays within the existing display throttle; the
# authoritative completion still retains its public summary.
assert steps[1]["reasoning_summary"] is True and steps[1]["title"] == "Checking the available notes."
assert "Read the notes first." not in json.dumps(events)
assert (steps[3]["verb"], steps[3]["title"], steps[3]["exit_code"]) == ("read", "notes.md", 0)
assert steps[3]["detail"] == "cat notes.md"
Expand Down
Loading