mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
Merge remote-tracking branch 'origin/main' into jackamadeo/fix-auto-updating
This commit is contained in:
@@ -192,10 +192,10 @@ jobs:
|
||||
source_job_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
unsigned_url="s3://block-goose-artifacts-bucket-production/unsigned/goose-${GITHUB_SHA}-${{ github.run_id }}-intel.zip"
|
||||
|
||||
zip -q -u -r out/goose-darwin-x64/goose_intel_mac.zip entitlements.plist
|
||||
zip -q -u -r out/Goose-darwin-x64/Goose_intel_mac.zip entitlements.plist
|
||||
|
||||
# upload unsigned goose to transfer bucket so it can be passed to lambda
|
||||
aws s3 cp --quiet out/goose-darwin-x64/goose_intel_mac.zip "${unsigned_url}"
|
||||
aws s3 cp --quiet out/Goose-darwin-x64/Goose_intel_mac.zip "${unsigned_url}"
|
||||
|
||||
# begin signing
|
||||
echo "🚀 launching signing process"
|
||||
@@ -247,7 +247,7 @@ jobs:
|
||||
|
||||
# download the signed app from S3
|
||||
echo "⬇️ downloading signed app"
|
||||
aws s3 cp --quiet "${signed_url}" out/goose-darwin-x64/goose_intel_mac.zip
|
||||
aws s3 cp --quiet "${signed_url}" out/Goose-darwin-x64/Goose_intel_mac.zip
|
||||
working-directory: ui/desktop
|
||||
|
||||
- name: Final cleanup before artifact upload
|
||||
@@ -261,26 +261,26 @@ jobs:
|
||||
- name: Upload Desktop artifact
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # pin@v4
|
||||
with:
|
||||
name: goose-darwin-x64
|
||||
path: ui/desktop/out/goose-darwin-x64/goose_intel_mac.zip
|
||||
name: Goose-darwin-x64
|
||||
path: ui/desktop/out/Goose-darwin-x64/Goose_intel_mac.zip
|
||||
|
||||
- name: Quick launch test (macOS)
|
||||
if: ${{ inputs.quick_test }}
|
||||
run: |
|
||||
# Ensure no quarantine attributes (if needed)
|
||||
xattr -cr "ui/desktop/out/goose-darwin-x64/goose.app"
|
||||
echo "Opening goose.app..."
|
||||
open -g "ui/desktop/out/goose-darwin-x64/goose.app"
|
||||
xattr -cr "ui/desktop/out/Goose-darwin-x64/Goose.app"
|
||||
echo "Opening Goose.app..."
|
||||
open -g "ui/desktop/out/Goose-darwin-x64/Goose.app"
|
||||
|
||||
# Give the app a few seconds to start and write logs
|
||||
sleep 5
|
||||
|
||||
# Check if it's running
|
||||
if pgrep -f "goose.app/Contents/MacOS/goose" > /dev/null; then
|
||||
if pgrep -f "Goose.app/Contents/MacOS/Goose" > /dev/null; then
|
||||
echo "App appears to be running."
|
||||
else
|
||||
echo "App did not stay open. Possible crash or startup error."
|
||||
exit 1
|
||||
fi
|
||||
# Kill the app to clean up
|
||||
pkill -f "goose.app/Contents/MacOS/goose"
|
||||
pkill -f "Goose.app/Contents/MacOS/Goose"
|
||||
|
||||
@@ -180,21 +180,21 @@ jobs:
|
||||
- name: Upload .deb package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: goose-linux-x64-deb
|
||||
name: Goose-linux-x64-deb
|
||||
path: ui/desktop/out/make/deb/x64/*.deb
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .rpm package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: goose-linux-x64-rpm
|
||||
name: Goose-linux-x64-rpm
|
||||
path: ui/desktop/out/make/rpm/x64/*.rpm
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload combined Linux packages
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: goose-linux-x64
|
||||
name: Goose-linux-x64
|
||||
path: |
|
||||
ui/desktop/out/make/deb/x64/*.deb
|
||||
ui/desktop/out/make/rpm/x64/*.rpm
|
||||
|
||||
@@ -284,12 +284,12 @@ jobs:
|
||||
- name: Copy exe/dll to final out folder and prepare flat distribution
|
||||
run: |
|
||||
cd ui/desktop
|
||||
mkdir -p ./out/goose-win32-x64/resources/bin
|
||||
rsync -av src/bin/ out/goose-win32-x64/resources/bin/
|
||||
mkdir -p ./out/Goose-win32-x64/resources/bin
|
||||
rsync -av src/bin/ out/Goose-win32-x64/resources/bin/
|
||||
|
||||
# Create flat distribution structure
|
||||
mkdir -p ./dist-windows
|
||||
cp -r ./out/goose-win32-x64/* ./dist-windows/
|
||||
cp -r ./out/Goose-win32-x64/* ./dist-windows/
|
||||
|
||||
# Verify the final structure
|
||||
echo "📋 Final flat distribution structure:"
|
||||
@@ -318,8 +318,8 @@ jobs:
|
||||
wget -q https://github.com/ebourg/jsign/releases/download/6.0/jsign-6.0.jar -O jsign.jar
|
||||
echo "05ca18d4ab7b8c2183289b5378d32860f0ea0f3bdab1f1b8cae5894fb225fa8a jsign.jar" | sha256sum -c
|
||||
|
||||
# Sign the main Electron executable (goose.exe)
|
||||
echo "🔐 Signing main Electron executable: goose.exe"
|
||||
# Sign the main Electron executable (Goose.exe)
|
||||
echo "🔐 Signing main Electron executable: Goose.exe"
|
||||
cd ui/desktop/dist-windows/
|
||||
|
||||
java -jar ${GITHUB_WORKSPACE}/jsign.jar \
|
||||
@@ -329,12 +329,12 @@ jobs:
|
||||
--alias windows-codesign \
|
||||
--certfile "${GITHUB_WORKSPACE}/block-codesign-cert.pem" \
|
||||
--tsaurl "http://timestamp.digicert.com" \
|
||||
--name "goose" \
|
||||
--name "Goose" \
|
||||
--url "https://github.com/block/goose" \
|
||||
"goose.exe"
|
||||
"Goose.exe"
|
||||
|
||||
osslsigncode verify goose.exe
|
||||
echo "✅ Main executable goose.exe signed successfully"
|
||||
osslsigncode verify Goose.exe
|
||||
echo "✅ Main executable Goose.exe signed successfully"
|
||||
|
||||
# Sign the backend executable (goosed.exe)
|
||||
echo "🔐 Signing backend executable: goosed.exe"
|
||||
@@ -347,7 +347,7 @@ jobs:
|
||||
--alias windows-codesign \
|
||||
--certfile "${GITHUB_WORKSPACE}/block-codesign-cert.pem" \
|
||||
--tsaurl "http://timestamp.digicert.com" \
|
||||
--name "goose Backend" \
|
||||
--name "Goose Backend" \
|
||||
--url "https://github.com/block/goose" \
|
||||
"goosed.exe"
|
||||
|
||||
@@ -357,8 +357,8 @@ jobs:
|
||||
# Show final file status
|
||||
echo "📋 Final signed files:"
|
||||
cd ../../
|
||||
ls -la goose.exe
|
||||
sha256sum goose.exe
|
||||
ls -la Goose.exe
|
||||
sha256sum Goose.exe
|
||||
ls -la resources/bin/goosed.exe
|
||||
sha256sum resources/bin/goosed.exe
|
||||
|
||||
@@ -371,8 +371,8 @@ jobs:
|
||||
run: |
|
||||
echo "📋 Verifying both signed executables in final distribution:"
|
||||
echo "Main executable:"
|
||||
ls -la ui/desktop/dist-windows/goose.exe
|
||||
osslsigncode verify ui/desktop/dist-windows/goose.exe
|
||||
ls -la ui/desktop/dist-windows/Goose.exe
|
||||
osslsigncode verify ui/desktop/dist-windows/Goose.exe
|
||||
echo "✅ Main executable signature verification passed"
|
||||
|
||||
echo "Backend executable:"
|
||||
@@ -387,18 +387,18 @@ jobs:
|
||||
echo "📦 Creating Windows zip package..."
|
||||
|
||||
# Create a zip file from the dist-windows directory
|
||||
zip -r "goose-win32-x64.zip" dist-windows/
|
||||
zip -r "Goose-win32-x64.zip" dist-windows/
|
||||
|
||||
echo "✅ Windows zip package created:"
|
||||
ls -la goose-win32-x64.zip
|
||||
ls -la Goose-win32-x64.zip
|
||||
|
||||
# Also create the zip in the expected output structure for consistency
|
||||
mkdir -p out/goose-win32-x64/
|
||||
cp goose-win32-x64.zip out/goose-win32-x64/
|
||||
mkdir -p out/Goose-win32-x64/
|
||||
cp Goose-win32-x64.zip out/Goose-win32-x64/
|
||||
|
||||
# 11) Upload the final Windows build
|
||||
- name: Upload Windows build artifacts
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # pin@v4
|
||||
with:
|
||||
name: goose-win32-x64
|
||||
path: ui/desktop/out/goose-win32-x64/goose-win32-x64.zip
|
||||
name: Goose-win32-x64
|
||||
path: ui/desktop/out/Goose-win32-x64/Goose-win32-x64.zip
|
||||
|
||||
@@ -226,10 +226,10 @@ jobs:
|
||||
source_job_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
unsigned_url="s3://block-goose-artifacts-bucket-production/unsigned/goose-${GITHUB_SHA}-${{ github.run_id }}-arm64.zip"
|
||||
|
||||
zip -q -u -r out/goose-darwin-arm64/goose.zip entitlements.plist
|
||||
zip -q -u -r out/Goose-darwin-arm64/Goose.zip entitlements.plist
|
||||
|
||||
# upload unsigned goose to transfer bucket so it can be passed to lambda
|
||||
aws s3 cp --quiet out/goose-darwin-arm64/goose.zip "${unsigned_url}"
|
||||
aws s3 cp --quiet out/Goose-darwin-arm64/Goose.zip "${unsigned_url}"
|
||||
|
||||
# begin signing
|
||||
echo "🚀 launching signing process"
|
||||
@@ -281,7 +281,7 @@ jobs:
|
||||
|
||||
# download the signed app from S3
|
||||
echo "⬇️ downloading signed app"
|
||||
aws s3 cp --quiet "${signed_url}" out/goose-darwin-arm64/goose.zip
|
||||
aws s3 cp --quiet "${signed_url}" out/Goose-darwin-arm64/Goose.zip
|
||||
working-directory: ui/desktop
|
||||
|
||||
- name: Final cleanup before artifact upload
|
||||
@@ -296,26 +296,26 @@ jobs:
|
||||
id: upload-app-bundle
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # pin@v4
|
||||
with:
|
||||
name: goose-darwin-arm64
|
||||
path: ui/desktop/out/goose-darwin-arm64/goose.zip
|
||||
name: Goose-darwin-arm64
|
||||
path: ui/desktop/out/Goose-darwin-arm64/Goose.zip
|
||||
|
||||
- name: Quick launch test (macOS)
|
||||
if: ${{ inputs.quick_test }}
|
||||
run: |
|
||||
# Ensure no quarantine attributes (if needed)
|
||||
xattr -cr "ui/desktop/out/goose-darwin-arm64/goose.app"
|
||||
echo "Opening goose.app..."
|
||||
open -g "ui/desktop/out/goose-darwin-arm64/goose.app"
|
||||
xattr -cr "ui/desktop/out/Goose-darwin-arm64/Goose.app"
|
||||
echo "Opening Goose.app..."
|
||||
open -g "ui/desktop/out/Goose-darwin-arm64/Goose.app"
|
||||
|
||||
# Give the app a few seconds to start and write logs
|
||||
sleep 5
|
||||
|
||||
# Check if it's running
|
||||
if pgrep -f "goose.app/Contents/MacOS/goose" > /dev/null; then
|
||||
if pgrep -f "Goose.app/Contents/MacOS/Goose" > /dev/null; then
|
||||
echo "App appears to be running."
|
||||
else
|
||||
echo "App did not stay open. Possible crash or startup error."
|
||||
exit 1
|
||||
fi
|
||||
# Kill the app to clean up
|
||||
pkill -f "goose.app/Contents/MacOS/goose"
|
||||
pkill -f "Goose.app/Contents/MacOS/Goose"
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: |
|
||||
goose-*.tar.bz2
|
||||
goose*.zip
|
||||
Goose*.zip
|
||||
*.deb
|
||||
*.rpm
|
||||
download_cli.sh
|
||||
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: |
|
||||
goose-*.tar.bz2
|
||||
goose*.zip
|
||||
Goose*.zip
|
||||
*.deb
|
||||
*.rpm
|
||||
download_cli.sh
|
||||
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
- name: Download Intel artifact
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # pin@v4
|
||||
with:
|
||||
name: goose-darwin-x64
|
||||
name: Goose-darwin-x64
|
||||
path: intel-dist
|
||||
|
||||
- name: Comment on PR with Intel download link
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
body: |
|
||||
### macOS Intel Desktop App (x64)
|
||||
|
||||
[💻 Download macOS Desktop App (Intel x64, signed)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-darwin-x64.zip)
|
||||
[💻 Download macOS Desktop App (Intel x64, signed)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-darwin-x64.zip)
|
||||
|
||||
**Instructions:**
|
||||
After downloading, unzip the file and drag the goose.app to your Applications folder. The app is signed and notarized for macOS.
|
||||
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
- name: Download Windows artifact
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # pin@v4
|
||||
with:
|
||||
name: desktop-windows-dist
|
||||
name: Goose-win32-x64
|
||||
path: windows-dist
|
||||
|
||||
- name: Comment on PR with Windows download link
|
||||
@@ -92,9 +92,9 @@ jobs:
|
||||
body: |
|
||||
### Windows Desktop App
|
||||
|
||||
[🪟 Download Windows Desktop App (x64, signed)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/desktop-windows-dist.zip)
|
||||
[🪟 Download Windows Desktop App (x64, signed)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-win32-x64.zip)
|
||||
|
||||
**Instructions:**
|
||||
After downloading, unzip the file and run goose.exe. The app is signed for Windows.
|
||||
After downloading, unzip the file and run Goose.exe. The app is signed for Windows.
|
||||
|
||||
This link is provided by nightly.link and will work even if you're not logged into GitHub.
|
||||
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
- name: Download ARM64 artifact
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # pin@v4
|
||||
with:
|
||||
name: goose-darwin-arm64
|
||||
name: Goose-darwin-arm64
|
||||
path: arm64-dist
|
||||
|
||||
- name: Comment on PR with ARM64 download link
|
||||
@@ -133,7 +133,7 @@ jobs:
|
||||
body: |
|
||||
### macOS ARM64 Desktop App (Apple Silicon)
|
||||
|
||||
[📱 Download macOS Desktop App (arm64, unsigned)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-darwin-arm64.zip)
|
||||
[📱 Download macOS Desktop App (arm64, unsigned)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-darwin-arm64.zip)
|
||||
|
||||
**Instructions:**
|
||||
After downloading, unzip the file and drag the goose.app to a location you prefer. The app is unsigned, so to run it run `xattr -r -d com.apple.quarantine '/path/to/goose.app'` and then open the app
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
body: |
|
||||
### macOS ARM64 Desktop App (Apple Silicon)
|
||||
|
||||
[📱 Download macOS Desktop App (arm64, unsigned)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-darwin-arm64.zip)
|
||||
[📱 Download macOS Desktop App (arm64, unsigned)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-darwin-arm64.zip)
|
||||
|
||||
**Instructions:**
|
||||
After downloading, unzip the file and drag the goose.app to a location you prefer. The app is unsigned, so to run it run `xattr -r -d com.apple.quarantine '/path/to/goose.app'` and then open the app
|
||||
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: |
|
||||
goose-*.tar.bz2
|
||||
goose*.zip
|
||||
Goose*.zip
|
||||
*.deb
|
||||
*.rpm
|
||||
download_cli.sh
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: |
|
||||
goose-*.tar.bz2
|
||||
goose*.zip
|
||||
Goose*.zip
|
||||
*.deb
|
||||
*.rpm
|
||||
download_cli.sh
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
use crate::agents::extension::PlatformExtensionContext;
|
||||
use crate::agents::mcp_client::{Error, McpClientTrait};
|
||||
use crate::session::SessionManager;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use indoc::indoc;
|
||||
use rmcp::model::{
|
||||
CallToolResult, Content, GetPromptResult, Implementation, InitializeResult, JsonObject,
|
||||
ListPromptsResult, ListResourcesResult, ListToolsResult, ProtocolVersion, ReadResourceResult,
|
||||
ServerCapabilities, ServerNotification, Tool, ToolAnnotations, ToolsCapability,
|
||||
};
|
||||
use schemars::{schema_for, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub static EXTENSION_NAME: &str = "chat_recall";
|
||||
|
||||
/// Parameters for the chat_recall tool
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
struct ChatRecallParams {
|
||||
/// Search keywords. Use multiple related terms/synonyms (e.g., 'database postgres sql'). Mutually exclusive with session_id.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
query: Option<String>,
|
||||
/// Session ID to load. Returns first/last 3 messages. Mutually exclusive with query.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
session_id: Option<String>,
|
||||
/// Max results (default: 10, max: 50). Search mode only.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
limit: Option<i64>,
|
||||
/// ISO 8601 date (e.g., '2025-10-01T00:00:00Z'). Search mode only.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
after_date: Option<String>,
|
||||
/// ISO 8601 date (e.g., '2025-10-15T23:59:59Z'). Search mode only.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
before_date: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ChatRecallClient {
|
||||
info: InitializeResult,
|
||||
context: PlatformExtensionContext,
|
||||
}
|
||||
|
||||
impl ChatRecallClient {
|
||||
pub fn new(context: PlatformExtensionContext) -> Result<Self> {
|
||||
let info = InitializeResult {
|
||||
protocol_version: ProtocolVersion::V_2025_03_26,
|
||||
capabilities: ServerCapabilities {
|
||||
tools: Some(ToolsCapability {
|
||||
list_changed: Some(false),
|
||||
}),
|
||||
resources: None,
|
||||
prompts: None,
|
||||
completions: None,
|
||||
experimental: None,
|
||||
logging: None,
|
||||
},
|
||||
server_info: Implementation {
|
||||
name: EXTENSION_NAME.to_string(),
|
||||
title: Some("Chat Recall".to_string()),
|
||||
version: "1.0.0".to_string(),
|
||||
icons: None,
|
||||
website_url: None,
|
||||
},
|
||||
instructions: Some(indoc! {r#"
|
||||
Chat Recall
|
||||
|
||||
Search past conversations and load session summaries when the user expects some memory or context.
|
||||
|
||||
Two modes:
|
||||
- Search mode: Use query with keywords/synonyms to find relevant messages
|
||||
- Load mode: Use session_id to get first and last messages of a specific session
|
||||
"#}.to_string()),
|
||||
};
|
||||
|
||||
Ok(Self { info, context })
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn handle_chat_recall(
|
||||
&self,
|
||||
arguments: Option<JsonObject>,
|
||||
) -> Result<Vec<Content>, String> {
|
||||
let arguments = arguments.ok_or("Missing arguments")?;
|
||||
|
||||
let session_id = arguments
|
||||
.get("session_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
if let Some(sid) = session_id {
|
||||
// LOAD MODE: Get session summary (first and last few messages)
|
||||
match SessionManager::get_session(&sid, true).await {
|
||||
Ok(loaded_session) => {
|
||||
let conversation = loaded_session.conversation.as_ref();
|
||||
|
||||
if conversation.is_none() {
|
||||
return Ok(vec![Content::text(format!(
|
||||
"Session {} has no conversation.",
|
||||
sid
|
||||
))]);
|
||||
}
|
||||
|
||||
let msgs = conversation.unwrap().messages();
|
||||
let total = msgs.len();
|
||||
|
||||
if total == 0 {
|
||||
return Ok(vec![Content::text(format!(
|
||||
"Session {} has no messages.",
|
||||
sid
|
||||
))]);
|
||||
}
|
||||
|
||||
let mut output = format!(
|
||||
"Session: {} (ID: {})\nWorking Dir: {}\nTotal Messages: {}\n\n",
|
||||
loaded_session.description,
|
||||
sid,
|
||||
loaded_session.working_dir.display(),
|
||||
total
|
||||
);
|
||||
|
||||
// Show first 3 messages
|
||||
let first_count = std::cmp::min(3, total);
|
||||
output.push_str("--- First Few Messages ---\n\n");
|
||||
for (idx, msg) in msgs.iter().take(first_count).enumerate() {
|
||||
output.push_str(&format!("{}. [{:?}] ", idx + 1, msg.role));
|
||||
for content in &msg.content {
|
||||
if let Some(text) = content.as_text() {
|
||||
output.push_str(text);
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
// Show last 3 messages (if different from first)
|
||||
if total > first_count {
|
||||
output.push_str("--- Last Few Messages ---\n\n");
|
||||
let last_count = std::cmp::min(3, total);
|
||||
let skip_count = total.saturating_sub(last_count);
|
||||
for (idx, msg) in msgs.iter().skip(skip_count).enumerate() {
|
||||
output.push_str(&format!(
|
||||
"{}. [{:?}] ",
|
||||
skip_count + idx + 1,
|
||||
msg.role
|
||||
));
|
||||
for content in &msg.content {
|
||||
if let Some(text) = content.as_text() {
|
||||
output.push_str(text);
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec![Content::text(output)])
|
||||
}
|
||||
Err(e) => Err(format!("Failed to load session: {}", e)),
|
||||
}
|
||||
} else {
|
||||
// SEARCH MODE: Search across all sessions
|
||||
let query = arguments
|
||||
.get("query")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Missing required parameter: query or session_id")?
|
||||
.to_string();
|
||||
|
||||
let limit = arguments
|
||||
.get("limit")
|
||||
.and_then(|v| v.as_i64())
|
||||
.map(|l| l as usize)
|
||||
.unwrap_or(10)
|
||||
.min(50);
|
||||
|
||||
let after_date = arguments
|
||||
.get("after_date")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
|
||||
.map(|dt| dt.with_timezone(&chrono::Utc));
|
||||
|
||||
let before_date = arguments
|
||||
.get("before_date")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
|
||||
.map(|dt| dt.with_timezone(&chrono::Utc));
|
||||
|
||||
// Exclude current session from results to avoid self-referential loops
|
||||
let exclude_session_id = self.context.session_id.clone();
|
||||
|
||||
match SessionManager::search_chat_history(
|
||||
&query,
|
||||
Some(limit),
|
||||
after_date,
|
||||
before_date,
|
||||
exclude_session_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(results) => {
|
||||
let formatted_results = if results.total_matches == 0 {
|
||||
format!("No results found for query: '{}'", query)
|
||||
} else {
|
||||
let mut output = format!(
|
||||
"Found {} matching message(s) across {} session(s) for query: '{}'\n\n",
|
||||
results.total_matches,
|
||||
results.results.len(),
|
||||
query
|
||||
);
|
||||
for (idx, result) in results.results.iter().enumerate() {
|
||||
output.push_str(&format!(
|
||||
"{}. Session: {} (ID: {})\n Working Dir: {}\n Last Activity: {}\n Showing {} of {} total message(s) in session:\n\n",
|
||||
idx + 1,
|
||||
result.session_description,
|
||||
result.session_id,
|
||||
result.session_working_dir,
|
||||
result.last_activity.format("%Y-%m-%d"),
|
||||
result.messages.len(),
|
||||
result.total_messages_in_session
|
||||
));
|
||||
|
||||
for (msg_idx, message) in result.messages.iter().enumerate() {
|
||||
output.push_str(&format!(
|
||||
" {}.{} [{}]\n {}\n\n",
|
||||
idx + 1,
|
||||
msg_idx + 1,
|
||||
message.role,
|
||||
message
|
||||
.content
|
||||
.lines()
|
||||
.map(|line| format!(" {}", line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
));
|
||||
}
|
||||
}
|
||||
output
|
||||
};
|
||||
Ok(vec![Content::text(formatted_results)])
|
||||
}
|
||||
Err(e) => Err(format!("Chat recall failed: {}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tools() -> Vec<Tool> {
|
||||
// Generate JSON schema from the ChatRecallParams struct
|
||||
let schema = schema_for!(ChatRecallParams);
|
||||
let schema_value =
|
||||
serde_json::to_value(schema).expect("Failed to serialize ChatRecallParams schema");
|
||||
|
||||
let input_schema = schema_value
|
||||
.as_object()
|
||||
.expect("Schema should be an object")
|
||||
.clone();
|
||||
|
||||
vec![Tool::new(
|
||||
"chat_recall".to_string(),
|
||||
indoc! {r#"
|
||||
Search past chat or load session summaries. Use when it is clear user expects some memory or context.
|
||||
|
||||
search mode (query): Use multiple keywords/synonyms. Returns messages grouped by session, ordered by recency. Supports date filters.
|
||||
load mode (session_id): Returns first/last 3 messages of a session.
|
||||
"#}
|
||||
.to_string(),
|
||||
input_schema,
|
||||
)
|
||||
.annotate(ToolAnnotations {
|
||||
title: Some("Recall past conversations".to_string()),
|
||||
read_only_hint: Some(true),
|
||||
destructive_hint: Some(false),
|
||||
idempotent_hint: Some(true),
|
||||
open_world_hint: Some(false),
|
||||
})]
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl McpClientTrait for ChatRecallClient {
|
||||
async fn list_resources(
|
||||
&self,
|
||||
_next_cursor: Option<String>,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<ListResourcesResult, Error> {
|
||||
Err(Error::TransportClosed)
|
||||
}
|
||||
|
||||
async fn read_resource(
|
||||
&self,
|
||||
_uri: &str,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<ReadResourceResult, Error> {
|
||||
Err(Error::TransportClosed)
|
||||
}
|
||||
|
||||
async fn list_tools(
|
||||
&self,
|
||||
_next_cursor: Option<String>,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<ListToolsResult, Error> {
|
||||
Ok(ListToolsResult {
|
||||
tools: Self::get_tools(),
|
||||
next_cursor: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn call_tool(
|
||||
&self,
|
||||
name: &str,
|
||||
arguments: Option<JsonObject>,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<CallToolResult, Error> {
|
||||
let content = match name {
|
||||
"chat_recall" => self.handle_chat_recall(arguments).await,
|
||||
_ => Err(format!("Unknown tool: {}", name)),
|
||||
};
|
||||
|
||||
match content {
|
||||
Ok(content) => Ok(CallToolResult::success(content)),
|
||||
Err(error) => Ok(CallToolResult::error(vec![Content::text(format!(
|
||||
"Error: {}",
|
||||
error
|
||||
))])),
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_prompts(
|
||||
&self,
|
||||
_next_cursor: Option<String>,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<ListPromptsResult, Error> {
|
||||
Err(Error::TransportClosed)
|
||||
}
|
||||
|
||||
async fn get_prompt(
|
||||
&self,
|
||||
_name: &str,
|
||||
_arguments: Value,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<GetPromptResult, Error> {
|
||||
Err(Error::TransportClosed)
|
||||
}
|
||||
|
||||
async fn subscribe(&self) -> mpsc::Receiver<ServerNotification> {
|
||||
mpsc::channel(1).1
|
||||
}
|
||||
|
||||
fn get_info(&self) -> Option<&InitializeResult> {
|
||||
Some(&self.info)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::agents::chat_recall_extension;
|
||||
use crate::agents::todo_extension;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -50,19 +49,6 @@ pub static PLATFORM_EXTENSIONS: Lazy<HashMap<&'static str, PlatformExtensionDef>
|
||||
},
|
||||
);
|
||||
|
||||
map.insert(
|
||||
chat_recall_extension::EXTENSION_NAME,
|
||||
PlatformExtensionDef {
|
||||
name: chat_recall_extension::EXTENSION_NAME,
|
||||
description:
|
||||
"Enable chat recall to search past conversations and load session summaries",
|
||||
default_enabled: true,
|
||||
client_factory: |ctx| {
|
||||
Box::new(chat_recall_extension::ChatRecallClient::new(ctx).unwrap())
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
map
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
mod agent;
|
||||
pub(crate) mod chat_recall_extension;
|
||||
pub mod extension;
|
||||
pub mod extension_malware_check;
|
||||
pub mod extension_manager;
|
||||
|
||||
@@ -116,11 +116,11 @@ pub fn manage_schedule_tool() -> Tool {
|
||||
PLATFORM_MANAGE_SCHEDULE_TOOL_NAME.to_string(),
|
||||
indoc! {r#"
|
||||
Manage scheduled recipe execution for this goose instance.
|
||||
|
||||
|
||||
Actions:
|
||||
- "list": List all scheduled jobs
|
||||
- "create": Create a new scheduled job from a recipe file
|
||||
- "run_now": Execute a scheduled job immediately
|
||||
- "run_now": Execute a scheduled job immediately
|
||||
- "pause": Pause a scheduled job
|
||||
- "unpause": Resume a paused job
|
||||
- "delete": Remove a scheduled job
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
use crate::conversation::message::MessageContent;
|
||||
use anyhow::Result;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ChatRecallResult {
|
||||
pub session_id: String,
|
||||
pub session_description: String,
|
||||
pub session_working_dir: String,
|
||||
pub last_activity: DateTime<Utc>,
|
||||
pub total_messages_in_session: usize,
|
||||
pub messages: Vec<ChatRecallMessage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ChatRecallMessage {
|
||||
pub role: String,
|
||||
pub content: String,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ChatRecallResults {
|
||||
pub results: Vec<ChatRecallResult>,
|
||||
pub total_matches: usize,
|
||||
}
|
||||
|
||||
type SqlQueryRow = (
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
DateTime<Utc>,
|
||||
String,
|
||||
String,
|
||||
DateTime<Utc>,
|
||||
);
|
||||
|
||||
type SessionMessageGroup = (
|
||||
String,
|
||||
String,
|
||||
DateTime<Utc>,
|
||||
Vec<(String, String, DateTime<Utc>)>,
|
||||
);
|
||||
|
||||
pub struct ChatHistorySearch<'a> {
|
||||
pool: &'a Pool<Sqlite>,
|
||||
query: &'a str,
|
||||
limit: usize,
|
||||
after_date: Option<DateTime<Utc>>,
|
||||
before_date: Option<DateTime<Utc>>,
|
||||
exclude_session_id: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> ChatHistorySearch<'a> {
|
||||
pub fn new(
|
||||
pool: &'a Pool<Sqlite>,
|
||||
query: &'a str,
|
||||
limit: Option<usize>,
|
||||
after_date: Option<DateTime<Utc>>,
|
||||
before_date: Option<DateTime<Utc>>,
|
||||
exclude_session_id: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
pool,
|
||||
query,
|
||||
limit: limit.unwrap_or(10),
|
||||
after_date,
|
||||
before_date,
|
||||
exclude_session_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute(self) -> Result<ChatRecallResults> {
|
||||
let keywords = self.parse_keywords();
|
||||
if keywords.is_empty() {
|
||||
return Ok(ChatRecallResults {
|
||||
results: vec![],
|
||||
total_matches: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let rows = self.fetch_rows(&keywords).await?;
|
||||
let session_messages = self.process_rows(rows);
|
||||
let session_totals = self.get_session_totals(&session_messages).await?;
|
||||
let results = self.convert_to_results(session_messages, session_totals);
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
async fn fetch_rows(&self, keywords: &[String]) -> Result<Vec<SqlQueryRow>> {
|
||||
let sql = self.build_sql(keywords);
|
||||
let mut query_builder = sqlx::query_as::<_, SqlQueryRow>(&sql);
|
||||
|
||||
for keyword in keywords {
|
||||
query_builder = query_builder.bind(keyword);
|
||||
}
|
||||
|
||||
if let Some(exclude_id) = &self.exclude_session_id {
|
||||
query_builder = query_builder.bind(exclude_id);
|
||||
}
|
||||
|
||||
if let Some(after) = self.after_date {
|
||||
query_builder = query_builder.bind(after);
|
||||
}
|
||||
if let Some(before) = self.before_date {
|
||||
query_builder = query_builder.bind(before);
|
||||
}
|
||||
|
||||
query_builder = query_builder.bind(self.limit as i64);
|
||||
|
||||
Ok(query_builder.fetch_all(self.pool).await?)
|
||||
}
|
||||
|
||||
fn parse_keywords(&self) -> Vec<String> {
|
||||
self.query
|
||||
.split_whitespace()
|
||||
.map(|word| format!("%{}%", word.to_lowercase()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build_sql(&self, keywords: &[String]) -> String {
|
||||
let mut sql = String::from(
|
||||
r#"
|
||||
SELECT
|
||||
s.id as session_id,
|
||||
s.description as session_description,
|
||||
s.working_dir as session_working_dir,
|
||||
s.created_at as session_created_at,
|
||||
m.role,
|
||||
m.content_json,
|
||||
m.timestamp
|
||||
FROM messages m
|
||||
INNER JOIN sessions s ON m.session_id = s.id
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM json_each(m.content_json)
|
||||
WHERE json_extract(value, '$.type') = 'text'
|
||||
AND (
|
||||
"#,
|
||||
);
|
||||
|
||||
for (i, _) in keywords.iter().enumerate() {
|
||||
if i > 0 {
|
||||
sql.push_str(" OR ");
|
||||
}
|
||||
sql.push_str("LOWER(json_extract(value, '$.text')) LIKE ?");
|
||||
}
|
||||
|
||||
sql.push_str(
|
||||
r#"
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
|
||||
if self.exclude_session_id.is_some() {
|
||||
sql.push_str(" AND s.id != ?");
|
||||
}
|
||||
|
||||
if self.after_date.is_some() {
|
||||
sql.push_str(" AND m.timestamp >= ?");
|
||||
}
|
||||
if self.before_date.is_some() {
|
||||
sql.push_str(" AND m.timestamp <= ?");
|
||||
}
|
||||
|
||||
sql.push_str(" ORDER BY m.timestamp DESC LIMIT ?");
|
||||
|
||||
sql
|
||||
}
|
||||
|
||||
fn process_rows(&self, rows: Vec<SqlQueryRow>) -> HashMap<String, SessionMessageGroup> {
|
||||
let mut session_messages: HashMap<String, SessionMessageGroup> = HashMap::new();
|
||||
|
||||
for (
|
||||
session_id,
|
||||
session_description,
|
||||
session_working_dir,
|
||||
session_created_at,
|
||||
role,
|
||||
content_json,
|
||||
timestamp,
|
||||
) in rows
|
||||
{
|
||||
if let Ok(content_vec) = serde_json::from_str::<Vec<MessageContent>>(&content_json) {
|
||||
let text_parts = Self::extract_text_content(content_vec);
|
||||
|
||||
if !text_parts.is_empty() {
|
||||
let entry = session_messages.entry(session_id.clone()).or_insert((
|
||||
session_description.clone(),
|
||||
session_working_dir.clone(),
|
||||
session_created_at,
|
||||
Vec::new(),
|
||||
));
|
||||
entry
|
||||
.3
|
||||
.push((role.clone(), text_parts.join("\n"), timestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
session_messages
|
||||
}
|
||||
|
||||
fn extract_text_content(content_vec: Vec<MessageContent>) -> Vec<String> {
|
||||
content_vec
|
||||
.into_iter()
|
||||
.filter_map(|content| match content {
|
||||
MessageContent::Text(ref tc) => Some(tc.text.clone()),
|
||||
MessageContent::ToolRequest(ref tr) => {
|
||||
Some(format!("[Tool: {}]", tr.to_readable_string()))
|
||||
}
|
||||
MessageContent::ToolResponse(_) => Some("[Tool Response]".to_string()),
|
||||
MessageContent::Thinking(ref t) => Some(format!("[Thinking: {}]", t.thinking)),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn get_session_totals(
|
||||
&self,
|
||||
session_messages: &HashMap<String, SessionMessageGroup>,
|
||||
) -> Result<HashMap<String, usize>> {
|
||||
let mut session_totals: HashMap<String, usize> = HashMap::new();
|
||||
for session_id in session_messages.keys() {
|
||||
let count: i64 =
|
||||
sqlx::query_scalar("SELECT COUNT(*) FROM messages WHERE session_id = ?")
|
||||
.bind(session_id)
|
||||
.fetch_one(self.pool)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
session_totals.insert(session_id.clone(), count as usize);
|
||||
}
|
||||
Ok(session_totals)
|
||||
}
|
||||
|
||||
fn convert_to_results(
|
||||
&self,
|
||||
session_messages: HashMap<String, SessionMessageGroup>,
|
||||
session_totals: HashMap<String, usize>,
|
||||
) -> ChatRecallResults {
|
||||
let mut results: Vec<ChatRecallResult> = session_messages
|
||||
.into_iter()
|
||||
.map(
|
||||
|(session_id, (description, working_dir, _created_at, messages))| {
|
||||
let message_vec: Vec<ChatRecallMessage> = messages
|
||||
.into_iter()
|
||||
.map(|(role, content, timestamp)| ChatRecallMessage {
|
||||
role,
|
||||
content,
|
||||
timestamp,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let last_activity = message_vec
|
||||
.iter()
|
||||
.map(|m| m.timestamp)
|
||||
.max()
|
||||
.unwrap_or_else(chrono::Utc::now);
|
||||
|
||||
let total_messages_in_session =
|
||||
session_totals.get(&session_id).copied().unwrap_or(0);
|
||||
|
||||
ChatRecallResult {
|
||||
session_id,
|
||||
session_description: description,
|
||||
session_working_dir: working_dir,
|
||||
last_activity,
|
||||
total_messages_in_session,
|
||||
messages: message_vec,
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
results.sort_by(|a, b| b.last_activity.cmp(&a.last_activity));
|
||||
|
||||
let total_matches = results.iter().map(|r| r.messages.len()).sum();
|
||||
ChatRecallResults {
|
||||
results,
|
||||
total_matches,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
mod chat_history_search;
|
||||
pub mod extension_data;
|
||||
mod legacy;
|
||||
pub mod session_manager;
|
||||
|
||||
@@ -69,8 +69,6 @@ pub struct SessionInsights {
|
||||
total_tokens: i64,
|
||||
}
|
||||
|
||||
pub type SessionId = String;
|
||||
|
||||
impl SessionUpdateBuilder {
|
||||
fn new(session_id: String) -> Self {
|
||||
Self {
|
||||
@@ -243,19 +241,6 @@ impl SessionManager {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn search_chat_history(
|
||||
query: &str,
|
||||
limit: Option<usize>,
|
||||
after_date: Option<DateTime<Utc>>,
|
||||
before_date: Option<DateTime<Utc>>,
|
||||
exclude_session_id: Option<String>,
|
||||
) -> Result<crate::session::chat_history_search::ChatRecallResults> {
|
||||
Self::instance()
|
||||
.await?
|
||||
.search_chat_history(query, limit, after_date, before_date, exclude_session_id)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SessionStorage {
|
||||
@@ -980,28 +965,6 @@ impl SessionStorage {
|
||||
|
||||
self.get_session(&session.id, true).await
|
||||
}
|
||||
|
||||
async fn search_chat_history(
|
||||
&self,
|
||||
query: &str,
|
||||
limit: Option<usize>,
|
||||
after_date: Option<DateTime<Utc>>,
|
||||
before_date: Option<DateTime<Utc>>,
|
||||
exclude_session_id: Option<String>,
|
||||
) -> Result<crate::session::chat_history_search::ChatRecallResults> {
|
||||
use crate::session::chat_history_search::ChatHistorySearch;
|
||||
|
||||
ChatHistorySearch::new(
|
||||
&self.pool,
|
||||
query,
|
||||
limit,
|
||||
after_date,
|
||||
before_date,
|
||||
exclude_session_id,
|
||||
)
|
||||
.execute()
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -8,13 +8,13 @@ const DesktopInstallButtons = () => {
|
||||
<div className="pill-button">
|
||||
<Link
|
||||
className="button button--primary button--lg"
|
||||
to="https://github.com/block/goose/releases/download/stable/goose.zip"
|
||||
to="https://github.com/block/goose/releases/download/stable/Goose.zip"
|
||||
>
|
||||
<IconDownload /> macOS Silicon
|
||||
</Link>
|
||||
<Link
|
||||
className="button button--primary button--lg"
|
||||
to="https://github.com/block/goose/releases/download/stable/goose_intel_mac.zip"
|
||||
to="https://github.com/block/goose/releases/download/stable/Goose_intel_mac.zip"
|
||||
>
|
||||
<IconDownload /> macOS Intel
|
||||
</Link>
|
||||
|
||||
@@ -8,7 +8,7 @@ const WindowsDesktopInstallButtons = () => {
|
||||
<div className="pill-button">
|
||||
<Link
|
||||
className="button button--primary button--lg"
|
||||
to="https://github.com/block/goose/releases/download/stable/goose-win32-x64.zip"
|
||||
to="https://github.com/block/goose/releases/download/stable/Goose-win32-x64.zip"
|
||||
>
|
||||
<IconDownload /> Windows
|
||||
</Link>
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
version: 1.0.0
|
||||
title: Code Documentation Generator
|
||||
description: Automatically generates comprehensive documentation for codebases by analyzing source files, extracting APIs, and creating formatted documentation with examples and best practices
|
||||
author:
|
||||
contact: ARYPROGRAMMER
|
||||
|
||||
activities:
|
||||
- Scan and analyze source code files in the project
|
||||
- Extract functions, classes, methods, and their signatures
|
||||
- Generate documentation with descriptions and usage examples
|
||||
- Create API reference documentation with type information
|
||||
- Build interactive documentation with cross-references
|
||||
- Store documentation patterns in repository files for consistency
|
||||
|
||||
prompt: |
|
||||
You are a Code Documentation Generator that creates comprehensive, well-structured documentation for codebases.
|
||||
Your goal is to analyze source code, extract relevant information, and generate clear, helpful documentation.
|
||||
|
||||
Key capabilities:
|
||||
- Analyze code structure and extract documentable elements
|
||||
- Generate consistent documentation following best practices
|
||||
- Create usage examples and code snippets
|
||||
- Build cross-referenced documentation with links
|
||||
- Store project-specific documentation styles in repository files
|
||||
- Research documentation standards for the detected language
|
||||
|
||||
IMPORTANT: Always detect the programming language first and apply language-specific documentation standards.
|
||||
|
||||
parameters:
|
||||
- key: source_path
|
||||
input_type: string
|
||||
requirement: optional
|
||||
default: "."
|
||||
description: "Path to the source code directory or specific file to document"
|
||||
|
||||
- key: doc_format
|
||||
input_type: string
|
||||
requirement: optional
|
||||
default: "html"
|
||||
description: "Documentation format: 'markdown', 'html', 'restructuredtext', 'javadoc', 'jsdoc'"
|
||||
|
||||
- key: include_examples
|
||||
input_type: string
|
||||
requirement: optional
|
||||
default: "true"
|
||||
description: "Whether to generate usage examples for documented items (true/false)"
|
||||
|
||||
- key: output_location
|
||||
input_type: string
|
||||
requirement: optional
|
||||
default: "./docs"
|
||||
description: "Directory where documentation files will be saved"
|
||||
|
||||
- key: doc_style
|
||||
input_type: string
|
||||
requirement: optional
|
||||
default: "comprehensive"
|
||||
description: "Documentation style: 'minimal' (brief descriptions), 'standard' (descriptions + params), 'comprehensive' (full with examples and cross-refs)"
|
||||
|
||||
- key: language_hint
|
||||
input_type: string
|
||||
requirement: optional
|
||||
default: ""
|
||||
description: "Optional: specify programming language if auto-detection is insufficient (e.g., 'python', 'javascript', 'rust', 'go')"
|
||||
|
||||
extensions:
|
||||
- type: builtin
|
||||
name: developer
|
||||
display_name: Developer
|
||||
timeout: 600
|
||||
bundled: true
|
||||
description: For file operations, code analysis, and documentation generation
|
||||
|
||||
- type: builtin
|
||||
name: browser
|
||||
display_name: Browser
|
||||
timeout: 300
|
||||
bundled: true
|
||||
description: For researching documentation standards and best practices
|
||||
|
||||
settings:
|
||||
temperature: 0.3
|
||||
|
||||
instructions: |
|
||||
Generate comprehensive documentation for the codebase with the following configuration:
|
||||
- Source Path: {{ source_path }}
|
||||
- Documentation Format: {{ doc_format }}
|
||||
- Include Examples: {{ include_examples }}
|
||||
- Output Location: {{ output_location }}
|
||||
- Documentation Style: {{ doc_style }}
|
||||
{% if language_hint %}
|
||||
- Language Hint: {{ language_hint }}
|
||||
{% endif %}
|
||||
|
||||
Follow this workflow:
|
||||
|
||||
## Phase 1: Project Analysis and Setup
|
||||
|
||||
1. **Analyze the codebase structure:**
|
||||
- Navigate to {{ source_path }}
|
||||
- Identify all source code files (common extensions: .py, .js, .ts, .rs, .go, .java, .cpp, .rb, etc.)
|
||||
- {% if language_hint %}Focus on {{ language_hint }} files{% else %}Detect the primary programming language(s){% endif %}
|
||||
- Map the project structure (modules, packages, components)
|
||||
|
||||
2. **Check for existing documentation:**
|
||||
- Look for existing doc files, READMEs, or inline documentation
|
||||
- Check if there are documentation standards already in use
|
||||
- Check for `.goosehints`, `AGENTS.md`, or `DOCS_GUIDELINES.md` files with documentation preferences
|
||||
- If documentation already exists, analyze it to maintain consistency in style and format
|
||||
- Preserve existing documentation structure while filling in gaps for undocumented items
|
||||
- Extract inline docstrings/comments to use as foundation for generated documentation
|
||||
|
||||
3. **Research documentation standards:**
|
||||
- Use the browser to look up the standard documentation format for the detected language
|
||||
- For Python: Look for PEP 257 docstring conventions and Sphinx documentation
|
||||
- For JavaScript/TypeScript: Look for JSDoc standards
|
||||
- For Rust: Look for rustdoc conventions
|
||||
- For Go: Look for godoc conventions
|
||||
- Check if `.goosehints` or `AGENTS.md` already exists and use those guidelines if present
|
||||
- If no local guidelines exist, prepare to create them based on detected language standards
|
||||
|
||||
## Phase 2: Code Analysis and Extraction
|
||||
|
||||
4. **Extract documentable elements:**
|
||||
For each source file, identify and extract:
|
||||
- **Functions/Methods:**
|
||||
* Name and signature
|
||||
* Parameters with types (if available)
|
||||
* Return type
|
||||
* Existing comments or docstrings
|
||||
* Purpose based on implementation analysis
|
||||
* Cyclomatic complexity level (simple: 1-5 branches, moderate: 6-10, complex: 11+)
|
||||
|
||||
- **Classes/Structs/Types:**
|
||||
* Name and inheritance/implementation
|
||||
* Constructor/initialization
|
||||
* Properties/fields
|
||||
* Methods
|
||||
* Usage patterns
|
||||
|
||||
- **Constants and Variables:**
|
||||
* Public constants
|
||||
* Configuration variables
|
||||
* Exported values
|
||||
|
||||
- **Modules/Packages:**
|
||||
* Module purpose (inferred from: exported functions, file name, directory structure, and existing comments)
|
||||
* Exported interfaces
|
||||
* Dependencies
|
||||
* Note: If purpose cannot be confidently determined, use placeholder like "TODO: Add module description" and flag for manual review
|
||||
|
||||
5. **Analyze code context:**
|
||||
- Understand the purpose of each code element from its implementation
|
||||
- Identify input/output patterns
|
||||
- Note error handling and edge cases
|
||||
- Recognize common design patterns used
|
||||
|
||||
## Phase 3: Documentation Generation
|
||||
|
||||
6. **Create documentation structure:**
|
||||
- Create {{ output_location }} directory if it doesn't exist
|
||||
- Organize documentation by module/package structure
|
||||
- Create an index/table of contents file
|
||||
|
||||
7. **Generate documentation for each element:**
|
||||
|
||||
{% if doc_style == "minimal" %}
|
||||
For minimal style, include:
|
||||
- Brief one-line description
|
||||
- Function/method signature
|
||||
- Parameter list
|
||||
{% elif doc_style == "standard" %}
|
||||
For standard style, include:
|
||||
- Clear description of purpose
|
||||
- Full parameter documentation with types and descriptions
|
||||
- Return value documentation
|
||||
- Basic usage information
|
||||
{% else %}
|
||||
For comprehensive style, include:
|
||||
- Detailed description of purpose and behavior
|
||||
- Complete parameter documentation with types, descriptions, and constraints
|
||||
- Return value documentation with possible values
|
||||
- Exceptions/errors that may be raised
|
||||
{% if include_examples == "true" %}
|
||||
- Usage examples demonstrating common scenarios
|
||||
- Code snippets showing best practices
|
||||
{% endif %}
|
||||
- Links to related functions/classes
|
||||
- Notes on performance, thread-safety, or other important considerations
|
||||
{% endif %}
|
||||
|
||||
8. **Format documentation:**
|
||||
{% if doc_format == "markdown" %}
|
||||
- Use Markdown formatting with proper headers (##, ###)
|
||||
- Create code blocks with language-specific syntax highlighting
|
||||
- Use tables for parameter lists
|
||||
- Add hyperlinks using [text](#anchor) for internal links and [text](file.md#section) for cross-file references
|
||||
- Create anchor links using header IDs (e.g., ## Function Name creates #function-name anchor)
|
||||
{% elif doc_format == "html" %}
|
||||
- Generate valid HTML5 documentation
|
||||
- Include CSS styling for readability
|
||||
- Add navigation links using <a href="..."> tags
|
||||
- Create searchable index
|
||||
{% elif doc_format == "restructuredtext" %}
|
||||
- Use reStructuredText formatting
|
||||
- Proper directive usage for code blocks
|
||||
- Cross-reference using :ref:`label` and :doc:`filename`
|
||||
- Use :py:func:`module.function` for Python cross-references
|
||||
{% elif doc_format == "javadoc" or doc_format == "jsdoc" %}
|
||||
- Generate language-specific doc comments
|
||||
- Use @param, @return, @throws tags
|
||||
- Include @example tags for usage
|
||||
- Use @see and @link for cross-references
|
||||
{% endif %}
|
||||
|
||||
{% if include_examples == "true" %}
|
||||
9. **Generate usage examples:**
|
||||
For each documented function/class:
|
||||
- Generate synthetic but realistic usage examples that demonstrate the API
|
||||
- Show input data and expected output based on the function's signature and implementation
|
||||
- Demonstrate error handling where applicable
|
||||
- Include edge cases where relevant
|
||||
- Mark examples clearly as "Example Usage" or "Code Example"
|
||||
- Ensure examples use the actual function signatures from the codebase
|
||||
- If you find actual usage in test files or other parts of the codebase, you may reference or adapt those patterns
|
||||
- Keep examples simple and focused on demonstrating the specific functionality
|
||||
{% endif %}
|
||||
|
||||
## Phase 4: Cross-Referencing and Index
|
||||
|
||||
10. **Build cross-references:**
|
||||
{% if doc_format == "markdown" %}
|
||||
- Link related functions and classes using [FunctionName](#functionname) format
|
||||
- Create "See Also" sections with links to related documentation
|
||||
- Build dependency graphs showing relationships
|
||||
- Add links from examples back to detailed docs using relative paths
|
||||
{% elif doc_format == "html" %}
|
||||
- Link related functions and classes using <a href="#anchor"> tags
|
||||
- Create "See Also" sections
|
||||
- Build dependency graphs showing relationships
|
||||
- Add links from examples back to detailed docs
|
||||
{% elif doc_format == "restructuredtext" %}
|
||||
- Use :ref:`label` for internal references
|
||||
- Use :doc:`filename` for cross-file references
|
||||
- Create "See Also" sections with proper reST directives
|
||||
{% else %}
|
||||
- Use format-appropriate linking mechanisms
|
||||
- Create "See Also" sections
|
||||
- Build relationship documentation
|
||||
{% endif %}
|
||||
|
||||
11. **Generate documentation index:**
|
||||
- Create a master index file (index.md, index.html, etc.)
|
||||
- Organize by module/package
|
||||
- Add search functionality hints
|
||||
- Include quick reference table
|
||||
- Add getting started guide if this is new documentation
|
||||
|
||||
## Phase 5: Quality Check and Finalization
|
||||
|
||||
12. **Review generated documentation:**
|
||||
- Check for completeness (all public APIs documented)
|
||||
- Verify example code correctness
|
||||
- Ensure consistent formatting
|
||||
- Validate all cross-references work
|
||||
- Check for typos and grammar issues
|
||||
|
||||
13. **Save documentation patterns:**
|
||||
- Create or update `.goosehints` file in {{ source_path }} with documentation preferences
|
||||
- Store documentation style, format preferences, and project-specific conventions
|
||||
- Include example patterns that worked well
|
||||
- Consider updating `AGENTS.md` with documentation standards for team reference
|
||||
- Optionally create `DOCS_GUIDELINES.md` with detailed documentation style guide
|
||||
|
||||
14. **Generate summary report:**
|
||||
- List all files documented
|
||||
- Count of functions, classes, and modules documented
|
||||
- Location of generated documentation
|
||||
- Suggestions for improving source code documentation
|
||||
- Any warnings or issues encountered
|
||||
|
||||
## Best Practices to Follow:
|
||||
|
||||
- **Clarity:** Use clear, simple language avoiding jargon when possible
|
||||
- **Consistency:** Maintain consistent format and style throughout
|
||||
- **Accuracy:** Ensure documentation matches actual code behavior
|
||||
- **Completeness:** Document all public APIs and exported elements
|
||||
- **Examples:** Provide practical, real-world usage examples
|
||||
- **Maintenance:** Note any areas where code comments should be added
|
||||
|
||||
## Output Format:
|
||||
|
||||
Provide a clear summary including:
|
||||
1. Number of files analyzed
|
||||
2. Number of elements documented (functions, classes, etc.)
|
||||
3. Location of generated documentation files
|
||||
4. Quick preview of the documentation structure
|
||||
5. Suggestions for improving inline code documentation
|
||||
6. Confirmation that documentation guidelines have been saved to `.goosehints` or `AGENTS.md`
|
||||
|
||||
**Note**: Documentation preferences and patterns are stored in repository files (`.goosehints`, `AGENTS.md`, or `DOCS_GUIDELINES.md`) rather than memory to avoid cluttering the LLM context on unrelated tasks.
|
||||
|
||||
@@ -65,8 +65,8 @@ module.exports = {
|
||||
{
|
||||
name: '@electron-forge/maker-deb',
|
||||
config: {
|
||||
name: 'goose',
|
||||
bin: 'goose',
|
||||
name: 'Goose',
|
||||
bin: 'Goose',
|
||||
maintainer: 'Block, Inc.',
|
||||
homepage: 'https://block.github.io/goose/',
|
||||
categories: ['Development'],
|
||||
@@ -79,8 +79,8 @@ module.exports = {
|
||||
{
|
||||
name: '@electron-forge/maker-rpm',
|
||||
config: {
|
||||
name: 'goose',
|
||||
bin: 'goose',
|
||||
name: 'Goose',
|
||||
bin: 'Goose',
|
||||
maintainer: 'Block, Inc.',
|
||||
homepage: 'https://block.github.io/goose/',
|
||||
categories: ['Development'],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "goose-app",
|
||||
"productName": "goose",
|
||||
"productName": "Goose",
|
||||
"version": "1.9.0",
|
||||
"description": "goose App",
|
||||
"description": "Goose App",
|
||||
"engines": {
|
||||
"node": "^22.17.1"
|
||||
},
|
||||
@@ -16,11 +16,11 @@
|
||||
"start:test-error": "GOOSE_TEST_ERROR=true electron-forge start",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"bundle:default": "node scripts/prepare-platform-binaries.js && npm run make && (cd out/goose-darwin-arm64 && ditto -c -k --sequesterRsrc --keepParent goose.app goose.zip) || echo 'out/goose-darwin-arm64 not found; either the binary is not built or you are not on macOS'",
|
||||
"bundle:alpha": "ALPHA=true node scripts/prepare-platform-binaries.js && ALPHA=true npm run make && (cd out/goose-darwin-arm64 && ditto -c -k --sequesterRsrc --keepParent goose.app goose_alpha.zip) || echo 'out/goose-darwin-arm64 not found; either the binary is not built or you are not on macOS'",
|
||||
"bundle:default": "node scripts/prepare-platform-binaries.js && npm run make && (cd out/Goose-darwin-arm64 && ditto -c -k --sequesterRsrc --keepParent Goose.app Goose.zip) || echo 'out/Goose-darwin-arm64 not found; either the binary is not built or you are not on macOS'",
|
||||
"bundle:alpha": "ALPHA=true node scripts/prepare-platform-binaries.js && ALPHA=true npm run make && (cd out/Goose-darwin-arm64 && ditto -c -k --sequesterRsrc --keepParent Goose.app Goose_alpha.zip) || echo 'out/Goose-darwin-arm64 not found; either the binary is not built or you are not on macOS'",
|
||||
"bundle:windows": "node scripts/build-main.js && ELECTRON_PLATFORM=win32 node scripts/prepare-platform-binaries.js && npm run make -- --platform=win32 --arch=x64",
|
||||
"bundle:intel": "node scripts/prepare-platform-binaries.js && npm run make -- --arch=x64 && cd out/goose-darwin-x64 && ditto -c -k --sequesterRsrc --keepParent goose.app goose_intel_mac.zip",
|
||||
"debug": "echo 'run --remote-debugging-port=8315' && lldb out/goose-darwin-arm64/goose.app",
|
||||
"bundle:intel": "node scripts/prepare-platform-binaries.js && npm run make -- --arch=x64 && cd out/Goose-darwin-x64 && ditto -c -k --sequesterRsrc --keepParent Goose.app Goose_intel_mac.zip",
|
||||
"debug": "echo 'run --remote-debugging-port=8315' && lldb out/Goose-darwin-arm64/Goose.app",
|
||||
"test-e2e": "npm run generate-api && playwright test",
|
||||
"test-e2e:dev": "npm run generate-api && playwright test --reporter=list --retries=0 --max-failures=1",
|
||||
"test-e2e:ui": "npm run generate-api && playwright test --ui",
|
||||
|
||||
Reference in New Issue
Block a user