wip: refactor permissions

This commit is contained in:
Dax Raad
2025-07-31 12:26:34 -04:00
parent 872b1e068f
commit a5b20f973f
15 changed files with 294 additions and 98 deletions

View File

@@ -5,6 +5,7 @@ export namespace Identifier {
const prefixes = {
session: "ses",
message: "msg",
permission: "per",
user: "usr",
part: "prt",
} as const

View File

@@ -3,6 +3,7 @@ import { z } from "zod"
import { Bus } from "../bus"
import { Log } from "../util/log"
import { Installation } from "../installation"
import { Identifier } from "../id/id"
export namespace Permission {
const log = Log.create({ service: "permission" })
@@ -10,9 +11,11 @@ export namespace Permission {
export const Info = z
.object({
id: z.string(),
type: z.string(),
pattern: z.string().optional(),
sessionID: z.string(),
messageID: z.string(),
toolCallID: z.string().optional(),
callID: z.string().optional(),
title: z.string(),
metadata: z.record(z.any()),
time: z.object({
@@ -55,18 +58,19 @@ export namespace Permission {
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.toolCallID))
item.reject(new RejectedError(item.info.sessionID, item.info.id, item.info.callID))
}
}
},
)
export function ask(input: {
id: Info["id"]
type: Info["type"]
title: Info["title"]
pattern?: Info["pattern"]
callID?: Info["callID"]
sessionID: Info["sessionID"]
messageID: Info["messageID"]
toolCallID?: Info["toolCallID"]
title: Info["title"]
metadata: Info["metadata"]
}) {
// TODO: dax, remove this when you're happy with permissions
@@ -75,24 +79,16 @@ export namespace Permission {
const { pending, approved } = state()
log.info("asking", {
sessionID: input.sessionID,
permissionID: input.id,
messageID: input.messageID,
toolCallID: input.toolCallID,
toolCallID: input.callID,
})
if (approved[input.sessionID]?.[input.id]) {
log.info("previously approved", {
sessionID: input.sessionID,
permissionID: input.id,
messageID: input.messageID,
toolCallID: input.toolCallID,
})
return
}
if (approved[input.sessionID]?.[input.pattern ?? input.type]) return
const info: Info = {
id: input.id,
id: Identifier.ascending("permission"),
type: input.type,
sessionID: input.sessionID,
messageID: input.messageID,
toolCallID: input.toolCallID,
callID: input.callID,
title: input.title,
metadata: input.metadata,
time: {
@@ -101,18 +97,11 @@ export namespace Permission {
}
pending[input.sessionID] = pending[input.sessionID] || {}
return new Promise<void>((resolve, reject) => {
pending[input.sessionID][input.id] = {
pending[input.sessionID][info.id] = {
info,
resolve,
reject,
}
// setTimeout(() => {
// respond({
// sessionID: input.sessionID,
// permissionID: input.id,
// response: "always",
// })
// }, 1000)
Bus.publish(Event.Updated, info)
})
}
@@ -127,7 +116,7 @@ export namespace Permission {
if (!match) return
delete pending[input.sessionID][input.permissionID]
if (input.response === "reject") {
match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.toolCallID))
match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.callID))
return
}
match.resolve()

View File

@@ -108,10 +108,11 @@ export const BashTool = Tool.define("bash", {
const cfg = await Config.get()
if (cfg.permission?.bash === "ask")
await Permission.ask({
id: "bash",
type: "bash",
pattern: params.command.split(" ").slice(0, 2).join(" ").trim(),
sessionID: ctx.sessionID,
messageID: ctx.messageID,
toolCallID: ctx.toolCallID,
callID: ctx.toolCallID,
title: "Run this command: " + params.command,
metadata: {
command: params.command,

View File

@@ -50,10 +50,10 @@ export const EditTool = Tool.define("edit", {
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
if (cfg.permission?.edit === "ask") {
await Permission.ask({
id: "edit",
type: "edit",
sessionID: ctx.sessionID,
messageID: ctx.messageID,
toolCallID: ctx.toolCallID,
callID: ctx.toolCallID,
title: "Edit this file: " + filePath,
metadata: {
filePath,
@@ -79,10 +79,10 @@ export const EditTool = Tool.define("edit", {
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
if (cfg.permission?.edit === "ask") {
await Permission.ask({
id: "edit",
type: "edit",
sessionID: ctx.sessionID,
messageID: ctx.messageID,
toolCallID: ctx.toolCallID,
callID: ctx.toolCallID,
title: "Edit this file: " + filePath,
metadata: {
filePath,

View File

@@ -31,10 +31,10 @@ export const WriteTool = Tool.define("write", {
const cfg = await Config.get()
if (cfg.permission?.edit === "ask")
await Permission.ask({
id: "write",
type: "write",
sessionID: ctx.sessionID,
messageID: ctx.messageID,
toolCallID: ctx.toolCallID,
callID: ctx.toolCallID,
title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
metadata: {
filePath: filepath,