chore: merge dev into v2 (#35962)

Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: James Long <longster@gmail.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Dustin Deus <deusdustin@gmail.com>
Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box>
Co-authored-by: Frank <frank@anoma.ly>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Co-authored-by: Dax <mail@thdxr.com>
Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com>
Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: runvip <164729189+runvip@users.noreply.github.com>
Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
Co-authored-by: Jack <jack@anoma.ly>
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com>
Co-authored-by: Julian Coy <julian@ex-machina.co>
Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: Kit Langton <kit.langton@gmail.com>
Co-authored-by: Simon Klee <hello@simonklee.dk>
Co-authored-by: Jay <air@live.ca>
Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com>
Co-authored-by: James Long <jlongster@users.noreply.github.com>
Co-authored-by: 冯基魁 <56265583+fengjikui@users.noreply.github.com>
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot] 2026-07-08 17:58:55 -05:00 committed by GitHub
commit 687dbba6a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
150 changed files with 8987 additions and 772 deletions

View file

@ -10,6 +10,11 @@ import { ConfigMarkdown } from "../markdown"
import { FSUtil } from "../../fs-util"
import { ConfigAgentV1 } from "../../v1/config/agent"
import { ConfigMigrateV1 } from "../../v1/config/migrate"
import { Global } from "../../global"
import { PermissionV2 } from "../../permission"
import type { LocationMutation } from "../../location-mutation"
import type { ReadTool } from "../../tool/read"
import type { EditTool } from "../../tool/edit"
const legacySources = [
{ pattern: "{agent,agents}/**/*.md", primary: false },
@ -18,6 +23,11 @@ const legacySources = [
const decodeAgent = Schema.decodeUnknownOption(ConfigAgent.Info)
const decodeLegacyAgent = Schema.decodeUnknownOption(ConfigAgentV1.Info)
const decodeConfig = Schema.decodeUnknownOption(Config.Info)
type PathAction =
| LocationMutation.ExternalDirectoryAuthorization["action"]
| typeof ReadTool.name
| typeof EditTool.name
const pathActions = ["external_directory", "read", "edit"] as const satisfies readonly PathAction[]
const agentKeys = new Set([
"model",
"variant",
@ -37,6 +47,7 @@ export const Plugin = define({
effect: Effect.fn(function* (ctx) {
const config = yield* Config.Service
const fs = yield* FSUtil.Service
const global = yield* Global.Service
const load = Effect.fn("ConfigAgentPlugin.load")(function* () {
return yield* Effect.forEach(yield* config.entries(), (entry) => {
if (entry.type === "document") return Effect.succeed([entry])
@ -58,11 +69,14 @@ export const Plugin = define({
})
const loaded = { documents: yield* load() }
yield* ctx.agent.transform((draft) => {
const global = loaded.documents.flatMap((document) => document.info.permissions ?? [])
const permissions = expandPermissions(
loaded.documents.flatMap((document) => document.info.permissions ?? []),
global.home,
)
const configuredDefault = Config.latest(loaded.documents, "default_agent")
if (configuredDefault !== undefined) draft.default(AgentV2.ID.make(configuredDefault))
for (const current of draft.list()) {
draft.update(current.id, (agent) => agent.permissions.push(...global))
draft.update(current.id, (agent) => agent.permissions.push(...permissions))
}
for (const document of loaded.documents) {
@ -75,7 +89,7 @@ export const Plugin = define({
const exists = draft.get(agentID) !== undefined
draft.update(agentID, (agent) => {
if (!exists) agent.permissions.push(...global)
if (!exists) agent.permissions.push(...permissions)
if (item.model !== undefined)
agent.model = {
id: item.model.model,
@ -92,7 +106,9 @@ export const Plugin = define({
if (item.hidden !== undefined) agent.hidden = item.hidden
if (item.color !== undefined) agent.color = item.color
if (item.steps !== undefined) agent.steps = item.steps
if (item.permissions !== undefined) agent.permissions.push(...item.permissions)
if (item.permissions !== undefined) {
agent.permissions.push(...expandPermissions(item.permissions, global.home))
}
})
}
}
@ -110,6 +126,27 @@ export const Plugin = define({
}),
})
function expandPermissions(rules: PermissionV2.Ruleset, home: string): PermissionV2.Ruleset {
// Expand only resources tools resolve as filesystem paths. Bash resources are raw shell text:
// rewriting `$HOME/private/**` would miss `$HOME/private/key`, and safe expansion needs shell-aware parsing.
return rules.map((rule) =>
isPathAction(rule.action) ? { ...rule, resource: expandHome(rule.resource, home) } : rule,
)
}
function isPathAction(action: string): action is PathAction {
return pathActions.some((item) => item === action)
}
function expandHome(resource: string, home: string) {
if (resource.startsWith("~/")) return home + resource.slice(1)
if (resource === "~") return home
if (resource === "$HOME") return home
if (resource.startsWith("$HOME/")) return home + resource.slice(5)
if (resource.startsWith("$HOME\\")) return home + resource.slice(5)
return resource
}
function discover(fs: FSUtil.Interface, directory: string) {
return Effect.forEach(legacySources, (source) =>
fs