From c5d1a51b1f1052b8e077eb80eb12b525127c116f Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 31 Dec 2025 13:35:14 -0500 Subject: [PATCH] sync --- packages/opencode/src/agent/agent.ts | 9 ++++-- .../src/cli/cmd/tui/context/local.tsx | 2 +- .../cli/cmd/tui/routes/session/permission.tsx | 4 +-- packages/opencode/src/permission/next.ts | 4 ++- packages/opencode/src/plugin/index.ts | 1 + packages/opencode/src/session/index.ts | 13 ++++++++- packages/opencode/src/session/prompt.ts | 28 +++++++++++++++++-- packages/opencode/src/tool/task.ts | 23 ++++++++++++++- packages/sdk/js/src/v2/gen/sdk.gen.ts | 3 ++ packages/sdk/js/src/v2/gen/types.gen.ts | 28 ++++++++++++------- 10 files changed, 94 insertions(+), 21 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 065bc2398f..db49b0f4fc 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -11,7 +11,7 @@ import PROMPT_EXPLORE from "./prompt/explore.txt" import PROMPT_SUMMARY from "./prompt/summary.txt" import PROMPT_TITLE from "./prompt/title.txt" import { PermissionNext } from "@/permission/next" -import { mergeDeep } from "remeda" +import { mergeDeep, pipe, sortBy, values } from "remeda" export namespace Agent { export const Info = z @@ -194,7 +194,12 @@ export namespace Agent { } export async function list() { - return state().then((x) => Object.values(x)) + const cfg = await Config.get() + return pipe( + await state(), + values(), + sortBy([(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "build"), "desc"]), + ) } export async function defaultAgent() { diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index ec7d86eaac..b60a775b37 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -38,7 +38,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const [agentStore, setAgentStore] = createStore<{ current: string }>({ - current: agents().find((x) => x.default)?.name ?? agents()[0].name, + current: agents()[0].name, }) const { theme } = useTheme() const colors = createMemo(() => [ diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index a9a2246278..37f169b193 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -125,11 +125,11 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { body={ - + - Applies to the following patterns + This will allow the following patterns until OpenCode is restarted {(pattern) => ( diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 54e3318dca..b434b79643 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -189,7 +189,9 @@ export namespace PermissionNext { action: "allow", }) } - await Storage.write(["permission", projectID], s.approved) + // TODO: we don't save the permission ruleset to disk yet until there's + // UI to manage it + // await Storage.write(["permission", projectID], s.approved) existing.resolve() return } diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 79f6094944..18a621fbbd 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -78,6 +78,7 @@ export namespace Plugin { const hooks = await state().then((x) => x.hooks) const config = await Config.get() for (const hook of hooks) { + // @ts-expect-error this is because we haven't moved plugin to sdk v2 await hook.config?.(config) } Bus.subscribeAll(async (input) => { diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 4285223bc5..0776590d6a 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -18,6 +18,7 @@ import { Command } from "../command" import { Snapshot } from "@/snapshot" import type { Provider } from "@/provider/provider" +import { PermissionNext } from "@/permission/next" export namespace Session { const log = Log.create({ service: "session" }) @@ -62,6 +63,7 @@ export namespace Session { compacting: z.number().optional(), archived: z.number().optional(), }), + permission: PermissionNext.Ruleset.optional(), revert: z .object({ messageID: z.string(), @@ -126,6 +128,7 @@ export namespace Session { .object({ parentID: Identifier.schema("session").optional(), title: z.string().optional(), + permission: Info.shape.permission, }) .optional(), async (input) => { @@ -133,6 +136,7 @@ export namespace Session { parentID: input?.parentID, directory: Instance.directory, title: input?.title, + permission: input?.permission, }) }, ) @@ -174,7 +178,13 @@ export namespace Session { }) }) - export async function createNext(input: { id?: string; title?: string; parentID?: string; directory: string }) { + export async function createNext(input: { + id?: string + title?: string + parentID?: string + directory: string + permission?: PermissionNext.Ruleset + }) { const result: Info = { id: Identifier.descending("session", input.id), version: Installation.VERSION, @@ -182,6 +192,7 @@ export namespace Session { directory: input.directory, parentID: input.parentID, title: input.title ?? createDefaultTitle(!!input.parentID), + permission: input.permission, time: { created: Date.now(), updated: Date.now(), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index b51c567fb7..1947f2e998 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -89,7 +89,12 @@ export namespace SessionPrompt { .optional(), agent: z.string().optional(), noReply: z.boolean().optional(), - tools: z.record(z.string(), z.boolean()).optional(), + tools: z + .record(z.string(), z.boolean()) + .optional() + .describe( + "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", + ), system: z.string().optional(), variant: z.string().optional(), parts: z.array( @@ -146,6 +151,23 @@ export namespace SessionPrompt { const message = await createUserMessage(input) await Session.touch(input.sessionID) + // this is backwards compatibility for allowing `tools` to be specified when + // prompting + const permissions: PermissionNext.Ruleset = [] + for (const [tool, enabled] of Object.entries(input.tools ?? {})) { + permissions.push({ + permission: tool, + action: enabled ? "allow" : "deny", + pattern: "*", + }) + } + if (permissions.length > 0) { + session.permission = permissions + await Session.update(session.id, (draft) => { + draft.permission = permissions + }) + } + if (input.noReply === true) { return message } @@ -372,7 +394,7 @@ export namespace SessionPrompt { await PermissionNext.ask({ ...req, sessionID: sessionID, - ruleset: taskAgent.permission, + ruleset: PermissionNext.merge(taskAgent.permission, session.permission ?? []), }) }, } @@ -626,7 +648,7 @@ export namespace SessionPrompt { ...req, sessionID: input.session.parentID ?? input.session.id, tool: { messageID: input.processor.message.id, callID: options.toolCallId }, - ruleset: input.agent.permission, + ruleset: PermissionNext.merge(input.agent.permission, input.session.permission ?? []), }) }, }) diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 3bf800f112..9caf8a5ae5 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -50,6 +50,28 @@ export const TaskTool = Tool.define("task", async () => { return await Session.create({ parentID: ctx.sessionID, title: params.description + ` (@${agent.name} subagent)`, + permission: [ + { + permission: "todowrite", + pattern: "*", + action: "deny", + }, + { + permission: "todoread", + pattern: "*", + action: "deny", + }, + { + permission: "task", + pattern: "*", + action: "deny", + }, + ...(config.experimental?.primary_tools?.map((t) => ({ + pattern: "*", + action: "allow" as const, + permission: t, + })) ?? []), + ], }) }) const msg = await MessageV2.get({ sessionID: ctx.sessionID, messageID: ctx.messageID }) @@ -112,7 +134,6 @@ export const TaskTool = Tool.define("task", async () => { todoread: false, task: false, ...Object.fromEntries((config.experimental?.primary_tools ?? []).map((t) => [t, false])), - ...agent.tools, }, parts: promptParts, }) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 7f6015982b..9ab91b7e9d 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -59,6 +59,7 @@ import type { PermissionReplyResponses, PermissionRespondErrors, PermissionRespondResponses, + PermissionRuleset, ProjectCurrentResponses, ProjectListResponses, ProjectUpdateErrors, @@ -730,6 +731,7 @@ export class Session extends HeyApiClient { directory?: string parentID?: string title?: string + permission?: PermissionRuleset }, options?: Options, ) { @@ -741,6 +743,7 @@ export class Session extends HeyApiClient { { in: "query", key: "directory" }, { in: "body", key: "parentID" }, { in: "body", key: "title" }, + { in: "body", key: "permission" }, ], }, ], diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index f0f8cf9a5f..69276f996f 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -639,6 +639,16 @@ export type EventCommandExecuted = { } } +export type PermissionAction = "allow" | "deny" | "ask" + +export type PermissionRule = { + permission: string + pattern: string + action: PermissionAction +} + +export type PermissionRuleset = Array + export type Session = { id: string projectID: string @@ -661,6 +671,7 @@ export type Session = { compacting?: number archived?: number } + permission?: PermissionRuleset revert?: { messageID: string partID?: string @@ -1895,16 +1906,6 @@ export type File = { status: "added" | "deleted" | "modified" } -export type PermissionAction = "allow" | "deny" | "ask" - -export type PermissionRule = { - permission: string - pattern: string - action: PermissionAction -} - -export type PermissionRuleset = Array - export type Agent = { name: string description?: string @@ -2467,6 +2468,7 @@ export type SessionCreateData = { body?: { parentID?: string title?: string + permission?: PermissionRuleset } path?: never query?: { @@ -2982,6 +2984,9 @@ export type SessionPromptData = { } agent?: string noReply?: boolean + /** + * @deprecated tools and permissions have been merged, you can set permissions on the session itself now + */ tools?: { [key: string]: boolean } @@ -3166,6 +3171,9 @@ export type SessionPromptAsyncData = { } agent?: string noReply?: boolean + /** + * @deprecated tools and permissions have been merged, you can set permissions on the session itself now + */ tools?: { [key: string]: boolean }