Dax Raad 3 luni în urmă
părinte
comite
f77e1d9734

+ 6 - 1
.opencode/opencode.jsonc

@@ -11,7 +11,12 @@
       "options": {},
     },
   },
-  "mcp": {},
+  "mcp": {
+    "context7": {
+      "type": "remote",
+      "url": "https://mcp.context7.com/mcp",
+    },
+  },
   "tools": {
     "github-triage": false,
   },

+ 28 - 5
packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx

@@ -75,14 +75,16 @@ function EditBody(props: { request: PermissionRequest }) {
   )
 }
 
-function TextBody(props: { title: string; description?: string; icon: string }) {
+function TextBody(props: { title: string; description?: string; icon?: string }) {
   const { theme } = useTheme()
   return (
     <>
       <box flexDirection="row" gap={1} paddingLeft={1}>
-        <text fg={theme.textMuted} flexShrink={0}>
-          {props.icon}
-        </text>
+        <Show when={props.icon}>
+          <text fg={theme.textMuted} flexShrink={0}>
+            {props.icon}
+          </text>
+        </Show>
         <text fg={theme.textMuted}>{props.title}</text>
       </box>
       <Show when={props.description}>
@@ -113,12 +115,33 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
     return {}
   })
 
+  const { theme } = useTheme()
+
   return (
     <Switch>
       <Match when={store.always}>
         <Prompt
           title="Always allow"
-          body={<TextBody icon="→" title={props.request.always.join("\n")} />}
+          body={
+            <Switch>
+              <Match when={props.request.always.length === 1 && props.request.always[0] === "*"}>
+                <TextBody title={"Are you sure you want to always allow " + props.request.permission + "?"} />
+              </Match>
+              <Match when={true}>
+                <box paddingLeft={1} gap={1}>
+                  <text fg={theme.textMuted}>Applies to the following patterns</text>
+                  <For each={props.request.always}>
+                    {(pattern) => (
+                      <text fg={theme.text}>
+                        {"- "}
+                        {pattern}
+                      </text>
+                    )}
+                  </For>
+                </box>
+              </Match>
+            </Switch>
+          }
           options={{ confirm: "Confirm", cancel: "Cancel" }}
           onSelect={(option) => {
             if (option === "cancel") {

+ 54 - 40
packages/opencode/src/session/prompt.ts

@@ -9,7 +9,7 @@ import { SessionRevert } from "./revert"
 import { Session } from "."
 import { Agent } from "../agent/agent"
 import { Provider } from "../provider/provider"
-import { type Tool as AITool, tool, jsonSchema } from "ai"
+import { type Tool as AITool, tool, jsonSchema, type ToolCallOptions } from "ai"
 import { SessionCompaction } from "./compaction"
 import { Instance } from "../project/instance"
 import { Bus } from "../bus"
@@ -596,6 +596,41 @@ export namespace SessionPrompt {
   }) {
     using _ = log.time("resolveTools")
     const tools: Record<string, AITool> = {}
+
+    const context = (args: any, options: ToolCallOptions): Tool.Context => ({
+      sessionID: input.session.id,
+      abort: options.abortSignal!,
+      messageID: input.processor.message.id,
+      callID: options.toolCallId,
+      extra: { model: input.model },
+      agent: input.agent.name,
+      metadata: async (val: { title?: string; metadata?: any }) => {
+        const match = input.processor.partFromToolCall(options.toolCallId)
+        if (match && match.state.status === "running") {
+          await Session.updatePart({
+            ...match,
+            state: {
+              title: val.title,
+              metadata: val.metadata,
+              status: "running",
+              input: args,
+              time: {
+                start: Date.now(),
+              },
+            },
+          })
+        }
+      },
+      async ask(req) {
+        await PermissionNext.ask({
+          ...req,
+          sessionID: input.session.parentID ?? input.session.id,
+          tool: { messageID: input.processor.message.id, callID: options.toolCallId },
+          ruleset: input.agent.permission,
+        })
+      },
+    })
+
     for (const item of await ToolRegistry.tools(input.model.providerID)) {
       const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters))
       tools[item.id] = tool({
@@ -603,57 +638,25 @@ export namespace SessionPrompt {
         description: item.description,
         inputSchema: jsonSchema(schema as any),
         async execute(args, options) {
+          const ctx = context(args, options)
           await Plugin.trigger(
             "tool.execute.before",
             {
               tool: item.id,
-              sessionID: input.session.id,
-              callID: options.toolCallId,
+              sessionID: ctx.sessionID,
+              callID: ctx.callID,
             },
             {
               args,
             },
           )
-          const ctx: Tool.Context = {
-            sessionID: input.session.id,
-            abort: options.abortSignal!,
-            messageID: input.processor.message.id,
-            callID: options.toolCallId,
-            extra: { model: input.model },
-            agent: input.agent.name,
-            metadata: async (val: { title?: string; metadata?: any }) => {
-              const match = input.processor.partFromToolCall(options.toolCallId)
-              if (match && match.state.status === "running") {
-                await Session.updatePart({
-                  ...match,
-                  state: {
-                    title: val.title,
-                    metadata: val.metadata,
-                    status: "running",
-                    input: args,
-                    time: {
-                      start: Date.now(),
-                    },
-                  },
-                })
-              }
-            },
-            async ask(req) {
-              await PermissionNext.ask({
-                ...req,
-                sessionID: input.session.parentID ?? input.session.id,
-                tool: { messageID: input.processor.message.id, callID: options.toolCallId },
-                ruleset: input.agent.permission,
-              })
-            },
-          }
           const result = await item.execute(args, ctx)
           await Plugin.trigger(
             "tool.execute.after",
             {
               tool: item.id,
-              sessionID: input.session.id,
-              callID: options.toolCallId,
+              sessionID: ctx.sessionID,
+              callID: ctx.callID,
             },
             result,
           )
@@ -667,30 +670,41 @@ export namespace SessionPrompt {
         },
       })
     }
+
     for (const [key, item] of Object.entries(await MCP.tools())) {
       const execute = item.execute
       if (!execute) continue
 
       // Wrap execute to add plugin hooks and format output
       item.execute = async (args, opts) => {
+        const ctx = context(args, opts)
+
         await Plugin.trigger(
           "tool.execute.before",
           {
             tool: key,
-            sessionID: input.session.id,
+            sessionID: ctx.sessionID,
             callID: opts.toolCallId,
           },
           {
             args,
           },
         )
+
+        await ctx.ask({
+          permission: key,
+          metadata: {},
+          patterns: ["*"],
+          always: ["*"],
+        })
+
         const result = await execute(args, opts)
 
         await Plugin.trigger(
           "tool.execute.after",
           {
             tool: key,
-            sessionID: input.session.id,
+            sessionID: ctx.sessionID,
             callID: opts.toolCallId,
           },
           result,

+ 0 - 1
packages/opencode/src/tool/registry.ts

@@ -2,7 +2,6 @@ import { BashTool } from "./bash"
 import { EditTool } from "./edit"
 import { GlobTool } from "./glob"
 import { GrepTool } from "./grep"
-import { ListTool } from "./ls"
 import { BatchTool } from "./batch"
 import { ReadTool } from "./read"
 import { TaskTool } from "./task"