Skip to content

Commit 4527942

Browse files
committed
Merge branch 'main' into lifei/e2e-test
* main: (74 commits) feat: return structured {stdout, stderr} from shell tool with output schema (#7604) Improve custom provider creation experience (#7541) fix(scheduler): schedules added via CLI showing up in UI (#7594) chore: openai reasoning model cleanup (#7529) chore(deps): bump hono from 4.12.1 to 4.12.3 in /evals/open-model-gym/mcp-harness (#7585) chore(deps): bump minimatch from 10.1.1 to 10.2.3 in /evals/open-model-gym/suite (#7498) chore(deps): bump swiper from 11.2.10 to 12.1.2 in /documentation (#7368) Better network failure error & antrhopic retry (#7595) feat: make the text bar persistent and add a queue for messages (#7560) fix: outdated clippy command in goosehints (#7590) chore(deps): bump hono from 4.11.7 to 4.12.1 in /evals/open-model-gym/mcp-harness (#7417) chore(deps-dev): bump ajv from 6.12.6 to 6.14.0 in /ui/desktop (#7437) chore(deps): bump ajv from 8.17.1 to 8.18.0 in /evals/open-model-gym/mcp-harness (#7491) chore(deps): bump hono from 4.12.0 to 4.12.2 in /ui/desktop (#7515) chore(deps-dev): bump rollup from 4.57.1 to 4.59.0 in /ui/desktop (#7522) chore(deps): bump minimatch in /ui/desktop (#7572) fix: validate configure probe for streaming providers (#7564) Dockerfile: add missing build/runtime dependencies (#7546) fix(claude-code): Permission routing for smart-approve (#7501) Add base_path field to custom provider config (#7558) ...
2 parents 1acd763 + 6702936 commit 4527942

File tree

280 files changed

+15409
-20026
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

280 files changed

+15409
-20026
lines changed

.cargo/config.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[target.x86_64-pc-windows-msvc]
2+
rustflags = ["-C", "link-args=/FORCE:MULTIPLE"]
3+
4+
[target.aarch64-pc-windows-msvc]
5+
rustflags = ["-C", "link-args=/FORCE:MULTIPLE"]

.github/CODEOWNERS

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# CODEOWNERS file for block/goose repository
22
# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
33

4-
# Documentation owned by DevRel team
5-
/documentation/ @block/goose-devrel
4+
# Documentation owned by DevRel
5+
/documentation/ @blackgirlbytes
66

.github/copilot-instructions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
- Async/await misuse or blocking operations in async contexts
3535
- Improper trait implementations
3636

37-
### No Prerelease Docs
38-
- If the PR contains both code changes to features/functionality AND updates in `/documentation`: Documentation updates must be separated to keep public docs in sync with released versions. Either mark new topics with `unlisted: true` or remove/hide the documentation.
37+
### No Doc Updates with Code Changes
38+
- PRs with code changes shouldn't update `/documentation` - docs deploy on merge, code on release. Use `unlisted: true` or remove/hide docs.
3939

4040
## Project-Specific Context
4141

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
# goose Release Notes Generator
2+
#
3+
# Automatically generates release notes using goose AI agent when a new release is published.
4+
# Updates the GitHub release with AI-generated categorized notes and posts to Discord.
5+
#
6+
# Uses workflow_run instead of release:published because GitHub doesn't trigger
7+
# workflows for events created by GITHUB_TOKEN (to prevent infinite loops).
8+
9+
name: goose Release Notes Generator
10+
11+
on:
12+
# Trigger when Release workflow completes (works around GITHUB_TOKEN limitation)
13+
workflow_run:
14+
workflows:
15+
- Release
16+
types:
17+
- completed
18+
19+
# Allow manual trigger for testing
20+
workflow_dispatch:
21+
inputs:
22+
tag:
23+
description: 'Release tag to generate notes for (e.g., v1.25.0)'
24+
required: true
25+
type: string
26+
27+
env:
28+
GOOSE_RECIPE: |
29+
version: "1.0.0"
30+
title: "Release Notes Generator"
31+
description: "Generate release notes for ${RELEASE_TAG}"
32+
33+
extensions:
34+
- type: builtin
35+
name: developer
36+
37+
instructions: |
38+
Generate release notes for the goose release.
39+
40+
## Process
41+
1. You are already in the goose repository. Do NOT clone or checkout anything.
42+
2. Get the previous release tag by running: git describe --tags --abbrev=0 ${RELEASE_TAG}^
43+
3. Get commits between tags: git log <previous_tag>..${RELEASE_TAG} --oneline --no-merges
44+
4. Analyze the commits and categorize changes
45+
46+
## Output Format
47+
Categorize changes into these sections (skip empty sections):
48+
- ✨ **Features** - New functionality
49+
- 🐛 **Bug Fixes** - Bug fixes
50+
- 🔧 **Improvements** - Enhancements to existing features
51+
- 📚 **Documentation** - Documentation updates
52+
53+
Format each item as:
54+
- Concise description [#XXXX](https://github.com/block/goose/pull/XXXX)
55+
56+
Rules:
57+
- Extract PR numbers from commit messages (look for (#XXXX) pattern)
58+
- Remove redundant words like "Added", "Fixed", "Documented" - the category headers make these clear
59+
- Keep descriptions user-friendly and concise
60+
- Order: Features → Bug Fixes → Improvements → Documentation
61+
62+
## Final Step
63+
Write ONLY the release notes content to /tmp/release_notes.md (no extra commentary)
64+
65+
prompt: |
66+
Generate release notes for ${RELEASE_TAG} in the goose repository.
67+
68+
permissions:
69+
contents: write
70+
71+
concurrency:
72+
group: release-notes-${{ github.event.workflow_run.head_branch || inputs.tag }}
73+
cancel-in-progress: true
74+
75+
jobs:
76+
generate-release-notes:
77+
name: Generate Release Notes
78+
runs-on: ubuntu-latest
79+
# For workflow_run: only run if Release succeeded and tag is not 'stable'
80+
# For workflow_dispatch: only run if tag is not 'stable'
81+
if: |
82+
(github.event_name == 'workflow_dispatch' && inputs.tag != 'stable') ||
83+
(github.event_name == 'workflow_run' &&
84+
github.event.workflow_run.conclusion == 'success' &&
85+
github.event.workflow_run.head_branch != 'stable')
86+
87+
container:
88+
image: ghcr.io/block/goose:latest
89+
options: --user root
90+
env:
91+
GOOSE_PROVIDER: ${{ vars.GOOSE_PROVIDER || 'anthropic' }}
92+
GOOSE_MODEL: ${{ vars.GOOSE_MODEL || 'claude-opus-4-5' }}
93+
ANTHROPIC_API_KEY: ${{ secrets.RELEASE_BOT_ANTHROPIC_KEY }}
94+
HOME: /tmp/goose-home
95+
96+
outputs:
97+
release_notes: ${{ steps.read-notes.outputs.notes }}
98+
notes_length: ${{ steps.read-notes.outputs.length }}
99+
release_tag: ${{ steps.get-tag.outputs.tag }}
100+
101+
steps:
102+
- name: Get release tag
103+
id: get-tag
104+
env:
105+
EVENT_NAME: ${{ github.event_name }}
106+
INPUT_TAG: ${{ inputs.tag }}
107+
WORKFLOW_RUN_BRANCH: ${{ github.event.workflow_run.head_branch }}
108+
run: |
109+
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
110+
echo "tag=$INPUT_TAG" >> $GITHUB_OUTPUT
111+
else
112+
echo "tag=$WORKFLOW_RUN_BRANCH" >> $GITHUB_OUTPUT
113+
fi
114+
115+
- name: Checkout repository
116+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
117+
with:
118+
fetch-depth: 0
119+
fetch-tags: true
120+
121+
- name: Install tools
122+
run: |
123+
apt-get update
124+
apt-get install -y gettext curl ripgrep git jq
125+
126+
- name: Run goose to generate release notes
127+
env:
128+
RELEASE_TAG: ${{ steps.get-tag.outputs.tag }}
129+
run: |
130+
mkdir -p $HOME/.local/share/goose/sessions
131+
mkdir -p $HOME/.config/goose
132+
git config --global --add safe.directory "$GITHUB_WORKSPACE"
133+
134+
# Checkout the release tag
135+
git checkout "${RELEASE_TAG}"
136+
137+
# Create recipe from env var with variable substitution
138+
echo "$GOOSE_RECIPE" | envsubst '$RELEASE_TAG' > /tmp/recipe.yaml
139+
140+
goose run --recipe /tmp/recipe.yaml
141+
142+
- name: Read release notes
143+
id: read-notes
144+
run: |
145+
if [ -f /tmp/release_notes.md ]; then
146+
# Use random delimiter to prevent injection
147+
DELIMITER="EOF_$(openssl rand -hex 8)"
148+
echo "notes<<$DELIMITER" >> $GITHUB_OUTPUT
149+
cat /tmp/release_notes.md >> $GITHUB_OUTPUT
150+
echo "" >> $GITHUB_OUTPUT
151+
echo "$DELIMITER" >> $GITHUB_OUTPUT
152+
153+
LENGTH=$(wc -c < /tmp/release_notes.md)
154+
echo "length=$LENGTH" >> $GITHUB_OUTPUT
155+
156+
echo "::notice::Release notes generated successfully (${LENGTH} chars)"
157+
else
158+
echo "::error::Release notes file not found at /tmp/release_notes.md"
159+
echo "notes=Release notes generation failed." >> $GITHUB_OUTPUT
160+
echo "length=0" >> $GITHUB_OUTPUT
161+
exit 1
162+
fi
163+
164+
update-github-release:
165+
name: Update GitHub Release
166+
runs-on: ubuntu-latest
167+
needs: generate-release-notes
168+
permissions:
169+
contents: write
170+
171+
steps:
172+
- name: Update release body
173+
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
174+
with:
175+
tag: ${{ needs.generate-release-notes.outputs.release_tag }}
176+
token: ${{ secrets.GITHUB_TOKEN }}
177+
body: ${{ needs.generate-release-notes.outputs.release_notes }}
178+
allowUpdates: true
179+
omitNameDuringUpdate: true
180+
omitPrereleaseDuringUpdate: true
181+
182+
post-to-discord:
183+
name: Post to Discord
184+
runs-on: ubuntu-latest
185+
needs: generate-release-notes
186+
187+
steps:
188+
- name: Post release announcement
189+
env:
190+
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_RELEASE_BOT_TOKEN }}
191+
DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_RELEASE_BOT_CHANNEL_ID }}
192+
RELEASE_TAG: ${{ needs.generate-release-notes.outputs.release_tag }}
193+
RELEASE_URL: https://github.com/block/goose/releases/tag/${{ needs.generate-release-notes.outputs.release_tag }}
194+
RELEASE_NOTES: ${{ needs.generate-release-notes.outputs.release_notes }}
195+
NOTES_LENGTH: ${{ needs.generate-release-notes.outputs.notes_length }}
196+
shell: bash
197+
run: |
198+
# Skip if Discord is not configured
199+
if [ -z "$DISCORD_CHANNEL_ID" ] || [ -z "$DISCORD_BOT_TOKEN" ]; then
200+
echo "::notice::Discord not configured, skipping"
201+
exit 0
202+
fi
203+
204+
# Discord message character limit is ~2000 for regular messages
205+
SAFE_LIMIT=1800
206+
207+
# Function to send Discord message via bot API, returns message ID
208+
send_discord() {
209+
local content="$1"
210+
local channel="$2"
211+
212+
content=$(echo "$content" | jq -Rs .)
213+
214+
response=$(curl -s -X POST "https://discord.com/api/v10/channels/${channel}/messages" \
215+
-H "Authorization: Bot ${DISCORD_BOT_TOKEN}" \
216+
-H "Content-Type: application/json" \
217+
-d "{\"content\": $content, \"flags\": 4}")
218+
219+
# Debug: show response if there's an error
220+
if echo "$response" | jq -e '.code' > /dev/null 2>&1; then
221+
echo "::warning::Discord API error: $(echo "$response" | jq -c '.')" >&2
222+
fi
223+
224+
echo "$response" | jq -r '.id // empty'
225+
}
226+
227+
# Function to create a thread from a message
228+
create_thread() {
229+
local channel="$1"
230+
local message_id="$2"
231+
local thread_name="$3"
232+
233+
response=$(curl -s -X POST "https://discord.com/api/v10/channels/${channel}/messages/${message_id}/threads" \
234+
-H "Authorization: Bot ${DISCORD_BOT_TOKEN}" \
235+
-H "Content-Type: application/json" \
236+
-d "{\"name\": \"${thread_name}\"}")
237+
238+
echo "$response" | jq -r '.id // empty'
239+
}
240+
241+
# Build the announcement header
242+
HEADER="## 🎉 goose ${RELEASE_TAG} is here!
243+
244+
📦 **Release:** ${RELEASE_URL}"
245+
246+
FOOTER="
247+
248+
@everyone
249+
📝 *More in thread...*"
250+
HEADER_LEN=${#HEADER}
251+
FOOTER_LEN=${#FOOTER}
252+
AVAILABLE=$((SAFE_LIMIT - HEADER_LEN - FOOTER_LEN - 10))
253+
254+
# Check if everything fits in one message (no thread needed)
255+
FULL_MSG="${HEADER}
256+
257+
${RELEASE_NOTES}"
258+
if [ ${#FULL_MSG} -le "$SAFE_LIMIT" ]; then
259+
send_discord "$FULL_MSG" "$DISCORD_CHANNEL_ID"
260+
echo "::notice::Discord notification sent successfully"
261+
exit 0
262+
fi
263+
264+
# Need to split - use awk to fit complete lines in main vs thread
265+
echo "$RELEASE_NOTES" > /tmp/release_notes_full.txt
266+
267+
awk -v avail="$AVAILABLE" '
268+
BEGIN { main_len = 0; in_thread = 0 }
269+
{
270+
line_len = length($0) + 1 # +1 for newline
271+
if (!in_thread && (main_len + line_len) <= avail) {
272+
print > "/tmp/main_content.txt"
273+
main_len += line_len
274+
} else {
275+
in_thread = 1
276+
print > "/tmp/thread_content.txt"
277+
}
278+
}
279+
' /tmp/release_notes_full.txt
280+
281+
# Read the split content
282+
MAIN_CONTENT=""
283+
[ -f /tmp/main_content.txt ] && MAIN_CONTENT=$(cat /tmp/main_content.txt)
284+
285+
THREAD_CONTENT=""
286+
[ -f /tmp/thread_content.txt ] && THREAD_CONTENT=$(cat /tmp/thread_content.txt)
287+
288+
# Build and send main message
289+
MAIN_MESSAGE="${HEADER}
290+
291+
${MAIN_CONTENT}
292+
${FOOTER}"
293+
294+
MESSAGE_ID=$(send_discord "$MAIN_MESSAGE" "$DISCORD_CHANNEL_ID")
295+
296+
if [ -z "$MESSAGE_ID" ]; then
297+
echo "::error::Failed to send Discord message"
298+
exit 1
299+
fi
300+
301+
echo "Created message $MESSAGE_ID, creating thread..."
302+
sleep 1
303+
304+
# Create thread
305+
THREAD_ID=$(create_thread "$DISCORD_CHANNEL_ID" "$MESSAGE_ID" "Release Notes ${RELEASE_TAG}")
306+
if [ -z "$THREAD_ID" ]; then
307+
echo "::warning::Failed to create thread"
308+
THREAD_ID="$DISCORD_CHANNEL_ID"
309+
fi
310+
sleep 1
311+
312+
# Post thread content in chunks (by complete lines)
313+
if [ -n "$THREAD_CONTENT" ]; then
314+
# Split thread content into chunks using awk
315+
awk -v limit="$SAFE_LIMIT" '
316+
BEGIN { chunk = ""; chunk_len = 0; chunk_num = 0 }
317+
{
318+
line_len = length($0) + 1
319+
if (chunk_len + line_len > limit && chunk_len > 0) {
320+
# Save current chunk and start new one
321+
print chunk > "/tmp/chunk_" chunk_num ".txt"
322+
chunk_num++
323+
chunk = $0 "\n"
324+
chunk_len = line_len
325+
} else {
326+
chunk = chunk $0 "\n"
327+
chunk_len += line_len
328+
}
329+
}
330+
END {
331+
if (chunk_len > 0) {
332+
print chunk > "/tmp/chunk_" chunk_num ".txt"
333+
}
334+
}
335+
' /tmp/thread_content.txt
336+
337+
# Send each chunk
338+
for chunk_file in /tmp/chunk_*.txt; do
339+
[ -f "$chunk_file" ] || continue
340+
CHUNK=$(cat "$chunk_file")
341+
[ -n "$CHUNK" ] && send_discord "$CHUNK" "$THREAD_ID"
342+
sleep 1
343+
rm -f "$chunk_file"
344+
done
345+
fi
346+
347+
# Cleanup
348+
rm -f /tmp/release_notes_full.txt /tmp/main_content.txt /tmp/thread_content.txt
349+
350+
echo "::notice::Discord notification sent successfully"

.goosehints

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ that you can call the functionality from the server from the typescript.
1414

1515
tips:
1616
- can look at unstaged changes for what is being worked on if starting
17-
- always check rust compiles, cargo fmt etc and `./scripts/clippy-lint.sh` (as well as run tests in files you are working on)
17+
- always check rust compiles, cargo fmt etc and `cargo clippy --all-targets -- -D warnings` (as well as run tests in files you are working on)
1818
- in ui/desktop, look at how you can run lint checks and if other tests can run

0 commit comments

Comments
 (0)