Procházet zdrojové kódy

fix: rm dead code and add back permission hook

Aiden Cline před 3 měsíci
rodič
revize
fbc60399a2

+ 0 - 210
packages/opencode/src/permission/index.ts

@@ -1,210 +0,0 @@
-import { BusEvent } from "@/bus/bus-event"
-import { Bus } from "@/bus"
-import z from "zod"
-import { Log } from "../util/log"
-import { Identifier } from "../id/id"
-import { Plugin } from "../plugin"
-import { Instance } from "../project/instance"
-import { Wildcard } from "../util/wildcard"
-
-export namespace Permission {
-  const log = Log.create({ service: "permission" })
-
-  function toKeys(pattern: Info["pattern"], type: string): string[] {
-    return pattern === undefined ? [type] : Array.isArray(pattern) ? pattern : [pattern]
-  }
-
-  function covered(keys: string[], approved: Record<string, boolean>): boolean {
-    const pats = Object.keys(approved)
-    return keys.every((k) => pats.some((p) => Wildcard.match(k, p)))
-  }
-
-  export const Info = z
-    .object({
-      id: z.string(),
-      type: z.string(),
-      pattern: z.union([z.string(), z.array(z.string())]).optional(),
-      sessionID: z.string(),
-      messageID: z.string(),
-      callID: z.string().optional(),
-      message: z.string(),
-      metadata: z.record(z.string(), z.any()),
-      time: z.object({
-        created: z.number(),
-      }),
-    })
-    .meta({
-      ref: "Permission",
-    })
-  export type Info = z.infer<typeof Info>
-
-  export const Event = {
-    Updated: BusEvent.define("permission.updated", Info),
-    Replied: BusEvent.define(
-      "permission.replied",
-      z.object({
-        sessionID: z.string(),
-        permissionID: z.string(),
-        response: z.string(),
-      }),
-    ),
-  }
-
-  const state = Instance.state(
-    () => {
-      const pending: {
-        [sessionID: string]: {
-          [permissionID: string]: {
-            info: Info
-            resolve: () => void
-            reject: (e: any) => void
-          }
-        }
-      } = {}
-
-      const approved: {
-        [sessionID: string]: {
-          [permissionID: string]: boolean
-        }
-      } = {}
-
-      return {
-        pending,
-        approved,
-      }
-    },
-    async (state) => {
-      for (const pending of Object.values(state.pending)) {
-        for (const item of Object.values(pending)) {
-          item.reject(new RejectedError(item.info.sessionID, item.info.id, item.info.callID, item.info.metadata))
-        }
-      }
-    },
-  )
-
-  export function pending() {
-    return state().pending
-  }
-
-  export function list() {
-    const { pending } = state()
-    const result: Info[] = []
-    for (const items of Object.values(pending)) {
-      for (const item of Object.values(items)) {
-        result.push(item.info)
-      }
-    }
-    return result.sort((a, b) => a.id.localeCompare(b.id))
-  }
-
-  export async function ask(input: {
-    type: Info["type"]
-    message: Info["message"]
-    pattern?: Info["pattern"]
-    callID?: Info["callID"]
-    sessionID: Info["sessionID"]
-    messageID: Info["messageID"]
-    metadata: Info["metadata"]
-  }) {
-    const { pending, approved } = state()
-    log.info("asking", {
-      sessionID: input.sessionID,
-      messageID: input.messageID,
-      toolCallID: input.callID,
-      pattern: input.pattern,
-    })
-    const approvedForSession = approved[input.sessionID] || {}
-    const keys = toKeys(input.pattern, input.type)
-    if (covered(keys, approvedForSession)) return
-    const info: Info = {
-      id: Identifier.ascending("permission"),
-      type: input.type,
-      pattern: input.pattern,
-      sessionID: input.sessionID,
-      messageID: input.messageID,
-      callID: input.callID,
-      message: input.message,
-      metadata: input.metadata,
-      time: {
-        created: Date.now(),
-      },
-    }
-
-    switch (
-      await Plugin.trigger("permission.ask", info, {
-        status: "ask",
-      }).then((x) => x.status)
-    ) {
-      case "deny":
-        throw new RejectedError(info.sessionID, info.id, info.callID, info.metadata)
-      case "allow":
-        return
-    }
-
-    pending[input.sessionID] = pending[input.sessionID] || {}
-    return new Promise<void>((resolve, reject) => {
-      pending[input.sessionID][info.id] = {
-        info,
-        resolve,
-        reject,
-      }
-      Bus.publish(Event.Updated, info)
-    })
-  }
-
-  export const Response = z.enum(["once", "always", "reject"])
-  export type Response = z.infer<typeof Response>
-
-  export function respond(input: { sessionID: Info["sessionID"]; permissionID: Info["id"]; response: Response }) {
-    log.info("response", input)
-    const { pending, approved } = state()
-    const match = pending[input.sessionID]?.[input.permissionID]
-    if (!match) return
-    delete pending[input.sessionID][input.permissionID]
-    Bus.publish(Event.Replied, {
-      sessionID: input.sessionID,
-      permissionID: input.permissionID,
-      response: input.response,
-    })
-    if (input.response === "reject") {
-      match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.callID, match.info.metadata))
-      return
-    }
-    match.resolve()
-    if (input.response === "always") {
-      approved[input.sessionID] = approved[input.sessionID] || {}
-      const approveKeys = toKeys(match.info.pattern, match.info.type)
-      for (const k of approveKeys) {
-        approved[input.sessionID][k] = true
-      }
-      const items = pending[input.sessionID]
-      if (!items) return
-      for (const item of Object.values(items)) {
-        const itemKeys = toKeys(item.info.pattern, item.info.type)
-        if (covered(itemKeys, approved[input.sessionID])) {
-          respond({
-            sessionID: item.info.sessionID,
-            permissionID: item.info.id,
-            response: input.response,
-          })
-        }
-      }
-    }
-  }
-
-  export class RejectedError extends Error {
-    constructor(
-      public readonly sessionID: string,
-      public readonly permissionID: string,
-      public readonly toolCallID?: string,
-      public readonly metadata?: Record<string, any>,
-      public readonly reason?: string,
-    ) {
-      super(
-        reason !== undefined
-          ? reason
-          : `The user rejected permission to use this specific tool call. You may try again with different parameters.`,
-      )
-    }
-  }
-}

