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
61 changes: 54 additions & 7 deletions .github/workflows/auto-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ on:
permissions:
contents: write

# Serialize auto-tag runs per branch to prevent race conditions when
# multiple version bumps land on main in quick succession.
concurrency:
group: auto-tag-${{ github.ref_name }}
cancel-in-progress: false

jobs:
tag:
runs-on: ubuntu-latest
Expand All @@ -39,11 +45,20 @@ jobs:
- name: Read workspace version
id: ver
run: |
# Robust version parsing: extract version from workspace Cargo.toml
# and validate it matches semver format vX.Y.Z (without v prefix).
v="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
if [ -z "$v" ]; then
echo "::error::Could not parse workspace version from Cargo.toml" >&2
exit 1
fi

# Validate semver format (digits.digits.digits)
if ! echo "$v" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::Workspace version '$v' is not valid semver (expected X.Y.Z)" >&2
exit 1
fi

echo "version=$v" >> "$GITHUB_OUTPUT"
echo "tag=v$v" >> "$GITHUB_OUTPUT"
echo "Workspace version: $v"
Expand All @@ -54,18 +69,26 @@ jobs:
TAG: ${{ steps.ver.outputs.tag }}
run: |
git fetch --tags --quiet
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null \
|| git ls-remote --tags origin "refs/tags/${TAG}" | grep -q .; then

# Check both local tags and remote tags for idempotency
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null 2>&1 || \
git ls-remote --tags origin "refs/tags/${TAG}" | grep -q .; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Tag ${TAG} already exists; nothing to do."
echo "::notice::Tag ${TAG} already exists; nothing to do (idempotent)."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "Tag ${TAG} does not exist; will create."
fi

- name: Verify version consistency
if: steps.check.outputs.exists == 'false'
run: ./scripts/release/check-versions.sh
run: |
# Run version consistency check before creating tag
# This prevents tagging inconsistent versions
./scripts/release/check-versions.sh || {
echo "::error::Version consistency check failed. Aborting tag creation." >&2
exit 1
}

- name: Create and push tag
if: steps.check.outputs.exists == 'false'
Expand All @@ -74,9 +97,33 @@ jobs:
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag "${TAG}"
git push origin "${TAG}"
echo "Pushed ${TAG}. release.yml should now run (requires RELEASE_TAG_PAT for trigger)."

# Create annotated tag with release information
git tag -a "${TAG}" -m "Release ${TAG}

Automatically created by auto-tag workflow.
Commit: ${GITHUB_SHA}
Repository: ${GITHUB_REPOSITORY}
"

# Push with retry logic for network resilience
max_retries=3
retry_count=0
while [[ ${retry_count} -lt ${max_retries} ]]; do
if git push origin "${TAG}" 2>/dev/null; then
echo "Successfully pushed ${TAG}."
echo "release.yml should now run (requires RELEASE_TAG_PAT for trigger)."
exit 0
fi
retry_count=$((retry_count + 1))
if [[ ${retry_count} -lt ${max_retries} ]]; then
echo "Push attempt ${retry_count} failed; retrying in 10s..."
sleep 10
fi
done

echo "::error::Failed to push tag ${TAG} after ${max_retries} attempts." >&2
exit 1

- name: Warn if PAT missing
if: steps.check.outputs.exists == 'false' && env.HAS_PAT != 'true'
Expand Down
93 changes: 91 additions & 2 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ on:
permissions:
contents: read

# Prevent concurrent nightly builds for the same branch; cancel older runs
# when a new push arrives. The group key includes the branch name so that
# nightly builds on main are serialized, but manual dispatches from other
# branches can still run independently.
concurrency:
group: nightly-${{ github.ref }}
group: nightly-${{ github.ref_name }}
cancel-in-progress: true

env:
Expand All @@ -18,7 +22,61 @@ env:
DEEPSEEK_BUILD_SHA: ${{ github.sha }}

jobs:
# Idempotency guard: skip the entire workflow if nightly artifacts for
# this exact commit already exist. This prevents wasteful rebuilds when
# multiple commits land on main in quick succession or when a workflow
# is re-run manually.
check-artifacts:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check.outputs.skip }}
steps:
- uses: actions/checkout@v4
- name: Check if nightly artifacts already exist
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_SHA: ${{ github.sha }}
run: |
short_sha="${COMMIT_SHA::12}"
# Check for any artifact from this commit in the last 14 days
# (matching the retention-days setting below). If all platform
# artifacts exist, we can safely skip the build.
artifact_count=$(gh run list --workflow nightly.yml \
--branch main \
--created ">=$(date -d '14 days ago' +%Y-%m-%d)" \
--json databaseId \
--jq 'length')

if [[ "${artifact_count}" -gt 0 ]]; then
latest_run_id=$(gh run list --workflow nightly.yml \
--branch main \
--created ">=$(date -d '14 days ago' +%Y-%m-%d)" \
--json databaseId,headSha \
--jq '[.[] | select(.headSha == env.COMMIT_SHA)] | first | .databaseId' 2>/dev/null || true)

