Aiden Cline hace 3 meses
padre
commit
ece719dc77

+ 14 - 3
packages/opencode/src/session/truncation.ts

@@ -4,7 +4,10 @@ import { Global } from "../global"
 import { Identifier } from "../id/id"
 import { iife } from "../util/iife"
 import { lazy } from "../util/lazy"
+import { PermissionNext } from "../permission/next"
+import type { Agent } from "../agent/agent"
 
+// what models does opencode provider support? Read: https://models.dev/api.json
 export namespace Truncate {
   export const MAX_LINES = 2000
   export const MAX_BYTES = 50 * 1024
@@ -38,7 +41,13 @@ export namespace Truncate {
     }
   })
 
-  export async function output(text: string, options: Options = {}): Promise<Result> {
+  function hasTaskTool(agent?: Agent.Info): boolean {
+    if (!agent?.permission) return false
+    const rule = PermissionNext.evaluate("task", "*", agent.permission)
+    return rule.action !== "deny"
+  }
+
+  export async function output(text: string, options: Options = {}, agent?: Agent.Info): Promise<Result> {
     const maxLines = options.maxLines ?? MAX_LINES
     const maxBytes = options.maxBytes ?? MAX_BYTES
     const direction = options.direction ?? "head"
@@ -85,10 +94,12 @@ export namespace Truncate {
     const filepath = path.join(DIR, id)
     await Bun.write(Bun.file(filepath), text)
 
+    const base = `Full output written to: ${filepath}\nUse Grep to search the full content and Read with offset/limit to read specific sections`
+    const hint = hasTaskTool(agent) ? `${base} (or use Task tool to delegate and save context).` : `${base}.`
     const message =
       direction === "head"
-        ? `${preview}\n\n...${removed} ${unit} truncated...\n\nFull output written to: ${filepath}\nUse Read or Grep to view the full content.`
-        : `...${removed} ${unit} truncated...\n\nFull output written to: ${filepath}\nUse Read or Grep to view the full content.\n\n${preview}`
+        ? `${preview}\n\n...${removed} ${unit} truncated...\n\n${hint}`
+        : `...${removed} ${unit} truncated...\n\n${hint}\n\n${preview}`
 
     return { content: message, truncated: true, outputPath: filepath }
   }

+ 2 - 2
packages/opencode/src/tool/registry.ts

@@ -60,12 +60,12 @@ export namespace ToolRegistry {
   function fromPlugin(id: string, def: ToolDefinition): Tool.Info {
     return {
       id,
-      init: async () => ({
+      init: async (initCtx) => ({
         parameters: z.object(def.args),
         description: def.description,
         execute: async (args, ctx) => {
           const result = await def.execute(args as any, ctx)
-          const out = await Truncate.output(result)
+          const out = await Truncate.output(result, {}, initCtx?.agent)
           return {
             title: "",
             output: out.truncated ? out.content : result,

+ 3 - 3
packages/opencode/src/tool/tool.ts

@@ -50,8 +50,8 @@ export namespace Tool {
   ): Info<Parameters, Result> {
     return {
       id,
-      init: async (ctx) => {
-        const toolInfo = init instanceof Function ? await init(ctx) : init
+      init: async (initCtx) => {
+        const toolInfo = init instanceof Function ? await init(initCtx) : init
         const execute = toolInfo.execute
         toolInfo.execute = async (args, ctx) => {
           try {
@@ -66,7 +66,7 @@ export namespace Tool {
             )
           }
           const result = await execute(args, ctx)
-          const truncated = await Truncate.output(result.output)
+          const truncated = await Truncate.output(result.output, {}, initCtx?.agent)
           return {
             ...result,
             output: truncated.content,

+ 21 - 1
packages/opencode/test/session/truncation.test.ts

@@ -83,12 +83,32 @@ describe("Truncate", () => {
       expect(result.outputPath).toBeDefined()
       expect(result.outputPath).toContain("tool_")
       expect(result.content).toContain("Full output written to:")
-      expect(result.content).toContain("Use Read or Grep to view the full content")
+      expect(result.content).toContain("Grep")
 
       const written = await Bun.file(result.outputPath!).text()
       expect(written).toBe(lines)
     })
 
+    test("suggests Task tool when agent has task permission", async () => {
+      const lines = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n")
+      const agent = { permission: [{ permission: "task", pattern: "*", action: "allow" as const }] }
+      const result = await Truncate.output(lines, { maxLines: 10 }, agent as any)
+
+      expect(result.truncated).toBe(true)
+      expect(result.content).toContain("Grep")
+      expect(result.content).toContain("Task tool")
+    })
+
+    test("omits Task tool hint when agent lacks task permission", async () => {
+      const lines = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n")
+      const agent = { permission: [{ permission: "task", pattern: "*", action: "deny" as const }] }
+      const result = await Truncate.output(lines, { maxLines: 10 }, agent as any)
+
+      expect(result.truncated).toBe(true)
+      expect(result.content).toContain("Grep")
+      expect(result.content).not.toContain("Task tool")
+    })
+
     test("does not write file when not truncated", async () => {
       const content = "short content"
       const result = await Truncate.output(content)