@@ -6,6 +6,7 @@ import type { JamConfig } from '../config/schema.js';
66import type { McpManager } from '../mcp/manager.js' ;
77import { appendMessage } from '../storage/history.js' ;
88import { READ_ONLY_TOOL_SCHEMAS , executeReadOnlyTool } from '../tools/context-tools.js' ;
9+ import { ALL_TOOL_SCHEMAS , READONLY_TOOL_NAMES , executeTool } from '../tools/all-tools.js' ;
910import { getWorkspaceRoot } from '../utils/workspace.js' ;
1011import {
1112 ToolCallTracker ,
@@ -30,6 +31,12 @@ export interface ChatOptions {
3031 sessionId : string ;
3132 initialMessages : Message [ ] ;
3233 mcpManager ?: McpManager ;
34+ /** Enable write tools (write_file, apply_patch, run_command) — used by `jam go`. */
35+ enableWriteTools ?: boolean ;
36+ /** Tool approval policy — 'ask_every_time', 'allowlist', 'always', 'never'. */
37+ toolPolicy ?: string ;
38+ /** Allowlisted tool names when toolPolicy is 'allowlist'. */
39+ toolAllowlist ?: string [ ] ;
3340}
3441
3542interface DisplayMessage {
@@ -44,6 +51,9 @@ interface ChatAppProps {
4451 sessionId : string ;
4552 initialMessages : Message [ ] ;
4653 mcpManager ?: McpManager ;
54+ enableWriteTools ?: boolean ;
55+ toolPolicy ?: string ;
56+ toolAllowlist ?: string [ ] ;
4757}
4858
4959function formatRole ( role : string ) : string {
@@ -79,6 +89,9 @@ function ChatApp({
7989 sessionId,
8090 initialMessages,
8191 mcpManager,
92+ enableWriteTools,
93+ toolPolicy,
94+ toolAllowlist,
8295} : ChatAppProps ) : React . ReactElement {
8396 const { exit } = useApp ( ) ;
8497
@@ -193,11 +206,12 @@ function ChatApp({
193206 const memory = new WorkingMemory ( provider , profile ?. model , undefined ) ;
194207 const cache = new ToolResultCache ( ) ;
195208
196- // Merge MCP tool schemas with read-only tools
209+ // Select tool schemas based on mode (read-only for chat, full for go)
210+ const baseSchemas = enableWriteTools ? ALL_TOOL_SCHEMAS : READ_ONLY_TOOL_SCHEMAS ;
197211 const mcpSchemas = mcpManager ?. getToolSchemas ( ) ?? [ ] ;
198212 const chatToolSchemas = mcpSchemas . length > 0
199- ? [ ...READ_ONLY_TOOL_SCHEMAS , ...mcpSchemas ]
200- : READ_ONLY_TOOL_SCHEMAS ;
213+ ? [ ...baseSchemas , ...mcpSchemas ]
214+ : baseSchemas ;
201215
202216 agentSystemPrompt =
203217 profile ?. systemPrompt ??
@@ -346,12 +360,38 @@ function ChatApp({
346360 }
347361 }
348362
349- setStreamingText ( ansi ( ANSI . dimCyan , `▸ ${ tc . name } (${ Object . entries ( tc . arguments ) . map ( ( [ k , v ] ) => `${ k } =${ JSON . stringify ( v ) } ` ) . join ( ', ' ) } )` ) ) ;
363+ const argsSummary = Object . entries ( tc . arguments ) . map ( ( [ k , v ] ) => `${ k } =${ JSON . stringify ( v ) } ` ) . join ( ', ' ) ;
364+ const isWriteTool = enableWriteTools && ! READONLY_TOOL_NAMES . has ( tc . name ) && ! mcpManager ?. isOwnTool ( tc . name ) ;
365+
366+ if ( isWriteTool ) {
367+ setStreamingText ( ansi ( ANSI . dimYellow , `⚡ ${ tc . name } (${ argsSummary } )` ) ) ;
368+ } else {
369+ setStreamingText ( ansi ( ANSI . dimCyan , `▸ ${ tc . name } (${ argsSummary } )` ) ) ;
370+ }
371+
372+ // Permission check for write tools
373+ if ( isWriteTool && toolPolicy !== 'always' ) {
374+ if ( toolPolicy === 'never' ) {
375+ toolMessages . push ( { role : 'user' , content : `[Tool result: ${ tc . name } ]\nDenied: write tools are disabled by tool policy.` } ) ;
376+ tracker . record ( tc . name , tc . arguments , true ) ;
377+ continue ;
378+ }
379+ if ( toolPolicy === 'allowlist' && ! toolAllowlist ?. includes ( tc . name ) ) {
380+ toolMessages . push ( { role : 'user' , content : `[Tool result: ${ tc . name } ]\nDenied: tool not in allowlist.` } ) ;
381+ tracker . record ( tc . name , tc . arguments , true ) ;
382+ continue ;
383+ }
384+ // ask_every_time: auto-approve for now (proper readline prompt would block React render)
385+ // TODO: implement proper permission UI in Ink
386+ }
387+
350388 let toolOutput : string ;
351389 let wasError = false ;
352390 try {
353391 if ( mcpManager ?. isOwnTool ( tc . name ) ) {
354392 toolOutput = await mcpManager . executeTool ( tc . name , tc . arguments ) ;
393+ } else if ( isWriteTool ) {
394+ toolOutput = await executeTool ( tc . name , tc . arguments , workspaceRoot ) ;
355395 } else {
356396 toolOutput = await executeReadOnlyTool ( tc . name , tc . arguments , workspaceRoot ) ;
357397 }
@@ -525,6 +565,9 @@ export async function startChat(options: ChatOptions): Promise<void> {
525565 sessionId = { options . sessionId }
526566 initialMessages = { options . initialMessages }
527567 mcpManager = { options . mcpManager }
568+ enableWriteTools = { options . enableWriteTools }
569+ toolPolicy = { options . toolPolicy }
570+ toolAllowlist = { options . toolAllowlist }
528571 />
529572 ) ;
530573
0 commit comments