Skip to content
Merged
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
31 changes: 29 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@

---

## [1.0.1] - 2026-05-14

upstream `six-ddc/ccbot` pending merge 3건을 cherry-pick. 버그픽스 only.

### Fixed

- **Interactive UI 버튼 누를 때 중복 메시지 생성 수정** (upstream [`865ab89`](https://github.com/six-ddc/ccbot/commit/865ab89), #67)
- "Message is not modified" BadRequest를 별도 처리: 기존 메시지 유지하고 early return
- 다른 edit 실패 시에는 교체 메시지를 먼저 보내고 원본 삭제
- **bind 시 사용자가 만든 Telegram 토픽 이름 rename 안 함** (upstream [`350c653`](https://github.com/six-ddc/ccbot/commit/350c653), #73)
- 사용자가 직접 만든 토픽 이름을 ccbot이 자동 변경하지 않음
- **Write tool result의 line count 정확히 표시** (upstream [`f5ddd7f`](https://github.com/six-ddc/ccbot/commit/f5ddd7f))
- 기존: Write의 tool_result는 `File created successfully at: ...` 같은 확인 메시지라 line count가 항상 1이었음
- 변경: 원본 `tool_use.input.content`에서 line count 계산 (trailing newline 보정 포함)
- `_format_tool_result_text`에 `tool_input_data` 인자 추가 (시그니처 변경, 기본값 `None`이라 fork 내부 호출과 호환)

### Tests

- `tests/ccbot/test_transcript_parser.py::TestFormatToolResultText` 갱신
- parametrize에 `tool_input_data` 컬럼 추가, Write 케이스를 새 동작에 맞춰 수정
- 전체 283/283 통과

---

## [1.0.0] - 2026-05-14

TejNote fork의 첫 공식 버전. 2026-04-27 이후 누적된 fork 전용 추가 사항을 한 번에 v1.0.0으로 정리합니다 (이전 내부 버전 `0.1.0`).
Expand Down Expand Up @@ -72,13 +96,16 @@ TejNote fork의 첫 공식 버전. 2026-04-27 이후 누적된 fork 전용 추

### Pending upstream merges

`six-ddc/ccbot:main`에는 있지만 아직 fork에 reconcile 안 된 commit (후속 PR에서 cherry-pick 예정):
> ✅ 아래 3건은 모두 [1.0.1]에서 reconcile 완료.

`six-ddc/ccbot:main`에는 있지만 v1.0.0 시점에는 아직 fork에 reconcile 안 된 commit이었음:

| Upstream commit | 설명 |
| ------------------------------------------------------------------ | -------------------------------------------------------------------- |
| [`865ab89`](https://github.com/six-ddc/ccbot/commit/865ab89) (#67) | Interactive UI 버튼 누를 때 중복 메시지 생성되는 문제 수정 |
| [`350c653`](https://github.com/six-ddc/ccbot/commit/350c653) (#73) | bind 시 사용자가 만든 Telegram 토픽 이름을 rename하지 않도록 수정 |
| [`f5ddd7f`](https://github.com/six-ddc/ccbot/commit/f5ddd7f) | Write tool 결과의 line count 정확히 표시 |

[Unreleased]: https://github.com/TejNote/ccbot/compare/v1.0.0...HEAD
[Unreleased]: https://github.com/TejNote/ccbot/compare/v1.0.1...HEAD
[1.0.1]: https://github.com/TejNote/ccbot/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/TejNote/ccbot/releases/tag/v1.0.0
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ src/ccbot/

상세 변경 이력은 [`CHANGELOG.md`](./CHANGELOG.md) 참고. 버전 정책은 [SemVer](https://semver.org/lang/ko/)를 따르고, 포맷은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) 기준입니다.

현재 버전: **v1.0.0** (TejNote fork 첫 공식 릴리스, 2026-05-14).
현재 버전: **v1.0.1** (upstream pending merge 3건 reconcile, 2026-05-14).

## Contributing back upstream

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ccbot"
version = "1.0.0"
version = "1.0.1"
description = "Telegram Bot for monitoring Claude Code sessions"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
22 changes: 0 additions & 22 deletions src/ccbot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1240,17 +1240,6 @@ async def _create_and_bind_window(
user.id, pending_thread_id, created_wid, window_name=created_wname
)

# Rename the topic to match the window name
resolved_chat = session_manager.resolve_chat_id(user.id, pending_thread_id)
try:
await context.bot.edit_forum_topic(
chat_id=resolved_chat,
message_thread_id=pending_thread_id,
name=created_wname,
)
except Exception as e:
logger.debug(f"Failed to rename topic: {e}")

status = "Resumed" if resume_session_id else "Created"
await safe_edit(
query,
Expand Down Expand Up @@ -1638,17 +1627,6 @@ async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -
user.id, thread_id, selected_wid, window_name=display
)

# Rename the topic to match the window name
resolved_chat = session_manager.resolve_chat_id(user.id, thread_id)
try:
await context.bot.edit_forum_topic(
chat_id=resolved_chat,
message_thread_id=thread_id,
name=display,
)
except Exception as e:
logger.debug(f"Failed to rename topic: {e}")

await safe_edit(
query,
f"✅ Bound to window `{display}`",
Expand Down
28 changes: 23 additions & 5 deletions src/ccbot/handlers/interactive_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import logging

from telegram import Bot, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.error import BadRequest

from ..session import session_manager
from ..terminal_parser import extract_interactive_content, is_interactive_ui
Expand Down Expand Up @@ -202,13 +203,24 @@ async def handle_interactive_ui(
)
_interactive_mode[ikey] = window_id
return True
except Exception:
# Edit failed (message deleted, etc.) - clear stale msg_id and send new
except BadRequest as e:
if "Message is not modified" in str(e):
# Content unchanged — keep existing message as-is
_interactive_mode[ikey] = window_id
return True
# Other edit failure — fall through to send new message,
# but keep old message until replacement succeeds
logger.debug(
"Edit failed for interactive msg %s: %s, sending new",
existing_msg_id,
e,
)
except Exception as e:
logger.debug(
"Edit failed for interactive msg %s, sending new", existing_msg_id
"Edit failed for interactive msg %s: %s, sending new",
existing_msg_id,
e,
)
_interactive_msgs.pop(ikey, None)
# Fall through to send new message

# Send new message (plain text — terminal content is not markdown)
logger.info(
Expand All @@ -228,6 +240,12 @@ async def handle_interactive_ui(
if sent:
_interactive_msgs[ikey] = sent.message_id
_interactive_mode[ikey] = window_id
# New message sent successfully — now safe to delete the old one
if existing_msg_id:
try:
await bot.delete_message(chat_id=chat_id, message_id=existing_msg_id)
except Exception:
pass # Old message may already be gone
return True
return False

Expand Down
28 changes: 21 additions & 7 deletions src/ccbot/transcript_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,12 @@ def _format_expandable_quote(cls, text: str) -> str:
return f"{cls.EXPANDABLE_QUOTE_START}{text}{cls.EXPANDABLE_QUOTE_END}"

@classmethod
def _format_tool_result_text(cls, text: str, tool_name: str | None = None) -> str:
def _format_tool_result_text(
cls,
text: str,
tool_name: str | None = None,
tool_input_data: dict | None = None,
) -> str:
"""Format tool result text with statistics summary.

Shows relevant statistics for each tool type, with expandable quote for full content.
Expand All @@ -363,9 +368,16 @@ def _format_tool_result_text(cls, text: str, tool_name: str | None = None) -> st
return f" ⎿ Read {line_count} lines"

elif tool_name == "Write":
# Write: show lines written
stats = f" ⎿ Wrote {line_count} lines"
return stats
# Write: line count comes from the input content, not the result
# (result is usually just "File created successfully at: ...")
written = tool_input_data.get("content", "") if tool_input_data else ""
if not written:
written_lines = 0
else:
written_lines = written.count("\n") + (
0 if written.endswith("\n") else 1
)
return f" ⎿ Wrote {written_lines} lines"

elif tool_name == "Bash":
# Bash: show output line count
Expand Down Expand Up @@ -528,7 +540,9 @@ def parse_entries(
# Store tool info for later tool_result formatting
# Edit tool needs input_data to generate diff in tool_result stage
input_data = (
inp if name in ("Edit", "NotebookEdit") else None
inp
if name in ("Edit", "NotebookEdit", "Write")
else None
)
pending_tools[tool_id] = PendingToolInfo(
summary=summary,
Expand Down Expand Up @@ -691,7 +705,7 @@ def parse_entries(
and cls.EXPANDABLE_QUOTE_START not in tool_summary
):
entry_text += "\n" + cls._format_tool_result_text(
result_text, tool_name
result_text, tool_name, tool_input_data
)
result.append(
ParsedEntry(
Expand All @@ -708,7 +722,7 @@ def parse_entries(
ParsedEntry(
role="assistant",
text=cls._format_tool_result_text(
result_text, tool_name
result_text, tool_name, tool_input_data
)
if result_text
else (tool_summary or ""),
Expand Down
24 changes: 20 additions & 4 deletions tests/ccbot/test_transcript_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,21 +256,24 @@ def test_format_edit_diff(self, old: str, new: str, check):

class TestFormatToolResultText:
@pytest.mark.parametrize(
"text, tool_name, check",
"text, tool_name, tool_input_data, check",
[
(
"line1\nline2\nline3",
"Read",
None,
lambda r: r == " ⎿ Read 3 lines",
),
(
"line1\nline2",
"File created successfully at: out.txt",
"Write",
{"content": "line1\nline2"},
lambda r: r == " ⎿ Wrote 2 lines",
),
(
"output line",
"Bash",
None,
lambda r: (
r.startswith(" ⎿ Output 1 lines")
and EXPQUOTE_START in r
Expand All @@ -280,21 +283,25 @@ class TestFormatToolResultText:
(
"file1.py\nfile2.py\n",
"Grep",
None,
lambda r: "Found 2 matches" in r and EXPQUOTE_START in r,
),
(
"a.py\nb.py\nc.py",
"Glob",
None,
lambda r: "Found 3 files" in r and EXPQUOTE_START in r,
),
(
"agent says hello",
"Task",
None,
lambda r: "Agent output 1 lines" in r and EXPQUOTE_START in r,
),
(
"page content here",
"WebFetch",
None,
lambda r: (
f"Fetched {len('page content here')} characters" in r
and EXPQUOTE_START in r
Expand All @@ -303,13 +310,22 @@ class TestFormatToolResultText:
(
"",
"Read",
None,
lambda r: r == "",
),
],
ids=["Read", "Write", "Bash", "Grep", "Glob", "Task", "WebFetch", "empty"],
)
def test_format_tool_result_text(self, text: str, tool_name: str, check):
result = TranscriptParser._format_tool_result_text(text, tool_name)
def test_format_tool_result_text(
self,
text: str,
tool_name: str,
tool_input_data: dict | None,
check,
):
result = TranscriptParser._format_tool_result_text(
text, tool_name, tool_input_data
)
assert check(result), f"Failed check for {tool_name!r}: {result!r}"


Expand Down