feat(worktree): add managed workspace cloning (#30117)
This commit is contained in:
@@ -122,6 +122,7 @@ export interface Interface {
|
||||
readonly remove: (id: ID) => Effect.Effect<void, Error>
|
||||
readonly activate: (id: ID) => Effect.Effect<void, Error>
|
||||
readonly active: (serviceID: ServiceID) => Effect.Effect<Info | undefined, Error>
|
||||
readonly activeAll: () => Effect.Effect<Map<ServiceID, Info>, Error>
|
||||
readonly forService: (serviceID: ServiceID) => Effect.Effect<Info[], Error>
|
||||
}
|
||||
|
||||
@@ -216,6 +217,19 @@ export const layer = Layer.effect(
|
||||
)
|
||||
}),
|
||||
|
||||
activeAll: Effect.fn("Auth.activeAll")(function* () {
|
||||
const data = yield* SynchronizedRef.get(state)
|
||||
const result = new Map<ServiceID, Info>()
|
||||
for (const account of Object.values(data.accounts)) {
|
||||
if (!result.has(account.serviceID)) result.set(account.serviceID, account)
|
||||
}
|
||||
for (const [serviceID, id] of Object.entries(data.active)) {
|
||||
const account = data.accounts[id]
|
||||
if (account) result.set(ServiceID.make(serviceID), account)
|
||||
}
|
||||
return result
|
||||
}),
|
||||
|
||||
forService: Effect.fn("Auth.list")(function* (serviceID) {
|
||||
return Object.values((yield* SynchronizedRef.get(state)).accounts).filter((a) => a.serviceID === serviceID)
|
||||
}),
|
||||
|
||||
@@ -166,8 +166,14 @@ export const layer = Layer.effect(
|
||||
model: {
|
||||
get: (providerID, modelID) => draft.providers.get(providerID)?.models.get(modelID),
|
||||
update: (providerID, modelID, fn) => {
|
||||
result.provider.update(providerID, () => {})
|
||||
const record = draft.providers.get(providerID)!
|
||||
let record = draft.providers.get(providerID)
|
||||
if (!record) {
|
||||
record = castDraft({
|
||||
provider: ProviderV2.Info.empty(providerID),
|
||||
models: new Map<ModelV2.ID, ModelV2.Info>(),
|
||||
})
|
||||
draft.providers.set(providerID, record)
|
||||
}
|
||||
const model = record.models.get(modelID) ?? castDraft(ModelV2.Info.empty(providerID, modelID))
|
||||
if (!record.models.has(modelID)) record.models.set(modelID, model)
|
||||
fn(model)
|
||||
@@ -190,6 +196,7 @@ export const layer = Layer.effect(
|
||||
},
|
||||
finalize: Effect.fn("CatalogV2.finalize")(function* (catalog, reason) {
|
||||
if (reason !== "plugin.added") yield* plugin.trigger("catalog.transform", catalog, {}).pipe(Effect.asVoid)
|
||||
if (!policy.hasStatements()) return
|
||||
for (const record of [...catalog.provider.list()]) {
|
||||
if ((yield* policy.evaluate("provider.use", record.provider.id, "allow")) === "deny") {
|
||||
catalog.provider.remove(record.provider.id)
|
||||
|
||||
@@ -68,8 +68,8 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
||||
output: Schema.Int,
|
||||
}),
|
||||
}) {
|
||||
static empty(providerID: ProviderV2.ID, modelID: ID) {
|
||||
return new Info({
|
||||
static empty(providerID: ProviderV2.ID, modelID: ID): Info {
|
||||
return {
|
||||
id: modelID,
|
||||
apiID: modelID,
|
||||
providerID,
|
||||
@@ -101,7 +101,7 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
||||
context: 0,
|
||||
output: 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,14 @@ export const layer = Layer.effect(
|
||||
const existing = hooks.find((item) => item.id === input.id)
|
||||
if (existing) yield* Scope.close(existing.scope, Exit.void).pipe(Effect.ignore)
|
||||
const scope = yield* Scope.make()
|
||||
const result = yield* input.effect.pipe(Scope.provide(scope))
|
||||
const result = yield* input.effect.pipe(
|
||||
Scope.provide(scope),
|
||||
Effect.withSpan("Plugin.load", {
|
||||
attributes: {
|
||||
"plugin.id": input.id,
|
||||
},
|
||||
}),
|
||||
)
|
||||
hooks = [
|
||||
...hooks.filter((item) => item.id !== input.id),
|
||||
{
|
||||
|
||||
@@ -21,8 +21,10 @@ export const AccountPlugin = PluginV2.define({
|
||||
|
||||
return {
|
||||
"catalog.transform": Effect.fn(function* (evt) {
|
||||
const active = yield* accounts.activeAll().pipe(Effect.orDie)
|
||||
if (active.size === 0) return
|
||||
for (const item of evt.provider.list()) {
|
||||
const account = yield* accounts.active(Auth.ServiceID.make(item.provider.id)).pipe(Effect.orDie)
|
||||
const account = active.get(Auth.ServiceID.make(item.provider.id))
|
||||
if (!account) continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.enabled = {
|
||||
|
||||
@@ -16,6 +16,7 @@ export class Info extends Schema.Class<Info>("Policy.Info")({
|
||||
export interface Interface {
|
||||
readonly load: (statements: Info[]) => EffectRuntime.Effect<void>
|
||||
readonly evaluate: (action: string, resource: string, fallback: Effect) => EffectRuntime.Effect<Effect>
|
||||
readonly hasStatements: () => boolean
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Policy") {}
|
||||
@@ -30,6 +31,7 @@ export const layer = Layer.effect(
|
||||
load: EffectRuntime.fn("Policy.load")(function* (input) {
|
||||
statements = input
|
||||
}),
|
||||
hasStatements: () => statements.length > 0,
|
||||
evaluate: EffectRuntime.fn("Policy.evaluate")(function* (action, resource, fallback) {
|
||||
return (
|
||||
statements.findLast(
|
||||
|
||||
@@ -101,8 +101,8 @@ export class Info extends Schema.Class<Info>("ProviderV2.Info")({
|
||||
endpoint: Endpoint,
|
||||
options: Options,
|
||||
}) {
|
||||
static empty(providerID: ID) {
|
||||
return new Info({
|
||||
static empty(providerID: ID): Info {
|
||||
return {
|
||||
id: providerID,
|
||||
name: providerID,
|
||||
enabled: false,
|
||||
@@ -118,6 +118,6 @@ export class Info extends Schema.Class<Info>("ProviderV2.Info")({
|
||||
request: {},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export * as State from "./state"
|
||||
|
||||
import { Effect, Scope, Semaphore } from "effect"
|
||||
import { createDraft, finishDraft, type Draft, type Objectish } from "immer"
|
||||
import type { Draft, Objectish } from "immer"
|
||||
|
||||
export type Transform<Editor> = (editor: Editor) => void
|
||||
export type MakeEditor<State extends Objectish, Editor> = (draft: Draft<State>) => Editor
|
||||
@@ -24,17 +24,18 @@ export function create<State extends Objectish, Editor>(options: Options<State,
|
||||
let transforms: { update: Transform<Editor> }[] = []
|
||||
const semaphore = Semaphore.makeUnsafe(1)
|
||||
|
||||
const commit = Effect.fn("State.commit")(function* (draft: Draft<State>, reason?: string) {
|
||||
const api = options.editor(draft)
|
||||
const commit = Effect.fn("State.commit")(function* (next: State, reason?: string) {
|
||||
const api = options.editor(next as Draft<State>)
|
||||
if (options.finalize) yield* options.finalize(api, reason)
|
||||
state = finishDraft(draft) as State
|
||||
state = next
|
||||
})
|
||||
|
||||
const rebuild = Effect.fn("State.rebuild")(function* () {
|
||||
const draft = createDraft(options.initial())
|
||||
const api = options.editor(draft)
|
||||
for (const transform of transforms) transform.update(api)
|
||||
yield* commit(draft)
|
||||
const next = options.initial()
|
||||
const api = options.editor(next as Draft<State>)
|
||||
for (const transform of transforms)
|
||||
yield* Effect.sync(() => transform.update(api)).pipe(Effect.withSpan("State.rebuild.update", {}))
|
||||
yield* commit(next)
|
||||
}, semaphore.withPermit)
|
||||
|
||||
return {
|
||||
@@ -55,9 +56,9 @@ export function create<State extends Objectish, Editor>(options: Options<State,
|
||||
})
|
||||
}),
|
||||
update: Effect.fn("State.update")(function* (update, reason) {
|
||||
const draft = createDraft(state)
|
||||
yield* update(options.editor(draft))
|
||||
yield* commit(draft, reason)
|
||||
const api = options.editor(state as Draft<State>)
|
||||
yield* update(api)
|
||||
if (options.finalize) yield* options.finalize(api, reason)
|
||||
}, semaphore.withPermit),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user