import { mkdirSync, writeFileSync } from 'fs' import { Box, Text } from 'ink' import { dirname, join } from 'path' import * as React from 'react' import { z } from 'zod' import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage' import { Tool } from '../../Tool' import { MEMORY_DIR } from '../../utils/env' import { resolveAgentId } from '../../utils/agentStorage' import { recordFileEdit } from '../../services/fileFreshness' import { DESCRIPTION, PROMPT } from './prompt' const inputSchema = z.strictObject({ file_path: z.string().describe('Path to the memory file to write'), content: z.string().describe('Content to write to the file'), }) export const MemoryWriteTool = { name: 'MemoryWrite', async description() { return DESCRIPTION }, async prompt() { return PROMPT }, inputSchema, userFacingName() { return 'Write Memory' }, async isEnabled() { // TODO: Use a statsig gate // TODO: Figure out how to do that without regressing app startup perf return false }, isReadOnly() { return false }, isConcurrencySafe() { return false // MemoryWrite modifies state, not safe for concurrent execution }, needsPermissions() { return false }, renderResultForAssistant(content) { return content }, renderToolUseMessage(input) { return Object.entries(input) .map(([key, value]) => `${key}: ${JSON.stringify(value)}`) .join(', ') }, renderToolUseRejectedMessage() { return }, renderToolResultMessage() { return ( {' '}⎿ Updated memory ) }, async validateInput({ file_path }, context) { const agentId = resolveAgentId(context?.agentId) const agentMemoryDir = join(MEMORY_DIR, 'agents', agentId) const fullPath = join(agentMemoryDir, file_path) if (!fullPath.startsWith(agentMemoryDir)) { return { result: false, message: 'Invalid memory file path' } } return { result: true } }, async *call({ file_path, content }, context) { const agentId = resolveAgentId(context?.agentId) const agentMemoryDir = join(MEMORY_DIR, 'agents', agentId) const fullPath = join(agentMemoryDir, file_path) mkdirSync(dirname(fullPath), { recursive: true }) writeFileSync(fullPath, content, 'utf-8') // Record Agent edit operation for file freshness tracking recordFileEdit(fullPath, content) yield { type: 'result', data: 'Saved', resultForAssistant: 'Saved', } }, } satisfies Tool