From 66db95f7fa1f6b2e8fdc8adf85724f46f1a4e5f4 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 5 Mar 2026 16:57:51 +0100 Subject: [PATCH 01/41] ci: add sync-build workflow --- .github/workflows/sync-build.yml | 68 ++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/sync-build.yml diff --git a/.github/workflows/sync-build.yml b/.github/workflows/sync-build.yml new file mode 100644 index 000000000..931557e79 --- /dev/null +++ b/.github/workflows/sync-build.yml @@ -0,0 +1,68 @@ +name: Sync upstream & build custom image + +on: + schedule: + - cron: '0 */6 * * *' # ogni 6 ore + workflow_dispatch: # trigger manuale + +jobs: + sync-and-build: + runs-on: ubuntu-latest + steps: + - name: Checkout fork + uses: actions/checkout@v4 + with: + ref: custom + fetch-depth: 0 + + - name: Fetch upstream tags + run: | + git remote add upstream https://github.com/RightNow-AI/openfang.git || true + git fetch upstream --tags + + - name: Check for new release + id: check + run: | + LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1) + CURRENT=$(cat .current-upstream-version 2>/dev/null || echo "none") + echo "latest=$LATEST_TAG" >> "$GITHUB_OUTPUT" + echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + if [ "$LATEST_TAG" != "$CURRENT" ]; then + echo "new_release=true" >> "$GITHUB_OUTPUT" + else + echo "new_release=false" >> "$GITHUB_OUTPUT" + fi + + - name: Rebase custom on latest tag + if: steps.check.outputs.new_release == 'true' + run: | + git config user.name "github-actions" + git config user.email "actions@github.com" + git rebase ${{ steps.check.outputs.latest }} + echo "${{ steps.check.outputs.latest }}" > .current-upstream-version + git add .current-upstream-version + git commit -m "chore: sync to upstream ${{ steps.check.outputs.latest }}" || true + git push --force-with-lease + + - name: Set up Docker Buildx + if: steps.check.outputs.new_release == 'true' + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + if: steps.check.outputs.new_release == 'true' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + if: steps.check.outputs.new_release == 'true' + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + fliva/openfang:latest + fliva/openfang:${{ steps.check.outputs.latest }} + cache-from: type=gha + cache-to: type=gha,mode=max From 51f731dcff43ebf50cfd534489429b69b42c57e6 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 5 Mar 2026 16:23:34 +0100 Subject: [PATCH 02/41] feat: custom Dockerfile for Lazycat NAS deployment - Add toolchain: Node.js 22, Claude Code CLI, Python 3, uv, Go, gh, ffmpeg - Add gosu + non-root user (openfang) with passwordless sudo - Entrypoint drops root privileges via gosu for Claude Code compatibility - Add GitHub Actions workflow to auto-sync upstream releases Co-Authored-By: Claude Opus 4.6 --- .current-upstream-version | 1 + Dockerfile | 20 ++++++++++++++++++-- entrypoint.sh | 5 +++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 .current-upstream-version create mode 100644 entrypoint.sh diff --git a/.current-upstream-version b/.current-upstream-version new file mode 100644 index 000000000..dd08ecbad --- /dev/null +++ b/.current-upstream-version @@ -0,0 +1 @@ +v0.3.20 diff --git a/Dockerfile b/Dockerfile index d794943ed..b6fea4216 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,11 +10,27 @@ COPY packages ./packages RUN cargo build --release --bin openfang FROM debian:bookworm-slim -RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y ca-certificates curl git ffmpeg python3 golang gosu sudo && rm -rf /var/lib/apt/lists/* +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +RUN (type -p wget >/dev/null || (apt-get update && apt-get install -y wget)) && \ + mkdir -p -m 755 /etc/apt/keyrings && \ + out=$(mktemp) && wget -qO "$out" https://cli.github.com/packages/githubcli-archive-keyring.gpg && \ + cat "$out" | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null && \ + chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null && \ + apt-get update && apt-get install -y gh && rm -rf /var/lib/apt/lists/* +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ + apt-get install -y nodejs && \ + npm install -g @anthropic-ai/claude-code && \ + rm -rf /var/lib/apt/lists/* +RUN useradd -m -s /bin/bash openfang && echo "openfang ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/openfang COPY --from=builder /build/target/release/openfang /usr/local/bin/ COPY --from=builder /build/agents /opt/openfang/agents +RUN mkdir -p /data && chown openfang:openfang /data +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh EXPOSE 4200 VOLUME /data ENV OPENFANG_HOME=/data -ENTRYPOINT ["openfang"] +ENTRYPOINT ["entrypoint.sh"] CMD ["start"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 000000000..621b91e31 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Drop root privileges and run openfang as the openfang user +chown -R openfang:openfang /data 2>/dev/null +chown -R openfang:openfang /home/openfang 2>/dev/null +exec gosu openfang openfang "$@" From e0751977aac49841ae8410ab7b9c485fb6d67843 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 5 Mar 2026 17:07:53 +0100 Subject: [PATCH 03/41] ci: use main branch instead of custom --- .github/workflows/sync-build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/sync-build.yml b/.github/workflows/sync-build.yml index 931557e79..b49f2f337 100644 --- a/.github/workflows/sync-build.yml +++ b/.github/workflows/sync-build.yml @@ -12,7 +12,6 @@ jobs: - name: Checkout fork uses: actions/checkout@v4 with: - ref: custom fetch-depth: 0 - name: Fetch upstream tags @@ -33,7 +32,7 @@ jobs: echo "new_release=false" >> "$GITHUB_OUTPUT" fi - - name: Rebase custom on latest tag + - name: Rebase on latest tag if: steps.check.outputs.new_release == 'true' run: | git config user.name "github-actions" From 4c47d9a7977cbc4a7c6ee69237f73eef03d0ae18 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 5 Mar 2026 18:30:09 +0100 Subject: [PATCH 04/41] feat: add gogcli to image --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index b6fea4216..707230f18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,7 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ apt-get install -y nodejs && \ npm install -g @anthropic-ai/claude-code && \ rm -rf /var/lib/apt/lists/* +RUN GOBIN=/usr/local/bin go install github.com/steipete/gogcli@latest RUN useradd -m -s /bin/bash openfang && echo "openfang ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/openfang COPY --from=builder /build/target/release/openfang /usr/local/bin/ COPY --from=builder /build/agents /opt/openfang/agents From 13d4baf2869ca9e3afec0cadf9e4c7b7ac65cd18 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 5 Mar 2026 18:32:03 +0000 Subject: [PATCH 05/41] chore: sync to upstream v0.3.22 --- .current-upstream-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.current-upstream-version b/.current-upstream-version index dd08ecbad..25309a8ce 100644 --- a/.current-upstream-version +++ b/.current-upstream-version @@ -1 +1 @@ -v0.3.20 +v0.3.22 From a8fc98eab1eef11b0d1f1e671a5221d307452873 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 5 Mar 2026 19:42:50 +0100 Subject: [PATCH 06/41] feat: install brew + gogcli as non-root, add PATH for npm-global --- Dockerfile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 707230f18..9d5c68622 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ COPY packages ./packages RUN cargo build --release --bin openfang FROM debian:bookworm-slim -RUN apt-get update && apt-get install -y ca-certificates curl git ffmpeg python3 golang gosu sudo && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y ca-certificates curl git ffmpeg python3 gosu sudo procps build-essential && rm -rf /var/lib/apt/lists/* RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN (type -p wget >/dev/null || (apt-get update && apt-get install -y wget)) && \ mkdir -p -m 755 /etc/apt/keyrings && \ @@ -23,8 +23,15 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ apt-get install -y nodejs && \ npm install -g @anthropic-ai/claude-code && \ rm -rf /var/lib/apt/lists/* -RUN GOBIN=/usr/local/bin go install github.com/steipete/gogcli@latest RUN useradd -m -s /bin/bash openfang && echo "openfang ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/openfang +USER openfang +RUN NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +RUN eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && brew install steipete/tap/gogcli +USER root +RUN echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/openfang/.bashrc && \ + echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /root/.bashrc && \ + echo 'export PATH="/data/npm-global/bin:$PATH"' >> /home/openfang/.bashrc && \ + echo 'export PATH="/data/npm-global/bin:$PATH"' >> /root/.bashrc COPY --from=builder /build/target/release/openfang /usr/local/bin/ COPY --from=builder /build/agents /opt/openfang/agents RUN mkdir -p /data && chown openfang:openfang /data From 6d86f1fee8532d84a91dd90edf4293b6610b571e Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 5 Mar 2026 18:44:10 +0000 Subject: [PATCH 07/41] chore: sync to upstream v0.3.23 --- .current-upstream-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.current-upstream-version b/.current-upstream-version index 25309a8ce..5e82df8ff 100644 --- a/.current-upstream-version +++ b/.current-upstream-version @@ -1 +1 @@ -v0.3.22 +v0.3.23 From e393cc4f11637f3db3210046954e977e3a1f987b Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 6 Mar 2026 00:11:33 +0000 Subject: [PATCH 08/41] chore: sync to upstream v0.3.24 --- .current-upstream-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.current-upstream-version b/.current-upstream-version index 5e82df8ff..c71f96780 100644 --- a/.current-upstream-version +++ b/.current-upstream-version @@ -1 +1 @@ -v0.3.23 +v0.3.24 From eec2adb9989066a2bde65603a54a8e6afceb4fb0 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Fri, 6 Mar 2026 09:11:06 +0100 Subject: [PATCH 09/41] docs: add Docker Hub README with auto-sync via GitHub Actions --- .github/workflows/sync-build.yml | 9 +++++++ DOCKER_README.md | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 DOCKER_README.md diff --git a/.github/workflows/sync-build.yml b/.github/workflows/sync-build.yml index b49f2f337..23022281a 100644 --- a/.github/workflows/sync-build.yml +++ b/.github/workflows/sync-build.yml @@ -65,3 +65,12 @@ jobs: fliva/openfang:${{ steps.check.outputs.latest }} cache-from: type=gha cache-to: type=gha,mode=max + + - name: Update Docker Hub description + if: steps.check.outputs.new_release == 'true' + uses: peter-evans/dockerhub-description@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + repository: fliva/openfang + readme-filepath: ./DOCKER_README.md diff --git a/DOCKER_README.md b/DOCKER_README.md new file mode 100644 index 000000000..1f39cab6f --- /dev/null +++ b/DOCKER_README.md @@ -0,0 +1,42 @@ +# OpenFang for Lazycat NAS + +Custom [OpenFang](https://github.com/RightNow-AI/openfang) Docker image optimized for deployment on Lazycat LCMD Microserver. + +**Automatically rebuilt on every new upstream release via GitHub Actions.** + +## What's included + +- **OpenFang Agent OS** — Rust-based autonomous AI agent daemon +- **Claude Code CLI** — Anthropic's CLI for Claude, as LLM provider +- **Node.js 22** — JavaScript runtime +- **Python 3** — Python runtime +- **Go** — via Homebrew +- **Homebrew** — package manager for additional tools +- **uv** — fast Python package manager +- **gh** — GitHub CLI +- **gogcli** — GOG.com CLI client +- **ffmpeg** — multimedia processing +- **jq** — JSON processor +- **git, curl, wget** — standard utilities + +## Non-root execution + +The image uses `gosu` to drop root privileges to the `openfang` user at runtime. This is required because Claude Code's `--dangerously-skip-permissions` flag refuses to run as root. + +The `openfang` user has passwordless `sudo` access, so it can still install system packages when needed. + +## Usage + +```bash +docker run -d \ + -p 4200:4200 \ + -v openfang-data:/data \ + -v openfang-home:/home/openfang \ + -e OPENFANG_HOME=/data \ + fliva/openfang:latest +``` + +## Source + +- **This fork**: [github.com/f-liva/openfang](https://github.com/f-liva/openfang) +- **Upstream**: [github.com/RightNow-AI/openfang](https://github.com/RightNow-AI/openfang) From 0350d4606fc76c9855a9bf816aea299d38482325 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Fri, 6 Mar 2026 09:18:50 +0100 Subject: [PATCH 10/41] feat: add jq to image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9d5c68622..e443cda52 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ COPY packages ./packages RUN cargo build --release --bin openfang FROM debian:bookworm-slim -RUN apt-get update && apt-get install -y ca-certificates curl git ffmpeg python3 gosu sudo procps build-essential && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y ca-certificates curl git ffmpeg python3 gosu sudo procps build-essential jq && rm -rf /var/lib/apt/lists/* RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN (type -p wget >/dev/null || (apt-get update && apt-get install -y wget)) && \ mkdir -p -m 755 /etc/apt/keyrings && \ From f2622af20a9140a4a33960f241aa35890599685e Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Fri, 6 Mar 2026 10:27:25 +0100 Subject: [PATCH 11/41] =?UTF-8?q?docs:=20fix=20gogcli=20description=20?= =?UTF-8?q?=E2=80=94=20Google=20Workspace=20CLI,=20not=20GOG.com?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DOCKER_README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DOCKER_README.md b/DOCKER_README.md index 1f39cab6f..d3e09a054 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -14,7 +14,7 @@ Custom [OpenFang](https://github.com/RightNow-AI/openfang) Docker image optimize - **Homebrew** — package manager for additional tools - **uv** — fast Python package manager - **gh** — GitHub CLI -- **gogcli** — GOG.com CLI client +- **gog** — [Google Workspace CLI](https://gogcli.sh/) (Gmail, Calendar, Drive, Sheets, etc.) - **ffmpeg** — multimedia processing - **jq** — JSON processor - **git, curl, wget** — standard utilities From 74e8df216490f8bce82fee90c4896053476ef7fc Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Fri, 6 Mar 2026 12:42:52 +0100 Subject: [PATCH 12/41] fix: logo visibility in light theme Replace opaque dark-background logo with transparent PNG and add CSS invert filter for light theme so the snake is visible in both dark and light modes. Co-Authored-By: Claude Opus 4.6 --- crates/openfang-api/static/css/layout.css | 4 ++++ crates/openfang-api/static/logo.png | Bin 10005 -> 174757 bytes 2 files changed, 4 insertions(+) diff --git a/crates/openfang-api/static/css/layout.css b/crates/openfang-api/static/css/layout.css index 880e6ca3f..c4b012113 100644 --- a/crates/openfang-api/static/css/layout.css +++ b/crates/openfang-api/static/css/layout.css @@ -50,6 +50,10 @@ transition: opacity 0.2s, transform 0.2s; } +[data-theme="light"] .sidebar-logo img { + filter: invert(1); +} + .sidebar-logo img:hover { opacity: 1; transform: scale(1.05); diff --git a/crates/openfang-api/static/logo.png b/crates/openfang-api/static/logo.png index cfe72c0b5cbbe95751eca341b353c86cef1aeae6..7f900d403a9c5ced9aea4f688aa3453273d6e458 100644 GIT binary patch literal 174757 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 nUI5!hQ4^M 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 St+#AY@J~xaM?D{B75qQSr$5yI literal 10005 zcma)C#=iSViM0RRAzvXb0O%s=Yi4}ya^c1oEK007VCmE~k~ zd@}YjajVJXABU&oMy9yyLfyL%`sAbvUA>J8WbS|ow!i=Jn0M`_b*AxhD)a0_8w}#x z@O(y5$uKuBo-FVOWs9F(4-sDe9U?s07xlEsFOc^~|2hbfMY2Up`)-(U(%-yU~SM9-(CQH$46N1A_Q0zipCSEXfT z>hkNWG2B^i_p*cTsqW5yzcIL2o%=hKzR_%&Cu_V_W>970dh&OuEbnfq_`j0NNj=lp z*6Zz(%cg^tn4QqAiGrTBpRqAZK5MZ)Cqu&DFMK%-s|zm-K25P1;*1jJ4W$VV#BtWn zSd9Hme?#X~GpYMx`rE57!C1Ho-b(@A0&h<@H!qfg;-#+ENyW2g=Q%n#Pcv(mLkJZ1 ze%Hn4tEWFMYxtWsR&L&zJ8j~}NiFJTb-G>heeR6kcDQsRLM6PO#7JFT{WzeTT<;yR z2F2TWaD!+4iwT?>xoCU*#S25z)Q?`ua%;wx>&9^A+AHq` z!Itwmhqdpm_hNykV~Tr=svH$^NaMFAVKpDTbC!PsLg6iM>Q};#3bF&KFZLIHLu-G% zRJG0&54`ocA@1U+Uk>?6g}DQ_tOS_BnJ4h}^56T{9yHcvO;%bD_$+u%*QB-%a5Y&i zWZOEIx8odXO5a(Hq-t|%o*gSoNo%BQl3Cfp{FcjgmZ{=>$tp z-7a`9NjkI{;3wdoFL;~Y5E0-8TlU1&xTE#&(|OF1{>ObRwy=1`(u9DuSYc|#S-*oH zq~{~Hm@uy0>^E_nn1_+Ce$q1$y}mQs;-QGlel7;u*k!=qj&J z_%L>GHL>(9kJQ8MNfl1EW_?@jZ|C&+ht`L?H;jETqnBhWn^!R$m65P$$ViF5MEXNR zDo z@Ot}KQJ)&NIR_r*Tgp$m#+y90NHjA<#?}eXtW&L%DwwTH%4&;c0gzz!vg(&eyDXPc zj%s6HU@f6hpSV_meYu9@nNIpu3|FI_oACp06ABZJXK0|_A)3)MUdaE_g((i}kx1Kp zM0&LcO~{GxZ%6)#qnooG+tz7A2Tp!g^IP-SX*CL%iCZ7@RD|Xu8S){#r9U2g9G=VX|9WL&K;ftHQPJDk*j&Pw|TEpkX7cFDL~) zB6%@i@ASL&{3gXRj`u9#KzzUc%hri;&jDxM{J71H@^?0NaHGZ%<-!ug6=Kr7#9+mcR(}Y z#w=4gi0=5{4UZ}7KV^%0WgI+WJhX(H?EKy22Z@|h^U+U(BpE2G{Nj7i9ecz6>4avT zLGcoNC)qTRms74sA)e037!mNPIzBZ;3c;}*FHAZ1Og+u&9Q;GOsK=-*e;!#AC$bv3 ziHTo)mfyapc=!&vH+yzN8ldhFM8E;%Y~0LMwi;RQOWZSIFD*x|o@Zj-b~gy6IZScr zcl9T^&Y?gp)uP9++78B9*~>U~x;3#!b-TaR8dQyGRH*|gQc$hj`(8TsK!WCIbg56X08A3Y=}UP z18=!oE*ZsE(4@J(8=1n5tI_PcGt2Fvdk@yhuY?+Jj|aZQOI`O`fc)h&OLWV26Hn|w z_YauN@1A^)po@MaafpZ^&N1zw5Ar|Vt6NeC#UbKj#N;4=!x?2uP(MxUJr5(I6%!Ek z`t4LFUy$i#SiM*Kd(IWoN$Omb!y$sQQSOPOC7P{U3T&dK$W1Bpe^`m2&ovFaV!qxg z$lev-!`xb6bI7%?Vn8 zsAq6>VAV0E(~>j*i?Rhx_&{EPV8tax$2&gB4a1%WdYn?hY2%5&fl-e+`+`*Ym&Hz^ zc3|svpa8%3tab8cMGu`Sh!=YhF=cGSUq#@h8p`jI@oZTP*{Xk0BUdiuPPosYJFN|-xwP|0&=nko|hgji7k?YH~_^QQ&F-7{Co3gsaw|!@S z0C{cIg z3)!QDeK3_Ygx9s(-J$dgin@bmCl<7{xY6uk`N>Ifjq8`KBXiIVLqfj?r7#h<7T*u! z8#aGDgo)te-G*@$_EfFf&PU>MWkz*z=QEb^R=RGBnC#GOsWfuoHE<^!-J7smb>cR`vrR%8imu5DP=Yt;Z3}k3l9ItPd9-K88h$;P0q#7a& z+{t!!-zM|bpHy)+W7v~HKu4RL2h_&)f$Od2b#ze9yZNrXJ=Dy(B7FdvscSd6L1><) zS`l63z2w%{#6e$KG#9GsXH%GU*a><)zE6Lm(pvRovwd40(YQ#eW4Fve83xx*`lziT z+MO`W$JAB`hFwaz((1}`M1#x1yG%U6@O4c8xfODcoH^@VxiOERwZXdH$PO}`pe_w> z1+f2SikJ8RZ%nXtSQhu%wWC9b`nxIrZ4}P8K(v4aZjPaqPS76cnRDp}53x7~~u^Z^ZCG9|LngDhULOVEY$T zyc?rW0!M~g4*I--?M@WL!`Le|r7SqT*o&U4yycmyz~pMaWnjOh`?1l?!{+!Gg;>5! zT1mr2nY##6)WJU{{YYdPaKaew1ty-)zA=;D#E@2x33Xv|9f1({axM$a`t=X0gWClj zuA+(MRLca*l)7!gi$0y-jDqgYhJHux0oqJZPBQn=FJ+Ytp#1T(F^B86PlSMgbIlC; z_D>9LZg}Sj0rXTMaH64VV`E&?_GlAF>x!eKs`De3Nc?cPJ{Z8hZ4P0&^} zW%TC9x(NrV9Z6x1^)fLlC-5tgz>Y{SHl&xVL}I73YS08F#Pj-B;nN94tiWF{6lVn+ zLU`oO3KIkPfq7SG1sKuib*0OBirjVK;C#r3fBm?NtsvoK>=Br<^7co6;it#5U>%bC zz4urkDt*9@xoqM}va6D{#yCppY{0mdLQ>jbK>4+OQi=I49+p&?6>}V*myKqkbc5Q0 zn4m&>(31~!`;aN-S?PJ4YMaFypJoM;-}8x2l|Yv+$q}wl{R#k+K2s~NneoGQKl_}I zc$1OU-pENzKeF+*In;;B*M?Q~vSrI)`=pO0+wr)cNO(Shh8z1M8X&*}Hd^+e!tm8q zKJ4|z$p&iu25%skEhzS$o2L<* zDv+50z_5$yqOs>oK@X*tArz+myzjBSmI5vc-8Y8E#1EP+A@Gg}X2h<}Mp@02;m>^Y z??ipN4txQjtWl>jCZD+@XuVQsS<;mL{HcD5siS9xwwa!##O1yTWoM>gd66$$wu?)0 z!yX&5?#vZ402p6;&$}w#>|==mz)z`sc@ClL?Sw3LWFQ3OwN$JNy%gt`9R{5XnwK#DuJaKk%f$K&lK|Jp* z%skZYfqoIBr4J^2UizgMn=-bXG-vzyL!5gb1Sbwd_)2XB?kms#PAmaFk9dXp^#62DADXOcF=EXQNXO2rr~;0ouLx^Rs-jTKhbbL+dIDlUB$Ca)j6~~z{Ev&TpURT|WH8&xOa7(-f)mon z5+4D;))JS?q00F6^?>o3*AFY6Omv8G>5ExR(=O$RffS{b`Q~d5x@3rcP4v|W^-Xqh z39b{^Hbjn9Sba#sVSv#vu6`E-wRsQ_bkTFey-YUjMhhEcXYbixK#gR&nCvCC*%>I|?QTWSl#CQi4zx}C`I*Hw7xp6+6@WRT z-AIAQc-NhLKiv81Zr8_~J70C$6A z6xy^T%Uq?5VB;Tz(UHp-*4tN1!kAPX%Vb&&I^U z;L=a|&RCx6FsA#k=(pykM+W|V<;pO!U-g$7bQsCNymPgYUH5?tz(Jymw;XhlXY$vh z{FEcK`3pt-O#_(J0MF47e0htHU&2PFK7?~15QW#@W=B}IIou$rGIjHA1E!3d(Qp*f z<^#?$#-0&Ae-}nfuKAN5XG9Tvv;H31@RIh&;U<5We-MOu2gAnwJBft5N=hqLrhcIH zvnQFRvTBVY0~4*H4OzR`(+s)*EV8PIHJMKwi6kdPLE79YdF1Z+QRoNP*q4P-?~Gqx zLCe^`s=iH1%4rUSMpQ7%nSE2Byc!Z-atvc)(adsm;6JFX% zp$t22D1qM9eSMIgv1@msY{a86jBvBK`(iVSO<`;nkSi^rnx#Am%f$f>b5zD~Hy(8n zGhD+nzW{Z8C|6c7L%aFv0b@Vy*_}=5#ZT#55~*H1(}^i}Fs?I}y?Pw2*?ip3KK4PC z%f9Fjt(Zso4N{^rf>>CXkL%ArU&FXY0=JHzDEDjpKTdm!NunZs;N@ppmQtstz1orq zVPAna%|{9){Al4shvy_OxO(4cfkp>NOJzKXZg1>L?dr2%%MIUQcsZ?8!=I${?A1f^o1VMF5$&0+e@@DVEZPMJC-WKrCH2G>;lhM6 zYKx)+A|D#M{L<#{IQ=yc*LNk1;vHg{1XC~3w5Aw)?!&!Lmn;p2*8PyY!1zl-8eU`a z6nSplZYUfhP5;f0$ES9M`WWY~PPtvc)K`2cA>?&VtjZ5p1FncCS!9wO1g8S_#Rn>~n+;UkgH4?_hiwrv> z94Hi=A36 z!k!Rf@n~)-;#Lx%Y?^1}4kIHP0z3hR5-YaV#$8d~pb*Mmo~BjO5-yRaY}xutrKGOl zU<6Fb)p;Lon;-5`8w_j*g=e-Ls-Z~gnoEM>-4f29gaIUAJfyvgE8?&Uz;4T)smGIQ_xxGJ|g z$$tPEeGWX4e4gz#-~Hy3;Zj=VO=r2tLqE6S-xMeVqs~yLfTqcj_GDsL1z^& zGuY+|qdu}HQ4Us=JH0;Rv9zW5=QHr(9)sO*9T(hLG9;cR=btI|hC%d^%O2}|U-6pV z^B9!2X~aaYxxy)mls31!2EMc=_A{?NVq3$MlC46<{zB95i)yF&u^!$oM-2MMgnm0( zDkNc)_WbT6jG5=1`vFMlpf#&S@!vkte_GvNF~((CkOaXeq9!1f1ls3>eq_~no5(ca zZ1QpJAg|QeUV~|!^MacCdZJso3Z5RAhhb?x@!s4|1mn<_Nq@{q#5a7Mj(EX3a4;RZ zoqv2#S^xmTD*LL`v|!+s_l)s&(CFeZH49}161}lnIjqmLaPb7ecZBd75`NC56)6D! zCz5K2Q`asmD)dmqV^iy7xCnvGk+nhk`dKUz5t&Sb@PEz1dmW)i>~qvjp*`*A#fr78 zW=9IRV@vafJE})%@ToceDKwXt4C@K+QplM7qCL;Ee&_dW{9n@dcE~t~jQ07YaqLGo2Is=IJ zDpDtrXA`?4m8vsgzSew;0x+po1zS;xjDncb$I}ppYCUf>8p?7)$y?*DrRrJ`-qGqyci7R z$CRF?15y(1BsZu*3Di9ZGx*CRxr8n;A_ZII#7S(sm+NA0pDyhcrNK|RM!f|VsiON@}M3okAvyPjXrCKUhv8>TMqBmqiwBs!HYs|Kg`eUcu^S=EfqTN8S_1Om!#e3 zpTbq1B@l<^i-8;suvlkmde4J@rrzmyKwUaK#osX?yr+UFL-ngXxqa~)a0*~x_+2Uv z5|PFc#{J{;!vepPLWII+Lt73NtOcy~QCzigeF5J`Ceo`w1lKv}KGfw$`AmbmZPOx? zu`8^XMTGr~$k(`e@uKU{Ip0yYjTVj3%2+1oB z)3Qmjy$--(ivv{GR@9QDz!;^~s^KO8k)tlsMECNFL<{o#7h$2fOEcu_U&r=ot4Il7 z(%a#gX9M=t$)L5t{=2ge@j{^G;&V%x))x`v#z-Hkfl%QmdQWk+ESa#U&W%5owrefU zV~nc&_twg>Jve&OzQ-m@Uh_JO5e1sgIti)-yK0sq%)^~9L zj`h%d&{#)?Um8V~E-|yS*m)dyfIi{*=h}`mQIuYk&Lr3bJ*WL`{A&>Se@QL@>XS0% zH6<-P8C@Gx!%@sG`D=8$H24ey>jW>94m4$c=zL5J zKXKt)?7D`O?&6`6Wf&Fcrw97+(9s0}AbUa%tmy9&AK#8a3!7ugAIn#qF(nQ z%r7>we4bp5B%6nYE;q}}uz854@R*0-`O4if&hRGs?$1^zrAlH6^W6wNF(yAid?Q3+{Onf@EZ2mA8)_+-Cbh;TrGyS)vTR{ANK3 zL|t5GeH`tuMD}9P*B;OmcD%S;OIcsQ}iKL+MuzgG!|;xgdl zDQ7O(9XK7n=dI?YMwSm{hTQb*OE5`$*(Lldh*Mtr?d1g68-MP>l8IlB}XeH00%Dk zm1wZPzJ^B8S^Pa}6#x~-!p+=Fp?_c(dY^^d>A@|d8o}Wp#!ea>2(~I5jc24;61v%d zT@encEDnFE7NIp(9HyFME>1cBP15gNppG>p)(zOZRnSpqu?ZuDg|l8CVwPRxF)J^% zw2Fn=+oGa(7~521z76uBYw?B_!AcX1ig3vw3KlRPPlhMIAAMhPQ%f2h`LNkQ_{W((v|Av=GS0Zw5|3?h_0*%GXu zGxb@~PeySXuK(Ok0m*pfEn?7oMM=S5h5fr>cx`b|$HS>I(pFfQhk{j(utr;~Nra+< z{WbS%y4H%lDvok1d+{sdd6LObkk{j5K6=vOr4DN*cT_PtMEFO@M z3r?JW#h8oiCodC&^wJ@76g@WY&s(pa?kL^KT%P>b{}&&Wz$Tj$J~AZ}V37nJ-5Mdr zjno7)s#T;3G7`>kBF$MnnP_tP+-TAzXc##W9=ejsj0`AB9m)FW;*X^Lj~K2g6+XyF z^7>JDiF{$u5P-B+Cul=Sw7N-Atl64_*~O_b`!QYdO)0Z-`ub`28PA@((=`BHu$u#^ z1u|8~Y!Ra2;A9k{*|$(H#iKwYlX@B#O02-pKZ&&J<#8d?6oIRoXiWJMD*XbZ0A1uA z1{_sFm3ZCkT6)acy+n&CR7iPFQi>1|`~tJNfxR z&(w?EiIoTEH!y3rQFC=~-c0uPw)_Xk`|2^+Iv>MkX$5d6`fJ1%X8*lIx6IH0Q9RfO z_RbB?k%X432=V_lYH`jrPeK%mx>s(3CJVir*;p)h|HKGL!WJ>ln`XXm{#ZAkOsqteCFShD2*#x`+2` znt6Q3?=$#^+Z&zT1??Vk2rGTkfAp-Pdr$uE*r79=8FwdVQ91vrr zf0=gozPicY^83~EGArWYm%kJ8pVz)i#C}EHW|K4YSmz?X{i9_>n;M9ZxjL+!7AZ6? z8cw;-Vl||kv&lihfc}gV6b3;)Y^B=_&$(2FI6FPg#phJ-(l?%=CtZipKQ&rXU(s~Gk{RMZc%p`gb7Qa#hGHkCOd zzC)|<739A;*_37kSn+9KHG!y}m^Hp+6*upDDY-2ro^@Y23fHoySIH)C`>sRf;=d?g z1&k_9MdLN9h1zZJVnRQyi!{?!;5 zu7xM`J;}a2Z)2xU@8s*fZ`Us}9&yjOZ)d+-BeWdroOp9qAvyY^9HCkP_^F>Ff6WH} x+cm~&D5piLgChV#e3B&v>WKayC!G&K-oCJ$wKObI%yu_`vb=`eH>6p}{{X}V`M&@F From f0ebfe35d9f2e755b7e6f9844c717002f284194c Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Fri, 6 Mar 2026 13:31:36 +0100 Subject: [PATCH 13/41] fix: invert logo in message avatar for light theme Extend the CSS invert filter to also cover .message-avatar img, so the agent logo in chat messages is visible in light mode. Co-Authored-By: Claude Opus 4.6 --- crates/openfang-api/static/css/layout.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/openfang-api/static/css/layout.css b/crates/openfang-api/static/css/layout.css index c4b012113..33919dac4 100644 --- a/crates/openfang-api/static/css/layout.css +++ b/crates/openfang-api/static/css/layout.css @@ -50,7 +50,8 @@ transition: opacity 0.2s, transform 0.2s; } -[data-theme="light"] .sidebar-logo img { +[data-theme="light"] .sidebar-logo img, +[data-theme="light"] .message-avatar img { filter: invert(1); } From e64eed37b0fdc66457eb930037090c8110e6f014 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Fri, 6 Mar 2026 15:46:22 +0100 Subject: [PATCH 14/41] fix: detect Claude Code CLI install and auth status in provider card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, Claude Code had key_required=false so detect_auth() always set NotRequired ("No Key Needed"), regardless of whether the CLI was installed or authenticated. Now it checks: - CLI installed + authenticated → Configured - CLI installed, not authenticated → Missing (Not Set) - CLI not installed → NotRequired (No Key Needed) This also fixes the Runtime/Overview page showing Claude Code as "not configured" when it is actually ready. Fixes #376 Co-Authored-By: Claude Opus 4.6 --- crates/openfang-runtime/src/model_catalog.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 8822c460e..51dc92ec5 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -56,15 +56,16 @@ impl ModelCatalog { /// Only checks presence — never reads or stores the actual secret. pub fn detect_auth(&mut self) { for provider in &mut self.providers { - // Claude Code is special: no API key needed, but we probe for CLI - // installation so the dashboard shows "Configured" vs "Not Installed". + // Claude Code: detect CLI installation + authentication if provider.id == "claude-code" { - provider.auth_status = - if crate::drivers::claude_code::claude_code_available() { - AuthStatus::Configured - } else { - AuthStatus::Missing - }; + let cli_installed = crate::drivers::claude_code::ClaudeCodeDriver::detect().is_some(); + if cli_installed && crate::drivers::claude_code::claude_code_available() { + provider.auth_status = AuthStatus::Configured; + } else if cli_installed { + provider.auth_status = AuthStatus::Missing; + } else { + provider.auth_status = AuthStatus::NotRequired; + } continue; } @@ -83,7 +84,6 @@ impl ModelCatalog { std::env::var("OPENAI_API_KEY").is_ok() || read_codex_credential().is_some() } - // claude-code is handled above (before key_required check) _ => false, }; From c72e09e253301f382bcac4b3e7f17fe7827ef64a Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Sat, 7 Mar 2026 09:37:05 +0100 Subject: [PATCH 15/41] fix: use PAT_TOKEN for workflow push permission GITHUB_TOKEN cannot push changes to .github/workflows/ files. Use a PAT with workflows permission instead. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sync-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sync-build.yml b/.github/workflows/sync-build.yml index 23022281a..f1c681b1c 100644 --- a/.github/workflows/sync-build.yml +++ b/.github/workflows/sync-build.yml @@ -13,6 +13,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + token: ${{ secrets.PAT_TOKEN }} - name: Fetch upstream tags run: | From 50d71543c485b48c2477106bccd899b17e9dbacd Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Sat, 7 Mar 2026 09:42:48 +0100 Subject: [PATCH 16/41] feat: add Playwright + Chromium to Docker image Install pip3, playwright, and Chromium with system dependencies for browser automation and PDF generation. Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e443cda52..33050604c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,8 @@ COPY packages ./packages RUN cargo build --release --bin openfang FROM debian:bookworm-slim -RUN apt-get update && apt-get install -y ca-certificates curl git ffmpeg python3 gosu sudo procps build-essential jq && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y ca-certificates curl git ffmpeg python3 python3-pip gosu sudo procps build-essential jq && rm -rf /var/lib/apt/lists/* +RUN pip3 install --break-system-packages playwright && playwright install --with-deps chromium RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN (type -p wget >/dev/null || (apt-get update && apt-get install -y wget)) && \ mkdir -p -m 755 /etc/apt/keyrings && \ From 15e64be07077f4b7c0e5cea197a373f495b9fa58 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Sat, 7 Mar 2026 09:45:06 +0100 Subject: [PATCH 17/41] fix: use force push in sync workflow to avoid stale info rejection The rebase creates new commit hashes, and --force-with-lease fails when the remote was updated between fetches. Since this is an intentional rebase sync, --force is appropriate. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sync-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-build.yml b/.github/workflows/sync-build.yml index f1c681b1c..7b83ff94b 100644 --- a/.github/workflows/sync-build.yml +++ b/.github/workflows/sync-build.yml @@ -42,7 +42,7 @@ jobs: echo "${{ steps.check.outputs.latest }}" > .current-upstream-version git add .current-upstream-version git commit -m "chore: sync to upstream ${{ steps.check.outputs.latest }}" || true - git push --force-with-lease + git push --force - name: Set up Docker Buildx if: steps.check.outputs.new_release == 'true' From 9fe7ffe636973b10d7dc0876511411d4befd9844 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 7 Mar 2026 08:50:26 +0000 Subject: [PATCH 18/41] chore: sync to upstream v0.3.26 --- .current-upstream-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.current-upstream-version b/.current-upstream-version index c71f96780..c40f247ce 100644 --- a/.current-upstream-version +++ b/.current-upstream-version @@ -1 +1 @@ -v0.3.24 +v0.3.26 From 54f720589dac63275507d7a9aeb3bfa8c5f4bcfb Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Sat, 7 Mar 2026 13:29:42 +0100 Subject: [PATCH 19/41] fix: add python -> python3 symlink for Browser Hand detection OpenFang's Browser Hand checks for 'python' not 'python3'. Debian only installs the python3 binary, so add a symlink. Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 33050604c..83cb0a6ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,8 @@ RUN cargo build --release --bin openfang FROM debian:bookworm-slim RUN apt-get update && apt-get install -y ca-certificates curl git ffmpeg python3 python3-pip gosu sudo procps build-essential jq && rm -rf /var/lib/apt/lists/* -RUN pip3 install --break-system-packages playwright && playwright install --with-deps chromium +RUN ln -s /usr/bin/python3 /usr/bin/python && \ + pip3 install --break-system-packages playwright && playwright install --with-deps chromium RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN (type -p wget >/dev/null || (apt-get update && apt-get install -y wget)) && \ mkdir -p -m 755 /etc/apt/keyrings && \ From 72b0f8e7a7c724a7a2bb63796e54d4b29e187303 Mon Sep 17 00:00:00 2001 From: Ambrogio Date: Sat, 7 Mar 2026 19:13:23 +0100 Subject: [PATCH 20/41] fix: default hourly cost quota should be unlimited (0.0), not $1.00 The ResourceQuota default set max_cost_per_hour_usd to 1.0 while daily and monthly were 0.0 (unlimited). This caused agents without explicit quota configuration to hit a hidden $1/hour cap. Also fixes apply_budget_defaults() which compared against the old hardcoded default value of 1.0. Fixes #416 Co-Authored-By: Claude Opus 4.6 --- crates/openfang-kernel/src/kernel.rs | 2 +- crates/openfang-types/src/agent.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs index 0b1b1cbe2..f172d4bfa 100644 --- a/crates/openfang-kernel/src/kernel.rs +++ b/crates/openfang-kernel/src/kernel.rs @@ -5002,7 +5002,7 @@ fn apply_budget_defaults( budget: &openfang_types::config::BudgetConfig, resources: &mut ResourceQuota, ) { - // Only override hourly if agent has unlimited (0.0) and global is set + // Only override hourly if agent has unlimited default (0.0) and global is set if budget.max_hourly_usd > 0.0 && resources.max_cost_per_hour_usd == 0.0 { resources.max_cost_per_hour_usd = budget.max_hourly_usd; } diff --git a/crates/openfang-types/src/agent.rs b/crates/openfang-types/src/agent.rs index 5f002b778..f8d59fcfd 100644 --- a/crates/openfang-types/src/agent.rs +++ b/crates/openfang-types/src/agent.rs @@ -267,7 +267,7 @@ impl Default for ResourceQuota { max_tool_calls_per_minute: 60, max_llm_tokens_per_hour: 0, // unlimited by default max_network_bytes_per_hour: 100 * 1024 * 1024, // 100 MB - max_cost_per_hour_usd: 0.0, // unlimited by default + max_cost_per_hour_usd: 0.0, // unlimited max_cost_per_day_usd: 0.0, // unlimited max_cost_per_month_usd: 0.0, // unlimited } From 11fd79ae33f4ed395f8e6e65af253d4514b3caa9 Mon Sep 17 00:00:00 2001 From: Ambrogio Date: Sat, 7 Mar 2026 20:41:19 +0100 Subject: [PATCH 21/41] ci: trigger Docker rebuild on every push to main Previously the workflow only rebuilt when a new upstream tag was detected, so custom commits on main were silently ignored. Now pushes to main trigger a full build+push to Docker Hub. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sync-build.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sync-build.yml b/.github/workflows/sync-build.yml index 7b83ff94b..2db83953c 100644 --- a/.github/workflows/sync-build.yml +++ b/.github/workflows/sync-build.yml @@ -4,6 +4,8 @@ on: schedule: - cron: '0 */6 * * *' # ogni 6 ore workflow_dispatch: # trigger manuale + push: + branches: [main] # rebuild ad ogni push su main jobs: sync-and-build: @@ -16,12 +18,14 @@ jobs: token: ${{ secrets.PAT_TOKEN }} - name: Fetch upstream tags + if: github.event_name != 'push' run: | git remote add upstream https://github.com/RightNow-AI/openfang.git || true git fetch upstream --tags - name: Check for new release id: check + if: github.event_name != 'push' run: | LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1) CURRENT=$(cat .current-upstream-version 2>/dev/null || echo "none") @@ -34,7 +38,7 @@ jobs: fi - name: Rebase on latest tag - if: steps.check.outputs.new_release == 'true' + if: github.event_name != 'push' && steps.check.outputs.new_release == 'true' run: | git config user.name "github-actions" git config user.email "actions@github.com" @@ -45,30 +49,30 @@ jobs: git push --force - name: Set up Docker Buildx - if: steps.check.outputs.new_release == 'true' + if: github.event_name == 'push' || steps.check.outputs.new_release == 'true' uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub - if: steps.check.outputs.new_release == 'true' + if: github.event_name == 'push' || steps.check.outputs.new_release == 'true' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - if: steps.check.outputs.new_release == 'true' + if: github.event_name == 'push' || steps.check.outputs.new_release == 'true' uses: docker/build-push-action@v6 with: context: . push: true tags: | fliva/openfang:latest - fliva/openfang:${{ steps.check.outputs.latest }} + fliva/openfang:${{ steps.check.outputs.latest || 'custom' }} cache-from: type=gha cache-to: type=gha,mode=max - name: Update Docker Hub description - if: steps.check.outputs.new_release == 'true' + if: github.event_name == 'push' || steps.check.outputs.new_release == 'true' uses: peter-evans/dockerhub-description@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} From d5c156593fb988234c661a503dabf03a85868c38 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 7 Mar 2026 20:38:49 +0000 Subject: [PATCH 22/41] chore: sync to upstream v0.3.27 --- .current-upstream-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.current-upstream-version b/.current-upstream-version index c40f247ce..7eccf8697 100644 --- a/.current-upstream-version +++ b/.current-upstream-version @@ -1 +1 @@ -v0.3.26 +v0.3.27 From b4ae2c931c068319c0e4489604c4b08bf7657107 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Mon, 9 Mar 2026 09:57:59 +0100 Subject: [PATCH 23/41] chore: sync to upstream v0.3.34 --- .current-upstream-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.current-upstream-version b/.current-upstream-version index 7eccf8697..23ed88082 100644 --- a/.current-upstream-version +++ b/.current-upstream-version @@ -1 +1 @@ -v0.3.27 +v0.3.34 From 01190004977d4dfa7bf2e281e68791754e87eb22 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Mon, 9 Mar 2026 10:00:42 +0100 Subject: [PATCH 24/41] fix: auto-skip conflicting commits during upstream rebase When upstream changes conflict with custom fork commits, the rebase now automatically skips the conflicting commits instead of failing. Skipped commits are typically duplicates already applied upstream. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sync-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-build.yml b/.github/workflows/sync-build.yml index 2db83953c..042beaaa7 100644 --- a/.github/workflows/sync-build.yml +++ b/.github/workflows/sync-build.yml @@ -42,7 +42,7 @@ jobs: run: | git config user.name "github-actions" git config user.email "actions@github.com" - git rebase ${{ steps.check.outputs.latest }} + GIT_SEQUENCE_EDITOR=true git rebase ${{ steps.check.outputs.latest }} || while git rebase --skip 2>/dev/null; do :; done echo "${{ steps.check.outputs.latest }}" > .current-upstream-version git add .current-upstream-version git commit -m "chore: sync to upstream ${{ steps.check.outputs.latest }}" || true From bdf9b05d3b7850c5eabbcdd8de1f3646e86c9691 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Mon, 9 Mar 2026 12:30:49 +0100 Subject: [PATCH 25/41] fix: replace Playwright with chromium package in Docker image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Browser Hand no longer requires Playwright — it now needs Chromium directly. Replace pip3 playwright install with apt chromium package. Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 83cb0a6ad..23b52ee14 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,9 +10,8 @@ COPY packages ./packages RUN cargo build --release --bin openfang FROM debian:bookworm-slim -RUN apt-get update && apt-get install -y ca-certificates curl git ffmpeg python3 python3-pip gosu sudo procps build-essential jq && rm -rf /var/lib/apt/lists/* -RUN ln -s /usr/bin/python3 /usr/bin/python && \ - pip3 install --break-system-packages playwright && playwright install --with-deps chromium +RUN apt-get update && apt-get install -y ca-certificates curl git ffmpeg python3 python3-pip chromium gosu sudo procps build-essential jq && rm -rf /var/lib/apt/lists/* +RUN ln -s /usr/bin/python3 /usr/bin/python RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN (type -p wget >/dev/null || (apt-get update && apt-get install -y wget)) && \ mkdir -p -m 755 /etc/apt/keyrings && \ From a4e82f2662f242fb8e6b37891ca0eb0184d53336 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Wed, 11 Mar 2026 08:59:58 +0100 Subject: [PATCH 26/41] fix: re-add --dangerously-skip-permissions to Claude Code driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream v0.3.46 removed the flag, causing all agents using Claude Code to be completely paralyzed — every command requires interactive terminal approval that cannot be given via dashboard or Telegram. Without this flag, Claude Code as a provider is unusable in any non-interactive context (web UI, Telegram, API, scheduled tasks). Refs: #515, #325 Co-Authored-By: Claude Opus 4.6 --- .current-upstream-version | 2 +- crates/openfang-runtime/src/drivers/claude_code.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.current-upstream-version b/.current-upstream-version index 23ed88082..ab27bf490 100644 --- a/.current-upstream-version +++ b/.current-upstream-version @@ -1 +1 @@ -v0.3.34 +v0.3.46 diff --git a/crates/openfang-runtime/src/drivers/claude_code.rs b/crates/openfang-runtime/src/drivers/claude_code.rs index 1cdfe3b44..7e721ae67 100644 --- a/crates/openfang-runtime/src/drivers/claude_code.rs +++ b/crates/openfang-runtime/src/drivers/claude_code.rs @@ -202,6 +202,7 @@ impl LlmDriver for ClaudeCodeDriver { let mut cmd = tokio::process::Command::new(&self.cli_path); cmd.arg("-p") .arg(&prompt) + .arg("--dangerously-skip-permissions") .arg("--output-format") .arg("json"); @@ -305,6 +306,7 @@ impl LlmDriver for ClaudeCodeDriver { let mut cmd = tokio::process::Command::new(&self.cli_path); cmd.arg("-p") .arg(&prompt) + .arg("--dangerously-skip-permissions") .arg("--output-format") .arg("stream-json") .arg("--verbose"); From 12565fcee37975618a3f0009503a204c0b6f0531 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 12 Mar 2026 00:06:05 +0000 Subject: [PATCH 27/41] chore: sync to upstream v0.3.47 --- .current-upstream-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.current-upstream-version b/.current-upstream-version index ab27bf490..acf24e50e 100644 --- a/.current-upstream-version +++ b/.current-upstream-version @@ -1 +1 @@ -v0.3.46 +v0.3.47 From 33c9ffae825f653425ed7c78789818e814aa2abd Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 09:19:09 +0100 Subject: [PATCH 28/41] fix(whatsapp-gateway): reconnect on all non-logout disconnections with exponential backoff The gateway only reconnected on `restartRequired` and `timedOut` disconnect reasons. All other Baileys disconnect codes (connectionClosed/428, connectionLost/408, connectionReplaced/440, multideviceMismatch, etc.) fell through to the else branch which treated them as "QR expired" and stopped trying. This caused the gateway to silently die after any transient network issue or WhatsApp server-side session refresh. Changes: - Treat every disconnect reason except `loggedOut` as recoverable - Add exponential backoff (2s base, 1.5x multiplier, 60s cap) instead of a fixed 2s delay - Reset backoff counter on successful connection - Auto-connect on startup when auth credentials exist from a previous session, eliminating the need for a manual POST /login/start after process restarts Closes #529 Co-Authored-By: Claude Opus 4.6 --- packages/whatsapp-gateway/index.js | 40 +++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/whatsapp-gateway/index.js b/packages/whatsapp-gateway/index.js index b6a00a747..7c9e973d7 100644 --- a/packages/whatsapp-gateway/index.js +++ b/packages/whatsapp-gateway/index.js @@ -20,6 +20,8 @@ let qrDataUrl = ''; // latest QR code as data:image/png;base64,... let connStatus = 'disconnected'; // disconnected | qr_ready | connected let qrExpired = false; let statusMessage = 'Not started'; +let reconnectAttempts = 0; // track consecutive reconnection attempts +const MAX_RECONNECT_DELAY = 60_000; // cap backoff at 60s // --------------------------------------------------------------------------- // Baileys connection @@ -84,6 +86,7 @@ async function startConnection() { statusMessage = 'Logged out. Generate a new QR code to reconnect.'; qrDataUrl = ''; sock = null; + reconnectAttempts = 0; // Remove auth store so next connect gets a fresh QR const fs = require('node:fs'); const path = require('node:path'); @@ -91,18 +94,17 @@ async function startConnection() { if (fs.existsSync(authPath)) { fs.rmSync(authPath, { recursive: true, force: true }); } - } else if (statusCode === DisconnectReason.restartRequired || - statusCode === DisconnectReason.timedOut) { - // Recoverable — reconnect automatically - console.log('[gateway] Reconnecting...'); - statusMessage = 'Reconnecting...'; - setTimeout(() => startConnection(), 2000); } else { - // QR expired or other non-recoverable close - qrExpired = true; + // All other close reasons are recoverable — reconnect with backoff. + // This includes: connectionClosed (428), connectionLost (408), + // connectionReplaced (440), restartRequired (515), timedOut (408), + // multideviceMismatch, and QR expiry. + reconnectAttempts++; + const delay = Math.min(2000 * Math.pow(1.5, reconnectAttempts - 1), MAX_RECONNECT_DELAY); + console.log(`[gateway] Reconnecting in ${Math.round(delay / 1000)}s (attempt ${reconnectAttempts})...`); connStatus = 'disconnected'; - statusMessage = 'QR code expired. Click "Generate New QR" to retry.'; - qrDataUrl = ''; + statusMessage = `Reconnecting (attempt ${reconnectAttempts})...`; + setTimeout(() => startConnection(), delay); } } @@ -110,6 +112,7 @@ async function startConnection() { connStatus = 'connected'; qrExpired = false; qrDataUrl = ''; + reconnectAttempts = 0; statusMessage = 'Connected to WhatsApp'; console.log('[gateway] Connected to WhatsApp!'); } @@ -332,11 +335,24 @@ const server = http.createServer(async (req, res) => { } }); -server.listen(PORT, '127.0.0.1', () => { +server.listen(PORT, '127.0.0.1', async () => { console.log(`[gateway] WhatsApp Web gateway listening on http://127.0.0.1:${PORT}`); console.log(`[gateway] OpenFang URL: ${OPENFANG_URL}`); console.log(`[gateway] Default agent: ${DEFAULT_AGENT}`); - console.log('[gateway] Waiting for POST /login/start to begin QR flow...'); + + // Auto-connect if auth credentials already exist (previous session) + const fs = require('node:fs'); + const authPath = require('node:path').join(__dirname, 'auth_store', 'creds.json'); + if (fs.existsSync(authPath)) { + console.log('[gateway] Found existing auth — auto-connecting...'); + try { + await startConnection(); + } catch (err) { + console.error('[gateway] Auto-connect failed:', err.message); + } + } else { + console.log('[gateway] No auth found — waiting for POST /login/start to begin QR flow...'); + } }); // Graceful shutdown From 1b2cdea480f9196f5f570642539d5558e92f141d Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 10:36:25 +0100 Subject: [PATCH 29/41] feat: add Qwen Code CLI as LLM provider + refactor build_args for testability Add qwen-code as a new subprocess-based LLM provider, mirroring the claude-code driver pattern. Qwen Code CLI (qwen) uses --yolo for non-interactive mode and supports json/stream-json output formats. New files: - drivers/qwen_code.rs: full driver with complete/stream, env filtering Changes: - drivers/mod.rs: register qwen-code provider (defaults, create_driver) - model_catalog.rs: add provider info, 3 models (qwen3-coder, qwen-coder-plus, qwq-32b), aliases, auth detection - claude_code.rs: extract build_args() for testability, fix duplicate --dangerously-skip-permissions flag that was always added regardless of skip_permissions setting Tests: 31 new/updated (18 qwen-code + 13 claude-code) covering build_args with/without permission flags, streaming, model selection, prompt building, JSON parsing, and catalog integration. Co-Authored-By: Claude Opus 4.6 --- .../src/drivers/claude_code.rs | 97 ++- crates/openfang-runtime/src/drivers/mod.rs | 21 +- .../openfang-runtime/src/drivers/qwen_code.rs | 652 ++++++++++++++++++ crates/openfang-runtime/src/model_catalog.rs | 98 +++ 4 files changed, 839 insertions(+), 29 deletions(-) create mode 100644 crates/openfang-runtime/src/drivers/qwen_code.rs diff --git a/crates/openfang-runtime/src/drivers/claude_code.rs b/crates/openfang-runtime/src/drivers/claude_code.rs index 7e721ae67..c73ef6f74 100644 --- a/crates/openfang-runtime/src/drivers/claude_code.rs +++ b/crates/openfang-runtime/src/drivers/claude_code.rs @@ -125,6 +125,42 @@ impl ClaudeCodeDriver { } } + /// Build the CLI argument list for a completion request. + /// + /// Exposed as a testable method so unit tests can verify that + /// `--dangerously-skip-permissions`, `--model`, and output format flags + /// are set correctly. + fn build_args( + &self, + prompt: &str, + model_flag: Option<&str>, + streaming: bool, + ) -> Vec { + let mut args = vec![ + "-p".to_string(), + prompt.to_string(), + ]; + + if self.skip_permissions { + args.push("--dangerously-skip-permissions".to_string()); + } + + args.push("--output-format".to_string()); + if streaming { + args.push("stream-json".to_string()); + args.push("--verbose".to_string()); + } else { + args.push("json".to_string()); + } + + if let Some(model) = model_flag { + args.push("--model".to_string()); + args.push(model.to_string()); + } + + args + } + /// Apply security env filtering to a command. /// /// Instead of `env_clear()` (which breaks Node.js, NVM, SSL, proxies), @@ -199,20 +235,10 @@ impl LlmDriver for ClaudeCodeDriver { let prompt = Self::build_prompt(&request); let model_flag = Self::model_flag(&request.model); - let mut cmd = tokio::process::Command::new(&self.cli_path); - cmd.arg("-p") - .arg(&prompt) - .arg("--dangerously-skip-permissions") - .arg("--output-format") - .arg("json"); + let args = self.build_args(&prompt, model_flag.as_deref(), false); - if self.skip_permissions { - cmd.arg("--dangerously-skip-permissions"); - } - - if let Some(ref model) = model_flag { - cmd.arg("--model").arg(model); - } + let mut cmd = tokio::process::Command::new(&self.cli_path); + cmd.args(&args); Self::apply_env_filter(&mut cmd); @@ -303,21 +329,10 @@ impl LlmDriver for ClaudeCodeDriver { let prompt = Self::build_prompt(&request); let model_flag = Self::model_flag(&request.model); - let mut cmd = tokio::process::Command::new(&self.cli_path); - cmd.arg("-p") - .arg(&prompt) - .arg("--dangerously-skip-permissions") - .arg("--output-format") - .arg("stream-json") - .arg("--verbose"); + let args = self.build_args(&prompt, model_flag.as_deref(), true); - if self.skip_permissions { - cmd.arg("--dangerously-skip-permissions"); - } - - if let Some(ref model) = model_flag { - cmd.arg("--model").arg(model); - } + let mut cmd = tokio::process::Command::new(&self.cli_path); + cmd.args(&args); Self::apply_env_filter(&mut cmd); @@ -540,6 +555,34 @@ mod tests { assert!(!driver.skip_permissions); } + #[test] + fn test_build_args_with_skip_permissions() { + let driver = ClaudeCodeDriver::new(None, true); + let args = driver.build_args("hello", Some("opus"), false); + assert!(args.contains(&"--dangerously-skip-permissions".to_string()), + "should contain --dangerously-skip-permissions when skip_permissions=true"); + assert!(args.contains(&"json".to_string())); + assert!(args.contains(&"--model".to_string())); + assert!(args.contains(&"opus".to_string())); + } + + #[test] + fn test_build_args_without_skip_permissions() { + let driver = ClaudeCodeDriver::new(None, false); + let args = driver.build_args("hello", Some("sonnet"), false); + assert!(!args.contains(&"--dangerously-skip-permissions".to_string()), + "should NOT contain --dangerously-skip-permissions when skip_permissions=false"); + } + + #[test] + fn test_build_args_streaming() { + let driver = ClaudeCodeDriver::new(None, true); + let args = driver.build_args("hello", None, true); + assert!(args.contains(&"stream-json".to_string())); + assert!(args.contains(&"--verbose".to_string())); + assert!(!args.contains(&"--model".to_string()), "no model flag when model_flag is None"); + } + #[test] fn test_sensitive_env_list_coverage() { // Ensure all major provider keys are in the strip list diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index a6fd2f65f..113d9bca7 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -7,6 +7,7 @@ pub mod anthropic; pub mod claude_code; pub mod copilot; +pub mod qwen_code; pub mod fallback; pub mod gemini; pub mod openai; @@ -150,6 +151,11 @@ fn provider_defaults(provider: &str) -> Option { api_key_env: "", key_required: false, }), + "qwen-code" => Some(ProviderDefaults { + base_url: "", + api_key_env: "", + key_required: false, + }), "moonshot" | "kimi" | "kimi2" => Some(ProviderDefaults { base_url: MOONSHOT_BASE_URL, api_key_env: "MOONSHOT_API_KEY", @@ -309,6 +315,15 @@ pub fn create_driver(config: &DriverConfig) -> Result, LlmErr ))); } + // Qwen Code CLI — subprocess-based, no API key needed + if provider == "qwen-code" { + let cli_path = config.base_url.clone(); + return Ok(Arc::new(qwen_code::QwenCodeDriver::new( + cli_path, + config.skip_permissions, + ))); + } + // GitHub Copilot — wraps OpenAI-compatible driver with automatic token exchange. // The CopilotDriver exchanges the GitHub PAT for a Copilot API token on demand, // caches it, and refreshes when expired. @@ -411,7 +426,7 @@ pub fn create_driver(config: &DriverConfig) -> Result, LlmErr "Unknown provider '{}'. Supported: anthropic, gemini, openai, groq, openrouter, \ deepseek, together, mistral, fireworks, ollama, vllm, lmstudio, perplexity, \ cohere, ai21, cerebras, sambanova, huggingface, xai, replicate, github-copilot, \ - chutes, venice, codex, claude-code. Or set base_url for a custom OpenAI-compatible endpoint.", + chutes, venice, codex, claude-code, qwen-code. Or set base_url for a custom OpenAI-compatible endpoint.", provider ), }) @@ -486,6 +501,7 @@ pub fn known_providers() -> &'static [&'static str] { "venice", "codex", "claude-code", + "qwen-code", ] } @@ -587,7 +603,8 @@ mod tests { assert!(providers.contains(&"chutes")); assert!(providers.contains(&"codex")); assert!(providers.contains(&"claude-code")); - assert_eq!(providers.len(), 34); + assert!(providers.contains(&"qwen-code")); + assert_eq!(providers.len(), 35); } #[test] diff --git a/crates/openfang-runtime/src/drivers/qwen_code.rs b/crates/openfang-runtime/src/drivers/qwen_code.rs new file mode 100644 index 000000000..97f4da136 --- /dev/null +++ b/crates/openfang-runtime/src/drivers/qwen_code.rs @@ -0,0 +1,652 @@ +//! Qwen Code CLI backend driver. +//! +//! Spawns the `qwen` CLI (Qwen Code) as a subprocess in print mode (`-p`), +//! which is non-interactive and handles its own authentication. +//! This allows users with Qwen Code installed to use it as an LLM provider +//! without needing a separate API key. + +use crate::llm_driver::{CompletionRequest, CompletionResponse, LlmDriver, LlmError, StreamEvent}; +use async_trait::async_trait; +use openfang_types::message::{ContentBlock, Role, StopReason, TokenUsage}; +use serde::Deserialize; +use tokio::io::AsyncBufReadExt; +use tracing::{debug, warn}; + +/// Environment variable names (and suffixes) to strip from the subprocess +/// to prevent leaking API keys from other providers. +const SENSITIVE_ENV_EXACT: &[&str] = &[ + "OPENAI_API_KEY", + "ANTHROPIC_API_KEY", + "GEMINI_API_KEY", + "GOOGLE_API_KEY", + "GROQ_API_KEY", + "DEEPSEEK_API_KEY", + "MISTRAL_API_KEY", + "TOGETHER_API_KEY", + "FIREWORKS_API_KEY", + "OPENROUTER_API_KEY", + "PERPLEXITY_API_KEY", + "COHERE_API_KEY", + "AI21_API_KEY", + "CEREBRAS_API_KEY", + "SAMBANOVA_API_KEY", + "HUGGINGFACE_API_KEY", + "XAI_API_KEY", + "REPLICATE_API_TOKEN", + "BRAVE_API_KEY", + "TAVILY_API_KEY", + "ELEVENLABS_API_KEY", +]; + +/// Suffixes that indicate a secret — remove any env var ending with these +/// unless it starts with `QWEN_`. +const SENSITIVE_SUFFIXES: &[&str] = &["_SECRET", "_TOKEN", "_PASSWORD"]; + +/// LLM driver that delegates to the Qwen Code CLI. +pub struct QwenCodeDriver { + cli_path: String, + skip_permissions: bool, +} + +impl QwenCodeDriver { + /// Create a new Qwen Code driver. + /// + /// `cli_path` overrides the CLI binary path; defaults to `"qwen"` on PATH. + /// `skip_permissions` adds `--yolo` to the spawned command so that the CLI + /// runs non-interactively (required for daemon mode). + pub fn new(cli_path: Option, skip_permissions: bool) -> Self { + if skip_permissions { + warn!( + "Qwen Code driver: --yolo enabled. \ + The CLI will not prompt for tool approvals. \ + OpenFang's own capability/RBAC system enforces access control." + ); + } + + Self { + cli_path: cli_path + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| "qwen".to_string()), + skip_permissions, + } + } + + /// Detect if the Qwen Code CLI is available on PATH. + pub fn detect() -> Option { + let output = std::process::Command::new("qwen") + .arg("--version") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::null()) + .output() + .ok()?; + + if output.status.success() { + Some(String::from_utf8_lossy(&output.stdout).trim().to_string()) + } else { + None + } + } + + /// Build a text prompt from the completion request messages. + fn build_prompt(request: &CompletionRequest) -> String { + let mut parts = Vec::new(); + + if let Some(ref sys) = request.system { + parts.push(format!("[System]\n{sys}")); + } + + for msg in &request.messages { + let role_label = match msg.role { + Role::User => "User", + Role::Assistant => "Assistant", + Role::System => "System", + }; + let text = msg.content.text_content(); + if !text.is_empty() { + parts.push(format!("[{role_label}]\n{text}")); + } + } + + parts.join("\n\n") + } + + /// Map a model ID like "qwen-code/qwen3-coder" to CLI --model flag value. + fn model_flag(model: &str) -> Option { + let stripped = model + .strip_prefix("qwen-code/") + .unwrap_or(model); + match stripped { + "qwen3-coder" => Some("qwen3-coder".to_string()), + "qwen-coder-plus" => Some("qwen-coder-plus".to_string()), + "qwq-32b" => Some("qwq-32b".to_string()), + _ => Some(stripped.to_string()), + } + } + + /// Build the CLI argument list for a completion request. + /// + /// Exposed as a testable method so unit tests can verify that `--yolo`, + /// `--model`, and output format flags are set correctly. + fn build_args( + &self, + prompt: &str, + model_flag: Option<&str>, + streaming: bool, + ) -> Vec { + let mut args = vec![ + "-p".to_string(), + prompt.to_string(), + "--output-format".to_string(), + if streaming { + "stream-json".to_string() + } else { + "json".to_string() + }, + ]; + + if self.skip_permissions { + args.push("--yolo".to_string()); + } + + if let Some(model) = model_flag { + args.push("--model".to_string()); + args.push(model.to_string()); + } + + args + } + + /// Apply security env filtering to a command. + /// + /// Instead of `env_clear()` (which breaks Node.js, NVM, SSL, proxies), + /// we keep the full environment and only remove known sensitive API keys + /// from other LLM providers. + fn apply_env_filter(cmd: &mut tokio::process::Command) { + for key in SENSITIVE_ENV_EXACT { + cmd.env_remove(key); + } + // Remove any env var with a sensitive suffix, unless it's QWEN_* + for (key, _) in std::env::vars() { + if key.starts_with("QWEN_") { + continue; + } + let upper = key.to_uppercase(); + for suffix in SENSITIVE_SUFFIXES { + if upper.ends_with(suffix) { + cmd.env_remove(&key); + break; + } + } + } + } +} + +/// JSON output from `qwen -p --output-format json`. +/// +/// The CLI may return the response text in different fields depending on +/// version: `result`, `content`, or `text`. We try all three. +#[derive(Debug, Deserialize)] +struct QwenJsonOutput { + result: Option, + #[serde(default)] + content: Option, + #[serde(default)] + text: Option, + #[serde(default)] + usage: Option, + #[serde(default)] + #[allow(dead_code)] + cost_usd: Option, +} + +/// Usage stats from Qwen CLI JSON output. +#[derive(Debug, Deserialize, Default)] +struct QwenUsage { + #[serde(default)] + input_tokens: u64, + #[serde(default)] + output_tokens: u64, +} + +/// Stream JSON event from `qwen -p --output-format stream-json`. +#[derive(Debug, Deserialize)] +struct QwenStreamEvent { + #[serde(default)] + r#type: String, + #[serde(default)] + content: Option, + #[serde(default)] + result: Option, + #[serde(default)] + usage: Option, +} + +#[async_trait] +impl LlmDriver for QwenCodeDriver { + async fn complete( + &self, + request: CompletionRequest, + ) -> Result { + let prompt = Self::build_prompt(&request); + let model_flag = Self::model_flag(&request.model); + + let args = self.build_args(&prompt, model_flag.as_deref(), false); + + let mut cmd = tokio::process::Command::new(&self.cli_path); + cmd.args(&args); + + Self::apply_env_filter(&mut cmd); + + cmd.stdout(std::process::Stdio::piped()); + cmd.stderr(std::process::Stdio::piped()); + + debug!(cli = %self.cli_path, skip_permissions = self.skip_permissions, "Spawning Qwen Code CLI"); + + let output = cmd + .output() + .await + .map_err(|e| LlmError::Http(format!( + "Qwen Code CLI not found or failed to start ({}). \ + Install: npm install -g @qwen-code/qwen-code", + e + )))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let detail = if !stderr.is_empty() { &stderr } else { &stdout }; + let code = output.status.code().unwrap_or(1); + + // Provide actionable error messages + let message = if detail.contains("not authenticated") + || detail.contains("auth") + || detail.contains("login") + || detail.contains("credentials") + { + format!( + "Qwen Code CLI is not authenticated. Run: qwen --auth-type qwen-oauth\nDetail: {detail}" + ) + } else if detail.contains("permission") + || detail.contains("--yolo") + { + format!( + "Qwen Code CLI requires permissions acceptance. \ + Run: qwen --yolo (once to accept)\nDetail: {detail}" + ) + } else { + format!("Qwen Code CLI exited with code {code}: {detail}") + }; + + return Err(LlmError::Api { + status: code as u16, + message, + }); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + + // Try JSON parse first + if let Ok(parsed) = serde_json::from_str::(&stdout) { + let text = parsed.result + .or(parsed.content) + .or(parsed.text) + .unwrap_or_default(); + let usage = parsed.usage.unwrap_or_default(); + return Ok(CompletionResponse { + content: vec![ContentBlock::Text { text: text.clone(), provider_metadata: None }], + stop_reason: StopReason::EndTurn, + tool_calls: Vec::new(), + usage: TokenUsage { + input_tokens: usage.input_tokens, + output_tokens: usage.output_tokens, + }, + }); + } + + // Fallback: treat entire stdout as plain text + let text = stdout.trim().to_string(); + Ok(CompletionResponse { + content: vec![ContentBlock::Text { text, provider_metadata: None }], + stop_reason: StopReason::EndTurn, + tool_calls: Vec::new(), + usage: TokenUsage { + input_tokens: 0, + output_tokens: 0, + }, + }) + } + + async fn stream( + &self, + request: CompletionRequest, + tx: tokio::sync::mpsc::Sender, + ) -> Result { + let prompt = Self::build_prompt(&request); + let model_flag = Self::model_flag(&request.model); + + let args = self.build_args(&prompt, model_flag.as_deref(), true); + + let mut cmd = tokio::process::Command::new(&self.cli_path); + cmd.args(&args); + + Self::apply_env_filter(&mut cmd); + + cmd.stdout(std::process::Stdio::piped()); + cmd.stderr(std::process::Stdio::piped()); + + debug!(cli = %self.cli_path, skip_permissions = self.skip_permissions, "Spawning Qwen Code CLI (streaming)"); + + let mut child = cmd + .spawn() + .map_err(|e| LlmError::Http(format!( + "Qwen Code CLI not found or failed to start ({}). \ + Install: npm install -g @qwen-code/qwen-code", + e + )))?; + + let stdout = child + .stdout + .take() + .ok_or_else(|| LlmError::Http("No stdout from qwen CLI".to_string()))?; + + let reader = tokio::io::BufReader::new(stdout); + let mut lines = reader.lines(); + + let mut full_text = String::new(); + let mut final_usage = TokenUsage { + input_tokens: 0, + output_tokens: 0, + }; + + while let Ok(Some(line)) = lines.next_line().await { + if line.trim().is_empty() { + continue; + } + + match serde_json::from_str::(&line) { + Ok(event) => { + match event.r#type.as_str() { + "content" | "text" | "assistant" | "content_block_delta" => { + if let Some(ref content) = event.content { + full_text.push_str(content); + let _ = tx + .send(StreamEvent::TextDelta { + text: content.clone(), + }) + .await; + } + } + "result" | "done" | "complete" => { + if let Some(ref result) = event.result { + if full_text.is_empty() { + full_text = result.clone(); + let _ = tx + .send(StreamEvent::TextDelta { + text: result.clone(), + }) + .await; + } + } + if let Some(usage) = event.usage { + final_usage = TokenUsage { + input_tokens: usage.input_tokens, + output_tokens: usage.output_tokens, + }; + } + } + _ => { + // Unknown event type — try content field as fallback + if let Some(ref content) = event.content { + full_text.push_str(content); + let _ = tx + .send(StreamEvent::TextDelta { + text: content.clone(), + }) + .await; + } + } + } + } + Err(e) => { + // Not valid JSON — treat as raw text + warn!(line = %line, error = %e, "Non-JSON line from Qwen CLI"); + full_text.push_str(&line); + let _ = tx + .send(StreamEvent::TextDelta { text: line }) + .await; + } + } + } + + // Wait for process to finish + let status = child + .wait() + .await + .map_err(|e| LlmError::Http(format!("Qwen CLI wait failed: {e}")))?; + + if !status.success() { + warn!(code = ?status.code(), "Qwen CLI exited with error"); + } + + let _ = tx + .send(StreamEvent::ContentComplete { + stop_reason: StopReason::EndTurn, + usage: final_usage, + }) + .await; + + Ok(CompletionResponse { + content: vec![ContentBlock::Text { text: full_text, provider_metadata: None }], + stop_reason: StopReason::EndTurn, + tool_calls: Vec::new(), + usage: final_usage, + }) + } +} + +/// Check if the Qwen Code CLI is available and authenticated. +pub fn qwen_code_available() -> bool { + QwenCodeDriver::detect().is_some() + || qwen_credentials_exist() +} + +/// Check if Qwen credentials exist. +/// +/// Qwen Code stores session/credentials in `~/.qwen-code/` directory. +fn qwen_credentials_exist() -> bool { + if let Some(home) = home_dir() { + let qwen_dir = home.join(".qwen-code"); + qwen_dir.exists() + } else { + false + } +} + +/// Cross-platform home directory. +fn home_dir() -> Option { + #[cfg(target_os = "windows")] + { + std::env::var("USERPROFILE").ok().map(std::path::PathBuf::from) + } + #[cfg(not(target_os = "windows"))] + { + std::env::var("HOME").ok().map(std::path::PathBuf::from) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_build_prompt_simple() { + use openfang_types::message::{Message, MessageContent}; + + let request = CompletionRequest { + model: "qwen-code/qwen3-coder".to_string(), + messages: vec![Message { + role: Role::User, + content: MessageContent::text("Hello"), + }], + tools: vec![], + max_tokens: 1024, + temperature: 0.7, + system: Some("You are helpful.".to_string()), + thinking: None, + }; + + let prompt = QwenCodeDriver::build_prompt(&request); + assert!(prompt.contains("[System]")); + assert!(prompt.contains("You are helpful.")); + assert!(prompt.contains("[User]")); + assert!(prompt.contains("Hello")); + } + + #[test] + fn test_build_prompt_multi_turn() { + use openfang_types::message::{Message, MessageContent}; + + let request = CompletionRequest { + model: "qwen-code/qwen3-coder".to_string(), + messages: vec![ + Message { + role: Role::User, + content: MessageContent::text("What is 2+2?"), + }, + Message { + role: Role::Assistant, + content: MessageContent::text("4"), + }, + Message { + role: Role::User, + content: MessageContent::text("And 3+3?"), + }, + ], + tools: vec![], + max_tokens: 1024, + temperature: 0.7, + system: None, + thinking: None, + }; + + let prompt = QwenCodeDriver::build_prompt(&request); + assert!(prompt.contains("[User]\nWhat is 2+2?")); + assert!(prompt.contains("[Assistant]\n4")); + assert!(prompt.contains("[User]\nAnd 3+3?")); + } + + #[test] + fn test_model_flag_mapping() { + assert_eq!( + QwenCodeDriver::model_flag("qwen-code/qwen3-coder"), + Some("qwen3-coder".to_string()) + ); + assert_eq!( + QwenCodeDriver::model_flag("qwen-code/qwen-coder-plus"), + Some("qwen-coder-plus".to_string()) + ); + assert_eq!( + QwenCodeDriver::model_flag("qwen-code/qwq-32b"), + Some("qwq-32b".to_string()) + ); + assert_eq!( + QwenCodeDriver::model_flag("custom-model"), + Some("custom-model".to_string()) + ); + } + + #[test] + fn test_new_defaults_to_qwen() { + let driver = QwenCodeDriver::new(None, true); + assert_eq!(driver.cli_path, "qwen"); + assert!(driver.skip_permissions); + } + + #[test] + fn test_new_with_custom_path() { + let driver = QwenCodeDriver::new(Some("/usr/local/bin/qwen".to_string()), true); + assert_eq!(driver.cli_path, "/usr/local/bin/qwen"); + } + + #[test] + fn test_new_with_empty_path() { + let driver = QwenCodeDriver::new(Some(String::new()), true); + assert_eq!(driver.cli_path, "qwen"); + } + + #[test] + fn test_skip_permissions_disabled() { + let driver = QwenCodeDriver::new(None, false); + assert!(!driver.skip_permissions); + } + + #[test] + fn test_build_args_with_yolo() { + let driver = QwenCodeDriver::new(None, true); + let args = driver.build_args("hello", Some("qwen3-coder"), false); + assert!(args.contains(&"--yolo".to_string()), "should contain --yolo when skip_permissions=true"); + assert!(args.contains(&"json".to_string())); + assert!(args.contains(&"--model".to_string())); + assert!(args.contains(&"qwen3-coder".to_string())); + } + + #[test] + fn test_build_args_without_yolo() { + let driver = QwenCodeDriver::new(None, false); + let args = driver.build_args("hello", Some("qwen3-coder"), false); + assert!(!args.contains(&"--yolo".to_string()), "should NOT contain --yolo when skip_permissions=false"); + } + + #[test] + fn test_build_args_streaming() { + let driver = QwenCodeDriver::new(None, true); + let args = driver.build_args("hello", None, true); + assert!(args.contains(&"stream-json".to_string())); + assert!(!args.contains(&"json".to_string()) || args.contains(&"stream-json".to_string())); + assert!(!args.contains(&"--model".to_string()), "no model flag when model_flag is None"); + } + + #[test] + fn test_sensitive_env_list_coverage() { + assert!(SENSITIVE_ENV_EXACT.contains(&"OPENAI_API_KEY")); + assert!(SENSITIVE_ENV_EXACT.contains(&"ANTHROPIC_API_KEY")); + assert!(SENSITIVE_ENV_EXACT.contains(&"GEMINI_API_KEY")); + assert!(SENSITIVE_ENV_EXACT.contains(&"GROQ_API_KEY")); + assert!(SENSITIVE_ENV_EXACT.contains(&"DEEPSEEK_API_KEY")); + } + + #[test] + fn test_json_output_parsing() { + let json = r#"{"result":"Hello world","usage":{"input_tokens":10,"output_tokens":5}}"#; + let parsed: QwenJsonOutput = serde_json::from_str(json).unwrap(); + assert_eq!(parsed.result.unwrap(), "Hello world"); + assert_eq!(parsed.usage.unwrap().input_tokens, 10); + } + + #[test] + fn test_json_output_content_fallback() { + let json = r#"{"content":"Fallback text"}"#; + let parsed: QwenJsonOutput = serde_json::from_str(json).unwrap(); + assert!(parsed.result.is_none()); + assert_eq!(parsed.content.unwrap(), "Fallback text"); + } + + #[test] + fn test_stream_event_parsing() { + let json = r#"{"type":"content","content":"chunk"}"#; + let event: QwenStreamEvent = serde_json::from_str(json).unwrap(); + assert_eq!(event.r#type, "content"); + assert_eq!(event.content.unwrap(), "chunk"); + } + + #[test] + fn test_stream_event_result() { + let json = r#"{"type":"result","result":"final answer","usage":{"input_tokens":100,"output_tokens":50}}"#; + let event: QwenStreamEvent = serde_json::from_str(json).unwrap(); + assert_eq!(event.r#type, "result"); + assert_eq!(event.result.unwrap(), "final answer"); + let usage = event.usage.unwrap(); + assert_eq!(usage.input_tokens, 100); + assert_eq!(usage.output_tokens, 50); + } +} diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 51dc92ec5..851123a07 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -69,6 +69,19 @@ impl ModelCatalog { continue; } + // Qwen Code: detect CLI installation + authentication + if provider.id == "qwen-code" { + let cli_installed = crate::drivers::qwen_code::QwenCodeDriver::detect().is_some(); + if cli_installed && crate::drivers::qwen_code::qwen_code_available() { + provider.auth_status = AuthStatus::Configured; + } else if cli_installed { + provider.auth_status = AuthStatus::Missing; + } else { + provider.auth_status = AuthStatus::NotRequired; + } + continue; + } + if !provider.key_required { provider.auth_status = AuthStatus::NotRequired; continue; @@ -755,6 +768,16 @@ fn builtin_providers() -> Vec { auth_status: AuthStatus::NotRequired, model_count: 0, }, + // ── Qwen Code CLI ───────────────────────────────────────── + ProviderInfo { + id: "qwen-code".into(), + display_name: "Qwen Code".into(), + api_key_env: String::new(), + base_url: String::new(), + key_required: false, + auth_status: AuthStatus::NotRequired, + model_count: 0, + }, ] } @@ -828,6 +851,11 @@ fn builtin_aliases() -> HashMap { ("claude-code-opus", "claude-code/opus"), ("claude-code-sonnet", "claude-code/sonnet"), ("claude-code-haiku", "claude-code/haiku"), + // Qwen Code aliases + ("qwen-code", "qwen-code/qwen3-coder"), + ("qwen-code-qwen3", "qwen-code/qwen3-coder"), + ("qwen-code-plus", "qwen-code/qwen-coder-plus"), + ("qwen-code-qwq", "qwen-code/qwq-32b"), ]; pairs .into_iter() @@ -3416,6 +3444,51 @@ fn builtin_models() -> Vec { aliases: vec!["claude-code-haiku".into()], }, // ══════════════════════════════════════════════════════════════ + // Qwen Code CLI (3) — subprocess-based + // ══════════════════════════════════════════════════════════════ + ModelCatalogEntry { + id: "qwen-code/qwen3-coder".into(), + display_name: "Qwen3 Coder (CLI)".into(), + provider: "qwen-code".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 65_536, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: false, + supports_vision: false, + supports_streaming: true, + aliases: vec!["qwen-code".into(), "qwen-code-qwen3".into()], + }, + ModelCatalogEntry { + id: "qwen-code/qwen-coder-plus".into(), + display_name: "Qwen Coder Plus (CLI)".into(), + provider: "qwen-code".into(), + tier: ModelTier::Frontier, + context_window: 131_072, + max_output_tokens: 65_536, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: false, + supports_vision: false, + supports_streaming: true, + aliases: vec!["qwen-code-plus".into()], + }, + ModelCatalogEntry { + id: "qwen-code/qwq-32b".into(), + display_name: "QwQ 32B (CLI)".into(), + provider: "qwen-code".into(), + tier: ModelTier::Balanced, + context_window: 131_072, + max_output_tokens: 65_536, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: false, + supports_vision: false, + supports_streaming: true, + aliases: vec!["qwen-code-qwq".into()], + }, + // ══════════════════════════════════════════════════════════════ // Chutes.ai (5) // ══════════════════════════════════════════════════════════════ ModelCatalogEntry { @@ -3917,4 +3990,29 @@ mod tests { let entry = catalog.find_model("claude-code").unwrap(); assert_eq!(entry.id, "claude-code/sonnet"); } + + #[test] + fn test_qwen_code_provider() { + let catalog = ModelCatalog::new(); + let qc = catalog.get_provider("qwen-code").unwrap(); + assert_eq!(qc.display_name, "Qwen Code"); + assert!(!qc.key_required); + } + + #[test] + fn test_qwen_code_models() { + let catalog = ModelCatalog::new(); + let models = catalog.models_by_provider("qwen-code"); + assert_eq!(models.len(), 3); + assert!(models.iter().any(|m| m.id == "qwen-code/qwen3-coder")); + assert!(models.iter().any(|m| m.id == "qwen-code/qwen-coder-plus")); + assert!(models.iter().any(|m| m.id == "qwen-code/qwq-32b")); + } + + #[test] + fn test_qwen_code_aliases() { + let catalog = ModelCatalog::new(); + let entry = catalog.find_model("qwen-code").unwrap(); + assert_eq!(entry.id, "qwen-code/qwen3-coder"); + } } From 254170cdee07f0c40e9b8fa47a5ece1c12188264 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 11:09:51 +0100 Subject: [PATCH 30/41] feat: add Qwen Code CLI to Docker image Install @qwen-code/qwen-code alongside Claude Code so the qwen-code provider is available in the container. Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 23b52ee14..b56fe096e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ RUN (type -p wget >/dev/null || (apt-get update && apt-get install -y wget)) && apt-get update && apt-get install -y gh && rm -rf /var/lib/apt/lists/* RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ apt-get install -y nodejs && \ - npm install -g @anthropic-ai/claude-code && \ + npm install -g @anthropic-ai/claude-code @qwen-code/qwen-code && \ rm -rf /var/lib/apt/lists/* RUN useradd -m -s /bin/bash openfang && echo "openfang ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/openfang USER openfang From 3abf3f54df614e8655ae71df1e320aeffa5adaa3 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 11:13:01 +0100 Subject: [PATCH 31/41] fix: correct Qwen Code credentials path from ~/.qwen-code to ~/.qwen Co-Authored-By: Claude Opus 4.6 --- crates/openfang-runtime/src/drivers/qwen_code.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/openfang-runtime/src/drivers/qwen_code.rs b/crates/openfang-runtime/src/drivers/qwen_code.rs index 97f4da136..3734ebef9 100644 --- a/crates/openfang-runtime/src/drivers/qwen_code.rs +++ b/crates/openfang-runtime/src/drivers/qwen_code.rs @@ -452,10 +452,10 @@ pub fn qwen_code_available() -> bool { /// Check if Qwen credentials exist. /// -/// Qwen Code stores session/credentials in `~/.qwen-code/` directory. +/// Qwen Code stores session/credentials in `~/.qwen/` directory. fn qwen_credentials_exist() -> bool { if let Some(home) = home_dir() { - let qwen_dir = home.join(".qwen-code"); + let qwen_dir = home.join(".qwen"); qwen_dir.exists() } else { false From 7592b83336afd4d743bde6ab0b120a17ad4f0fa2 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 12:38:32 +0100 Subject: [PATCH 32/41] fix(images): fix Telegram image pipeline - dual bug in vision model routing and ClaudeCode driver Root cause 1: Vision model swap (kernel.rs) changed the model to qwen-vl-plus but left the provider as claude-code, routing images to the wrong driver. Root cause 2: ClaudeCodeDriver.build_prompt() called text_content() which silently dropped all ContentBlock::Image and ContentBlock::ImageUrl blocks. Fix: - kernel.rs: vision model swap now also updates the provider - claude_code.rs: full image support via temp files passed with --files flag (handles base64, data URIs, and HTTP URLs) - All other drivers: ensure ImageUrl content blocks are handled - compactor.rs: handle ImageUrl in conversation compaction - bridge.rs: improved image dispatch reliability Closes #528 Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 28 ++-- crates/openfang-api/src/routes.rs | 1 + crates/openfang-channels/src/bridge.rs | 43 +++-- crates/openfang-kernel/src/kernel.rs | 28 ++++ crates/openfang-memory/src/session.rs | 3 + crates/openfang-runtime/src/compactor.rs | 3 + .../openfang-runtime/src/drivers/anthropic.rs | 6 + .../src/drivers/claude_code.rs | 157 +++++++++++++++++- crates/openfang-runtime/src/drivers/gemini.rs | 8 + crates/openfang-runtime/src/drivers/openai.rs | 7 + crates/openfang-types/src/config.rs | 5 + crates/openfang-types/src/message.rs | 7 + 12 files changed, 257 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73ba5baf3..6c7c32eb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3792,7 +3792,7 @@ dependencies = [ [[package]] name = "openfang-api" -version = "0.3.46" +version = "0.3.47" dependencies = [ "async-trait", "axum", @@ -3829,7 +3829,7 @@ dependencies = [ [[package]] name = "openfang-channels" -version = "0.3.46" +version = "0.3.47" dependencies = [ "async-trait", "axum", @@ -3861,7 +3861,7 @@ dependencies = [ [[package]] name = "openfang-cli" -version = "0.3.46" +version = "0.3.47" dependencies = [ "clap", "clap_complete", @@ -3888,7 +3888,7 @@ dependencies = [ [[package]] name = "openfang-desktop" -version = "0.3.46" +version = "0.3.47" dependencies = [ "axum", "open", @@ -3914,7 +3914,7 @@ dependencies = [ [[package]] name = "openfang-extensions" -version = "0.3.46" +version = "0.3.47" dependencies = [ "aes-gcm", "argon2", @@ -3942,7 +3942,7 @@ dependencies = [ [[package]] name = "openfang-hands" -version = "0.3.46" +version = "0.3.47" dependencies = [ "chrono", "dashmap", @@ -3959,7 +3959,7 @@ dependencies = [ [[package]] name = "openfang-kernel" -version = "0.3.46" +version = "0.3.47" dependencies = [ "async-trait", "chrono", @@ -3996,7 +3996,7 @@ dependencies = [ [[package]] name = "openfang-memory" -version = "0.3.46" +version = "0.3.47" dependencies = [ "async-trait", "chrono", @@ -4015,7 +4015,7 @@ dependencies = [ [[package]] name = "openfang-migrate" -version = "0.3.46" +version = "0.3.47" dependencies = [ "chrono", "dirs 6.0.0", @@ -4034,7 +4034,7 @@ dependencies = [ [[package]] name = "openfang-runtime" -version = "0.3.46" +version = "0.3.47" dependencies = [ "anyhow", "async-trait", @@ -4068,7 +4068,7 @@ dependencies = [ [[package]] name = "openfang-skills" -version = "0.3.46" +version = "0.3.47" dependencies = [ "chrono", "hex", @@ -4091,7 +4091,7 @@ dependencies = [ [[package]] name = "openfang-types" -version = "0.3.46" +version = "0.3.47" dependencies = [ "async-trait", "chrono", @@ -4110,7 +4110,7 @@ dependencies = [ [[package]] name = "openfang-wire" -version = "0.3.46" +version = "0.3.47" dependencies = [ "async-trait", "chrono", @@ -8773,7 +8773,7 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xtask" -version = "0.3.46" +version = "0.3.47" [[package]] name = "yoke" diff --git a/crates/openfang-api/src/routes.rs b/crates/openfang-api/src/routes.rs index d659d7169..223df0940 100644 --- a/crates/openfang-api/src/routes.rs +++ b/crates/openfang-api/src/routes.rs @@ -6852,6 +6852,7 @@ pub async fn set_provider_key( model: model_id, api_key_env: env_var.clone(), base_url: None, + vision_model: None, }; let mut guard = state .kernel diff --git a/crates/openfang-channels/src/bridge.rs b/crates/openfang-channels/src/bridge.rs index 3a8c3d68a..24e5bee23 100644 --- a/crates/openfang-channels/src/bridge.rs +++ b/crates/openfang-channels/src/bridge.rs @@ -528,25 +528,34 @@ async fn dispatch_message( return; } - // For images: download, base64 encode, and send as multimodal content blocks + // For images: build content blocks with the image URL for vision models. + // We pass the original URL rather than downloading + base64-encoding because + // many providers (DashScope/Qwen, OpenAI) prefer or require direct URLs. if let ChannelContent::Image { ref url, ref caption } = message.content { - let blocks = download_image_to_blocks(url, caption.as_deref()).await; - if blocks.iter().any(|b| matches!(b, ContentBlock::Image { .. })) { - // We have actual image data — send as structured blocks for vision - dispatch_with_blocks( - blocks, - message, - handle, - router, - adapter, - ct_str, - thread_id, - output_format, - ) - .await; - return; + let mut blocks = Vec::new(); + if let Some(cap) = caption { + if !cap.is_empty() { + blocks.push(ContentBlock::Text { + text: cap.clone(), + provider_metadata: None, + }); + } } - // Image download failed — fall through to text description below + blocks.push(ContentBlock::ImageUrl { + url: url.clone(), + }); + dispatch_with_blocks( + blocks, + message, + handle, + router, + adapter, + ct_str, + thread_id, + output_format, + ) + .await; + return; } let text = match &message.content { diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs index 0b1b1cbe2..0f56139fd 100644 --- a/crates/openfang-kernel/src/kernel.rs +++ b/crates/openfang-kernel/src/kernel.rs @@ -2262,6 +2262,34 @@ impl OpenFangKernel { } } + // If message contains images and a vision model is configured, swap to it. + // Many text models (e.g. qwen-plus) don't support image input — the vision + // model (e.g. qwen-vl-plus) handles multimodal content correctly. + if let Some(ref blocks) = content_blocks { + let has_images = blocks.iter().any(|b| { + matches!( + b, + openfang_types::message::ContentBlock::Image { .. } + | openfang_types::message::ContentBlock::ImageUrl { .. } + ) + }); + if has_images { + if let Some(ref vision_model) = self.config.default_model.vision_model { + info!( + agent = %manifest.name, + default_model = %manifest.model.model, + vision_model = %vision_model, + "Swapping to vision model for image content" + ); + manifest.model.model = vision_model.clone(); + // The vision model lives on the same provider as the default + // model. Without this swap, an agent using e.g. claude-code + // would try to send the image to the wrong driver. + manifest.model.provider = self.config.default_model.provider.clone(); + } + } + } + let driver = self.resolve_driver(&manifest)?; // Look up model's actual context window from the catalog diff --git a/crates/openfang-memory/src/session.rs b/crates/openfang-memory/src/session.rs index 74862c372..a7d1a83aa 100644 --- a/crates/openfang-memory/src/session.rs +++ b/crates/openfang-memory/src/session.rs @@ -584,6 +584,9 @@ impl SessionStore { ContentBlock::Image { media_type, .. } => { text_parts.push(format!("[image: {media_type}]")); } + ContentBlock::ImageUrl { ref url } => { + text_parts.push(format!("[image: {url}]")); + } ContentBlock::Thinking { thinking } => { text_parts.push(format!( "[thinking: {}]", diff --git a/crates/openfang-runtime/src/compactor.rs b/crates/openfang-runtime/src/compactor.rs index 855705469..e9e246a86 100644 --- a/crates/openfang-runtime/src/compactor.rs +++ b/crates/openfang-runtime/src/compactor.rs @@ -399,6 +399,9 @@ fn build_conversation_text(messages: &[Message], config: &CompactionConfig) -> S ContentBlock::Image { media_type, .. } => { conversation_text.push_str(&format!("[Image: {media_type}]\n\n")); } + ContentBlock::ImageUrl { url } => { + conversation_text.push_str(&format!("[Image: {url}]\n\n")); + } ContentBlock::Thinking { .. } => {} ContentBlock::Unknown => {} } diff --git a/crates/openfang-runtime/src/drivers/anthropic.rs b/crates/openfang-runtime/src/drivers/anthropic.rs index 857774e26..4d79c2a81 100644 --- a/crates/openfang-runtime/src/drivers/anthropic.rs +++ b/crates/openfang-runtime/src/drivers/anthropic.rs @@ -573,6 +573,12 @@ fn convert_message(msg: &Message) -> ApiMessage { data: data.clone(), }, }), + ContentBlock::ImageUrl { url } => { + // Anthropic requires base64; pass as text description for now. + Some(ApiContentBlock::Text { + text: format!("[Image: {url}]"), + }) + } ContentBlock::ToolUse { id, name, input, .. } => Some(ApiContentBlock::ToolUse { id: id.clone(), name: name.clone(), diff --git a/crates/openfang-runtime/src/drivers/claude_code.rs b/crates/openfang-runtime/src/drivers/claude_code.rs index 1cdfe3b44..a677512c1 100644 --- a/crates/openfang-runtime/src/drivers/claude_code.rs +++ b/crates/openfang-runtime/src/drivers/claude_code.rs @@ -7,8 +7,9 @@ use crate::llm_driver::{CompletionRequest, CompletionResponse, LlmDriver, LlmError, StreamEvent}; use async_trait::async_trait; -use openfang_types::message::{ContentBlock, Role, StopReason, TokenUsage}; +use openfang_types::message::{ContentBlock, MessageContent, Role, StopReason, TokenUsage}; use serde::Deserialize; +use std::path::PathBuf; use tokio::io::AsyncBufReadExt; use tracing::{debug, warn}; @@ -90,8 +91,12 @@ impl ClaudeCodeDriver { } /// Build a text prompt from the completion request messages. - fn build_prompt(request: &CompletionRequest) -> String { + /// + /// Image content blocks are represented as `[Attached image: ]` + /// placeholders — the actual image files are passed via `--files`. + fn build_prompt(request: &CompletionRequest, image_files: &[PathBuf]) -> String { let mut parts = Vec::new(); + let mut img_idx = 0; if let Some(ref sys) = request.system { parts.push(format!("[System]\n{sys}")); @@ -103,15 +108,135 @@ impl ClaudeCodeDriver { Role::Assistant => "Assistant", Role::System => "System", }; - let text = msg.content.text_content(); - if !text.is_empty() { - parts.push(format!("[{role_label}]\n{text}")); + + let mut msg_parts = Vec::new(); + + match &msg.content { + MessageContent::Text(s) => { + if !s.is_empty() { + msg_parts.push(s.clone()); + } + } + MessageContent::Blocks(blocks) => { + for block in blocks { + match block { + ContentBlock::Text { text, .. } => { + if !text.is_empty() { + msg_parts.push(text.clone()); + } + } + ContentBlock::Image { .. } | ContentBlock::ImageUrl { .. } => { + if img_idx < image_files.len() { + let fname = image_files[img_idx] + .file_name() + .map(|n| n.to_string_lossy().to_string()) + .unwrap_or_else(|| format!("image_{img_idx}")); + msg_parts.push(format!("[Attached image: {fname}]")); + img_idx += 1; + } + } + _ => {} + } + } + } + } + + if !msg_parts.is_empty() { + let combined = msg_parts.join("\n"); + parts.push(format!("[{role_label}]\n{combined}")); } } parts.join("\n\n") } + /// Extract image content blocks from messages and write them to temp files. + /// + /// Returns the list of temp file paths. The caller is responsible for + /// cleaning them up after the CLI finishes. + async fn extract_images_to_temp(request: &CompletionRequest) -> Vec { + use base64::Engine; + + let mut paths = Vec::new(); + let ts = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_millis()) + .unwrap_or(0); + + for msg in &request.messages { + if let MessageContent::Blocks(blocks) = &msg.content { + for (i, block) in blocks.iter().enumerate() { + match block { + ContentBlock::Image { media_type, data } => { + let ext = media_type + .strip_prefix("image/") + .unwrap_or("png"); + let path = PathBuf::from(format!( + "/tmp/openfang-img-{ts}-{i}.{ext}" + )); + if let Ok(bytes) = + base64::engine::general_purpose::STANDARD.decode(data) + { + if std::fs::write(&path, &bytes).is_ok() { + paths.push(path); + } + } + } + ContentBlock::ImageUrl { url } => { + // If it's a data URI, decode it; otherwise download + if let Some(rest) = url.strip_prefix("data:") { + // data:image/png;base64, + if let Some((meta, b64)) = rest.split_once(",") { + let ext = meta + .split(';') + .next() + .and_then(|m| m.strip_prefix("image/")) + .unwrap_or("png"); + let path = PathBuf::from(format!( + "/tmp/openfang-img-{ts}-{i}.{ext}" + )); + if let Ok(bytes) = + base64::engine::general_purpose::STANDARD.decode(b64) + { + if std::fs::write(&path, &bytes).is_ok() { + paths.push(path); + } + } + } + } else { + // HTTP(S) URL — try to download + let path = PathBuf::from(format!( + "/tmp/openfang-img-{ts}-{i}.jpg" + )); + match reqwest::get(url).await { + Ok(resp) => { + if let Ok(bytes) = resp.bytes().await { + if std::fs::write(&path, &bytes).is_ok() { + paths.push(path); + } + } + } + Err(e) => { + warn!(url = %url, error = %e, "Failed to download image for Claude CLI"); + } + } + } + } + _ => {} + } + } + } + } + paths + } + + /// Clean up temporary image files. + fn cleanup_temp_images(paths: &[PathBuf]) { + for p in paths { + let _ = std::fs::remove_file(p); + } + } + /// Map a model ID like "claude-code/opus" to CLI --model flag value. fn model_flag(model: &str) -> Option { let stripped = model @@ -196,7 +321,8 @@ impl LlmDriver for ClaudeCodeDriver { &self, request: CompletionRequest, ) -> Result { - let prompt = Self::build_prompt(&request); + let image_files = Self::extract_images_to_temp(&request).await; + let prompt = Self::build_prompt(&request, &image_files); let model_flag = Self::model_flag(&request.model); let mut cmd = tokio::process::Command::new(&self.cli_path); @@ -213,6 +339,11 @@ impl LlmDriver for ClaudeCodeDriver { cmd.arg("--model").arg(model); } + // Attach image files so the CLI can see them + for img_path in &image_files { + cmd.arg("--files").arg(img_path); + } + Self::apply_env_filter(&mut cmd); cmd.stdout(std::process::Stdio::piped()); @@ -230,6 +361,7 @@ impl LlmDriver for ClaudeCodeDriver { )))?; if !output.status.success() { + Self::cleanup_temp_images(&image_files); let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); let detail = if !stderr.is_empty() { &stderr } else { &stdout }; @@ -261,6 +393,7 @@ impl LlmDriver for ClaudeCodeDriver { }); } + Self::cleanup_temp_images(&image_files); let stdout = String::from_utf8_lossy(&output.stdout); // Try JSON parse first @@ -299,7 +432,8 @@ impl LlmDriver for ClaudeCodeDriver { request: CompletionRequest, tx: tokio::sync::mpsc::Sender, ) -> Result { - let prompt = Self::build_prompt(&request); + let image_files = Self::extract_images_to_temp(&request).await; + let prompt = Self::build_prompt(&request, &image_files); let model_flag = Self::model_flag(&request.model); let mut cmd = tokio::process::Command::new(&self.cli_path); @@ -317,6 +451,11 @@ impl LlmDriver for ClaudeCodeDriver { cmd.arg("--model").arg(model); } + // Attach image files so the CLI can see them + for img_path in &image_files { + cmd.arg("--files").arg(img_path); + } + Self::apply_env_filter(&mut cmd); cmd.stdout(std::process::Stdio::piped()); @@ -412,6 +551,8 @@ impl LlmDriver for ClaudeCodeDriver { .await .map_err(|e| LlmError::Http(format!("Claude CLI wait failed: {e}")))?; + Self::cleanup_temp_images(&image_files); + if !status.success() { warn!(code = ?status.code(), "Claude CLI exited with error"); } @@ -486,7 +627,7 @@ mod tests { thinking: None, }; - let prompt = ClaudeCodeDriver::build_prompt(&request); + let prompt = ClaudeCodeDriver::build_prompt(&request, &[]); assert!(prompt.contains("[System]")); assert!(prompt.contains("You are helpful.")); assert!(prompt.contains("[User]")); diff --git a/crates/openfang-runtime/src/drivers/gemini.rs b/crates/openfang-runtime/src/drivers/gemini.rs index f70a9efe0..651b6088e 100644 --- a/crates/openfang-runtime/src/drivers/gemini.rs +++ b/crates/openfang-runtime/src/drivers/gemini.rs @@ -291,6 +291,14 @@ fn convert_messages( }, }); } + ContentBlock::ImageUrl { url } => { + // Gemini supports fileData for URL-based images; + // fall back to a text description if not supported. + parts.push(GeminiPart::Text { + text: format!("[Image: {url}]"), + thought_signature: None, + }); + } ContentBlock::ToolResult { content, tool_name, .. } => { diff --git a/crates/openfang-runtime/src/drivers/openai.rs b/crates/openfang-runtime/src/drivers/openai.rs index 15a5a6657..c52e772cf 100644 --- a/crates/openfang-runtime/src/drivers/openai.rs +++ b/crates/openfang-runtime/src/drivers/openai.rs @@ -278,6 +278,13 @@ impl LlmDriver for OpenAIDriver { }, }); } + ContentBlock::ImageUrl { url } => { + parts.push(OaiContentPart::ImageUrl { + image_url: OaiImageUrl { + url: url.clone(), + }, + }); + } ContentBlock::Thinking { .. } => {} _ => {} } diff --git a/crates/openfang-types/src/config.rs b/crates/openfang-types/src/config.rs index 76c3a5107..811784008 100644 --- a/crates/openfang-types/src/config.rs +++ b/crates/openfang-types/src/config.rs @@ -1393,6 +1393,10 @@ pub struct DefaultModelConfig { pub api_key_env: String, /// Optional base URL override. pub base_url: Option, + /// Optional vision-capable model for image messages. + /// When set, agents receiving images will automatically use this model + /// instead of the default (which may not support vision). + pub vision_model: Option, } impl Default for DefaultModelConfig { @@ -1402,6 +1406,7 @@ impl Default for DefaultModelConfig { model: "claude-sonnet-4-20250514".to_string(), api_key_env: "ANTHROPIC_API_KEY".to_string(), base_url: None, + vision_model: None, } } } diff --git a/crates/openfang-types/src/message.rs b/crates/openfang-types/src/message.rs index 99be59571..72a608e88 100644 --- a/crates/openfang-types/src/message.rs +++ b/crates/openfang-types/src/message.rs @@ -56,6 +56,12 @@ pub enum ContentBlock { /// Base64-encoded image data. data: String, }, + /// A URL-referenced image (for providers like DashScope that prefer URLs over base64). + #[serde(rename = "image_url")] + ImageUrl { + /// The URL of the image. + url: String, + }, /// A tool use request from the assistant. #[serde(rename = "tool_use")] ToolUse { @@ -144,6 +150,7 @@ impl MessageContent { ContentBlock::Thinking { thinking } => thinking.len(), ContentBlock::ToolUse { .. } | ContentBlock::Image { .. } + | ContentBlock::ImageUrl { .. } | ContentBlock::Unknown => 0, }) .sum(), From 56180ec4c5d355273434805dea6bf23d6627f8c2 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 12:47:11 +0100 Subject: [PATCH 33/41] feat: add Telegram notifications to CI workflow Notify on build success/failure via Telegram Bot API using TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID secrets. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sync-build.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/sync-build.yml b/.github/workflows/sync-build.yml index 042beaaa7..4e09f9403 100644 --- a/.github/workflows/sync-build.yml +++ b/.github/workflows/sync-build.yml @@ -79,3 +79,19 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} repository: fliva/openfang readme-filepath: ./DOCKER_README.md + + - name: Notify Telegram (success) + if: success() && (github.event_name == 'push' || steps.check.outputs.new_release == 'true') + run: | + curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \ + -d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \ + -d parse_mode="Markdown" \ + -d text="✅ *OpenFang Build OK*%0A%0ATag: \`${{ steps.check.outputs.latest || 'custom' }}\`%0ACommit: \`${GITHUB_SHA::7}\`%0A[View run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" + + - name: Notify Telegram (failure) + if: failure() + run: | + curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \ + -d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \ + -d parse_mode="Markdown" \ + -d text="❌ *OpenFang Build FAILED*%0A%0ACommit: \`${GITHUB_SHA::7}\`%0A[View run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" From a3d499831e2e1360ed3a6c83a6e993a14c5baa6b Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 13:35:58 +0100 Subject: [PATCH 34/41] fix: skip vision model swap when current model already supports vision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check model catalog's supports_vision flag before swapping. Models like claude-opus-4-6 handle images natively — no need to swap to a separate vision model (which may use a different, unconfigured provider). Also warn when images arrive but no vision fallback is available. Co-Authored-By: Claude Opus 4.6 --- crates/openfang-kernel/src/kernel.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs index 0f56139fd..d25a2d401 100644 --- a/crates/openfang-kernel/src/kernel.rs +++ b/crates/openfang-kernel/src/kernel.rs @@ -2262,9 +2262,9 @@ impl OpenFangKernel { } } - // If message contains images and a vision model is configured, swap to it. - // Many text models (e.g. qwen-plus) don't support image input — the vision - // model (e.g. qwen-vl-plus) handles multimodal content correctly. + // If message contains images and the current model doesn't support vision, + // swap to the configured vision model. Models that already support vision + // (e.g. claude-opus-4-6) keep running — no swap needed. if let Some(ref blocks) = content_blocks { let has_images = blocks.iter().any(|b| { matches!( @@ -2274,7 +2274,20 @@ impl OpenFangKernel { ) }); if has_images { - if let Some(ref vision_model) = self.config.default_model.vision_model { + let current_supports_vision = self + .model_catalog + .read() + .ok() + .and_then(|cat| cat.find_model(&manifest.model.model).map(|m| m.supports_vision)) + .unwrap_or(false); + + if current_supports_vision { + info!( + agent = %manifest.name, + model = %manifest.model.model, + "Current model supports vision — skipping swap" + ); + } else if let Some(ref vision_model) = self.config.default_model.vision_model { info!( agent = %manifest.name, default_model = %manifest.model.model, @@ -2286,6 +2299,12 @@ impl OpenFangKernel { // model. Without this swap, an agent using e.g. claude-code // would try to send the image to the wrong driver. manifest.model.provider = self.config.default_model.provider.clone(); + } else { + warn!( + agent = %manifest.name, + model = %manifest.model.model, + "Image received but model lacks vision and no vision_model configured" + ); } } } From df0e9206e463f552d09cad91bccc421a2fa72c18 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 13:39:39 +0100 Subject: [PATCH 35/41] fix: vision model swap respects config priority over current model When vision_model is explicitly set in config, always use it (forced override). Only fall back to the current agent model when no vision_model is configured and the model supports vision natively. Co-Authored-By: Claude Opus 4.6 --- crates/openfang-kernel/src/kernel.rs | 56 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs index d25a2d401..2d5656f15 100644 --- a/crates/openfang-kernel/src/kernel.rs +++ b/crates/openfang-kernel/src/kernel.rs @@ -2262,9 +2262,10 @@ impl OpenFangKernel { } } - // If message contains images and the current model doesn't support vision, - // swap to the configured vision model. Models that already support vision - // (e.g. claude-opus-4-6) keep running — no swap needed. + // Vision model selection for image content. + // Priority: 1) explicit vision_model from config (forced override) + // 2) current agent model if it supports vision (no swap needed) + // 3) error — no vision capability available if let Some(ref blocks) = content_blocks { let has_images = blocks.iter().any(|b| { matches!( @@ -2274,37 +2275,38 @@ impl OpenFangKernel { ) }); if has_images { - let current_supports_vision = self - .model_catalog - .read() - .ok() - .and_then(|cat| cat.find_model(&manifest.model.model).map(|m| m.supports_vision)) - .unwrap_or(false); - - if current_supports_vision { + if let Some(ref vision_model) = self.config.default_model.vision_model { + // Explicit vision_model configured — always use it info!( agent = %manifest.name, - model = %manifest.model.model, - "Current model supports vision — skipping swap" - ); - } else if let Some(ref vision_model) = self.config.default_model.vision_model { - info!( - agent = %manifest.name, - default_model = %manifest.model.model, + current_model = %manifest.model.model, vision_model = %vision_model, - "Swapping to vision model for image content" + "Swapping to configured vision model for image content" ); manifest.model.model = vision_model.clone(); - // The vision model lives on the same provider as the default - // model. Without this swap, an agent using e.g. claude-code - // would try to send the image to the wrong driver. manifest.model.provider = self.config.default_model.provider.clone(); } else { - warn!( - agent = %manifest.name, - model = %manifest.model.model, - "Image received but model lacks vision and no vision_model configured" - ); + // No vision_model forced — check if current model handles vision + let current_supports_vision = self + .model_catalog + .read() + .ok() + .and_then(|cat| cat.find_model(&manifest.model.model).map(|m| m.supports_vision)) + .unwrap_or(false); + + if current_supports_vision { + info!( + agent = %manifest.name, + model = %manifest.model.model, + "Current model supports vision — no swap needed" + ); + } else { + warn!( + agent = %manifest.name, + model = %manifest.model.model, + "Image received but no vision_model configured and current model lacks vision support" + ); + } } } } From 2f9f711d07605fa38c0c3262b239d30cbe6a399d Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 14:05:44 +0100 Subject: [PATCH 36/41] fix: use --file instead of --files for Claude Code CLI The CLI option is --file (singular), not --files. Co-Authored-By: Claude Opus 4.6 --- crates/openfang-runtime/src/drivers/claude_code.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/openfang-runtime/src/drivers/claude_code.rs b/crates/openfang-runtime/src/drivers/claude_code.rs index 01ed2c46b..6b3efa488 100644 --- a/crates/openfang-runtime/src/drivers/claude_code.rs +++ b/crates/openfang-runtime/src/drivers/claude_code.rs @@ -93,7 +93,7 @@ impl ClaudeCodeDriver { /// Build a text prompt from the completion request messages. /// /// Image content blocks are represented as `[Attached image: ]` - /// placeholders — the actual image files are passed via `--files`. + /// placeholders — the actual image files are passed via `--file`. fn build_prompt(request: &CompletionRequest, image_files: &[PathBuf]) -> String { let mut parts = Vec::new(); let mut img_idx = 0; @@ -368,7 +368,7 @@ impl LlmDriver for ClaudeCodeDriver { // Attach image files so the CLI can see them for img_path in &image_files { - cmd.arg("--files").arg(img_path); + cmd.arg("--file").arg(img_path); } Self::apply_env_filter(&mut cmd); @@ -470,7 +470,7 @@ impl LlmDriver for ClaudeCodeDriver { // Attach image files so the CLI can see them for img_path in &image_files { - cmd.arg("--files").arg(img_path); + cmd.arg("--file").arg(img_path); } Self::apply_env_filter(&mut cmd); From 4e28cbd7efab257299ea1b4a345df28f7c253758 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 14:43:57 +0100 Subject: [PATCH 37/41] fix: use @path syntax for local images instead of --file flag The --file flag requires a session token for file downloads (Files API). Instead, embed @/tmp/image.jpg directly in the prompt text, which tells Claude Code CLI to read the local file natively. Co-Authored-By: Claude Opus 4.6 --- .../src/drivers/claude_code.rs | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/crates/openfang-runtime/src/drivers/claude_code.rs b/crates/openfang-runtime/src/drivers/claude_code.rs index 6b3efa488..cb47940d9 100644 --- a/crates/openfang-runtime/src/drivers/claude_code.rs +++ b/crates/openfang-runtime/src/drivers/claude_code.rs @@ -92,8 +92,8 @@ impl ClaudeCodeDriver { /// Build a text prompt from the completion request messages. /// - /// Image content blocks are represented as `[Attached image: ]` - /// placeholders — the actual image files are passed via `--file`. + /// Image content blocks are referenced using Claude Code's `@path` syntax, + /// which tells the CLI to read the local file directly. fn build_prompt(request: &CompletionRequest, image_files: &[PathBuf]) -> String { let mut parts = Vec::new(); let mut img_idx = 0; @@ -127,11 +127,8 @@ impl ClaudeCodeDriver { } ContentBlock::Image { .. } | ContentBlock::ImageUrl { .. } => { if img_idx < image_files.len() { - let fname = image_files[img_idx] - .file_name() - .map(|n| n.to_string_lossy().to_string()) - .unwrap_or_else(|| format!("image_{img_idx}")); - msg_parts.push(format!("[Attached image: {fname}]")); + let path = &image_files[img_idx]; + msg_parts.push(format!("@{}", path.display())); img_idx += 1; } } @@ -366,11 +363,6 @@ impl LlmDriver for ClaudeCodeDriver { let mut cmd = tokio::process::Command::new(&self.cli_path); cmd.args(&args); - // Attach image files so the CLI can see them - for img_path in &image_files { - cmd.arg("--file").arg(img_path); - } - Self::apply_env_filter(&mut cmd); cmd.stdout(std::process::Stdio::piped()); @@ -468,11 +460,6 @@ impl LlmDriver for ClaudeCodeDriver { let mut cmd = tokio::process::Command::new(&self.cli_path); cmd.args(&args); - // Attach image files so the CLI can see them - for img_path in &image_files { - cmd.arg("--file").arg(img_path); - } - Self::apply_env_filter(&mut cmd); cmd.stdout(std::process::Stdio::piped()); From d6ba3cb2f52429402cc94a18aa7da6e26517b2fc Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 15:15:27 +0100 Subject: [PATCH 38/41] feat: add Telegram notification on build start Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sync-build.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/sync-build.yml b/.github/workflows/sync-build.yml index 4e09f9403..66b59d72b 100644 --- a/.github/workflows/sync-build.yml +++ b/.github/workflows/sync-build.yml @@ -17,6 +17,13 @@ jobs: fetch-depth: 0 token: ${{ secrets.PAT_TOKEN }} + - name: Notify Telegram (start) + run: | + curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \ + -d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \ + -d parse_mode="Markdown" \ + -d text="🚀 *OpenFang Build Started*%0A%0ATrigger: \`${{ github.event_name }}\`%0ACommit: \`${GITHUB_SHA::7}\`%0A[View run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" + - name: Fetch upstream tags if: github.event_name != 'push' run: | From 18fe89eaacdea38ec3b22f12d811592d465b0e8c Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 16:43:07 +0100 Subject: [PATCH 39/41] feat: add lifecycle_reactions config flag to disable emoji reactions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `lifecycle_reactions` boolean to ChannelOverrides (default: true). When set to false, suppresses the ⏳→🤔→✅/❌ emoji reactions on incoming messages. Applies to both text and multimodal dispatch paths. Co-Authored-By: Claude Opus 4.6 --- crates/openfang-channels/src/bridge.rs | 33 ++++++++++++++++++++------ crates/openfang-types/src/config.rs | 24 ++++++++++++++++++- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/crates/openfang-channels/src/bridge.rs b/crates/openfang-channels/src/bridge.rs index 24e5bee23..1bb8e6258 100644 --- a/crates/openfang-channels/src/bridge.rs +++ b/crates/openfang-channels/src/bridge.rs @@ -765,14 +765,22 @@ async fn dispatch_message( let _ = adapter.send_typing(&message.sender).await; // Lifecycle reaction: ⏳ Queued → 🤔 Thinking → ✅ Done / ❌ Error + let lifecycle_reactions = overrides + .as_ref() + .map(|o| o.lifecycle_reactions) + .unwrap_or(true); let msg_id = &message.platform_message_id; - send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Queued).await; - send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Thinking).await; + if lifecycle_reactions { + send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Queued).await; + send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Thinking).await; + } // Send to agent and relay response match handle.send_message(agent_id, &text).await { Ok(response) => { - send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Done).await; + if lifecycle_reactions { + send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Done).await; + } send_response(adapter, &message.sender, response, thread_id, output_format).await; handle .record_delivery(agent_id, ct_str, &message.sender.platform_id, true, None, thread_id) @@ -991,20 +999,31 @@ async fn dispatch_with_blocks( let _ = adapter.send_typing(&message.sender).await; // Lifecycle reaction: ⏳ Queued → 🤔 Thinking → ✅ Done / ❌ Error + let lifecycle_reactions = handle + .channel_overrides(ct_str) + .await + .map(|o| o.lifecycle_reactions) + .unwrap_or(true); let msg_id = &message.platform_message_id; - send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Queued).await; - send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Thinking).await; + if lifecycle_reactions { + send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Queued).await; + send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Thinking).await; + } match handle.send_message_with_blocks(agent_id, blocks).await { Ok(response) => { - send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Done).await; + if lifecycle_reactions { + send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Done).await; + } send_response(adapter, &message.sender, response, thread_id, output_format).await; handle .record_delivery(agent_id, ct_str, &message.sender.platform_id, true, None, thread_id) .await; } Err(e) => { - send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Error).await; + if lifecycle_reactions { + send_lifecycle_reaction(adapter, &message.sender, msg_id, AgentPhase::Error).await; + } warn!("Agent error for {agent_id}: {e}"); let err_msg = format!("Agent error: {e}"); send_response( diff --git a/crates/openfang-types/src/config.rs b/crates/openfang-types/src/config.rs index 811784008..3cd103d99 100644 --- a/crates/openfang-types/src/config.rs +++ b/crates/openfang-types/src/config.rs @@ -68,7 +68,7 @@ pub enum OutputFormat { } /// Per-channel behavior overrides. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct ChannelOverrides { /// Model override (uses agent's default if None). @@ -89,6 +89,27 @@ pub struct ChannelOverrides { pub usage_footer: Option, /// Typing indicator mode override. pub typing_mode: Option, + /// Enable lifecycle emoji reactions on messages (⏳→🤔→✅/❌). + /// Defaults to true; set to false to disable. + #[serde(default = "default_true")] + pub lifecycle_reactions: bool, +} + +impl Default for ChannelOverrides { + fn default() -> Self { + Self { + model: None, + system_prompt: None, + dm_policy: DmPolicy::default(), + group_policy: GroupPolicy::default(), + rate_limit_per_user: 0, + threading: false, + output_format: None, + usage_footer: None, + typing_mode: None, + lifecycle_reactions: true, + } + } } /// Controls what usage info appears in response footers. @@ -3600,6 +3621,7 @@ mod tests { assert!(!ov.threading); assert!(ov.output_format.is_none()); assert!(ov.model.is_none()); + assert!(ov.lifecycle_reactions); } #[test] From 0573e12d6406c6c4bb77e07dadb3721df9f6ec74 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Thu, 12 Mar 2026 16:51:37 +0100 Subject: [PATCH 40/41] feat(telegram): add reply_to_message context for quoted messages Extract reply_to_message metadata from Telegram updates (text, caption, sender, message_id) and prepend it as contextual prefix when dispatching to agents. Supports both text and image message paths. Co-Authored-By: Claude Opus 4.6 --- crates/openfang-channels/src/bridge.rs | 36 ++++++++++++++++++++++++ crates/openfang-channels/src/telegram.rs | 27 ++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/crates/openfang-channels/src/bridge.rs b/crates/openfang-channels/src/bridge.rs index 1bb8e6258..19d9598cf 100644 --- a/crates/openfang-channels/src/bridge.rs +++ b/crates/openfang-channels/src/bridge.rs @@ -434,6 +434,29 @@ async fn send_lifecycle_reaction( let _ = adapter.send_reaction(user, message_id, &reaction).await; } +/// Build a contextual prefix when the user is replying to a previous message. +/// +/// Returns `Some("[Replying to : ]\n\n")` if reply metadata exists. +fn build_reply_context(metadata: &std::collections::HashMap) -> Option { + // Need at least some quoted content + let quoted = metadata + .get("reply_to_text") + .and_then(|v| v.as_str()) + .or_else(|| metadata.get("reply_to_caption").and_then(|v| v.as_str())); + let quoted = quoted?; + let sender = metadata + .get("reply_to_sender") + .and_then(|v| v.as_str()) + .unwrap_or("Unknown"); + // Truncate very long quoted messages to keep context manageable + let truncated = if quoted.len() > 500 { + format!("{}…", "ed[..500]) + } else { + quoted.to_string() + }; + Some(format!("[Replying to {sender}: {truncated}]\n\n")) +} + /// Dispatch a single incoming message — handles bot commands or routes to an agent. /// /// Applies per-channel policies (DM/group filtering, rate limiting, formatting, threading). @@ -533,6 +556,13 @@ async fn dispatch_message( // many providers (DashScope/Qwen, OpenAI) prefer or require direct URLs. if let ChannelContent::Image { ref url, ref caption } = message.content { let mut blocks = Vec::new(); + // Prepend reply context if this is a reply to a previous message + if let Some(reply_ctx) = build_reply_context(&message.metadata) { + blocks.push(ContentBlock::Text { + text: reply_ctx, + provider_metadata: None, + }); + } if let Some(cap) = caption { if !cap.is_empty() { blocks.push(ContentBlock::Text { @@ -582,6 +612,12 @@ async fn dispatch_message( } }; + // Prepend reply context if this is a reply to a previous message + let text = match build_reply_context(&message.metadata) { + Some(ctx) => format!("{ctx}{text}"), + None => text, + }; + // Check if it's a slash command embedded in text (e.g. "/agents") if text.starts_with('/') { let parts: Vec<&str> = text.splitn(2, ' ').collect(); diff --git a/crates/openfang-channels/src/telegram.rs b/crates/openfang-channels/src/telegram.rs index f03829f77..a4eb1baed 100644 --- a/crates/openfang-channels/src/telegram.rs +++ b/crates/openfang-channels/src/telegram.rs @@ -825,6 +825,33 @@ async fn parse_telegram_update( } } + // Extract reply_to_message context so agents can see what the user is replying to. + if let Some(reply_msg) = message.get("reply_to_message") { + if let Some(reply_text) = reply_msg["text"].as_str() { + metadata.insert("reply_to_text".to_string(), serde_json::json!(reply_text)); + } + if let Some(reply_caption) = reply_msg["caption"].as_str() { + metadata.insert( + "reply_to_caption".to_string(), + serde_json::json!(reply_caption), + ); + } + if let Some(reply_id) = reply_msg["message_id"].as_i64() { + metadata.insert( + "reply_to_message_id".to_string(), + serde_json::json!(reply_id), + ); + } + // Sender of the quoted message + let reply_sender = reply_msg["from"]["first_name"] + .as_str() + .unwrap_or("Unknown"); + metadata.insert( + "reply_to_sender".to_string(), + serde_json::json!(reply_sender), + ); + } + Some(ChannelMessage { channel: ChannelType::Telegram, platform_message_id: message_id.to_string(), From 30aa7f5e7de56def53dbb11a63d4859bf40242d9 Mon Sep 17 00:00:00 2001 From: Federico Liva Date: Sat, 14 Mar 2026 13:01:19 +0100 Subject: [PATCH 41/41] feat(whatsapp): propagate sender identity metadata to agent context WhatsApp gateway sends sender metadata (phone number, display name) but the API was silently discarding it because MessageRequest had no metadata field. This caused agents to treat all WhatsApp users as their owner. Changes: - Add optional metadata field to MessageRequest (types.rs) - Parse metadata into SenderContext and propagate through kernel (routes.rs, kernel.rs) - Inject sender identity into agent system prompt (prompt_builder.rs) - Add SenderContext struct to message types (message.rs) - Activate is_allowed() filter with open-by-default behavior (whatsapp.rs) - Add allowed_users check in API routes (open mode when list is empty) Co-Authored-By: Claude Opus 4.6 --- crates/openfang-api/src/routes.rs | 37 +++++++++++++++++- crates/openfang-api/src/types.rs | 9 +++++ crates/openfang-channels/src/whatsapp.rs | 4 +- crates/openfang-kernel/src/kernel.rs | 38 +++++++++++++++++-- crates/openfang-runtime/src/prompt_builder.rs | 20 +++++++++- crates/openfang-types/src/message.rs | 14 +++++++ 6 files changed, 114 insertions(+), 8 deletions(-) diff --git a/crates/openfang-api/src/routes.rs b/crates/openfang-api/src/routes.rs index 223df0940..2241e93f7 100644 --- a/crates/openfang-api/src/routes.rs +++ b/crates/openfang-api/src/routes.rs @@ -358,10 +358,45 @@ pub async fn send_message( } } + // Convert metadata from the gateway into a SenderContext for the kernel + let sender_context = req.metadata.as_ref().map(|meta| { + openfang_types::message::SenderContext { + channel: meta.get("channel").and_then(|v| v.as_str().map(String::from)), + sender_id: meta.get("sender").and_then(|v| v.as_str().map(String::from)), + sender_name: meta.get("sender_name").and_then(|v| v.as_str().map(String::from)), + } + }); + + // SECURITY: Check allowed_users for channel-based messages (WhatsApp, etc.) + // If allowed_users is empty, all senders are permitted (open mode). + if let Some(ref ctx) = sender_context { + if let Some(ref sender_id) = ctx.sender_id { + if let Some(ref channel) = ctx.channel { + let channels_config = state.channels_config.read().await; + let blocked = match channel.as_str() { + "whatsapp" => channels_config.whatsapp.as_ref().map_or(false, |wa| { + !wa.allowed_users.is_empty() + && !wa.allowed_users.iter().any(|u| u == sender_id) + }), + _ => false, + }; + if blocked { + tracing::warn!( + "Rejected message from unlisted {channel} user {sender_id}" + ); + return ( + StatusCode::FORBIDDEN, + Json(serde_json::json!({"error": "Sender not in allowed_users list"})), + ); + } + } + } + } + let kernel_handle: Arc = state.kernel.clone() as Arc; match state .kernel - .send_message_with_handle(agent_id, &req.message, Some(kernel_handle)) + .send_message_with_handle_and_blocks(agent_id, &req.message, Some(kernel_handle), None, sender_context) .await { Ok(result) => { diff --git a/crates/openfang-api/src/types.rs b/crates/openfang-api/src/types.rs index 80b71140a..b16e19199 100644 --- a/crates/openfang-api/src/types.rs +++ b/crates/openfang-api/src/types.rs @@ -42,6 +42,15 @@ pub struct MessageRequest { /// Optional file attachments (uploaded via /upload endpoint). #[serde(default)] pub attachments: Vec, + /// Optional channel metadata (sender identity, channel type). + /// + /// Used by external gateways (e.g. WhatsApp) to forward sender information + /// so the agent knows who is writing. Expected keys: + /// - `channel`: channel name (e.g. "whatsapp", "telegram") + /// - `sender`: platform-specific sender ID (e.g. phone number) + /// - `sender_name`: human-readable sender name + #[serde(default)] + pub metadata: Option>, } /// Response from sending a message. diff --git a/crates/openfang-channels/src/whatsapp.rs b/crates/openfang-channels/src/whatsapp.rs index 82ad5840d..b8508c3c3 100644 --- a/crates/openfang-channels/src/whatsapp.rs +++ b/crates/openfang-channels/src/whatsapp.rs @@ -162,8 +162,8 @@ impl WhatsAppAdapter { } /// Check if a phone number is allowed. - #[allow(dead_code)] - fn is_allowed(&self, phone: &str) -> bool { + /// Returns true if allowed_users is empty (open mode) or phone is in the list. + pub fn is_allowed(&self, phone: &str) -> bool { self.allowed_users.is_empty() || self.allowed_users.iter().any(|u| u == phone) } diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs index e70a7d6c4..e0186ecce 100644 --- a/crates/openfang-kernel/src/kernel.rs +++ b/crates/openfang-kernel/src/kernel.rs @@ -1381,6 +1381,27 @@ impl OpenFangKernel { .await } + /// Send a message with channel sender context (from external gateways like WhatsApp). + /// + /// The sender context is injected into the agent's system prompt so the agent + /// can identify who is writing and apply appropriate privacy rules. + pub async fn send_message_with_sender_context( + &self, + agent_id: AgentId, + message: &str, + sender_context: openfang_types::message::SenderContext, + kernel_handle: Option>, + ) -> KernelResult { + self.send_message_with_handle_and_blocks( + agent_id, + message, + kernel_handle, + None, + Some(sender_context), + ) + .await + } + /// Send a multimodal message (text + images) to an agent and get a response. /// /// Used by channel bridges when a user sends a photo — the image is downloaded, @@ -1396,7 +1417,7 @@ impl OpenFangKernel { .get() .and_then(|w| w.upgrade()) .map(|arc| arc as Arc); - self.send_message_with_handle_and_blocks(agent_id, message, handle, Some(blocks)) + self.send_message_with_handle_and_blocks(agent_id, message, handle, Some(blocks), None) .await } @@ -1407,7 +1428,7 @@ impl OpenFangKernel { message: &str, kernel_handle: Option>, ) -> KernelResult { - self.send_message_with_handle_and_blocks(agent_id, message, kernel_handle, None) + self.send_message_with_handle_and_blocks(agent_id, message, kernel_handle, None, None) .await } @@ -1417,6 +1438,9 @@ impl OpenFangKernel { /// multimodal content (text + images) instead of just a text string. This /// enables vision models to process images sent from channels like Telegram. /// + /// When `sender_context` is `Some`, the sender identity is injected into the + /// agent's system prompt so it can distinguish between its owner and other users. + /// /// Per-agent locking ensures that concurrent messages for the same agent /// are serialized (preventing session corruption), while messages for /// different agents run in parallel. @@ -1426,6 +1450,7 @@ impl OpenFangKernel { message: &str, kernel_handle: Option>, content_blocks: Option>, + sender_context: Option, ) -> KernelResult { // Acquire per-agent lock to serialize concurrent messages for the same agent. // This prevents session corruption when multiple messages arrive in quick @@ -1455,7 +1480,7 @@ impl OpenFangKernel { self.execute_python_agent(&entry, agent_id, message).await } else { // Default: LLM agent loop (builtin:chat or any unrecognized module) - self.execute_llm_agent(&entry, agent_id, message, kernel_handle, content_blocks) + self.execute_llm_agent(&entry, agent_id, message, kernel_handle, content_blocks, sender_context) .await }; @@ -1698,6 +1723,8 @@ impl OpenFangKernel { .and_then(|(s, _)| s), user_name, channel_type: None, + sender_id: None, + sender_name: None, is_subagent: manifest .metadata .get("is_subagent") @@ -2064,6 +2091,7 @@ impl OpenFangKernel { message: &str, kernel_handle: Option>, content_blocks: Option>, + sender_context: Option, ) -> KernelResult { // Check metering quota before starting self.metering @@ -2168,7 +2196,9 @@ impl OpenFangKernel { .ok() .and_then(|(s, _)| s), user_name, - channel_type: None, + channel_type: sender_context.as_ref().and_then(|sc| sc.channel.clone()), + sender_id: sender_context.as_ref().and_then(|sc| sc.sender_id.clone()), + sender_name: sender_context.as_ref().and_then(|sc| sc.sender_name.clone()), is_subagent: manifest .metadata .get("is_subagent") diff --git a/crates/openfang-runtime/src/prompt_builder.rs b/crates/openfang-runtime/src/prompt_builder.rs index e0a8bd2a6..91b8a7b3b 100644 --- a/crates/openfang-runtime/src/prompt_builder.rs +++ b/crates/openfang-runtime/src/prompt_builder.rs @@ -37,6 +37,10 @@ pub struct PromptContext { pub user_name: Option, /// Channel type (telegram, discord, web, etc.). pub channel_type: Option, + /// Platform-specific sender ID (e.g. phone number) — from channel gateway metadata. + pub sender_id: Option, + /// Human-readable sender display name — from channel gateway metadata. + pub sender_name: Option, /// Whether this agent was spawned as a subagent. pub is_subagent: bool, /// Whether this agent has autonomous config. @@ -144,7 +148,21 @@ pub fn build_system_prompt(ctx: &PromptContext) -> String { // Section 9 — Channel Awareness (skip for subagents) if !ctx.is_subagent { if let Some(ref channel) = ctx.channel_type { - sections.push(build_channel_section(channel)); + let mut section = build_channel_section(channel); + // Append sender identity when available (from channel gateway metadata) + if ctx.sender_id.is_some() || ctx.sender_name.is_some() { + section.push_str("\n\n### Current Message Sender\n"); + if let Some(ref name) = ctx.sender_name { + section.push_str(&format!("- **Name**: {name}\n")); + } + if let Some(ref id) = ctx.sender_id { + section.push_str(&format!("- **Platform ID**: {id}\n")); + } + section.push_str("\nIMPORTANT: Check this sender identity against your USER.md to determine \ + if this is your owner/master or someone else. If it is NOT your owner, \ + read and follow PRIVACY-RULES.md before responding."); + } + sections.push(section); } } diff --git a/crates/openfang-types/src/message.rs b/crates/openfang-types/src/message.rs index 72a608e88..ac8fcd9bd 100644 --- a/crates/openfang-types/src/message.rs +++ b/crates/openfang-types/src/message.rs @@ -2,6 +2,20 @@ use serde::{Deserialize, Serialize}; +/// Sender context forwarded from channel gateways (e.g. WhatsApp, Telegram). +/// +/// Carries the identity of the person who sent the message so the agent +/// can distinguish between its owner and other users. +#[derive(Debug, Clone, Default)] +pub struct SenderContext { + /// Channel name (e.g. "whatsapp", "telegram"). + pub channel: Option, + /// Platform-specific sender ID (e.g. phone number, Telegram user ID). + pub sender_id: Option, + /// Human-readable sender display name. + pub sender_name: Option, +} + /// A message in an LLM conversation. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message {