Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions services/ask-ai-bot/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
# Include documentation (only docs subfolder needed)
!documentation/docs/

# Include codebase source for code browsing
!ui/
!crates/

# Exclude unnecessary files from included directories
services/ask-ai-bot/node_modules
services/ask-ai-bot/dist
Expand All @@ -19,3 +23,6 @@ services/ask-ai-bot/.discraft

# Exclude large assets from docs that aren't needed for search
documentation/docs/assets

# Exclude build artifacts from crates
crates/**/target
3 changes: 3 additions & 0 deletions services/ask-ai-bot/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ AI_MODEL=claude-sonnet-4-6

# Path to documentation directory (default: ./docs in Docker, for local dev use ../../documentation/docs)
DOCS_PATH=../../documentation/docs

# Path to codebase root (default: ../.. relative to this service, /app/codebase in Docker)
CODEBASE_PATH=../..
5 changes: 5 additions & 0 deletions services/ask-ai-bot/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ RUN bun run build
FROM base AS production
ENV NODE_ENV=production
ENV DOCS_PATH=/app/docs
ENV CODEBASE_PATH=/app/codebase

COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
Expand All @@ -24,6 +25,10 @@ COPY --from=build /app/package.json ./
# Copy documentation (only docs/ subdirectory with markdown files)
COPY documentation/docs ./docs

# Copy codebase source for code browsing
COPY ui/ ./codebase/ui/
COPY crates ./codebase/crates
Comment thread
The-Best-Codes marked this conversation as resolved.

# Empty index.ts for discraft start to detect
RUN touch index.ts

