feat(core): admit v2 skill guidance (#30843)
This commit is contained in:
parent
cc487dd032
commit
3f64b5e621
40 changed files with 3119 additions and 174 deletions
|
|
@ -33,14 +33,18 @@ export const Source = Schema.Union([
|
|||
]).annotate({ identifier: "PermissionV2.Source" })
|
||||
export type Source = typeof Source.Type
|
||||
|
||||
export const Request = Schema.Struct({
|
||||
id: ID,
|
||||
const RequestFields = {
|
||||
sessionID: SessionV2.ID,
|
||||
action: Schema.String,
|
||||
resources: Schema.Array(Schema.String),
|
||||
save: Schema.Array(Schema.String).pipe(Schema.optional),
|
||||
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
source: Source.pipe(Schema.optional),
|
||||
}
|
||||
|
||||
export const Request = Schema.Struct({
|
||||
id: ID,
|
||||
...RequestFields,
|
||||
}).annotate({ identifier: "PermissionV2.Request" })
|
||||
export type Request = typeof Request.Type
|
||||
|
||||
|
|
@ -49,12 +53,8 @@ export type Reply = typeof Reply.Type
|
|||
|
||||
export const AssertInput = Schema.Struct({
|
||||
id: ID.pipe(Schema.optional),
|
||||
sessionID: SessionV2.ID,
|
||||
action: Schema.String,
|
||||
resources: Schema.Array(Schema.String),
|
||||
save: Schema.Array(Schema.String).pipe(Schema.optional),
|
||||
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
source: Source.pipe(Schema.optional),
|
||||
...RequestFields,
|
||||
agent: AgentV2.ID.pipe(Schema.optional),
|
||||
}).annotate({ identifier: "PermissionV2.AssertInput" })
|
||||
export type AssertInput = typeof AssertInput.Type
|
||||
|
||||
|
|
@ -128,6 +128,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
|
|||
|
||||
interface Pending {
|
||||
readonly request: Request
|
||||
readonly agent?: AgentV2.ID
|
||||
readonly deferred: Deferred.Deferred<void, RejectedError | CorrectedError>
|
||||
}
|
||||
|
||||
|
|
@ -159,10 +160,14 @@ export const layer = Layer.effect(
|
|||
)
|
||||
})
|
||||
|
||||
const configured = EffectRuntime.fn("PermissionV2.configured")(function* (sessionID: SessionV2.ID) {
|
||||
const configured = EffectRuntime.fn("PermissionV2.configured")(function* (
|
||||
sessionID: SessionV2.ID,
|
||||
agentID?: AgentV2.ID,
|
||||
) {
|
||||
const session = yield* sessions.get(sessionID)
|
||||
if (!session) return yield* new SessionV2.NotFoundError({ sessionID })
|
||||
return (yield* agents.resolve(session.agent))?.permissions ?? missingAgentPermissions
|
||||
const agent = yield* agents.resolve(agentID ?? session.agent)
|
||||
return agent?.permissions ?? missingAgentPermissions
|
||||
})
|
||||
|
||||
function denied(input: AssertInput, rules: Ruleset) {
|
||||
|
|
@ -174,7 +179,7 @@ export const layer = Layer.effect(
|
|||
}
|
||||
|
||||
const evaluateInput = EffectRuntime.fnUntraced(function* (input: AssertInput) {
|
||||
const rules = yield* configured(input.sessionID)
|
||||
const rules = yield* configured(input.sessionID, input.agent)
|
||||
if (denied(input, rules)) return { effect: "deny" as const, rules }
|
||||
const all = [...rules, ...(yield* savedRules())]
|
||||
const effects = input.resources.map((resource) => evaluate(input.action, resource, all).effect)
|
||||
|
|
@ -194,11 +199,11 @@ export const layer = Layer.effect(
|
|||
}
|
||||
}
|
||||
|
||||
const create = (request: Request) =>
|
||||
const create = (request: Request, agent?: AgentV2.ID) =>
|
||||
EffectRuntime.uninterruptible(
|
||||
EffectRuntime.gen(function* () {
|
||||
const deferred = yield* Deferred.make<void, RejectedError | CorrectedError>()
|
||||
const item = { request, deferred }
|
||||
const item = { request, agent, deferred }
|
||||
if (pending.has(request.id)) return yield* EffectRuntime.die(`Duplicate pending permission ID: ${request.id}`)
|
||||
pending.set(request.id, item)
|
||||
yield* events
|
||||
|
|
@ -211,7 +216,7 @@ export const layer = Layer.effect(
|
|||
const ask = EffectRuntime.fn("PermissionV2.ask")(function* (input: AssertInput) {
|
||||
const result = yield* evaluateInput(input)
|
||||
const value = request(input)
|
||||
if (result.effect === "ask") yield* create(value)
|
||||
if (result.effect === "ask") yield* create(value, input.agent)
|
||||
return { id: value.id, effect: result.effect }
|
||||
})
|
||||
|
||||
|
|
@ -225,7 +230,7 @@ export const layer = Layer.effect(
|
|||
})
|
||||
}
|
||||
if (result.effect === "allow") return
|
||||
const item = yield* create(request(input))
|
||||
const item = yield* create(request(input), input.agent)
|
||||
return yield* restore(Deferred.await(item.deferred)).pipe(
|
||||
EffectRuntime.ensuring(
|
||||
EffectRuntime.sync(() => {
|
||||
|
|
@ -281,7 +286,7 @@ export const layer = Layer.effect(
|
|||
const rememberedRules = yield* savedRules()
|
||||
for (const [id, item] of pending) {
|
||||
const input = { ...item.request }
|
||||
const rules = yield* configured(item.request.sessionID).pipe(
|
||||
const rules = yield* configured(item.request.sessionID, item.agent).pipe(
|
||||
EffectRuntime.catchTag("Session.NotFoundError", () => EffectRuntime.succeed(undefined)),
|
||||
)
|
||||
if (!rules) continue
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue