feat(id): brand PermissionID, PtyID, QuestionID, and ToolID (#17042)

This commit is contained in:
Kit Langton
2026-03-11 21:49:57 -04:00
committed by GitHub
parent b0bca6342e
commit 2a4dedc210
19 changed files with 127 additions and 52 deletions

View File

@@ -3,13 +3,13 @@ import { Schema } from "effect"
import { withStatics } from "@/util/schema" import { withStatics } from "@/util/schema"
export const AccountID = Schema.String.pipe( export const AccountID = Schema.String.pipe(
Schema.brand("AccountId"), Schema.brand("AccountID"),
withStatics((s) => ({ make: (id: string) => s.makeUnsafe(id) })), withStatics((s) => ({ make: (id: string) => s.makeUnsafe(id) })),
) )
export type AccountID = Schema.Schema.Type<typeof AccountID> export type AccountID = Schema.Schema.Type<typeof AccountID>
export const OrgID = Schema.String.pipe( export const OrgID = Schema.String.pipe(
Schema.brand("OrgId"), Schema.brand("OrgID"),
withStatics((s) => ({ make: (id: string) => s.makeUnsafe(id) })), withStatics((s) => ({ make: (id: string) => s.makeUnsafe(id) })),
) )
export type OrgID = Schema.Schema.Type<typeof OrgID> export type OrgID = Schema.Schema.Type<typeof OrgID>

View File

@@ -4,7 +4,7 @@ import z from "zod"
import { withStatics } from "@/util/schema" import { withStatics } from "@/util/schema"
import { Identifier } from "@/id/id" import { Identifier } from "@/id/id"
const workspaceIdSchema = Schema.String.pipe(Schema.brand("WorkspaceId")) const workspaceIdSchema = Schema.String.pipe(Schema.brand("WorkspaceID"))
export type WorkspaceID = typeof workspaceIdSchema.Type export type WorkspaceID = typeof workspaceIdSchema.Type
@@ -12,6 +12,6 @@ export const WorkspaceID = workspaceIdSchema.pipe(
withStatics((schema: typeof workspaceIdSchema) => ({ withStatics((schema: typeof workspaceIdSchema) => ({
make: (id: string) => schema.makeUnsafe(id), make: (id: string) => schema.makeUnsafe(id),
ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("workspace", id)), ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("workspace", id)),
zod: z.string().startsWith("wrk").pipe(z.custom<WorkspaceID>()), zod: Identifier.schema("workspace").pipe(z.custom<WorkspaceID>()),
})), })),
) )

View File