Expand Down
60 changes: 46 additions & 14 deletions services/ask-ai-bot/utils/ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,35 @@ export async function answerQuestion({

for await (const event of result.fullStream) {
if (event.type === "tool-call") {
if (event.toolName === "search_docs" && statusMessage) {
if (statusMessage) {
try {
await statusMessage.edit("Searching the docs...");
} catch (error) {
logger.verbose("Failed to update status message:", error);
}
} else if (event.toolName === "view_docs" && statusMessage) {
const input = event.input as { filePaths?: string | string[] };
const filePaths = input.filePaths;
const pathArray = Array.isArray(filePaths) ? filePaths : [filePaths];
const pagesText = pathArray.length === 1 ? "page" : "pages";
try {
await statusMessage.edit(
`Viewing ${pathArray.length} ${pagesText}...`,
);
if (event.toolName === "search_docs") {
await statusMessage.edit("Searching the docs...");
} else if (event.toolName === "view_docs") {
const input = event.input as { filePaths?: string | string[] };
const filePaths = input.filePaths;
const pathArray = Array.isArray(filePaths)
? filePaths
: [filePaths];
const pagesText = pathArray.length === 1 ? "page" : "pages";
await statusMessage.edit(
`Viewing ${pathArray.length} ${pagesText}...`,
);
} else if (event.toolName === "search_codebase") {
await statusMessage.edit("Searching the codebase...");
} else if (event.toolName === "view_codebase") {
const input = event.input as { filePaths?: string | string[] };
const filePaths = input.filePaths;
const pathArray = Array.isArray(filePaths)
? filePaths
: [filePaths];
const filesText = pathArray.length === 1 ? "file" : "files";
await statusMessage.edit(
`Reading ${pathArray.length} source ${filesText}...`,
);
} else if (event.toolName === "list_codebase_files") {
await statusMessage.edit("Exploring project structure...");
}
} catch (error) {
logger.verbose("Failed to update status message:", error);
}
Expand All @@ -91,6 +105,24 @@ export async function answerQuestion({
if (pathArray.length > 0) {
tracker.recordViewCall(pathArray);
}
} else if (event.toolName === "search_codebase") {
const resultText = String(event.output);
const matchCount = (resultText.match(/\*\*[^*]+:\d+\*\*/g) || [])
.length;
tracker.recordCodeSearchCall(matchCount);
} else if (event.toolName === "view_codebase") {
const input = event.input as { filePaths?: string | string[] };
const filePaths = input.filePaths;
const pathArray = Array.isArray(filePaths)
? filePaths
: filePaths
? [filePaths]
: [];
if (pathArray.length > 0) {
tracker.recordCodeViewCall(pathArray);
}
} else if (event.toolName === "list_codebase_files") {
tracker.recordListDir();
}
}
}
Expand Down
17 changes: 15 additions & 2 deletions services/ask-ai-bot/utils/ai/system-prompt.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import dedent from "dedent";

export const MAX_STEPS = 10;
export const MAX_STEPS = 15;

export function buildSystemPrompt(serverContext?: string): string {
let prompt = dedent`You are a helpful assistant in the goose Discord server.
Your role is to provide assistance and answer questions about codename goose, an open-source AI agent developed by Block. codename goose's website is \`https://block.github.io/goose\`. Your answers should be short and to the point. Always assume that a user's question is related to codename goose unless they specifically state otherwise. DO NOT capitalize "goose" or "codename goose".

You can perform a maximum of ${MAX_STEPS} steps (tool calls, text outputs, etc.). If you exceed this limit, no response will be provided to the user. BEFORE you reach the limit, STOP calling tools, respond to the user, and don't call any tools after your final response until the user asks another question.

When answering questions about goose:
## Documentation tools
When answering questions about how to use goose, configuration, setup, etc.:
1. Use the \`search_docs\` tool to find relevant documentation
2. Use the \`view_docs\` tool to read documentation (read multiple relevant files to get the full picture)
3. Iterate on steps 1 and 2 (not necessarily in order) until you have a deep understanding of the question and relevant documentation
4. Cite the documentation source in your response (using its Web URL)

## Codebase tools
When answering questions about how goose works internally, its architecture, implementation details, or when users ask about specific code:
1. Use \`search_codebase\` to grep for relevant code patterns (function names, struct names, error messages, etc.)
2. Use \`list_codebase_files\` to explore the project structure and find relevant directories
3. Use \`view_codebase\` to read the actual source code files
4. The codebase is split into two main areas:
- \`crates/\` - Rust backend code (core agent logic, CLI, server, MCP extensions)
- \`ui/\` - Electron/TypeScript desktop application and other UIs
5. Cite the source file in your response (using its GitHub URL)
Comment thread
The-Best-Codes marked this conversation as resolved.

You can combine documentation and codebase tools in a single response when needed. For example, if a user asks how a feature works, you might search the docs for usage instructions AND search the codebase for the implementation.

When providing links, wrap the URL in angle brackets (e.g., \`<https://example.com>\` or \`[Example](<https://example.com>)\`) to prevent excessive link previews. Do not use backtick characters around the URL.`;

if (serverContext) {
Expand Down
61 changes: 47 additions & 14 deletions services/ask-ai-bot/utils/ai/tool-tracker.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,72 @@
export class ToolTracker {
private searchCalls: number = 0;
private searchResults: Set<string> = new Set();
private viewedPaths: Set<string> = new Set();
private docSearchCalls: number = 0;
private docSearchResults: Set<string> = new Set();
private viewedDocPaths: Set<string> = new Set();
private codeSearchCalls: number = 0;
private codeSearchResults: number = 0;
private viewedCodePaths: Set<string> = new Set();
private listedDirs: number = 0;

recordSearchCall(results: string[]): void {
this.searchCalls++;
results.forEach((result) => this.searchResults.add(result));
this.docSearchCalls++;
results.forEach((result) => this.docSearchResults.add(result));
}

recordViewCall(filePaths: string | string[]): void {
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
paths.forEach((path) => this.viewedPaths.add(path));
paths.forEach((path) => this.viewedDocPaths.add(path));
}

recordCodeSearchCall(resultCount: number): void {
this.codeSearchCalls++;
this.codeSearchResults += resultCount;
}

recordCodeViewCall(filePaths: string | string[]): void {
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
paths.forEach((path) => this.viewedCodePaths.add(path));
}

recordListDir(): void {
this.listedDirs++;
}

getSummary(): string {
const parts: string[] = [];

if (this.searchCalls > 0) {
const resultCount = this.searchResults.size;
const timesText = this.searchCalls === 1 ? "time" : "times";
if (this.docSearchCalls > 0) {
const resultCount = this.docSearchResults.size;
const timesText = this.docSearchCalls === 1 ? "time" : "times";
const resultsText = resultCount === 1 ? "result" : "results";
parts.push(
`searched ${this.searchCalls} ${timesText} with ${resultCount} ${resultsText}`,
`searched docs ${this.docSearchCalls} ${timesText} with ${resultCount} ${resultsText}`,
);
}

if (this.viewedPaths.size > 0) {
const pageCount = this.viewedPaths.size;
if (this.viewedDocPaths.size > 0) {
const pageCount = this.viewedDocPaths.size;
const pagesText = pageCount === 1 ? "page" : "pages";
parts.push(`viewed ${pageCount} ${pagesText}`);
parts.push(`viewed ${pageCount} doc ${pagesText}`);
}

if (this.codeSearchCalls > 0) {
const timesText = this.codeSearchCalls === 1 ? "time" : "times";
const matchText = this.codeSearchResults === 1 ? "match" : "matches";
parts.push(
`searched code ${this.codeSearchCalls} ${timesText} with ${this.codeSearchResults} ${matchText}`,
);
}

if (this.viewedCodePaths.size > 0) {
const fileCount = this.viewedCodePaths.size;
const filesText = fileCount === 1 ? "file" : "files";
parts.push(`viewed ${fileCount} source ${filesText}`);
}

if (parts.length === 0) return "";

const firstPart = parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
return parts.length === 1 ? firstPart : firstPart + ", " + parts[1];
if (parts.length === 1) return firstPart;
return firstPart + ", " + parts.slice(1).join(", ");
}
}
Loading
Loading