+ 23 - 4
packages/opencode/src/permission/next.ts

@@ -2,6 +2,7 @@ import { Bus } from "@/bus"
 import { BusEvent } from "@/bus/bus-event"
 import { Config } from "@/config/config"
 import { Identifier } from "@/id/id"
+import { Plugin } from "@/plugin"
 import { Instance } from "@/project/instance"
 import { Storage } from "@/storage/storage"
 import { fn } from "@/util/fn"
@@ -127,11 +128,29 @@ export namespace PermissionNext {
           throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission)))
         if (rule.action === "ask") {
           const id = input.id ?? Identifier.ascending("permission")
-          return new Promise<void>((resolve, reject) => {
-            const info: Request = {
+          const info: Request = {
+            id,
+            ...request,
+          }
+          const hook = await Plugin.trigger(
+            "permission.ask",
+            {
               id,
-              ...request,
-            }
+              type: request.permission,
+              pattern: request.patterns,
+              sessionID: request.sessionID,
+              messageID: request.tool?.messageID ?? "",
+              callID: request.tool?.callID,
+              title: request.permission,
+              metadata: request.metadata,
+              time: { created: Date.now() },
+            },
+            { status: "ask" as "ask" | "deny" | "allow" },
+          ).catch(() => ({ status: "ask" as const }))
+          if (hook.status === "deny")
+            throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission)))
+          if (hook.status === "allow") continue
+          return new Promise<void>((resolve, reject) => {
             s.pending[id] = {
               info,
               resolve,