From 31688e62df720488d4034cb17a776d7bb3ecd508 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:12:23 +0000 Subject: [PATCH 01/17] loader --- packages/tools/loader.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/tools/loader.ts b/packages/tools/loader.ts index 6a5a5fc9..0b7b7024 100644 --- a/packages/tools/loader.ts +++ b/packages/tools/loader.ts @@ -12,12 +12,7 @@ export async function runTool(name: string, args: any): Promise { } try { - // We use the directory name (e.g., 'smoke') and the entry file (e.g., 'index.ts') - // Assuming your registry structure stores the relative directory or path - // If your registry generation script just has the name, we use the tool's directory: - const folderName = name === 'smoke_test' ? 'smoke' : name; // Temporary fix for the naming mismatch - - const modulePath = join(__dirname, folderName, toolEntry.entry || 'index.ts'); + const modulePath = join(__dirname, toolEntry.importPath || toolEntry.entry || 'index.ts'); const toolModule = await import(modulePath); if (typeof toolModule.run !== 'function') { From 9c5bc43f3dfcfa7fcbe64d0bde924751f133b507 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:12:42 +0000 Subject: [PATCH 02/17] agent test --- packages/agents/agents.test.ts | 50 +++++++++++++++++++++++++++++++++ packages/agents/coding-agent.ts | 7 ++--- 2 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 packages/agents/agents.test.ts diff --git a/packages/agents/agents.test.ts b/packages/agents/agents.test.ts new file mode 100644 index 00000000..968a4fde --- /dev/null +++ b/packages/agents/agents.test.ts @@ -0,0 +1,50 @@ +import { test, expect } from 'bun:test'; +import { codingAgent } from './coding-agent'; +import { supportAgent } from './support-agent'; + +const TEST_TIMEOUT = 30000; + +function makeMockClient(returnText: string) { + return { + specificationVersion: 'v2' as const, + provider: 'test-provider', + modelId: 'mock-model', + doGenerate: async () => ({ + text: returnText, + content: [{ type: 'text', text: returnText }], + finishReason: 'stop' as const, + usage: { promptTokens: 0, completionTokens: 0 }, + rawCall: { rawPrompt: null, rawSettings: {} }, + }), + } as any; +} + +test('supportAgent has github tools available in registry', async () => { + const mockResponse = 'I cannot find issues.'; + const mock = makeMockClient(mockResponse); + const session = { id: 'test-support-github', events: [] } as any; + + const gen = supportAgent('Search for bug issues', session, { client: mock }); + + for await (const step of gen) { + // Just iterate through to confirm no errors + } + + // If no error thrown, tools were registered successfully + expect(true).toBe(true); +}, TEST_TIMEOUT); + +test('codingAgent has github tools available in registry', async () => { + const mockRouterResponse = 'no'; // Say no tools needed to simplify test + const mock = makeMockClient(mockRouterResponse); + const session = { id: 'test-coding-github', events: [] } as any; + + const gen = codingAgent('Create a GitHub issue', session, { client: mock }); + + for await (const step of gen) { + // Just iterate through to confirm no errors + } + + // If no error thrown, tools were registered successfully + expect(true).toBe(true); +}, TEST_TIMEOUT); diff --git a/packages/agents/coding-agent.ts b/packages/agents/coding-agent.ts index d4714cc9..d45c72d4 100644 --- a/packages/agents/coding-agent.ts +++ b/packages/agents/coding-agent.ts @@ -26,8 +26,7 @@ const instructions = readFileSync( 'utf-8' ).replace('{CWD}', CWD); -const FS_TOOLS = ['fs/write', 'fs/read', 'fs/bash', 'fs/glob']; -const fsRegistry = registry.filter((t) => FS_TOOLS.includes(t.name)); +const toolRegistry = registry; function toToolKey(name: string): string { return name.replace('/', '_'); @@ -205,7 +204,7 @@ export async function* codingAgent( logger.debug({ model: CODER_MODEL }, '[codingAgent] tool path'); const toolsMap = Object.fromEntries( - fsRegistry.map((t) => [ + toolRegistry.map((t) => [ toToolKey(t.name), { description: t.description, @@ -294,7 +293,7 @@ export async function* codingAgent( const parsed = JSON.parse(jsonMatch[0]); const toolKey = parsed.name || parsed.tool || parsed.toolName; const originalName = fromToolKey(toolKey); - const toolDef = fsRegistry.find((t) => t.name === originalName); + const toolDef = toolRegistry.find((t) => t.name === originalName); const args = parsed.arguments ?? parsed.parameters ?? parsed.args; logger.debug({ toolKey, originalName, hasToolDef: !!toolDef, args }, '[codingAgent] regex fallback parsed'); From dbcd9dcb60f6c52c734d82fbb4e11d53292f1b95 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:12:58 +0000 Subject: [PATCH 03/17] loader test --- packages/tools/loader.test.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 packages/tools/loader.test.ts diff --git a/packages/tools/loader.test.ts b/packages/tools/loader.test.ts new file mode 100644 index 00000000..caff27f5 --- /dev/null +++ b/packages/tools/loader.test.ts @@ -0,0 +1,26 @@ +import { test, expect } from 'bun:test'; +import { runTool } from './loader'; + +const originalFetch = globalThis.fetch; + +test('runTool can load nested github tools from registry importPath', async () => { + process.env.GITHUB_TOKEN = 'fake-token'; + process.env.REPO_OWNER = 'example'; + process.env.REPO_NAME = 'repo'; + + globalThis.fetch = async () => { + return { + ok: true, + json: async () => ({ total_count: 0, items: [] }), + text: async () => '[]' + } as any; + }; + + const result = await runTool('github/search_issues', { query: 'bug', state: 'open' }); + + expect(result).toEqual({ total: 0, issues: [] }); +}); + +test.afterEach(() => { + globalThis.fetch = originalFetch; +}); From 2c74d2402ead79d6f120195c55451246a74cbc87 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:14:16 +0000 Subject: [PATCH 04/17] agent test --- packages/agents/agents.test.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/agents/agents.test.ts b/packages/agents/agents.test.ts index 968a4fde..53e8ab51 100644 --- a/packages/agents/agents.test.ts +++ b/packages/agents/agents.test.ts @@ -1,5 +1,4 @@ import { test, expect } from 'bun:test'; -import { codingAgent } from './coding-agent'; import { supportAgent } from './support-agent'; const TEST_TIMEOUT = 30000; @@ -19,7 +18,7 @@ function makeMockClient(returnText: string) { } as any; } -test('supportAgent has github tools available in registry', async () => { +test('supportAgent has all tools available in registry including github/*', async () => { const mockResponse = 'I cannot find issues.'; const mock = makeMockClient(mockResponse); const session = { id: 'test-support-github', events: [] } as any; @@ -33,18 +32,3 @@ test('supportAgent has github tools available in registry', async () => { // If no error thrown, tools were registered successfully expect(true).toBe(true); }, TEST_TIMEOUT); - -test('codingAgent has github tools available in registry', async () => { - const mockRouterResponse = 'no'; // Say no tools needed to simplify test - const mock = makeMockClient(mockRouterResponse); - const session = { id: 'test-coding-github', events: [] } as any; - - const gen = codingAgent('Create a GitHub issue', session, { client: mock }); - - for await (const step of gen) { - // Just iterate through to confirm no errors - } - - // If no error thrown, tools were registered successfully - expect(true).toBe(true); -}, TEST_TIMEOUT); From 7a9ef7b410dab259b24778b4fafb4112acd4cb15 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:14:21 +0000 Subject: [PATCH 05/17] loader test --- packages/tools/loader.test.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/tools/loader.test.ts b/packages/tools/loader.test.ts index caff27f5..9dca3193 100644 --- a/packages/tools/loader.test.ts +++ b/packages/tools/loader.test.ts @@ -1,13 +1,13 @@ import { test, expect } from 'bun:test'; import { runTool } from './loader'; -const originalFetch = globalThis.fetch; - test('runTool can load nested github tools from registry importPath', async () => { process.env.GITHUB_TOKEN = 'fake-token'; process.env.REPO_OWNER = 'example'; process.env.REPO_NAME = 'repo'; + const originalFetch = globalThis.fetch; + globalThis.fetch = async () => { return { ok: true, @@ -16,11 +16,10 @@ test('runTool can load nested github tools from registry importPath', async () = } as any; }; - const result = await runTool('github/search_issues', { query: 'bug', state: 'open' }); - - expect(result).toEqual({ total: 0, issues: [] }); -}); - -test.afterEach(() => { - globalThis.fetch = originalFetch; + try { + const result = await runTool('github/search_issues', { query: 'bug', state: 'open' }); + expect(result).toEqual({ total: 0, issues: [] }); + } finally { + globalThis.fetch = originalFetch; + } }); From a0ed27d158bf6a255a434ba8f5688ec7ca11c260 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:20:53 +0000 Subject: [PATCH 06/17] get repo info --- packages/agents/agents.test.ts | 12 ++ packages/agents/coding-agent.ts | 22 +-- .../tools/github/get_repo_info/index.test.ts | 28 ++++ packages/tools/github/get_repo_info/index.ts | 35 +++++ .../tools/github/get_repo_info/manifest.json | 10 ++ packages/tools/registry.json | 141 ++++++++++-------- 6 files changed, 174 insertions(+), 74 deletions(-) create mode 100644 packages/tools/github/get_repo_info/index.test.ts create mode 100644 packages/tools/github/get_repo_info/index.ts create mode 100644 packages/tools/github/get_repo_info/manifest.json diff --git a/packages/agents/agents.test.ts b/packages/agents/agents.test.ts index 53e8ab51..c3d16e79 100644 --- a/packages/agents/agents.test.ts +++ b/packages/agents/agents.test.ts @@ -1,5 +1,6 @@ import { test, expect } from 'bun:test'; import { supportAgent } from './support-agent'; +import { runTool } from '@sup/tools'; const TEST_TIMEOUT = 30000; @@ -32,3 +33,14 @@ test('supportAgent has all tools available in registry including github/*', asyn // If no error thrown, tools were registered successfully expect(true).toBe(true); }, TEST_TIMEOUT); + +test('agent can discover repository configuration via github/get_repo_info tool', async () => { + process.env.REPO_OWNER = 'TestOrg'; + process.env.REPO_NAME = 'test-repository'; + + const repoInfo = await runTool('github/get_repo_info', {}); + + expect(repoInfo.owner).toBe('TestOrg'); + expect(repoInfo.repo).toBe('test-repository'); + expect(repoInfo.fullName).toBe('TestOrg/test-repository'); +}, TEST_TIMEOUT); diff --git a/packages/agents/coding-agent.ts b/packages/agents/coding-agent.ts index d45c72d4..e9390876 100644 --- a/packages/agents/coding-agent.ts +++ b/packages/agents/coding-agent.ts @@ -53,7 +53,7 @@ async function needsTools(userInput: string, history: string): Promise const response = await generateText({ model: ollama(ROUTER_MODEL), temperature: 0, - system: `You are a router. Decide if the user's request requires reading or writing files, running shell commands, or executing code. + system: `You are a router. Decide if the user's request requires tool use: reading or writing files, running shell commands, executing code, creating/searching/updating GitHub issues, or other external actions. Reply with only "yes" or "no".`, prompt: history ? `${history}\nUser: ${userInput}` : userInput, }); @@ -204,14 +204,18 @@ export async function* codingAgent( logger.debug({ model: CODER_MODEL }, '[codingAgent] tool path'); const toolsMap = Object.fromEntries( - toolRegistry.map((t) => [ - toToolKey(t.name), - { - description: t.description, - parameters: t.parameters, - execute: async (args: any) => runTool(t.name, args), - }, - ]) + toolRegistry.map((t) => { + // Only transform FS tool names; keep others (like github/*) as-is + const toolKey = t.name.startsWith('fs/') ? toToolKey(t.name) : t.name; + return [ + toolKey, + { + description: t.description, + parameters: t.parameters, + execute: async (args: any) => runTool(t.name, args), + }, + ]; + }) ); logger.debug({ toolKeys: Object.keys(toolsMap) }, '[codingAgent] registered tools'); diff --git a/packages/tools/github/get_repo_info/index.test.ts b/packages/tools/github/get_repo_info/index.test.ts new file mode 100644 index 00000000..f34e7f5d --- /dev/null +++ b/packages/tools/github/get_repo_info/index.test.ts @@ -0,0 +1,28 @@ +import { test, expect } from 'bun:test'; +import { runTool } from '../../loader'; + +test('runTool can get repository info', async () => { + process.env.REPO_OWNER = 'TestOwner'; + process.env.REPO_NAME = 'test-repo'; + + const result = await runTool('github/get_repo_info', {}); + + expect(result).toEqual({ + owner: 'TestOwner', + repo: 'test-repo', + fullName: 'TestOwner/test-repo', + }); +}); + +test('get_repo_info fails gracefully when repo not configured', async () => { + delete process.env.REPO_OWNER; + delete process.env.REPO_NAME; + + try { + await runTool('github/get_repo_info', {}); + expect.unreachable('Should have thrown an error'); + } catch (error) { + expect(error instanceof Error).toBe(true); + expect((error as Error).message).toContain('Repository not configured'); + } +}); diff --git a/packages/tools/github/get_repo_info/index.ts b/packages/tools/github/get_repo_info/index.ts new file mode 100644 index 00000000..17ff59d7 --- /dev/null +++ b/packages/tools/github/get_repo_info/index.ts @@ -0,0 +1,35 @@ +/** + * @file /packages/tools/github/get_repo_info/index.ts + * @description Get the current GitHub repository configuration that will be used for ticket operations. + */ + +export interface RepositoryInfo { + owner: string; + repo: string; + fullName: string; +} + +export async function getRepoInfo(): Promise { + const owner = process.env.REPO_OWNER; + const repo = process.env.REPO_NAME; + + if (!owner || !repo) { + throw new Error( + `Repository not configured. Set REPO_OWNER and REPO_NAME environment variables.` + ); + } + + return { + owner, + repo, + fullName: `${owner}/${repo}`, + }; +} + +/** + * Agent entrypoint + * The agent runner calls this function with tool arguments. + */ +export async function run() { + return await getRepoInfo(); +} diff --git a/packages/tools/github/get_repo_info/manifest.json b/packages/tools/github/get_repo_info/manifest.json new file mode 100644 index 00000000..f13697b7 --- /dev/null +++ b/packages/tools/github/get_repo_info/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "github/get_repo_info", + "description": "Get the GitHub repository (owner and name) that ticket operations will target. Use this to discover which repository the agent is configured to work with.", + "entry": "index.ts", + "parameters": { + "type": "object", + "properties": {}, + "required": [] + } +} diff --git a/packages/tools/registry.json b/packages/tools/registry.json index 487aa793..05a49dba 100644 --- a/packages/tools/registry.json +++ b/packages/tools/registry.json @@ -18,80 +18,58 @@ "importPath": "smoke/index.ts" }, { - "name": "github/create_issue", - "description": "Files a new issue in a GitHub repository.", - "entry": "./index.ts", + "name": "fs/read", + "description": "Read the content of a file. The path must be relative to the project root, e.g. src/main.ts or README.md.", + "entry": "index.ts", "parameters": { "type": "object", "properties": { - "title": { - "type": "string" - }, - "body": { - "type": "string" - }, - "labels": { - "type": "array", - "items": { - "type": "string" - } + "path": { + "type": "string", + "description": "Relative path from the project root, e.g. src/main.ts" } }, "required": [ - "title" + "path" ] }, - "importPath": "github/create_issue/index.ts" + "importPath": "fs/read/index.ts" }, { - "name": "github/search_issues", - "description": "Search for GitHub issues in the repository.", - "entry": "./index.ts", + "name": "fs/bash", + "description": "Run a shell command in the project root directory.", + "entry": "index.ts", "parameters": { "type": "object", "properties": { - "query": { - "type": "string", - "description": "Search query" - }, - "state": { + "command": { "type": "string", - "enum": [ - "open", - "closed", - "all" - ], - "description": "Issue state filter" + "description": "The shell command to run, e.g. npm test or git status" } }, "required": [ - "query" + "command" ] }, - "importPath": "github/search_issues/index.ts" + "importPath": "fs/bash/index.ts" }, { - "name": "github/comment_issue", - "description": "Adds a comment to an existing GitHub issue.", - "entry": "./index.ts", + "name": "fs/glob", + "description": "List files in the project matching a pattern, e.g. *.ts or *.json.", + "entry": "index.ts", "parameters": { "type": "object", "properties": { - "issue_number": { - "type": "number", - "description": "The issue number to comment on" - }, - "body": { + "pattern": { "type": "string", - "description": "The comment text" + "description": "Glob pattern to match, e.g. *.ts or src/**/*.ts" } }, "required": [ - "issue_number", - "body" + "pattern" ] }, - "importPath": "github/comment_issue/index.ts" + "importPath": "fs/glob/index.ts" }, { "name": "fs/write", @@ -117,57 +95,90 @@ "importPath": "fs/write/index.ts" }, { - "name": "fs/read", - "description": "Read the content of a file. The path must be relative to the project root, e.g. src/main.ts or README.md.", - "entry": "index.ts", + "name": "github/comment_issue", + "description": "Adds a comment to an existing GitHub issue.", + "entry": "./index.ts", "parameters": { "type": "object", "properties": { - "path": { + "issue_number": { + "type": "number", + "description": "The issue number to comment on" + }, + "body": { "type": "string", - "description": "Relative path from the project root, e.g. src/main.ts" + "description": "The comment text" } }, "required": [ - "path" + "issue_number", + "body" ] }, - "importPath": "fs/read/index.ts" + "importPath": "github/comment_issue/index.ts" }, { - "name": "fs/bash", - "description": "Run a shell command in the project root directory.", - "entry": "index.ts", + "name": "github/search_issues", + "description": "Search for GitHub issues in the repository.", + "entry": "./index.ts", "parameters": { "type": "object", "properties": { - "command": { + "query": { "type": "string", - "description": "The shell command to run, e.g. npm test or git status" + "description": "Search query" + }, + "state": { + "type": "string", + "enum": [ + "open", + "closed", + "all" + ], + "description": "Issue state filter" } }, "required": [ - "command" + "query" ] }, - "importPath": "fs/bash/index.ts" + "importPath": "github/search_issues/index.ts" }, { - "name": "fs/glob", - "description": "List files in the project matching a pattern, e.g. *.ts or *.json.", + "name": "github/get_repo_info", + "description": "Get the GitHub repository (owner and name) that ticket operations will target. Use this to discover which repository the agent is configured to work with.", "entry": "index.ts", + "parameters": { + "type": "object", + "properties": {}, + "required": [] + }, + "importPath": "github/get_repo_info/index.ts" + }, + { + "name": "github/create_issue", + "description": "Files a new issue in a GitHub repository.", + "entry": "./index.ts", "parameters": { "type": "object", "properties": { - "pattern": { - "type": "string", - "description": "Glob pattern to match, e.g. *.ts or src/**/*.ts" + "title": { + "type": "string" + }, + "body": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ - "pattern" + "title" ] }, - "importPath": "fs/glob/index.ts" + "importPath": "github/create_issue/index.ts" } ] \ No newline at end of file From 7c8585a452eb4f22110f1d635a07335015da1f61 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:22:09 +0000 Subject: [PATCH 07/17] get OG name --- packages/agents/coding-agent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/agents/coding-agent.ts b/packages/agents/coding-agent.ts index e9390876..04e43ce0 100644 --- a/packages/agents/coding-agent.ts +++ b/packages/agents/coding-agent.ts @@ -296,7 +296,8 @@ export async function* codingAgent( try { const parsed = JSON.parse(jsonMatch[0]); const toolKey = parsed.name || parsed.tool || parsed.toolName; - const originalName = fromToolKey(toolKey); + // Try both: if it looks like an FS tool (fs_*), convert back; otherwise use as-is + const originalName = toolKey.startsWith('fs_') ? fromToolKey(toolKey) : toolKey; const toolDef = toolRegistry.find((t) => t.name === originalName); const args = parsed.arguments ?? parsed.parameters ?? parsed.args; logger.debug({ toolKey, originalName, hasToolDef: !!toolDef, args }, '[codingAgent] regex fallback parsed'); From a44ad0fb0d0246505eb2f6242c1954480724eb46 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:51:34 +0000 Subject: [PATCH 08/17] support agent --- packages/agents/support-agent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/agents/support-agent.ts b/packages/agents/support-agent.ts index cb504c20..2deeb870 100644 --- a/packages/agents/support-agent.ts +++ b/packages/agents/support-agent.ts @@ -19,6 +19,7 @@ import { rebuildGraph } from '@sup/lib/graph-reducer'; import { logger } from '@sup/infra/logger'; import { CONTEXT_ANCHOR } from '@sup/agents/config'; import { tools as registry, runTool } from "@sup/tools"; +import { filterToolsForAgent } from "./agent-tool-registry"; // import { OutputPort } from '@sup/domain'; // const DEFAULT_MODEL = 'qwen2.5:7b'; // AGENT_MODEL @@ -159,7 +160,7 @@ export async function* supportAgent( system: systemPrompt, temperature: supportAgentConfig.temperature, tools: Object.fromEntries( - registry.map((t) => [ + filterToolsForAgent(registry, 'support').map((t) => [ t.name, { description: t.description, From db768d1bf56d652cd8fa419084652cadb96334f3 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:52:20 +0000 Subject: [PATCH 09/17] config --- config/coding-agent-instructions.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/config/coding-agent-instructions.txt b/config/coding-agent-instructions.txt index 6640b516..26ccee2e 100644 --- a/config/coding-agent-instructions.txt +++ b/config/coding-agent-instructions.txt @@ -8,8 +8,18 @@ You are a coding agent with the following tools: - fs/read: read a file (relative path) - fs/bash: run a shell command - fs/glob: list files matching a pattern +- github/get_repo_info: discover which GitHub repository tickets will be created in +- github/create_issue: create a GitHub issue (requires title, optional: body, labels) +- github/search_issues: search for GitHub issues +- github/comment_issue: add a comment to a GitHub issue +- github/close_issue: close a GitHub issue -Only use tools when the task requires reading or writing files, or running shell commands. +When using GitHub tools: +1. If the user asks to create/search/manage issues, first call github/get_repo_info to understand the target repository. +2. For create_issue: always provide a title. Ask the user if they haven't specified what the issue should be about. +3. For other GitHub operations: be explicit about what action you're taking. + +Only use tools when the task requires reading/writing files, running commands, or performing GitHub operations. For general questions, answer directly without using tools. Be direct. No commentary unless asked. \ No newline at end of file From 67f2cad772e704092bc25128cd497dc6144e918c Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:52:32 +0000 Subject: [PATCH 10/17] coding-agent --- packages/agents/coding-agent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/agents/coding-agent.ts b/packages/agents/coding-agent.ts index 04e43ce0..e9a1fec2 100644 --- a/packages/agents/coding-agent.ts +++ b/packages/agents/coding-agent.ts @@ -14,6 +14,7 @@ import { readFileSync } from 'fs'; import type { AgentSession, AgentEvent, AgentStep } from '@sup/types/types'; import { logger } from '@sup/infra/logger'; import { tools as registry, runTool } from '@sup/tools'; +import { filterToolsForAgent } from './agent-tool-registry'; const ROUTER_MODEL = 'qwen3:8b'; const CODER_MODEL = process.env.CODING_AGENT_MODEL || 'qwen2.5-coder:14b'; @@ -26,7 +27,7 @@ const instructions = readFileSync( 'utf-8' ).replace('{CWD}', CWD); -const toolRegistry = registry; +const toolRegistry = filterToolsForAgent(registry, 'coding'); function toToolKey(name: string): string { return name.replace('/', '_'); From 2b3be91cf269ae3bdbe1e6e99b9e9c39cf927f6e Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:52:54 +0000 Subject: [PATCH 11/17] create issue --- packages/tools/github/create_issue/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/tools/github/create_issue/index.ts b/packages/tools/github/create_issue/index.ts index e798d2e2..45e2e992 100644 --- a/packages/tools/github/create_issue/index.ts +++ b/packages/tools/github/create_issue/index.ts @@ -17,8 +17,10 @@ export interface CreateIssueParams { export async function createIssue(params: CreateIssueParams) { const { title, body = "", labels = [] } = params; - if (!title) { - throw new Error("title parameter is required"); + if (!title || title.trim() === '') { + throw new Error( + 'title parameter is required. Please provide a brief title for the GitHub issue. Example: "Fix login bug" or "Add email validation"' + ); } if (!token) { From 391bb3268aadcbbc85e2ce7c804a505e6133ddc7 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:53:52 +0000 Subject: [PATCH 12/17] gh tool --- .../tools/github/create_issue/manifest.json | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/tools/github/create_issue/manifest.json b/packages/tools/github/create_issue/manifest.json index 117cdad0..f064dd10 100644 --- a/packages/tools/github/create_issue/manifest.json +++ b/packages/tools/github/create_issue/manifest.json @@ -1,13 +1,23 @@ { "name": "github/create_issue", - "description": "Files a new issue in a GitHub repository.", + "description": "Create a new GitHub issue in the configured repository. REQUIRED: title. OPTIONAL: body (description), labels (tags for the issue).", "entry": "./index.ts", "parameters": { "type": "object", "properties": { - "title": { "type": "string" }, - "body": { "type": "string" }, - "labels": { "type": "array", "items": { "type": "string" } } + "title": { + "type": "string", + "description": "Brief title for the issue (required). Example: 'Fix login button styling' or 'Add email validation'" + }, + "body": { + "type": "string", + "description": "Detailed description of the issue (optional). Include context, steps to reproduce, or desired behavior." + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Tags to categorize the issue (optional). Examples: 'bug', 'feature', 'docs', 'enhancement'" + } }, "required": ["title"] } From 9b4f7e7c033108f93b0ef512e1502aeca1c95ce6 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:54:10 +0000 Subject: [PATCH 13/17] update tool index --- packages/tools/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/index.ts b/packages/tools/index.ts index fe843540..a5eb2bc3 100644 --- a/packages/tools/index.ts +++ b/packages/tools/index.ts @@ -1,6 +1,6 @@ /** * @file index.ts - * @description Central registry for all support tools. + * @description Central registry for all tools. */ import registry from "./registry.json" assert { type: "json" }; import { runTool } from "./loader"; From 7a6a79806c17bc7c56aa93e757bd1f11328a84a2 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:54:34 +0000 Subject: [PATCH 14/17] tool reg types --- packages/tools/tool-registry-types.ts | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 packages/tools/tool-registry-types.ts diff --git a/packages/tools/tool-registry-types.ts b/packages/tools/tool-registry-types.ts new file mode 100644 index 00000000..c7664f60 --- /dev/null +++ b/packages/tools/tool-registry-types.ts @@ -0,0 +1,33 @@ +/** + * @file packages/tools/tool-registry-types.ts + * @description Type definitions for the tool registry and mode-specific filtering. + */ + +export type AgentMode = 'support' | 'coding'; + +export interface ToolEntry { + name: string; + description: string; + entry?: string; + parameters: { + type: string; + properties: Record; + required?: string[]; + }; + importPath?: string; + modes?: AgentMode[]; // Which agent modes can use this tool. Defaults to both if omitted. +} + +/** + * Filter registry entries by agent mode. + * Tools without a 'modes' field are available to all modes (backward compatible). + */ +export function filterToolsByMode(tools: ToolEntry[], mode: AgentMode): ToolEntry[] { + return tools.filter((tool) => { + // If modes not specified, tool is available to all modes + if (!tool.modes || tool.modes.length === 0) { + return true; + } + return tool.modes.includes(mode); + }); +} From ba7f111d522b4ff2bd588f763288114d8c5f7e25 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:54:42 +0000 Subject: [PATCH 15/17] tool reg types test --- packages/tools/tool-registry-types.test.ts | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 packages/tools/tool-registry-types.test.ts diff --git a/packages/tools/tool-registry-types.test.ts b/packages/tools/tool-registry-types.test.ts new file mode 100644 index 00000000..b3eec864 --- /dev/null +++ b/packages/tools/tool-registry-types.test.ts @@ -0,0 +1,37 @@ +import { test, expect } from 'bun:test'; +import { filterToolsByMode } from './tool-registry-types'; +import registry from './registry.json' assert { type: 'json' }; + +test('filterToolsByMode: support-agent gets support-only tools', () => { + const supportTools = filterToolsByMode(registry, 'support'); + + // smoke is support-only, so it should be in support tools + expect(supportTools.some((t) => t.name === 'smoke')).toBe(true); + + // GitHub tools should be available (no modes restriction) + expect(supportTools.some((t) => t.name === 'github/create_issue')).toBe(true); +}); + +test('filterToolsByMode: coding-agent excludes support-only tools', () => { + const codingTools = filterToolsByMode(registry, 'coding'); + + // smoke is support-only, so it should NOT be in coding tools + expect(codingTools.some((t) => t.name === 'smoke')).toBe(false); + + // GitHub tools should still be available (no modes restriction) + expect(codingTools.some((t) => t.name === 'github/create_issue')).toBe(true); + + // Filesystem tools should be available + expect(codingTools.some((t) => t.name === 'fs/read')).toBe(true); +}); + +test('filterToolsByMode: tools without modes are available to both', () => { + const supportTools = filterToolsByMode(registry, 'support'); + const codingTools = filterToolsByMode(registry, 'coding'); + + // GitHub tools have no modes restriction, should be in both + const githubCreateInSupport = supportTools.some((t) => t.name === 'github/create_issue'); + const githubCreateInCoding = codingTools.some((t) => t.name === 'github/create_issue'); + + expect(githubCreateInSupport && githubCreateInCoding).toBe(true); +}); From f20d9e45931b80869eed0b6e7ff95849cc3074f1 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:54:56 +0000 Subject: [PATCH 16/17] registry --- packages/tools/registry.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/tools/registry.json b/packages/tools/registry.json index 05a49dba..0fc07a84 100644 --- a/packages/tools/registry.json +++ b/packages/tools/registry.json @@ -3,6 +3,9 @@ "name": "smoke", "description": "A sanity check tool to verify tool-calling is working. Triggered when the user asks what day it is.", "entry": "index.ts", + "modes": [ + "support" + ], "parameters": { "type": "object", "properties": { @@ -157,22 +160,25 @@ }, { "name": "github/create_issue", - "description": "Files a new issue in a GitHub repository.", + "description": "Create a new GitHub issue in the configured repository. REQUIRED: title. OPTIONAL: body (description), labels (tags for the issue).", "entry": "./index.ts", "parameters": { "type": "object", "properties": { "title": { - "type": "string" + "type": "string", + "description": "Brief title for the issue (required). Example: 'Fix login button styling' or 'Add email validation'" }, "body": { - "type": "string" + "type": "string", + "description": "Detailed description of the issue (optional). Include context, steps to reproduce, or desired behavior." }, "labels": { "type": "array", "items": { "type": "string" - } + }, + "description": "Tags to categorize the issue (optional). Examples: 'bug', 'feature', 'docs', 'enhancement'" } }, "required": [ From 8275f7a4edf74270e862f8a523bf66d3678c9663 Mon Sep 17 00:00:00 2001 From: Forest Mars Date: Fri, 19 Jun 2026 16:55:12 +0000 Subject: [PATCH 17/17] agent tool registry --- packages/agents/agent-tool-registry.ts | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 packages/agents/agent-tool-registry.ts diff --git a/packages/agents/agent-tool-registry.ts b/packages/agents/agent-tool-registry.ts new file mode 100644 index 00000000..65a29d43 --- /dev/null +++ b/packages/agents/agent-tool-registry.ts @@ -0,0 +1,84 @@ +/** + * @file packages/agents/agent-tool-registry.ts + * @description Central, auditable agent-to-tools access policy. + * + * This is the single source of truth for which tools each agent can access. + * It is NOT embedded in tool manifests (tools are agent-agnostic). + * Instead, agents reference this registry when building their tool maps. + * + * For event-sourcing: this config is versioned and can be snapshotted + * to recreate deterministic agent behavior at any point in time. + */ + +export type AgentType = 'support' | 'coding'; + +interface AgentToolPolicy { + description: string; + tools: string[]; // tool names that this agent can access +} + +/** + * Central declaration of which tools each agent can access. + * Add new agents and update tool access here. + */ +export const AGENT_TOOL_POLICIES: Record = { + support: { + description: 'Support agent: handles customer inquiries, order lookups, issue tracking', + tools: [ + // Smoke test + 'smoke', + + // GitHub operations + 'github/get_repo_info', + 'github/create_issue', + 'github/search_issues', + 'github/comment_issue', + 'github/close_issue', + ], + }, + + coding: { + description: 'Coding agent: handles file operations, shell commands, and code tasks', + tools: [ + // Filesystem operations + 'fs/read', + 'fs/write', + 'fs/bash', + 'fs/glob', + + // GitHub operations (available to both) + 'github/get_repo_info', + 'github/create_issue', + 'github/search_issues', + 'github/comment_issue', + 'github/close_issue', + ], + }, +}; + +/** + * Get the list of tool names allowed for a given agent. + * @param agentType The agent type (e.g., 'support', 'coding') + * @returns Array of tool names this agent can access + */ +export function getAllowedToolsForAgent(agentType: AgentType): string[] { + const policy = AGENT_TOOL_POLICIES[agentType]; + if (!policy) { + throw new Error(`Unknown agent type: ${agentType}`); + } + return policy.tools; +} + +/** + * Filter a tool registry by agent type. + * @param tools Full list of available tools from registry + * @param agentType The agent type to filter for + * @returns Only the tools this agent is allowed to use + */ +export function filterToolsForAgent( + tools: T[], + agentType: AgentType +): T[] { + const allowedNames = getAllowedToolsForAgent(agentType); + return tools.filter((tool) => allowedNames.includes(tool.name)); +}