mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-06 09:10:47 -04:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f840b3e3a2 | |||
| 05c3866788 | |||
| 3eca11a7da | |||
| 794283057a | |||
| 56c33e84a3 | |||
| 73581b3c3b | |||
| 6e4d01f846 | |||
| 8646587c95 | |||
| fb47f06228 | |||
| 3acaa5a359 | |||
| 0726a25142 | |||
| 0df96ddeb0 | |||
| 887673310b | |||
| 693a1bff81 | |||
| b40ed3aa85 | |||
| 732bb9c3cb | |||
| 2fd1660b4d | |||
| 681526d348 | |||
| e78869c849 | |||
| 934935963d | |||
| f3912a2a8a | |||
| 45d58717a4 | |||
| 74e3155ef0 | |||
| 0af6c82563 | |||
| 686127f809 | |||
| 5256655c4d | |||
| d10b652637 | |||
| b03ca0d4e2 | |||
| 25aaea3d31 | |||
| cae7a139bc | |||
| 5ea62ab05f | |||
| faadc05c88 |
@@ -248,11 +248,6 @@ export function formatKeybind(config: string, t?: (key: KeyLabel) => string): st
|
|||||||
return IS_MAC ? parts.join("") : parts.join("+")
|
return IS_MAC ? parts.join("") : parts.join("+")
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeybindV2 takes an array instead of a string
|
|
||||||
export function formatKeybindKeys(config: string, t?: (key: KeyLabel) => string): string[] {
|
|
||||||
return formatKeybindParts(config, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEditableTarget(target: EventTarget | null) {
|
function isEditableTarget(target: EventTarget | null) {
|
||||||
if (!(target instanceof HTMLElement)) return false
|
if (!(target instanceof HTMLElement)) return false
|
||||||
if (target.isContentEditable) return true
|
if (target.isContentEditable) return true
|
||||||
|
|||||||
@@ -286,13 +286,6 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
|||||||
children: tree.children,
|
children: tree.children,
|
||||||
expand: tree.expandDir,
|
expand: tree.expandDir,
|
||||||
collapse: tree.collapseDir,
|
collapse: tree.collapseDir,
|
||||||
toggle(input: string) {
|
|
||||||
if (tree.dirState(input)?.expanded) {
|
|
||||||
tree.collapseDir(input)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tree.expandDir(input)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
get,
|
get,
|
||||||
load,
|
load,
|
||||||
|
|||||||
@@ -153,18 +153,6 @@ export function normalizeProviderList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeProject(project: Project) {
|
|
||||||
if (!project.icon?.url && !project.icon?.override) return project
|
|
||||||
return {
|
|
||||||
...project,
|
|
||||||
icon: {
|
|
||||||
...project.icon,
|
|
||||||
url: undefined,
|
|
||||||
override: undefined,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeProjectInfo(project: Project | CurrentProject): Project {
|
export function normalizeProjectInfo(project: Project | CurrentProject): Project {
|
||||||
return {
|
return {
|
||||||
...project,
|
...project,
|
||||||
|
|||||||
@@ -753,9 +753,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
},
|
},
|
||||||
mobileSidebar: {
|
mobileSidebar: {
|
||||||
opened: createMemo(() => store.mobileSidebar?.opened ?? false),
|
opened: createMemo(() => store.mobileSidebar?.opened ?? false),
|
||||||
show() {
|
|
||||||
setStore("mobileSidebar", "opened", true)
|
|
||||||
},
|
|
||||||
hide() {
|
hide() {
|
||||||
setStore("mobileSidebar", "opened", false)
|
setStore("mobileSidebar", "opened", false)
|
||||||
},
|
},
|
||||||
@@ -961,33 +958,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
if (current.reviewOpen.includes(path)) return
|
if (current.reviewOpen.includes(path)) return
|
||||||
setStore("sessionView", session, "reviewOpen", current.reviewOpen.length, path)
|
setStore("sessionView", session, "reviewOpen", current.reviewOpen.length, path)
|
||||||
},
|
},
|
||||||
closePath(path: string) {
|
|
||||||
const session = key()
|
|
||||||
const current = store.sessionView[session]?.reviewOpen
|
|
||||||
if (!current) return
|
|
||||||
|
|
||||||
const index = current.indexOf(path)
|
|
||||||
if (index === -1) return
|
|
||||||
setStore(
|
|
||||||
"sessionView",
|
|
||||||
session,
|
|
||||||
"reviewOpen",
|
|
||||||
produce((draft) => {
|
|
||||||
if (!draft) return
|
|
||||||
draft.splice(index, 1)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
togglePath(path: string) {
|
|
||||||
const session = key()
|
|
||||||
const current = store.sessionView[session]?.reviewOpen
|
|
||||||
if (!current || !current.includes(path)) {
|
|
||||||
this.openPath(path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.closePath(path)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ type TabsInput = {
|
|||||||
fileBrowser?: Accessor<boolean>
|
fileBrowser?: Accessor<boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSessionKey = (dir: string | undefined, id: string | undefined) => `${dir ?? ""}${id ? `/${id}` : ""}`
|
|
||||||
|
|
||||||
export function shouldShowFileTree(input: { visible: boolean; opened: boolean }) {
|
export function shouldShowFileTree(input: { visible: boolean; opened: boolean }) {
|
||||||
return input.opened && input.visible
|
return input.opened && input.visible
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export async function runMini(input: MiniCommandInput) {
|
|||||||
) =>
|
) =>
|
||||||
resolveSessionTarget({
|
resolveSessionTarget({
|
||||||
client,
|
client,
|
||||||
location: { directory: next.location.directory, workspace: next.location.workspaceID },
|
location: { directory: next.location.directory },
|
||||||
agent: next.agent,
|
agent: next.agent,
|
||||||
model: next.model
|
model: next.model
|
||||||
? { providerID: next.model.providerID, id: next.model.modelID, variant: next.variant }
|
? { providerID: next.model.providerID, id: next.model.modelID, variant: next.variant }
|
||||||
|
|||||||
@@ -703,7 +703,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||||||
? Promise.resolve(undefined)
|
? Promise.resolve(undefined)
|
||||||
: input.client.form.request
|
: input.client.form.request
|
||||||
.list({
|
.list({
|
||||||
location: { directory: input.location.directory, workspace: input.location.workspaceID },
|
location: { directory: input.location.directory },
|
||||||
})
|
})
|
||||||
.catch(() => undefined),
|
.catch(() => undefined),
|
||||||
])
|
])
|
||||||
@@ -757,7 +757,6 @@ function formRequestOptions(location: LocationRef | undefined): [] | [{ headers:
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"x-opencode-directory": encodeURIComponent(location.directory),
|
"x-opencode-directory": encodeURIComponent(location.directory),
|
||||||
...(location.workspaceID ? { "x-opencode-workspace": location.workspaceID } : {}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ async function execute(input: RunCommandInput, prepared: Prepared, endpoint: End
|
|||||||
next.model ??
|
next.model ??
|
||||||
(options.variant
|
(options.variant
|
||||||
? await client.model
|
? await client.model
|
||||||
.default({ location: { directory: next.location.directory, workspace: next.location.workspaceID } })
|
.default({ location: { directory: next.location.directory } })
|
||||||
.then((result) => result.data)
|
.then((result) => result.data)
|
||||||
: undefined)
|
: undefined)
|
||||||
const model = selected
|
const model = selected
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ async function latestSession(
|
|||||||
const page = await client.session.list(
|
const page = await client.session.list(
|
||||||
{
|
{
|
||||||
directory: location.directory,
|
directory: location.directory,
|
||||||
workspace: location.workspaceID,
|
|
||||||
parentID: null,
|
parentID: null,
|
||||||
limit: SESSION_PAGE_LIMIT,
|
limit: SESSION_PAGE_LIMIT,
|
||||||
order: "desc",
|
order: "desc",
|
||||||
|
|||||||
@@ -426,14 +426,13 @@ describe("runNonInteractivePrompt", () => {
|
|||||||
const globalOptions = {
|
const globalOptions = {
|
||||||
headers: {
|
headers: {
|
||||||
"x-opencode-directory": "%2Fwork%20tree",
|
"x-opencode-directory": "%2Fwork%20tree",
|
||||||
"x-opencode-workspace": "wrk_1",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expect(sdk.form.cancel).toHaveBeenCalledWith({ sessionID: "global", formID: "frm_live" }, globalOptions)
|
expect(sdk.form.cancel).toHaveBeenCalledWith({ sessionID: "global", formID: "frm_live" }, globalOptions)
|
||||||
expect(sdk.form.cancel).toHaveBeenCalledWith({ sessionID: "ses_1", formID: "frm_pending" })
|
expect(sdk.form.cancel).toHaveBeenCalledWith({ sessionID: "ses_1", formID: "frm_pending" })
|
||||||
expect(sdk.form.cancel).toHaveBeenCalledWith({ sessionID: "global", formID: "frm_pending_global" }, globalOptions)
|
expect(sdk.form.cancel).toHaveBeenCalledWith({ sessionID: "global", formID: "frm_pending_global" }, globalOptions)
|
||||||
expect(sdk.form.request.list).toHaveBeenCalledWith({
|
expect(sdk.form.request.list).toHaveBeenCalledWith({
|
||||||
location: { directory: "/work tree", workspace: "wrk_1" },
|
location: { directory: "/work tree" },
|
||||||
})
|
})
|
||||||
expect(sdk.question.list).not.toHaveBeenCalled()
|
expect(sdk.question.list).not.toHaveBeenCalled()
|
||||||
expect(sdk.question.reject).not.toHaveBeenCalled()
|
expect(sdk.question.reject).not.toHaveBeenCalled()
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import type { Effect, Stream } from "effect"
|
|||||||
import type { Location } from "@opencode-ai/schema/location"
|
import type { Location } from "@opencode-ai/schema/location"
|
||||||
import type { Agent } from "@opencode-ai/schema/agent"
|
import type { Agent } from "@opencode-ai/schema/agent"
|
||||||
import type { Plugin } from "@opencode-ai/schema/plugin"
|
import type { Plugin } from "@opencode-ai/schema/plugin"
|
||||||
import type { Workspace } from "@opencode-ai/schema/workspace"
|
|
||||||
import type { Session } from "@opencode-ai/schema/session"
|
import type { Session } from "@opencode-ai/schema/session"
|
||||||
import type { AbsolutePath } from "@opencode-ai/schema/schema"
|
import type { AbsolutePath } from "@opencode-ai/schema/schema"
|
||||||
import type { Project } from "@opencode-ai/schema/project"
|
import type { Project } from "@opencode-ai/schema/project"
|
||||||
import type { RelativePath } from "@opencode-ai/schema/schema"
|
import type { RelativePath } from "@opencode-ai/schema/schema"
|
||||||
import type { Brand } from "effect"
|
import type { Brand } from "effect"
|
||||||
import type { Model } from "@opencode-ai/schema/model"
|
import type { Model } from "@opencode-ai/schema/model"
|
||||||
|
import type { Workspace } from "@opencode-ai/schema/workspace"
|
||||||
import type { SessionMessage } from "@opencode-ai/schema/session-message"
|
import type { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||||
import type { PromptInput } from "@opencode-ai/schema/prompt-input"
|
import type { PromptInput } from "@opencode-ai/schema/prompt-input"
|
||||||
import type { AgentAttachment } from "@opencode-ai/schema/prompt"
|
import type { AgentAttachment } from "@opencode-ai/schema/prompt"
|
||||||
@@ -58,9 +58,7 @@ export interface ServerApi<E = never> {
|
|||||||
readonly get: ServerGetOperation<E>
|
readonly get: ServerGetOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint2_0Input = {
|
export type Endpoint2_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint2_0Output = Location.Info
|
export type Endpoint2_0Output = Location.Info
|
||||||
export type LocationGetOperation<E = never> = (input?: Endpoint2_0Input) => Effect.Effect<Endpoint2_0Output, E>
|
export type LocationGetOperation<E = never> = (input?: Endpoint2_0Input) => Effect.Effect<Endpoint2_0Output, E>
|
||||||
|
|
||||||
@@ -68,15 +66,13 @@ export interface LocationApi<E = never> {
|
|||||||
readonly get: LocationGetOperation<E>
|
readonly get: LocationGetOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint3_0Input = {
|
export type Endpoint3_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint3_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Agent.Info> }
|
export type Endpoint3_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Agent.Info> }
|
||||||
export type AgentListOperation<E = never> = (input?: Endpoint3_0Input) => Effect.Effect<Endpoint3_0Output, E>
|
export type AgentListOperation<E = never> = (input?: Endpoint3_0Input) => Effect.Effect<Endpoint3_0Output, E>
|
||||||
|
|
||||||
export type Endpoint3_1Input = {
|
export type Endpoint3_1Input = {
|
||||||
readonly agentID: Agent.ID
|
readonly agentID: Agent.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint3_1Output = { readonly location: Location.Info; readonly data: Agent.Info }
|
export type Endpoint3_1Output = { readonly location: Location.Info; readonly data: Agent.Info }
|
||||||
export type AgentGetOperation<E = never> = (input: Endpoint3_1Input) => Effect.Effect<Endpoint3_1Output, E>
|
export type AgentGetOperation<E = never> = (input: Endpoint3_1Input) => Effect.Effect<Endpoint3_1Output, E>
|
||||||
@@ -86,9 +82,7 @@ export interface AgentApi<E = never> {
|
|||||||
readonly get: AgentGetOperation<E>
|
readonly get: AgentGetOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint4_0Input = {
|
export type Endpoint4_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint4_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Plugin.Info> }
|
export type Endpoint4_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Plugin.Info> }
|
||||||
export type PluginListOperation<E = never> = (input?: Endpoint4_0Input) => Effect.Effect<Endpoint4_0Output, E>
|
export type PluginListOperation<E = never> = (input?: Endpoint4_0Input) => Effect.Effect<Endpoint4_0Output, E>
|
||||||
|
|
||||||
@@ -97,7 +91,6 @@ export interface PluginApi<E = never> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint5_0Input = {
|
export type Endpoint5_0Input = {
|
||||||
readonly workspace?: Workspace.ID | undefined
|
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
@@ -898,15 +891,11 @@ export interface MessageApi<E = never> {
|
|||||||
readonly list: MessageListOperation<E>
|
readonly list: MessageListOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint7_0Input = {
|
export type Endpoint7_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint7_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Model.Info> }
|
export type Endpoint7_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Model.Info> }
|
||||||
export type ModelListOperation<E = never> = (input?: Endpoint7_0Input) => Effect.Effect<Endpoint7_0Output, E>
|
export type ModelListOperation<E = never> = (input?: Endpoint7_0Input) => Effect.Effect<Endpoint7_0Output, E>
|
||||||
|
|
||||||
export type Endpoint7_1Input = {
|
export type Endpoint7_1Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint7_1Output = { readonly location: Location.Info; readonly data: Model.Info | undefined }
|
export type Endpoint7_1Output = { readonly location: Location.Info; readonly data: Model.Info | undefined }
|
||||||
export type ModelDefaultOperation<E = never> = (input?: Endpoint7_1Input) => Effect.Effect<Endpoint7_1Output, E>
|
export type ModelDefaultOperation<E = never> = (input?: Endpoint7_1Input) => Effect.Effect<Endpoint7_1Output, E>
|
||||||
|
|
||||||
@@ -916,7 +905,7 @@ export interface ModelApi<E = never> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint8_0Input = {
|
export type Endpoint8_0Input = {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly prompt: string
|
readonly prompt: string
|
||||||
readonly model?: Model.Ref | undefined
|
readonly model?: Model.Ref | undefined
|
||||||
}
|
}
|
||||||
@@ -927,15 +916,13 @@ export interface GenerateApi<E = never> {
|
|||||||
readonly text: GenerateTextOperation<E>
|
readonly text: GenerateTextOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint9_0Input = {
|
export type Endpoint9_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint9_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Provider.Info> }
|
export type Endpoint9_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Provider.Info> }
|
||||||
export type ProviderListOperation<E = never> = (input?: Endpoint9_0Input) => Effect.Effect<Endpoint9_0Output, E>
|
export type ProviderListOperation<E = never> = (input?: Endpoint9_0Input) => Effect.Effect<Endpoint9_0Output, E>
|
||||||
|
|
||||||
export type Endpoint9_1Input = {
|
export type Endpoint9_1Input = {
|
||||||
readonly providerID: Provider.ID
|
readonly providerID: Provider.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint9_1Output = { readonly location: Location.Info; readonly data: Provider.Info }
|
export type Endpoint9_1Output = { readonly location: Location.Info; readonly data: Provider.Info }
|
||||||
export type ProviderGetOperation<E = never> = (input: Endpoint9_1Input) => Effect.Effect<Endpoint9_1Output, E>
|
export type ProviderGetOperation<E = never> = (input: Endpoint9_1Input) => Effect.Effect<Endpoint9_1Output, E>
|
||||||
@@ -945,21 +932,19 @@ export interface ProviderApi<E = never> {
|
|||||||
readonly get: ProviderGetOperation<E>
|
readonly get: ProviderGetOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint10_0Input = {
|
export type Endpoint10_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint10_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Integration.Info> }
|
export type Endpoint10_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Integration.Info> }
|
||||||
export type IntegrationListOperation<E = never> = (input?: Endpoint10_0Input) => Effect.Effect<Endpoint10_0Output, E>
|
export type IntegrationListOperation<E = never> = (input?: Endpoint10_0Input) => Effect.Effect<Endpoint10_0Output, E>
|
||||||
|
|
||||||
export type Endpoint10_1Input = {
|
export type Endpoint10_1Input = {
|
||||||
readonly integrationID: Integration.ID
|
readonly integrationID: Integration.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint10_1Output = { readonly location: Location.Info; readonly data: Integration.Info | undefined }
|
export type Endpoint10_1Output = { readonly location: Location.Info; readonly data: Integration.Info | undefined }
|
||||||
export type IntegrationGetOperation<E = never> = (input: Endpoint10_1Input) => Effect.Effect<Endpoint10_1Output, E>
|
export type IntegrationGetOperation<E = never> = (input: Endpoint10_1Input) => Effect.Effect<Endpoint10_1Output, E>
|
||||||
|
|
||||||
export type Endpoint10_2Input = {
|
export type Endpoint10_2Input = {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly url: string
|
readonly url: string
|
||||||
}
|
}
|
||||||
export type Endpoint10_2Output = void
|
export type Endpoint10_2Output = void
|
||||||
@@ -969,7 +954,7 @@ export type IntegrationWellknownAddOperation<E = never> = (
|
|||||||
|
|
||||||
export type Endpoint10_3Input = {
|
export type Endpoint10_3Input = {
|
||||||
readonly integrationID: Integration.ID
|
readonly integrationID: Integration.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly key: string
|
readonly key: string
|
||||||
readonly label?: string | undefined
|
readonly label?: string | undefined
|
||||||
}
|
}
|
||||||
@@ -980,7 +965,7 @@ export type IntegrationConnectKeyOperation<E = never> = (
|
|||||||
|
|
||||||
export type Endpoint10_4Input = {
|
export type Endpoint10_4Input = {
|
||||||
readonly integrationID: Integration.ID
|
readonly integrationID: Integration.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly methodID: Integration.MethodID
|
readonly methodID: Integration.MethodID
|
||||||
readonly inputs: { readonly [x: string]: string }
|
readonly inputs: { readonly [x: string]: string }
|
||||||
readonly label?: string | undefined
|
readonly label?: string | undefined
|
||||||
@@ -993,7 +978,7 @@ export type IntegrationOauthConnectOperation<E = never> = (
|
|||||||
export type Endpoint10_5Input = {
|
export type Endpoint10_5Input = {
|
||||||
readonly integrationID: Integration.ID
|
readonly integrationID: Integration.ID
|
||||||
readonly attemptID: Integration.AttemptID
|
readonly attemptID: Integration.AttemptID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint10_5Output = { readonly location: Location.Info; readonly data: Integration.AttemptStatus }
|
export type Endpoint10_5Output = { readonly location: Location.Info; readonly data: Integration.AttemptStatus }
|
||||||
export type IntegrationOauthStatusOperation<E = never> = (
|
export type IntegrationOauthStatusOperation<E = never> = (
|
||||||
@@ -1003,7 +988,7 @@ export type IntegrationOauthStatusOperation<E = never> = (
|
|||||||
export type Endpoint10_6Input = {
|
export type Endpoint10_6Input = {
|
||||||
readonly integrationID: Integration.ID
|
readonly integrationID: Integration.ID
|
||||||
readonly attemptID: Integration.AttemptID
|
readonly attemptID: Integration.AttemptID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly code?: string | undefined
|
readonly code?: string | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint10_6Output = void
|
export type Endpoint10_6Output = void
|
||||||
@@ -1014,7 +999,7 @@ export type IntegrationOauthCompleteOperation<E = never> = (
|
|||||||
export type Endpoint10_7Input = {
|
export type Endpoint10_7Input = {
|
||||||
readonly integrationID: Integration.ID
|
readonly integrationID: Integration.ID
|
||||||
readonly attemptID: Integration.AttemptID
|
readonly attemptID: Integration.AttemptID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint10_7Output = void
|
export type Endpoint10_7Output = void
|
||||||
export type IntegrationOauthCancelOperation<E = never> = (
|
export type IntegrationOauthCancelOperation<E = never> = (
|
||||||
@@ -1023,7 +1008,7 @@ export type IntegrationOauthCancelOperation<E = never> = (
|
|||||||
|
|
||||||
export type Endpoint10_8Input = {
|
export type Endpoint10_8Input = {
|
||||||
readonly integrationID: Integration.ID
|
readonly integrationID: Integration.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly methodID: Integration.MethodID
|
readonly methodID: Integration.MethodID
|
||||||
readonly label?: string | undefined
|
readonly label?: string | undefined
|
||||||
}
|
}
|
||||||
@@ -1035,7 +1020,7 @@ export type IntegrationCommandConnectOperation<E = never> = (
|
|||||||
export type Endpoint10_9Input = {
|
export type Endpoint10_9Input = {
|
||||||
readonly integrationID: Integration.ID
|
readonly integrationID: Integration.ID
|
||||||
readonly attemptID: Integration.AttemptID
|
readonly attemptID: Integration.AttemptID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint10_9Output = { readonly location: Location.Info; readonly data: Integration.CommandAttemptStatus }
|
export type Endpoint10_9Output = { readonly location: Location.Info; readonly data: Integration.CommandAttemptStatus }
|
||||||
export type IntegrationCommandStatusOperation<E = never> = (
|
export type IntegrationCommandStatusOperation<E = never> = (
|
||||||
@@ -1045,7 +1030,7 @@ export type IntegrationCommandStatusOperation<E = never> = (
|
|||||||
export type Endpoint10_10Input = {
|
export type Endpoint10_10Input = {
|
||||||
readonly integrationID: Integration.ID
|
readonly integrationID: Integration.ID
|
||||||
readonly attemptID: Integration.AttemptID
|
readonly attemptID: Integration.AttemptID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint10_10Output = void
|
export type Endpoint10_10Output = void
|
||||||
export type IntegrationCommandCancelOperation<E = never> = (
|
export type IntegrationCommandCancelOperation<E = never> = (
|
||||||
@@ -1070,15 +1055,13 @@ export interface IntegrationApi<E = never> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint11_0Input = {
|
export type Endpoint11_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint11_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Mcp.Server> }
|
export type Endpoint11_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Mcp.Server> }
|
||||||
export type McpListOperation<E = never> = (input?: Endpoint11_0Input) => Effect.Effect<Endpoint11_0Output, E>
|
export type McpListOperation<E = never> = (input?: Endpoint11_0Input) => Effect.Effect<Endpoint11_0Output, E>
|
||||||
|
|
||||||
export type Endpoint11_1Input = {
|
export type Endpoint11_1Input = {
|
||||||
readonly server: string
|
readonly server: string
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly config: Mcp.LocalConfig | Mcp.RemoteConfig
|
readonly config: Mcp.LocalConfig | Mcp.RemoteConfig
|
||||||
}
|
}
|
||||||
export type Endpoint11_1Output = void
|
export type Endpoint11_1Output = void
|
||||||
@@ -1086,28 +1069,26 @@ export type McpAddOperation<E = never> = (input: Endpoint11_1Input) => Effect.Ef
|
|||||||
|
|
||||||
export type Endpoint11_2Input = {
|
export type Endpoint11_2Input = {
|
||||||
readonly server: string
|
readonly server: string
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint11_2Output = void
|
export type Endpoint11_2Output = void
|
||||||
export type McpRemoveOperation<E = never> = (input: Endpoint11_2Input) => Effect.Effect<Endpoint11_2Output, E>
|
export type McpRemoveOperation<E = never> = (input: Endpoint11_2Input) => Effect.Effect<Endpoint11_2Output, E>
|
||||||
|
|
||||||
export type Endpoint11_3Input = {
|
export type Endpoint11_3Input = {
|
||||||
readonly server: string
|
readonly server: string
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint11_3Output = void
|
export type Endpoint11_3Output = void
|
||||||
export type McpConnectOperation<E = never> = (input: Endpoint11_3Input) => Effect.Effect<Endpoint11_3Output, E>
|
export type McpConnectOperation<E = never> = (input: Endpoint11_3Input) => Effect.Effect<Endpoint11_3Output, E>
|
||||||
|
|
||||||
export type Endpoint11_4Input = {
|
export type Endpoint11_4Input = {
|
||||||
readonly server: string
|
readonly server: string
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint11_4Output = void
|
export type Endpoint11_4Output = void
|
||||||
export type McpDisconnectOperation<E = never> = (input: Endpoint11_4Input) => Effect.Effect<Endpoint11_4Output, E>
|
export type McpDisconnectOperation<E = never> = (input: Endpoint11_4Input) => Effect.Effect<Endpoint11_4Output, E>
|
||||||
|
|
||||||
export type Endpoint11_5Input = {
|
export type Endpoint11_5Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint11_5Output = { readonly location: Location.Info; readonly data: Mcp.ResourceCatalog }
|
export type Endpoint11_5Output = { readonly location: Location.Info; readonly data: Mcp.ResourceCatalog }
|
||||||
export type McpResourceCatalogOperation<E = never> = (input?: Endpoint11_5Input) => Effect.Effect<Endpoint11_5Output, E>
|
export type McpResourceCatalogOperation<E = never> = (input?: Endpoint11_5Input) => Effect.Effect<Endpoint11_5Output, E>
|
||||||
|
|
||||||
@@ -1122,7 +1103,7 @@ export interface McpApi<E = never> {
|
|||||||
|
|
||||||
export type Endpoint12_0Input = {
|
export type Endpoint12_0Input = {
|
||||||
readonly credentialID: Credential.ID
|
readonly credentialID: Credential.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly label: string
|
readonly label: string
|
||||||
}
|
}
|
||||||
export type Endpoint12_0Output = void
|
export type Endpoint12_0Output = void
|
||||||
@@ -1130,7 +1111,7 @@ export type CredentialUpdateOperation<E = never> = (input: Endpoint12_0Input) =>
|
|||||||
|
|
||||||
export type Endpoint12_1Input = {
|
export type Endpoint12_1Input = {
|
||||||
readonly credentialID: Credential.ID
|
readonly credentialID: Credential.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint12_1Output = void
|
export type Endpoint12_1Output = void
|
||||||
export type CredentialRemoveOperation<E = never> = (input: Endpoint12_1Input) => Effect.Effect<Endpoint12_1Output, E>
|
export type CredentialRemoveOperation<E = never> = (input: Endpoint12_1Input) => Effect.Effect<Endpoint12_1Output, E>
|
||||||
@@ -1143,15 +1124,13 @@ export interface CredentialApi<E = never> {
|
|||||||
export type Endpoint13_0Output = ReadonlyArray<Project.Info>
|
export type Endpoint13_0Output = ReadonlyArray<Project.Info>
|
||||||
export type ProjectListOperation<E = never> = () => Effect.Effect<Endpoint13_0Output, E>
|
export type ProjectListOperation<E = never> = () => Effect.Effect<Endpoint13_0Output, E>
|
||||||
|
|
||||||
export type Endpoint13_1Input = {
|
export type Endpoint13_1Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint13_1Output = Project.Current
|
export type Endpoint13_1Output = Project.Current
|
||||||
export type ProjectCurrentOperation<E = never> = (input?: Endpoint13_1Input) => Effect.Effect<Endpoint13_1Output, E>
|
export type ProjectCurrentOperation<E = never> = (input?: Endpoint13_1Input) => Effect.Effect<Endpoint13_1Output, E>
|
||||||
|
|
||||||
export type Endpoint13_2Input = {
|
export type Endpoint13_2Input = {
|
||||||
readonly projectID: Project.ID
|
readonly projectID: Project.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint13_2Output = Project.Directories
|
export type Endpoint13_2Output = Project.Directories
|
||||||
export type ProjectDirectoriesOperation<E = never> = (input: Endpoint13_2Input) => Effect.Effect<Endpoint13_2Output, E>
|
export type ProjectDirectoriesOperation<E = never> = (input: Endpoint13_2Input) => Effect.Effect<Endpoint13_2Output, E>
|
||||||
@@ -1162,9 +1141,7 @@ export interface ProjectApi<E = never> {
|
|||||||
readonly directories: ProjectDirectoriesOperation<E>
|
readonly directories: ProjectDirectoriesOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint14_0Input = {
|
export type Endpoint14_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint14_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Form.Info> }
|
export type Endpoint14_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Form.Info> }
|
||||||
export type FormRequestListOperation<E = never> = (input?: Endpoint14_0Input) => Effect.Effect<Endpoint14_0Output, E>
|
export type FormRequestListOperation<E = never> = (input?: Endpoint14_0Input) => Effect.Effect<Endpoint14_0Output, E>
|
||||||
|
|
||||||
@@ -1208,9 +1185,7 @@ export interface FormApi<E = never> {
|
|||||||
readonly cancel: FormCancelOperation<E>
|
readonly cancel: FormCancelOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint15_0Input = {
|
export type Endpoint15_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint15_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Permission.Request> }
|
export type Endpoint15_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Permission.Request> }
|
||||||
export type PermissionRequestListOperation<E = never> = (
|
export type PermissionRequestListOperation<E = never> = (
|
||||||
input?: Endpoint15_0Input,
|
input?: Endpoint15_0Input,
|
||||||
@@ -1268,14 +1243,14 @@ export interface PermissionApi<E = never> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint16_0Input = {
|
export type Endpoint16_0Input = {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly path?: RelativePath | undefined
|
readonly path?: RelativePath | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint16_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<FileSystem.Entry> }
|
export type Endpoint16_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<FileSystem.Entry> }
|
||||||
export type FileListOperation<E = never> = (input?: Endpoint16_0Input) => Effect.Effect<Endpoint16_0Output, E>
|
export type FileListOperation<E = never> = (input?: Endpoint16_0Input) => Effect.Effect<Endpoint16_0Output, E>
|
||||||
|
|
||||||
export type Endpoint16_1Input = {
|
export type Endpoint16_1Input = {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly query: string
|
readonly query: string
|
||||||
readonly type?: "file" | "directory" | undefined
|
readonly type?: "file" | "directory" | undefined
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
@@ -1288,9 +1263,7 @@ export interface FileApi<E = never> {
|
|||||||
readonly find: FileFindOperation<E>
|
readonly find: FileFindOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint17_0Input = {
|
export type Endpoint17_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint17_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Command.Info> }
|
export type Endpoint17_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Command.Info> }
|
||||||
export type CommandListOperation<E = never> = (input?: Endpoint17_0Input) => Effect.Effect<Endpoint17_0Output, E>
|
export type CommandListOperation<E = never> = (input?: Endpoint17_0Input) => Effect.Effect<Endpoint17_0Output, E>
|
||||||
|
|
||||||
@@ -1298,9 +1271,7 @@ export interface CommandApi<E = never> {
|
|||||||
readonly list: CommandListOperation<E>
|
readonly list: CommandListOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint18_0Input = {
|
export type Endpoint18_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint18_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Skill.Info> }
|
export type Endpoint18_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Skill.Info> }
|
||||||
export type SkillListOperation<E = never> = (input?: Endpoint18_0Input) => Effect.Effect<Endpoint18_0Output, E>
|
export type SkillListOperation<E = never> = (input?: Endpoint18_0Input) => Effect.Effect<Endpoint18_0Output, E>
|
||||||
|
|
||||||
@@ -1315,14 +1286,12 @@ export interface EventApi<E = never> {
|
|||||||
readonly subscribe: EventSubscribeOperation<E>
|
readonly subscribe: EventSubscribeOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint20_0Input = {
|
export type Endpoint20_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint20_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Pty.Info> }
|
export type Endpoint20_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Pty.Info> }
|
||||||
export type PtyListOperation<E = never> = (input?: Endpoint20_0Input) => Effect.Effect<Endpoint20_0Output, E>
|
export type PtyListOperation<E = never> = (input?: Endpoint20_0Input) => Effect.Effect<Endpoint20_0Output, E>
|
||||||
|
|
||||||
export type Endpoint20_1Input = {
|
export type Endpoint20_1Input = {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly command?: string | undefined
|
readonly command?: string | undefined
|
||||||
readonly args?: ReadonlyArray<string> | undefined
|
readonly args?: ReadonlyArray<string> | undefined
|
||||||
readonly cwd?: string | undefined
|
readonly cwd?: string | undefined
|
||||||
@@ -1334,14 +1303,14 @@ export type PtyCreateOperation<E = never> = (input?: Endpoint20_1Input) => Effec
|
|||||||
|
|
||||||
export type Endpoint20_2Input = {
|
export type Endpoint20_2Input = {
|
||||||
readonly ptyID: Pty.ID
|
readonly ptyID: Pty.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint20_2Output = { readonly location: Location.Info; readonly data: Pty.Info }
|
export type Endpoint20_2Output = { readonly location: Location.Info; readonly data: Pty.Info }
|
||||||
export type PtyGetOperation<E = never> = (input: Endpoint20_2Input) => Effect.Effect<Endpoint20_2Output, E>
|
export type PtyGetOperation<E = never> = (input: Endpoint20_2Input) => Effect.Effect<Endpoint20_2Output, E>
|
||||||
|
|
||||||
export type Endpoint20_3Input = {
|
export type Endpoint20_3Input = {
|
||||||
readonly ptyID: Pty.ID
|
readonly ptyID: Pty.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly title?: string | undefined
|
readonly title?: string | undefined
|
||||||
readonly size?: { readonly rows: number; readonly cols: number } | undefined
|
readonly size?: { readonly rows: number; readonly cols: number } | undefined
|
||||||
}
|
}
|
||||||
@@ -1350,7 +1319,7 @@ export type PtyUpdateOperation<E = never> = (input: Endpoint20_3Input) => Effect
|
|||||||
|
|
||||||
export type Endpoint20_4Input = {
|
export type Endpoint20_4Input = {
|
||||||
readonly ptyID: Pty.ID
|
readonly ptyID: Pty.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint20_4Output = void
|
export type Endpoint20_4Output = void
|
||||||
export type PtyRemoveOperation<E = never> = (input: Endpoint20_4Input) => Effect.Effect<Endpoint20_4Output, E>
|
export type PtyRemoveOperation<E = never> = (input: Endpoint20_4Input) => Effect.Effect<Endpoint20_4Output, E>
|
||||||
@@ -1363,14 +1332,12 @@ export interface PtyApi<E = never> {
|
|||||||
readonly remove: PtyRemoveOperation<E>
|
readonly remove: PtyRemoveOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint21_0Input = {
|
export type Endpoint21_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint21_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Shell.Info> }
|
export type Endpoint21_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Shell.Info> }
|
||||||
export type ShellListOperation<E = never> = (input?: Endpoint21_0Input) => Effect.Effect<Endpoint21_0Output, E>
|
export type ShellListOperation<E = never> = (input?: Endpoint21_0Input) => Effect.Effect<Endpoint21_0Output, E>
|
||||||
|
|
||||||
export type Endpoint21_1Input = {
|
export type Endpoint21_1Input = {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly command: string
|
readonly command: string
|
||||||
readonly cwd?: string | undefined
|
readonly cwd?: string | undefined
|
||||||
readonly timeout: number
|
readonly timeout: number
|
||||||
@@ -1381,14 +1348,14 @@ export type ShellCreateOperation<E = never> = (input: Endpoint21_1Input) => Effe
|
|||||||
|
|
||||||
export type Endpoint21_2Input = {
|
export type Endpoint21_2Input = {
|
||||||
readonly id: Shell.ID
|
readonly id: Shell.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint21_2Output = { readonly location: Location.Info; readonly data: Shell.Info }
|
export type Endpoint21_2Output = { readonly location: Location.Info; readonly data: Shell.Info }
|
||||||
export type ShellGetOperation<E = never> = (input: Endpoint21_2Input) => Effect.Effect<Endpoint21_2Output, E>
|
export type ShellGetOperation<E = never> = (input: Endpoint21_2Input) => Effect.Effect<Endpoint21_2Output, E>
|
||||||
|
|
||||||
export type Endpoint21_3Input = {
|
export type Endpoint21_3Input = {
|
||||||
readonly id: Shell.ID
|
readonly id: Shell.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly timeout: number
|
readonly timeout: number
|
||||||
}
|
}
|
||||||
export type Endpoint21_3Output = { readonly location: Location.Info; readonly data: Shell.Info }
|
export type Endpoint21_3Output = { readonly location: Location.Info; readonly data: Shell.Info }
|
||||||
@@ -1396,7 +1363,7 @@ export type ShellTimeoutOperation<E = never> = (input: Endpoint21_3Input) => Eff
|
|||||||
|
|
||||||
export type Endpoint21_4Input = {
|
export type Endpoint21_4Input = {
|
||||||
readonly id: Shell.ID
|
readonly id: Shell.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly cursor?: number | undefined
|
readonly cursor?: number | undefined
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
}
|
}
|
||||||
@@ -1413,7 +1380,7 @@ export type ShellOutputOperation<E = never> = (input: Endpoint21_4Input) => Effe
|
|||||||
|
|
||||||
export type Endpoint21_5Input = {
|
export type Endpoint21_5Input = {
|
||||||
readonly id: Shell.ID
|
readonly id: Shell.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint21_5Output = void
|
export type Endpoint21_5Output = void
|
||||||
export type ShellRemoveOperation<E = never> = (input: Endpoint21_5Input) => Effect.Effect<Endpoint21_5Output, E>
|
export type ShellRemoveOperation<E = never> = (input: Endpoint21_5Input) => Effect.Effect<Endpoint21_5Output, E>
|
||||||
@@ -1427,9 +1394,7 @@ export interface ShellApi<E = never> {
|
|||||||
readonly remove: ShellRemoveOperation<E>
|
readonly remove: ShellRemoveOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint22_0Input = {
|
export type Endpoint22_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint22_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Question.Request> }
|
export type Endpoint22_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Question.Request> }
|
||||||
export type QuestionRequestListOperation<E = never> = (
|
export type QuestionRequestListOperation<E = never> = (
|
||||||
input?: Endpoint22_0Input,
|
input?: Endpoint22_0Input,
|
||||||
@@ -1458,9 +1423,7 @@ export interface QuestionApi<E = never> {
|
|||||||
readonly reject: QuestionRejectOperation<E>
|
readonly reject: QuestionRejectOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint23_0Input = {
|
export type Endpoint23_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint23_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Reference.Info> }
|
export type Endpoint23_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Reference.Info> }
|
||||||
export type ReferenceListOperation<E = never> = (input?: Endpoint23_0Input) => Effect.Effect<Endpoint23_0Output, E>
|
export type ReferenceListOperation<E = never> = (input?: Endpoint23_0Input) => Effect.Effect<Endpoint23_0Output, E>
|
||||||
|
|
||||||
@@ -1470,7 +1433,7 @@ export interface ReferenceApi<E = never> {
|
|||||||
|
|
||||||
export type Endpoint24_0Input = {
|
export type Endpoint24_0Input = {
|
||||||
readonly projectID: Project.ID
|
readonly projectID: Project.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly strategy: ProjectCopy.StrategyID
|
readonly strategy: ProjectCopy.StrategyID
|
||||||
readonly directory: AbsolutePath
|
readonly directory: AbsolutePath
|
||||||
readonly name?: string | undefined
|
readonly name?: string | undefined
|
||||||
@@ -1480,7 +1443,7 @@ export type ProjectCopyCreateOperation<E = never> = (input: Endpoint24_0Input) =
|
|||||||
|
|
||||||
export type Endpoint24_1Input = {
|
export type Endpoint24_1Input = {
|
||||||
readonly projectID: Project.ID
|
readonly projectID: Project.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly directory: AbsolutePath
|
readonly directory: AbsolutePath
|
||||||
readonly force: boolean
|
readonly force: boolean
|
||||||
}
|
}
|
||||||
@@ -1489,7 +1452,7 @@ export type ProjectCopyRemoveOperation<E = never> = (input: Endpoint24_1Input) =
|
|||||||
|
|
||||||
export type Endpoint24_2Input = {
|
export type Endpoint24_2Input = {
|
||||||
readonly projectID: Project.ID
|
readonly projectID: Project.ID
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
}
|
}
|
||||||
export type Endpoint24_2Output = void
|
export type Endpoint24_2Output = void
|
||||||
export type ProjectCopyRefreshOperation<E = never> = (input: Endpoint24_2Input) => Effect.Effect<Endpoint24_2Output, E>
|
export type ProjectCopyRefreshOperation<E = never> = (input: Endpoint24_2Input) => Effect.Effect<Endpoint24_2Output, E>
|
||||||
@@ -1500,20 +1463,16 @@ export interface ProjectCopyApi<E = never> {
|
|||||||
readonly refresh: ProjectCopyRefreshOperation<E>
|
readonly refresh: ProjectCopyRefreshOperation<E>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint25_0Input = {
|
export type Endpoint25_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint25_0Output = { readonly location: Location.Info; readonly data: Vcs.Info }
|
export type Endpoint25_0Output = { readonly location: Location.Info; readonly data: Vcs.Info }
|
||||||
export type VcsGetOperation<E = never> = (input?: Endpoint25_0Input) => Effect.Effect<Endpoint25_0Output, E>
|
export type VcsGetOperation<E = never> = (input?: Endpoint25_0Input) => Effect.Effect<Endpoint25_0Output, E>
|
||||||
|
|
||||||
export type Endpoint25_1Input = {
|
export type Endpoint25_1Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint25_1Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Vcs.FileStatus> }
|
export type Endpoint25_1Output = { readonly location: Location.Info; readonly data: ReadonlyArray<Vcs.FileStatus> }
|
||||||
export type VcsStatusOperation<E = never> = (input?: Endpoint25_1Input) => Effect.Effect<Endpoint25_1Output, E>
|
export type VcsStatusOperation<E = never> = (input?: Endpoint25_1Input) => Effect.Effect<Endpoint25_1Output, E>
|
||||||
|
|
||||||
export type Endpoint25_2Input = {
|
export type Endpoint25_2Input = {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly mode: Vcs.Mode
|
readonly mode: Vcs.Mode
|
||||||
readonly context?: number | undefined
|
readonly context?: number | undefined
|
||||||
}
|
}
|
||||||
@@ -1529,9 +1488,7 @@ export interface VcsApi<E = never> {
|
|||||||
export type Endpoint26_0Output = ReadonlyArray<Location.Ref>
|
export type Endpoint26_0Output = ReadonlyArray<Location.Ref>
|
||||||
export type DebugLocationListOperation<E = never> = () => Effect.Effect<Endpoint26_0Output, E>
|
export type DebugLocationListOperation<E = never> = () => Effect.Effect<Endpoint26_0Output, E>
|
||||||
|
|
||||||
export type Endpoint26_1Input = {
|
export type Endpoint26_1Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint26_1Output = void
|
export type Endpoint26_1Output = void
|
||||||
export type DebugLocationEvictOperation<E = never> = (input?: Endpoint26_1Input) => Effect.Effect<Endpoint26_1Output, E>
|
export type DebugLocationEvictOperation<E = never> = (input?: Endpoint26_1Input) => Effect.Effect<Endpoint26_1Output, E>
|
||||||
|
|
||||||
@@ -1539,14 +1496,12 @@ export interface DebugApi<E = never> {
|
|||||||
readonly location: { readonly list: DebugLocationListOperation<E>; readonly evict: DebugLocationEvictOperation<E> }
|
readonly location: { readonly list: DebugLocationListOperation<E>; readonly evict: DebugLocationEvictOperation<E> }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Endpoint27_0Input = {
|
export type Endpoint27_0Input = { readonly location?: { readonly directory?: string | undefined } | undefined }
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}
|
|
||||||
export type Endpoint27_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<WebSearch.Provider> }
|
export type Endpoint27_0Output = { readonly location: Location.Info; readonly data: ReadonlyArray<WebSearch.Provider> }
|
||||||
export type WebsearchProvidersOperation<E = never> = (input?: Endpoint27_0Input) => Effect.Effect<Endpoint27_0Output, E>
|
export type WebsearchProvidersOperation<E = never> = (input?: Endpoint27_0Input) => Effect.Effect<Endpoint27_0Output, E>
|
||||||
|
|
||||||
export type Endpoint27_1Input = {
|
export type Endpoint27_1Input = {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly query: string
|
readonly query: string
|
||||||
readonly providerID?: WebSearch.ID | undefined
|
readonly providerID?: WebSearch.ID | undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,7 +285,6 @@ const Endpoint5_0 = (raw: RawClient["server.session"]) => (input?: Endpoint5_0In
|
|||||||
preserveEffect<Endpoint5_0Output>()(
|
preserveEffect<Endpoint5_0Output>()(
|
||||||
raw["session.list"]({
|
raw["session.list"]({
|
||||||
query: {
|
query: {
|
||||||
workspace: input?.["workspace"],
|
|
||||||
limit: input?.["limit"],
|
limit: input?.["limit"],
|
||||||
order: input?.["order"],
|
order: input?.["order"],
|
||||||
search: input?.["search"],
|
search: input?.["search"],
|
||||||
|
|||||||
@@ -441,7 +441,6 @@ export function make(options: ClientOptions) {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
path: `/api/session`,
|
path: `/api/session`,
|
||||||
query: {
|
query: {
|
||||||
workspace: input?.["workspace"],
|
|
||||||
limit: input?.["limit"],
|
limit: input?.["limit"],
|
||||||
order: input?.["order"],
|
order: input?.["order"],
|
||||||
search: input?.["search"],
|
search: input?.["search"],
|
||||||
|
|||||||
@@ -2514,9 +2514,7 @@ export type HealthStopOutput = ServiceStopResponse
|
|||||||
export type ServerGetOutput = { urls: Array<string> }
|
export type ServerGetOutput = { urls: Array<string> }
|
||||||
|
|
||||||
export type LocationGetInput = {
|
export type LocationGetInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LocationGetOutput = {
|
export type LocationGetOutput = {
|
||||||
@@ -2526,9 +2524,7 @@ export type LocationGetOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type AgentListInput = {
|
export type AgentListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AgentListOutput = {
|
export type AgentListOutput = {
|
||||||
@@ -2538,9 +2534,7 @@ export type AgentListOutput = {
|
|||||||
|
|
||||||
export type AgentGetInput = {
|
export type AgentGetInput = {
|
||||||
readonly agentID: { readonly agentID: string }["agentID"]
|
readonly agentID: { readonly agentID: string }["agentID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AgentGetOutput = {
|
export type AgentGetOutput = {
|
||||||
@@ -2549,9 +2543,7 @@ export type AgentGetOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PluginListInput = {
|
export type PluginListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PluginListOutput = {
|
export type PluginListOutput = {
|
||||||
@@ -2560,19 +2552,7 @@ export type PluginListOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SessionListInput = {
|
export type SessionListInput = {
|
||||||
readonly workspace?: {
|
|
||||||
readonly workspace?: string | undefined
|
|
||||||
readonly limit?: number | undefined
|
|
||||||
readonly order?: "asc" | "desc" | undefined
|
|
||||||
readonly search?: string | undefined
|
|
||||||
readonly parentID?: string | null | undefined
|
|
||||||
readonly directory?: string | undefined
|
|
||||||
readonly project?: string | undefined
|
|
||||||
readonly subpath?: string | undefined
|
|
||||||
readonly cursor?: string | undefined
|
|
||||||
}["workspace"]
|
|
||||||
readonly limit?: {
|
readonly limit?: {
|
||||||
readonly workspace?: string | undefined
|
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
@@ -2583,7 +2563,6 @@ export type SessionListInput = {
|
|||||||
readonly cursor?: string | undefined
|
readonly cursor?: string | undefined
|
||||||
}["limit"]
|
}["limit"]
|
||||||
readonly order?: {
|
readonly order?: {
|
||||||
readonly workspace?: string | undefined
|
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
@@ -2594,7 +2573,6 @@ export type SessionListInput = {
|
|||||||
readonly cursor?: string | undefined
|
readonly cursor?: string | undefined
|
||||||
}["order"]
|
}["order"]
|
||||||
readonly search?: {
|
readonly search?: {
|
||||||
readonly workspace?: string | undefined
|
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
@@ -2605,7 +2583,6 @@ export type SessionListInput = {
|
|||||||
readonly cursor?: string | undefined
|
readonly cursor?: string | undefined
|
||||||
}["search"]
|
}["search"]
|
||||||
readonly parentID?: {
|
readonly parentID?: {
|
||||||
readonly workspace?: string | undefined
|
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
@@ -2616,7 +2593,6 @@ export type SessionListInput = {
|
|||||||
readonly cursor?: string | undefined
|
readonly cursor?: string | undefined
|
||||||
}["parentID"]
|
}["parentID"]
|
||||||
readonly directory?: {
|
readonly directory?: {
|
||||||
readonly workspace?: string | undefined
|
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
@@ -2627,7 +2603,6 @@ export type SessionListInput = {
|
|||||||
readonly cursor?: string | undefined
|
readonly cursor?: string | undefined
|
||||||
}["directory"]
|
}["directory"]
|
||||||
readonly project?: {
|
readonly project?: {
|
||||||
readonly workspace?: string | undefined
|
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
@@ -2638,7 +2613,6 @@ export type SessionListInput = {
|
|||||||
readonly cursor?: string | undefined
|
readonly cursor?: string | undefined
|
||||||
}["project"]
|
}["project"]
|
||||||
readonly subpath?: {
|
readonly subpath?: {
|
||||||
readonly workspace?: string | undefined
|
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
@@ -2649,7 +2623,6 @@ export type SessionListInput = {
|
|||||||
readonly cursor?: string | undefined
|
readonly cursor?: string | undefined
|
||||||
}["subpath"]
|
}["subpath"]
|
||||||
readonly cursor?: {
|
readonly cursor?: {
|
||||||
readonly workspace?: string | undefined
|
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
readonly order?: "asc" | "desc" | undefined
|
readonly order?: "asc" | "desc" | undefined
|
||||||
readonly search?: string | undefined
|
readonly search?: string | undefined
|
||||||
@@ -3244,9 +3217,7 @@ export type MessageListInput = {
|
|||||||
export type MessageListOutput = SessionMessagesResponse
|
export type MessageListOutput = SessionMessagesResponse
|
||||||
|
|
||||||
export type ModelListInput = {
|
export type ModelListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModelListOutput = {
|
export type ModelListOutput = {
|
||||||
@@ -3255,9 +3226,7 @@ export type ModelListOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ModelDefaultInput = {
|
export type ModelDefaultInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModelDefaultOutput = {
|
export type ModelDefaultOutput = {
|
||||||
@@ -3266,9 +3235,7 @@ export type ModelDefaultOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type GenerateTextInput = {
|
export type GenerateTextInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly prompt: {
|
readonly prompt: {
|
||||||
readonly prompt: string
|
readonly prompt: string
|
||||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||||
@@ -3282,9 +3249,7 @@ export type GenerateTextInput = {
|
|||||||
export type GenerateTextOutput = GenerateTextResponse["data"]
|
export type GenerateTextOutput = GenerateTextResponse["data"]
|
||||||
|
|
||||||
export type ProviderListInput = {
|
export type ProviderListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProviderListOutput = {
|
export type ProviderListOutput = {
|
||||||
@@ -3294,9 +3259,7 @@ export type ProviderListOutput = {
|
|||||||
|
|
||||||
export type ProviderGetInput = {
|
export type ProviderGetInput = {
|
||||||
readonly providerID: { readonly providerID: string }["providerID"]
|
readonly providerID: { readonly providerID: string }["providerID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProviderGetOutput = {
|
export type ProviderGetOutput = {
|
||||||
@@ -3305,9 +3268,7 @@ export type ProviderGetOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationListInput = {
|
export type IntegrationListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationListOutput = {
|
export type IntegrationListOutput = {
|
||||||
@@ -3317,9 +3278,7 @@ export type IntegrationListOutput = {
|
|||||||
|
|
||||||
export type IntegrationGetInput = {
|
export type IntegrationGetInput = {
|
||||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationGetOutput = {
|
export type IntegrationGetOutput = {
|
||||||
@@ -3328,9 +3287,7 @@ export type IntegrationGetOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationWellknownAddInput = {
|
export type IntegrationWellknownAddInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly url: { readonly url: string }["url"]
|
readonly url: { readonly url: string }["url"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3338,9 +3295,7 @@ export type IntegrationWellknownAddOutput = void
|
|||||||
|
|
||||||
export type IntegrationConnectKeyInput = {
|
export type IntegrationConnectKeyInput = {
|
||||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly key: { readonly key: string; readonly label?: string | undefined }["key"]
|
readonly key: { readonly key: string; readonly label?: string | undefined }["key"]
|
||||||
readonly label?: { readonly key: string; readonly label?: string | undefined }["label"]
|
readonly label?: { readonly key: string; readonly label?: string | undefined }["label"]
|
||||||
}
|
}
|
||||||
@@ -3349,9 +3304,7 @@ export type IntegrationConnectKeyOutput = void
|
|||||||
|
|
||||||
export type IntegrationOauthConnectInput = {
|
export type IntegrationOauthConnectInput = {
|
||||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly methodID: {
|
readonly methodID: {
|
||||||
readonly methodID: string
|
readonly methodID: string
|
||||||
readonly inputs: { readonly [x: string]: string }
|
readonly inputs: { readonly [x: string]: string }
|
||||||
@@ -3383,9 +3336,7 @@ export type IntegrationOauthConnectOutput = {
|
|||||||
export type IntegrationOauthStatusInput = {
|
export type IntegrationOauthStatusInput = {
|
||||||
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||||
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationOauthStatusOutput = {
|
export type IntegrationOauthStatusOutput = {
|
||||||
@@ -3396,9 +3347,7 @@ export type IntegrationOauthStatusOutput = {
|
|||||||
export type IntegrationOauthCompleteInput = {
|
export type IntegrationOauthCompleteInput = {
|
||||||
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||||
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly code?: { readonly code?: string | undefined }["code"]
|
readonly code?: { readonly code?: string | undefined }["code"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3407,18 +3356,14 @@ export type IntegrationOauthCompleteOutput = void
|
|||||||
export type IntegrationOauthCancelInput = {
|
export type IntegrationOauthCancelInput = {
|
||||||
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||||
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationOauthCancelOutput = void
|
export type IntegrationOauthCancelOutput = void
|
||||||
|
|
||||||
export type IntegrationCommandConnectInput = {
|
export type IntegrationCommandConnectInput = {
|
||||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly methodID: { readonly methodID: string; readonly label?: string | undefined }["methodID"]
|
readonly methodID: { readonly methodID: string; readonly label?: string | undefined }["methodID"]
|
||||||
readonly label?: { readonly methodID: string; readonly label?: string | undefined }["label"]
|
readonly label?: { readonly methodID: string; readonly label?: string | undefined }["label"]
|
||||||
}
|
}
|
||||||
@@ -3431,9 +3376,7 @@ export type IntegrationCommandConnectOutput = {
|
|||||||
export type IntegrationCommandStatusInput = {
|
export type IntegrationCommandStatusInput = {
|
||||||
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||||
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationCommandStatusOutput = {
|
export type IntegrationCommandStatusOutput = {
|
||||||
@@ -3444,17 +3387,13 @@ export type IntegrationCommandStatusOutput = {
|
|||||||
export type IntegrationCommandCancelInput = {
|
export type IntegrationCommandCancelInput = {
|
||||||
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
readonly integrationID: { readonly integrationID: string; readonly attemptID: string }["integrationID"]
|
||||||
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
readonly attemptID: { readonly integrationID: string; readonly attemptID: string }["attemptID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IntegrationCommandCancelOutput = void
|
export type IntegrationCommandCancelOutput = void
|
||||||
|
|
||||||
export type McpListInput = {
|
export type McpListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type McpListOutput = {
|
export type McpListOutput = {
|
||||||
@@ -3464,9 +3403,7 @@ export type McpListOutput = {
|
|||||||
|
|
||||||
export type McpAddInput = {
|
export type McpAddInput = {
|
||||||
readonly server: { readonly server: string }["server"]
|
readonly server: { readonly server: string }["server"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly config: {
|
readonly config: {
|
||||||
readonly config:
|
readonly config:
|
||||||
| {
|
| {
|
||||||
@@ -3515,35 +3452,27 @@ export type McpAddOutput = void
|
|||||||
|
|
||||||
export type McpRemoveInput = {
|
export type McpRemoveInput = {
|
||||||
readonly server: { readonly server: string }["server"]
|
readonly server: { readonly server: string }["server"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type McpRemoveOutput = void
|
export type McpRemoveOutput = void
|
||||||
|
|
||||||
export type McpConnectInput = {
|
export type McpConnectInput = {
|
||||||
readonly server: { readonly server: string }["server"]
|
readonly server: { readonly server: string }["server"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type McpConnectOutput = void
|
export type McpConnectOutput = void
|
||||||
|
|
||||||
export type McpDisconnectInput = {
|
export type McpDisconnectInput = {
|
||||||
readonly server: { readonly server: string }["server"]
|
readonly server: { readonly server: string }["server"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type McpDisconnectOutput = void
|
export type McpDisconnectOutput = void
|
||||||
|
|
||||||
export type McpResourceCatalogInput = {
|
export type McpResourceCatalogInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type McpResourceCatalogOutput = {
|
export type McpResourceCatalogOutput = {
|
||||||
@@ -3553,9 +3482,7 @@ export type McpResourceCatalogOutput = {
|
|||||||
|
|
||||||
export type CredentialUpdateInput = {
|
export type CredentialUpdateInput = {
|
||||||
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly label: { readonly label: string }["label"]
|
readonly label: { readonly label: string }["label"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3563,9 +3490,7 @@ export type CredentialUpdateOutput = void
|
|||||||
|
|
||||||
export type CredentialRemoveInput = {
|
export type CredentialRemoveInput = {
|
||||||
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CredentialRemoveOutput = void
|
export type CredentialRemoveOutput = void
|
||||||
@@ -3573,26 +3498,20 @@ export type CredentialRemoveOutput = void
|
|||||||
export type ProjectListOutput = Array<Project>
|
export type ProjectListOutput = Array<Project>
|
||||||
|
|
||||||
export type ProjectCurrentInput = {
|
export type ProjectCurrentInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProjectCurrentOutput = ProjectCurrent
|
export type ProjectCurrentOutput = ProjectCurrent
|
||||||
|
|
||||||
export type ProjectDirectoriesInput = {
|
export type ProjectDirectoriesInput = {
|
||||||
readonly projectID: { readonly projectID: string }["projectID"]
|
readonly projectID: { readonly projectID: string }["projectID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProjectDirectoriesOutput = ProjectDirectories
|
export type ProjectDirectoriesOutput = ProjectDirectories
|
||||||
|
|
||||||
export type FormRequestListInput = {
|
export type FormRequestListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FormRequestListOutput = {
|
export type FormRequestListOutput = {
|
||||||
@@ -4446,9 +4365,7 @@ export type FormCancelInput = {
|
|||||||
export type FormCancelOutput = void
|
export type FormCancelOutput = void
|
||||||
|
|
||||||
export type PermissionRequestListInput = {
|
export type PermissionRequestListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PermissionRequestListOutput = {
|
export type PermissionRequestListOutput = {
|
||||||
@@ -4554,9 +4471,7 @@ export type PermissionReplyInput = {
|
|||||||
export type PermissionReplyOutput = void
|
export type PermissionReplyOutput = void
|
||||||
|
|
||||||
export type FileReadInput = {
|
export type FileReadInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly path: string
|
readonly path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4564,11 +4479,11 @@ export type FileReadOutput = globalThis.Uint8Array
|
|||||||
|
|
||||||
export type FileListInput = {
|
export type FileListInput = {
|
||||||
readonly location?: {
|
readonly location?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly path?: string | undefined
|
readonly path?: string | undefined
|
||||||
}["location"]
|
}["location"]
|
||||||
readonly path?: {
|
readonly path?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly path?: string | undefined
|
readonly path?: string | undefined
|
||||||
}["path"]
|
}["path"]
|
||||||
}
|
}
|
||||||
@@ -4580,25 +4495,25 @@ export type FileListOutput = {
|
|||||||
|
|
||||||
export type FileFindInput = {
|
export type FileFindInput = {
|
||||||
readonly location?: {
|
readonly location?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly query: string
|
readonly query: string
|
||||||
readonly type?: "file" | "directory" | undefined
|
readonly type?: "file" | "directory" | undefined
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
}["location"]
|
}["location"]
|
||||||
readonly query: {
|
readonly query: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly query: string
|
readonly query: string
|
||||||
readonly type?: "file" | "directory" | undefined
|
readonly type?: "file" | "directory" | undefined
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
}["query"]
|
}["query"]
|
||||||
readonly type?: {
|
readonly type?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly query: string
|
readonly query: string
|
||||||
readonly type?: "file" | "directory" | undefined
|
readonly type?: "file" | "directory" | undefined
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
}["type"]
|
}["type"]
|
||||||
readonly limit?: {
|
readonly limit?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly query: string
|
readonly query: string
|
||||||
readonly type?: "file" | "directory" | undefined
|
readonly type?: "file" | "directory" | undefined
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
@@ -4611,9 +4526,7 @@ export type FileFindOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CommandListInput = {
|
export type CommandListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CommandListOutput = {
|
export type CommandListOutput = {
|
||||||
@@ -4622,9 +4535,7 @@ export type CommandListOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SkillListInput = {
|
export type SkillListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SkillListOutput = {
|
export type SkillListOutput = {
|
||||||
@@ -4635,9 +4546,7 @@ export type SkillListOutput = {
|
|||||||
export type EventSubscribeOutput = V2Event
|
export type EventSubscribeOutput = V2Event
|
||||||
|
|
||||||
export type PtyListInput = {
|
export type PtyListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PtyListOutput = {
|
export type PtyListOutput = {
|
||||||
@@ -4646,9 +4555,7 @@ export type PtyListOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PtyCreateInput = {
|
export type PtyCreateInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly command?: {
|
readonly command?: {
|
||||||
readonly command?: string
|
readonly command?: string
|
||||||
readonly args?: ReadonlyArray<string>
|
readonly args?: ReadonlyArray<string>
|
||||||
@@ -4693,9 +4600,7 @@ export type PtyCreateOutput = {
|
|||||||
|
|
||||||
export type PtyGetInput = {
|
export type PtyGetInput = {
|
||||||
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PtyGetOutput = {
|
export type PtyGetOutput = {
|
||||||
@@ -4705,9 +4610,7 @@ export type PtyGetOutput = {
|
|||||||
|
|
||||||
export type PtyUpdateInput = {
|
export type PtyUpdateInput = {
|
||||||
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly title?: {
|
readonly title?: {
|
||||||
readonly title?: string
|
readonly title?: string
|
||||||
readonly size?: { readonly rows: number; readonly cols: number }
|
readonly size?: { readonly rows: number; readonly cols: number }
|
||||||
@@ -4722,17 +4625,13 @@ export type PtyUpdateOutput = {
|
|||||||
|
|
||||||
export type PtyRemoveInput = {
|
export type PtyRemoveInput = {
|
||||||
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PtyRemoveOutput = void
|
export type PtyRemoveOutput = void
|
||||||
|
|
||||||
export type ShellListInput = {
|
export type ShellListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ShellListOutput = {
|
export type ShellListOutput = {
|
||||||
@@ -4741,9 +4640,7 @@ export type ShellListOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ShellCreateInput = {
|
export type ShellCreateInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly command: {
|
readonly command: {
|
||||||
readonly command: string
|
readonly command: string
|
||||||
readonly cwd?: string
|
readonly cwd?: string
|
||||||
@@ -4777,9 +4674,7 @@ export type ShellCreateOutput = {
|
|||||||
|
|
||||||
export type ShellGetInput = {
|
export type ShellGetInput = {
|
||||||
readonly id: { readonly id: string }["id"]
|
readonly id: { readonly id: string }["id"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ShellGetOutput = {
|
export type ShellGetOutput = {
|
||||||
@@ -4789,9 +4684,7 @@ export type ShellGetOutput = {
|
|||||||
|
|
||||||
export type ShellTimeoutInput = {
|
export type ShellTimeoutInput = {
|
||||||
readonly id: { readonly id: string }["id"]
|
readonly id: { readonly id: string }["id"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly timeout: { readonly timeout: number }["timeout"]
|
readonly timeout: { readonly timeout: number }["timeout"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4803,17 +4696,17 @@ export type ShellTimeoutOutput = {
|
|||||||
export type ShellOutputInput = {
|
export type ShellOutputInput = {
|
||||||
readonly id: { readonly id: string }["id"]
|
readonly id: { readonly id: string }["id"]
|
||||||
readonly location?: {
|
readonly location?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly cursor?: number | undefined
|
readonly cursor?: number | undefined
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
}["location"]
|
}["location"]
|
||||||
readonly cursor?: {
|
readonly cursor?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly cursor?: number | undefined
|
readonly cursor?: number | undefined
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
}["cursor"]
|
}["cursor"]
|
||||||
readonly limit?: {
|
readonly limit?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly cursor?: number | undefined
|
readonly cursor?: number | undefined
|
||||||
readonly limit?: number | undefined
|
readonly limit?: number | undefined
|
||||||
}["limit"]
|
}["limit"]
|
||||||
@@ -4826,17 +4719,13 @@ export type ShellOutputOutput = {
|
|||||||
|
|
||||||
export type ShellRemoveInput = {
|
export type ShellRemoveInput = {
|
||||||
readonly id: { readonly id: string }["id"]
|
readonly id: { readonly id: string }["id"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ShellRemoveOutput = void
|
export type ShellRemoveOutput = void
|
||||||
|
|
||||||
export type QuestionRequestListInput = {
|
export type QuestionRequestListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QuestionRequestListOutput = {
|
export type QuestionRequestListOutput = {
|
||||||
@@ -4864,9 +4753,7 @@ export type QuestionRejectInput = {
|
|||||||
export type QuestionRejectOutput = void
|
export type QuestionRejectOutput = void
|
||||||
|
|
||||||
export type ReferenceListInput = {
|
export type ReferenceListInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReferenceListOutput = {
|
export type ReferenceListOutput = {
|
||||||
@@ -4876,9 +4763,7 @@ export type ReferenceListOutput = {
|
|||||||
|
|
||||||
export type ProjectCopyCreateInput = {
|
export type ProjectCopyCreateInput = {
|
||||||
readonly projectID: { readonly projectID: string }["projectID"]
|
readonly projectID: { readonly projectID: string }["projectID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly strategy: { readonly strategy: string; readonly directory: string; readonly name?: string }["strategy"]
|
readonly strategy: { readonly strategy: string; readonly directory: string; readonly name?: string }["strategy"]
|
||||||
readonly directory: { readonly strategy: string; readonly directory: string; readonly name?: string }["directory"]
|
readonly directory: { readonly strategy: string; readonly directory: string; readonly name?: string }["directory"]
|
||||||
readonly name?: { readonly strategy: string; readonly directory: string; readonly name?: string }["name"]
|
readonly name?: { readonly strategy: string; readonly directory: string; readonly name?: string }["name"]
|
||||||
@@ -4888,9 +4773,7 @@ export type ProjectCopyCreateOutput = ProjectCopyCopy
|
|||||||
|
|
||||||
export type ProjectCopyRemoveInput = {
|
export type ProjectCopyRemoveInput = {
|
||||||
readonly projectID: { readonly projectID: string }["projectID"]
|
readonly projectID: { readonly projectID: string }["projectID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly directory: { readonly directory: string; readonly force: boolean }["directory"]
|
readonly directory: { readonly directory: string; readonly force: boolean }["directory"]
|
||||||
readonly force: { readonly directory: string; readonly force: boolean }["force"]
|
readonly force: { readonly directory: string; readonly force: boolean }["force"]
|
||||||
}
|
}
|
||||||
@@ -4899,17 +4782,13 @@ export type ProjectCopyRemoveOutput = void
|
|||||||
|
|
||||||
export type ProjectCopyRefreshInput = {
|
export type ProjectCopyRefreshInput = {
|
||||||
readonly projectID: { readonly projectID: string }["projectID"]
|
readonly projectID: { readonly projectID: string }["projectID"]
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProjectCopyRefreshOutput = void
|
export type ProjectCopyRefreshOutput = void
|
||||||
|
|
||||||
export type VcsGetInput = {
|
export type VcsGetInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VcsGetOutput = {
|
export type VcsGetOutput = {
|
||||||
@@ -4918,9 +4797,7 @@ export type VcsGetOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type VcsStatusInput = {
|
export type VcsStatusInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VcsStatusOutput = {
|
export type VcsStatusOutput = {
|
||||||
@@ -4930,17 +4807,17 @@ export type VcsStatusOutput = {
|
|||||||
|
|
||||||
export type VcsDiffInput = {
|
export type VcsDiffInput = {
|
||||||
readonly location?: {
|
readonly location?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly mode: "working" | "branch"
|
readonly mode: "working" | "branch"
|
||||||
readonly context?: number | undefined
|
readonly context?: number | undefined
|
||||||
}["location"]
|
}["location"]
|
||||||
readonly mode: {
|
readonly mode: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly mode: "working" | "branch"
|
readonly mode: "working" | "branch"
|
||||||
readonly context?: number | undefined
|
readonly context?: number | undefined
|
||||||
}["mode"]
|
}["mode"]
|
||||||
readonly context?: {
|
readonly context?: {
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
readonly location?: { readonly directory?: string | undefined } | undefined
|
||||||
readonly mode: "working" | "branch"
|
readonly mode: "working" | "branch"
|
||||||
readonly context?: number | undefined
|
readonly context?: number | undefined
|
||||||
}["context"]
|
}["context"]
|
||||||
@@ -4954,17 +4831,13 @@ export type VcsDiffOutput = {
|
|||||||
export type DebugLocationListOutput = Array<LocationRef>
|
export type DebugLocationListOutput = Array<LocationRef>
|
||||||
|
|
||||||
export type DebugLocationEvictInput = {
|
export type DebugLocationEvictInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DebugLocationEvictOutput = void
|
export type DebugLocationEvictOutput = void
|
||||||
|
|
||||||
export type WebsearchProvidersInput = {
|
export type WebsearchProvidersInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WebsearchProvidersOutput = {
|
export type WebsearchProvidersOutput = {
|
||||||
@@ -4973,9 +4846,7 @@ export type WebsearchProvidersOutput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type WebsearchQueryInput = {
|
export type WebsearchQueryInput = {
|
||||||
readonly location?: {
|
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
|
||||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
|
||||||
}["location"]
|
|
||||||
readonly query: { readonly query: string; readonly providerID?: string }["query"]
|
readonly query: { readonly query: string; readonly providerID?: string }["query"]
|
||||||
readonly providerID?: { readonly query: string; readonly providerID?: string }["providerID"]
|
readonly providerID?: { readonly query: string; readonly providerID?: string }["providerID"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,13 +58,3 @@ export const setMethods = new Set([
|
|||||||
"isSupersetOf",
|
"isSupersetOf",
|
||||||
"isDisjointFrom",
|
"isDisjointFrom",
|
||||||
])
|
])
|
||||||
|
|
||||||
export const spreadItems = (value: unknown): Array<unknown> | undefined => {
|
|
||||||
if (Array.isArray(value)) return value
|
|
||||||
if (typeof value === "string") return Array.from(value)
|
|
||||||
if (value instanceof CodeModeMap) return Array.from(value.map.entries(), ([key, item]) => [key, item])
|
|
||||||
if (value instanceof CodeModeSet) return Array.from(value.set.values())
|
|
||||||
if (value instanceof CodeModeURLSearchParams) return Array.from(value.params.entries(), ([key, item]) => [key, item])
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
import { CodeModeMap, CodeModeSet, CodeModeURLSearchParams } from "../values.js"
|
|
||||||
|
|||||||
@@ -1021,13 +1021,12 @@ describe("OpenAPI.fromSpec", () => {
|
|||||||
|
|
||||||
await Effect.runPromise(
|
await Effect.runPromise(
|
||||||
location
|
location
|
||||||
.execute({ location: { directory: "/tmp", workspace: "workspace-1" } })
|
.execute({ location: { directory: "/tmp" } })
|
||||||
.pipe(Effect.provide(client.layer)),
|
.pipe(Effect.provide(client.layer)),
|
||||||
)
|
)
|
||||||
|
|
||||||
const url = new URL(client.requests[0]!.url)
|
const url = new URL(client.requests[0]!.url)
|
||||||
expect(url.searchParams.get("location[directory]")).toBe("/tmp")
|
expect(url.searchParams.get("location[directory]")).toBe("/tmp")
|
||||||
expect(url.searchParams.get("location[workspace]")).toBe("workspace-1")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("serializes supported simple and form parameter shapes", async () => {
|
test("serializes supported simple and form parameter shapes", async () => {
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
[data-component="desktop-promo"] {
|
|
||||||
--promo-background: hsl(0, 20%, 99%);
|
|
||||||
--promo-background-weak: hsl(0, 8%, 97%);
|
|
||||||
--promo-text: hsl(0, 1%, 39%);
|
|
||||||
--promo-text-strong: hsl(0, 5%, 12%);
|
|
||||||
--promo-border: hsla(0, 100%, 3%, 0.12);
|
|
||||||
|
|
||||||
position: fixed;
|
|
||||||
z-index: 20;
|
|
||||||
right: 1.5rem;
|
|
||||||
bottom: 1.5rem;
|
|
||||||
width: min(28rem, calc(100vw - 2rem));
|
|
||||||
padding: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
color: var(--promo-text);
|
|
||||||
border: 1px solid var(--promo-border);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--promo-background);
|
|
||||||
box-shadow: 0 0.75rem 2rem rgb(0 0 0 / 15%);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
--promo-background: hsl(0, 9%, 7%);
|
|
||||||
--promo-background-weak: hsl(0, 6%, 10%);
|
|
||||||
--promo-text: hsl(0, 4%, 71%);
|
|
||||||
--promo-text-strong: hsl(0, 15%, 94%);
|
|
||||||
--promo-border: hsl(0, 4%, 23%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 40rem) {
|
|
||||||
right: 1rem;
|
|
||||||
bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="desktop-promo-link"] {
|
|
||||||
display: block;
|
|
||||||
color: var(--promo-text);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="desktop-promo-link"]:focus-visible {
|
|
||||||
outline: 2px solid var(--promo-text-strong);
|
|
||||||
outline-offset: -3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: var(--promo-background-weak);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="desktop-promo-copy"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.35rem;
|
|
||||||
padding: 1rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="desktop-promo-copy"] strong {
|
|
||||||
color: var(--promo-text-strong);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="desktop-promo-close"] {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.5rem;
|
|
||||||
right: 0.5rem;
|
|
||||||
display: grid;
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
padding: 0;
|
|
||||||
place-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
background: rgb(0 0 0 / 70%);
|
|
||||||
opacity: 0;
|
|
||||||
transition:
|
|
||||||
opacity 150ms ease,
|
|
||||||
background 150ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover [data-slot="desktop-promo-close"],
|
|
||||||
[data-slot="desktop-promo-close"]:focus-visible {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="desktop-promo-close"]:hover {
|
|
||||||
background: rgb(0 0 0 / 90%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: none) {
|
|
||||||
[data-slot="desktop-promo-close"] {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import "./desktop-promo.css"
|
|
||||||
import { A, useLocation } from "@solidjs/router"
|
|
||||||
import { createSignal, Show } from "solid-js"
|
|
||||||
import { getRequestEvent } from "solid-js/web"
|
|
||||||
import desktopPromoVideo from "~/asset/lander/desktop-tabs-landscape.mp4"
|
|
||||||
import { useI18n } from "~/context/i18n"
|
|
||||||
import { useLanguage } from "~/context/language"
|
|
||||||
import { strip } from "~/lib/language"
|
|
||||||
|
|
||||||
const DISMISSED_COOKIE = "desktop_promo_dismissed"
|
|
||||||
|
|
||||||
export function DesktopPromo() {
|
|
||||||
const i18n = useI18n()
|
|
||||||
const language = useLanguage()
|
|
||||||
const location = useLocation()
|
|
||||||
const request = getRequestEvent()?.request
|
|
||||||
const cookie = request?.headers.get("cookie") ?? (typeof document === "object" ? document.cookie : "")
|
|
||||||
const [visible, setVisible] = createSignal(
|
|
||||||
!cookie.split(";").some((value) => value.trim() === `${DISMISSED_COOKIE}=1`),
|
|
||||||
)
|
|
||||||
const hostname = request ? new URL(request.url).hostname : typeof window === "object" ? window.location.hostname : ""
|
|
||||||
const primaryHost =
|
|
||||||
hostname === "opencode.ai" || hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Show
|
|
||||||
when={
|
|
||||||
visible() &&
|
|
||||||
primaryHost &&
|
|
||||||
strip(location.pathname) !== "/download" &&
|
|
||||||
!strip(location.pathname).startsWith("/download/")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<aside data-component="desktop-promo">
|
|
||||||
<A href={language.route("/download")} data-slot="desktop-promo-link">
|
|
||||||
<video src={desktopPromoVideo} autoplay playsinline loop muted preload="metadata" aria-hidden="true" />
|
|
||||||
<span data-slot="desktop-promo-copy">
|
|
||||||
<strong>{i18n.t("home.promo.title")}</strong>
|
|
||||||
<span>
|
|
||||||
{i18n.t("home.promo.body")} {i18n.t("home.promo.cta")}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</A>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-slot="desktop-promo-close"
|
|
||||||
onClick={() => {
|
|
||||||
document.cookie = `${DISMISSED_COOKIE}=1; Path=/; Max-Age=31536000; SameSite=Lax`
|
|
||||||
setVisible(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span class="sr-only">{i18n.t("home.promo.close")}</span>
|
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
||||||
<path d="M5 5L15 15M15 5L5 15" stroke="currentColor" stroke-width="1.5" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</aside>
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -19,10 +19,6 @@ export function Span({ children, ...props }: SpanProps) {
|
|||||||
return React.createElement("span", props, children)
|
return React.createElement("span", props, children)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Wbr({ children, ...props }: WbrProps) {
|
|
||||||
return React.createElement("wbr", props, children)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Fonts({ assetsUrl }: { assetsUrl: string }) {
|
export function Fonts({ assetsUrl }: { assetsUrl: string }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -59,14 +55,3 @@ export function Fonts({ assetsUrl }: { assetsUrl: string }) {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SplitString({ text, split }: { text: string; split: number }) {
|
|
||||||
const segments: JSX.Element[] = []
|
|
||||||
for (let i = 0; i < text.length; i += split) {
|
|
||||||
segments.push(<>{text.slice(i, i + split)}</>)
|
|
||||||
if (i + split < text.length) {
|
|
||||||
segments.push(<Wbr key={`${i}wbr`} />)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <>{segments}</>
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-110
@@ -1,15 +1,11 @@
|
|||||||
{
|
{
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "e43ed7e2-b9fc-4178-beae-3646e4a976e1",
|
"id": "6307bc18-2612-47e2-acfc-2a6a68ff28c5",
|
||||||
"prevIds": [
|
"prevIds": [
|
||||||
"db37a97f-9b5e-4c87-be8b-4feace35136c"
|
"e43ed7e2-b9fc-4178-beae-3646e4a976e1"
|
||||||
],
|
],
|
||||||
"ddl": [
|
"ddl": [
|
||||||
{
|
|
||||||
"name": "workspace",
|
|
||||||
"entityType": "tables"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "data_migration",
|
"name": "data_migration",
|
||||||
"entityType": "tables"
|
"entityType": "tables"
|
||||||
@@ -90,86 +86,6 @@
|
|||||||
"name": "session_share",
|
"name": "session_share",
|
||||||
"entityType": "tables"
|
"entityType": "tables"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "id",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "workspace"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "type",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "workspace"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": "''",
|
|
||||||
"generated": null,
|
|
||||||
"name": "name",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "workspace"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "branch",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "workspace"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "directory",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "workspace"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "extra",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "workspace"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "project_id",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "workspace"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "time_used",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "workspace"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -1590,21 +1506,6 @@
|
|||||||
"entityType": "columns",
|
"entityType": "columns",
|
||||||
"table": "session_share"
|
"table": "session_share"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"columns": [
|
|
||||||
"project_id"
|
|
||||||
],
|
|
||||||
"tableTo": "project",
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onUpdate": "NO ACTION",
|
|
||||||
"onDelete": "CASCADE",
|
|
||||||
"nameExplicit": false,
|
|
||||||
"name": "fk_workspace_project_id_project_id_fk",
|
|
||||||
"entityType": "fks",
|
|
||||||
"table": "workspace"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"columns": [
|
"columns": [
|
||||||
"active_account_id"
|
"active_account_id"
|
||||||
@@ -1815,15 +1716,6 @@
|
|||||||
"entityType": "pks",
|
"entityType": "pks",
|
||||||
"table": "instruction_entry"
|
"table": "instruction_entry"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"columns": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"nameExplicit": false,
|
|
||||||
"name": "workspace_pk",
|
|
||||||
"table": "workspace",
|
|
||||||
"entityType": "pks"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"columns": [
|
"columns": [
|
||||||
"name"
|
"name"
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ export type Draft = {
|
|||||||
|
|
||||||
export interface Interface extends State.Transformable<Draft> {
|
export interface Interface extends State.Transformable<Draft> {
|
||||||
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
||||||
readonly default: () => Effect.Effect<Info | undefined>
|
|
||||||
readonly resolve: (id?: ID | string) => Effect.Effect<Info | undefined>
|
readonly resolve: (id?: ID | string) => Effect.Effect<Info | undefined>
|
||||||
readonly select: (id?: ID | string) => Effect.Effect<Selection>
|
readonly select: (id?: ID | string) => Effect.Effect<Selection>
|
||||||
readonly list: () => Effect.Effect<Info[]>
|
readonly list: () => Effect.Effect<Info[]>
|
||||||
@@ -110,9 +109,6 @@ const layer = Layer.effect(
|
|||||||
get: Effect.fn("Agent.get")(function* (id) {
|
get: Effect.fn("Agent.get")(function* (id) {
|
||||||
return state.get().agents.get(id)
|
return state.get().agents.get(id)
|
||||||
}),
|
}),
|
||||||
default: Effect.fn("Agent.default")(function* () {
|
|
||||||
return selectedDefault()
|
|
||||||
}),
|
|
||||||
resolve: Effect.fn("Agent.resolve")(function* (id) {
|
resolve: Effect.fn("Agent.resolve")(function* (id) {
|
||||||
if (id !== undefined) return state.get().agents.get(ID.make(id))
|
if (id !== undefined) return state.get().agents.get(ID.make(id))
|
||||||
return selectedDefault()
|
return selectedDefault()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export * as Bus from "./bus"
|
|||||||
import { Cause, Context, DateTime, Effect, Layer, Option, PubSub, Schema, Stream } from "effect"
|
import { Cause, Context, DateTime, Effect, Layer, Option, PubSub, Schema, Stream } from "effect"
|
||||||
import { Event } from "@opencode-ai/schema/event"
|
import { Event } from "@opencode-ai/schema/event"
|
||||||
import type { EventLog } from "@opencode-ai/schema/event-log"
|
import type { EventLog } from "@opencode-ai/schema/event-log"
|
||||||
import { and, asc, eq, gt, inArray, lte, sql } from "drizzle-orm"
|
import { and, asc, eq, gt, lte, sql } from "drizzle-orm"
|
||||||
import { Database } from "./database/database"
|
import { Database } from "./database/database"
|
||||||
import { EventSequenceTable, EventTable } from "./event/sql"
|
import { EventSequenceTable, EventTable } from "./event/sql"
|
||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
@@ -134,8 +134,6 @@ export interface Interface {
|
|||||||
readonly after?: number
|
readonly after?: number
|
||||||
readonly follow?: boolean
|
readonly follow?: boolean
|
||||||
}) => Stream.Stream<LogItem>
|
}) => Stream.Stream<LogItem>
|
||||||
/** Latest committed seq per aggregate. Aggregates without events are absent. */
|
|
||||||
readonly sequences: (aggregateIDs: ReadonlyArray<string>) => Effect.Effect<ReadonlyMap<string, Event.Seq>>
|
|
||||||
/** @deprecated Use `subscribe()` and consume the returned stream. */
|
/** @deprecated Use `subscribe()` and consume the returned stream. */
|
||||||
readonly listen: (listener: Subscriber) => Effect.Effect<Unsubscribe>
|
readonly listen: (listener: Subscriber) => Effect.Effect<Unsubscribe>
|
||||||
readonly project: <D extends Event.Definition>(definition: D, projector: Subscriber<D>) => Effect.Effect<void>
|
readonly project: <D extends Event.Definition>(definition: D, projector: Subscriber<D>) => Effect.Effect<void>
|
||||||
@@ -657,19 +655,6 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const sequences = (aggregateIDs: ReadonlyArray<string>): Effect.Effect<ReadonlyMap<string, Event.Seq>> => {
|
|
||||||
if (aggregateIDs.length === 0) return Effect.succeed(new Map())
|
|
||||||
return db
|
|
||||||
.select({ aggregateID: EventSequenceTable.aggregate_id, seq: EventSequenceTable.seq })
|
|
||||||
.from(EventSequenceTable)
|
|
||||||
.where(inArray(EventSequenceTable.aggregate_id, Array.from(aggregateIDs)))
|
|
||||||
.all()
|
|
||||||
.pipe(
|
|
||||||
Effect.orDie,
|
|
||||||
Effect.map((rows) => new Map(rows.map((row) => [row.aggregateID, Event.Seq.make(row.seq)]))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const listen = (listener: Subscriber): Effect.Effect<Unsubscribe> =>
|
const listen = (listener: Subscriber): Effect.Effect<Unsubscribe> =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
listeners.push(listener)
|
listeners.push(listener)
|
||||||
@@ -691,7 +676,6 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
publish,
|
publish,
|
||||||
subscribe,
|
subscribe,
|
||||||
log,
|
log,
|
||||||
sequences,
|
|
||||||
listen,
|
listen,
|
||||||
project,
|
project,
|
||||||
replay,
|
replay,
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"
|
|
||||||
import { ProjectTable } from "../project/sql"
|
|
||||||
import { Project } from "../project"
|
|
||||||
import { Workspace } from "../workspace"
|
|
||||||
|
|
||||||
export const WorkspaceTable = sqliteTable("workspace", {
|
|
||||||
id: text().$type<Workspace.ID>().primaryKey(),
|
|
||||||
type: text().notNull(),
|
|
||||||
name: text().notNull().default(""),
|
|
||||||
branch: text(),
|
|
||||||
directory: text(),
|
|
||||||
extra: text({ mode: "json" }),
|
|
||||||
project_id: text()
|
|
||||||
.$type<Project.ID>()
|
|
||||||
.notNull()
|
|
||||||
.references(() => ProjectTable.id, { onDelete: "cascade" }),
|
|
||||||
time_used: integer()
|
|
||||||
.notNull()
|
|
||||||
.$default(() => Date.now()),
|
|
||||||
})
|
|
||||||
+1
@@ -59,5 +59,6 @@ export const migrations = (
|
|||||||
import("./migration/20260722170000_canonical_tool_results"),
|
import("./migration/20260722170000_canonical_tool_results"),
|
||||||
import("./migration/20260729022634_session_fork_boundary"),
|
import("./migration/20260729022634_session_fork_boundary"),
|
||||||
import("./migration/20260730195856_optional_session_title"),
|
import("./migration/20260730195856_optional_session_title"),
|
||||||
|
import("./migration/20260805225117_remove_workspace"),
|
||||||
])
|
])
|
||||||
).map((module) => module.default) satisfies DatabaseMigration.Migration[]
|
).map((module) => module.default) satisfies DatabaseMigration.Migration[]
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Effect } from "effect"
|
||||||
|
import type { DatabaseMigration } from "../migration"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
id: "20260805225117_remove_workspace",
|
||||||
|
up(tx) {
|
||||||
|
return Effect.gen(function* () {
|
||||||
|
yield* tx.run(`DROP TABLE \`workspace\`;`)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
} satisfies DatabaseMigration.Migration
|
||||||
@@ -4,19 +4,6 @@ import type { DatabaseMigration } from "./migration"
|
|||||||
export default {
|
export default {
|
||||||
up(tx) {
|
up(tx) {
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
yield* tx.run(`
|
|
||||||
CREATE TABLE \`workspace\` (
|
|
||||||
\`id\` text PRIMARY KEY,
|
|
||||||
\`type\` text NOT NULL,
|
|
||||||
\`name\` text DEFAULT '' NOT NULL,
|
|
||||||
\`branch\` text,
|
|
||||||
\`directory\` text,
|
|
||||||
\`extra\` text,
|
|
||||||
\`project_id\` text NOT NULL,
|
|
||||||
\`time_used\` integer NOT NULL,
|
|
||||||
CONSTRAINT \`fk_workspace_project_id_project_id_fk\` FOREIGN KEY (\`project_id\`) REFERENCES \`project\`(\`id\`) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
yield* tx.run(`
|
yield* tx.run(`
|
||||||
CREATE TABLE \`data_migration\` (
|
CREATE TABLE \`data_migration\` (
|
||||||
\`name\` text PRIMARY KEY,
|
\`name\` text PRIMARY KEY,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { FSUtil } from "@opencode-ai/util/fs-util"
|
|||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
import { PositiveInt, RelativePath } from "./schema"
|
import { PositiveInt, RelativePath } from "./schema"
|
||||||
import { FileSystemSearch } from "./filesystem/search"
|
import { FileSystemSearch } from "./filesystem/search"
|
||||||
import { Entry, FileSystem, FindInput, Match } from "@opencode-ai/schema/filesystem"
|
import { Entry, FileSystem, FindInput } from "@opencode-ai/schema/filesystem"
|
||||||
export { Entry, Match, Submatch } from "@opencode-ai/schema/filesystem"
|
export { Entry, Match, Submatch } from "@opencode-ai/schema/filesystem"
|
||||||
|
|
||||||
export const ReadInput = Schema.Struct({
|
export const ReadInput = Schema.Struct({
|
||||||
@@ -53,8 +53,6 @@ export interface Interface {
|
|||||||
readonly read: (input: ReadInput) => Effect.Effect<{ readonly content: Uint8Array; readonly mime: string }>
|
readonly read: (input: ReadInput) => Effect.Effect<{ readonly content: Uint8Array; readonly mime: string }>
|
||||||
readonly list: (input?: ListInput) => Effect.Effect<Entry[]>
|
readonly list: (input?: ListInput) => Effect.Effect<Entry[]>
|
||||||
readonly find: (input: FindInput) => Effect.Effect<Entry[]>
|
readonly find: (input: FindInput) => Effect.Effect<Entry[]>
|
||||||
readonly glob: (input: GlobInput) => Effect.Effect<readonly Entry[]>
|
|
||||||
readonly grep: (input: GrepInput) => Effect.Effect<readonly Match[]>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/FileSystem") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/FileSystem") {}
|
||||||
@@ -76,8 +74,6 @@ const baseLayer = Layer.effect(
|
|||||||
})
|
})
|
||||||
return Service.of({
|
return Service.of({
|
||||||
find: search.find,
|
find: search.find,
|
||||||
glob: search.glob,
|
|
||||||
grep: search.grep,
|
|
||||||
read: Effect.fn("FileSystem.read")(function* (input) {
|
read: Effect.fn("FileSystem.read")(function* (input) {
|
||||||
const target = yield* resolve(input.path)
|
const target = yield* resolve(input.path)
|
||||||
const info = yield* fs.stat(target.real).pipe(Effect.orDie)
|
const info = yield* fs.stat(target.real).pipe(Effect.orDie)
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import {
|
|||||||
type DirItem,
|
type DirItem,
|
||||||
type DirSearchResult,
|
type DirSearchResult,
|
||||||
type FileItem,
|
type FileItem,
|
||||||
type GrepCursor,
|
|
||||||
type GrepMatch,
|
|
||||||
type GrepResult,
|
|
||||||
type InitOptions,
|
type InitOptions,
|
||||||
type MixedItem,
|
type MixedItem,
|
||||||
type MixedSearchResult,
|
type MixedSearchResult,
|
||||||
@@ -45,19 +42,6 @@ export interface MixedSearch {
|
|||||||
export type File = FileItem
|
export type File = FileItem
|
||||||
export type Directory = DirItem
|
export type Directory = DirItem
|
||||||
export type Mixed = MixedItem
|
export type Mixed = MixedItem
|
||||||
export type Cursor = GrepCursor | null
|
|
||||||
export type Hit = GrepMatch
|
|
||||||
|
|
||||||
export interface Grep {
|
|
||||||
items: GrepResult["items"]
|
|
||||||
totalMatched: number
|
|
||||||
totalFilesSearched: number
|
|
||||||
totalFiles: number
|
|
||||||
filteredFileCount: number
|
|
||||||
nextCursor: Cursor
|
|
||||||
regexFallbackError?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Picker {
|
export interface Picker {
|
||||||
destroy(): void
|
destroy(): void
|
||||||
isScanning(): boolean
|
isScanning(): boolean
|
||||||
@@ -71,14 +55,6 @@ export interface Picker {
|
|||||||
pageSize?: number
|
pageSize?: number
|
||||||
},
|
},
|
||||||
): Result<Search>
|
): Result<Search>
|
||||||
glob(
|
|
||||||
pattern: string,
|
|
||||||
opts?: {
|
|
||||||
currentFile?: string
|
|
||||||
pageIndex?: number
|
|
||||||
pageSize?: number
|
|
||||||
},
|
|
||||||
): Result<Search>
|
|
||||||
directorySearch(
|
directorySearch(
|
||||||
query: string,
|
query: string,
|
||||||
opts?: {
|
opts?: {
|
||||||
@@ -95,18 +71,6 @@ export interface Picker {
|
|||||||
pageSize?: number
|
pageSize?: number
|
||||||
},
|
},
|
||||||
): Result<MixedSearch>
|
): Result<MixedSearch>
|
||||||
grep(
|
|
||||||
query: string,
|
|
||||||
opts?: {
|
|
||||||
mode?: "plain" | "regex" | "fuzzy"
|
|
||||||
maxMatchesPerFile?: number
|
|
||||||
timeBudgetMs?: number
|
|
||||||
beforeContext?: number
|
|
||||||
afterContext?: number
|
|
||||||
cursor?: Cursor
|
|
||||||
pageSize?: number
|
|
||||||
},
|
|
||||||
): Result<Grep>
|
|
||||||
trackQuery(query: string, file: string): Result<boolean>
|
trackQuery(query: string, file: string): Result<boolean>
|
||||||
getHistoricalQuery(offset: number): Result<string | null>
|
getHistoricalQuery(offset: number): Result<string | null>
|
||||||
}
|
}
|
||||||
@@ -127,10 +91,8 @@ export function create(opts: Init): Result<Picker> {
|
|||||||
waitForScan: (timeoutMs) => pick.waitForScan(timeoutMs),
|
waitForScan: (timeoutMs) => pick.waitForScan(timeoutMs),
|
||||||
refreshGitStatus: () => pick.refreshGitStatus(),
|
refreshGitStatus: () => pick.refreshGitStatus(),
|
||||||
fileSearch: (query, next) => pick.fileSearch(query, next),
|
fileSearch: (query, next) => pick.fileSearch(query, next),
|
||||||
glob: (pattern, next) => pick.glob(pattern, next),
|
|
||||||
directorySearch: (query, next) => pick.directorySearch(query, next),
|
directorySearch: (query, next) => pick.directorySearch(query, next),
|
||||||
mixedSearch: (query, next) => pick.mixedSearch(query, next),
|
mixedSearch: (query, next) => pick.mixedSearch(query, next),
|
||||||
grep: (query, next) => pick.grep(query, next),
|
|
||||||
trackQuery: (query, file) => pick.trackQuery(query, file),
|
trackQuery: (query, file) => pick.trackQuery(query, file),
|
||||||
getHistoricalQuery: (offset) => pick.getHistoricalQuery(offset),
|
getHistoricalQuery: (offset) => pick.getHistoricalQuery(offset),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ import type {
|
|||||||
DirItem,
|
DirItem,
|
||||||
DirSearchResult,
|
DirSearchResult,
|
||||||
FileItem,
|
FileItem,
|
||||||
GrepCursor,
|
|
||||||
GrepMatch,
|
|
||||||
GrepResult,
|
|
||||||
InitOptions,
|
InitOptions,
|
||||||
MixedItem,
|
MixedItem,
|
||||||
MixedSearchResult,
|
MixedSearchResult,
|
||||||
@@ -42,19 +39,6 @@ export interface MixedSearch {
|
|||||||
export type File = FileItem
|
export type File = FileItem
|
||||||
export type Directory = DirItem
|
export type Directory = DirItem
|
||||||
export type Mixed = MixedItem
|
export type Mixed = MixedItem
|
||||||
export type Cursor = GrepCursor | null
|
|
||||||
export type Hit = GrepMatch
|
|
||||||
|
|
||||||
export interface Grep {
|
|
||||||
items: GrepResult["items"]
|
|
||||||
totalMatched: number
|
|
||||||
totalFilesSearched: number
|
|
||||||
totalFiles: number
|
|
||||||
filteredFileCount: number
|
|
||||||
nextCursor: Cursor
|
|
||||||
regexFallbackError?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Picker {
|
export interface Picker {
|
||||||
destroy(): void
|
destroy(): void
|
||||||
isScanning(): boolean
|
isScanning(): boolean
|
||||||
@@ -68,14 +52,6 @@ export interface Picker {
|
|||||||
pageSize?: number
|
pageSize?: number
|
||||||
},
|
},
|
||||||
): Result<Search>
|
): Result<Search>
|
||||||
glob(
|
|
||||||
pattern: string,
|
|
||||||
opts?: {
|
|
||||||
currentFile?: string
|
|
||||||
pageIndex?: number
|
|
||||||
pageSize?: number
|
|
||||||
},
|
|
||||||
): Result<Search>
|
|
||||||
directorySearch(
|
directorySearch(
|
||||||
query: string,
|
query: string,
|
||||||
opts?: {
|
opts?: {
|
||||||
@@ -92,18 +68,6 @@ export interface Picker {
|
|||||||
pageSize?: number
|
pageSize?: number
|
||||||
},
|
},
|
||||||
): Result<MixedSearch>
|
): Result<MixedSearch>
|
||||||
grep(
|
|
||||||
query: string,
|
|
||||||
opts?: {
|
|
||||||
mode?: "plain" | "regex" | "fuzzy"
|
|
||||||
maxMatchesPerFile?: number
|
|
||||||
timeBudgetMs?: number
|
|
||||||
beforeContext?: number
|
|
||||||
afterContext?: number
|
|
||||||
cursor?: Cursor
|
|
||||||
pageSize?: number
|
|
||||||
},
|
|
||||||
): Result<Grep>
|
|
||||||
trackQuery(query: string, file: string): Result<boolean>
|
trackQuery(query: string, file: string): Result<boolean>
|
||||||
getHistoricalQuery(offset: number): Result<string | null>
|
getHistoricalQuery(offset: number): Result<string | null>
|
||||||
}
|
}
|
||||||
@@ -125,10 +89,8 @@ export function create(opts: Init): Result<Picker> {
|
|||||||
waitForScan: (timeoutMs) => pick.waitForScan(timeoutMs),
|
waitForScan: (timeoutMs) => pick.waitForScan(timeoutMs),
|
||||||
refreshGitStatus: () => pick.refreshGitStatus(),
|
refreshGitStatus: () => pick.refreshGitStatus(),
|
||||||
fileSearch: (query, next) => pick.fileSearch(query, next),
|
fileSearch: (query, next) => pick.fileSearch(query, next),
|
||||||
glob: (pattern, next) => pick.glob(pattern, next),
|
|
||||||
directorySearch: (query, next) => pick.directorySearch(query, next),
|
directorySearch: (query, next) => pick.directorySearch(query, next),
|
||||||
mixedSearch: (query, next) => pick.mixedSearch(query, next),
|
mixedSearch: (query, next) => pick.mixedSearch(query, next),
|
||||||
grep: (query, next) => pick.grep(query, next),
|
|
||||||
trackQuery: (query, file) => pick.trackQuery(query, file),
|
trackQuery: (query, file) => pick.trackQuery(query, file),
|
||||||
getHistoricalQuery: (offset) => pick.getHistoricalQuery(offset),
|
getHistoricalQuery: (offset) => pick.getHistoricalQuery(offset),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Glob } from "@opencode-ai/util/glob"
|
|
||||||
|
|
||||||
const FOLDERS = new Set([
|
const FOLDERS = new Set([
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"bower_components",
|
"bower_components",
|
||||||
@@ -47,21 +45,4 @@ const FILES = [
|
|||||||
|
|
||||||
export const PATTERNS = [...FILES, ...FOLDERS, `**/{${Array.from(FOLDERS).join(",")}}/**`]
|
export const PATTERNS = [...FILES, ...FOLDERS, `**/{${Array.from(FOLDERS).join(",")}}/**`]
|
||||||
|
|
||||||
export function match(filepath: string, opts?: { extra?: string[]; whitelist?: string[] }) {
|
|
||||||
for (const pattern of opts?.whitelist || []) {
|
|
||||||
if (Glob.match(pattern, filepath)) return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = filepath.split(/[/\\]/)
|
|
||||||
for (const part of parts) {
|
|
||||||
if (FOLDERS.has(part)) return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const pattern of [...FILES, ...(opts?.extra || [])]) {
|
|
||||||
if (Glob.match(pattern, filepath)) return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
export * as Ignore from "./ignore"
|
export * as Ignore from "./ignore"
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ export * as LocationWatcher from "./location-watcher"
|
|||||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||||
import { Context, Effect, Layer, Stream } from "effect"
|
import { Context, Effect, Layer, Stream } from "effect"
|
||||||
import { FileSystem } from "@opencode-ai/schema/filesystem"
|
import { FileSystem } from "@opencode-ai/schema/filesystem"
|
||||||
import os from "os"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Config } from "../config"
|
import { Config } from "../config"
|
||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
@@ -44,7 +43,7 @@ const layer = Layer.effect(
|
|||||||
const config = (yield* configService.entries())
|
const config = (yield* configService.entries())
|
||||||
.filter((entry): entry is Config.Document => entry.type === "document")
|
.filter((entry): entry is Config.Document => entry.type === "document")
|
||||||
.flatMap((item) => item.info.watcher?.ignore ?? [])
|
.flatMap((item) => item.info.watcher?.ignore ?? [])
|
||||||
const home = path.resolve(location.directory) === path.resolve(os.homedir())
|
const home = Protected.isHome(location.directory)
|
||||||
|
|
||||||
if (!home && location.vcs) {
|
if (!home && location.vcs) {
|
||||||
const updates = yield* watcher.subscribe({
|
const updates = yield* watcher.subscribe({
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import path from "path"
|
|||||||
|
|
||||||
const home = os.homedir()
|
const home = os.homedir()
|
||||||
|
|
||||||
|
export function isHome(directory: string) {
|
||||||
|
return path.resolve(directory) === path.resolve(home)
|
||||||
|
}
|
||||||
|
|
||||||
const DARWIN_HOME = [
|
const DARWIN_HOME = [
|
||||||
"Music",
|
"Music",
|
||||||
"Pictures",
|
"Pictures",
|
||||||
|
|||||||
@@ -6,15 +6,13 @@ import { Context, Effect, Layer, Schema, Scope } from "effect"
|
|||||||
import { Fff } from "#fff"
|
import { Fff } from "#fff"
|
||||||
import fuzzysort from "fuzzysort"
|
import fuzzysort from "fuzzysort"
|
||||||
import { FileSystem } from "../filesystem"
|
import { FileSystem } from "../filesystem"
|
||||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
|
||||||
import { Location } from "../location"
|
import { Location } from "../location"
|
||||||
import { Ripgrep } from "../ripgrep"
|
import { Ripgrep } from "../ripgrep"
|
||||||
import { RelativePath } from "../schema"
|
import { RelativePath } from "../schema"
|
||||||
|
import { Protected } from "./protected"
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly find: (input: FileSystem.FindInput) => Effect.Effect<FileSystem.Entry[]>
|
readonly find: (input: FileSystem.FindInput) => Effect.Effect<FileSystem.Entry[]>
|
||||||
readonly glob: (input: FileSystem.GlobInput) => Effect.Effect<readonly FileSystem.Entry[]>
|
|
||||||
readonly grep: (input: FileSystem.GrepInput) => Effect.Effect<readonly FileSystem.Match[]>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Options = Schema.Struct({
|
export const Options = Schema.Struct({
|
||||||
@@ -27,17 +25,18 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Fi
|
|||||||
export const ripgrepLayer = Layer.effect(
|
export const ripgrepLayer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* FSUtil.Service
|
|
||||||
const location = yield* Location.Service
|
const location = yield* Location.Service
|
||||||
const ripgrep = yield* Ripgrep.Service
|
const ripgrep = yield* Ripgrep.Service
|
||||||
const scope = yield* Scope.Scope
|
const scope = yield* Scope.Scope
|
||||||
const files: string[] = []
|
const files: string[] = []
|
||||||
const directories = new Set<string>()
|
const directories = new Set<string>()
|
||||||
|
const home = Protected.isHome(location.directory)
|
||||||
yield* ripgrep
|
yield* ripgrep
|
||||||
.find({
|
.find({
|
||||||
cwd: location.directory,
|
cwd: location.directory,
|
||||||
pattern: "*",
|
pattern: "*",
|
||||||
limit: location.vcs ? Number.MAX_SAFE_INTEGER : 100_000,
|
limit: location.vcs && !home ? Number.MAX_SAFE_INTEGER : 100_000,
|
||||||
|
exclude: home ? [...Protected.names()].map((name) => `${name}/**`) : undefined,
|
||||||
onEntry: (entry) =>
|
onEntry: (entry) =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
files.push(entry.path)
|
files.push(entry.path)
|
||||||
@@ -47,57 +46,6 @@ export const ripgrepLayer = Layer.effect(
|
|||||||
})
|
})
|
||||||
.pipe(Effect.orDie, Effect.asVoid, Effect.forkIn(scope))
|
.pipe(Effect.orDie, Effect.asVoid, Effect.forkIn(scope))
|
||||||
return Service.of({
|
return Service.of({
|
||||||
glob: (input) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const target = path.resolve(location.directory, input.path ?? ".")
|
|
||||||
const info = yield* fs.stat(target).pipe(Effect.orDie)
|
|
||||||
const cwd = info.type === "File" ? path.dirname(target) : target
|
|
||||||
return yield* ripgrep
|
|
||||||
.glob({
|
|
||||||
cwd,
|
|
||||||
pattern: input.pattern,
|
|
||||||
limit: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
Effect.map((result) =>
|
|
||||||
result.map((entry) =>
|
|
||||||
FileSystem.Entry.make({
|
|
||||||
...entry,
|
|
||||||
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Effect.orDie,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
grep: (input) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const target = path.resolve(location.directory, input.path ?? ".")
|
|
||||||
const info = yield* fs.stat(target).pipe(Effect.orDie)
|
|
||||||
const cwd = info.type === "File" ? path.dirname(target) : target
|
|
||||||
return yield* ripgrep
|
|
||||||
.grep({
|
|
||||||
cwd,
|
|
||||||
pattern: input.pattern,
|
|
||||||
file: info.type === "File" ? path.basename(target) : undefined,
|
|
||||||
include: input.include,
|
|
||||||
limit: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
Effect.map((result) =>
|
|
||||||
result.map((match) =>
|
|
||||||
FileSystem.Match.make({
|
|
||||||
...match,
|
|
||||||
entry: FileSystem.Entry.make({
|
|
||||||
...match.entry,
|
|
||||||
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, match.entry.path))),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Effect.orDie,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
find: (input) =>
|
find: (input) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const items =
|
const items =
|
||||||
@@ -139,55 +87,10 @@ export const fffLayer = Layer.effect(
|
|||||||
if (result) yield* Effect.logWarning("failed to initialize fff", { error: result.error })
|
if (result) yield* Effect.logWarning("failed to initialize fff", { error: result.error })
|
||||||
return Service.of({
|
return Service.of({
|
||||||
find: () => Effect.succeed([]),
|
find: () => Effect.succeed([]),
|
||||||
glob: () => Effect.succeed([]),
|
|
||||||
grep: () => Effect.succeed([]),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
yield* Effect.addFinalizer(() => Effect.sync(() => result.value.destroy()).pipe(Effect.ignore))
|
yield* Effect.addFinalizer(() => Effect.sync(() => result.value.destroy()).pipe(Effect.ignore))
|
||||||
return Service.of({
|
return Service.of({
|
||||||
glob: (input) =>
|
|
||||||
Effect.sync(() => {
|
|
||||||
const prefix = input.path?.replaceAll("\\", "/").replace(/\/$/, "")
|
|
||||||
const found = result.value.glob(prefix ? `${prefix}/${input.pattern}` : input.pattern, {
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
|
|
||||||
})
|
|
||||||
if (!found.ok) throw found.error
|
|
||||||
return found.value.items.map((item) =>
|
|
||||||
FileSystem.Entry.make({
|
|
||||||
path: RelativePath.make(item.relativePath.replaceAll("\\", "/")),
|
|
||||||
type: "file",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
grep: (input) =>
|
|
||||||
Effect.sync(() => {
|
|
||||||
const prefix = input.path?.replaceAll("\\", "/").replace(/\/$/, "")
|
|
||||||
const found = result.value.grep(
|
|
||||||
[prefix ? `${prefix}/**` : undefined, input.include, input.pattern]
|
|
||||||
.filter((value) => value !== undefined)
|
|
||||||
.join(" "),
|
|
||||||
{ mode: "regex", pageSize: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT, timeBudgetMs: 1_500 },
|
|
||||||
)
|
|
||||||
if (!found.ok) throw found.error
|
|
||||||
return found.value.items.map((match) => {
|
|
||||||
const bytes = Buffer.from(match.lineContent)
|
|
||||||
return FileSystem.Match.make({
|
|
||||||
entry: FileSystem.Entry.make({
|
|
||||||
path: RelativePath.make(match.relativePath.replaceAll("\\", "/")),
|
|
||||||
type: "file",
|
|
||||||
}),
|
|
||||||
line: match.lineNumber,
|
|
||||||
offset: match.byteOffset,
|
|
||||||
text: match.lineContent.length > 2_000 ? match.lineContent.slice(0, 2_000) + "..." : match.lineContent,
|
|
||||||
submatches: match.matchRanges.map(([start, end]) => ({
|
|
||||||
text: bytes.subarray(start, end).toString("utf8"),
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
find: (input) =>
|
find: (input) =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
const options = { pageIndex: 0, pageSize: input.limit ?? 50 }
|
const options = { pageIndex: 0, pageSize: input.limit ?? 50 }
|
||||||
@@ -232,18 +135,19 @@ export const fffLayer = Layer.effect(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const layer = (options?: Options) => Layer.unwrap(
|
export const layer = (options?: Options) =>
|
||||||
Effect.gen(function* () {
|
Layer.unwrap(
|
||||||
if (options?.fff === false || (options?.fff === undefined && process.platform === "win32") || !Fff.available())
|
Effect.gen(function* () {
|
||||||
return ripgrepLayer
|
if (options?.fff === false || (options?.fff === undefined && process.platform === "win32") || !Fff.available())
|
||||||
const location = yield* Location.Service
|
return ripgrepLayer
|
||||||
// Non-VCS locations can contain many repositories, so avoid eagerly content-indexing the entire aggregate tree.
|
const location = yield* Location.Service
|
||||||
return location.vcs ? fffLayer : ripgrepLayer
|
// Non-VCS locations can contain many repositories, so avoid eagerly content-indexing the entire aggregate tree.
|
||||||
}),
|
return location.vcs && !Protected.isHome(location.directory) ? fffLayer : ripgrepLayer
|
||||||
)
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
export function configured(options?: Options) {
|
export function configured(options?: Options) {
|
||||||
return makeLocationNode({ service: Service, layer: layer(options), deps: [FSUtil.node, Location.node, Ripgrep.node] })
|
return makeLocationNode({ service: Service, layer: layer(options), deps: [Location.node, Ripgrep.node] })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const node = configured()
|
export const node = configured()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export * as Formatter from "./formatter"
|
export * as Formatter from "./formatter"
|
||||||
|
|
||||||
import { Context, Effect, Layer, Schema } from "effect"
|
import { Context, Effect, Layer } from "effect"
|
||||||
import { ChildProcess } from "effect/unstable/process"
|
import { ChildProcess } from "effect/unstable/process"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||||
@@ -11,16 +11,7 @@ import { Config } from "./config"
|
|||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
import { make, type Info } from "./formatter/builtins"
|
import { make, type Info } from "./formatter/builtins"
|
||||||
|
|
||||||
export const Status = Schema.Struct({
|
|
||||||
name: Schema.String,
|
|
||||||
extensions: Schema.Array(Schema.String),
|
|
||||||
enabled: Schema.Boolean,
|
|
||||||
}).annotate({ identifier: "FormatterStatus" })
|
|
||||||
export type Status = typeof Status.Type
|
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly init: () => Effect.Effect<void>
|
|
||||||
readonly status: () => Effect.Effect<Status[]>
|
|
||||||
readonly file: (filepath: string) => Effect.Effect<boolean>
|
readonly file: (filepath: string) => Effect.Effect<boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,23 +75,6 @@ const layer = Layer.effect(
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
const init = Effect.fn("Formatter.init")(function* () {
|
|
||||||
yield* load
|
|
||||||
})
|
|
||||||
|
|
||||||
const status = Effect.fn("Formatter.status")(function* () {
|
|
||||||
yield* load
|
|
||||||
return yield* Effect.forEach(formatters, (formatter) =>
|
|
||||||
command(formatter).pipe(
|
|
||||||
Effect.map((enabled) => ({
|
|
||||||
name: formatter.name,
|
|
||||||
extensions: [...formatter.extensions],
|
|
||||||
enabled: enabled !== false,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const file = Effect.fn("Formatter.file")(function* (filepath: string) {
|
const file = Effect.fn("Formatter.file")(function* (filepath: string) {
|
||||||
yield* load
|
yield* load
|
||||||
const matching = formatters.filter((formatter) =>
|
const matching = formatters.filter((formatter) =>
|
||||||
@@ -143,7 +117,7 @@ const layer = Layer.effect(
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
return Service.of({ init, status, file })
|
return Service.of({ file })
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+1
-224
@@ -1,8 +1,7 @@
|
|||||||
export * as Git from "./git"
|
export * as Git from "./git"
|
||||||
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { randomUUID } from "crypto"
|
import { Context, Effect, Layer, Schema } from "effect"
|
||||||
import { Context, Effect, Layer, Schema, Stream } from "effect"
|
|
||||||
import { ChildProcess } from "effect/unstable/process"
|
import { ChildProcess } from "effect/unstable/process"
|
||||||
import { AbsolutePath, RelativePath } from "./schema"
|
import { AbsolutePath, RelativePath } from "./schema"
|
||||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||||
@@ -36,9 +35,6 @@ const snapshotConfig = `[core]
|
|||||||
threads = true
|
threads = true
|
||||||
`
|
`
|
||||||
|
|
||||||
export const ChangeSet = Schema.String.pipe(Schema.brand("Git.ChangeSet"))
|
|
||||||
export type ChangeSet = typeof ChangeSet.Type
|
|
||||||
|
|
||||||
export const TreeID = Schema.String.pipe(Schema.brand("Git.TreeID"))
|
export const TreeID = Schema.String.pipe(Schema.brand("Git.TreeID"))
|
||||||
export type TreeID = typeof TreeID.Type
|
export type TreeID = typeof TreeID.Type
|
||||||
|
|
||||||
@@ -73,13 +69,6 @@ export class WorktreeError extends Schema.TaggedErrorClass<WorktreeError>()("Git
|
|||||||
cause: Schema.optional(Schema.Defect()),
|
cause: Schema.optional(Schema.Defect()),
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export class PatchError extends Schema.TaggedErrorClass<PatchError>()("Git.PatchError", {
|
|
||||||
operation: Schema.Literals(["capture", "apply", "reset"]),
|
|
||||||
directory: AbsolutePath,
|
|
||||||
message: Schema.String,
|
|
||||||
cause: Schema.optional(Schema.Defect()),
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly repo: {
|
readonly repo: {
|
||||||
readonly discover: (input: AbsolutePath) => Effect.Effect<Repository | undefined>
|
readonly discover: (input: AbsolutePath) => Effect.Effect<Repository | undefined>
|
||||||
@@ -116,20 +105,6 @@ export interface Interface {
|
|||||||
) => Effect.Effect<void, OperationError>
|
) => Effect.Effect<void, OperationError>
|
||||||
readonly resetHard: (repository: Repository, revision: string) => Effect.Effect<void, OperationError>
|
readonly resetHard: (repository: Repository, revision: string) => Effect.Effect<void, OperationError>
|
||||||
}
|
}
|
||||||
readonly change: {
|
|
||||||
readonly capture: (input: { repository: Repository; path: AbsolutePath }) => Effect.Effect<ChangeSet, PatchError>
|
|
||||||
readonly apply: (input: {
|
|
||||||
repository: Repository
|
|
||||||
path: AbsolutePath
|
|
||||||
changes: ChangeSet
|
|
||||||
}) => Effect.Effect<void, PatchError>
|
|
||||||
readonly discard: (input: {
|
|
||||||
repository: Repository
|
|
||||||
path: AbsolutePath
|
|
||||||
index: "preserve" | "reset"
|
|
||||||
untracked: "preserve" | "remove"
|
|
||||||
}) => Effect.Effect<void, PatchError>
|
|
||||||
}
|
|
||||||
readonly worktree: {
|
readonly worktree: {
|
||||||
readonly create: (input: {
|
readonly create: (input: {
|
||||||
repository: Repository
|
repository: Repository
|
||||||
@@ -175,17 +150,10 @@ export interface Interface {
|
|||||||
context?: number
|
context?: number
|
||||||
paths?: readonly RelativePath[]
|
paths?: readonly RelativePath[]
|
||||||
}) => Effect.Effect<readonly File.Diff[], OperationError>
|
}) => Effect.Effect<readonly File.Diff[], OperationError>
|
||||||
readonly preview: (input: {
|
|
||||||
repository: Repository
|
|
||||||
current: TreeID
|
|
||||||
files: ReadonlyMap<RelativePath, TreeID>
|
|
||||||
context?: number
|
|
||||||
}) => Effect.Effect<readonly File.Diff[], OperationError>
|
|
||||||
readonly restore: (input: {
|
readonly restore: (input: {
|
||||||
repository: Repository
|
repository: Repository
|
||||||
files: ReadonlyMap<RelativePath, TreeID>
|
files: ReadonlyMap<RelativePath, TreeID>
|
||||||
}) => Effect.Effect<void, OperationError>
|
}) => Effect.Effect<void, OperationError>
|
||||||
readonly checkout: (input: { repository: Repository; tree: TreeID }) => Effect.Effect<void, OperationError>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,58 +625,6 @@ const layer = Layer.effect(
|
|||||||
return { mode: match[1], object: match[2] }
|
return { mode: match[1], object: match[2] }
|
||||||
})
|
})
|
||||||
|
|
||||||
const preview = Effect.fn("Git.tree.preview")(
|
|
||||||
(input: {
|
|
||||||
repository: Repository
|
|
||||||
current: TreeID
|
|
||||||
files: ReadonlyMap<RelativePath, TreeID>
|
|
||||||
context?: number
|
|
||||||
}) =>
|
|
||||||
locked(
|
|
||||||
input.repository,
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const index = path.join(input.repository.gitDirectory, `preview-${randomUUID()}.index`)
|
|
||||||
const env = { GIT_INDEX_FILE: index }
|
|
||||||
return yield* Effect.gen(function* () {
|
|
||||||
yield* repositoryOperation("diff", input.repository, ["read-tree", input.current], { env })
|
|
||||||
yield* Effect.forEach(
|
|
||||||
input.files,
|
|
||||||
([file, tree]) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const source = yield* entry(input.repository, tree, file)
|
|
||||||
if (!source) {
|
|
||||||
yield* repositoryOperation(
|
|
||||||
"diff",
|
|
||||||
input.repository,
|
|
||||||
["update-index", "--force-remove", "--", file],
|
|
||||||
{ env },
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
yield* repositoryOperation(
|
|
||||||
"diff",
|
|
||||||
input.repository,
|
|
||||||
["update-index", "--add", "--cacheinfo", source.mode, source.object, file],
|
|
||||||
{ env },
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
{ discard: true },
|
|
||||||
)
|
|
||||||
const target = TreeID.make(
|
|
||||||
(yield* repositoryOperation("diff", input.repository, ["write-tree"], { env })).text.trim(),
|
|
||||||
)
|
|
||||||
return yield* treeDiff({
|
|
||||||
repository: input.repository,
|
|
||||||
from: input.current,
|
|
||||||
to: target,
|
|
||||||
context: input.context,
|
|
||||||
paths: Array.from(input.files.keys()),
|
|
||||||
})
|
|
||||||
}).pipe(Effect.ensuring(fs.remove(index).pipe(Effect.catch(() => Effect.void))))
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const restore = Effect.fn("Git.tree.restore")(
|
const restore = Effect.fn("Git.tree.restore")(
|
||||||
(input: { repository: Repository; files: ReadonlyMap<RelativePath, TreeID> }) =>
|
(input: { repository: Repository; files: ReadonlyMap<RelativePath, TreeID> }) =>
|
||||||
locked(
|
locked(
|
||||||
@@ -738,142 +654,6 @@ const layer = Layer.effect(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const checkoutTree = Effect.fn("Git.tree.checkout")((input: { repository: Repository; tree: TreeID }) =>
|
|
||||||
locked(
|
|
||||||
input.repository,
|
|
||||||
Effect.gen(function* () {
|
|
||||||
yield* repositoryOperation("restore", input.repository, ["read-tree", input.tree])
|
|
||||||
yield* repositoryOperation("restore", input.repository, ["checkout-index", "--all", "--force"])
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const capture = Effect.fn("Git.change.capture")(function* (input: { repository: Repository; path: AbsolutePath }) {
|
|
||||||
const scope = path.relative(input.repository.worktree, input.path).replaceAll("\\", "/") || "."
|
|
||||||
const tracked = yield* execute(
|
|
||||||
input.repository.worktree,
|
|
||||||
proc,
|
|
||||||
)(["diff", "--binary", "HEAD", "--", scope]).pipe(
|
|
||||||
Effect.mapError(
|
|
||||||
(cause) => new PatchError({ operation: "capture", directory: input.path, message: cause.message, cause }),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (tracked.exitCode !== 0) {
|
|
||||||
return yield* new PatchError({
|
|
||||||
operation: "capture",
|
|
||||||
directory: input.path,
|
|
||||||
message: tracked.stderr.trim() || tracked.text.trim() || "Failed to capture tracked changes",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const untracked = yield* execute(
|
|
||||||
input.repository.worktree,
|
|
||||||
proc,
|
|
||||||
)(["ls-files", "--others", "--exclude-standard", "-z", "--", scope]).pipe(
|
|
||||||
Effect.mapError(
|
|
||||||
(cause) => new PatchError({ operation: "capture", directory: input.path, message: cause.message, cause }),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (untracked.exitCode !== 0) {
|
|
||||||
return yield* new PatchError({
|
|
||||||
operation: "capture",
|
|
||||||
directory: input.path,
|
|
||||||
message: untracked.stderr.trim() || untracked.text.trim() || "Failed to list untracked changes",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const created = yield* Effect.forEach(untracked.text.split("\0").filter(Boolean), (file) =>
|
|
||||||
execute(
|
|
||||||
input.repository.worktree,
|
|
||||||
proc,
|
|
||||||
)(["diff", "--binary", "--no-index", "--", "/dev/null", file]).pipe(
|
|
||||||
Effect.mapError(
|
|
||||||
(cause) => new PatchError({ operation: "capture", directory: input.path, message: cause.message, cause }),
|
|
||||||
),
|
|
||||||
Effect.flatMap((result) =>
|
|
||||||
// git diff --no-index returns 1 when differences were found.
|
|
||||||
result.exitCode === 0 || result.exitCode === 1
|
|
||||||
? Effect.succeed(result.text)
|
|
||||||
: Effect.fail(
|
|
||||||
new PatchError({
|
|
||||||
operation: "capture",
|
|
||||||
directory: input.path,
|
|
||||||
message:
|
|
||||||
result.stderr.trim() || result.text.trim() || `Failed to capture untracked change: ${file}`,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return ChangeSet.make([tracked.text, ...created].filter(Boolean).join("\n"))
|
|
||||||
})
|
|
||||||
|
|
||||||
const apply = Effect.fn("Git.change.apply")(function* (input: {
|
|
||||||
repository: Repository
|
|
||||||
path: AbsolutePath
|
|
||||||
changes: ChangeSet
|
|
||||||
}) {
|
|
||||||
const result = yield* proc
|
|
||||||
.run(
|
|
||||||
ChildProcess.make("git", ["apply", "-"], {
|
|
||||||
cwd: input.path,
|
|
||||||
extendEnv: true,
|
|
||||||
stdin: Stream.make(new TextEncoder().encode(input.changes)),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
Effect.mapError(
|
|
||||||
(cause) => new PatchError({ operation: "apply", directory: input.path, message: cause.message, cause }),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (result.exitCode === 0) return
|
|
||||||
return yield* new PatchError({
|
|
||||||
operation: "apply",
|
|
||||||
directory: input.path,
|
|
||||||
message:
|
|
||||||
result.stderr.toString("utf8").trim() || result.stdout.toString("utf8").trim() || "Failed to apply changes",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const discard = Effect.fn("Git.change.discard")(function* (input: {
|
|
||||||
repository: Repository
|
|
||||||
path: AbsolutePath
|
|
||||||
index: "preserve" | "reset"
|
|
||||||
untracked: "preserve" | "remove"
|
|
||||||
}) {
|
|
||||||
const scope = path.relative(input.repository.worktree, input.path).replaceAll("\\", "/") || "."
|
|
||||||
const restore = yield* execute(
|
|
||||||
input.repository.worktree,
|
|
||||||
proc,
|
|
||||||
)(input.index === "reset" ? ["checkout", "HEAD", "--", scope] : ["checkout", "--", scope]).pipe(
|
|
||||||
Effect.mapError(
|
|
||||||
(cause) => new PatchError({ operation: "reset", directory: input.path, message: cause.message, cause }),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (restore.exitCode !== 0) {
|
|
||||||
return yield* new PatchError({
|
|
||||||
operation: "reset",
|
|
||||||
directory: input.path,
|
|
||||||
message: restore.stderr.trim() || restore.text.trim() || "Failed to restore tracked changes",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (input.untracked === "preserve") return
|
|
||||||
const clean = yield* execute(
|
|
||||||
input.repository.worktree,
|
|
||||||
proc,
|
|
||||||
)(["clean", "-fd", "--", scope]).pipe(
|
|
||||||
Effect.mapError(
|
|
||||||
(cause) => new PatchError({ operation: "reset", directory: input.path, message: cause.message, cause }),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (clean.exitCode === 0) return
|
|
||||||
return yield* new PatchError({
|
|
||||||
operation: "reset",
|
|
||||||
directory: input.path,
|
|
||||||
message: clean.stderr.trim() || clean.text.trim() || "Failed to clean untracked changes",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const worktreeRun = Effect.fnUntraced(function* (
|
const worktreeRun = Effect.fnUntraced(function* (
|
||||||
operation: "create" | "remove" | "list",
|
operation: "create" | "remove" | "list",
|
||||||
repository: Repository,
|
repository: Repository,
|
||||||
@@ -949,7 +729,6 @@ const layer = Layer.effect(
|
|||||||
remote: { get: remote },
|
remote: { get: remote },
|
||||||
history: { head, branch, defaultRemoteBranch: remoteHead, rootCommits: roots },
|
history: { head, branch, defaultRemoteBranch: remoteHead, rootCommits: roots },
|
||||||
sync: { fetchRemotes: fetch, fetchBranch, checkoutRemoteBranch: checkout, resetHard: reset },
|
sync: { fetchRemotes: fetch, fetchBranch, checkoutRemoteBranch: checkout, resetHard: reset },
|
||||||
change: { capture, apply, discard },
|
|
||||||
worktree: { create: worktreeCreate, remove: worktreeRemove, list: worktreeList },
|
worktree: { create: worktreeCreate, remove: worktreeRemove, list: worktreeList },
|
||||||
index: { refresh, ignored },
|
index: { refresh, ignored },
|
||||||
tree: {
|
tree: {
|
||||||
@@ -957,9 +736,7 @@ const layer = Layer.effect(
|
|||||||
write: writeTree,
|
write: writeTree,
|
||||||
files: treeFiles,
|
files: treeFiles,
|
||||||
diff: treeDiff,
|
diff: treeDiff,
|
||||||
preview,
|
|
||||||
restore,
|
restore,
|
||||||
checkout: checkoutTree,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ const prefixes = {
|
|||||||
part: "prt",
|
part: "prt",
|
||||||
pty: "pty",
|
pty: "pty",
|
||||||
tool: "tool",
|
tool: "tool",
|
||||||
workspace: "wrk",
|
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function ascending(prefix: keyof typeof prefixes, given?: string) {
|
export function ascending(prefix: keyof typeof prefixes, given?: string) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { Credential } from "../credential"
|
|||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
import { Form } from "../form"
|
import { Form } from "../form"
|
||||||
import { Integration } from "../integration"
|
import { Integration } from "../integration"
|
||||||
import { IntegrationConnection } from "../integration/connection"
|
|
||||||
import { KeyedMutex } from "../effect/keyed-mutex"
|
import { KeyedMutex } from "../effect/keyed-mutex"
|
||||||
import { Location } from "../location"
|
import { Location } from "../location"
|
||||||
import { waitForAbort } from "@opencode-ai/util/process"
|
import { waitForAbort } from "@opencode-ai/util/process"
|
||||||
@@ -31,7 +30,6 @@ export class ServerInfo extends Schema.Class<ServerInfo>("MCP.ServerInfo")({
|
|||||||
name: ServerName,
|
name: ServerName,
|
||||||
status: Status,
|
status: Status,
|
||||||
integrationID: Integration.ID.pipe(Schema.optional),
|
integrationID: Integration.ID.pipe(Schema.optional),
|
||||||
connection: IntegrationConnection.Info.pipe(Schema.optional),
|
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export class ServerInstructions extends Schema.Class<ServerInstructions>("MCP.ServerInstructions")({
|
export class ServerInstructions extends Schema.Class<ServerInstructions>("MCP.ServerInstructions")({
|
||||||
@@ -247,14 +245,6 @@ export const layer = (options?: Options) => Layer.effect(
|
|||||||
return { name, entry }
|
return { name, entry }
|
||||||
})
|
})
|
||||||
|
|
||||||
const info = (name: ServerName, entry: ServerEntry, connection: IntegrationConnection.Info | undefined) =>
|
|
||||||
new ServerInfo({
|
|
||||||
name,
|
|
||||||
status: entry.status,
|
|
||||||
integrationID: entry.integrationID,
|
|
||||||
connection,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Builds the connect-time auth provider for a remote OAuth-integration server. The SDK presents and
|
// Builds the connect-time auth provider for a remote OAuth-integration server. The SDK presents and
|
||||||
// refreshes stored tokens, persisting refreshes back to the same credential row. The provider never
|
// refreshes stored tokens, persisting refreshes back to the same credential row. The provider never
|
||||||
// opens a browser, so an auth-gated connect ends in UnauthorizedError -> needs_auth rather than a redirect.
|
// opens a browser, so an auth-gated connect ends in UnauthorizedError -> needs_auth rather than a redirect.
|
||||||
@@ -603,15 +593,12 @@ export const layer = (options?: Options) => Layer.effect(
|
|||||||
)
|
)
|
||||||
return Service.of({
|
return Service.of({
|
||||||
servers: Effect.fn("MCP.servers")(function* () {
|
servers: Effect.fn("MCP.servers")(function* () {
|
||||||
const entries = Array.from(runtime).toSorted(([a], [b]) => a.localeCompare(b))
|
return Array.from(runtime)
|
||||||
return yield* Effect.forEach(entries, ([name, entry]) =>
|
.toSorted(([a], [b]) => a.localeCompare(b))
|
||||||
Effect.gen(function* () {
|
.map(
|
||||||
const connection = entry.integrationID
|
([name, entry]) =>
|
||||||
? yield* integration.connection.active(entry.integrationID)
|
new ServerInfo({ name, status: entry.status, integrationID: entry.integrationID }),
|
||||||
: undefined
|
)
|
||||||
return info(name, entry, connection)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
add: Effect.fn("MCP.add")(function* (server, config) {
|
add: Effect.fn("MCP.add")(function* (server, config) {
|
||||||
const name = ServerName.make(server)
|
const name = ServerName.make(server)
|
||||||
|
|||||||
@@ -59,16 +59,6 @@ export interface Interface {
|
|||||||
readonly list: () => Effect.Effect<ReadonlyArray<Info>>
|
readonly list: () => Effect.Effect<ReadonlyArray<Info>>
|
||||||
readonly directories: (input: DirectoriesInput) => Effect.Effect<Directories>
|
readonly directories: (input: DirectoriesInput) => Effect.Effect<Directories>
|
||||||
readonly resolve: (input: AbsolutePath) => Effect.Effect<Resolved>
|
readonly resolve: (input: AbsolutePath) => Effect.Effect<Resolved>
|
||||||
/**
|
|
||||||
* Temporary bridge method for writing the resolved project ID to the repo-local cache.
|
|
||||||
*
|
|
||||||
* This exists while the old opencode project service and this core project
|
|
||||||
* service work together: core resolves the ID, while the old service still owns
|
|
||||||
* database migration and persistence. The old service should call this after it
|
|
||||||
* finishes migrating from `resolve().previous` to `resolve().id`; once project
|
|
||||||
* persistence moves into core, this separate bridge method can go away.
|
|
||||||
*/
|
|
||||||
readonly commit: (input: { store: AbsolutePath; id: ID }) => Effect.Effect<void>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Project") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/Project") {}
|
||||||
@@ -268,11 +258,7 @@ const layer = Layer.effect(
|
|||||||
return yield* persist({ id: ID.global, directory, canonical: directory, vcs: undefined })
|
return yield* persist({ id: ID.global, directory, canonical: directory, vcs: undefined })
|
||||||
})
|
})
|
||||||
|
|
||||||
const commit = Effect.fn("Project.commit")(function* (input: { store: AbsolutePath; id: ID }) {
|
return Service.of({ list, directories, resolve })
|
||||||
yield* fs.writeFileString(path.join(input.store, "opencode"), input.id).pipe(Effect.ignore)
|
|
||||||
})
|
|
||||||
|
|
||||||
return Service.of({ list, directories, resolve, commit })
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -70,11 +70,6 @@ export class StrategyUnavailableError extends Schema.TaggedErrorClass<StrategyUn
|
|||||||
{ strategy: StrategyID },
|
{ strategy: StrategyID },
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
export class DuplicateStrategyError extends Schema.TaggedErrorClass<DuplicateStrategyError>()(
|
|
||||||
"ProjectCopy.DuplicateStrategyError",
|
|
||||||
{ strategy: StrategyID },
|
|
||||||
) {}
|
|
||||||
|
|
||||||
export type Error =
|
export type Error =
|
||||||
| SourceDirectoryNotFoundError
|
| SourceDirectoryNotFoundError
|
||||||
| DestinationExistsError
|
| DestinationExistsError
|
||||||
@@ -99,7 +94,6 @@ export interface Strategy {
|
|||||||
export { Event }
|
export { Event }
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly register: (strategy: Strategy) => Effect.Effect<void, DuplicateStrategyError>
|
|
||||||
readonly create: (input: CreateInput) => Effect.Effect<Copy, Error>
|
readonly create: (input: CreateInput) => Effect.Effect<Copy, Error>
|
||||||
readonly remove: (input: RemoveInput) => Effect.Effect<void, Error>
|
readonly remove: (input: RemoveInput) => Effect.Effect<void, Error>
|
||||||
readonly refresh: (input: RefreshInput) => Effect.Effect<RefreshResult, Error>
|
readonly refresh: (input: RefreshInput) => Effect.Effect<RefreshResult, Error>
|
||||||
@@ -144,29 +138,18 @@ const layer = Layer.effect(
|
|||||||
return resolved
|
return resolved
|
||||||
})
|
})
|
||||||
|
|
||||||
const registry = new Map<StrategyID, Strategy>()
|
const strategy = makeGitWorktreeStrategy({ git, canonical })
|
||||||
|
|
||||||
const register = Effect.fn("ProjectCopy.register")(function* (strategy: Strategy) {
|
|
||||||
if (registry.has(strategy.id)) return yield* new DuplicateStrategyError({ strategy: strategy.id })
|
|
||||||
registry.set(strategy.id, strategy)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Register default strategies
|
|
||||||
yield* register(makeGitWorktreeStrategy({ git, canonical })).pipe(Effect.orDie)
|
|
||||||
|
|
||||||
const strategies = () => Array.from(registry.values())
|
|
||||||
|
|
||||||
const source = Effect.fnUntraced(function* (input: AbsolutePath, projectID: Project.ID) {
|
const source = Effect.fnUntraced(function* (input: AbsolutePath, projectID: Project.ID) {
|
||||||
const sourceDirectory = yield* canonical(input)
|
const sourceDirectory = yield* canonical(input)
|
||||||
if (!(yield* directories.contains({ projectID, directory: sourceDirectory })))
|
if ((yield* directories.get({ projectID, directory: sourceDirectory })) === undefined)
|
||||||
return yield* new SourceDirectoryNotFoundError({ directory: sourceDirectory })
|
return yield* new SourceDirectoryNotFoundError({ directory: sourceDirectory })
|
||||||
return sourceDirectory
|
return sourceDirectory
|
||||||
})
|
})
|
||||||
|
|
||||||
const getStrategy = Effect.fnUntraced(function* (id: StrategyID) {
|
const getStrategy = Effect.fnUntraced(function* (id: StrategyID) {
|
||||||
const found = registry.get(id)
|
if (id !== strategy.id) return yield* new StrategyUnavailableError({ strategy: id })
|
||||||
if (!found) return yield* new StrategyUnavailableError({ strategy: id })
|
return strategy
|
||||||
return found
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const create = Effect.fn("ProjectCopy.create")(function* (input: CreateInput) {
|
const create = Effect.fn("ProjectCopy.create")(function* (input: CreateInput) {
|
||||||
@@ -226,20 +209,18 @@ const layer = Layer.effect(
|
|||||||
const discovered = yield* Effect.forEach(
|
const discovered = yield* Effect.forEach(
|
||||||
sourceDirectories,
|
sourceDirectories,
|
||||||
(sourceDirectory) =>
|
(sourceDirectory) =>
|
||||||
Effect.forEach(strategies(), (strategy) =>
|
strategy.list(sourceDirectory).pipe(
|
||||||
strategy.list(sourceDirectory).pipe(
|
Effect.catchTag("ProjectCopy.DirectoryUnavailableError", () => Effect.succeed([])),
|
||||||
Effect.catchTag("ProjectCopy.DirectoryUnavailableError", () => Effect.succeed([])),
|
Effect.map((items) =>
|
||||||
Effect.map((items) =>
|
items.map((item) => ({
|
||||||
items.map((item) => ({
|
directory: item.directory,
|
||||||
directory: item.directory,
|
strategy: item.type === "copy" ? strategy.id : undefined,
|
||||||
strategy: item.type === "copy" ? strategy.id : undefined,
|
})),
|
||||||
})),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
{ concurrency: "unbounded" },
|
{ concurrency: "unbounded" },
|
||||||
).pipe(
|
).pipe(
|
||||||
Effect.map((sets) => new Map(sets.flat(2).map((item) => [item.directory, item] as const)).values().toArray()),
|
Effect.map((sets) => new Map(sets.flat().map((item) => [item.directory, item] as const)).values().toArray()),
|
||||||
)
|
)
|
||||||
const removed = checked.filter((item) => !item.exists).map((item) => item.directory)
|
const removed = checked.filter((item) => !item.exists).map((item) => item.directory)
|
||||||
const result = yield* db
|
const result = yield* db
|
||||||
@@ -271,7 +252,6 @@ const layer = Layer.effect(
|
|||||||
})
|
})
|
||||||
|
|
||||||
return Service.of({
|
return Service.of({
|
||||||
register,
|
|
||||||
create,
|
create,
|
||||||
remove,
|
remove,
|
||||||
refresh,
|
refresh,
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ export interface Interface {
|
|||||||
projectID: ProjectSchema.ID
|
projectID: ProjectSchema.ID
|
||||||
directory: AbsolutePath
|
directory: AbsolutePath
|
||||||
}) => Effect.Effect<Directory | undefined>
|
}) => Effect.Effect<Directory | undefined>
|
||||||
readonly contains: (input: { projectID: ProjectSchema.ID; directory: AbsolutePath }) => Effect.Effect<boolean>
|
|
||||||
readonly create: (input: CreateInput, tx?: Transaction) => Effect.Effect<boolean>
|
readonly create: (input: CreateInput, tx?: Transaction) => Effect.Effect<boolean>
|
||||||
readonly remove: (input: RemoveInput, tx?: Transaction) => Effect.Effect<boolean>
|
readonly remove: (input: RemoveInput, tx?: Transaction) => Effect.Effect<boolean>
|
||||||
}
|
}
|
||||||
@@ -99,25 +98,6 @@ const layer = Layer.effect(
|
|||||||
return rows.map((row) => ({ directory: row.directory, strategy: row.strategy ?? undefined }))
|
return rows.map((row) => ({ directory: row.directory, strategy: row.strategy ?? undefined }))
|
||||||
})
|
})
|
||||||
|
|
||||||
const contains = Effect.fn("ProjectDirectories.contains")(function* (input: {
|
|
||||||
projectID: ProjectSchema.ID
|
|
||||||
directory: AbsolutePath
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
(yield* db
|
|
||||||
.select({ directory: ProjectDirectoryTable.directory })
|
|
||||||
.from(ProjectDirectoryTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(ProjectDirectoryTable.project_id, input.projectID),
|
|
||||||
eq(ProjectDirectoryTable.directory, input.directory),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.get()
|
|
||||||
.pipe(Effect.orDie)) !== undefined
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const get = Effect.fn("ProjectDirectories.get")(function* (input: {
|
const get = Effect.fn("ProjectDirectories.get")(function* (input: {
|
||||||
projectID: ProjectSchema.ID
|
projectID: ProjectSchema.ID
|
||||||
directory: AbsolutePath
|
directory: AbsolutePath
|
||||||
@@ -139,7 +119,6 @@ const layer = Layer.effect(
|
|||||||
return Service.of({
|
return Service.of({
|
||||||
list,
|
list,
|
||||||
get,
|
get,
|
||||||
contains,
|
|
||||||
create,
|
create,
|
||||||
remove,
|
remove,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export * as PtyTicket from "./ticket"
|
export * as PtyTicket from "./ticket"
|
||||||
|
|
||||||
import { Workspace } from "../workspace"
|
|
||||||
import { PtyTicket } from "@opencode-ai/schema/pty-ticket"
|
import { PtyTicket } from "@opencode-ai/schema/pty-ticket"
|
||||||
import { PtyID } from "./schema"
|
import { PtyID } from "./schema"
|
||||||
import { Cache, Context, Duration, Effect, Layer } from "effect"
|
import { Cache, Context, Duration, Effect, Layer } from "effect"
|
||||||
@@ -14,7 +13,6 @@ export const ConnectToken = PtyTicket.ConnectToken
|
|||||||
export type Scope = {
|
export type Scope = {
|
||||||
readonly ptyID: PtyID
|
readonly ptyID: PtyID
|
||||||
readonly directory?: string
|
readonly directory?: string
|
||||||
readonly workspaceID?: Workspace.ID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
@@ -25,9 +23,7 @@ export interface Interface {
|
|||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/PtyTicket") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/PtyTicket") {}
|
||||||
|
|
||||||
function matches(record: Scope, input: Scope) {
|
function matches(record: Scope, input: Scope) {
|
||||||
return (
|
return record.ptyID === input.ptyID && record.directory === input.directory
|
||||||
record.ptyID === input.ptyID && record.directory === input.directory && record.workspaceID === input.workspaceID
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tickets are inserted via Cache.set and removed atomically via invalidateWhen. The lookup is
|
// Tickets are inserted via Cache.set and removed atomically via invalidateWhen. The lookup is
|
||||||
|
|||||||
@@ -31,14 +31,6 @@ export type EnsureInput = {
|
|||||||
readonly branch?: string
|
readonly branch?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InvalidRepositoryError extends Schema.TaggedErrorClass<InvalidRepositoryError>()(
|
|
||||||
"RepositoryCacheInvalidRepositoryError",
|
|
||||||
{
|
|
||||||
repository: Schema.String,
|
|
||||||
message: Schema.String,
|
|
||||||
},
|
|
||||||
) {}
|
|
||||||
|
|
||||||
export class InvalidBranchError extends Schema.TaggedErrorClass<InvalidBranchError>()(
|
export class InvalidBranchError extends Schema.TaggedErrorClass<InvalidBranchError>()(
|
||||||
"RepositoryCacheInvalidBranchError",
|
"RepositoryCacheInvalidBranchError",
|
||||||
{
|
{
|
||||||
@@ -86,7 +78,6 @@ export class CacheOperationError extends Schema.TaggedErrorClass<CacheOperationE
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
export type Error =
|
export type Error =
|
||||||
| InvalidRepositoryError
|
|
||||||
| InvalidBranchError
|
| InvalidBranchError
|
||||||
| CloneFailedError
|
| CloneFailedError
|
||||||
| FetchFailedError
|
| FetchFailedError
|
||||||
@@ -103,7 +94,6 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Re
|
|||||||
|
|
||||||
export function isError(error: unknown): error is Error {
|
export function isError(error: unknown): error is Error {
|
||||||
return (
|
return (
|
||||||
error instanceof InvalidRepositoryError ||
|
|
||||||
error instanceof InvalidBranchError ||
|
error instanceof InvalidBranchError ||
|
||||||
error instanceof CloneFailedError ||
|
error instanceof CloneFailedError ||
|
||||||
error instanceof FetchFailedError ||
|
error instanceof FetchFailedError ||
|
||||||
@@ -114,13 +104,6 @@ export function isError(error: unknown): error is Error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseRemote = Effect.fn("RepositoryCache.parseRemote")(function* (repository: string) {
|
|
||||||
return yield* Effect.try({
|
|
||||||
try: () => Repository.parseRemote(repository),
|
|
||||||
catch: (error) => new InvalidRepositoryError({ repository, message: errorMessage(error) }),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(function* (branch: string) {
|
export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(function* (branch: string) {
|
||||||
return yield* Effect.try({
|
return yield* Effect.try({
|
||||||
try: () => Repository.validateBranch(branch),
|
try: () => Repository.validateBranch(branch),
|
||||||
|
|||||||
@@ -44,16 +44,6 @@ export class InvalidBranchError extends Schema.TaggedErrorClass<InvalidBranchErr
|
|||||||
message: Schema.String,
|
message: Schema.String,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export type Error = InvalidReferenceError | UnsupportedLocalRepositoryError | InvalidBranchError
|
|
||||||
|
|
||||||
export function isError(error: unknown): error is Error {
|
|
||||||
return (
|
|
||||||
error instanceof InvalidReferenceError ||
|
|
||||||
error instanceof UnsupportedLocalRepositoryError ||
|
|
||||||
error instanceof InvalidBranchError
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parse(input: string): Reference | undefined {
|
export function parse(input: string): Reference | undefined {
|
||||||
const cleaned = normalizeInput(input)
|
const cleaned = normalizeInput(input)
|
||||||
if (!cleaned) return
|
if (!cleaned) return
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export interface FindInput {
|
|||||||
readonly cwd: string
|
readonly cwd: string
|
||||||
readonly pattern: string
|
readonly pattern: string
|
||||||
readonly limit: number
|
readonly limit: number
|
||||||
|
readonly exclude?: readonly string[]
|
||||||
readonly hidden?: boolean
|
readonly hidden?: boolean
|
||||||
readonly follow?: boolean
|
readonly follow?: boolean
|
||||||
readonly signal?: AbortSignal
|
readonly signal?: AbortSignal
|
||||||
@@ -195,6 +196,7 @@ const layer = Layer.effect(
|
|||||||
...(input.hidden ? ["--hidden"] : []),
|
...(input.hidden ? ["--hidden"] : []),
|
||||||
...(input.follow ? ["--follow"] : []),
|
...(input.follow ? ["--follow"] : []),
|
||||||
...(input.pattern === "*" ? [] : [`--glob=${input.pattern}`]),
|
...(input.pattern === "*" ? [] : [`--glob=${input.pattern}`]),
|
||||||
|
...(input.exclude ?? []).map((pattern) => `--glob=!${pattern}`),
|
||||||
"--glob=!**/.git/**",
|
"--glob=!**/.git/**",
|
||||||
".",
|
".",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Effect, Layer, Schema, Context, Stream, Scope } from "effect"
|
|||||||
import { ListAnchor } from "@opencode-ai/schema/session"
|
import { ListAnchor } from "@opencode-ai/schema/session"
|
||||||
import { and, asc, desc, eq, gt, isNotNull, isNull, like, lt, ne, or, type SQL } from "drizzle-orm"
|
import { and, asc, desc, eq, gt, isNotNull, isNull, like, lt, ne, or, type SQL } from "drizzle-orm"
|
||||||
import { Project } from "./project"
|
import { Project } from "./project"
|
||||||
import { Workspace } from "./workspace"
|
|
||||||
import { Model } from "./model"
|
import { Model } from "./model"
|
||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
import { SessionMessage } from "./session/message"
|
import { SessionMessage } from "./session/message"
|
||||||
@@ -52,9 +51,6 @@ import { Shell as ShellSchema } from "@opencode-ai/schema/shell"
|
|||||||
import { KeyedMutex } from "./effect/keyed-mutex"
|
import { KeyedMutex } from "./effect/keyed-mutex"
|
||||||
import { fileURLToPath } from "url"
|
import { fileURLToPath } from "url"
|
||||||
|
|
||||||
export const RevertState = Session.Revert
|
|
||||||
export type RevertState = Session.Revert
|
|
||||||
|
|
||||||
// get project -> project.locations
|
// get project -> project.locations
|
||||||
//
|
//
|
||||||
// get all sessions
|
// get all sessions
|
||||||
@@ -62,12 +58,9 @@ export type RevertState = Session.Revert
|
|||||||
|
|
||||||
// - by project
|
// - by project
|
||||||
// - by subpath
|
// - by subpath
|
||||||
// - by workspace (home is special)
|
|
||||||
|
|
||||||
export { ListAnchor }
|
export { ListAnchor }
|
||||||
|
|
||||||
const ListInputBase = {
|
const ListInputBase = {
|
||||||
workspaceID: Workspace.ID.pipe(Schema.optional),
|
|
||||||
search: Schema.String.pipe(Schema.optional),
|
search: Schema.String.pipe(Schema.optional),
|
||||||
limit: PositiveInt.pipe(Schema.optional),
|
limit: PositiveInt.pipe(Schema.optional),
|
||||||
order: Schema.Literals(["asc", "desc"]).pipe(Schema.optional),
|
order: Schema.Literals(["asc", "desc"]).pipe(Schema.optional),
|
||||||
@@ -110,13 +103,6 @@ type ForkInput = {
|
|||||||
boundary: Session.ForkRequestBoundary
|
boundary: Session.ForkRequestBoundary
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OperationUnavailableError extends Schema.TaggedErrorClass<OperationUnavailableError>()(
|
|
||||||
"Session.OperationUnavailableError",
|
|
||||||
{
|
|
||||||
operation: Schema.Literals(["move", "skill", "switchAgent", "compact"]),
|
|
||||||
},
|
|
||||||
) {}
|
|
||||||
|
|
||||||
export { MessageDecodeError, NotFoundError }
|
export { MessageDecodeError, NotFoundError }
|
||||||
|
|
||||||
export class PromptConflictError extends Schema.TaggedErrorClass<PromptConflictError>()("Session.PromptConflictError", {
|
export class PromptConflictError extends Schema.TaggedErrorClass<PromptConflictError>()("Session.PromptConflictError", {
|
||||||
@@ -160,23 +146,6 @@ export class DestinationNotDirectoryError extends Schema.TaggedErrorClass<Destin
|
|||||||
export const MessageNotFoundError = SessionRevert.MessageNotFoundError
|
export const MessageNotFoundError = SessionRevert.MessageNotFoundError
|
||||||
export type MessageNotFoundError = SessionRevert.MessageNotFoundError
|
export type MessageNotFoundError = SessionRevert.MessageNotFoundError
|
||||||
|
|
||||||
export type Error =
|
|
||||||
| NotFoundError
|
|
||||||
| MessageDecodeError
|
|
||||||
| OperationUnavailableError
|
|
||||||
| PromptConflictError
|
|
||||||
| SyntheticConflictError
|
|
||||||
| AttachmentError
|
|
||||||
| CompactionConflictError
|
|
||||||
| BusyError
|
|
||||||
| SkillNotFoundError
|
|
||||||
| DestinationNotFoundError
|
|
||||||
| DestinationNotDirectoryError
|
|
||||||
| Command.NotFoundError
|
|
||||||
| Command.EvaluationError
|
|
||||||
| MessageNotFoundError
|
|
||||||
| SessionGenerate.Error
|
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly list: (input?: ListInput) => Effect.Effect<{
|
readonly list: (input?: ListInput) => Effect.Effect<{
|
||||||
readonly data: SessionSchema.Info[]
|
readonly data: SessionSchema.Info[]
|
||||||
@@ -233,7 +202,6 @@ export interface Interface {
|
|||||||
readonly move: (input: {
|
readonly move: (input: {
|
||||||
sessionID: SessionSchema.ID
|
sessionID: SessionSchema.ID
|
||||||
directory: AbsolutePath
|
directory: AbsolutePath
|
||||||
workspaceID?: Location.Ref["workspaceID"]
|
|
||||||
}) => Effect.Effect<void, NotFoundError | DestinationNotFoundError | DestinationNotDirectoryError>
|
}) => Effect.Effect<void, NotFoundError | DestinationNotFoundError | DestinationNotDirectoryError>
|
||||||
readonly prompt: (input: {
|
readonly prompt: (input: {
|
||||||
id?: SessionMessage.ID
|
id?: SessionMessage.ID
|
||||||
@@ -373,7 +341,6 @@ const layer = Layer.effect(
|
|||||||
parentID: input.parentID,
|
parentID: input.parentID,
|
||||||
directory: location.directory,
|
directory: location.directory,
|
||||||
path: path.relative(project.directory, location.directory).replaceAll("\\", "/"),
|
path: path.relative(project.directory, location.directory).replaceAll("\\", "/"),
|
||||||
workspaceID: location.workspaceID ? Workspace.ID.make(location.workspaceID) : undefined,
|
|
||||||
title: input.title,
|
title: input.title,
|
||||||
agent: input.agent,
|
agent: input.agent,
|
||||||
model: input.model
|
model: input.model
|
||||||
@@ -461,7 +428,6 @@ const layer = Layer.effect(
|
|||||||
const sortColumn = SessionTable.time_updated
|
const sortColumn = SessionTable.time_updated
|
||||||
const conditions: SQL[] = []
|
const conditions: SQL[] = []
|
||||||
if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory))
|
if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory))
|
||||||
if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
|
|
||||||
if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project))
|
if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project))
|
||||||
if ("project" in input && input.subpath !== undefined) conditions.push(eq(SessionTable.path, input.subpath))
|
if ("project" in input && input.subpath !== undefined) conditions.push(eq(SessionTable.path, input.subpath))
|
||||||
if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
|
if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
|
||||||
@@ -739,11 +705,7 @@ const layer = Layer.effect(
|
|||||||
const info = yield* fs.stat(directory).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
const info = yield* fs.stat(directory).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||||
if (!info) return yield* new DestinationNotFoundError({ directory })
|
if (!info) return yield* new DestinationNotFoundError({ directory })
|
||||||
if (info.type !== "Directory") return yield* new DestinationNotDirectoryError({ directory })
|
if (info.type !== "Directory") return yield* new DestinationNotDirectoryError({ directory })
|
||||||
if (
|
if (current.location.directory === directory) return
|
||||||
current.location.directory === directory &&
|
|
||||||
current.location.workspaceID === input.workspaceID
|
|
||||||
)
|
|
||||||
return
|
|
||||||
const project = yield* projects.resolve(directory)
|
const project = yield* projects.resolve(directory)
|
||||||
yield* persistProject(project)
|
yield* persistProject(project)
|
||||||
if ((yield* execution.active).has(input.sessionID)) {
|
if ((yield* execution.active).has(input.sessionID)) {
|
||||||
@@ -752,7 +714,7 @@ const layer = Layer.effect(
|
|||||||
}
|
}
|
||||||
yield* bus.publish(SessionEvent.Moved, {
|
yield* bus.publish(SessionEvent.Moved, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
location: Location.Ref.make({ directory, workspaceID: input.workspaceID }),
|
location: Location.Ref.make({ directory }),
|
||||||
projectID: project.id,
|
projectID: project.id,
|
||||||
subpath: RelativePath.make(path.relative(project.directory, directory).replaceAll("\\", "/")),
|
subpath: RelativePath.make(path.relative(project.directory, directory).replaceAll("\\", "/")),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { castDraft, produce, type WritableDraft } from "immer"
|
import { castDraft, produce, type WritableDraft } from "immer"
|
||||||
import { DateTime, Effect } from "effect"
|
import { DateTime, Effect, Match, pipe } from "effect"
|
||||||
import { SessionEvent } from "./event"
|
import { SessionEvent } from "./event"
|
||||||
import { SessionMessage } from "./message"
|
import { SessionMessage } from "./message"
|
||||||
|
|
||||||
export type MemoryState = {
|
|
||||||
messages: SessionMessage.Info[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Adapter {
|
export interface Adapter {
|
||||||
readonly getModel: () => Effect.Effect<SessionMessage.ModelSelected["model"] | undefined, never, never>
|
readonly getModel: () => Effect.Effect<SessionMessage.ModelSelected["model"] | undefined, never, never>
|
||||||
readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined, never, never>
|
readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined, never, never>
|
||||||
@@ -23,89 +19,7 @@ export interface Adapter {
|
|||||||
readonly appendMessage: (message: SessionMessage.Info) => Effect.Effect<void, never, never>
|
readonly appendMessage: (message: SessionMessage.Info) => Effect.Effect<void, never, never>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function memory(state: MemoryState): Adapter {
|
export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||||
const assistantIndex = (messageID: SessionMessage.ID) =>
|
|
||||||
state.messages.findLastIndex((message) => message.id === messageID)
|
|
||||||
const shellIndex = (messageID: SessionMessage.ID) =>
|
|
||||||
state.messages.findLastIndex((message) => message.id === messageID)
|
|
||||||
const compactionIndex = () =>
|
|
||||||
state.messages.findLastIndex((message) => message.type === "compaction" && message.status === "running")
|
|
||||||
// A newer step supersedes stale incomplete rows; never resume an older assistant projection.
|
|
||||||
const latestAssistantIndex = () => state.messages.findLastIndex((message) => message.type === "assistant")
|
|
||||||
|
|
||||||
return {
|
|
||||||
getModel() {
|
|
||||||
return Effect.sync(
|
|
||||||
() =>
|
|
||||||
state.messages.findLast(
|
|
||||||
(message): message is SessionMessage.ModelSelected | SessionMessage.Assistant =>
|
|
||||||
message.type === "model-switched" || message.type === "assistant",
|
|
||||||
)?.model,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
getCurrentAssistant() {
|
|
||||||
return Effect.sync(() => {
|
|
||||||
const index = latestAssistantIndex()
|
|
||||||
if (index < 0) return
|
|
||||||
const assistant = state.messages[index]
|
|
||||||
return assistant?.type === "assistant" && !assistant.time.completed ? assistant : undefined
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getAssistant(messageID) {
|
|
||||||
return Effect.sync(() => {
|
|
||||||
const index = assistantIndex(messageID)
|
|
||||||
if (index < 0) return
|
|
||||||
const assistant = state.messages[index]
|
|
||||||
return assistant?.type === "assistant" ? assistant : undefined
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getShell(shellID) {
|
|
||||||
return Effect.sync(() => {
|
|
||||||
return state.messages.find((message): message is SessionMessage.Shell => {
|
|
||||||
return message.type === "shell" && message.shellID === shellID
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getCompaction() {
|
|
||||||
return Effect.sync(() => {
|
|
||||||
const index = compactionIndex()
|
|
||||||
const message = state.messages[index]
|
|
||||||
return message?.type === "compaction" ? message : undefined
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateAssistant(assistant) {
|
|
||||||
return Effect.sync(() => {
|
|
||||||
const index = assistantIndex(assistant.id)
|
|
||||||
if (index < 0) return
|
|
||||||
const current = state.messages[index]
|
|
||||||
if (current?.type !== "assistant") return
|
|
||||||
state.messages[index] = assistant
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateShell(shell) {
|
|
||||||
return Effect.sync(() => {
|
|
||||||
const index = shellIndex(shell.id)
|
|
||||||
if (index < 0) return
|
|
||||||
const current = state.messages[index]
|
|
||||||
if (current?.type !== "shell") return
|
|
||||||
state.messages[index] = shell
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateCompaction(compaction) {
|
|
||||||
return Effect.sync(() => {
|
|
||||||
const index = state.messages.findLastIndex((message) => message.id === compaction.id)
|
|
||||||
if (index >= 0) state.messages[index] = compaction
|
|
||||||
})
|
|
||||||
},
|
|
||||||
appendMessage(message) {
|
|
||||||
return Effect.sync(() => {
|
|
||||||
state.messages.push(message)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|
||||||
type DraftAssistant = WritableDraft<SessionMessage.Assistant>
|
type DraftAssistant = WritableDraft<SessionMessage.Assistant>
|
||||||
type DraftTool = WritableDraft<SessionMessage.AssistantTool>
|
type DraftTool = WritableDraft<SessionMessage.AssistantTool>
|
||||||
type DraftText = WritableDraft<SessionMessage.AssistantText>
|
type DraftText = WritableDraft<SessionMessage.AssistantText>
|
||||||
@@ -139,9 +53,9 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return Effect.gen(function* () {
|
const project = pipe(
|
||||||
yield* SessionEvent.All.match(event, {
|
Match.type<SessionEvent.DurableEvent>(),
|
||||||
"session.usage.updated": () => Effect.void,
|
Match.discriminatorsExhaustive("type")({
|
||||||
"session.usage.recorded": () => Effect.void,
|
"session.usage.recorded": () => Effect.void,
|
||||||
"session.agent.selected": (event) => {
|
"session.agent.selected": (event) => {
|
||||||
return adapter.appendMessage(
|
return adapter.appendMessage(
|
||||||
@@ -321,12 +235,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||||||
draft.content.push(castDraft(SessionMessage.AssistantText.make({ type: "text", text: "" })))
|
draft.content.push(castDraft(SessionMessage.AssistantText.make({ type: "text", text: "" })))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
"session.text.delta": (event) => {
|
|
||||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
|
||||||
const match = latestText(draft)
|
|
||||||
if (match) match.text += event.data.delta
|
|
||||||
})
|
|
||||||
},
|
|
||||||
"session.text.ended": (event) => {
|
"session.text.ended": (event) => {
|
||||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||||
const match = latestText(draft)
|
const match = latestText(draft)
|
||||||
@@ -351,7 +259,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
"session.tool.input.delta": () => Effect.void,
|
|
||||||
"session.tool.input.ended": (event) => {
|
"session.tool.input.ended": (event) => {
|
||||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||||
const match = latestTool(draft, event.data.id)
|
const match = latestTool(draft, event.data.id)
|
||||||
@@ -375,14 +282,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
"session.tool.progress": (event) => {
|
|
||||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
|
||||||
const match = latestTool(draft, event.data.id)
|
|
||||||
if (match && match.state.status === "running") {
|
|
||||||
match.state.metadata = event.data.metadata
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// Terminal tool events are self-contained; projection is a direct copy and
|
// Terminal tool events are self-contained; projection is a direct copy and
|
||||||
// never reaches into ephemeral progress history.
|
// never reaches into ephemeral progress history.
|
||||||
"session.tool.success": (event) => {
|
"session.tool.success": (event) => {
|
||||||
@@ -436,12 +335,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
"session.reasoning.delta": (event) => {
|
|
||||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
|
||||||
const match = latestReasoning(draft)
|
|
||||||
if (match) match.text += event.data.delta
|
|
||||||
})
|
|
||||||
},
|
|
||||||
"session.reasoning.ended": (event) => {
|
"session.reasoning.ended": (event) => {
|
||||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||||
const match = latestReasoning(draft)
|
const match = latestReasoning(draft)
|
||||||
@@ -475,12 +368,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||||||
time: { created: event.created },
|
time: { created: event.created },
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
"session.compaction.delta": (event) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const current = yield* adapter.getCompaction()
|
|
||||||
if (current?.status !== "running") return
|
|
||||||
yield* adapter.updateCompaction({ ...current, summary: current.summary + event.data.text })
|
|
||||||
}),
|
|
||||||
"session.compaction.ended": (event) => {
|
"session.compaction.ended": (event) => {
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
const current = yield* adapter.getCompaction()
|
const current = yield* adapter.getCompaction()
|
||||||
@@ -526,8 +413,9 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||||||
"session.revert.staged": () => Effect.void,
|
"session.revert.staged": () => Effect.void,
|
||||||
"session.revert.cleared": () => Effect.void,
|
"session.revert.cleared": () => Effect.void,
|
||||||
"session.revert.committed": () => Effect.void,
|
"session.revert.committed": () => Effect.void,
|
||||||
})
|
}),
|
||||||
})
|
)
|
||||||
|
return project(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as SessionMessageUpdater from "./message-updater"
|
export * as SessionMessageUpdater from "./message-updater"
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
|
|||||||
import { Model } from "../model"
|
import { Model } from "../model"
|
||||||
import { SessionEvent } from "./event"
|
import { SessionEvent } from "./event"
|
||||||
import { SessionV1 } from "../v1/session"
|
import { SessionV1 } from "../v1/session"
|
||||||
import { WorkspaceTable } from "../control-plane/workspace.sql"
|
|
||||||
import { SessionMessage } from "./message"
|
import { SessionMessage } from "./message"
|
||||||
import { SessionMessageUpdater } from "./message-updater"
|
import { SessionMessageUpdater } from "./message-updater"
|
||||||
import { SessionPending } from "./pending"
|
import { SessionPending } from "./pending"
|
||||||
import { Workspace } from "../workspace"
|
|
||||||
import { InstructionState } from "./instruction-state"
|
import { InstructionState } from "./instruction-state"
|
||||||
import { MessageTable, PartTable, SessionPendingTable, SessionMessageTable, SessionTable } from "./sql"
|
import { MessageTable, PartTable, SessionPendingTable, SessionMessageTable, SessionTable } from "./sql"
|
||||||
import type { DeepMutable } from "../schema"
|
import type { DeepMutable } from "../schema"
|
||||||
@@ -59,7 +57,7 @@ function sessionRow(info: SessionV1.SessionInfo): typeof SessionTable.$inferInse
|
|||||||
return {
|
return {
|
||||||
id: info.id,
|
id: info.id,
|
||||||
project_id: info.projectID,
|
project_id: info.projectID,
|
||||||
workspace_id: info.workspaceID ?? null,
|
workspace_id: null,
|
||||||
parent_id: info.parentID,
|
parent_id: info.parentID,
|
||||||
slug: info.slug,
|
slug: info.slug,
|
||||||
directory: info.directory,
|
directory: info.directory,
|
||||||
@@ -429,14 +427,6 @@ const layer = Layer.effectDiscard(
|
|||||||
.get()
|
.get()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
if (!stored) return yield* Effect.die(new SessionAlreadyProjected())
|
if (!stored) return yield* Effect.die(new SessionAlreadyProjected())
|
||||||
if (event.data.info.workspaceID) {
|
|
||||||
yield* db
|
|
||||||
.update(WorkspaceTable)
|
|
||||||
.set({ time_used: Date.now() })
|
|
||||||
.where(eq(WorkspaceTable.id, event.data.info.workspaceID))
|
|
||||||
.run()
|
|
||||||
.pipe(Effect.orDie)
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
yield* bus.project(SessionV1.Event.Updated, (event) =>
|
yield* bus.project(SessionV1.Event.Updated, (event) =>
|
||||||
@@ -455,7 +445,7 @@ const layer = Layer.effectDiscard(
|
|||||||
directory: event.data.location.directory,
|
directory: event.data.location.directory,
|
||||||
path: event.data.subpath,
|
path: event.data.subpath,
|
||||||
...(event.data.projectID ? { project_id: event.data.projectID } : {}),
|
...(event.data.projectID ? { project_id: event.data.projectID } : {}),
|
||||||
workspace_id: event.data.location.workspaceID ? Workspace.ID.make(event.data.location.workspaceID) : null,
|
workspace_id: null,
|
||||||
time_updated: DateTime.toEpochMillis(event.created),
|
time_updated: DateTime.toEpochMillis(event.created),
|
||||||
})
|
})
|
||||||
.where(eq(SessionTable.id, event.data.sessionID))
|
.where(eq(SessionTable.id, event.data.sessionID))
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
export * as ShellSelect from "./select"
|
export * as ShellSelect from "./select"
|
||||||
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { spawn, type ChildProcess } from "child_process"
|
|
||||||
import { readFile } from "fs/promises"
|
import { readFile } from "fs/promises"
|
||||||
import { statSync } from "fs"
|
import { statSync } from "fs"
|
||||||
import { setTimeout } from "node:timers/promises"
|
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||||
import { which } from "../util/which"
|
import { which } from "../util/which"
|
||||||
|
|
||||||
const SIGKILL_TIMEOUT_MS = 200
|
const META: Record<string, { deny?: boolean; login?: boolean; ps?: boolean }> = {
|
||||||
const META: Record<string, { deny?: boolean; login?: boolean; posix?: boolean; ps?: boolean }> = {
|
bash: { login: true },
|
||||||
bash: { login: true, posix: true },
|
dash: { login: true },
|
||||||
dash: { login: true, posix: true },
|
|
||||||
fish: { deny: true, login: true },
|
fish: { deny: true, login: true },
|
||||||
ksh: { login: true, posix: true },
|
ksh: { login: true },
|
||||||
nu: { deny: true },
|
nu: { deny: true },
|
||||||
powershell: { ps: true },
|
powershell: { ps: true },
|
||||||
pwsh: { ps: true },
|
pwsh: { ps: true },
|
||||||
sh: { login: true, posix: true },
|
sh: { login: true },
|
||||||
zsh: { login: true, posix: true },
|
zsh: { login: true },
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Item = {
|
export type Item = {
|
||||||
@@ -33,37 +30,6 @@ export const Options = Schema.Struct({
|
|||||||
})
|
})
|
||||||
export type Options = typeof Options.Type
|
export type Options = typeof Options.Type
|
||||||
|
|
||||||
export async function killTree(proc: ChildProcess, opts?: { exited?: () => boolean }): Promise<void> {
|
|
||||||
const pid = proc.pid
|
|
||||||
if (!pid || opts?.exited?.()) return
|
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
await new Promise<void>((resolve) => {
|
|
||||||
const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], {
|
|
||||||
stdio: "ignore",
|
|
||||||
windowsHide: true,
|
|
||||||
})
|
|
||||||
killer.once("exit", () => resolve())
|
|
||||||
killer.once("error", () => resolve())
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
process.kill(-pid, "SIGTERM")
|
|
||||||
await setTimeout(SIGKILL_TIMEOUT_MS)
|
|
||||||
if (!opts?.exited?.()) {
|
|
||||||
process.kill(-pid, "SIGKILL")
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
proc.kill("SIGTERM")
|
|
||||||
await setTimeout(SIGKILL_TIMEOUT_MS)
|
|
||||||
if (!opts?.exited?.()) {
|
|
||||||
proc.kill("SIGKILL")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stat(file: string) {
|
function stat(file: string) {
|
||||||
return statSync(file, { throwIfNoEntry: false }) ?? undefined
|
return statSync(file, { throwIfNoEntry: false }) ?? undefined
|
||||||
}
|
}
|
||||||
@@ -150,10 +116,6 @@ export function login(file: string) {
|
|||||||
return meta(file)?.login === true
|
return meta(file)?.login === true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function posix(file: string) {
|
|
||||||
return meta(file)?.posix === true
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ps(file: string) {
|
export function ps(file: string) {
|
||||||
return meta(file)?.ps === true
|
return meta(file)?.ps === true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { Hash } from "@opencode-ai/util/hash"
|
|||||||
export { ID }
|
export { ID }
|
||||||
|
|
||||||
export class Error extends Schema.TaggedErrorClass<Error>()("Snapshot.Error", {
|
export class Error extends Schema.TaggedErrorClass<Error>()("Snapshot.Error", {
|
||||||
operation: Schema.Literals(["capture", "files", "diff", "preview", "restore"]),
|
operation: Schema.Literals(["capture", "files", "diff", "restore"]),
|
||||||
message: Schema.String,
|
message: Schema.String,
|
||||||
cause: Schema.optional(Schema.Defect()),
|
cause: Schema.optional(Schema.Defect()),
|
||||||
}) {}
|
}) {}
|
||||||
@@ -36,10 +36,6 @@ export interface RestoreInput {
|
|||||||
readonly files: ReadonlyMap<RelativePath, ID>
|
readonly files: ReadonlyMap<RelativePath, ID>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreviewInput extends RestoreInput {
|
|
||||||
readonly context?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
/**
|
/**
|
||||||
* Capture the current Location-scoped filesystem state as a content-addressed
|
* Capture the current Location-scoped filesystem state as a content-addressed
|
||||||
@@ -60,25 +56,11 @@ export interface Interface {
|
|||||||
*/
|
*/
|
||||||
readonly diff: (input: DiffInput) => Effect.Effect<readonly File.Diff[], Error>
|
readonly diff: (input: DiffInput) => Effect.Effect<readonly File.Diff[], Error>
|
||||||
|
|
||||||
/**
|
|
||||||
* Preview the filesystem result of a selective restore without modifying the
|
|
||||||
* worktree. Each project-relative path maps to the tree it would be restored
|
|
||||||
* from.
|
|
||||||
*/
|
|
||||||
readonly preview: (input: PreviewInput) => Effect.Effect<readonly File.Diff[], Error>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore selected project-relative paths from their associated trees. A path
|
* Restore selected project-relative paths from their associated trees. A path
|
||||||
* absent from its selected tree is removed; paths outside the map are untouched.
|
* absent from its selected tree is removed; paths outside the map are untouched.
|
||||||
*/
|
*/
|
||||||
readonly restore: (input: RestoreInput) => Effect.Effect<void, Error>
|
readonly restore: (input: RestoreInput) => Effect.Effect<void, Error>
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace the snapshot index with a captured tree and check out all its entries.
|
|
||||||
* Files absent from the tree remain untouched. Prefer selective `restore` when
|
|
||||||
* only known paths should change.
|
|
||||||
*/
|
|
||||||
readonly checkout: (snapshot: ID) => Effect.Effect<void, Error>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Snapshot") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/Snapshot") {}
|
||||||
@@ -176,59 +158,26 @@ const layer = Layer.effect(
|
|||||||
.pipe(Effect.mapError((cause) => failure("diff", cause)))
|
.pipe(Effect.mapError((cause) => failure("diff", cause)))
|
||||||
})
|
})
|
||||||
|
|
||||||
const plan = Effect.fnUntraced(function* (
|
const plan = Effect.fnUntraced(function* (worktree: AbsolutePath, input: RestoreInput) {
|
||||||
operation: "preview" | "restore",
|
|
||||||
worktree: AbsolutePath,
|
|
||||||
input: RestoreInput,
|
|
||||||
) {
|
|
||||||
const files = new Map<RelativePath, Git.TreeID>()
|
const files = new Map<RelativePath, Git.TreeID>()
|
||||||
for (const [file, snapshot] of input.files) {
|
for (const [file, snapshot] of input.files) {
|
||||||
const absolute = path.resolve(worktree, file)
|
const absolute = path.resolve(worktree, file)
|
||||||
if (!FSUtil.contains(worktree, absolute))
|
if (!FSUtil.contains(worktree, absolute))
|
||||||
return yield* new Error({ operation, message: `Path escapes the project: ${file}` })
|
return yield* new Error({ operation: "restore", message: `Path escapes the project: ${file}` })
|
||||||
files.set(file, Git.TreeID.make(snapshot))
|
files.set(file, Git.TreeID.make(snapshot))
|
||||||
}
|
}
|
||||||
return files
|
return files
|
||||||
})
|
})
|
||||||
|
|
||||||
const preview = Effect.fn("Snapshot.preview")(function* (input: PreviewInput) {
|
|
||||||
if (!(yield* enabled())) return yield* new Error({ operation: "preview", message: "Snapshots are disabled" })
|
|
||||||
const repo = yield* repository.pipe(Effect.mapError((cause) => failure("preview", cause)))
|
|
||||||
const files = yield* plan("preview", repo.worktree, input)
|
|
||||||
const current = yield* git.tree
|
|
||||||
.capture({
|
|
||||||
repository: repo.snapshotRepository,
|
|
||||||
scopes: Array.from(files.keys()),
|
|
||||||
ignores: repo.source,
|
|
||||||
maximumUntrackedFileBytes: 2 * 1024 * 1024,
|
|
||||||
})
|
|
||||||
.pipe(Effect.mapError((cause) => failure("preview", cause)))
|
|
||||||
return yield* git.tree
|
|
||||||
.preview({
|
|
||||||
repository: repo.snapshotRepository,
|
|
||||||
current,
|
|
||||||
files,
|
|
||||||
context: input.context,
|
|
||||||
})
|
|
||||||
.pipe(Effect.mapError((cause) => failure("preview", cause)))
|
|
||||||
})
|
|
||||||
|
|
||||||
const restore = Effect.fn("Snapshot.restore")(function* (input: RestoreInput) {
|
const restore = Effect.fn("Snapshot.restore")(function* (input: RestoreInput) {
|
||||||
if (!(yield* enabled())) return yield* new Error({ operation: "restore", message: "Snapshots are disabled" })
|
if (!(yield* enabled())) return yield* new Error({ operation: "restore", message: "Snapshots are disabled" })
|
||||||
const repo = yield* repository.pipe(Effect.mapError((cause) => failure("restore", cause)))
|
const repo = yield* repository.pipe(Effect.mapError((cause) => failure("restore", cause)))
|
||||||
yield* git.tree
|
yield* git.tree
|
||||||
.restore({ repository: repo.snapshotRepository, files: yield* plan("restore", repo.worktree, input) })
|
.restore({ repository: repo.snapshotRepository, files: yield* plan(repo.worktree, input) })
|
||||||
.pipe(Effect.mapError((cause) => failure("restore", cause)))
|
.pipe(Effect.mapError((cause) => failure("restore", cause)))
|
||||||
})
|
})
|
||||||
|
|
||||||
const checkout = Effect.fn("Snapshot.checkout")(function* (snapshot: ID) {
|
return Service.of({ capture, files, diff, restore })
|
||||||
const repo = yield* repository.pipe(Effect.mapError((cause) => failure("restore", cause)))
|
|
||||||
yield* git.tree
|
|
||||||
.checkout({ repository: repo.snapshotRepository, tree: Git.TreeID.make(snapshot) })
|
|
||||||
.pipe(Effect.mapError((cause) => failure("restore", cause)))
|
|
||||||
})
|
|
||||||
|
|
||||||
return Service.of({ capture, files, diff, preview, restore, checkout })
|
|
||||||
}).pipe(Effect.withSpan("Snapshot.boot")),
|
}).pipe(Effect.withSpan("Snapshot.boot")),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -244,9 +193,7 @@ export const noopLayer = Layer.succeed(
|
|||||||
capture: () => Effect.succeed(undefined),
|
capture: () => Effect.succeed(undefined),
|
||||||
files: () => Effect.succeed([]),
|
files: () => Effect.succeed([]),
|
||||||
diff: () => Effect.succeed([]),
|
diff: () => Effect.succeed([]),
|
||||||
preview: () => Effect.succeed([]),
|
|
||||||
restore: () => Effect.void,
|
restore: () => Effect.void,
|
||||||
checkout: () => Effect.void,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1298,24 +1298,4 @@ describe("Bus", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("sequences returns the latest committed seq per aggregate and omits unknown aggregates", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const bus = yield* Bus.Service
|
|
||||||
const first = Session.ID.create()
|
|
||||||
const second = Session.ID.create()
|
|
||||||
yield* bus.publish(DurableMessage, durableData(first, "zero"))
|
|
||||||
yield* bus.publish(DurableMessage, durableData(first, "one"))
|
|
||||||
yield* bus.publish(DurableMessage, durableData(second, "zero"))
|
|
||||||
|
|
||||||
const sequences = yield* bus.sequences([first, second, Session.ID.create()])
|
|
||||||
|
|
||||||
expect(sequences).toEqual(
|
|
||||||
new Map([
|
|
||||||
[first, Event.Seq.make(1)],
|
|
||||||
[second, Event.Seq.make(0)],
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
expect(yield* bus.sequences([])).toEqual(new Map())
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -967,9 +967,6 @@ describe("DatabaseMigration", () => {
|
|||||||
yield* db.run(
|
yield* db.run(
|
||||||
sql`INSERT INTO project (id, worktree, time_created, time_updated, sandboxes) VALUES ('global', '/project', 1, 1, '[]')`,
|
sql`INSERT INTO project (id, worktree, time_created, time_updated, sandboxes) VALUES ('global', '/project', 1, 1, '[]')`,
|
||||||
)
|
)
|
||||||
yield* db.run(
|
|
||||||
sql`INSERT INTO workspace (id, type, project_id, time_used) VALUES ('workspace', 'local', 'global', 1)`,
|
|
||||||
)
|
|
||||||
yield* db.run(
|
yield* db.run(
|
||||||
sql`INSERT INTO session (id, project_id, workspace_id, slug, directory, title, version, time_created, time_updated) VALUES ('session', 'global', 'workspace', 'session', '/project', 'Before', 'test', 1, 1)`,
|
sql`INSERT INTO session (id, project_id, workspace_id, slug, directory, title, version, time_created, time_updated) VALUES ('session', 'global', 'workspace', 'session', '/project', 'Before', 'test', 1, 1)`,
|
||||||
)
|
)
|
||||||
@@ -994,6 +991,7 @@ describe("DatabaseMigration", () => {
|
|||||||
// must drop before the historical rename dance and recreate after.
|
// must drop before the historical rename dance and recreate after.
|
||||||
yield* db.run(sql`DROP INDEX session_pending_session_compaction_idx`)
|
yield* db.run(sql`DROP INDEX session_pending_session_compaction_idx`)
|
||||||
yield* db.run(sql`ALTER TABLE session_pending RENAME TO session_input`)
|
yield* db.run(sql`ALTER TABLE session_pending RENAME TO session_input`)
|
||||||
|
yield* db.run(sql`CREATE TABLE workspace (id text PRIMARY KEY)`)
|
||||||
yield* db.run(sql`DELETE FROM migration WHERE id = ${simplifySessionPendingMigration.id}`)
|
yield* db.run(sql`DELETE FROM migration WHERE id = ${simplifySessionPendingMigration.id}`)
|
||||||
yield* DatabaseMigration.applyOnly(db, [simplifySessionPendingMigration])
|
yield* DatabaseMigration.applyOnly(db, [simplifySessionPendingMigration])
|
||||||
yield* db.run(sql`DROP TABLE session_context_epoch`)
|
yield* db.run(sql`DROP TABLE session_context_epoch`)
|
||||||
@@ -1029,7 +1027,6 @@ describe("DatabaseMigration", () => {
|
|||||||
(SELECT workspace_id FROM session WHERE id = 'session') AS workspaceID,
|
(SELECT workspace_id FROM session WHERE id = 'session') AS workspaceID,
|
||||||
(SELECT COUNT(*) FROM message WHERE id = 'message') AS messages,
|
(SELECT COUNT(*) FROM message WHERE id = 'message') AS messages,
|
||||||
(SELECT COUNT(*) FROM part WHERE id = 'part') AS parts,
|
(SELECT COUNT(*) FROM part WHERE id = 'part') AS parts,
|
||||||
(SELECT COUNT(*) FROM workspace) AS workspaces,
|
|
||||||
(SELECT COUNT(*) FROM session_pending) AS sessionInputs,
|
(SELECT COUNT(*) FROM session_pending) AS sessionInputs,
|
||||||
(SELECT COUNT(*) FROM session_message) AS sessionMessages,
|
(SELECT COUNT(*) FROM session_message) AS sessionMessages,
|
||||||
(SELECT COUNT(*) FROM instruction_state) AS instructionStates,
|
(SELECT COUNT(*) FROM instruction_state) AS instructionStates,
|
||||||
@@ -1041,7 +1038,6 @@ describe("DatabaseMigration", () => {
|
|||||||
workspaceID: null,
|
workspaceID: null,
|
||||||
messages: 1,
|
messages: 1,
|
||||||
parts: 1,
|
parts: 1,
|
||||||
workspaces: 0,
|
|
||||||
sessionInputs: 0,
|
sessionInputs: 0,
|
||||||
sessionMessages: 0,
|
sessionMessages: 0,
|
||||||
instructionStates: 0,
|
instructionStates: 0,
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ describe("node build", () => {
|
|||||||
list: () => Effect.succeed([]),
|
list: () => Effect.succeed([]),
|
||||||
directories: () => Effect.succeed([]),
|
directories: () => Effect.succeed([]),
|
||||||
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
||||||
commit: () => Effect.void,
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,14 +3,6 @@ import { Ignore } from "@opencode-ai/core/filesystem/ignore"
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { createWrapper } from "@parcel/watcher/wrapper"
|
import { createWrapper } from "@parcel/watcher/wrapper"
|
||||||
|
|
||||||
test("match nested and non-nested", () => {
|
|
||||||
expect(Ignore.match("node_modules/index.js")).toBe(true)
|
|
||||||
expect(Ignore.match("node_modules")).toBe(true)
|
|
||||||
expect(Ignore.match("node_modules/")).toBe(true)
|
|
||||||
expect(Ignore.match("node_modules/bar")).toBe(true)
|
|
||||||
expect(Ignore.match("node_modules/bar/")).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("parcel patterns ignore built-in folders at any depth", async () => {
|
test("parcel patterns ignore built-in folders at any depth", async () => {
|
||||||
let ignoreGlobs: string[] = []
|
let ignoreGlobs: string[] = []
|
||||||
const watcher = createWrapper({
|
const watcher = createWrapper({
|
||||||
|
|||||||
@@ -1,44 +1,59 @@
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import fs from "fs/promises"
|
import os from "os"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Effect } from "effect"
|
import { Effect, Layer } from "effect"
|
||||||
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||||
|
import { FileSystem } from "@opencode-ai/core/filesystem"
|
||||||
|
import { Protected } from "@opencode-ai/core/filesystem/protected"
|
||||||
|
import { FileSystemSearch } from "@opencode-ai/core/filesystem/search"
|
||||||
|
import { Location } from "@opencode-ai/core/location"
|
||||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||||
import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
|
import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
|
||||||
import { tmpdir } from "../fixture/tmpdir"
|
import { location } from "../fixture/location"
|
||||||
import { testEffect } from "../lib/effect"
|
|
||||||
|
|
||||||
const it = testEffect(LayerNode.compile(Ripgrep.node))
|
describe("FileSystemSearch", () => {
|
||||||
|
test("bounds a home scan even when home is detected as a repository", async () => {
|
||||||
|
let observed: Ripgrep.FindInput | undefined
|
||||||
|
const home = AbsolutePath.make(os.homedir())
|
||||||
|
const layer = AppNodeBuilder.build(FileSystemSearch.node, [
|
||||||
|
[
|
||||||
|
Location.node,
|
||||||
|
Layer.succeed(
|
||||||
|
Location.Service,
|
||||||
|
Location.Service.of(
|
||||||
|
location({ directory: home }, { vcs: { type: "git", store: AbsolutePath.make(path.join(home, ".git")) } }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Ripgrep.node,
|
||||||
|
Layer.succeed(
|
||||||
|
Ripgrep.Service,
|
||||||
|
Ripgrep.Service.of({
|
||||||
|
find: (input) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
observed = input
|
||||||
|
if (input.onEntry)
|
||||||
|
yield* input.onEntry(FileSystem.Entry.make({ path: RelativePath.make("src/index.ts"), type: "file" }))
|
||||||
|
return []
|
||||||
|
}),
|
||||||
|
glob: () => Effect.succeed([]),
|
||||||
|
grep: () => Effect.succeed([]),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
])
|
||||||
|
|
||||||
const withTmp = <A, E, R>(f: (directory: AbsolutePath) => Effect.Effect<A, E, R>) =>
|
await Effect.runPromise(
|
||||||
Effect.acquireRelease(
|
|
||||||
Effect.promise(() => tmpdir()),
|
|
||||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
|
||||||
).pipe(Effect.flatMap((tmp) => f(AbsolutePath.make(tmp.path))))
|
|
||||||
|
|
||||||
describe("Ripgrep", () => {
|
|
||||||
it.live("globs files as an array", () =>
|
|
||||||
withTmp((cwd) =>
|
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* Effect.promise(() => fs.mkdir(path.join(cwd, "src")))
|
const search = yield* FileSystemSearch.Service
|
||||||
yield* Effect.promise(() => fs.writeFile(path.join(cwd, "src", "match.ts"), "needle\n"))
|
yield* Effect.sleep("10 millis")
|
||||||
const result = yield* (yield* Ripgrep.Service).glob({ cwd, pattern: "**/*.ts", limit: 10 })
|
expect(observed?.limit).toBe(100_000)
|
||||||
expect(result.map((item) => item.path)).toEqual([RelativePath.make("src/match.ts")])
|
expect(observed?.exclude).toEqual([...Protected.names()].map((name) => `${name}/**`))
|
||||||
}),
|
expect((yield* search.find({ query: "src", type: "directory" }))[0]?.path).toBe(
|
||||||
),
|
RelativePath.make(`src${path.sep}`),
|
||||||
)
|
)
|
||||||
|
}).pipe(Effect.provide(layer), Effect.scoped),
|
||||||
it.live("greps files with include filtering", () =>
|
)
|
||||||
withTmp((cwd) =>
|
})
|
||||||
Effect.gen(function* () {
|
|
||||||
yield* Effect.promise(() => fs.mkdir(path.join(cwd, "src")))
|
|
||||||
yield* Effect.promise(() => fs.writeFile(path.join(cwd, "src", "match.ts"), "needle\n"))
|
|
||||||
yield* Effect.promise(() => fs.writeFile(path.join(cwd, "src", "skip.txt"), "needle\n"))
|
|
||||||
const result = yield* (yield* Ripgrep.Service).grep({ cwd, pattern: "needle", include: "*.ts", limit: 10 })
|
|
||||||
expect(result).toHaveLength(1)
|
|
||||||
expect(result[0]?.entry.path).toBe(RelativePath.make("src/match.ts"))
|
|
||||||
expect(result[0]?.submatches[0]?.text).toBe("needle")
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -56,52 +56,22 @@ function withTemp<A, E, R>(body: (directory: string) => Effect.Effect<A, E, R>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("Formatter", () => {
|
describe("Formatter", () => {
|
||||||
it.live("status() returns empty list when no formatters are configured", () =>
|
it.live("does not run formatters marked as disabled in config", () =>
|
||||||
withTemp((directory) =>
|
withTemp((directory) =>
|
||||||
Formatter.Service.use((formatter) => formatter.status()).pipe(Effect.provide(formatterLayer(directory))),
|
Effect.gen(function* () {
|
||||||
),
|
const file = path.join(directory, "test.disabled")
|
||||||
)
|
expect(yield* Formatter.Service.use((formatter) => formatter.file(file))).toBe(false)
|
||||||
|
}).pipe(
|
||||||
it.live("status() returns built-in formatters when formatter is true", () =>
|
Effect.provide(
|
||||||
withTemp((directory) =>
|
formatterLayer(directory, {
|
||||||
Formatter.Service.use((formatter) =>
|
disabled: {
|
||||||
Effect.gen(function* () {
|
disabled: true,
|
||||||
const statuses = yield* formatter.status()
|
command: [process.execPath, "-e", "process.exit(0)", "$FILE"],
|
||||||
const gofmt = statuses.find((item) => item.name === "gofmt")
|
extensions: [".disabled"],
|
||||||
expect(gofmt).toBeDefined()
|
},
|
||||||
expect(gofmt?.extensions).toContain(".go")
|
}),
|
||||||
}),
|
),
|
||||||
).pipe(Effect.provide(formatterLayer(directory, true))),
|
),
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.live("status() keeps built-in formatters when config object is provided", () =>
|
|
||||||
withTemp((directory) =>
|
|
||||||
Formatter.Service.use((formatter) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const statuses = yield* formatter.status()
|
|
||||||
expect(statuses.find((item) => item.name === "gofmt")?.extensions).toContain(".go")
|
|
||||||
expect(statuses.find((item) => item.name === "mix")).toBeDefined()
|
|
||||||
}),
|
|
||||||
).pipe(Effect.provide(formatterLayer(directory, { gofmt: {} }))),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.live("status() excludes formatters marked as disabled in config", () =>
|
|
||||||
withTemp((directory) =>
|
|
||||||
Formatter.Service.use((formatter) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const statuses = yield* formatter.status()
|
|
||||||
expect(statuses.find((item) => item.name === "gofmt")).toBeUndefined()
|
|
||||||
expect(statuses.find((item) => item.name === "mix")).toBeDefined()
|
|
||||||
}),
|
|
||||||
).pipe(Effect.provide(formatterLayer(directory, { gofmt: { disabled: true } }))),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.live("service initializes without error", () =>
|
|
||||||
withTemp((directory) =>
|
|
||||||
Formatter.Service.use((formatter) => formatter.init()).pipe(Effect.provide(formatterLayer(directory))),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -115,22 +85,29 @@ describe("Formatter", () => {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("status() initializes formatter state per directory", () =>
|
it.live("loads formatter state per directory", () =>
|
||||||
Effect.acquireUseRelease(
|
withTemp((off) =>
|
||||||
Effect.promise(() => Promise.all([tmpdir(), tmpdir()])),
|
withTemp((on) =>
|
||||||
([off, on]) =>
|
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const disabled = yield* Formatter.Service.use((formatter) => formatter.status()).pipe(
|
const offFile = path.join(off, "test.isolated")
|
||||||
Effect.provide(formatterLayer(off.path, false)),
|
const onFile = path.join(on, "test.isolated")
|
||||||
|
const disabled = yield* Formatter.Service.use((formatter) => formatter.file(offFile)).pipe(
|
||||||
|
Effect.provide(formatterLayer(off, false)),
|
||||||
)
|
)
|
||||||
const enabled = yield* Formatter.Service.use((formatter) => formatter.status()).pipe(
|
const enabled = yield* Formatter.Service.use((formatter) => formatter.file(onFile)).pipe(
|
||||||
Effect.provide(formatterLayer(on.path, true)),
|
Effect.provide(
|
||||||
|
formatterLayer(on, {
|
||||||
|
isolated: {
|
||||||
|
command: [process.execPath, "-e", "process.exit(0)", "$FILE"],
|
||||||
|
extensions: [".isolated"],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
expect(disabled).toEqual([])
|
expect(disabled).toBe(false)
|
||||||
expect(enabled.find((item) => item.name === "gofmt")).toBeDefined()
|
expect(enabled).toBe(true)
|
||||||
}),
|
}),
|
||||||
(directories) =>
|
),
|
||||||
Effect.promise(() => Promise.all(directories.map((tmp) => tmp[Symbol.asyncDispose]())).then(() => undefined)),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -185,9 +185,6 @@ describe("Git trees", () => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
const files = new Map([[RelativePath.make("scope/tracked.txt"), before]])
|
const files = new Map([[RelativePath.make("scope/tracked.txt"), before]])
|
||||||
const preview = yield* git.tree.preview({ repository, current: after, files, context: 1 })
|
|
||||||
expect(preview).toHaveLength(1)
|
|
||||||
expect(preview[0]?.file).toBe(RelativePath.make("scope/tracked.txt"))
|
|
||||||
yield* git.tree.restore({ repository, files })
|
yield* git.tree.restore({ repository, files })
|
||||||
expect(yield* read(path.join(root.path, "scope", "tracked.txt"))).toBe("one\n")
|
expect(yield* read(path.join(root.path, "scope", "tracked.txt"))).toBe("one\n")
|
||||||
expect(yield* read(path.join(root.path, "scope", "added.txt"))).toBe("added\n")
|
expect(yield* read(path.join(root.path, "scope", "added.txt"))).toBe("added\n")
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ const projectLayer = Layer.succeed(
|
|||||||
canonical: AbsolutePath.make("/main/repo"),
|
canonical: AbsolutePath.make("/main/repo"),
|
||||||
vcs: { type: "git", store: AbsolutePath.make("/repo/.git") },
|
vcs: { type: "git", store: AbsolutePath.make("/repo/.git") },
|
||||||
}),
|
}),
|
||||||
commit: () => Effect.void,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const it = testEffect(AppNodeBuilder.build(Location.boundNode(ref), [[Project.node, projectLayer]]))
|
const it = testEffect(AppNodeBuilder.build(Location.boundNode(ref), [[Project.node, projectLayer]]))
|
||||||
|
|||||||
@@ -83,20 +83,10 @@ describe("ProjectCopy", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("rejects duplicate strategies and reports unavailable ids", () =>
|
it.effect("reports unavailable strategy ids", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const input = yield* setup()
|
const input = yield* setup()
|
||||||
const copy = yield* ProjectCopy.Service
|
const copy = yield* ProjectCopy.Service
|
||||||
const strategy: ProjectCopy.Strategy = {
|
|
||||||
id: ProjectCopy.StrategyID.make("test/duplicate"),
|
|
||||||
create: () => Effect.die("unused"),
|
|
||||||
remove: () => Effect.die("unused"),
|
|
||||||
list: () => Effect.succeed([]),
|
|
||||||
}
|
|
||||||
|
|
||||||
yield* copy.register(strategy)
|
|
||||||
expect(yield* copy.register(strategy).pipe(Effect.flip)).toBeInstanceOf(ProjectCopy.DuplicateStrategyError)
|
|
||||||
|
|
||||||
const unavailable = ProjectCopy.StrategyID.make("acme/missing")
|
const unavailable = ProjectCopy.StrategyID.make("acme/missing")
|
||||||
const error = yield* copy
|
const error = yield* copy
|
||||||
.create({
|
.create({
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Effect, Layer } from "effect"
|
|||||||
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
||||||
import { PtyID } from "@opencode-ai/core/pty/schema"
|
import { PtyID } from "@opencode-ai/core/pty/schema"
|
||||||
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
||||||
import { Workspace } from "@opencode-ai/core/workspace"
|
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
|
|
||||||
const it = testEffect(LayerNode.compile(PtyTicket.node))
|
const it = testEffect(LayerNode.compile(PtyTicket.node))
|
||||||
@@ -46,17 +45,14 @@ describe("PTY websocket tickets", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("rejects tickets scoped to a different workspace", () =>
|
it.live("rejects tickets scoped to a different pty", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const tickets = yield* PtyTicket.Service
|
const tickets = yield* PtyTicket.Service
|
||||||
const ptyID = PtyID.ascending()
|
const ptyID = PtyID.ascending()
|
||||||
const workspaceID = Workspace.ID.ascending()
|
const issued = yield* tickets.issue({ ptyID })
|
||||||
const issued = yield* tickets.issue({ ptyID, workspaceID })
|
|
||||||
|
|
||||||
expect(yield* tickets.consume({ ptyID, workspaceID: Workspace.ID.ascending(), ticket: issued.ticket })).toBe(
|
expect(yield* tickets.consume({ ptyID: PtyID.ascending(), ticket: issued.ticket })).toBe(false)
|
||||||
false,
|
expect(yield* tickets.consume({ ptyID, ticket: issued.ticket })).toBe(true)
|
||||||
)
|
|
||||||
expect(yield* tickets.consume({ ptyID, workspaceID, ticket: issued.ticket })).toBe(true)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -101,13 +101,10 @@ describe("RepositoryCache", () => {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("returns typed validation and clone failures", () =>
|
it.live("returns typed branch validation and clone failures", () =>
|
||||||
withRemote((fixture) =>
|
withRemote((fixture) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const cache = yield* RepositoryCache.Service
|
const cache = yield* RepositoryCache.Service
|
||||||
const invalidRepository = yield* Effect.flip(RepositoryCache.parseRemote("not-a-repo"))
|
|
||||||
expect(invalidRepository).toBeInstanceOf(RepositoryCache.InvalidRepositoryError)
|
|
||||||
|
|
||||||
const invalidBranch = yield* Effect.flip(cache.ensure({ reference: fixture.reference, branch: "../unsafe" }))
|
const invalidBranch = yield* Effect.flip(cache.ensure({ reference: fixture.reference, branch: "../unsafe" }))
|
||||||
expect(invalidBranch).toBeInstanceOf(RepositoryCache.InvalidBranchError)
|
expect(invalidBranch).toBeInstanceOf(RepositoryCache.InvalidBranchError)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,44 @@ import { testEffect } from "./lib/effect"
|
|||||||
const it = testEffect(LayerNode.compile(Ripgrep.node))
|
const it = testEffect(LayerNode.compile(Ripgrep.node))
|
||||||
|
|
||||||
describe("Ripgrep", () => {
|
describe("Ripgrep", () => {
|
||||||
|
it.live("globs files as an array", () =>
|
||||||
|
Effect.acquireUseRelease(
|
||||||
|
Effect.promise(() => tmpdir()),
|
||||||
|
(tmp) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, "src")))
|
||||||
|
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "match.ts"), "needle\n"))
|
||||||
|
|
||||||
|
const result = yield* (yield* Ripgrep.Service).glob({ cwd: tmp.path, pattern: "**/*.ts", limit: 10 })
|
||||||
|
expect(result.map((item) => item.path)).toEqual([RelativePath.make("src/match.ts")])
|
||||||
|
}),
|
||||||
|
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.live("greps files with include filtering", () =>
|
||||||
|
Effect.acquireUseRelease(
|
||||||
|
Effect.promise(() => tmpdir()),
|
||||||
|
(tmp) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, "src")))
|
||||||
|
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "match.ts"), "needle\n"))
|
||||||
|
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "skip.txt"), "needle\n"))
|
||||||
|
|
||||||
|
const result = yield* (yield* Ripgrep.Service).grep({
|
||||||
|
cwd: tmp.path,
|
||||||
|
pattern: "needle",
|
||||||
|
include: "*.ts",
|
||||||
|
limit: 10,
|
||||||
|
})
|
||||||
|
expect(result).toHaveLength(1)
|
||||||
|
expect(result[0]?.entry.path).toBe(RelativePath.make("src/match.ts"))
|
||||||
|
expect(result[0]?.submatches[0]?.text).toBe("needle")
|
||||||
|
}),
|
||||||
|
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
it.live("keeps ignored files out of catch-all find results", () =>
|
it.live("keeps ignored files out of catch-all find results", () =>
|
||||||
Effect.acquireUseRelease(
|
Effect.acquireUseRelease(
|
||||||
Effect.promise(() => tmpdir()),
|
Effect.promise(() => tmpdir()),
|
||||||
@@ -63,6 +101,29 @@ describe("Ripgrep", () => {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.live("excludes protected directory trees from catch-all find results", () =>
|
||||||
|
Effect.acquireUseRelease(
|
||||||
|
Effect.promise(() => tmpdir()),
|
||||||
|
(tmp) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, "Pictures")))
|
||||||
|
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "Pictures", "private.jpg"), "private\n"))
|
||||||
|
yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "visible.txt"), "visible\n"))
|
||||||
|
|
||||||
|
const files = yield* (yield* Ripgrep.Service).find({
|
||||||
|
cwd: tmp.path,
|
||||||
|
pattern: "*",
|
||||||
|
limit: 10,
|
||||||
|
exclude: ["Pictures/**"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(files.map((item) => item.path)).toContain(RelativePath.make("visible.txt"))
|
||||||
|
expect(files.map((item) => item.path)).not.toContain(RelativePath.make("Pictures/private.jpg"))
|
||||||
|
}),
|
||||||
|
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
it.live("returns a bounded preview for matches on oversized lines", () =>
|
it.live("returns a bounded preview for matches on oversized lines", () =>
|
||||||
Effect.acquireUseRelease(
|
Effect.acquireUseRelease(
|
||||||
Effect.promise(() => tmpdir()),
|
Effect.promise(() => tmpdir()),
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ const projects = Layer.succeed(
|
|||||||
list: () => Effect.succeed([]),
|
list: () => Effect.succeed([]),
|
||||||
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
||||||
directories: () => Effect.succeed([]),
|
directories: () => Effect.succeed([]),
|
||||||
commit: () => Effect.void,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
let requests: LLMRequest[] = []
|
let requests: LLMRequest[] = []
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import { SessionPending } from "@opencode-ai/core/session/pending"
|
|||||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||||
import { SessionStore } from "@opencode-ai/core/session/store"
|
import { SessionStore } from "@opencode-ai/core/session/store"
|
||||||
import { Workspace } from "@opencode-ai/core/workspace"
|
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { tmpdir } from "./fixture/tmpdir"
|
import { tmpdir } from "./fixture/tmpdir"
|
||||||
|
|
||||||
@@ -34,7 +33,6 @@ const projects = Layer.succeed(
|
|||||||
list: () => Effect.succeed([]),
|
list: () => Effect.succeed([]),
|
||||||
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
||||||
directories: () => Effect.succeed([]),
|
directories: () => Effect.succeed([]),
|
||||||
commit: () => Effect.void,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const it = testEffect(
|
const it = testEffect(
|
||||||
@@ -120,7 +118,6 @@ describe("Session.create", () => {
|
|||||||
it.effect("stores supplied immutable create attributes", () =>
|
it.effect("stores supplied immutable create attributes", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const session = yield* Session.Service
|
const session = yield* Session.Service
|
||||||
const workspaceID = Workspace.ID.make("wrk_test")
|
|
||||||
const model = Model.Ref.make({
|
const model = Model.Ref.make({
|
||||||
id: Model.ID.make("sonnet"),
|
id: Model.ID.make("sonnet"),
|
||||||
providerID: Provider.ID.anthropic,
|
providerID: Provider.ID.anthropic,
|
||||||
@@ -129,11 +126,11 @@ describe("Session.create", () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
yield* session.create({
|
yield* session.create({
|
||||||
location: Location.Ref.make({ directory: location.directory, workspaceID }),
|
location: Location.Ref.make({ directory: location.directory }),
|
||||||
agent: Agent.ID.make("build"),
|
agent: Agent.ID.make("build"),
|
||||||
model,
|
model,
|
||||||
}),
|
}),
|
||||||
).toMatchObject({ location: { directory: location.directory, workspaceID }, agent: "build", model })
|
).toMatchObject({ location: { directory: location.directory }, agent: "build", model })
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ const projects = Layer.succeed(
|
|||||||
list: () => Effect.succeed([]),
|
list: () => Effect.succeed([]),
|
||||||
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
||||||
directories: () => Effect.succeed([]),
|
directories: () => Effect.succeed([]),
|
||||||
commit: () => Effect.void,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const permission = Layer.succeed(
|
const permission = Layer.succeed(
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const projects = Layer.succeed(
|
|||||||
list: () => Effect.succeed([]),
|
list: () => Effect.succeed([]),
|
||||||
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
||||||
directories: () => Effect.succeed([]),
|
directories: () => Effect.succeed([]),
|
||||||
commit: () => Effect.void,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const it = testEffect(
|
const it = testEffect(
|
||||||
@@ -41,17 +40,15 @@ describe("Session.log", () => {
|
|||||||
it.effect("replays public session events and marks synced at the aggregate watermark", () =>
|
it.effect("replays public session events and marks synced at the aggregate watermark", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const session = yield* Session.Service
|
const session = yield* Session.Service
|
||||||
const bus = yield* Bus.Service
|
|
||||||
const created = yield* session.create({ location })
|
const created = yield* session.create({ location })
|
||||||
yield* session.rename({ sessionID: created.id, title: "session.renamed" })
|
yield* session.rename({ sessionID: created.id, title: "session.renamed" })
|
||||||
|
|
||||||
const items = Array.from(yield* Stream.runCollect(session.log({ sessionID: created.id })))
|
const items = Array.from(yield* Stream.runCollect(session.log({ sessionID: created.id })))
|
||||||
const watermark = (yield* bus.sequences([created.id])).get(created.id)
|
|
||||||
|
|
||||||
// Session creation commits a non-public durable event, so the marker's
|
// Session creation commits a non-public durable event, so the marker's
|
||||||
// seq covers more of the aggregate than the public events emitted.
|
// seq covers more of the aggregate than the public events emitted.
|
||||||
expect(items.map((item) => item.type)).toEqual(["session.renamed", "log.synced"])
|
expect(items.map((item) => item.type)).toEqual(["session.renamed", "log.synced"])
|
||||||
expect(items.at(-1)).toEqual({ type: "log.synced", aggregateID: created.id, seq: watermark })
|
expect(items.at(-1)).toEqual({ type: "log.synced", aggregateID: created.id, seq: Event.Seq.make(1) })
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { Session } from "@opencode-ai/core/session"
|
|||||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { Money } from "@opencode-ai/schema/money"
|
import { Money } from "@opencode-ai/schema/money"
|
||||||
import { SessionMessageUpdater } from "@opencode-ai/core/session/message-updater"
|
|
||||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||||
import { fromRow } from "@opencode-ai/core/session/info"
|
import { fromRow } from "@opencode-ai/core/session/info"
|
||||||
@@ -126,34 +125,6 @@ describe("SessionProjector", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("folds live compaction deltas into running memory state", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const state = {
|
|
||||||
messages: [
|
|
||||||
SessionMessage.CompactionRunning.make({
|
|
||||||
id: SessionMessage.ID.make("msg_compaction"),
|
|
||||||
type: "compaction",
|
|
||||||
status: "running",
|
|
||||||
reason: "manual",
|
|
||||||
summary: "partial ",
|
|
||||||
recent: "recent",
|
|
||||||
time: { created },
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
yield* SessionMessageUpdater.update(
|
|
||||||
SessionMessageUpdater.memory(state),
|
|
||||||
SessionEvent.Compaction.Delta.make({
|
|
||||||
id: Event.ID.make("evt_delta"),
|
|
||||||
type: "session.compaction.delta",
|
|
||||||
created,
|
|
||||||
data: { sessionID, text: "summary" },
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
expect(state.messages[0]).toMatchObject({ status: "running", summary: "partial summary", recent: "recent" })
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("projects staged, cleared, and committed reverts", () =>
|
it.effect("projects staged, cleared, and committed reverts", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const db = (yield* Database.Service).db
|
const db = (yield* Database.Service).db
|
||||||
@@ -550,31 +521,6 @@ describe("SessionProjector", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("does not revive a stale incomplete in-memory assistant projection", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const stale = SessionMessage.Assistant.make({
|
|
||||||
id: SessionMessage.ID.make("msg_assistant_stale"),
|
|
||||||
type: "assistant",
|
|
||||||
agent: build,
|
|
||||||
model,
|
|
||||||
content: [],
|
|
||||||
time: { created },
|
|
||||||
})
|
|
||||||
const completed = SessionMessage.Assistant.make({
|
|
||||||
id: SessionMessage.ID.make("msg_assistant_completed"),
|
|
||||||
type: "assistant",
|
|
||||||
agent: build,
|
|
||||||
model,
|
|
||||||
content: [],
|
|
||||||
time: { created: DateTime.makeUnsafe(1), completed: DateTime.makeUnsafe(2) },
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(
|
|
||||||
yield* SessionMessageUpdater.memory({ messages: [stale, completed] }).getCurrentAssistant(),
|
|
||||||
).toBeUndefined()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("projects retry state and clears it at the next step or execution terminal", () =>
|
it.effect("projects retry state and clears it at the next step or execution terminal", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ const projects = Layer.succeed(
|
|||||||
list: () => Effect.succeed([]),
|
list: () => Effect.succeed([]),
|
||||||
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
|
||||||
directories: () => Effect.succeed([]),
|
directories: () => Effect.succeed([]),
|
||||||
commit: () => Effect.void,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const it = testEffect(
|
const it = testEffect(
|
||||||
|
|||||||
@@ -34,12 +34,6 @@ describe("shell", () => {
|
|||||||
expect(ShellSelect.login("C:/tools/pwsh.exe")).toBe(false)
|
expect(ShellSelect.login("C:/tools/pwsh.exe")).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("detects posix shells", () => {
|
|
||||||
expect(ShellSelect.posix("/bin/bash")).toBe(true)
|
|
||||||
expect(ShellSelect.posix("/bin/fish")).toBe(false)
|
|
||||||
expect(ShellSelect.posix("C:/tools/pwsh.exe")).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("falls back when configured shell cannot be resolved", async () => {
|
test("falls back when configured shell cannot be resolved", async () => {
|
||||||
await withShell(undefined, async () => {
|
await withShell(undefined, async () => {
|
||||||
const preferred = ShellSelect.preferred()
|
const preferred = ShellSelect.preferred()
|
||||||
|
|||||||
@@ -117,9 +117,6 @@ describe("Snapshot", () => {
|
|||||||
RelativePath.make("scope/tracked.txt"),
|
RelativePath.make("scope/tracked.txt"),
|
||||||
])
|
])
|
||||||
const plan = new Map([[RelativePath.make("scope/tracked.txt"), before]])
|
const plan = new Map([[RelativePath.make("scope/tracked.txt"), before]])
|
||||||
const preview = yield* snapshot.preview({ files: plan, context: 1 })
|
|
||||||
expect(preview).toHaveLength(1)
|
|
||||||
expect(preview[0]?.file).toBe(RelativePath.make("scope/tracked.txt"))
|
|
||||||
yield* snapshot.restore({ files: plan })
|
yield* snapshot.restore({ files: plan })
|
||||||
expect(yield* read(path.join(location, "tracked.txt"))).toBe("one\n")
|
expect(yield* read(path.join(location, "tracked.txt"))).toBe("one\n")
|
||||||
expect(yield* read(path.join(location, "added.txt"))).toBe("added\n")
|
expect(yield* read(path.join(location, "added.txt"))).toBe("added\n")
|
||||||
@@ -185,36 +182,6 @@ describe("Snapshot", () => {
|
|||||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
testEffect(Layer.empty).live("checks out a legacy revert snapshot without removing unrelated files", () =>
|
|
||||||
Effect.acquireUseRelease(
|
|
||||||
Effect.promise(() => tmpdir()),
|
|
||||||
(tmp) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const project = path.join(tmp.path, "project")
|
|
||||||
yield* Effect.promise(async () => {
|
|
||||||
await fs.mkdir(project)
|
|
||||||
await fs.writeFile(path.join(project, "tracked.txt"), "one\n")
|
|
||||||
await initGit(project)
|
|
||||||
})
|
|
||||||
|
|
||||||
yield* Effect.gen(function* () {
|
|
||||||
const snapshot = yield* Snapshot.Service
|
|
||||||
const before = yield* snapshot.capture()
|
|
||||||
expect(before).toBeDefined()
|
|
||||||
if (!before) return
|
|
||||||
yield* Effect.promise(async () => {
|
|
||||||
await fs.writeFile(path.join(project, "tracked.txt"), "two\n")
|
|
||||||
await fs.writeFile(path.join(project, "unrelated.txt"), "keep\n")
|
|
||||||
})
|
|
||||||
yield* snapshot.checkout(before)
|
|
||||||
expect(yield* read(path.join(project, "tracked.txt"))).toBe("one\n")
|
|
||||||
expect(yield* read(path.join(project, "unrelated.txt"))).toBe("keep\n")
|
|
||||||
}).pipe(Effect.provide(snapshotLayer(tmp.path, project)))
|
|
||||||
}),
|
|
||||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function snapshotLayer(data: string, directory: string) {
|
function snapshotLayer(data: string, directory: string) {
|
||||||
|
|||||||
Vendored
-12
@@ -5,15 +5,3 @@ interface ImportMetaEnv {
|
|||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv
|
readonly env: ImportMetaEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "virtual:opencode-server" {
|
|
||||||
export namespace Server {
|
|
||||||
export const listen: typeof import("../../../opencode/dist/types/src/node").Server.listen
|
|
||||||
export type Listener = import("../../../opencode/dist/types/src/node").Server.Listener
|
|
||||||
}
|
|
||||||
export namespace Config {
|
|
||||||
export const get: typeof import("../../../opencode/dist/types/src/node").Config.get
|
|
||||||
export type Info = import("../../../opencode/dist/types/src/node").Config.Info
|
|
||||||
}
|
|
||||||
export const bootstrap: typeof import("../../../opencode/dist/types/src/node").bootstrap
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,32 +1,8 @@
|
|||||||
import { dirname, join } from "node:path"
|
|
||||||
import { fileURLToPath } from "node:url"
|
|
||||||
import { app, utilityProcess } from "electron"
|
|
||||||
import type { Details } from "electron"
|
|
||||||
import { getLogger } from "./logging"
|
import { getLogger } from "./logging"
|
||||||
import { getUserShell, loadShellEnv } from "./shell-env"
|
import { getUserShell, loadShellEnv } from "./shell-env"
|
||||||
import { getStore } from "./store"
|
import { getStore } from "./store"
|
||||||
import { DEFAULT_SERVER_URL_KEY } from "./store-keys"
|
import { DEFAULT_SERVER_URL_KEY } from "./store-keys"
|
||||||
|
|
||||||
export type HealthCheck = { wait: Promise<void> }
|
|
||||||
|
|
||||||
type SidecarMessage =
|
|
||||||
| { type: "ready" }
|
|
||||||
| { type: "stopped" }
|
|
||||||
| { type: "error"; error: { message: string; stack?: string } }
|
|
||||||
|
|
||||||
export type SidecarListener = { stop: () => Promise<void> }
|
|
||||||
|
|
||||||
const SIDECAR_SERVICE_NAME = "opencode server"
|
|
||||||
const SIDECAR_START_STALL_TIMEOUT = 60_000
|
|
||||||
const SIDECAR_STOP_TIMEOUT = 6_000
|
|
||||||
|
|
||||||
type SpawnLocalServerOptions = {
|
|
||||||
userDataPath: string
|
|
||||||
onStdout?: (message: string) => void
|
|
||||||
onStderr?: (message: string) => void
|
|
||||||
onExit?: (code: number) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDefaultServerUrl(): string | null {
|
export function getDefaultServerUrl(): string | null {
|
||||||
const value = getStore().get(DEFAULT_SERVER_URL_KEY)
|
const value = getStore().get(DEFAULT_SERVER_URL_KEY)
|
||||||
return typeof value === "string" ? value : null
|
return typeof value === "string" ? value : null
|
||||||
@@ -54,135 +30,6 @@ export function preferAppEnv(userDataPath: string) {
|
|||||||
return shellEnv
|
return shellEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function spawnLocalServer(
|
|
||||||
hostname: string,
|
|
||||||
port: number,
|
|
||||||
password: string,
|
|
||||||
options: SpawnLocalServerOptions,
|
|
||||||
) {
|
|
||||||
const sidecar = join(dirname(fileURLToPath(import.meta.url)), "sidecar.js")
|
|
||||||
const child = utilityProcess.fork(sidecar, [], {
|
|
||||||
cwd: process.cwd(),
|
|
||||||
env: createSidecarEnv(),
|
|
||||||
serviceName: SIDECAR_SERVICE_NAME,
|
|
||||||
stdio: "pipe",
|
|
||||||
})
|
|
||||||
let exited = false
|
|
||||||
const exit = defer<number>()
|
|
||||||
|
|
||||||
const onProcessGone = (_event: unknown, details: Details) => {
|
|
||||||
if (details.type !== "Utility" || details.name !== SIDECAR_SERVICE_NAME) return
|
|
||||||
options.onStderr?.(`utility process gone reason=${details.reason} exitCode=${details.exitCode}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on("child-process-gone", onProcessGone)
|
|
||||||
child.once("exit", (code) => {
|
|
||||||
exited = true
|
|
||||||
app.off("child-process-gone", onProcessGone)
|
|
||||||
options.onExit?.(code)
|
|
||||||
exit.resolve(code)
|
|
||||||
})
|
|
||||||
child.on("error", (error) => options.onStderr?.(`utility process error: ${serializeError(error).message}`))
|
|
||||||
|
|
||||||
child.stdout?.on("data", (chunk: Buffer) => options.onStdout?.(chunk.toString("utf8").trimEnd()))
|
|
||||||
child.stderr?.on("data", (chunk: Buffer) => options.onStderr?.(chunk.toString("utf8").trimEnd()))
|
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
let done = false
|
|
||||||
let timeout: NodeJS.Timeout
|
|
||||||
|
|
||||||
const fail = (error: Error) => {
|
|
||||||
if (done) return
|
|
||||||
done = true
|
|
||||||
cleanup()
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshTimeout = () => {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
fail(new Error(`Sidecar did not become ready within ${SIDECAR_START_STALL_TIMEOUT}ms: ${sidecar}`))
|
|
||||||
}, SIDECAR_START_STALL_TIMEOUT)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onMessage = (message: SidecarMessage) => {
|
|
||||||
if (message.type === "ready") {
|
|
||||||
if (done) return
|
|
||||||
done = true
|
|
||||||
cleanup()
|
|
||||||
resolve()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (message.type === "error") {
|
|
||||||
fail(Object.assign(new Error(message.error.message), { stack: message.error.stack }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const onExit = (code: number) => {
|
|
||||||
fail(new Error(`Sidecar exited before ready with code ${code}`))
|
|
||||||
}
|
|
||||||
const cleanup = () => {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
child.off("message", onMessage)
|
|
||||||
child.off("exit", onExit)
|
|
||||||
}
|
|
||||||
|
|
||||||
child.on("message", onMessage)
|
|
||||||
child.on("exit", onExit)
|
|
||||||
refreshTimeout()
|
|
||||||
child.postMessage({
|
|
||||||
type: "start",
|
|
||||||
hostname,
|
|
||||||
port,
|
|
||||||
password,
|
|
||||||
userDataPath: options.userDataPath,
|
|
||||||
})
|
|
||||||
}).catch((error) => {
|
|
||||||
if (!exited) child.kill()
|
|
||||||
throw error
|
|
||||||
})
|
|
||||||
|
|
||||||
const wait = (async () => {
|
|
||||||
const url = `http://${hostname}:${port}`
|
|
||||||
let healthy = false
|
|
||||||
const gone = exit.promise.then((code) => {
|
|
||||||
if (healthy) return
|
|
||||||
throw new Error(`Sidecar exited before health check passed with code ${code}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
const ready = async () => {
|
|
||||||
while (true) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
||||||
if (await checkHealth(url, password)) {
|
|
||||||
healthy = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.race([ready(), gone])
|
|
||||||
})()
|
|
||||||
|
|
||||||
let stopping: Promise<void> | undefined
|
|
||||||
|
|
||||||
return {
|
|
||||||
listener: {
|
|
||||||
stop: () => {
|
|
||||||
if (stopping) return stopping
|
|
||||||
if (exited) return Promise.resolve()
|
|
||||||
child.postMessage({ type: "stop" })
|
|
||||||
stopping = Promise.race([
|
|
||||||
exit.promise.then(() => undefined),
|
|
||||||
delay(SIDECAR_STOP_TIMEOUT).then(() => {
|
|
||||||
if (!exited) child.kill()
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
return stopping
|
|
||||||
},
|
|
||||||
},
|
|
||||||
health: { wait },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkHealth(url: string, password?: string | null): Promise<boolean> {
|
export async function checkHealth(url: string, password?: string | null): Promise<boolean> {
|
||||||
let healthUrls: URL[]
|
let healthUrls: URL[]
|
||||||
try {
|
try {
|
||||||
@@ -209,31 +56,3 @@ export async function checkHealth(url: string, password?: string | null): Promis
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSidecarEnv(): Record<string, string> {
|
|
||||||
const env = Object.fromEntries(
|
|
||||||
Object.entries(process.env).flatMap(([key, value]) => (value === undefined ? [] : [[key, String(value)]])),
|
|
||||||
)
|
|
||||||
delete env.DEBUG
|
|
||||||
if (process.platform === "linux") delete env.LD_PRELOAD
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
function delay(ms: number) {
|
|
||||||
return new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeError(error: unknown) {
|
|
||||||
if (error instanceof Error) return { message: error.message, stack: error.stack }
|
|
||||||
return { message: String(error) }
|
|
||||||
}
|
|
||||||
|
|
||||||
function defer<T>() {
|
|
||||||
let resolve!: (value: T) => void
|
|
||||||
let reject!: (error: Error) => void
|
|
||||||
const promise = new Promise<T>((res, rej) => {
|
|
||||||
resolve = res
|
|
||||||
reject = rej
|
|
||||||
})
|
|
||||||
return { promise, resolve, reject }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
import * as http from "node:http"
|
|
||||||
import * as tls from "node:tls"
|
|
||||||
|
|
||||||
type NodeHttpWithEnvProxy = typeof http & {
|
|
||||||
setGlobalProxyFromEnv: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeTlsWithSystemCertificates = typeof tls & {
|
|
||||||
getCACertificates: (type: "default" | "system") => string[]
|
|
||||||
setDefaultCACertificates: (certificates: string[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
type StartCommand = {
|
|
||||||
type: "start"
|
|
||||||
hostname: string
|
|
||||||
port: number
|
|
||||||
password: string
|
|
||||||
userDataPath: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type StopCommand = { type: "stop" }
|
|
||||||
type SidecarCommand = StartCommand | StopCommand
|
|
||||||
|
|
||||||
type SidecarMessage =
|
|
||||||
| { type: "ready" }
|
|
||||||
| { type: "stopped" }
|
|
||||||
| { type: "error"; error: { message: string; stack?: string } }
|
|
||||||
|
|
||||||
type ParentPort = {
|
|
||||||
postMessage(message: SidecarMessage): void
|
|
||||||
on(event: "message", listener: (event: { data: unknown }) => void): void
|
|
||||||
}
|
|
||||||
|
|
||||||
type Listener = {
|
|
||||||
stop(close?: boolean): void | Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentPort = getParentPort()
|
|
||||||
let listener: Listener | undefined
|
|
||||||
|
|
||||||
parentPort.on("message", (event) => {
|
|
||||||
const command = parseCommand(event.data)
|
|
||||||
if (!command) return
|
|
||||||
if (command.type === "stop") {
|
|
||||||
void stop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
void start(command)
|
|
||||||
})
|
|
||||||
|
|
||||||
async function start(command: StartCommand) {
|
|
||||||
try {
|
|
||||||
prepareSidecarEnv(command.password, command.userDataPath)
|
|
||||||
ensureLoopbackNoProxy()
|
|
||||||
useSystemCertificates()
|
|
||||||
useEnvProxy()
|
|
||||||
const { Server } = await import("virtual:opencode-server")
|
|
||||||
|
|
||||||
listener = await Server.listen({
|
|
||||||
port: command.port,
|
|
||||||
hostname: command.hostname,
|
|
||||||
username: "opencode",
|
|
||||||
password: command.password,
|
|
||||||
cors: ["oc://renderer"],
|
|
||||||
})
|
|
||||||
parentPort.postMessage({ type: "ready" })
|
|
||||||
} catch (error) {
|
|
||||||
parentPort.postMessage({ type: "error", error: serializeError(error) })
|
|
||||||
setImmediate(() => process.exit(1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stop() {
|
|
||||||
try {
|
|
||||||
await listener?.stop()
|
|
||||||
} finally {
|
|
||||||
listener = undefined
|
|
||||||
parentPort.postMessage({ type: "stopped" })
|
|
||||||
setImmediate(() => process.exit(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareSidecarEnv(password: string, userDataPath: string) {
|
|
||||||
Object.assign(process.env, {
|
|
||||||
OPENCODE_SERVER_USERNAME: "opencode",
|
|
||||||
OPENCODE_SERVER_PASSWORD: password,
|
|
||||||
XDG_STATE_HOME: process.env.XDG_STATE_HOME ?? userDataPath,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureLoopbackNoProxy() {
|
|
||||||
const loopback = ["127.0.0.1", "localhost", "::1"]
|
|
||||||
const upsert = (key: string) => {
|
|
||||||
const items = (process.env[key] ?? "")
|
|
||||||
.split(",")
|
|
||||||
.map((value: string) => value.trim())
|
|
||||||
.filter((value: string) => Boolean(value))
|
|
||||||
|
|
||||||
for (const host of loopback) {
|
|
||||||
if (items.some((value: string) => value.toLowerCase() === host)) continue
|
|
||||||
items.push(host)
|
|
||||||
}
|
|
||||||
|
|
||||||
process.env[key] = items.join(",")
|
|
||||||
}
|
|
||||||
|
|
||||||
upsert("NO_PROXY")
|
|
||||||
upsert("no_proxy")
|
|
||||||
}
|
|
||||||
|
|
||||||
function useSystemCertificates() {
|
|
||||||
try {
|
|
||||||
const nodeTls = tls as NodeTlsWithSystemCertificates
|
|
||||||
nodeTls.setDefaultCACertificates([
|
|
||||||
...new Set([...nodeTls.getCACertificates("default"), ...nodeTls.getCACertificates("system")]),
|
|
||||||
])
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("failed to load system certificates", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function useEnvProxy() {
|
|
||||||
try {
|
|
||||||
;(http as NodeHttpWithEnvProxy).setGlobalProxyFromEnv()
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("failed to load proxy environment", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseCommand(value: unknown): SidecarCommand | undefined {
|
|
||||||
if (!value || typeof value !== "object") return
|
|
||||||
const command = value as Partial<StartCommand | StopCommand>
|
|
||||||
if (command.type === "stop") return { type: "stop" }
|
|
||||||
if (command.type !== "start") return
|
|
||||||
if (typeof command.hostname !== "string") return
|
|
||||||
if (typeof command.port !== "number") return
|
|
||||||
if (typeof command.password !== "string") return
|
|
||||||
if (typeof command.userDataPath !== "string") return
|
|
||||||
return {
|
|
||||||
type: "start",
|
|
||||||
hostname: command.hostname,
|
|
||||||
port: command.port,
|
|
||||||
password: command.password,
|
|
||||||
userDataPath: command.userDataPath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeError(error: unknown) {
|
|
||||||
if (error instanceof Error) return { message: error.message, stack: error.stack }
|
|
||||||
return { message: String(error) }
|
|
||||||
}
|
|
||||||
|
|
||||||
function getParentPort() {
|
|
||||||
const port = process.parentPort as ParentPort | undefined
|
|
||||||
if (!port) throw new Error("Sidecar parent port unavailable")
|
|
||||||
return port
|
|
||||||
}
|
|
||||||
@@ -23,44 +23,11 @@ export type ProviderContext = {
|
|||||||
options: Record<string, any>
|
options: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkspaceInfo = {
|
|
||||||
id: string
|
|
||||||
type: string
|
|
||||||
name: string
|
|
||||||
branch: string | null
|
|
||||||
directory: string | null
|
|
||||||
extra: unknown | null
|
|
||||||
projectID: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WorkspaceTarget =
|
|
||||||
| {
|
|
||||||
type: "local"
|
|
||||||
directory: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "remote"
|
|
||||||
url: string | URL
|
|
||||||
headers?: HeadersInit
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WorkspaceAdapter = {
|
|
||||||
name: string
|
|
||||||
description: string
|
|
||||||
configure(config: WorkspaceInfo): WorkspaceInfo | Promise<WorkspaceInfo>
|
|
||||||
create(config: WorkspaceInfo, env: Record<string, string | undefined>, from?: WorkspaceInfo): Promise<void>
|
|
||||||
remove(config: WorkspaceInfo): Promise<void>
|
|
||||||
target(config: WorkspaceInfo): WorkspaceTarget | Promise<WorkspaceTarget>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PluginInput = {
|
export type PluginInput = {
|
||||||
client: ReturnType<typeof createOpencodeClient>
|
client: ReturnType<typeof createOpencodeClient>
|
||||||
project: Project
|
project: Project
|
||||||
directory: string
|
directory: string
|
||||||
worktree: string
|
worktree: string
|
||||||
experimental_workspace: {
|
|
||||||
register(type: string, adapter: WorkspaceAdapter): void
|
|
||||||
}
|
|
||||||
serverUrl: URL
|
serverUrl: URL
|
||||||
$: BunShell
|
$: BunShell
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ export const LocationQuery = Schema.Struct({
|
|||||||
location: Schema.optional(
|
location: Schema.optional(
|
||||||
Schema.Struct({
|
Schema.Struct({
|
||||||
directory: Schema.optional(Schema.String),
|
directory: Schema.optional(Schema.String),
|
||||||
workspace: Schema.optional(Schema.String),
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
}).annotate({ identifier: "LocationQuery" })
|
}).annotate({ identifier: "LocationQuery" })
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export const PtyGroup = HttpApiGroup.make("server.pty")
|
|||||||
"x-websocket": true,
|
"x-websocket": true,
|
||||||
parameters: [
|
parameters: [
|
||||||
...(operation.parameters ?? []),
|
...(operation.parameters ?? []),
|
||||||
...["location[directory]", "location[workspace]", "cursor", PTY_CONNECT_TICKET_QUERY].map((name) => ({
|
...["location[directory]", "cursor", PTY_CONNECT_TICKET_QUERY].map((name) => ({
|
||||||
in: "query",
|
in: "query",
|
||||||
name,
|
name,
|
||||||
schema: { type: "string" },
|
schema: { type: "string" },
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { InstructionEntry } from "@opencode-ai/schema/instruction-entry"
|
|||||||
import { Project } from "@opencode-ai/schema/project"
|
import { Project } from "@opencode-ai/schema/project"
|
||||||
import { AbsolutePath, PositiveInt, RelativePath, statics } from "@opencode-ai/schema/schema"
|
import { AbsolutePath, PositiveInt, RelativePath, statics } from "@opencode-ai/schema/schema"
|
||||||
import { Event } from "@opencode-ai/schema/event"
|
import { Event } from "@opencode-ai/schema/event"
|
||||||
import { Workspace } from "@opencode-ai/schema/workspace"
|
|
||||||
import { Context, Effect, Encoding, Result, Schema, SchemaGetter, Struct } from "effect"
|
import { Context, Effect, Encoding, Result, Schema, SchemaGetter, Struct } from "effect"
|
||||||
import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||||
import {
|
import {
|
||||||
@@ -42,7 +41,6 @@ const ParentIDFilter = Schema.Union([
|
|||||||
})
|
})
|
||||||
|
|
||||||
const SessionsQueryFields = {
|
const SessionsQueryFields = {
|
||||||
workspace: Workspace.ID.pipe(Schema.optional),
|
|
||||||
limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({
|
limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({
|
||||||
description: "Maximum number of sessions to return. Defaults to the newest 50 sessions.",
|
description: "Maximum number of sessions to return. Defaults to the newest 50 sessions.",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { Session } from "@opencode-ai/schema/session"
|
|||||||
describe("SessionsCursor", () => {
|
describe("SessionsCursor", () => {
|
||||||
test("round trips without Node globals", async () => {
|
test("round trips without Node globals", async () => {
|
||||||
const input = {
|
const input = {
|
||||||
workspace: undefined,
|
|
||||||
search: "protocol",
|
search: "protocol",
|
||||||
order: "desc" as const,
|
order: "desc" as const,
|
||||||
anchor: { id: Session.ID.make("ses_test"), time: 1, direction: "next" as const },
|
anchor: { id: Session.ID.make("ses_test"), time: 1, direction: "next" as const },
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import { SessionStatusEvent } from "./session-status-event.js"
|
|||||||
import { SessionV1 } from "./session-v1.js"
|
import { SessionV1 } from "./session-v1.js"
|
||||||
import { TuiEvent } from "./tui-event.js"
|
import { TuiEvent } from "./tui-event.js"
|
||||||
import { VcsEvent } from "./vcs-event.js"
|
import { VcsEvent } from "./vcs-event.js"
|
||||||
import { WorkspaceEvent } from "./workspace-event.js"
|
|
||||||
import { WorktreeEvent } from "./worktree-event.js"
|
import { WorktreeEvent } from "./worktree-event.js"
|
||||||
import { WebSearch } from "./websearch.js"
|
import { WebSearch } from "./websearch.js"
|
||||||
|
|
||||||
@@ -100,7 +99,6 @@ export const Definitions = Event.inventory(
|
|||||||
...SessionStatusEvent.Definitions,
|
...SessionStatusEvent.Definitions,
|
||||||
...SessionCompactionEvent.Definitions,
|
...SessionCompactionEvent.Definitions,
|
||||||
...VcsEvent.Definitions,
|
...VcsEvent.Definitions,
|
||||||
...WorkspaceEvent.Definitions,
|
|
||||||
...WorktreeEvent.Definitions,
|
...WorktreeEvent.Definitions,
|
||||||
...ServerEvent.Definitions,
|
...ServerEvent.Definitions,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
export * as WorkspaceEvent from "./workspace-event.js"
|
|
||||||
|
|
||||||
import { Schema } from "effect"
|
|
||||||
import { Event } from "./event.js"
|
|
||||||
import { WorkspaceID } from "./workspace-id.js"
|
|
||||||
|
|
||||||
export const ConnectionStatus = Schema.Struct({
|
|
||||||
workspaceID: WorkspaceID,
|
|
||||||
status: Schema.Literals(["connected", "connecting", "disconnected", "error"]),
|
|
||||||
}).annotate({ identifier: "WorkspaceEvent.ConnectionStatus" })
|
|
||||||
export interface ConnectionStatus extends Schema.Schema.Type<typeof ConnectionStatus> {}
|
|
||||||
|
|
||||||
export const Ready = Event.ephemeral({
|
|
||||||
type: "workspace.ready",
|
|
||||||
schema: {
|
|
||||||
name: Schema.String,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Failed = Event.ephemeral({
|
|
||||||
type: "workspace.failed",
|
|
||||||
schema: {
|
|
||||||
message: Schema.String,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Status = Event.ephemeral({
|
|
||||||
type: "workspace.status",
|
|
||||||
schema: ConnectionStatus.fields,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Definitions = Event.inventory(Ready, Failed, Status)
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
export * as Workspace from "./workspace.js"
|
export * as Workspace from "./workspace.js"
|
||||||
|
|
||||||
import { WorkspaceEvent } from "./workspace-event.js"
|
|
||||||
import { WorkspaceID } from "./workspace-id.js"
|
import { WorkspaceID } from "./workspace-id.js"
|
||||||
|
|
||||||
export const ID = WorkspaceID
|
export const ID = WorkspaceID
|
||||||
export type ID = WorkspaceID
|
export type ID = WorkspaceID
|
||||||
|
|
||||||
export const Event = WorkspaceEvent
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
Project,
|
Project,
|
||||||
Reference,
|
Reference,
|
||||||
Session,
|
Session,
|
||||||
Workspace,
|
|
||||||
} from "../src/index.js"
|
} from "../src/index.js"
|
||||||
import { EventManifest } from "../src/event-manifest.js"
|
import { EventManifest } from "../src/event-manifest.js"
|
||||||
import { FileSystemV1 } from "../src/filesystem-v1.js"
|
import { FileSystemV1 } from "../src/filesystem-v1.js"
|
||||||
@@ -20,7 +19,6 @@ import { SessionEvent } from "../src/session-event.js"
|
|||||||
import { SessionID } from "../src/session-id.js"
|
import { SessionID } from "../src/session-id.js"
|
||||||
import { SessionMessage } from "../src/session-message.js"
|
import { SessionMessage } from "../src/session-message.js"
|
||||||
import { SessionV1 } from "../src/session-v1.js"
|
import { SessionV1 } from "../src/session-v1.js"
|
||||||
import { WorkspaceEvent } from "../src/workspace-event.js"
|
|
||||||
|
|
||||||
describe("public event manifest", () => {
|
describe("public event manifest", () => {
|
||||||
test("owns the complete public event surface", () => {
|
test("owns the complete public event surface", () => {
|
||||||
@@ -60,8 +58,6 @@ describe("public event manifest", () => {
|
|||||||
test("uses canonical definitions for current public events", () => {
|
test("uses canonical definitions for current public events", () => {
|
||||||
expect(Session.Event).toBe(SessionEvent)
|
expect(Session.Event).toBe(SessionEvent)
|
||||||
expect(Session.Event.Definitions).toBe(SessionEvent.Definitions)
|
expect(Session.Event.Definitions).toBe(SessionEvent.Definitions)
|
||||||
expect(Workspace.Event).toBe(WorkspaceEvent)
|
|
||||||
expect(Workspace.Event.Definitions).toBe(WorkspaceEvent.Definitions)
|
|
||||||
expect(EventManifest.Latest.get("session.step.ended")).toBe(SessionEvent.Step.Ended)
|
expect(EventManifest.Latest.get("session.step.ended")).toBe(SessionEvent.Step.Ended)
|
||||||
expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated)
|
expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated)
|
||||||
expect(EventManifest.Latest.get("project.updated")).toBe(Project.Event.Updated)
|
expect(EventManifest.Latest.get("project.updated")).toBe(Project.Event.Updated)
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ export * as ServerAuth from "./auth"
|
|||||||
|
|
||||||
import { Context, Layer, Option, Redacted } from "effect"
|
import { Context, Layer, Option, Redacted } from "effect"
|
||||||
|
|
||||||
export type Credentials = {
|
|
||||||
password?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DecodedCredentials = {
|
export type DecodedCredentials = {
|
||||||
readonly username: string
|
readonly username: string
|
||||||
readonly password: Redacted.Redacted
|
readonly password: Redacted.Redacted
|
||||||
@@ -37,16 +33,3 @@ export function authorized(credentials: DecodedCredentials, config: Info) {
|
|||||||
Redacted.value(credentials.password) === config.password.value
|
Redacted.value(credentials.password) === config.password.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function header(credentials?: Credentials) {
|
|
||||||
const password = credentials?.password
|
|
||||||
if (!password) return undefined
|
|
||||||
|
|
||||||
return `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function headers(credentials?: Credentials) {
|
|
||||||
const authorization = header(credentials)
|
|
||||||
if (!authorization) return undefined
|
|
||||||
return { Authorization: authorization }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export const LocationHandler = HttpApiBuilder.group(Api, "server.location", (han
|
|||||||
const location = yield* Location.Service
|
const location = yield* Location.Service
|
||||||
return new Location.Info({
|
return new Location.Info({
|
||||||
directory: location.directory,
|
directory: location.directory,
|
||||||
workspaceID: location.workspaceID,
|
|
||||||
project: location.project,
|
project: location.project,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { PtyEnvironment } from "../pty-environment"
|
|||||||
|
|
||||||
const ticketScope = Effect.gen(function* () {
|
const ticketScope = Effect.gen(function* () {
|
||||||
const location = yield* Location.Service
|
const location = yield* Location.Service
|
||||||
return { directory: location.directory as string, workspaceID: location.workspaceID }
|
return { directory: location.directory as string }
|
||||||
})
|
})
|
||||||
|
|
||||||
export const PtyHandler = HttpApiBuilder.group(Api, "server.pty", (handlers) =>
|
export const PtyHandler = HttpApiBuilder.group(Api, "server.pty", (handlers) =>
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|||||||
: ctx.query
|
: ctx.query
|
||||||
const page = yield* session.list({
|
const page = yield* session.list({
|
||||||
...query,
|
...query,
|
||||||
workspaceID: query.workspace,
|
|
||||||
limit: ctx.query.limit ?? DefaultSessionsLimit,
|
limit: ctx.query.limit ?? DefaultSessionsLimit,
|
||||||
})
|
})
|
||||||
const sessions = page.data
|
const sessions = page.data
|
||||||
@@ -213,7 +212,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|||||||
.move({
|
.move({
|
||||||
sessionID: ctx.params.sessionID,
|
sessionID: ctx.params.sessionID,
|
||||||
directory: ctx.payload.directory,
|
directory: ctx.payload.directory,
|
||||||
workspaceID: ctx.payload.workspaceID,
|
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
Effect.catchTag("Session.NotFoundError", (error) =>
|
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Location } from "@opencode-ai/core/location"
|
import { Location } from "@opencode-ai/core/location"
|
||||||
import { LocationServiceMap } from "@opencode-ai/core/location-services"
|
import { LocationServiceMap } from "@opencode-ai/core/location-services"
|
||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||||
import { Workspace } from "@opencode-ai/core/workspace"
|
|
||||||
import { Effect, Layer } from "effect"
|
import { Effect, Layer } from "effect"
|
||||||
import { HttpServerRequest } from "effect/unstable/http"
|
import { HttpServerRequest } from "effect/unstable/http"
|
||||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||||
@@ -18,7 +17,6 @@ export function response<A, E, R>(data: Effect.Effect<A, E, R>) {
|
|||||||
return {
|
return {
|
||||||
location: new Location.Info({
|
location: new Location.Info({
|
||||||
directory: location.directory,
|
directory: location.directory,
|
||||||
workspaceID: location.workspaceID,
|
|
||||||
project: location.project,
|
project: location.project,
|
||||||
}),
|
}),
|
||||||
data: yield* data,
|
data: yield* data,
|
||||||
@@ -28,13 +26,11 @@ export function response<A, E, R>(data: Effect.Effect<A, E, R>) {
|
|||||||
|
|
||||||
export function requestRef(request: HttpServerRequest.HttpServerRequest): Location.Ref {
|
export function requestRef(request: HttpServerRequest.HttpServerRequest): Location.Ref {
|
||||||
const query = new URL(request.url, "http://localhost").searchParams
|
const query = new URL(request.url, "http://localhost").searchParams
|
||||||
const workspaceID = query.get("location[workspace]") || request.headers["x-opencode-workspace"]
|
|
||||||
const directory =
|
const directory =
|
||||||
query.get("location[directory]") ||
|
query.get("location[directory]") ||
|
||||||
(request.headers["x-opencode-directory"] ? decode(request.headers["x-opencode-directory"]) : process.cwd())
|
(request.headers["x-opencode-directory"] ? decode(request.headers["x-opencode-directory"]) : process.cwd())
|
||||||
return Location.Ref.make({
|
return Location.Ref.make({
|
||||||
directory: AbsolutePath.make(directory),
|
directory: AbsolutePath.make(directory),
|
||||||
workspaceID: workspaceID ? Workspace.ID.make(workspaceID) : undefined,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Location } from "@opencode-ai/core/location"
|
|||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||||
import { Session } from "@opencode-ai/core/session"
|
import { Session } from "@opencode-ai/core/session"
|
||||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||||
import { Workspace } from "@opencode-ai/core/workspace"
|
|
||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
import { Effect, Layer, Schema } from "effect"
|
import { Effect, Layer, Schema } from "effect"
|
||||||
import { HttpRouter } from "effect/unstable/http"
|
import { HttpRouter } from "effect/unstable/http"
|
||||||
@@ -40,7 +39,7 @@ export const sessionLocationLayer = Layer.effect(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
const row = yield* db
|
const row = yield* db
|
||||||
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
|
.select({ directory: SessionTable.directory })
|
||||||
.from(SessionTable)
|
.from(SessionTable)
|
||||||
.where(eq(SessionTable.id, sessionID))
|
.where(eq(SessionTable.id, sessionID))
|
||||||
.get()
|
.get()
|
||||||
@@ -56,7 +55,6 @@ export const sessionLocationLayer = Layer.effect(
|
|||||||
locations.get(
|
locations.get(
|
||||||
Location.Ref.make({
|
Location.Ref.make({
|
||||||
directory: AbsolutePath.make(row.directory),
|
directory: AbsolutePath.make(row.directory),
|
||||||
workspaceID: row.workspaceID ? Workspace.ID.make(row.workspaceID) : undefined,
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,7 +7,3 @@ test("accepts only the fixed opencode username", () => {
|
|||||||
expect(ServerAuth.authorized({ username: "opencode", password: Redacted.make("secret") }, config)).toBe(true)
|
expect(ServerAuth.authorized({ username: "opencode", password: Redacted.make("secret") }, config)).toBe(true)
|
||||||
expect(ServerAuth.authorized({ username: "custom", password: Redacted.make("secret") }, config)).toBe(false)
|
expect(ServerAuth.authorized({ username: "custom", password: Redacted.make("secret") }, config)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("encodes the fixed opencode username", () => {
|
|
||||||
expect(ServerAuth.header({ password: "secret" })).toBe(`Basic ${Buffer.from("opencode:secret").toString("base64")}`)
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -36,10 +36,6 @@ export const lineCommentStyles = `
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="line-comment"][data-variant="add"] [data-slot="line-comment-button"] {
|
|
||||||
background: var(--syntax-diff-add);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="line-comment"] [data-component="icon"] {
|
[data-component="line-comment"] [data-component="icon"] {
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useI18n } from "@opencode-ai/ui/context/i18n"
|
|||||||
|
|
||||||
installLineCommentStyles()
|
installLineCommentStyles()
|
||||||
|
|
||||||
export type LineCommentVariant = "default" | "editor" | "add"
|
export type LineCommentVariant = "default" | "editor"
|
||||||
|
|
||||||
function InlineGlyph(props: { icon: "comment" | "plus" }) {
|
function InlineGlyph(props: { icon: "comment" | "plus" }) {
|
||||||
return (
|
return (
|
||||||
@@ -156,25 +156,6 @@ export const LineComment = (props: LineCommentProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LineCommentAddProps = Omit<LineCommentAnchorProps, "children" | "variant" | "open" | "icon"> & {
|
|
||||||
label?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LineCommentAdd = (props: LineCommentAddProps) => {
|
|
||||||
const [split, rest] = splitProps(props, ["label"])
|
|
||||||
const i18n = useI18n()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LineCommentAnchor
|
|
||||||
{...rest}
|
|
||||||
open={false}
|
|
||||||
variant="add"
|
|
||||||
icon="plus"
|
|
||||||
buttonLabel={split.label ?? i18n.t("ui.lineComment.submit")}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LineCommentEditorProps = Omit<LineCommentAnchorProps, "children" | "open" | "variant" | "onClick"> & {
|
export type LineCommentEditorProps = Omit<LineCommentAnchorProps, "children" | "open" | "variant" | "onClick"> & {
|
||||||
value: string
|
value: string
|
||||||
selection: JSX.Element
|
selection: JSX.Element
|
||||||
|
|||||||
@@ -948,10 +948,6 @@ function ExaOutput(props: { output?: string }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerPartComponent(type: string, component: PartComponent) {
|
|
||||||
PART_MAPPING[type] = component
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Message(props: MessageProps) {
|
export function Message(props: MessageProps) {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
|
|||||||
@@ -34,10 +34,6 @@ export function SessionFilePanelV2(props: {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SessionFilePanelV2Title(props: ParentProps) {
|
|
||||||
return <div data-slot="session-review-v2-toolbar-title">{props.children}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SessionFilePanelV2Empty(props: ParentProps) {
|
export function SessionFilePanelV2Empty(props: ParentProps) {
|
||||||
return <div data-slot="session-review-v2-empty">{props.children}</div>
|
return <div data-slot="session-review-v2-empty">{props.children}</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -600,7 +600,6 @@ function App(props: { pair?: DialogPairCredentials }) {
|
|||||||
order: "desc",
|
order: "desc",
|
||||||
parentID: null,
|
parentID: null,
|
||||||
directory: location.directory,
|
directory: location.directory,
|
||||||
workspace: location.workspaceID,
|
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const match = response.data[0]?.id
|
const match = response.data[0]?.id
|
||||||
|
|||||||
@@ -684,7 +684,7 @@ async function disconnected(
|
|||||||
|
|
||||||
function location(data: ReturnType<typeof useData>) {
|
function location(data: ReturnType<typeof useData>) {
|
||||||
const current = data.location.default()
|
const current = data.location.default()
|
||||||
return { directory: current.directory, workspace: current.workspaceID }
|
return { directory: current.directory }
|
||||||
}
|
}
|
||||||
|
|
||||||
function message(cause: unknown) {
|
function message(cause: unknown) {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function DialogMcp() {
|
|||||||
if (!server || server.status.status === "pending") return
|
if (!server || server.status.status === "pending") return
|
||||||
setLoading(name)
|
setLoading(name)
|
||||||
const current = data.location.default()
|
const current = data.location.default()
|
||||||
const input = { server: name, location: { directory: current.directory, workspace: current.workspaceID } }
|
const input = { server: name, location: { directory: current.directory } }
|
||||||
const call = server.status.status === "connected" ? client.api.mcp.disconnect(input) : client.api.mcp.connect(input)
|
const call = server.status.status === "connected" ? client.api.mcp.disconnect(input) : client.api.mcp.connect(input)
|
||||||
void call.catch(toast.error).finally(() => setLoading(null))
|
void call.catch(toast.error).finally(() => setLoading(null))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import { DialogVariant } from "./dialog-variant"
|
|||||||
import * as fuzzysort from "fuzzysort"
|
import * as fuzzysort from "fuzzysort"
|
||||||
import { useConnected } from "./use-connected"
|
import { useConnected } from "./use-connected"
|
||||||
import { useData } from "../context/data"
|
import { useData } from "../context/data"
|
||||||
|
import { modelPreferenceKey } from "../model-preference"
|
||||||
|
|
||||||
export function DialogModel(props: { providerID?: string }) {
|
export function DialogModel(props: { providerID?: string }) {
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const [query, setQuery] = createSignal("")
|
const [query, setQuery] = createSignal("")
|
||||||
|
const favoritePriority = new Set(local.model.favorite().map(modelPreferenceKey))
|
||||||
|
|
||||||
const connected = useConnected()
|
const connected = useConnected()
|
||||||
const providers = createMemo(() => new Map((data.location.provider.list() ?? []).map((item) => [item.id, item])))
|
const providers = createMemo(() => new Map((data.location.provider.list() ?? []).map((item) => [item.id, item])))
|
||||||
@@ -63,14 +65,14 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
.filter((model) => (props.providerID ? model.providerID === props.providerID : true))
|
.filter((model) => (props.providerID ? model.providerID === props.providerID : true))
|
||||||
.map((model) => {
|
.map((model) => {
|
||||||
const provider = providers().get(model.providerID)
|
const provider = providers().get(model.providerID)
|
||||||
const favorite = favorites.some((item) => item.providerID === model.providerID && item.modelID === model.id)
|
const key = modelPreferenceKey({ providerID: model.providerID, modelID: model.id })
|
||||||
|
const favorite = favorites.some((item) => modelPreferenceKey(item) === key)
|
||||||
return {
|
return {
|
||||||
value: { providerID: model.providerID, modelID: model.id },
|
value: { providerID: model.providerID, modelID: model.id },
|
||||||
providerID: model.providerID,
|
providerID: model.providerID,
|
||||||
providerName: provider?.name ?? model.providerID,
|
providerName: provider?.name ?? model.providerID,
|
||||||
title: model.name,
|
title: model.name,
|
||||||
releaseDate: model.time.released,
|
releaseDate: model.time.released,
|
||||||
favorite,
|
|
||||||
description: favorite ? "(Favorite)" : undefined,
|
description: favorite ? "(Favorite)" : undefined,
|
||||||
category: connected() ? (provider?.name ?? model.providerID) : undefined,
|
category: connected() ? (provider?.name ?? model.providerID) : undefined,
|
||||||
footer: free(model) ? "Free" : undefined,
|
footer: free(model) ? "Free" : undefined,
|
||||||
@@ -98,6 +100,7 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
if (needle) {
|
if (needle) {
|
||||||
return prioritizeFavorites(
|
return prioritizeFavorites(
|
||||||
fuzzysort.go(needle, modelOptions, { keys: ["title", "category"] }).map((item) => item.obj),
|
fuzzysort.go(needle, modelOptions, { keys: ["title", "category"] }).map((item) => item.obj),
|
||||||
|
favoritePriority,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,8 +165,13 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prioritizeFavorites<T extends { favorite: boolean }>(options: T[]) {
|
export function prioritizeFavorites<T extends { value: { providerID: string; modelID: string } }>(
|
||||||
return options.toSorted((a, b) => Number(b.favorite) - Number(a.favorite))
|
options: T[],
|
||||||
|
favorites: Set<string>,
|
||||||
|
) {
|
||||||
|
return options.toSorted(
|
||||||
|
(a, b) => Number(favorites.has(modelPreferenceKey(b.value))) - Number(favorites.has(modelPreferenceKey(a.value))),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortModelOptions<
|
export function sortModelOptions<
|
||||||
|
|||||||
@@ -290,7 +290,6 @@ export function Autocomplete(props: {
|
|||||||
limit: 20,
|
limit: 20,
|
||||||
location: {
|
location: {
|
||||||
directory: input.location?.directory,
|
directory: input.location?.directory,
|
||||||
workspace: input.location?.workspaceID ?? data.location.default().workspaceID,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ function location(ref: LocationRef) {
|
|||||||
return {
|
return {
|
||||||
location: {
|
location: {
|
||||||
directory: ref.directory,
|
directory: ref.directory,
|
||||||
workspace: ref.workspaceID,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,7 +151,6 @@ function formRequestOptions(location: LocationRef | undefined) {
|
|||||||
return {
|
return {
|
||||||
headers: {
|
headers: {
|
||||||
"x-opencode-directory": encodeURIComponent(location.directory),
|
"x-opencode-directory": encodeURIComponent(location.directory),
|
||||||
...(location.workspaceID ? { "x-opencode-workspace": location.workspaceID } : {}),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,7 +235,7 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
|||||||
.find({
|
.find({
|
||||||
query,
|
query,
|
||||||
type: "file",
|
type: "file",
|
||||||
location: { directory: state.location.directory, workspace: state.location.workspaceID },
|
location: { directory: state.location.directory },
|
||||||
})
|
})
|
||||||
.then((result) => result.data.map((file) => file.path))
|
.then((result) => result.data.map((file) => file.path))
|
||||||
.catch(() => []),
|
.catch(() => []),
|
||||||
@@ -651,7 +650,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
|||||||
{
|
{
|
||||||
location: {
|
location: {
|
||||||
directory: state.location.directory,
|
directory: state.location.directory,
|
||||||
workspace: state.location.workspaceID,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ signal: attempt.signal },
|
{ signal: attempt.signal },
|
||||||
|
|||||||
@@ -421,7 +421,6 @@ async function resolveSelectedModel(
|
|||||||
? {
|
? {
|
||||||
location: {
|
location: {
|
||||||
directory: input.location.directory,
|
directory: input.location.directory,
|
||||||
workspace: input.location.workspaceID,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -819,7 +818,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||||||
input.location
|
input.location
|
||||||
? client.form.request.list(
|
? client.form.request.list(
|
||||||
{
|
{
|
||||||
location: { directory: input.location.directory, workspace: input.location.workspaceID },
|
location: { directory: input.location.directory },
|
||||||
},
|
},
|
||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user