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
50 changes: 50 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]

jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read

# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'

# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)'

2 changes: 1 addition & 1 deletion docs/open-api-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ openapi: 3.0.3
info:
title: The Agent's user-facing API
description: The user-facing parts of The Agent's API service (excluding system-level endpoints, chat completion, maintenance endpoints, etc.)
version: 5.0.21
version: 5.0.22
license:
name: MIT
url: https://opensource.org/licenses/MIT
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "the-agent"
version = "5.0.21"
version = "5.0.22"

[tool.setuptools]
package-dir = {"" = "src"}
Expand Down
5 changes: 3 additions & 2 deletions src/features/chat/telegram/telegram_markdown_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ def protect_inline_code(match):
text = re.sub(r"\*\*([^*]+?)\*\*", rf"{BOLD_START}{BOLD_START}\1{BOLD_END}{BOLD_END}", text)

# Protect *bold* (single asterisk - Telegram's format, not standard markdown!)
# Matches text with spaces inside: *bold text* is valid Telegram markdown
text = re.sub(r"(^|\s)\*([^*]+?)\*(\s|$)", rf"\1{BOLD_START}\2{BOLD_END}\3", text)
# Uses negative lookbehind/lookahead for word chars to allow punctuation adjacency:
# *bold*, and *bold*. are preserved, while 2*3*4 (word-char adjacent) still gets escaped
text = re.sub(r"(?<!\w)\*([^*\n]+?)\*(?!\w)", rf"{BOLD_START}\1{BOLD_END}", text)

# Protect _italic_ (underscore italic - allow spaces inside)
text = re.sub(r"\b_([^_]+?)_\b", rf"{ITALIC_START}\1{ITALIC_END}", text)
Expand Down
6 changes: 4 additions & 2 deletions src/features/prompting/prompt_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,10 @@ class _FormatLibrary:
section = PromptSection.format,
content = (
"You are messaging an informal WhatsApp chat, where a couple of good human friends are talking. "
"Structure your replies accordingly and use *WhatsApp's* markdown flavor for formatting, "
"for example single asterisk for bold, single backticks for code, single underscore for italic, etc. "
"Structure your replies accordingly and use *WhatsApp's* markdown flavor for formatting. "
"IMPORTANT: WhatsApp uses single asterisks for bold (*bold*), NOT double (**bold**). "
"Never use double asterisks (**) - they will break formatting on WhatsApp. "
"Also use single backticks for code, single underscore for italic, etc. "
"Messages sent to you are formatted programatically, and you should not mirror the input formatting - "
"especially when it comes to quotes and attachments. Follow the flow and format of the chat accordingly. "
"Note that while doing so, no headers `#` nor dividers `---` work in the WhatsApp context. "
Expand Down
18 changes: 18 additions & 0 deletions test/features/chat/telegram/test_telegram_markdown_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,21 @@ def test_escape_markdown_real_release_title(self):
text = "**🚀 Verzija 5.0.6: Čišći Digitalni Otisak**"
expected = "**🚀 Verzija 5.0.6: Čišći Digitalni Otisak**"
self.assertEqual(escape_markdown(text), expected)

def test_escape_markdown_bold_adjacent_to_punctuation(self):
# Bold text adjacent to punctuation should be preserved
self.assertEqual(escape_markdown("*bold*."), "*bold*.")
self.assertEqual(escape_markdown("*bold*,"), "*bold*,")
self.assertEqual(escape_markdown("*bold*!"), "*bold*!")
self.assertEqual(escape_markdown("*bold*?"), "*bold*?")
self.assertEqual(escape_markdown("(*bold*)"), "(*bold*)")

def test_escape_markdown_bold_at_start_with_punctuation(self):
# Bold at start of string adjacent to punctuation
self.assertEqual(escape_markdown("*Hello.*"), "*Hello.*")
self.assertEqual(escape_markdown("*Done*,"), "*Done*,")

def test_escape_markdown_bold_in_sentence_with_punctuation(self):
# Bold embedded in a sentence with trailing punctuation
self.assertEqual(escape_markdown("Say *Hello.* right now"), "Say *Hello.* right now")
self.assertEqual(escape_markdown("I am *very happy*, you know"), "I am *very happy*, you know")
Loading