Skip to content

agentboot: parse tool results, isolate child sessions, capture stderr… #1006

agentboot: parse tool results, isolate child sessions, capture stderr…

agentboot: parse tool results, isolate child sessions, capture stderr… #1006

Workflow file for this run

name: Github Release
on:
push:
tags:
- 'v*'
branches:
- 'ci/**'
- 'main'
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag (e.g., v0.20250101.0)'
required: true
type: string
default: ''
build_gui:
description: 'Build GUI version'
required: false
type: boolean
default: false
env:
GO_VERSION: '1.26'
NODE_VERSION: '24'
jobs:
setup:
runs-on: ubuntu-latest
outputs:
build_gui: ${{ steps.set_vars.outputs.build_gui }}
release_tag: ${{ steps.set_vars.outputs.release_tag }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: true
- name: Set variables
id: set_vars
shell: bash
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "release_tag=${{ github.event.inputs.release_tag }}" >> $GITHUB_OUTPUT
echo "build_gui=${{ github.event.inputs.build_gui }}" >> $GITHUB_OUTPUT
elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "release_tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "build_gui=false" >> $GITHUB_OUTPUT
else
# CI push to branch - use a dev version
echo "release_tag=dev-${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT
echo "build_gui=false" >> $GITHUB_OUTPUT
fi
build-frontend:
needs: setup
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache pnpm modules
uses: actions/cache@v4
with:
path: |
~/.pnpm-store
frontend/node_modules
key: ${{ runner.os }}-pnpm-${{ hashFiles('frontend/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Install pnpm
run: corepack enable
- name: Install frontend dependencies
run: |
pnpm approve-builds --all
pnpm install --frozen-lockfile
working-directory: ./frontend
- name: Run codegen
run: pnpm gen:api
working-directory: ./frontend
- name: Type check (non-blocking)
# Legacy type errors exist; report but don't fail the release build.
continue-on-error: true
run: |
pnpm typecheck || echo "::warning::TypeScript errors found (non-blocking, see step log)"
working-directory: ./frontend
- name: Build frontend
run: |
pnpm build && mkdir -p ../internal/web/dist && cp -R dist/* ../internal/web/dist/
working-directory: ./frontend
- name: Upload frontend artifact
uses: actions/upload-artifact@v4
with:
name: frontend-dist
path: internal/web/dist/
retention-days: 7
build-cli:
needs: [setup, build-frontend]
runs-on: ${{ matrix.runs-on }}
strategy:
matrix:
include:
- goos: linux
goarch: amd64
suffix: linux-amd64
runs-on: ubuntu-22.04
- goos: linux
goarch: arm64
suffix: linux-arm64
runs-on: ubuntu-22.04
cross_compile: true
- goos: darwin
goarch: amd64
suffix: macos-amd64
runs-on: macos-15-intel
- goos: darwin
goarch: arm64
suffix: macos-arm64
runs-on: macos-latest
- goos: windows
goarch: amd64
suffix: windows-amd64
exe: .exe
runs-on: windows-latest
- goos: windows
goarch: arm64
suffix: windows-arm64
exe: .exe
runs-on: ubuntu-22.04
cross_compile: true
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
submodules: true
- name: Set up tag environment
shell: bash
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "RELEASE_TAG=${{ github.event.inputs.release_tag }}" >> $GITHUB_ENV
echo "TAG_EXISTS=$(git tag -l "${{ github.event.inputs.release_tag }}" | wc -l)" >> $GITHUB_ENV
elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
echo "TAG_EXISTS=1" >> $GITHUB_ENV
else
# CI push to branch
echo "RELEASE_TAG=${{ needs.setup.outputs.release_tag }}" >> $GITHUB_ENV
echo "TAG_EXISTS=0" >> $GITHUB_ENV
fi
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Download frontend artifact
uses: actions/download-artifact@v4
with:
name: frontend-dist
path: internal/web/dist/
- name: Install musl toolchain (for amd64 static linking)
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
run: |
sudo apt-get update -qq || echo "done"
sudo apt-get install -y -qq musl-tools musl-dev || echo "done"
- name: Install zig (for arm64 cross-compilation)
if: matrix.cross_compile == true
run: |
wget -q https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz
tar -xf zig-linux-x86_64-0.13.0.tar.xz
echo "$PWD/zig-linux-x86_64-0.13.0" >> $GITHUB_PATH
- name: Build Go binary (${{ matrix.suffix }})
shell: bash
run: |
# Create build directory
mkdir -p dist
# Remove dev-only files before embedding
rm -f internal/web/dist/stats.html
rm -f internal/web/dist/mockServiceWorker.js
# Get build info
VERSION="${{ env.RELEASE_TAG }}"
GIT_COMMIT="$(git rev-parse HEAD 2>/dev/null || echo 'unknown')"
BUILD_TIME="$(date -u '+%Y-%m-%d-%H-%M-%S' 2>/dev/null || echo 'unknown')"
GO_VERSION="$(go version | awk '{print $3}' | sed 's/go//' 2>/dev/null || echo 'unknown')"
PLATFORM="${{ matrix.goos }}/${{ matrix.goarch }}"
# Build with optimized ldflags
# -s: strip symbol table
# -w: strip DWARF debug info
# -X: for metadata
LDFLAGS="-s -w"
LDFLAGS+=" -X main.version=${VERSION}"
LDFLAGS+=" -X main.gitCommit=${GIT_COMMIT}"
LDFLAGS+=" -X main.buildTime=${BUILD_TIME}"
LDFLAGS+=" -X main.goVersion=${GO_VERSION}"
LDFLAGS+=" -X main.platform=${PLATFORM}"
# Configure build flags based on platform
BUILD_TAGS=""
LDFLAGS_EXT=""
if [ "${{ matrix.goos }}" == "linux" ]; then
if [ "${{ matrix.goarch }}" == "amd64" ]; then
# Static linking with musl for Linux amd64
BUILD_TAGS="-tags 'sqlite_omit_load_extension'"
LDFLAGS_EXT="-linkmode external -extldflags \"-static\""
CC=musl-gcc
export CC
elif [ "${{ matrix.goarch }}" == "arm64" ]; then
# Use zig for cross-compilation with musl (static)
CC="zig cc -target aarch64-linux-musl"
CXX="zig c++ -target aarch64-linux-musl"
export CC CXX
BUILD_TAGS="-tags 'sqlite_omit_load_extension'"
LDFLAGS_EXT="-linkmode external -extldflags \"-static\""
fi
elif [ "${{ matrix.goos }}" == "windows" ] && [ "${{ matrix.goarch }}" == "arm64" ]; then
# Cross-compile Windows on ARM (e.g. Snapdragon) via zig.
# Link statically so the bare .exe carries no mingw runtime DLL
# dependency (libwinpthread/libssp), matching the linux-arm64 leg.
CC="zig cc -target aarch64-windows-gnu"
CXX="zig c++ -target aarch64-windows-gnu"
export CC CXX
BUILD_TAGS="-tags 'sqlite_omit_load_extension'"
LDFLAGS_EXT="-linkmode external -extldflags \"-static\""
fi
# Build with optimizations
CGO_ENABLED=1 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \
go build \
${BUILD_TAGS} \
-ldflags="${LDFLAGS} ${LDFLAGS_EXT}" \
-trimpath \
-o ./dist/tingly-box${{ matrix.exe }} \
./cli/tingly-box
# Make binary executable for non-Windows
[ "${{ matrix.goos }}" != "windows" ] && chmod +x ./dist/tingly-box${{ matrix.exe }} || true
# UPX is Linux-only. Windows is deliberately excluded: UPX-packed,
# unsigned .exe files are prime targets for Windows Defender / AV
# heuristic false positives (e.g. Wacatac), which quarantine or lock the
# binary on user machines and surface as "This app can't run on your PC".
# macOS is excluded because UPX breaks Mach-O signing/notarization.
- name: Install UPX 5.1.1
if: matrix.goos == 'linux'
shell: bash
run: |
wget -q https://github.com/upx/upx/releases/download/v5.1.1/upx-5.1.1-amd64_linux.tar.xz
tar -xf upx-5.1.1-amd64_linux.tar.xz
export PATH="$PWD/upx-5.1.1-amd64_linux:$PATH"
- name: Compress binary with UPX
if: matrix.goos == 'linux'
shell: bash
run: |
if command -v upx &> /dev/null || [ -f "./upx-5.1.1-amd64_linux/upx" ]; then
UPX_CMD="upx"
[ -f "./upx-5.1.1-amd64_linux/upx" ] && UPX_CMD="./upx-5.1.1-amd64_linux/upx"
# Use best compression with LZMA (slower but smaller)
$UPX_CMD --best --lzma ./dist/tingly-box${{ matrix.exe }} || \
$UPX_CMD --best ./dist/tingly-box${{ matrix.exe }}
else
echo "UPX not available, skipping compression"
fi
- name: Upload build artifact (${{ matrix.suffix }})
uses: actions/upload-artifact@v4
with:
name: cli-${{ matrix.suffix }}
path: ./dist/tingly-box${{ matrix.exe }}
retention-days: 7
build-gui:
needs: setup
if: needs.setup.outputs.build_gui == 'true'
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
# macOS builds
- os: macos-latest
platform: macos
arch: arm64
goos: darwin
goarch: arm64
# Windows builds
- os: windows-latest
platform: windows
arch: amd64
goos: windows
goarch: amd64
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: true
- name: Set up tag environment
shell: bash
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "RELEASE_TAG=${{ github.event.inputs.release_tag }}" >> $GITHUB_ENV
echo "TAG_EXISTS=$(git tag -l "${{ github.event.inputs.release_tag }}" | wc -l)" >> $GITHUB_ENV
elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
echo "TAG_EXISTS=1" >> $GITHUB_ENV
else
# CI push to branch
echo "RELEASE_TAG=${{ needs.setup.outputs.release_tag }}" >> $GITHUB_ENV
echo "TAG_EXISTS=0" >> $GITHUB_ENV
fi
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: ${{ env.NODE_VERSION }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ matrix.goarch }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.goarch }}-
${{ runner.os }}-go-
- name: Cache pnpm modules
uses: actions/cache@v4
with:
path: |
~/.pnpm-store
frontend/node_modules
key: ${{ runner.os }}-pnpm-${{ matrix.goarch }}-${{ hashFiles('frontend/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-${{ matrix.goarch }}-
${{ runner.os }}-pnpm-
- name: Install go-task
run: go install github.com/go-task/task/v3/cmd/task@latest
- name: Install wails3
# Keep in lockstep with the library version in gui/wails3/go.mod
run: go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.12
- name: Install pnpm
run: corepack enable
- name: Install frontend dependencies
shell: bash
run: |
pnpm approve-builds --all
pnpm install --frozen-lockfile
working-directory: ./frontend
- name: Run codegen
run: task codegen
- name: Install NSIS (Windows only)
if: matrix.platform == 'windows'
run: choco install nsis -y
- name: Build Go binary (macOS)
if: matrix.platform == 'macos'
run: |
task darwin:package VERSION="${RELEASE_TAG}"
mkdir -p dist
cd bin
ls -la .
zip -r ../dist/tingly-box-gui-macos-arm64.zip TinglyBox.app
cd ..
cd dist
ls -la .
- name: Build Go binary (Windows)
if: matrix.platform == 'windows'
shell: pwsh
run: |
task windows:build PRODUCTION=true VERSION="$env:RELEASE_TAG"
New-Item -ItemType Directory -Force -Path dist
cd bin
Get-ChildItem
Compress-Archive -Path tingly-box.exe -DestinationPath ../dist/tingly-box-gui-windows-amd64.zip
cd ..
cd dist
Get-ChildItem
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: gui-${{ matrix.platform }}-${{ matrix.arch }}
path: ./dist/
retention-days: 7
github-release:
needs: [setup, build-cli, build-gui]
runs-on: ubuntu-latest
if: |
always() &&
(startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') &&
needs.build-cli.result == 'success' &&
needs.setup.result == 'success' &&
(needs.build-gui.result == 'success' || needs.build-gui.result == 'skipped')
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: true
- name: Set up tag environment
run: |
echo "RELEASE_TAG=${{ needs.setup.outputs.release_tag }}" >> $GITHUB_ENV
- name: Download CLI artifacts
uses: actions/download-artifact@v4
with:
pattern: cli-*
path: ./dist/cli
# Keep artifacts in separate subdirectories to avoid overwriting same-named binaries
- name: Download GUI artifacts
if: needs.setup.outputs.build_gui == 'true'
uses: actions/download-artifact@v4
with:
pattern: gui-*
path: ./dist/gui
# Keep artifacts in separate subdirectories
- name: Package binaries with proper extensions
run: |
# Save original directory and use absolute path for release
ORIGINAL_DIR="$(pwd)"
RELEASE_DIR="${ORIGINAL_DIR}/release"
mkdir -p "$RELEASE_DIR"
# Process CLI binaries - each artifact is in its own subdirectory
if [ -d "dist/cli" ]; then
for artifact_dir in dist/cli/cli-*; do
if [ -d "$artifact_dir" ]; then
# Extract platform from artifact directory name
platform=$(basename "$artifact_dir" | sed 's/cli-//')
chmod +x "$artifact_dir"/tingly-box* || true
# Package the binary with platform suffix in zip filename
binary=$(ls "$artifact_dir"/tingly-box* 2>/dev/null | head -1)
if [ -n "$binary" ]; then
(cd "$artifact_dir" && zip "${RELEASE_DIR}/tingly-box-${platform}.zip" "$(basename "$binary")")
fi
fi
done
fi
# Process GUI binaries (already zipped)
if [ -d "dist/gui" ]; then
# GUI artifacts are also in subdirectories
for artifact_dir in dist/gui/gui-*; do
if [ -d "$artifact_dir" ]; then
cp "$artifact_dir"/*.zip "$RELEASE_DIR/" 2>/dev/null || true
fi
done
# Also check for direct files
find dist/gui -maxdepth 1 -name "*.zip" -exec cp {} "$RELEASE_DIR/" \; 2>/dev/null || true
fi
# Create checksums for all files (only if we have zip files)
if ls "$RELEASE_DIR"/*.zip 1> /dev/null 2>&1; then
(cd "$RELEASE_DIR" && sha256sum *.zip > checksums.txt)
else
echo "No zip files found in release directory"
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.RELEASE_TAG }}
files: |
release/*.zip
release/checksums.txt
generate_release_notes: true
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# GitHub never starts workflow runs for events that a workflow creates with
# its own GITHUB_TOKEN, so the release published above does not fire the
# `release: published` trigger in npm.yml. Dispatch it explicitly instead.
# Nothing gets published by this: the npm run stops at the `production`
# environment approval gate before the first `npm publish`. This only
# removes the manual "Run workflow" step, not the human approval.
trigger-npm-publish:
needs: [setup, github-release]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') && needs.github-release.result == 'success'
permissions:
actions: write
contents: read
steps:
- name: Dispatch NPX publish workflow
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.setup.outputs.release_tag }}
run: |
# Same inference npm.yml applies on a release event: stable → latest,
# pre-release (v1.2.3-rc1) → rc.
VERSION="${RELEASE_TAG#v}"
if [[ "$VERSION" == *-* ]]; then
NPM_TAG=rc
else
NPM_TAG=latest
fi
echo "Dispatching npm.yml for $RELEASE_TAG (npm dist-tag: $NPM_TAG)"
gh workflow run npm.yml \
--repo "$GITHUB_REPOSITORY" \
--ref "$RELEASE_TAG" \
-f tag="$RELEASE_TAG" \
-f npm_tag="$NPM_TAG" \
-f publish_cli=true \
-f publish_gui=false \
-f build_docker=true
# Best effort: link the dispatched run so approvers can find it from here.
RUN_URL=""
for _ in 1 2 3 4 5 6; do
sleep 5
RUN_URL="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow npm.yml \
--event workflow_dispatch --branch "$RELEASE_TAG" --limit 1 \
--json url -q '.[0].url' 2>/dev/null || true)"
[ -n "$RUN_URL" ] && break
done
{
echo "## 📦 NPX publish dispatched"
echo ""
echo "| Item | Value |"
echo "| --- | --- |"
echo "| Release | \`$RELEASE_TAG\` |"
echo "| npm dist-tag | \`$NPM_TAG\` |"
echo "| Publish | cli=true, gui=false, docker=true |"
if [ -n "$RUN_URL" ]; then
echo "| Run | $RUN_URL |"
else
echo "| Run | see the [NPX Package Publish workflow](https://github.com/$GITHUB_REPOSITORY/actions/workflows/npm.yml) |"
fi
echo ""
echo "The run waits for approval of the \`production\` environment before publishing."
} >> "$GITHUB_STEP_SUMMARY"