mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-24 06:45:22 +00:00
sync
This commit is contained in:
@@ -78,6 +78,8 @@
|
||||
"url": "https://github.com/sst/opencode"
|
||||
},
|
||||
"license": "MIT",
|
||||
"randomField": "hello from claude",
|
||||
"anotherRandomField": "xkcd-927-compliance-level",
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 120
|
||||
|
||||
@@ -206,7 +206,7 @@ export const RunCommand = cmd({
|
||||
const permission = event.properties
|
||||
if (permission.sessionID !== sessionID) continue
|
||||
const result = await select({
|
||||
message: `Permission required to run: ${permission.message}`,
|
||||
message: `Permission required: ${permission.permission} (${permission.patterns.join(", ")})`,
|
||||
options: [
|
||||
{ value: "once", label: "Allow once" },
|
||||
{ value: "always", label: "Always allow: " + permission.always.join(", ") },
|
||||
|
||||
@@ -1390,7 +1390,7 @@ function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMess
|
||||
},
|
||||
get permission() {
|
||||
const permissions = sync.data.permission[props.message.sessionID] ?? []
|
||||
const permissionIndex = permissions.findIndex((x) => x.callID === props.part.callID)
|
||||
const permissionIndex = permissions.findIndex((x) => x.tool?.callID === props.part.callID)
|
||||
return permissions[permissionIndex]
|
||||
},
|
||||
get tool() {
|
||||
@@ -1483,12 +1483,13 @@ function InlineTool(props: { icon: string; complete: any; pending: string; child
|
||||
const sync = useSync()
|
||||
|
||||
const permission = createMemo(() => {
|
||||
const callID = sync.data.permission[ctx.sessionID]?.at(0)?.callID
|
||||
const callID = sync.data.permission[ctx.sessionID]?.at(0)?.tool?.callID
|
||||
if (!callID) return false
|
||||
return callID === props.part.callID
|
||||
})
|
||||
|
||||
const fg = createMemo(() => {
|
||||
if (permission()) return theme.warning
|
||||
if (props.complete) return theme.textMuted
|
||||
return theme.text
|
||||
})
|
||||
@@ -1528,9 +1529,6 @@ function InlineTool(props: { icon: string; complete: any; pending: string; child
|
||||
<Show fallback={<>~ {props.pending}</>} when={props.complete}>
|
||||
<span style={{ bold: true }}>{props.icon}</span> {props.children}
|
||||
</Show>
|
||||
<Show when={permission()}>
|
||||
·<span style={{ fg: theme.warning }}> Permission required</span>
|
||||
</Show>
|
||||
</text>
|
||||
<Show when={error() && !denied()}>
|
||||
<text fg={theme.error}>{error()}</text>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SplitBorder } from "../../component/border"
|
||||
import { useSync } from "../../context/sync"
|
||||
import path from "path"
|
||||
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
||||
import { Locale } from "@/util/locale"
|
||||
|
||||
function normalizePath(input?: string) {
|
||||
if (!input) return ""
|
||||
@@ -30,9 +31,8 @@ function EditBody(props: { request: PermissionRequest }) {
|
||||
const sync = useSync()
|
||||
const dimensions = useTerminalDimensions()
|
||||
|
||||
const metadata = props.request.metadata as { filepath?: string; diff?: string }
|
||||
const filepath = createMemo(() => metadata.filepath ?? "")
|
||||
const diff = createMemo(() => metadata.diff ?? "")
|
||||
const filepath = createMemo(() => (props.request.metadata?.filepath as string) ?? "")
|
||||
const diff = createMemo(() => (props.request.metadata?.diff as string) ?? "")
|
||||
|
||||
const view = createMemo(() => {
|
||||
const diffStyle = sync.data.config.tui?.diff_style
|
||||
@@ -44,7 +44,7 @@ function EditBody(props: { request: PermissionRequest }) {
|
||||
|
||||
return (
|
||||
<box flexDirection="column" gap={1}>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<box flexDirection="row" gap={1} paddingLeft={1}>
|
||||
<text fg={theme.textMuted}>{"→"}</text>
|
||||
<text fg={theme.textMuted}>Edit {normalizePath(filepath())}</text>
|
||||
</box>
|
||||
@@ -75,32 +75,50 @@ function EditBody(props: { request: PermissionRequest }) {
|
||||
)
|
||||
}
|
||||
|
||||
function TextBody(props: { text: string }) {
|
||||
function TextBody(props: { title: string; description?: string; icon: string }) {
|
||||
const { theme } = useTheme()
|
||||
return (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={theme.textMuted} flexShrink={0}>
|
||||
{"→"}
|
||||
</text>
|
||||
<text fg={theme.textMuted}>{props.text}</text>
|
||||
</box>
|
||||
<>
|
||||
<box flexDirection="row" gap={1} paddingLeft={1}>
|
||||
<text fg={theme.textMuted} flexShrink={0}>
|
||||
{props.icon}
|
||||
</text>
|
||||
<text fg={theme.textMuted}>{props.title}</text>
|
||||
</box>
|
||||
<Show when={props.description}>
|
||||
<box paddingLeft={1}>
|
||||
<text fg={theme.text}>{props.description}</text>
|
||||
</box>
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function PermissionPrompt(props: { request: PermissionRequest }) {
|
||||
const sdk = useSDK()
|
||||
const sync = useSync()
|
||||
const [store, setStore] = createStore({
|
||||
always: false,
|
||||
})
|
||||
|
||||
const metadata = props.request.metadata as { filepath?: string }
|
||||
const input = createMemo(() => {
|
||||
const tool = props.request.tool
|
||||
if (!tool) return {}
|
||||
const parts = sync.data.part[tool.messageID] ?? []
|
||||
for (const part of parts) {
|
||||
if (part.type === "tool" && part.callID === tool.callID && part.state.status !== "pending") {
|
||||
return part.state.input ?? {}
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={store.always}>
|
||||
<Prompt
|
||||
title="Always allow"
|
||||
body={<TextBody text={props.request.always.join("\n")} />}
|
||||
body={<TextBody icon="→" title={props.request.always.join("\n")} />}
|
||||
options={{ confirm: "Confirm", cancel: "Cancel" }}
|
||||
onSelect={(option) => {
|
||||
if (option === "cancel") {
|
||||
@@ -114,27 +132,61 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
|
||||
}}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.request.permission === "edit" && !store.always}>
|
||||
<Prompt
|
||||
title="Permission required"
|
||||
body={<EditBody request={props.request} />}
|
||||
options={{ once: "Allow once", always: "Allow always", reject: "Reject" }}
|
||||
onSelect={(option) => {
|
||||
if (option === "always") {
|
||||
setStore("always", true)
|
||||
return
|
||||
}
|
||||
sdk.client.permission.reply({
|
||||
reply: option as "once" | "reject",
|
||||
requestID: props.request.id,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={!store.always}>
|
||||
<Prompt
|
||||
title="Permission required"
|
||||
body={<TextBody text={props.request.message} />}
|
||||
body={
|
||||
<Switch>
|
||||
<Match when={props.request.permission === "edit"}>
|
||||
<EditBody request={props.request} />
|
||||
</Match>
|
||||
<Match when={props.request.permission === "read"}>
|
||||
<TextBody icon="→" title={`Read ` + normalizePath(input().filePath as string)} />
|
||||
</Match>
|
||||
<Match when={props.request.permission === "glob"}>
|
||||
<TextBody icon="✱" title={`Glob "` + (input().pattern ?? "") + `"`} />
|
||||
</Match>
|
||||
<Match when={props.request.permission === "grep"}>
|
||||
<TextBody icon="✱" title={`Grep "` + (input().pattern ?? "") + `"`} />
|
||||
</Match>
|
||||
<Match when={props.request.permission === "list"}>
|
||||
<TextBody icon="→" title={`List ` + normalizePath(input().path as string)} />
|
||||
</Match>
|
||||
<Match when={props.request.permission === "bash"}>
|
||||
<TextBody
|
||||
icon="#"
|
||||
title={(input().description as string) ?? ""}
|
||||
description={("$ " + input().command) as string}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.request.permission === "task"}>
|
||||
<TextBody
|
||||
icon="◉"
|
||||
title={
|
||||
`${Locale.titlecase(input().subagent_type as string)} Task "` + (input().description ?? "") + `"`
|
||||
}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.request.permission === "webfetch"}>
|
||||
<TextBody icon="%" title={`WebFetch ` + (input().url ?? "")} />
|
||||
</Match>
|
||||
<Match when={props.request.permission === "websearch"}>
|
||||
<TextBody icon="◈" title={`Exa Web Search "` + (input().query ?? "") + `"`} />
|
||||
</Match>
|
||||
<Match when={props.request.permission === "codesearch"}>
|
||||
<TextBody icon="◇" title={`Exa Code Search "` + (input().query ?? "") + `"`} />
|
||||
</Match>
|
||||
<Match when={props.request.permission === "external_directory"}>
|
||||
<TextBody icon="⚠" title={`Access external directory ` + normalizePath(input().path as string)} />
|
||||
</Match>
|
||||
<Match when={props.request.permission === "doom_loop"}>
|
||||
<TextBody icon="⟳" title="Continue after repeated failures" />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<TextBody icon="⚙" title={`Call tool ` + props.request.permission} />
|
||||
</Match>
|
||||
</Switch>
|
||||
}
|
||||
options={{ once: "Allow once", always: "Allow always", reject: "Reject" }}
|
||||
onSelect={(option) => {
|
||||
if (option === "always") {
|
||||
@@ -192,8 +244,8 @@ function Prompt<const T extends Record<string, string>>(props: {
|
||||
borderColor={theme.warning}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
>
|
||||
<box gap={1} paddingLeft={2} paddingRight={3} paddingTop={1} paddingBottom={1}>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<box gap={1} paddingLeft={1} paddingRight={3} paddingTop={1} paddingBottom={1}>
|
||||
<box flexDirection="row" gap={1} paddingLeft={1}>
|
||||
<text fg={theme.warning}>{"△"}</text>
|
||||
<text fg={theme.text}>{props.title}</text>
|
||||
</box>
|
||||
|
||||
@@ -56,13 +56,17 @@ export namespace PermissionNext {
|
||||
export const Request = z
|
||||
.object({
|
||||
id: Identifier.schema("permission"),
|
||||
callID: z.string().optional(),
|
||||
sessionID: Identifier.schema("session"),
|
||||
permission: z.string(),
|
||||
patterns: z.string().array(),
|
||||
message: z.string(),
|
||||
metadata: z.record(z.string(), z.any()),
|
||||
always: z.string().array(),
|
||||
tool: z
|
||||
.object({
|
||||
messageID: z.string(),
|
||||
callID: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.meta({
|
||||
ref: "PermissionRequest",
|
||||
|
||||
@@ -157,7 +157,6 @@ export namespace SessionProcessor {
|
||||
await PermissionNext.ask({
|
||||
permission: "doom_loop",
|
||||
patterns: [value.toolName],
|
||||
message: `Possible doom loop: "${value.toolName}" called ${DOOM_LOOP_THRESHOLD} times with identical arguments`,
|
||||
sessionID: input.assistantMessage.sessionID,
|
||||
metadata: {
|
||||
tool: value.toolName,
|
||||
|
||||
@@ -38,6 +38,8 @@ import { NamedError } from "@opencode-ai/util/error"
|
||||
import { fn } from "@/util/fn"
|
||||
import { SessionProcessor } from "./processor"
|
||||
import { TaskTool } from "@/tool/task"
|
||||
import { Tool } from "@/tool/tool"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
import { SessionStatus } from "./status"
|
||||
import { LLM } from "./llm"
|
||||
import { iife } from "@/util/iife"
|
||||
@@ -349,28 +351,35 @@ export namespace SessionPrompt {
|
||||
{ args: taskArgs },
|
||||
)
|
||||
let executionError: Error | undefined
|
||||
const result = await taskTool
|
||||
.execute(taskArgs, {
|
||||
agent: task.agent,
|
||||
messageID: assistantMessage.id,
|
||||
sessionID: sessionID,
|
||||
abort,
|
||||
async metadata(input) {
|
||||
await Session.updatePart({
|
||||
...part,
|
||||
type: "tool",
|
||||
state: {
|
||||
...part.state,
|
||||
...input,
|
||||
},
|
||||
} satisfies MessageV2.ToolPart)
|
||||
},
|
||||
})
|
||||
.catch((error) => {
|
||||
executionError = error
|
||||
log.error("subtask execution failed", { error, agent: task.agent, description: task.description })
|
||||
return undefined
|
||||
})
|
||||
const taskAgent = await Agent.get(task.agent)
|
||||
const taskCtx: Tool.Context = {
|
||||
agent: task.agent,
|
||||
messageID: assistantMessage.id,
|
||||
sessionID: sessionID,
|
||||
abort,
|
||||
async metadata(input) {
|
||||
await Session.updatePart({
|
||||
...part,
|
||||
type: "tool",
|
||||
state: {
|
||||
...part.state,
|
||||
...input,
|
||||
},
|
||||
} satisfies MessageV2.ToolPart)
|
||||
},
|
||||
async ask(req) {
|
||||
await PermissionNext.ask({
|
||||
...req,
|
||||
sessionID: sessionID,
|
||||
ruleset: taskAgent.permission,
|
||||
})
|
||||
},
|
||||
}
|
||||
const result = await taskTool.execute(taskArgs, taskCtx).catch((error) => {
|
||||
executionError = error
|
||||
log.error("subtask execution failed", { error, agent: task.agent, description: task.description })
|
||||
return undefined
|
||||
})
|
||||
await Plugin.trigger(
|
||||
"tool.execute.after",
|
||||
{
|
||||
@@ -604,14 +613,14 @@ export namespace SessionPrompt {
|
||||
args,
|
||||
},
|
||||
)
|
||||
const result = await item.execute(args, {
|
||||
const ctx: Tool.Context = {
|
||||
sessionID: input.sessionID,
|
||||
abort: options.abortSignal!,
|
||||
messageID: input.processor.message.id,
|
||||
callID: options.toolCallId,
|
||||
extra: { model: input.model },
|
||||
agent: input.agent.name,
|
||||
metadata: async (val) => {
|
||||
metadata: async (val: { title?: string; metadata?: any }) => {
|
||||
const match = input.processor.partFromToolCall(options.toolCallId)
|
||||
if (match && match.state.status === "running") {
|
||||
await Session.updatePart({
|
||||
@@ -628,7 +637,16 @@ export namespace SessionPrompt {
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
async ask(req) {
|
||||
await PermissionNext.ask({
|
||||
...req,
|
||||
sessionID: input.sessionID,
|
||||
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",
|
||||
{
|
||||
@@ -826,14 +844,16 @@ export namespace SessionPrompt {
|
||||
await ReadTool.init()
|
||||
.then(async (t) => {
|
||||
const model = await Provider.getModel(info.model.providerID, info.model.modelID)
|
||||
const result = await t.execute(args, {
|
||||
const readCtx: Tool.Context = {
|
||||
sessionID: input.sessionID,
|
||||
abort: new AbortController().signal,
|
||||
agent: input.agent!,
|
||||
messageID: info.id,
|
||||
extra: { bypassCwdCheck: true, model },
|
||||
metadata: async () => {},
|
||||
})
|
||||
ask: async () => {},
|
||||
}
|
||||
const result = await t.execute(args, readCtx)
|
||||
pieces.push({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: info.id,
|
||||
@@ -885,16 +905,16 @@ export namespace SessionPrompt {
|
||||
|
||||
if (part.mime === "application/x-directory") {
|
||||
const args = { path: filepath }
|
||||
const result = await ListTool.init().then((t) =>
|
||||
t.execute(args, {
|
||||
sessionID: input.sessionID,
|
||||
abort: new AbortController().signal,
|
||||
agent: input.agent!,
|
||||
messageID: info.id,
|
||||
extra: { bypassCwdCheck: true },
|
||||
metadata: async () => {},
|
||||
}),
|
||||
)
|
||||
const listCtx: Tool.Context = {
|
||||
sessionID: input.sessionID,
|
||||
abort: new AbortController().signal,
|
||||
agent: input.agent!,
|
||||
messageID: info.id,
|
||||
extra: { bypassCwdCheck: true },
|
||||
metadata: async () => {},
|
||||
ask: async () => {},
|
||||
}
|
||||
const result = await ListTool.init().then((t) => t.execute(args, listCtx))
|
||||
return [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
|
||||
@@ -6,13 +6,13 @@ import { Log } from "../util/log"
|
||||
import { Instance } from "../project/instance"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { Language } from "web-tree-sitter"
|
||||
import { Agent } from "@/agent/agent"
|
||||
|
||||
import { $ } from "bun"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { fileURLToPath } from "url"
|
||||
import { Flag } from "@/flag/flag.ts"
|
||||
import { Shell } from "@/shell/shell"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
import { BashArity } from "@/permission/arity"
|
||||
|
||||
const MAX_OUTPUT_LENGTH = Flag.OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH || 30_000
|
||||
@@ -80,8 +80,6 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
if (!tree) {
|
||||
throw new Error("Failed to parse command")
|
||||
}
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
|
||||
const directories = new Set<string>()
|
||||
if (!Filesystem.contains(Instance.directory, cwd)) directories.add(cwd)
|
||||
const patterns = new Set<string>()
|
||||
@@ -134,29 +132,20 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
}
|
||||
|
||||
if (directories.size > 0) {
|
||||
const dirs = Array.from(directories)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "external_directory",
|
||||
message: `Requesting access to external directories: ${dirs.join(", ")}`,
|
||||
patterns: Array.from(directories),
|
||||
always: Array.from(directories).map((x) => x + "*"),
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {},
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
}
|
||||
|
||||
if (patterns.size > 0) {
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "bash",
|
||||
patterns: Array.from(patterns),
|
||||
always: Array.from(always),
|
||||
sessionID: ctx.sessionID,
|
||||
message: params.command,
|
||||
metadata: {},
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import z from "zod"
|
||||
import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./codesearch.txt"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
import { Agent } from "@/agent/agent"
|
||||
|
||||
const API_CONFIG = {
|
||||
BASE_URL: "https://mcp.exa.ai",
|
||||
@@ -52,20 +50,14 @@ export const CodeSearchTool = Tool.define("codesearch", {
|
||||
),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "codesearch",
|
||||
message: "Search code for: " + params.query,
|
||||
patterns: [params.query],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
query: params.query,
|
||||
tokensNum: params.tokensNum,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
|
||||
const codeRequest: McpCodeRequest = {
|
||||
|
||||
@@ -14,9 +14,7 @@ import { Bus } from "../bus"
|
||||
import { FileTime } from "../file/time"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Agent } from "../agent/agent"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
const MAX_DIAGNOSTICS_PER_FILE = 20
|
||||
|
||||
@@ -41,24 +39,17 @@ export const EditTool = Tool.define("edit", {
|
||||
throw new Error("oldString and newString must be different")
|
||||
}
|
||||
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
|
||||
const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
|
||||
if (!Filesystem.contains(Instance.directory, filePath)) {
|
||||
const parentDir = path.dirname(filePath)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "external_directory",
|
||||
message: `Edit file outside working directory: ${filePath}`,
|
||||
patterns: [parentDir, path.join(parentDir, "*")],
|
||||
always: [parentDir + "/*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
filepath: filePath,
|
||||
parentDir,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,19 +60,14 @@ export const EditTool = Tool.define("edit", {
|
||||
if (params.oldString === "") {
|
||||
contentNew = params.newString
|
||||
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "edit",
|
||||
message: "Edit this file: " + path.relative(Instance.directory, filePath),
|
||||
patterns: [path.relative(Instance.worktree, filePath)],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
filepath: filePath,
|
||||
diff,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
await Bun.write(filePath, params.newString)
|
||||
await Bus.publish(File.Event.Edited, {
|
||||
@@ -102,18 +88,14 @@ export const EditTool = Tool.define("edit", {
|
||||
diff = trimDiff(
|
||||
createTwoFilesPatch(filePath, filePath, normalizeLineEndings(contentOld), normalizeLineEndings(contentNew)),
|
||||
)
|
||||
await PermissionNext.ask({
|
||||
await ctx.ask({
|
||||
permission: "edit",
|
||||
callID: ctx.callID,
|
||||
message: "Edit this file: " + path.relative(Instance.directory, filePath),
|
||||
patterns: [path.relative(Instance.worktree, filePath)],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
filepath: filePath,
|
||||
diff,
|
||||
},
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
|
||||
await file.write(contentNew)
|
||||
@@ -127,6 +109,26 @@ export const EditTool = Tool.define("edit", {
|
||||
FileTime.read(ctx.sessionID, filePath)
|
||||
})
|
||||
|
||||
const filediff: Snapshot.FileDiff = {
|
||||
file: filePath,
|
||||
before: contentOld,
|
||||
after: contentNew,
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
}
|
||||
for (const change of diffLines(contentOld, contentNew)) {
|
||||
if (change.added) filediff.additions += change.count || 0
|
||||
if (change.removed) filediff.deletions += change.count || 0
|
||||
}
|
||||
|
||||
ctx.metadata({
|
||||
metadata: {
|
||||
diff,
|
||||
filediff,
|
||||
diagnostics: {},
|
||||
},
|
||||
})
|
||||
|
||||
let output = ""
|
||||
await LSP.touchFile(filePath, true)
|
||||
const diagnostics = await LSP.diagnostics()
|
||||
@@ -140,18 +142,6 @@ export const EditTool = Tool.define("edit", {
|
||||
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</file_diagnostics>\n`
|
||||
}
|
||||
|
||||
const filediff: Snapshot.FileDiff = {
|
||||
file: filePath,
|
||||
before: contentOld,
|
||||
after: contentNew,
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
}
|
||||
for (const change of diffLines(contentOld, contentNew)) {
|
||||
if (change.added) filediff.additions += change.count || 0
|
||||
if (change.removed) filediff.deletions += change.count || 0
|
||||
}
|
||||
|
||||
return {
|
||||
metadata: {
|
||||
diagnostics,
|
||||
|
||||
@@ -4,8 +4,6 @@ import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./glob.txt"
|
||||
import { Ripgrep } from "../file/ripgrep"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
export const GlobTool = Tool.define("glob", {
|
||||
description: DESCRIPTION,
|
||||
@@ -19,20 +17,14 @@ export const GlobTool = Tool.define("glob", {
|
||||
),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "glob",
|
||||
message: `Glob search: ${params.pattern}`,
|
||||
patterns: [params.pattern],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
pattern: params.pattern,
|
||||
path: params.path,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
|
||||
let search = params.path ?? Instance.directory
|
||||
|
||||
@@ -4,8 +4,6 @@ import { Ripgrep } from "../file/ripgrep"
|
||||
|
||||
import DESCRIPTION from "./grep.txt"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
const MAX_LINE_LENGTH = 2000
|
||||
|
||||
@@ -21,21 +19,15 @@ export const GrepTool = Tool.define("grep", {
|
||||
throw new Error("pattern is required")
|
||||
}
|
||||
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "grep",
|
||||
message: `Grep search: ${params.pattern}`,
|
||||
patterns: [params.pattern],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
pattern: params.pattern,
|
||||
path: params.path,
|
||||
include: params.include,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
|
||||
const searchPath = params.path || Instance.directory
|
||||
|
||||
@@ -4,8 +4,6 @@ import * as path from "path"
|
||||
import DESCRIPTION from "./ls.txt"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Ripgrep } from "../file/ripgrep"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
export const IGNORE_PATTERNS = [
|
||||
"node_modules/",
|
||||
@@ -45,19 +43,13 @@ export const ListTool = Tool.define("list", {
|
||||
async execute(params, ctx) {
|
||||
const searchPath = path.resolve(Instance.directory, params.path || ".")
|
||||
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "list",
|
||||
message: `List directory: ${searchPath}`,
|
||||
patterns: [searchPath],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
path: searchPath,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
|
||||
const ignoreGlobs = IGNORE_PATTERNS.map((p) => `!${p}*`).concat(params.ignore?.map((p) => `!${p}`) || [])
|
||||
|
||||
@@ -6,11 +6,9 @@ import { FileTime } from "../file/time"
|
||||
import { Bus } from "../bus"
|
||||
import { FileWatcher } from "../file/watcher"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Agent } from "../agent/agent"
|
||||
import { Patch } from "../patch"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { createTwoFilesPatch } from "diff"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
const PatchParams = z.object({
|
||||
patchText: z.string().describe("The full patch text that describes all changes to be made"),
|
||||
@@ -39,7 +37,6 @@ export const PatchTool = Tool.define("patch", {
|
||||
}
|
||||
|
||||
// Validate file paths and check permissions
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
const fileChanges: Array<{
|
||||
filePath: string
|
||||
oldContent: string
|
||||
@@ -55,19 +52,14 @@ export const PatchTool = Tool.define("patch", {
|
||||
|
||||
if (!Filesystem.contains(Instance.directory, filePath)) {
|
||||
const parentDir = path.dirname(filePath)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "external_directory",
|
||||
message: `Patch file outside working directory: ${filePath}`,
|
||||
patterns: [parentDir, path.join(parentDir, "*")],
|
||||
always: [parentDir + "/*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
filepath: filePath,
|
||||
parentDir,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -141,18 +133,13 @@ export const PatchTool = Tool.define("patch", {
|
||||
}
|
||||
|
||||
// Check permissions if needed
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "edit",
|
||||
message: `Apply patch to ${fileChanges.length} files`,
|
||||
patterns: fileChanges.map((c) => path.relative(Instance.worktree, c.filePath)),
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
diff: totalDiff,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
|
||||
// Apply the changes
|
||||
|
||||
@@ -8,9 +8,7 @@ import DESCRIPTION from "./read.txt"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Identifier } from "../id/id"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { iife } from "@/util/iife"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
const DEFAULT_READ_LIMIT = 2000
|
||||
const MAX_LINE_LENGTH = 2000
|
||||
@@ -28,36 +26,25 @@ export const ReadTool = Tool.define("read", {
|
||||
filepath = path.join(process.cwd(), filepath)
|
||||
}
|
||||
const title = path.relative(Instance.worktree, filepath)
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
|
||||
if (!ctx.extra?.["bypassCwdCheck"] && !Filesystem.contains(Instance.directory, filepath)) {
|
||||
const parentDir = path.dirname(filepath)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "external_directory",
|
||||
message: `Access file outside working directory: ${filepath}`,
|
||||
patterns: [parentDir],
|
||||
always: [parentDir + "/*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
filepath,
|
||||
parentDir,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
}
|
||||
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "read",
|
||||
message: `Read file ${filepath}`,
|
||||
patterns: [filepath],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
|
||||
const block = iife(() => {
|
||||
|
||||
@@ -2,9 +2,7 @@ import path from "path"
|
||||
import z from "zod"
|
||||
import { Tool } from "./tool"
|
||||
import { Skill } from "../skill"
|
||||
import { Agent } from "../agent/agent"
|
||||
import { ConfigMarkdown } from "../config/markdown"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
export const SkillTool = Tool.define("skill", async () => {
|
||||
const skills = await Skill.all()
|
||||
@@ -46,8 +44,6 @@ export const SkillTool = Tool.define("skill", async () => {
|
||||
.describe("The skill identifier from available_skills (e.g., 'code-review' or 'category/helper')"),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
|
||||
const skill = await Skill.get(params.name)
|
||||
|
||||
if (!skill) {
|
||||
@@ -55,15 +51,11 @@ export const SkillTool = Tool.define("skill", async () => {
|
||||
throw new Error(`Skill "${params.name}" not found. Available skills: ${available || "none"}`)
|
||||
}
|
||||
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "skill",
|
||||
patterns: [params.name],
|
||||
always: [params.name],
|
||||
sessionID: ctx.sessionID,
|
||||
message: `Activate skill ${params.name}`,
|
||||
metadata: {},
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
// Load and parse skill content
|
||||
const parsed = await ConfigMarkdown.parse(skill.location)
|
||||
|
||||
@@ -10,7 +10,6 @@ import { SessionPrompt } from "../session/prompt"
|
||||
import { iife } from "@/util/iife"
|
||||
import { defer } from "@/util/defer"
|
||||
import { Config } from "../config/config"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
export const TaskTool = Tool.define("task", async () => {
|
||||
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
|
||||
@@ -30,19 +29,14 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
command: z.string().describe("The command that triggered this task").optional(),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const callingAgent = await Agent.get(ctx.agent)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "task",
|
||||
message: `Launch task: ${params.description}`,
|
||||
patterns: [params.subagent_type],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
description: params.description,
|
||||
subagent_type: params.subagent_type,
|
||||
},
|
||||
ruleset: callingAgent.permission,
|
||||
})
|
||||
|
||||
const agent = await Agent.get(params.subagent_type)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import z from "zod"
|
||||
import type { MessageV2 } from "../session/message-v2"
|
||||
import type { Agent } from "../agent/agent"
|
||||
import type { PermissionNext } from "../permission/next"
|
||||
|
||||
export namespace Tool {
|
||||
interface Metadata {
|
||||
@@ -19,6 +20,7 @@ export namespace Tool {
|
||||
callID?: string
|
||||
extra?: { [key: string]: any }
|
||||
metadata(input: { title?: string; metadata?: M }): void
|
||||
ask(input: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">): Promise<void>
|
||||
}
|
||||
export interface Info<Parameters extends z.ZodType = z.ZodType, M extends Metadata = Metadata> {
|
||||
id: string
|
||||
|
||||
@@ -2,8 +2,6 @@ import z from "zod"
|
||||
import { Tool } from "./tool"
|
||||
import TurndownService from "turndown"
|
||||
import DESCRIPTION from "./webfetch.txt"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
import { Agent } from "@/agent/agent"
|
||||
|
||||
const MAX_RESPONSE_SIZE = 5 * 1024 * 1024 // 5MB
|
||||
const DEFAULT_TIMEOUT = 30 * 1000 // 30 seconds
|
||||
@@ -25,21 +23,15 @@ export const WebFetchTool = Tool.define("webfetch", {
|
||||
throw new Error("URL must start with http:// or https://")
|
||||
}
|
||||
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "webfetch",
|
||||
message: "Fetch content from: " + params.url,
|
||||
patterns: [params.url],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
url: params.url,
|
||||
format: params.format,
|
||||
timeout: params.timeout,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
|
||||
const timeout = Math.min((params.timeout ?? DEFAULT_TIMEOUT / 1000) * 1000, MAX_TIMEOUT)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import z from "zod"
|
||||
import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./websearch.txt"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
import { Agent } from "@/agent/agent"
|
||||
|
||||
const API_CONFIG = {
|
||||
BASE_URL: "https://mcp.exa.ai",
|
||||
@@ -59,14 +57,10 @@ export const WebSearchTool = Tool.define("websearch", {
|
||||
.describe("Maximum characters for context string optimized for LLMs (default: 10000)"),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "websearch",
|
||||
message: "Search web for: " + params.query,
|
||||
patterns: [params.query],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {
|
||||
query: params.query,
|
||||
numResults: params.numResults,
|
||||
@@ -74,8 +68,6 @@ export const WebSearchTool = Tool.define("websearch", {
|
||||
type: params.type,
|
||||
contextMaxCharacters: params.contextMaxCharacters,
|
||||
},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
|
||||
const searchRequest: McpSearchRequest = {
|
||||
|
||||
@@ -8,8 +8,6 @@ import { File } from "../file"
|
||||
import { FileTime } from "../file/time"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Agent } from "../agent/agent"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
const MAX_DIAGNOSTICS_PER_FILE = 20
|
||||
const MAX_PROJECT_DIAGNOSTICS_FILES = 5
|
||||
@@ -21,37 +19,11 @@ export const WriteTool = Tool.define("write", {
|
||||
filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
|
||||
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
|
||||
/* TODO
|
||||
if (!Filesystem.contains(Instance.directory, filepath)) {
|
||||
const parentDir = path.dirname(filepath)
|
||||
if (agent.permission.external_directory === "ask") {
|
||||
await Permission.ask({
|
||||
type: "external_directory",
|
||||
pattern: [parentDir, path.join(parentDir, "*")],
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: `Write file outside working directory: ${filepath}`,
|
||||
metadata: {
|
||||
filepath,
|
||||
parentDir,
|
||||
},
|
||||
})
|
||||
} else if (agent.permission.external_directory === "deny") {
|
||||
throw new Permission.RejectedError(
|
||||
ctx.sessionID,
|
||||
"external_directory",
|
||||
ctx.callID,
|
||||
{
|
||||
filepath: filepath,
|
||||
parentDir,
|
||||
},
|
||||
`File ${filepath} is not in the current working directory`,
|
||||
)
|
||||
}
|
||||
...
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -59,16 +31,11 @@ export const WriteTool = Tool.define("write", {
|
||||
const exists = await file.exists()
|
||||
if (exists) await FileTime.assert(ctx.sessionID, filepath)
|
||||
|
||||
await PermissionNext.ask({
|
||||
callID: ctx.callID,
|
||||
await ctx.ask({
|
||||
permission: "edit",
|
||||
message: `Create new file ${path.relative(Instance.directory, filepath)}`,
|
||||
patterns: [path.relative(Instance.worktree, filepath)],
|
||||
always: ["*"],
|
||||
sessionID: ctx.sessionID,
|
||||
metadata: {},
|
||||
|
||||
ruleset: agent.permission,
|
||||
})
|
||||
|
||||
await Bun.write(filepath, params.content)
|
||||
|
||||
@@ -428,7 +428,6 @@ test("ask - resolves immediately when action is allow", async () => {
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["ls"],
|
||||
message: "Run ls command",
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [{ permission: "bash", pattern: "*", action: "allow" }],
|
||||
@@ -448,7 +447,6 @@ test("ask - throws RejectedError when action is deny", async () => {
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["rm -rf /"],
|
||||
message: "Run dangerous command",
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [{ permission: "bash", pattern: "*", action: "deny" }],
|
||||
@@ -467,7 +465,6 @@ test("ask - returns pending promise when action is ask", async () => {
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["ls"],
|
||||
message: "Run ls command",
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [{ permission: "bash", pattern: "*", action: "ask" }],
|
||||
@@ -491,7 +488,6 @@ test("reply - once resolves the pending ask", async () => {
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["ls"],
|
||||
message: "Run ls command",
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
@@ -517,7 +513,6 @@ test("reply - reject throws RejectedError", async () => {
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["ls"],
|
||||
message: "Run ls command",
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
@@ -543,7 +538,6 @@ test("reply - always persists approval and resolves", async () => {
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["ls"],
|
||||
message: "Run ls command",
|
||||
metadata: {},
|
||||
always: ["ls"],
|
||||
ruleset: [],
|
||||
@@ -566,7 +560,6 @@ test("reply - always persists approval and resolves", async () => {
|
||||
sessionID: "session_test2",
|
||||
permission: "bash",
|
||||
patterns: ["ls"],
|
||||
message: "Run ls command",
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
@@ -586,7 +579,6 @@ test("reply - reject cancels all pending for same session", async () => {
|
||||
sessionID: "session_same",
|
||||
permission: "bash",
|
||||
patterns: ["ls"],
|
||||
message: "Run ls",
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
@@ -597,7 +589,6 @@ test("reply - reject cancels all pending for same session", async () => {
|
||||
sessionID: "session_same",
|
||||
permission: "edit",
|
||||
patterns: ["foo.ts"],
|
||||
message: "Edit file",
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [],
|
||||
@@ -630,7 +621,6 @@ test("ask - checks all patterns and stops on first deny", async () => {
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["echo hello", "rm -rf /"],
|
||||
message: "Run commands",
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [
|
||||
@@ -652,7 +642,6 @@ test("ask - allows all patterns when all match allow rules", async () => {
|
||||
sessionID: "session_test",
|
||||
permission: "bash",
|
||||
patterns: ["echo hello", "ls -la", "pwd"],
|
||||
message: "Run safe commands",
|
||||
metadata: {},
|
||||
always: [],
|
||||
ruleset: [{ permission: "bash", pattern: "*", action: "allow" }],
|
||||
|
||||
@@ -12,6 +12,7 @@ const ctx = {
|
||||
agent: "build",
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
ask: async () => {},
|
||||
}
|
||||
|
||||
const projectRoot = path.join(__dirname, "../..")
|
||||
|
||||
@@ -11,6 +11,7 @@ const ctx = {
|
||||
agent: "build",
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
ask: async () => {},
|
||||
}
|
||||
|
||||
const projectRoot = path.join(__dirname, "../..")
|
||||
|
||||
@@ -9,10 +9,11 @@ import * as fs from "fs/promises"
|
||||
const ctx = {
|
||||
sessionID: "test",
|
||||
messageID: "",
|
||||
toolCallID: "",
|
||||
callID: "",
|
||||
agent: "build",
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
ask: async () => {},
|
||||
}
|
||||
|
||||
const patchTool = await PatchTool.init()
|
||||
|
||||
@@ -11,6 +11,7 @@ const ctx = {
|
||||
agent: "build",
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
ask: async () => {},
|
||||
}
|
||||
|
||||
describe("tool.read external_directory permission", () => {
|
||||
|
||||
@@ -453,15 +453,17 @@ export type EventMessagePartRemoved = {
|
||||
|
||||
export type PermissionRequest = {
|
||||
id: string
|
||||
callID?: string
|
||||
sessionID: string
|
||||
permission: string
|
||||
patterns: Array<string>
|
||||
message: string
|
||||
metadata: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
always: Array<string>
|
||||
tool?: {
|
||||
messageID: string
|
||||
callID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventPermissionNextAsked = {
|
||||
|
||||
Reference in New Issue
Block a user