diff --git a/packages/tfcode/src/cli/cmd/tui/app.tsx b/packages/tfcode/src/cli/cmd/tui/app.tsx index 4a6a6b208..797e03347 100644 --- a/packages/tfcode/src/cli/cmd/tui/app.tsx +++ b/packages/tfcode/src/cli/cmd/tui/app.tsx @@ -784,7 +784,7 @@ function App() { await DialogAlert.show( dialog, "Update Complete", - `Successfully updated to TF Code v${result.data.version}. Please restart the application.`, + `Successfully updated to TF Code (tfcode) v${result.data.version}. Please restart the application.`, ) exit() diff --git a/packages/tfcode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/tfcode/src/cli/cmd/tui/routes/session/permission.tsx index a49f4b080..0918832b1 100644 --- a/packages/tfcode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/tfcode/src/cli/cmd/tui/routes/session/permission.tsx @@ -157,11 +157,15 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { body={ - + - This will allow the following patterns until TF Code is restarted + + This will allow the following patterns until TF Code (tfcode) is restarted + {(pattern) => ( @@ -501,7 +505,7 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: ( Reject permission - Tell TF Code what to do differently + Tell TF Code (tfcode) what to do differently {context()?.tokens ?? 0} tokens {context()?.percentage ?? 0}% used - {cost()} spent 0}> @@ -292,7 +291,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { ✕ - TF Code includes free models so you can start immediately. + TF Code (tfcode) includes free models so you can start immediately. Connect from 75+ providers to use other models, including Claude, GPT, Gemini etc diff --git a/packages/tfcode/src/provider/sdk/toothfairyai/toothfairyai-provider.ts b/packages/tfcode/src/provider/sdk/toothfairyai/toothfairyai-provider.ts index 994a43fe8..dc96f6f8f 100644 --- a/packages/tfcode/src/provider/sdk/toothfairyai/toothfairyai-provider.ts +++ b/packages/tfcode/src/provider/sdk/toothfairyai/toothfairyai-provider.ts @@ -151,6 +151,13 @@ export function createToothFairyAI(options: ToothFairyAIProviderSettings = {}): finish_reason: parsed.choices[0].finish_reason, }) } + if (parsed.usage) { + log.info("stream usage", { + prompt_tokens: parsed.usage.prompt_tokens, + completion_tokens: parsed.usage.completion_tokens, + total_tokens: parsed.usage.total_tokens, + }) + } } catch {} } } else { @@ -181,6 +188,7 @@ export function createToothFairyAI(options: ToothFairyAIProviderSettings = {}): headers: () => baseHeaders, url: ({ path }) => `${baseURL}${path}`, fetch: customFetch as FetchFunction, + includeUsage: true, }) } diff --git a/packages/tfcode/src/session/processor.ts b/packages/tfcode/src/session/processor.ts index ccb09e71a..6a5ddb7fb 100644 --- a/packages/tfcode/src/session/processor.ts +++ b/packages/tfcode/src/session/processor.ts @@ -152,7 +152,18 @@ export namespace SessionProcessor { const parts = await MessageV2.parts(input.assistantMessage.id) const lastThree = parts.slice(-DOOM_LOOP_THRESHOLD) - if ( + const getLoopKey = (tool: string, input: unknown): string | null => { + if (typeof input !== "object" || !input) return null + const i = input as Record + if (tool === "read" && typeof i.filePath === "string") return `read:${i.filePath}` + if (tool === "glob" && typeof i.pattern === "string") return `glob:${i.pattern}` + if (tool === "grep" && typeof i.pattern === "string") return `grep:${i.pattern}` + return null + } + + const loopKey = getLoopKey(value.toolName, value.input) + + const isIdenticalLoop = lastThree.length === DOOM_LOOP_THRESHOLD && lastThree.every( (p) => @@ -161,7 +172,19 @@ export namespace SessionProcessor { p.state.status !== "pending" && JSON.stringify(p.state.input) === JSON.stringify(value.input), ) - ) { + + const isSimilarLoop = + loopKey && + lastThree.length === DOOM_LOOP_THRESHOLD && + lastThree.every( + (p) => + p.type === "tool" && + p.tool === value.toolName && + p.state.status !== "pending" && + getLoopKey(p.tool, p.state.input) === loopKey, + ) + + if (isIdenticalLoop || isSimilarLoop) { const agent = await Agent.get(input.assistantMessage.agent) await Permission.ask({ permission: "doom_loop", @@ -170,6 +193,7 @@ export namespace SessionProcessor { metadata: { tool: value.toolName, input: value.input, + reason: isSimilarLoop ? "same file/pattern read multiple times" : "identical input", }, always: [value.toolName], ruleset: agent.permission, diff --git a/packages/tfcode/src/session/prompt/default.txt b/packages/tfcode/src/session/prompt/default.txt index 365663eee..3d65ac405 100644 --- a/packages/tfcode/src/session/prompt/default.txt +++ b/packages/tfcode/src/session/prompt/default.txt @@ -1,12 +1,12 @@ -You are opencode, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user. +You are TF Code (also referred to as tfcode), an interactive CLI tool that helps users with software engineering tasks. Users may refer to you as "TF Code", "tfcode", or both interchangeably. Use the instructions below and the tools available to you to assist the user. IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files. If the user asks for help or wants to give feedback inform them of the following: -- /help: Get help with using opencode +- /help: Get help with using TF Code - To give feedback, users should report the issue at https://github.com/anomalyco/opencode/issues -When the user directly asks about opencode (eg 'can opencode do...', 'does opencode have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from opencode docs at https://opencode.ai +When the user directly asks about TF Code (eg 'can TF Code do...', 'does TF Code have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from opencode docs at https://opencode.ai # Tone and style You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system). @@ -85,6 +85,12 @@ The user will primarily request you perform software engineering tasks. This inc - VERY IMPORTANT: When you have completed a task, you MUST run the lint and typecheck commands (e.g. npm run lint, npm run typecheck, ruff, etc.) with Bash if they were provided to you to ensure your code is correct. If you are unable to find the correct command, ask the user for the command to run and if they supply it, proactively suggest writing it to AGENTS.md so that you will know to run it next time. NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive. +# Avoiding loops +- CRITICAL: Avoid verification loops. After making edits, do NOT re-read the same file to verify your changes. Trust that your edits were applied correctly. +- If you need to verify something, read the file ONCE before editing, not repeatedly after each edit. +- If you believe an edit was not applied correctly, check the file ONCE. If it looks correct, move on. Do not re-read the same file multiple times. +- Reading the same file 3 or more times is a sign of a loop - STOP and ask the user for guidance. + - Tool results and user messages may include tags. tags contain useful information and reminders. They are NOT part of the user's provided input or the tool result. # Tool usage policy