@@ -3,10 +3,10 @@ import { Bus } from "@/bus"
import { SessionID, MessageID } from "@/session/schema" import { SessionID, MessageID } from "@/session/schema"
import z from "zod" import z from "zod"
import { Log } from "../util/log" import { Log } from "../util/log"
import { Identifier } from "../id/id"
import { Plugin } from "../plugin" import { Plugin } from "../plugin"
import { Instance } from "../project/instance" import { Instance } from "../project/instance"
import { Wildcard } from "../util/wildcard" import { Wildcard } from "../util/wildcard"
import { PermissionID } from "./schema"
export namespace Permission { export namespace Permission {
const log = Log.create({ service: "permission" }) const log = Log.create({ service: "permission" })
@@ -22,7 +22,7 @@ export namespace Permission {
export const Info = z export const Info = z
.object({ .object({
id: z.string(), id: PermissionID.zod,
type: z.string(), type: z.string(),
pattern: z.union([z.string(), z.array(z.string())]).optional(), pattern: z.union([z.string(), z.array(z.string())]).optional(),
sessionID: SessionID.zod, sessionID: SessionID.zod,
@@ -45,7 +45,7 @@ export namespace Permission {
"permission.replied", "permission.replied",
z.object({ z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
permissionID: z.string(), permissionID: PermissionID.zod,
response: z.string(), response: z.string(),
}), }),
), ),
@@ -118,7 +118,7 @@ export namespace Permission {
const keys = toKeys(input.pattern, input.type) const keys = toKeys(input.pattern, input.type)
if (covered(keys, approvedForSession)) return if (covered(keys, approvedForSession)) return
const info: Info = { const info: Info = {
id: Identifier.ascending("permission"), id: PermissionID.ascending(),
type: input.type, type: input.type,
pattern: input.pattern, pattern: input.pattern,
sessionID: input.sessionID, sessionID: input.sessionID,

View File

@@ -1,8 +1,8 @@
import { Bus } from "@/bus" import { Bus } from "@/bus"
import { BusEvent } from "@/bus/bus-event" import { BusEvent } from "@/bus/bus-event"
import { Config } from "@/config/config" import { Config } from "@/config/config"
import { Identifier } from "@/id/id"
import { SessionID, MessageID } from "@/session/schema" import { SessionID, MessageID } from "@/session/schema"
import { PermissionID } from "./schema"
import { Instance } from "@/project/instance" import { Instance } from "@/project/instance"
import { Database, eq } from "@/storage/db" import { Database, eq } from "@/storage/db"
import { PermissionTable } from "@/session/session.sql" import { PermissionTable } from "@/session/session.sql"
@@ -69,7 +69,7 @@ export namespace PermissionNext {
export const Request = z export const Request = z
.object({ .object({
id: Identifier.schema("permission"), id: PermissionID.zod,
sessionID: SessionID.zod, sessionID: SessionID.zod,
permission: z.string(), permission: z.string(),
patterns: z.string().array(), patterns: z.string().array(),
@@ -102,7 +102,7 @@ export namespace PermissionNext {
"permission.replied", "permission.replied",
z.object({ z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
requestID: z.string(), requestID: PermissionID.zod,
reply: Reply, reply: Reply,
}), }),
), ),
@@ -143,7 +143,7 @@ export namespace PermissionNext {
if (rule.action === "deny") if (rule.action === "deny")
throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission))) throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission)))
if (rule.action === "ask") { if (rule.action === "ask") {
const id = input.id ?? Identifier.ascending("permission") const id = input.id ?? PermissionID.ascending()
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const info: Request = { const info: Request = {
id, id,
@@ -164,7 +164,7 @@ export namespace PermissionNext {
export const reply = fn( export const reply = fn(
z.object({ z.object({
requestID: Identifier.schema("permission"), requestID: PermissionID.zod,
reply: Reply, reply: Reply,
message: z.string().optional(), message: z.string().optional(),
}), }),

View File

@@ -0,0 +1,17 @@
import { Schema } from "effect"
import z from "zod"
import { Identifier } from "@/id/id"
import { withStatics } from "@/util/schema"
const permissionIdSchema = Schema.String.pipe(Schema.brand("PermissionID"))
export type PermissionID = typeof permissionIdSchema.Type
export const PermissionID = permissionIdSchema.pipe(
withStatics((schema: typeof permissionIdSchema) => ({
make: (id: string) => schema.makeUnsafe(id),
ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("permission", id)),
zod: Identifier.schema("permission").pipe(z.custom<PermissionID>()),
})),
)

View File

@@ -3,7 +3,7 @@ import z from "zod"
import { withStatics } from "@/util/schema" import { withStatics } from "@/util/schema"
const projectIdSchema = Schema.String.pipe(Schema.brand("ProjectId")) const projectIdSchema = Schema.String.pipe(Schema.brand("ProjectID"))
export type ProjectID = typeof projectIdSchema.Type export type ProjectID = typeof projectIdSchema.Type

View File

@@ -2,12 +2,12 @@ import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus" import { Bus } from "@/bus"
import { type IPty } from "bun-pty" import { type IPty } from "bun-pty"
import z from "zod" import z from "zod"
import { Identifier } from "../id/id"
import { Log } from "../util/log" import { Log } from "../util/log"
import { Instance } from "../project/instance" import { Instance } from "../project/instance"
import { lazy } from "@opencode-ai/util/lazy" import { lazy } from "@opencode-ai/util/lazy"
import { Shell } from "@/shell/shell" import { Shell } from "@/shell/shell"
import { Plugin } from "@/plugin" import { Plugin } from "@/plugin"
import { PtyID } from "./schema"
export namespace Pty { export namespace Pty {
const log = Log.create({ service: "pty" }) const log = Log.create({ service: "pty" })
@@ -40,7 +40,7 @@ export namespace Pty {
export const Info = z export const Info = z
.object({ .object({
id: Identifier.schema("pty"), id: PtyID.zod,
title: z.string(), title: z.string(),
command: z.string(), command: z.string(),
args: z.array(z.string()), args: z.array(z.string()),
@@ -77,8 +77,8 @@ export namespace Pty {
export const Event = { export const Event = {
Created: BusEvent.define("pty.created", z.object({ info: Info })), Created: BusEvent.define("pty.created", z.object({ info: Info })),
Updated: BusEvent.define("pty.updated", z.object({ info: Info })), Updated: BusEvent.define("pty.updated", z.object({ info: Info })),
Exited: BusEvent.define("pty.exited", z.object({ id: Identifier.schema("pty"), exitCode: z.number() })), Exited: BusEvent.define("pty.exited", z.object({ id: PtyID.zod, exitCode: z.number() })),
Deleted: BusEvent.define("pty.deleted", z.object({ id: Identifier.schema("pty") })), Deleted: BusEvent.define("pty.deleted", z.object({ id: PtyID.zod })),
} }
interface ActiveSession { interface ActiveSession {
@@ -118,7 +118,7 @@ export namespace Pty {
} }
export async function create(input: CreateInput) { export async function create(input: CreateInput) {
const id = Identifier.create("pty", false) const id = PtyID.ascending()
const command = input.command || Shell.preferred() const command = input.command || Shell.preferred()
const args = input.args || [] const args = input.args || []
if (command.endsWith("sh")) { if (command.endsWith("sh")) {
@@ -234,7 +234,7 @@ export namespace Pty {
} }
} }
session.subscribers.clear() session.subscribers.clear()
Bus.publish(Event.Deleted, { id }) Bus.publish(Event.Deleted, { id: session.info.id })
} }
export function resize(id: string, cols: number, rows: number) { export function resize(id: string, cols: number, rows: number) {

View File

@@ -0,0 +1,17 @@
import { Schema } from "effect"
import z from "zod"
import { Identifier } from "@/id/id"
import { withStatics } from "@/util/schema"
const ptyIdSchema = Schema.String.pipe(Schema.brand("PtyID"))
export type PtyID = typeof ptyIdSchema.Type
export const PtyID = ptyIdSchema.pipe(
withStatics((schema: typeof ptyIdSchema) => ({
make: (id: string) => schema.makeUnsafe(id),
ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("pty", id)),
zod: Identifier.schema("pty").pipe(z.custom<PtyID>()),
})),
)

View File

@@ -1,10 +1,10 @@
import { Bus } from "@/bus" import { Bus } from "@/bus"
import { BusEvent } from "@/bus/bus-event" import { BusEvent } from "@/bus/bus-event"
import { Identifier } from "@/id/id"
import { SessionID, MessageID } from "@/session/schema" import { SessionID, MessageID } from "@/session/schema"
import { Instance } from "@/project/instance" import { Instance } from "@/project/instance"
import { Log } from "@/util/log" import { Log } from "@/util/log"
import z from "zod" import z from "zod"
import { QuestionID } from "./schema"
export namespace Question { export namespace Question {
const log = Log.create({ service: "question" }) const log = Log.create({ service: "question" })
@@ -34,7 +34,7 @@ export namespace Question {
export const Request = z export const Request = z
.object({ .object({
id: Identifier.schema("question"), id: QuestionID.zod,
sessionID: SessionID.zod, sessionID: SessionID.zod,
questions: z.array(Info).describe("Questions to ask"), questions: z.array(Info).describe("Questions to ask"),
tool: z tool: z
@@ -67,7 +67,7 @@ export namespace Question {
"question.replied", "question.replied",
z.object({ z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
requestID: z.string(), requestID: QuestionID.zod,
answers: z.array(Answer), answers: z.array(Answer),
}), }),
), ),
@@ -75,7 +75,7 @@ export namespace Question {
"question.rejected", "question.rejected",
z.object({ z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
requestID: z.string(), requestID: QuestionID.zod,
}), }),
), ),
} }
@@ -101,7 +101,7 @@ export namespace Question {
tool?: { messageID: MessageID; callID: string } tool?: { messageID: MessageID; callID: string }
}): Promise<Answer[]> { }): Promise<Answer[]> {
const s = await state() const s = await state()
const id = Identifier.ascending("question") const id = QuestionID.ascending()
log.info("asking", { id, questions: input.questions.length }) log.info("asking", { id, questions: input.questions.length })

View File

@@ -0,0 +1,17 @@
import { Schema } from "effect"
import z from "zod"
import { Identifier } from "@/id/id"
import { withStatics } from "@/util/schema"
const questionIdSchema = Schema.String.pipe(Schema.brand("QuestionID"))
export type QuestionID = typeof questionIdSchema.Type
export const QuestionID = questionIdSchema.pipe(
withStatics((schema: typeof questionIdSchema) => ({
make: (id: string) => schema.makeUnsafe(id),
ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("question", id)),
zod: Identifier.schema("question").pipe(z.custom<QuestionID>()),
})),
)

View File

@@ -2,6 +2,7 @@ import { Hono } from "hono"
import { describeRoute, validator, resolver } from "hono-openapi" import { describeRoute, validator, resolver } from "hono-openapi"
import z from "zod" import z from "zod"
import { PermissionNext } from "@/permission/next" import { PermissionNext } from "@/permission/next"
import { PermissionID } from "@/permission/schema"
import { errors } from "../error" import { errors } from "../error"
import { lazy } from "../../util/lazy" import { lazy } from "../../util/lazy"
@@ -28,7 +29,7 @@ export const PermissionRoutes = lazy(() =>
validator( validator(
"param", "param",
z.object({ z.object({
requestID: z.string(), requestID: PermissionID.zod,
}), }),
), ),
validator("json", z.object({ reply: PermissionNext.Reply, message: z.string().optional() })), validator("json", z.object({ reply: PermissionNext.Reply, message: z.string().optional() })),

View File

@@ -3,6 +3,7 @@ import { describeRoute, validator, resolver } from "hono-openapi"
import { upgradeWebSocket } from "hono/bun" import { upgradeWebSocket } from "hono/bun"
import z from "zod" import z from "zod"
import { Pty } from "@/pty" import { Pty } from "@/pty"
import { PtyID } from "@/pty/schema"
import { NotFoundError } from "../../storage/db" import { NotFoundError } from "../../storage/db"
import { errors } from "../error" import { errors } from "../error"
import { lazy } from "../../util/lazy" import { lazy } from "../../util/lazy"
@@ -72,7 +73,7 @@ export const PtyRoutes = lazy(() =>
...errors(404), ...errors(404),
}, },
}), }),
validator("param", z.object({ ptyID: z.string() })), validator("param", z.object({ ptyID: PtyID.zod })),
async (c) => { async (c) => {
const info = Pty.get(c.req.valid("param").ptyID) const info = Pty.get(c.req.valid("param").ptyID)
if (!info) { if (!info) {
@@ -99,7 +100,7 @@ export const PtyRoutes = lazy(() =>
...errors(400), ...errors(400),
}, },
}), }),
validator("param", z.object({ ptyID: z.string() })), validator("param", z.object({ ptyID: PtyID.zod })),
validator("json", Pty.UpdateInput), validator("json", Pty.UpdateInput),
async (c) => { async (c) => {
const info = await Pty.update(c.req.valid("param").ptyID, c.req.valid("json")) const info = await Pty.update(c.req.valid("param").ptyID, c.req.valid("json"))
@@ -124,7 +125,7 @@ export const PtyRoutes = lazy(() =>
...errors(404), ...errors(404),
}, },
}), }),
validator("param", z.object({ ptyID: z.string() })), validator("param", z.object({ ptyID: PtyID.zod })),
async (c) => { async (c) => {
await Pty.remove(c.req.valid("param").ptyID) await Pty.remove(c.req.valid("param").ptyID)
return c.json(true) return c.json(true)
@@ -148,9 +149,9 @@ export const PtyRoutes = lazy(() =>
...errors(404), ...errors(404),
}, },
}), }),
validator("param", z.object({ ptyID: z.string() })), validator("param", z.object({ ptyID: PtyID.zod })),
upgradeWebSocket((c) => { upgradeWebSocket((c) => {
const id = c.req.param("ptyID") const id = PtyID.zod.parse(c.req.param("ptyID"))
const cursor = (() => { const cursor = (() => {
const value = c.req.query("cursor") const value = c.req.query("cursor")
if (!value) return if (!value) return

View File

@@ -1,6 +1,7 @@
import { Hono } from "hono" import { Hono } from "hono"
import { describeRoute, validator } from "hono-openapi" import { describeRoute, validator } from "hono-openapi"
import { resolver } from "hono-openapi" import { resolver } from "hono-openapi"
import { QuestionID } from "@/question/schema"
import { Question } from "../../question" import { Question } from "../../question"
import z from "zod" import z from "zod"
import { errors } from "../error" import { errors } from "../error"
@@ -51,7 +52,7 @@ export const QuestionRoutes = lazy(() =>
validator( validator(
"param", "param",
z.object({ z.object({
requestID: z.string(), requestID: QuestionID.zod,
}), }),
), ),
validator("json", Question.Reply), validator("json", Question.Reply),
@@ -86,7 +87,7 @@ export const QuestionRoutes = lazy(() =>
validator( validator(
"param", "param",
z.object({ z.object({
requestID: z.string(), requestID: QuestionID.zod,
}), }),
), ),
async (c) => { async (c) => {

View File

@@ -15,6 +15,7 @@ import { Agent } from "../../agent/agent"
import { Snapshot } from "@/snapshot" import { Snapshot } from "@/snapshot"
import { Log } from "../../util/log" import { Log } from "../../util/log"
import { PermissionNext } from "@/permission/next" import { PermissionNext } from "@/permission/next"
import { PermissionID } from "@/permission/schema"
import { errors } from "../error" import { errors } from "../error"
import { lazy } from "../../util/lazy" import { lazy } from "../../util/lazy"
@@ -957,7 +958,7 @@ export const SessionRoutes = lazy(() =>
"param", "param",
z.object({ z.object({
sessionID: SessionID.zod, sessionID: SessionID.zod,
permissionID: z.string(), permissionID: PermissionID.zod,
}), }),
), ),
validator("json", z.object({ response: PermissionNext.Reply })), validator("json", z.object({ response: PermissionNext.Reply })),

View File

@@ -4,7 +4,7 @@ import z from "zod"
import { withStatics } from "@/util/schema" import { withStatics } from "@/util/schema"
import { Identifier } from "@/id/id" import { Identifier } from "@/id/id"
const sessionIdSchema = Schema.String.pipe(Schema.brand("SessionId")) const sessionIdSchema = Schema.String.pipe(Schema.brand("SessionID"))
export type SessionID = typeof sessionIdSchema.Type export type SessionID = typeof sessionIdSchema.Type
@@ -12,11 +12,11 @@ export const SessionID = sessionIdSchema.pipe(
withStatics((schema: typeof sessionIdSchema) => ({ withStatics((schema: typeof sessionIdSchema) => ({
make: (id: string) => schema.makeUnsafe(id), make: (id: string) => schema.makeUnsafe(id),
descending: (id?: string) => schema.makeUnsafe(Identifier.descending("session", id)), descending: (id?: string) => schema.makeUnsafe(Identifier.descending("session", id)),
zod: z.string().startsWith("ses").pipe(z.custom<SessionID>()), zod: Identifier.schema("session").pipe(z.custom<SessionID>()),
})), })),
) )
const messageIdSchema = Schema.String.pipe(Schema.brand("MessageId")) const messageIdSchema = Schema.String.pipe(Schema.brand("MessageID"))
export type MessageID = typeof messageIdSchema.Type export type MessageID = typeof messageIdSchema.Type
@@ -24,11 +24,11 @@ export const MessageID = messageIdSchema.pipe(
withStatics((schema: typeof messageIdSchema) => ({ withStatics((schema: typeof messageIdSchema) => ({
make: (id: string) => schema.makeUnsafe(id), make: (id: string) => schema.makeUnsafe(id),
ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("message", id)), ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("message", id)),
zod: z.string().startsWith("msg").pipe(z.custom<MessageID>()), zod: Identifier.schema("message").pipe(z.custom<MessageID>()),
})), })),
) )
const partIdSchema = Schema.String.pipe(Schema.brand("PartId")) const partIdSchema = Schema.String.pipe(Schema.brand("PartID"))
export type PartID = typeof partIdSchema.Type export type PartID = typeof partIdSchema.Type
@@ -36,6 +36,6 @@ export const PartID = partIdSchema.pipe(
withStatics((schema: typeof partIdSchema) => ({ withStatics((schema: typeof partIdSchema) => ({
make: (id: string) => schema.makeUnsafe(id), make: (id: string) => schema.makeUnsafe(id),
ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("part", id)), ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("part", id)),
zod: z.string().startsWith("prt").pipe(z.custom<PartID>()), zod: Identifier.schema("part").pipe(z.custom<PartID>()),
})), })),
) )

View File

@@ -0,0 +1,17 @@
import { Schema } from "effect"
import z from "zod"
import { Identifier } from "@/id/id"
import { withStatics } from "@/util/schema"
const toolIdSchema = Schema.String.pipe(Schema.brand("ToolID"))
export type ToolID = typeof toolIdSchema.Type
export const ToolID = toolIdSchema.pipe(
withStatics((schema: typeof toolIdSchema) => ({
make: (id: string) => schema.makeUnsafe(id),
ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("tool", id)),
zod: Identifier.schema("tool").pipe(z.custom<ToolID>()),
})),
)

View File

@@ -7,6 +7,7 @@ import type { Agent } from "../agent/agent"
import { Scheduler } from "../scheduler" import { Scheduler } from "../scheduler"
import { Filesystem } from "../util/filesystem" import { Filesystem } from "../util/filesystem"
import { Glob } from "../util/glob" import { Glob } from "../util/glob"
import { ToolID } from "./schema"
export namespace Truncate { export namespace Truncate {
export const MAX_LINES = 2000 export const MAX_LINES = 2000
@@ -90,7 +91,7 @@ export namespace Truncate {
const unit = hitBytes ? "bytes" : "lines" const unit = hitBytes ? "bytes" : "lines"
const preview = out.join("\n") const preview = out.join("\n")
const id = Identifier.ascending("tool") const id = ToolID.ascending()
const filepath = path.join(DIR, id) const filepath = path.join(DIR, id)
await Filesystem.write(filepath, text) await Filesystem.write(filepath, text)

View File

@@ -1,6 +1,7 @@
import { test, expect } from "bun:test" import { test, expect } from "bun:test"
import os from "os" import os from "os"
import { PermissionNext } from "../../src/permission/next" import { PermissionNext } from "../../src/permission/next"
import { PermissionID } from "../../src/permission/schema"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
import { SessionID } from "../../src/session/schema" import { SessionID } from "../../src/session/schema"
@@ -522,7 +523,7 @@ test("reply - once resolves the pending ask", async () => {
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
const askPromise = PermissionNext.ask({ const askPromise = PermissionNext.ask({
id: "permission_test1", id: PermissionID.make("per_test1"),
sessionID: SessionID.make("session_test"), sessionID: SessionID.make("session_test"),
permission: "bash", permission: "bash",
patterns: ["ls"], patterns: ["ls"],
@@ -532,7 +533,7 @@ test("reply - once resolves the pending ask", async () => {
}) })
await PermissionNext.reply({ await PermissionNext.reply({
requestID: "permission_test1", requestID: PermissionID.make("per_test1"),
reply: "once", reply: "once",
}) })
@@ -547,7 +548,7 @@ test("reply - reject throws RejectedError", async () => {
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
const askPromise = PermissionNext.ask({ const askPromise = PermissionNext.ask({
id: "permission_test2", id: PermissionID.make("per_test2"),
sessionID: SessionID.make("session_test"), sessionID: SessionID.make("session_test"),
permission: "bash", permission: "bash",
patterns: ["ls"], patterns: ["ls"],
@@ -557,7 +558,7 @@ test("reply - reject throws RejectedError", async () => {
}) })
await PermissionNext.reply({ await PermissionNext.reply({
requestID: "permission_test2", requestID: PermissionID.make("per_test2"),
reply: "reject", reply: "reject",
}) })
@@ -572,7 +573,7 @@ test("reply - always persists approval and resolves", async () => {
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
const askPromise = PermissionNext.ask({ const askPromise = PermissionNext.ask({
id: "permission_test3", id: PermissionID.make("per_test3"),
sessionID: SessionID.make("session_test"), sessionID: SessionID.make("session_test"),
permission: "bash", permission: "bash",
patterns: ["ls"], patterns: ["ls"],
@@ -582,7 +583,7 @@ test("reply - always persists approval and resolves", async () => {
}) })
await PermissionNext.reply({ await PermissionNext.reply({
requestID: "permission_test3", requestID: PermissionID.make("per_test3"),
reply: "always", reply: "always",
}) })
@@ -613,7 +614,7 @@ test("reply - reject cancels all pending for same session", async () => {
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
const askPromise1 = PermissionNext.ask({ const askPromise1 = PermissionNext.ask({
id: "permission_test4a", id: PermissionID.make("per_test4a"),
sessionID: SessionID.make("session_same"), sessionID: SessionID.make("session_same"),
permission: "bash", permission: "bash",
patterns: ["ls"], patterns: ["ls"],
@@ -623,7 +624,7 @@ test("reply - reject cancels all pending for same session", async () => {
}) })
const askPromise2 = PermissionNext.ask({ const askPromise2 = PermissionNext.ask({
id: "permission_test4b", id: PermissionID.make("per_test4b"),
sessionID: SessionID.make("session_same"), sessionID: SessionID.make("session_same"),
permission: "edit", permission: "edit",
patterns: ["foo.ts"], patterns: ["foo.ts"],
@@ -638,7 +639,7 @@ test("reply - reject cancels all pending for same session", async () => {
// Reject the first one // Reject the first one
await PermissionNext.reply({ await PermissionNext.reply({
requestID: "permission_test4a", requestID: PermissionID.make("per_test4a"),
reply: "reject", reply: "reject",
}) })

View File

@@ -1,6 +1,7 @@
import { test, expect } from "bun:test" import { test, expect } from "bun:test"
import { Question } from "../../src/question" import { Question } from "../../src/question"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { QuestionID } from "../../src/question/schema"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
import { SessionID } from "../../src/session/schema" import { SessionID } from "../../src/session/schema"
@@ -131,7 +132,7 @@ test("reply - does nothing for unknown requestID", async () => {
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
await Question.reply({ await Question.reply({
requestID: "que_unknown", requestID: QuestionID.make("que_unknown"),
answers: [["Option 1"]], answers: [["Option 1"]],
}) })
// Should not throw // Should not throw
@@ -204,7 +205,7 @@ test("reject - does nothing for unknown requestID", async () => {
await Instance.provide({ await Instance.provide({
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
await Question.reject("que_unknown") await Question.reject(QuestionID.make("que_unknown"))
// Should not throw // Should not throw
}, },
}) })