| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- import { describe, expect, test } from "bun:test"
- import {
- createPermissionBodyState,
- permissionAlwaysLines,
- permissionCancel,
- permissionEscape,
- permissionInfo,
- permissionReject,
- permissionRun,
- } from "../../src/mini/permission.shared"
- import type { MiniPermissionRequest } from "../../src/mini/types"
- import { canonicalToolPart } from "./fixture/tool-part"
- function req(input: Partial<MiniPermissionRequest> = {}): MiniPermissionRequest {
- return {
- id: "perm-1",
- sessionID: "session-1",
- action: "read",
- resources: [],
- metadata: {},
- save: [],
- ...input,
- }
- }
- function body() {
- return createPermissionBodyState(req())
- }
- describe("run permission shared", () => {
- test("replies immediately for allow once", () => {
- const out = permissionRun(body(), "perm-1", "once")
- expect(out.reply).toEqual({
- sessionID: "session-1",
- requestID: "perm-1",
- reply: "once",
- })
- })
- test("requires confirmation for allow always", () => {
- const next = permissionRun(body(), "perm-1", "always")
- expect(next.state.stage).toBe("always")
- expect(next.state.selected).toBe("confirm")
- expect(next.reply).toBeUndefined()
- expect(permissionRun(next.state, "perm-1", "confirm").reply).toEqual({
- sessionID: "session-1",
- requestID: "perm-1",
- reply: "always",
- })
- expect(permissionRun(next.state, "perm-1", "cancel").state).toMatchObject({
- stage: "permission",
- selected: "always",
- })
- })
- test("builds trimmed reject replies and stage transitions", () => {
- const next = permissionRun(body(), "perm-1", "reject")
- expect(next.state.stage).toBe("reject")
- const out = permissionReject({ ...next.state, message: " use rg " }, "perm-1")
- expect(out).toEqual({
- sessionID: "session-1",
- requestID: "perm-1",
- reply: "reject",
- message: "use rg",
- })
- expect(permissionCancel(next.state)).toMatchObject({
- stage: "permission",
- selected: "reject",
- })
- expect(permissionEscape(body())).toMatchObject({
- stage: "reject",
- selected: "reject",
- })
- expect(permissionEscape({ ...next.state, stage: "always", selected: "confirm" })).toMatchObject({
- stage: "permission",
- selected: "always",
- })
- })
- test("maps supported permission types into display info", () => {
- expect(
- permissionInfo(
- req({
- action: "shell",
- source: { type: "tool", messageID: "msg-shell", id: "call-shell" },
- tool: canonicalToolPart(
- "shell",
- {
- status: "running",
- input: { command: "git status --short" },
- metadata: {},
- },
- "call-shell",
- ),
- }),
- ),
- ).toMatchObject({
- title: "Shell command",
- lines: ["$ git status --short"],
- })
- expect(
- permissionInfo(
- req({
- action: "external_directory",
- resources: ["/tmp/work/**/*.ts", "/tmp/work/**/*.tsx"],
- }),
- ),
- ).toMatchObject({
- title: "Access external directory /tmp/work",
- lines: ["- /tmp/work/**/*.ts", "- /tmp/work/**/*.tsx"],
- })
- expect(permissionInfo(req({ action: "doom_loop" }))).toMatchObject({
- title: "Continue after repeated failures",
- })
- expect(permissionInfo(req({ action: "custom_tool" }))).toMatchObject({
- title: "Call tool custom_tool",
- lines: ["Tool: custom_tool"],
- })
- })
- test("prefers canonical request metadata over source tool metadata", () => {
- expect(
- permissionInfo(
- req({
- action: "websearch",
- metadata: { provider: "parallel" },
- source: { type: "tool", messageID: "msg-search", id: "call-search" },
- tool: canonicalToolPart(
- "websearch",
- {
- status: "running",
- input: { query: "current releases" },
- metadata: { provider: "exa", retained: true },
- },
- "call-search",
- ),
- }),
- ),
- ).toMatchObject({
- title: 'Parallel Web Search "current releases"',
- lines: ["Query: current releases"],
- })
- })
- test("uses source patch text when an edit has no generated diff", () => {
- const patch = '*** Begin Patch\n*** Update File: src/index.ts\n@@\n-old\n+const arrow = "→"\n*** End Patch'
- const request = req({
- action: "edit",
- resources: ["src/index.ts"],
- source: { type: "tool", messageID: "msg-edit", id: "call-edit" },
- tool: canonicalToolPart(
- "edit",
- {
- status: "running",
- input: { patchText: patch },
- metadata: {},
- },
- "call-edit",
- ),
- })
- expect(permissionInfo(request)).toMatchObject({
- title: "Edit src/index.ts",
- diff: undefined,
- patch,
- })
- expect(permissionInfo(request, undefined, true)).toMatchObject({
- title: "Edit src/index.ts",
- lines: [patch],
- diff: undefined,
- patch: undefined,
- })
- })
- test("formats always-allow copy for wildcard and explicit patterns", () => {
- expect(permissionAlwaysLines(req({ action: "bash", save: ["*"] }))).toEqual([
- "This will allow bash until OpenCode is restarted.",
- ])
- expect(permissionAlwaysLines(req({ save: ["src/**/*.ts", "src/**/*.tsx"] }))).toEqual([
- "This will allow the following patterns until OpenCode is restarted.",
- "- src/**/*.ts",
- "- src/**/*.tsx",
- ])
- })
- })
|