if [[ -n "${latest_run_id}" ]]; then
artifacts=$(gh run view "${latest_run_id}" --json artifacts --jq '.artifacts | length')
# We expect 10 artifacts (5 platforms × 2 binaries: codewhale + codewhale-tui)
if [[ "${artifacts}" -ge 10 ]]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Nightly artifacts for commit ${short_sha} already exist; skipping build."
else
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "Nightly artifacts incomplete (${artifacts}/10); rebuilding."
fi
else
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "No nightly run found for this commit; building."
fi
else
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "No recent nightly runs found; building."
fi

build:
needs: [check-artifacts]
if: ${{ needs.check-artifacts.outputs.skip != 'true' }}
name: Build ${{ matrix.artifact_name }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -86,7 +144,20 @@ jobs:
sudo apt-get install -y libdbus-1-dev pkg-config
- name: Build
shell: bash
run: cargo build --release --locked --target ${{ matrix.target }}
run: |
# Retry build up to 2 times on transient failure (e.g. network
# issues fetching crates, OOM kills on shared runners).
for attempt in 1 2 3; do
if cargo build --release --locked --target ${{ matrix.target }}; then
exit 0
fi
if [[ ${attempt} -lt 3 ]]; then
echo "Build attempt ${attempt} failed; retrying in 30s..."
sleep 30
fi
done
echo "Build failed after 3 attempts" >&2
exit 1
- name: Stage artifact
id: stage
shell: bash
Expand All @@ -113,3 +184,21 @@ jobs:
name: ${{ steps.stage.outputs.name }}
path: nightly/*
retention-days: 14

# Summary job that aggregates the matrix build results and provides a
# single status indicator for branch protection rules.
nightly-complete:
needs: [build]
if: always()
runs-on: ubuntu-latest
steps:
- name: Check matrix build status
run: |
if [[ "${{ needs.build.result }}" == "success" ]]; then
echo "✅ All nightly builds completed successfully"
elif [[ "${{ needs.build.result }}" == "skipped" ]]; then
echo "ℹ️ Nightly builds were skipped (artifacts already exist)"
else
echo "❌ Nightly builds failed or were cancelled"
exit 1
fi
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Check the [releases page](https://github.com/Hmbown/CodeWhale/releases) for the
Report privately via one of:

- **GitHub private advisory**: [github.com/Hmbown/CodeWhale/security/advisories/new](https://github.com/Hmbown/CodeWhale/security/advisories/new)
- **Email**: [security@deepseek-tui.com](mailto:security@deepseek-tui.com) — include `[SECURITY]` in the subject line
- **Email**: [security@codewhale.com](mailto:security@codewhale.com) — include `[SECURITY]` in the subject line

Include in your report:

Expand Down
1 change: 1 addition & 0 deletions codewhale
Submodule codewhale added at 5dffec
6 changes: 6 additions & 0 deletions crates/tui/src/core/engine/tool_catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ pub(super) const DEFAULT_ACTIVE_NATIVE_TOOLS: &[&str] = &[
"apply_patch",
"checklist_write",
"edit_file",
"exec_interact",
"exec_shell",
"exec_shell_interact",
"exec_shell_wait",
"exec_wait",
"fetch_url",
"file_search",
"git_diff",
Expand All @@ -46,6 +50,8 @@ pub(super) const DEFAULT_ACTIVE_NATIVE_TOOLS: &[&str] = &[
"task_create",
"task_list",
"task_read",
"task_shell_start",
"task_shell_wait",
"update_plan",
"web_search",
"write_file",
Expand Down
2 changes: 1 addition & 1 deletion crates/tui/src/tui/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ const DISPATCH_WATCHDOG_TIMEOUT: Duration = Duration::from_secs(30);
// the per-tool spinner pulse — keep this fast enough that the spout reads as
// motion (~12 fps) instead of teleport-frames.
const UI_STATUS_ANIMATION_MS: u64 = 80;
const SIDEBAR_VISIBLE_MIN_WIDTH: u16 = 100;
const SIDEBAR_VISIBLE_MIN_WIDTH: u16 = 60;
const DEFAULT_TERMINAL_PROBE_TIMEOUT_MS: u64 = 500;
const PERIODIC_FULL_REPAINT_EVERY_N: u64 = 50;
const TURN_META_PREFIX: &str = "<turn_meta>";
Expand Down
9 changes: 9 additions & 0 deletions fix-edit_file-fuzz.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The optional `fuzz` parameter was required to attempt the leading-indentation fuzzy fallback when exact search found zero matches. This forced the model to make two calls on every edit that needed fuzzy matching (first without fuzz -> error -> second with fuzz: true), causing a round-trip delay.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This patch file seems to be a temporary or local file that was committed by accident, as it is not mentioned in the PR description and does not seem to be used by any automated process in the repository. If it is indeed a temporary file, please remove it from the PR.


Fix: remove the `fuzz` gate from the count == 0 branch. The tool now automatically retries with indentation-tolerant fuzzy matching when exact search produces no results. The `fuzz` parameter is kept in the schema for backward compatibility but marked deprecated.

Changes:
- crates/tui/src/tools/file.rs: `if count == 0 && fuzz` -> `if count == 0` (always retry fuzzy fallback)
- crates/tui/src/tools/file.rs: removed dead `else if count == 0 { error }` branch
- crates/tui/src/tools/file.rs: updated description to note automatic fuzzy fallback
- crates/tui/src/tools/file.rs: marked fuzz parameter as deprecated in schema
39 changes: 39 additions & 0 deletions fix_engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import re

file_path = r'C:\project\F_project1\CodeWhale\crates\tui\src\core\engine.rs'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The script uses a hardcoded absolute Windows path (C:\project\F_project1...), which makes it non-portable and will fail on other environments (including CI and other developers' machines). Additionally, this file seems to be a temporary script that was committed by accident. If it is needed, please use a relative path from the repository root to ensure portability.

Suggested change
file_path = r'C:\project\F_project1\CodeWhale\crates\tui\src\core\engine.rs'
file_path = 'crates/tui/src/core/engine.rs'

with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()

# 1. Add helper function after runtime_prompt_text
marker = ' </runtime_prompt>"\n )\n}\n\n/// Spawn the engine'
helper_fn = ''' </runtime_prompt>"
)
}

/// Check if a user message contains real user input (not just runtime metadata).
/// Returns true if the message has actual user text content beyond internal tags.
fn has_real_user_content(text: &str) -> bool {
// Strip known internal tags and check if meaningful content remains
let stripped = text
.replace("<turn_meta>", "")
.replace("</turn_meta>", "")
.replace("<runtime_prompt", "")
.replace("</runtime_prompt>", "")
.replace("<codewhale:runtime_event", "")
.replace("</codewhale:runtime_event>", "");

// Check if there's non-whitespace content after stripping tags
let trimmed = stripped.trim();
!trimmed.is_empty() && trimmed.len() > 10 // Allow for minimal metadata
}

/// Spawn the engine'''

if marker in content:
content = content.replace(marker, helper_fn)
print("OK: helper function added")
else:
print("WARN: marker not found for helper function")

with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
33 changes: 33 additions & 0 deletions pr-body-agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Summary

Auto-collapse completed sub-agents in the Agents sidebar panel. Non-running agents now show only a single line (label), freeing vertical space for active agents.

## Problem

Completed/failed/interrupted/cancelled sub-agents each occupied **2 lines** in the sidebar (label + detail line), wasting space that could be used for running agents or other content. With many agents, the sidebar became unnecessarily crowded.

## Solution

In `subagent_panel_lines()`, check the agent status before rendering the detail line. If the agent is not running (i.e. completed, failed, interrupted, cancelled), skip the detail line entirely and only render the single-line label.

**Before:**
```
✓ explore foo ← 2 lines per agent
abc123 · 3 steps · 12.3s
✗ build failed ← 2 lines per agent
def456 · 7 steps · 45.6s
● analysis running ← 2 lines per agent
ghi789 · 2 steps · 5.1s · parsing output...
```

**After:**
```
✓ explore foo ← 1 line (collapsed)
✗ build failed ← 1 line (collapsed)
● analysis running ← 2 lines (expanded: label + detail)
ghi789 · 2 steps · 5.1s · parsing output...
```

## File changed

`crates/tui/src/tui/sidebar.rs` — 5 lines added: `is_completed` check + early `continue` before the detail line.
27 changes: 27 additions & 0 deletions pr-body-cjk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Bug

When `assistant_text` contains CJK characters (3-byte UTF-8), the byte slice
`&assistant_text[..SUMMARY_LIMIT.saturating_sub(3)]` (byte 277) can land in
the middle of a multi-byte sequence, causing a panic:

```
byte index 277 is not a char boundary (it is inside a 3-byte UTF-8 sequence)
```

## Fix

Replace the raw byte-index slice with `truncate_with_ellipsis()` which already
finds the nearest safe char boundary via `char_indices()`.

## Change

```diff
-format!("{}...", &assistant_text[..SUMMARY_LIMIT.saturating_sub(3)])
+crate::utils::truncate_with_ellipsis(&assistant_text, SUMMARY_LIMIT, "...")
```

1 line changed.

## Files

- crates/tui/src/runtime_threads.rs:1437
9 changes: 9 additions & 0 deletions pr-body-fuzz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The optional `fuzz` parameter was required to attempt the leading-indentation fuzzy fallback when exact search found zero matches. This forced the model to make two calls on every edit that needed fuzzy matching (first without fuzz -> error -> second with fuzz: true), causing a round-trip delay.

Fix: remove the `fuzz` gate from the count == 0 branch. The tool now automatically retries with indentation-tolerant fuzzy matching when exact search produces no results. The `fuzz` parameter is kept in the schema for backward compatibility but marked deprecated.

Changes:
- crates/tui/src/tools/file.rs: `if count == 0 && fuzz` -> `if count == 0` (always retry fuzzy fallback)
- crates/tui/src/tools/file.rs: removed dead `else if count == 0 { error }` branch
- crates/tui/src/tools/file.rs: updated description to note automatic fuzzy fallback
- crates/tui/src/tools/file.rs: marked fuzz parameter as deprecated in schema
Loading